http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java
index 0000000,0000000..bcd8a07
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java
@@@ -1,0 -1,0 +1,261 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.openshift;
++
++import com.google.common.collect.ImmutableSet;
++import io.fabric8.kubernetes.api.model.Container;
++import io.fabric8.kubernetes.api.model.HasMetadata;
++import io.fabric8.kubernetes.api.model.Namespace;
++import io.fabric8.kubernetes.api.model.Pod;
++import io.fabric8.kubernetes.api.model.PodTemplateSpec;
++import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder;
++import io.fabric8.kubernetes.client.KubernetesClient;
++import io.fabric8.kubernetes.client.KubernetesClientException;
++import io.fabric8.openshift.api.model.DeploymentConfig;
++import io.fabric8.openshift.api.model.DeploymentConfigBuilder;
++import io.fabric8.openshift.api.model.DeploymentConfigStatus;
++import io.fabric8.openshift.api.model.Project;
++import io.fabric8.openshift.api.model.ProjectBuilder;
++import io.fabric8.openshift.client.OpenShiftClient;
++import org.apache.brooklyn.api.entity.Entity;
++import org.apache.brooklyn.api.location.LocationSpec;
++import org.apache.brooklyn.container.entity.openshift.OpenShiftPod;
++import org.apache.brooklyn.container.entity.openshift.OpenShiftResource;
++import 
org.apache.brooklyn.container.location.kubernetes.KubernetesClientRegistry;
++import org.apache.brooklyn.container.location.kubernetes.KubernetesLocation;
++import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesMachineLocation;
++import org.apache.brooklyn.location.ssh.SshMachineLocation;
++import org.apache.brooklyn.util.core.config.ConfigBag;
++import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
++import org.apache.brooklyn.util.net.Networking;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++
++import java.net.InetAddress;
++import java.util.Map;
++
++public class OpenShiftLocation extends KubernetesLocation implements 
OpenShiftLocationConfig {
++
++    public static final String OPENSHIFT_GENERATED_BY = 
"openshift.io/generated-by";
++    private static final Logger LOG = 
LoggerFactory.getLogger(OpenShiftLocation.class);
++    private OpenShiftClient client;
++
++    public OpenShiftLocation() {
++        super();
++    }
++
++    public OpenShiftLocation(Map<?, ?> properties) {
++        super(properties);
++    }
++
++    @Override
++    protected KubernetesClient getClient(ConfigBag config) {
++        if (client == null) {
++            KubernetesClientRegistry registry = 
getConfig(OPENSHIFT_CLIENT_REGISTRY);
++            client = (OpenShiftClient) 
registry.getKubernetesClient(ResolvingConfigBag.newInstanceExtending(getManagementContext(),
 config));
++        }
++        return client;
++    }
++
++    @Override
++    protected boolean handleResourceDelete(String resourceType, String 
resourceName, String namespace) {
++        if (super.handleResourceDelete(resourceType, resourceName, 
namespace)) {
++            return true;
++        }
++
++        try {
++            switch (resourceType) {
++                case OpenShiftResource.DEPLOYMENT_CONFIG:
++                    return 
client.deploymentConfigs().inNamespace(namespace).withName(resourceName).delete();
++                case OpenShiftResource.PROJECT:
++                    return client.projects().withName(resourceName).delete();
++                case OpenShiftResource.TEMPLATE:
++                    return 
client.templates().inNamespace(namespace).withName(resourceName).delete();
++                case OpenShiftResource.BUILD_CONFIG:
++                    return 
client.buildConfigs().inNamespace(namespace).withName(resourceName).delete();
++            }
++        } catch (KubernetesClientException kce) {
++            LOG.warn("Error deleting resource {}: {}", resourceName, kce);
++        }
++        return false;
++    }
++
++    @Override
++    protected boolean findResourceAddress(LocationSpec<? extends 
KubernetesMachineLocation> locationSpec, Entity entity, HasMetadata metadata, 
String resourceType, String resourceName, String namespace) {
++        if (super.findResourceAddress(locationSpec, entity, metadata, 
resourceType, resourceName, namespace)) {
++            return true;
++        }
++
++        if (resourceType.equals(OpenShiftResource.DEPLOYMENT_CONFIG)) {
++            DeploymentConfig deploymentConfig = (DeploymentConfig) metadata;
++            Map<String, String> labels = 
deploymentConfig.getSpec().getTemplate().getMetadata().getLabels();
++            Pod pod = getPod(namespace, labels);
++            entity.sensors().set(OpenShiftPod.KUBERNETES_POD, 
pod.getMetadata().getName());
++
++            InetAddress node = 
Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName());
++            String podAddress = pod.getStatus().getPodIP();
++
++            locationSpec.configure("address", node);
++            locationSpec.configure(SshMachineLocation.PRIVATE_ADDRESSES, 
ImmutableSet.of(podAddress));
++
++            return true;
++        } else {
++            return false;
++        }
++    }
++
++    @Override
++    protected synchronized Namespace createOrGetNamespace(final String name, 
Boolean create) {
++        Project project = client.projects().withName(name).get();
++        ExitCondition projectReady = new ExitCondition() {
++            @Override
++            public Boolean call() {
++                Project actualProject = 
client.projects().withName(name).get();
++                return actualProject != null && 
actualProject.getStatus().getPhase().equals(PHASE_ACTIVE);
++            }
++
++            @Override
++            public String getFailureMessage() {
++                Project actualProject = 
client.projects().withName(name).get();
++                return "Project for " + name + " " + (actualProject == null ? 
"absent" : " status " + actualProject.getStatus());
++            }
++        };
++        if (project != null) {
++            LOG.debug("Found project {}, returning it.", project);
++        } else if (create) {
++            project = client.projects().create(new 
ProjectBuilder().withNewMetadata().withName(name).endMetadata().build());
++            LOG.debug("Created project {}.", project);
++        } else {
++            throw new IllegalStateException("Project " + name + " does not 
exist and namespace.create is not set");
++        }
++        waitForExitCondition(projectReady);
++        return client.namespaces().withName(name).get();
++    }
++
++    @Override
++    protected synchronized void deleteEmptyNamespace(final String name) {
++        if (!name.equals("default") && isNamespaceEmpty(name)) {
++            if (client.projects().withName(name).get() != null &&
++                    
!client.projects().withName(name).get().getStatus().getPhase().equals(PHASE_TERMINATING))
 {
++                client.projects().withName(name).delete();
++                ExitCondition exitCondition = new ExitCondition() {
++                    @Override
++                    public Boolean call() {
++                        return client.projects().withName(name).get() == null;
++                    }
++
++                    @Override
++                    public String getFailureMessage() {
++                        return "Project " + name + " still present";
++                    }
++                };
++                waitForExitCondition(exitCondition);
++            }
++        }
++    }
++
++    @Override
++    protected boolean isNamespaceEmpty(String namespace) {
++        return 
client.deploymentConfigs().inNamespace(namespace).list().getItems().isEmpty() &&
++                
client.services().inNamespace(namespace).list().getItems().isEmpty() &&
++                
client.secrets().inNamespace(namespace).list().getItems().isEmpty();
++    }
++
++    @Override
++    protected void deploy(final String namespace, Entity entity, Map<String, 
String> metadata, final String deploymentName, Container container, final 
Integer replicas, Map<String, String> secrets) {
++        PodTemplateSpecBuilder podTemplateSpecBuilder = new 
PodTemplateSpecBuilder()
++                .withNewMetadata()
++                .addToLabels("name", deploymentName)
++                .addToLabels(metadata)
++                .endMetadata()
++                .withNewSpec()
++                .addToContainers(container)
++                .endSpec();
++        if (secrets != null) {
++            for (String secretName : secrets.keySet()) {
++                podTemplateSpecBuilder.withNewSpec()
++                        .addToContainers(container)
++                        .addNewImagePullSecret(secretName)
++                        .endSpec();
++            }
++        }
++        PodTemplateSpec template = podTemplateSpecBuilder.build();
++        DeploymentConfig deployment = new DeploymentConfigBuilder()
++                .withNewMetadata()
++                .withName(deploymentName)
++                .addToAnnotations(OPENSHIFT_GENERATED_BY, "Apache Brooklyn")
++                .addToAnnotations(CLOUDSOFT_ENTITY_ID, entity.getId())
++                .addToAnnotations(CLOUDSOFT_APPLICATION_ID, 
entity.getApplicationId())
++                .endMetadata()
++                .withNewSpec()
++                .withNewStrategy()
++                .withType("Recreate")
++                .endStrategy()
++                .addNewTrigger()
++                .withType("ConfigChange")
++                .endTrigger()
++                .withReplicas(replicas)
++                .addToSelector("name", deploymentName)
++                .withTemplate(template)
++                .endSpec()
++                .build();
++        client.deploymentConfigs().inNamespace(namespace).create(deployment);
++        ExitCondition exitCondition = new ExitCondition() {
++            @Override
++            public Boolean call() {
++                DeploymentConfig dc = 
client.deploymentConfigs().inNamespace(namespace).withName(deploymentName).get();
++                DeploymentConfigStatus status = (dc == null) ? null : 
dc.getStatus();
++                Integer replicas = (status == null) ? null : 
status.getAvailableReplicas();
++                return replicas != null && replicas.intValue() == replicas;
++            }
++
++            @Override
++            public String getFailureMessage() {
++                DeploymentConfig dc = 
client.deploymentConfigs().inNamespace(namespace).withName(deploymentName).get();
++                DeploymentConfigStatus status = (dc == null) ? null : 
dc.getStatus();
++                return "Namespace=" + namespace + "; deploymentName= " + 
deploymentName + "; Deployment=" + dc + "; status=" + status;
++            }
++        };
++        waitForExitCondition(exitCondition);
++        LOG.debug("Deployed {} to namespace {}.", deployment, namespace);
++    }
++
++    @Override
++    protected String getContainerResourceType() {
++        return OpenShiftResource.DEPLOYMENT_CONFIG;
++    }
++
++    @Override
++    protected void undeploy(final String namespace, final String deployment, 
final String pod) {
++        
client.deploymentConfigs().inNamespace(namespace).withName(deployment).delete();
++        ExitCondition exitCondition = new ExitCondition() {
++            @Override
++            public Boolean call() {
++                return 
client.deploymentConfigs().inNamespace(namespace).withName(deployment).get() == 
null;
++            }
++
++            @Override
++            public String getFailureMessage() {
++                return "No deployment with namespace=" + namespace + ", 
deployment=" + deployment;
++            }
++        };
++        waitForExitCondition(exitCondition);
++    }
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationConfig.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationConfig.java
index 0000000,0000000..e089b93
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationConfig.java
@@@ -1,0 -1,0 +1,32 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.openshift;
++
++import org.apache.brooklyn.config.ConfigKey;
++import 
org.apache.brooklyn.container.location.kubernetes.KubernetesClientRegistry;
++import org.apache.brooklyn.core.config.ConfigKeys;
++
++public interface OpenShiftLocationConfig {
++
++    ConfigKey<KubernetesClientRegistry> OPENSHIFT_CLIENT_REGISTRY = 
ConfigKeys.newConfigKey(
++            KubernetesClientRegistry.class, "openShiftClientRegistry",
++            "Registry/Factory for creating OpenShift client; default is 
almost always fine, " +
++                    "except where tests want to customize behaviour", 
OpenShiftClientRegistryImpl.INSTANCE);
++}
++

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolver.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolver.java
index 0000000,0000000..3dc129f
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolver.java
@@@ -1,0 -1,0 +1,65 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.openshift;
++
++import org.apache.brooklyn.api.location.Location;
++import org.apache.brooklyn.api.location.LocationResolver;
++import org.apache.brooklyn.core.location.AbstractLocationResolver;
++import org.apache.brooklyn.core.location.LocationConfigUtils;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++
++/**
++ * Locations starting with the given prefix (@code "openshift") will use this 
resolver, to instantiate
++ * a {@link OpenShiftLocation}.
++ * <p>
++ * We ensure that config will be picked up from brooklyn.properties using the 
appropriate precedence:
++ * <ol>
++ * <li>named location config
++ * <li>Prefix {@code brooklyn.location.openshift.}
++ * <li>Prefix {@code brooklyn.openshift.}
++ * </ol>
++ */
++public class OpenShiftLocationResolver extends AbstractLocationResolver 
implements LocationResolver {
++
++    public static final Logger log = 
LoggerFactory.getLogger(OpenShiftLocationResolver.class);
++
++    public static final String PREFIX = "openshift";
++
++    @Override
++    public boolean isEnabled() {
++        return LocationConfigUtils.isResolverPrefixEnabled(managementContext, 
getPrefix());
++    }
++
++    @Override
++    public String getPrefix() {
++        return PREFIX;
++    }
++
++    @Override
++    protected Class<? extends Location> getLocationType() {
++        return OpenShiftLocation.class;
++    }
++
++    @Override
++    protected SpecParser getSpecParser() {
++        return new SpecParser(getPrefix()).setExampleUsage("\"openshift\"");
++    }
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/resources/META-INF/services/org.apache.brooklyn.api.location.LocationResolver
----------------------------------------------------------------------
diff --cc 
locations/container/src/main/resources/META-INF/services/org.apache.brooklyn.api.location.LocationResolver
index 0000000,0000000..25d27bb
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/main/resources/META-INF/services/org.apache.brooklyn.api.location.LocationResolver
@@@ -1,0 -1,0 +1,21 @@@
++#
++# Licensed to the Apache Software Foundation (ASF) under one
++# or more contributor license agreements.  See the NOTICE file
++# distributed with this work for additional information
++# regarding copyright ownership.  The ASF licenses this file
++# to you under the Apache License, Version 2.0 (the
++# "License"); you may not use this file except in compliance
++# with the License.  You may obtain a copy of the License at
++#
++#  http://www.apache.org/licenses/LICENSE-2.0
++#
++# Unless required by applicable law or agreed to in writing,
++# software distributed under the License is distributed on an
++# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++# KIND, either express or implied.  See the License for the
++# specific language governing permissions and limitations
++# under the License.
++#
++org.apache.brooklyn.container.location.docker.DockerLocationResolver
++org.apache.brooklyn.container.location.kubernetes.KubernetesLocationResolver
++org.apache.brooklyn.container.location.openshift.OpenShiftLocationResolver

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/resources/OSGI-INF/blueprint/blueprint.xml
----------------------------------------------------------------------
diff --cc 
locations/container/src/main/resources/OSGI-INF/blueprint/blueprint.xml
index 0000000,0000000..a831291
new file mode 100644
--- /dev/null
+++ b/locations/container/src/main/resources/OSGI-INF/blueprint/blueprint.xml
@@@ -1,0 -1,0 +1,37 @@@
++<?xml version="1.0" encoding="UTF-8"?>
++<!--
++Copyright 2015 The Apache Software Foundation.
++
++Licensed under the Apache License, Version 2.0 (the "License");
++you may not use this file except in compliance with the License.
++You may obtain a copy of the License at
++
++     http://www.apache.org/licenses/LICENSE-2.0
++
++Unless required by applicable law or agreed to in writing, software
++distributed under the License is distributed on an "AS IS" BASIS,
++WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++See the License for the specific language governing permissions and
++limitations under the License.
++-->
++<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
++           xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0";
++           xsi:schemaLocation="
++           http://www.osgi.org/xmlns/blueprint/v1.0.0 
http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd";>
++
++
++    <bean id="dockerLocationResolver" scope="prototype"
++          
class="org.apache.brooklyn.container.location.docker.DockerLocationResolver"/>
++    <bean id="kubernetesLocationResolver" scope="prototype"
++          
class="org.apache.brooklyn.container.location.kubernetes.KubernetesLocationResolver"/>
++    <bean id="openshiftLocationResolver" scope="prototype"
++          
class="org.apache.brooklyn.container.location.openshift.OpenShiftLocationResolver"/>
++
++    <service id="dockerLocationResolverService" ref="dockerLocationResolver"
++             interface="org.apache.brooklyn.api.location.LocationResolver"/>
++    <service id="kubernetesLocationResolverService" 
ref="kubernetesLocationResolver"
++             interface="org.apache.brooklyn.api.location.LocationResolver"/>
++    <service id="openshiftLocationResolverService" 
ref="openshiftLocationResolver"
++             interface="org.apache.brooklyn.api.location.LocationResolver"/>
++
++</blueprint>

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocationLiveTest.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocationLiveTest.java
index 0000000,0000000..dcb3324
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocationLiveTest.java
@@@ -1,0 -1,0 +1,265 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.docker;
++
++import com.google.common.base.Optional;
++import com.google.common.collect.ImmutableList;
++import com.google.common.collect.ImmutableMap;
++import com.google.common.collect.Lists;
++import org.apache.brooklyn.api.location.MachineLocation;
++import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
++import org.apache.brooklyn.location.jclouds.BasicJcloudsLocationCustomizer;
++import org.apache.brooklyn.location.jclouds.JcloudsLocation;
++import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig;
++import org.apache.brooklyn.location.jclouds.JcloudsLocationCustomizer;
++import org.apache.brooklyn.location.jclouds.JcloudsSshMachineLocation;
++import org.apache.brooklyn.util.collections.MutableMap;
++import org.apache.brooklyn.util.os.Os;
++import org.jclouds.compute.ComputeService;
++import org.jclouds.compute.domain.Image;
++import org.jclouds.compute.domain.OsFamily;
++import org.jclouds.compute.options.TemplateOptions;
++import org.jclouds.docker.compute.options.DockerTemplateOptions;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++import org.testng.annotations.AfterMethod;
++import org.testng.annotations.BeforeMethod;
++import org.testng.annotations.Test;
++
++import java.lang.reflect.Method;
++import java.util.List;
++import java.util.Map;
++
++import static com.google.common.base.Preconditions.checkNotNull;
++import static org.testng.Assert.*;
++
++/**
++ * Assumes that a pre-existing swarm endpoint is available. See system 
properties and the defaults
++ * below.
++ */
++public class DockerJcloudsLocationLiveTest extends BrooklynAppLiveTestSupport 
{
++
++    private static final Logger LOG = 
LoggerFactory.getLogger(DockerJcloudsLocationLiveTest.class);
++
++    private static final String SWARM_ENDPOINT = 
System.getProperty("test.brooklyn-container-service.docker.swarmEndpoint", 
"https://10.104.0.162:3376/";);
++    private static final String IDENTITY_FILE_PATH = 
System.getProperty("test.brooklyn-container-service.docker.identity", 
Os.tidyPath("~/.docker/.certs/cert.pem"));
++    private static final String CREDENTIAL_FILE_PATH = 
System.getProperty("test.brooklyn-container-service.docker.credential", 
Os.tidyPath("~/.docker/.certs/key.pem"));
++    private static final String SWARM_NETWORK_NAME = 
System.getProperty("test.brooklyn-container-service.docker.networkName", 
Os.tidyPath("brooklyn"));
++
++    protected DockerJcloudsLocation loc;
++    protected List<MachineLocation> machines;
++    protected DockerTemplateOptions templateOptions;
++
++    @BeforeMethod(alwaysRun = true)
++    @Override
++    public void setUp() throws Exception {
++        super.setUp();
++        machines = Lists.newCopyOnWriteArrayList();
++    }
++
++    @AfterMethod(alwaysRun = true)
++    @Override
++    public void tearDown() throws Exception {
++        for (MachineLocation machine : machines) {
++            try {
++                loc.release(machine);
++            } catch (Exception e) {
++                LOG.error("Error releasing machine " + machine + " in 
location " + loc, e);
++            }
++        }
++        super.tearDown();
++    }
++
++    protected DockerJcloudsLocation newDockerLocation(Map<String, ?> flags) 
throws Exception {
++        JcloudsLocationCustomizer locationCustomizer = new 
BasicJcloudsLocationCustomizer() {
++            @Override
++            public void customize(JcloudsLocation location, ComputeService 
computeService, TemplateOptions templateOptions) {
++                DockerJcloudsLocationLiveTest.this.templateOptions = 
(DockerTemplateOptions) templateOptions;
++            }
++        };
++        Map<String, ?> templateOptionsOverrides = (Map<String, ?>) 
flags.get(JcloudsLocation.TEMPLATE_OPTIONS.getName());
++        Map<String, ?> templateOptions = MutableMap.<String, Object>builder()
++                .put("networkMode", SWARM_NETWORK_NAME)
++                .putAll(templateOptionsOverrides != null ? 
templateOptionsOverrides : ImmutableMap.<String, Object>of())
++                .build();
++        Map<String, ?> allFlags = MutableMap.<String, Object>builder()
++                .put("identity", IDENTITY_FILE_PATH)
++                .put("credential", CREDENTIAL_FILE_PATH)
++                .put("endpoint", SWARM_ENDPOINT)
++                .put("tags", ImmutableList.of(getClass().getName()))
++                .put(JcloudsLocation.WAIT_FOR_SSHABLE.getName(), false)
++                .put(JcloudsLocation.JCLOUDS_LOCATION_CUSTOMIZERS.getName(), 
ImmutableList.of(locationCustomizer))
++                .putAll(flags)
++                .put(JcloudsLocation.TEMPLATE_OPTIONS.getName(), 
templateOptions)
++                .build();
++        return (DockerJcloudsLocation) 
mgmt.getLocationRegistry().getLocationManaged("docker", allFlags);
++    }
++
++    private JcloudsSshMachineLocation newDockerMachine(DockerJcloudsLocation 
loc, Map<?, ?> flags) throws Exception {
++        MachineLocation result = loc.obtain(flags);
++        machines.add(result);
++        return (JcloudsSshMachineLocation) result;
++    }
++
++    @Test(groups = {"Live", "Live-sanity"})
++    public void testDefaultImageHasAutoGeneratedCredentials() throws 
Exception {
++        loc = newDockerLocation(ImmutableMap.<String, Object>of());
++        JcloudsSshMachineLocation machine = newDockerMachine(loc, 
ImmutableMap.<String, Object>of(
++                JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m"));
++
++        assertMachineSshableSecureAndFromImage(machine, "cloudsoft/centos:7");
++    }
++
++    @Test(groups = {"Live", "Live-sanity"})
++    public void testExplicitCredentialsNotOverwritten() throws Exception {
++        loc = newDockerLocation(ImmutableMap.<String, Object>of());
++        JcloudsSshMachineLocation machine = newDockerMachine(loc, 
MutableMap.of(
++                JcloudsLocationConfig.LOGIN_USER, "myuser",
++                JcloudsLocationConfig.LOGIN_USER_PASSWORD, "mypassword"));
++        Image image = getOptionalImage(machine).get();
++        assertEquals(image.getDescription(), "cloudsoft/centos:7");
++        assertEquals(templateOptions.getLoginUser(), "myuser");
++        assertEquals(templateOptions.getLoginPassword(), "mypassword");
++        assertEquals(templateOptions.getLoginPassword(), "mypassword");
++        assertEnvNotContainsKey(templateOptions, "CLOUDSOFT_ROOT_PASSWORD");
++    }
++
++    @Test(groups = {"Live", "Live-sanity"})
++    public void testExplicitImageIdNotOverwritten() throws Exception {
++        // TODO This id will likely change sometimes; once CCS-29 is done, 
then use an image name.
++        // Assumes we have executed:
++        //     docker run ubuntu /bin/echo 'Hello world'
++        // which will have downloaded the ubuntu image with the given id.
++        String imageId = 
"sha256:2fa927b5cdd31cdec0027ff4f45ef4343795c7a2d19a9af4f32425132a222330";
++        loc = newDockerLocation(ImmutableMap.<String, Object>of());
++        JcloudsSshMachineLocation machine = newDockerMachine(loc, 
MutableMap.of(
++                JcloudsLocation.IMAGE_ID, imageId,
++                JcloudsLocation.TEMPLATE_OPTIONS, ImmutableMap.of(
++                        "entrypoint", ImmutableList.of("/bin/sleep", 
"1000"))));
++        Image image = getOptionalImage(machine).get();
++        assertEquals(image.getId(), imageId);
++    }
++
++    @Test(groups = {"Live", "Live-sanity"})
++    public void testMatchingImageDescriptionHasAutoGeneratedCredentials() 
throws Exception {
++        loc = newDockerLocation(ImmutableMap.<String, Object>of());
++        JcloudsSshMachineLocation machine = newDockerMachine(loc, 
ImmutableMap.<String, Object>of(
++                JcloudsLocation.IMAGE_DESCRIPTION_REGEX.getName(), 
"cloudsoft/centos:7",
++                JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m"));
++
++        assertTrue(machine.isSshable(), "machine=" + machine);
++    }
++
++    @Test(groups = {"Live", "Live-sanity"})
++    public void testMatchingOsFamilyCentosHasAutoGeneratedCredentials() 
throws Exception {
++        loc = newDockerLocation(ImmutableMap.<String, Object>of());
++        JcloudsSshMachineLocation machine = newDockerMachine(loc, 
ImmutableMap.<String, Object>of(
++                JcloudsLocation.OS_FAMILY.getName(), OsFamily.CENTOS,
++                JcloudsLocation.OS_VERSION_REGEX.getName(), "7.*",
++                JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m"));
++
++        assertMachineSshableSecureAndFromImage(machine, "cloudsoft/centos:7");
++    }
++
++    @Test(groups = {"Live", "Live-sanity"})
++    public void testMatchingOsFamilyUbuntu14HasAutoGeneratedCredentials() 
throws Exception {
++        loc = newDockerLocation(ImmutableMap.<String, Object>of());
++        JcloudsSshMachineLocation machine = newDockerMachine(loc, 
ImmutableMap.<String, Object>of(
++                JcloudsLocation.OS_FAMILY.getName(), OsFamily.UBUNTU,
++                JcloudsLocation.OS_VERSION_REGEX.getName(), "14.04.*",
++                JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m"));
++
++        assertMachineSshableSecureAndFromImage(machine, 
"cloudsoft/ubuntu:14.04");
++    }
++
++    @Test(groups = {"Live", "Live-sanity"})
++    public void testMatchingOsFamilyUbuntu16HasAutoGeneratedCredentials() 
throws Exception {
++        loc = newDockerLocation(ImmutableMap.<String, Object>of());
++        JcloudsSshMachineLocation machine = newDockerMachine(loc, 
ImmutableMap.<String, Object>of(
++                JcloudsLocation.OS_FAMILY.getName(), OsFamily.UBUNTU,
++                JcloudsLocation.OS_VERSION_REGEX.getName(), "16.04.*",
++                JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m"));
++
++        assertMachineSshableSecureAndFromImage(machine, 
"cloudsoft/ubuntu:16.04");
++    }
++
++    @Test(groups = {"Live", "Live-sanity"})
++    public void 
testMatchingOsFamilyConfiguredOnLocationHasAutoGeneratedCredentials() throws 
Exception {
++        loc = newDockerLocation(ImmutableMap.<String, Object>of(
++                JcloudsLocation.OS_FAMILY.getName(), OsFamily.UBUNTU,
++                JcloudsLocation.OS_VERSION_REGEX.getName(), "16.04.*",
++                JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m"));
++        JcloudsSshMachineLocation machine = newDockerMachine(loc, 
ImmutableMap.<String, Object>of());
++
++        assertMachineSshableSecureAndFromImage(machine, 
"cloudsoft/ubuntu:16.04");
++    }
++
++    protected void 
assertMachineSshableSecureAndFromImage(JcloudsSshMachineLocation machine, 
String expectedImageDescription) throws Exception {
++        Image image = getOptionalImage(machine).get();
++        assertEquals(image.getDescription(), expectedImageDescription);
++        assertEquals(templateOptions.getLoginUser(), "root");
++        assertEnvContainsKeyValue(templateOptions, "CLOUDSOFT_ROOT_PASSWORD", 
templateOptions.getLoginPassword());
++        assertPasswordIsSecure(templateOptions.getLoginPassword());
++
++        assertTrue(machine.isSshable(), "machine=" + machine);
++    }
++
++    protected void assertEnvNotContainsKey(DockerTemplateOptions 
templateOptions, String key) {
++        List<String> env = templateOptions.getEnv();
++        if (env == null) return;
++        for (String keyval : env) {
++            if (keyval.startsWith(key + "=")) {
++                fail("has key " + key + "; env=" + env);
++            }
++        }
++    }
++
++    protected void assertEnvContainsKeyValue(DockerTemplateOptions 
templateOptions, String key, String value) {
++        String keyval = key + "=" + value;
++        List<String> env = templateOptions.getEnv();
++        if (env == null) {
++            fail("env is null; does not contain " + keyval);
++        }
++        if (!env.contains(keyval)) {
++            fail("env does not contain " + keyval + "; env=" + env);
++        }
++    }
++
++    protected void assertPasswordIsSecure(String val) {
++        if (!val.matches(".*[0-9].*")) {
++            fail("Password '" + val + "' does not contain a digit");
++        }
++        if (!val.matches(".*[A-Z].*")) {
++            fail("Password '" + val + "' does not contain an upper-case 
letter");
++        }
++        if (val.trim().length() < 7) {
++            fail("Password '" + val + "' is too short");
++        }
++
++        LOG.debug("Password '" + val + "' passes basic security check");
++    }
++
++    @SuppressWarnings("unchecked")
++    protected Optional<Image> getOptionalImage(JcloudsSshMachineLocation 
machine) throws Exception {
++        Method method = 
machine.getClass().getDeclaredMethod("getOptionalImage");
++        method.setAccessible(true);
++        Optional<Image> result = (Optional<Image>) method.invoke(machine);
++        return checkNotNull(result, "null must not be returned by 
getOptionalImage, for %s", machine);
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerLocationResolverTest.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerLocationResolverTest.java
index 0000000,0000000..117d9d9
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerLocationResolverTest.java
@@@ -1,0 -1,0 +1,118 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.docker;
++
++import org.apache.brooklyn.api.location.LocationSpec;
++import org.apache.brooklyn.core.internal.BrooklynProperties;
++import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++import org.testng.annotations.BeforeMethod;
++import org.testng.annotations.Test;
++
++import java.util.Map;
++
++import static org.testng.Assert.assertEquals;
++import static org.testng.Assert.assertTrue;
++
++public class DockerLocationResolverTest extends BrooklynMgmtUnitTestSupport {
++
++    @SuppressWarnings("unused")
++    private static final Logger log = 
LoggerFactory.getLogger(DockerLocationResolverTest.class);
++
++    private BrooklynProperties brooklynProperties;
++
++    @BeforeMethod(alwaysRun = true)
++    @Override
++    public void setUp() throws Exception {
++        super.setUp();
++        brooklynProperties = mgmt.getBrooklynProperties();
++
++        brooklynProperties.put("brooklyn.location.docker.identity", 
"docker-id");
++        brooklynProperties.put("brooklyn.location.docker.credential", 
"docker-cred");
++    }
++
++    @Test
++    public void testGivesCorrectLocationType() {
++        LocationSpec<?> spec = getLocationSpec("docker");
++        assertEquals(spec.getType(), DockerJcloudsLocation.class);
++
++        DockerJcloudsLocation loc = resolve("docker");
++        assertTrue(loc instanceof DockerJcloudsLocation, "loc=" + loc);
++    }
++
++    @Test
++    public void testParametersInSpecString() {
++        DockerJcloudsLocation loc = 
resolve("docker(loginUser=myLoginUser,imageId=myImageId)");
++        assertEquals(loc.getConfig(DockerJcloudsLocation.LOGIN_USER), 
"myLoginUser");
++        assertEquals(loc.getConfig(DockerJcloudsLocation.IMAGE_ID), 
"myImageId");
++    }
++
++    @Test
++    public void testTakesDotSeparateProperty() {
++        brooklynProperties.put("brooklyn.location.docker.loginUser", 
"myLoginUser");
++        DockerJcloudsLocation loc = resolve("docker");
++        assertEquals(loc.getConfig(DockerJcloudsLocation.LOGIN_USER), 
"myLoginUser");
++    }
++
++    @Test
++    public void testPropertiesPrecedence() {
++        // prefer those in "spec" over everything else
++        brooklynProperties.put("brooklyn.location.named.mydocker", 
"docker:(loginUser=\"loginUser-inSpec\")");
++
++        brooklynProperties.put("brooklyn.location.named.mydocker.loginUser", 
"loginUser-inNamed");
++        brooklynProperties.put("brooklyn.location.docker.loginUser", 
"loginUser-inDocker");
++        brooklynProperties.put("brooklyn.location.jclouds.docker.loginUser", 
"loginUser-inJcloudsProviderSpecific");
++        brooklynProperties.put("brooklyn.location.jclouds.loginUser", 
"loginUser-inJcloudsGeneric");
++
++        // prefer those in "named" over everything else
++        
brooklynProperties.put("brooklyn.location.named.mydocker.privateKeyFile", 
"privateKeyFile-inNamed");
++        brooklynProperties.put("brooklyn.location.docker.privateKeyFile", 
"privateKeyFile-inDocker");
++        
brooklynProperties.put("brooklyn.location.jclouds.docker.privateKeyFile", 
"privateKeyFile-inJcloudsProviderSpecific");
++        brooklynProperties.put("brooklyn.location.jclouds.privateKeyFile", 
"privateKeyFile-inJcloudsGeneric");
++
++        // prefer those in docker-specific
++        brooklynProperties.put("brooklyn.location.docker.publicKeyFile", 
"publicKeyFile-inDocker");
++        
brooklynProperties.put("brooklyn.location.jclouds.docker.publicKeyFile", 
"publicKeyFile-inJcloudsProviderSpecific");
++        brooklynProperties.put("brooklyn.location.jclouds.publicKeyFile", 
"publicKeyFile-inJcloudsGeneric");
++
++        // prefer those in jclouds provider-specific
++        
brooklynProperties.put("brooklyn.location.jclouds.docker.privateKeyPassphrase", 
"privateKeyPassphrase-inJcloudsProviderSpecific");
++        
brooklynProperties.put("brooklyn.location.jclouds.privateKeyPassphrase", 
"privateKeyPassphrase-inJcloudsGeneric");
++
++        // accept those in jclouds generic
++        brooklynProperties.put("brooklyn.location.jclouds.privateKeyData", 
"privateKeyData-inJcloudsGeneric");
++
++        Map<String, Object> conf = 
resolve("named:mydocker").config().getBag().getAllConfig();
++
++        assertEquals(conf.get("loginUser"), "loginUser-inSpec");
++        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
++        assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inDocker");
++        assertEquals(conf.get("privateKeyPassphrase"), 
"privateKeyPassphrase-inJcloudsProviderSpecific");
++        assertEquals(conf.get("privateKeyData"), 
"privateKeyData-inJcloudsGeneric");
++    }
++
++    private LocationSpec<?> getLocationSpec(String spec) {
++        return mgmt.getLocationRegistry().getLocationSpec(spec).get();
++    }
++
++    private DockerJcloudsLocation resolve(String spec) {
++        return (DockerJcloudsLocation) 
mgmt.getLocationRegistry().getLocationManaged(spec);
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/ImageChooserTest.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/ImageChooserTest.java
index 0000000,0000000..cb041aa
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/ImageChooserTest.java
@@@ -1,0 -1,0 +1,85 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.kubernetes;
++
++import org.testng.annotations.BeforeMethod;
++import org.testng.annotations.Test;
++
++import static org.testng.Assert.assertEquals;
++import static org.testng.Assert.assertFalse;
++
++public class ImageChooserTest {
++
++    private ImageChooser chooser;
++
++    @BeforeMethod(alwaysRun = true)
++    public void setUp() {
++        chooser = new ImageChooser();
++    }
++
++    @Test
++    public void testDefault() throws Exception {
++        assertEquals(chooser.chooseImage((String) null, null).get(), 
"cloudsoft/centos:7");
++    }
++
++    @Test
++    public void testCentos() throws Exception {
++        assertEquals(chooser.chooseImage("cEnToS", null).get(), 
"cloudsoft/centos:7");
++    }
++
++    @Test
++    public void testCentos7() throws Exception {
++        assertEquals(chooser.chooseImage("cEnToS", "7").get(), 
"cloudsoft/centos:7");
++    }
++
++    @Test
++    public void testUbnutu() throws Exception {
++        assertEquals(chooser.chooseImage("uBuNtU", null).get(), 
"cloudsoft/ubuntu:14.04");
++    }
++
++    @Test
++    public void testUbnutu14() throws Exception {
++        assertEquals(chooser.chooseImage("uBuNtU", "14.*").get(), 
"cloudsoft/ubuntu:14.04");
++    }
++
++    @Test
++    public void testUbnutu16() throws Exception {
++        assertEquals(chooser.chooseImage("uBuNtU", "16.*").get(), 
"cloudsoft/ubuntu:16.04");
++    }
++
++    @Test
++    public void testAbsentForCentos6() throws Exception {
++        assertFalse(chooser.chooseImage("cEnToS", "6").isPresent());
++    }
++
++    @Test
++    public void testAbsentForUbuntu15() throws Exception {
++        assertFalse(chooser.chooseImage("uBuNtU", "15").isPresent());
++    }
++
++    @Test
++    public void testAbsentForDebian() throws Exception {
++        assertFalse(chooser.chooseImage("debian", null).isPresent());
++    }
++
++    @Test
++    public void testAbsentForWrongOsFamily() throws Exception {
++        assertFalse(chooser.chooseImage("weirdOsFamily", null).isPresent());
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCertsTest.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCertsTest.java
index 0000000,0000000..b216db6
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCertsTest.java
@@@ -1,0 -1,0 +1,162 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.kubernetes;
++
++import ch.qos.logback.classic.spi.ILoggingEvent;
++import com.google.common.base.Charsets;
++import com.google.common.base.Predicate;
++import com.google.common.collect.ImmutableMap;
++import com.google.common.collect.Lists;
++import com.google.common.io.Files;
++import org.apache.brooklyn.test.Asserts;
++import org.apache.brooklyn.test.LogWatcher;
++import org.apache.brooklyn.test.LogWatcher.EventPredicates;
++import org.apache.brooklyn.util.core.config.ConfigBag;
++import org.apache.brooklyn.util.text.Identifiers;
++import org.testng.annotations.AfterMethod;
++import org.testng.annotations.BeforeMethod;
++import org.testng.annotations.Test;
++
++import java.io.File;
++import java.util.List;
++
++import static org.testng.Assert.assertEquals;
++import static org.testng.Assert.assertFalse;
++
++public class KubernetesCertsTest {
++
++    private List<File> tempFiles;
++
++    @BeforeMethod(alwaysRun = true)
++    public void setUp() throws Exception {
++        tempFiles = Lists.newArrayList();
++    }
++
++    @AfterMethod(alwaysRun = true)
++    public void tearDown() throws Exception {
++        if (tempFiles != null) {
++            for (File tempFile : tempFiles) {
++                tempFile.delete();
++            }
++        }
++    }
++
++    @Test
++    public void testCertsAbsent() throws Exception {
++        ConfigBag config = ConfigBag.newInstance();
++        KubernetesCerts certs = new KubernetesCerts(config);
++
++        assertFalse(certs.caCertData.isPresent());
++        assertFalse(certs.clientCertData.isPresent());
++        assertFalse(certs.clientKeyData.isPresent());
++        assertFalse(certs.clientKeyAlgo.isPresent());
++        assertFalse(certs.clientKeyPassphrase.isPresent());
++    }
++
++    @Test
++    public void testCertsFromData() throws Exception {
++        ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder()
++                .put(KubernetesLocationConfig.CA_CERT_DATA, "myCaCertData")
++                .put(KubernetesLocationConfig.CLIENT_CERT_DATA, 
"myClientCertData")
++                .put(KubernetesLocationConfig.CLIENT_KEY_DATA, 
"myClientKeyData")
++                .put(KubernetesLocationConfig.CLIENT_KEY_ALGO, 
"myClientKeyAlgo")
++                .put(KubernetesLocationConfig.CLIENT_KEY_PASSPHRASE, 
"myClientKeyPassphrase")
++                .build());
++        KubernetesCerts certs = new KubernetesCerts(config);
++
++        assertEquals(certs.caCertData.get(), "myCaCertData");
++        assertEquals(certs.clientCertData.get(), "myClientCertData");
++        assertEquals(certs.clientKeyData.get(), "myClientKeyData");
++        assertEquals(certs.clientKeyAlgo.get(), "myClientKeyAlgo");
++        assertEquals(certs.clientKeyPassphrase.get(), 
"myClientKeyPassphrase");
++    }
++
++    @Test
++    public void testCertsFromFile() throws Exception {
++        ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder()
++                .put(KubernetesLocationConfig.CA_CERT_FILE, 
newTempFile("myCaCertData").getAbsolutePath())
++                .put(KubernetesLocationConfig.CLIENT_CERT_FILE, 
newTempFile("myClientCertData").getAbsolutePath())
++                .put(KubernetesLocationConfig.CLIENT_KEY_FILE, 
newTempFile("myClientKeyData").getAbsolutePath())
++                .build());
++        KubernetesCerts certs = new KubernetesCerts(config);
++
++        assertEquals(certs.caCertData.get(), "myCaCertData");
++        assertEquals(certs.clientCertData.get(), "myClientCertData");
++        assertEquals(certs.clientKeyData.get(), "myClientKeyData");
++    }
++
++    @Test
++    public void testCertsFailsIfConflictingConfig() throws Exception {
++        ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder()
++                .put(KubernetesLocationConfig.CA_CERT_DATA, "myCaCertData")
++                .put(KubernetesLocationConfig.CA_CERT_FILE, 
newTempFile("differentCaCertData").getAbsolutePath())
++                .build());
++        try {
++            new KubernetesCerts(config);
++            Asserts.shouldHaveFailedPreviously();
++        } catch (Exception e) {
++            Asserts.expectedFailureContains(e, "Duplicate conflicting 
configuration for caCertData and caCertFile");
++        }
++    }
++
++    @Test
++    public void testCertsWarnsIfConflictingConfig() throws Exception {
++        ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder()
++                .put(KubernetesLocationConfig.CA_CERT_DATA, "myCaCertData")
++                .put(KubernetesLocationConfig.CA_CERT_FILE, 
newTempFile("myCaCertData").getAbsolutePath())
++                .build());
++
++        String loggerName = KubernetesCerts.class.getName();
++        ch.qos.logback.classic.Level logLevel = 
ch.qos.logback.classic.Level.WARN;
++        Predicate<ILoggingEvent> filter = 
EventPredicates.containsMessage("Duplicate (matching) configuration for "
++                + "caCertData and caCertFile (continuing)");
++        LogWatcher watcher = new LogWatcher(loggerName, logLevel, filter);
++
++        watcher.start();
++        KubernetesCerts certs;
++        try {
++            certs = new KubernetesCerts(config);
++            watcher.assertHasEvent();
++        } finally {
++            watcher.close();
++        }
++
++        assertEquals(certs.caCertData.get(), "myCaCertData");
++    }
++
++    @Test
++    public void testCertsFailsIfFileNotFound() throws Exception {
++        ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder()
++                .put(KubernetesLocationConfig.CA_CERT_FILE, 
"/path/to/fileDoesNotExist-" + Identifiers.makeRandomId(8))
++                .build());
++        try {
++            new KubernetesCerts(config);
++            Asserts.shouldHaveFailedPreviously();
++        } catch (Exception e) {
++            Asserts.expectedFailureContains(e, "not found on classpath or 
filesystem");
++        }
++    }
++
++    private File newTempFile(String contents) throws Exception {
++        File file = File.createTempFile("KubernetesCertsTest", ".txt");
++        tempFiles.add(file);
++        Files.write(contents, file, Charsets.UTF_8);
++        return file;
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java
index 0000000,0000000..6a30650
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java
@@@ -1,0 -1,0 +1,235 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.kubernetes;
++
++import com.google.common.collect.ImmutableList;
++import com.google.common.collect.ImmutableMap;
++import com.google.common.collect.Lists;
++import com.google.common.net.HostAndPort;
++import org.apache.brooklyn.api.location.MachineDetails;
++import org.apache.brooklyn.api.location.OsDetails;
++import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesMachineLocation;
++import 
org.apache.brooklyn.container.location.kubernetes.machine.KubernetesSshMachineLocation;
++import org.apache.brooklyn.core.location.BasicMachineDetails;
++import org.apache.brooklyn.core.location.LocationConfigKeys;
++import org.apache.brooklyn.core.location.access.PortForwardManager;
++import 
org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
++import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport;
++import org.apache.brooklyn.location.ssh.SshMachineLocation;
++import org.apache.brooklyn.test.Asserts;
++import org.apache.brooklyn.util.collections.MutableMap;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++import org.testng.annotations.AfterMethod;
++import org.testng.annotations.BeforeMethod;
++import org.testng.annotations.Test;
++
++import java.util.List;
++import java.util.Map;
++
++import static org.testng.Assert.*;
++
++/**
++ * /**
++ * Live tests for deploying simple containers. Particularly useful during 
dev, but not so useful
++ * after that (because assumes the existence of a kubernetes endpoint). It 
needs configured with
++ * something like:
++ * <p>
++ * {@code 
-Dtest.brooklyn-container-service.kubernetes.endpoint=http://10.104.2.206:8080}
++ */
++public class KubernetesLocationLiveTest extends BrooklynAppLiveTestSupport {
++
++    public static final String KUBERNETES_ENDPOINT = 
System.getProperty("test.brooklyn-container-service.kubernetes.endpoint", "");
++    public static final String IDENTITY = 
System.getProperty("test.brooklyn-container-service.kubernetes.identity", "");
++    public static final String CREDENTIAL = 
System.getProperty("test.brooklyn-container-service.kubernetes.credential", "");
++    private static final Logger LOG = 
LoggerFactory.getLogger(KubernetesLocationLiveTest.class);
++    protected KubernetesLocation loc;
++    protected List<KubernetesMachineLocation> machines;
++
++    @BeforeMethod(alwaysRun = true)
++    @Override
++    public void setUp() throws Exception {
++        super.setUp();
++        machines = Lists.newCopyOnWriteArrayList();
++    }
++
++    // FIXME: Clear up properly: Test leaves deployment, replicas and pods 
behind if obtain fails.
++    @AfterMethod(alwaysRun = true)
++    @Override
++    public void tearDown() throws Exception {
++        for (KubernetesMachineLocation machine : machines) {
++            try {
++                loc.release(machine);
++            } catch (Exception e) {
++                LOG.error("Error releasing machine " + machine + " in 
location " + loc, e);
++            }
++        }
++        super.tearDown();
++    }
++
++    protected KubernetesLocation newKubernetesLocation(Map<String, ?> flags) 
throws Exception {
++        Map<String, ?> allFlags = MutableMap.<String, Object>builder()
++                .put("identity", IDENTITY)
++                .put("credential", CREDENTIAL)
++                .put("endpoint", KUBERNETES_ENDPOINT)
++                .putAll(flags)
++                .build();
++        return (KubernetesLocation) 
mgmt.getLocationRegistry().getLocationManaged("kubernetes", allFlags);
++    }
++
++    @Test(groups = {"Live"})
++    public void testDefault() throws Exception {
++        // Default is "cloudsoft/centos:7"
++        runImage(ImmutableMap.<String, Object>of(), "centos", "7");
++    }
++
++    @Test(groups = {"Live"})
++    public void testMatchesCentos() throws Exception {
++        runImage(ImmutableMap.<String, 
Object>of(KubernetesLocationConfig.OS_FAMILY.getName(), "centos"), "centos", 
"7");
++    }
++
++    @Test(groups = {"Live"})
++    public void testMatchesCentos7() throws Exception {
++        ImmutableMap<String, Object> conf = ImmutableMap.<String, Object>of(
++                KubernetesLocationConfig.OS_FAMILY.getName(), "centos",
++                KubernetesLocationConfig.OS_VERSION_REGEX.getName(), "7.*");
++        runImage(conf, "centos", "7");
++    }
++
++    @Test(groups = {"Live"})
++    public void testMatchesUbuntu() throws Exception {
++        runImage(ImmutableMap.<String, 
Object>of(KubernetesLocationConfig.OS_FAMILY.getName(), "ubuntu"), "ubuntu", 
"14.04");
++    }
++
++    @Test(groups = {"Live"})
++    public void testMatchesUbuntu16() throws Exception {
++        ImmutableMap<String, Object> conf = ImmutableMap.<String, Object>of(
++                KubernetesLocationConfig.OS_FAMILY.getName(), "ubuntu",
++                KubernetesLocationConfig.OS_VERSION_REGEX.getName(), "16.*");
++        runImage(conf, "ubuntu", "16.04");
++    }
++
++    @Test(groups = {"Live"})
++    public void testCloudsoftCentos7() throws Exception {
++        runImage(ImmutableMap.of(KubernetesLocationConfig.IMAGE.getName(), 
"cloudsoft/centos:7"), "centos", "7");
++    }
++
++    @Test(groups = {"Live"})
++    public void testCloudsoftUbuntu14() throws Exception {
++        runImage(ImmutableMap.of(KubernetesLocationConfig.IMAGE.getName(), 
"cloudsoft/ubuntu:14.04"), "ubuntu", "14.04");
++    }
++
++    @Test(groups = {"Live"})
++    public void testCloudsoftUbuntu16() throws Exception {
++        runImage(ImmutableMap.of(KubernetesLocationConfig.IMAGE.getName(), 
"cloudsoft/ubuntu:16.04"), "ubuntu", "16.04");
++    }
++
++    @Test(groups = {"Live"})
++    public void testFailsForNonMatching() throws Exception {
++        ImmutableMap<String, Object> conf = ImmutableMap.<String, Object>of(
++                KubernetesLocationConfig.OS_FAMILY.getName(), 
"weirdOsFamiliy");
++        try {
++            runImage(conf, null, null);
++            Asserts.shouldHaveFailedPreviously();
++        } catch (Exception e) {
++            Asserts.expectedFailureContains(e, "No matching image found");
++        }
++    }
++
++    protected void runImage(Map<String, ?> config, String expectedOs, String 
expectedVersion) throws Exception {
++        loc = newKubernetesLocation(ImmutableMap.<String, Object>of());
++        SshMachineLocation machine = newContainerMachine(loc, 
ImmutableMap.<String, Object>builder()
++                .putAll(config)
++                .put(LocationConfigKeys.CALLER_CONTEXT.getName(), app)
++                .build());
++
++        assertTrue(machine.isSshable(), "not sshable machine=" + machine);
++        assertOsNameContains(machine, expectedOs, expectedVersion);
++        assertMachinePasswordSecure(machine);
++    }
++
++    @Test(groups = {"Live"})
++    protected void testUsesSuppliedLoginPassword() throws Exception {
++        // Because defaulting to "cloudsoft/centos:7", it knows to set the 
loginUserPassword
++        // on container creation.
++        String password = "myCustomP4ssword";
++        loc = newKubernetesLocation(ImmutableMap.<String, Object>of());
++        SshMachineLocation machine = newContainerMachine(loc, 
ImmutableMap.<String, Object>builder()
++                .put(KubernetesLocationConfig.LOGIN_USER_PASSWORD.getName(), 
password)
++                .put(LocationConfigKeys.CALLER_CONTEXT.getName(), app)
++                .build());
++
++        assertTrue(machine.isSshable(), "not sshable machine=" + machine);
++        assertEquals(machine.config().get(SshMachineLocation.PASSWORD), 
password);
++    }
++
++    @Test(groups = {"Live"})
++    public void testOpenPorts() throws Exception {
++        List<Integer> inboundPorts = ImmutableList.of(22, 443, 8000, 8081);
++        loc = newKubernetesLocation(ImmutableMap.<String, Object>of());
++        SshMachineLocation machine = newContainerMachine(loc, 
ImmutableMap.<String, Object>builder()
++                .put(KubernetesLocationConfig.IMAGE.getName(), 
"cloudsoft/centos:7")
++                .put(KubernetesLocationConfig.LOGIN_USER_PASSWORD.getName(), 
"p4ssw0rd")
++                .put(KubernetesLocationConfig.INBOUND_PORTS.getName(), 
inboundPorts)
++                .put(LocationConfigKeys.CALLER_CONTEXT.getName(), app)
++                .build());
++        assertTrue(machine.isSshable());
++
++        String publicHostText = machine.getSshHostAndPort().getHostText();
++        PortForwardManager pfm = (PortForwardManager) 
mgmt.getLocationRegistry().getLocationManaged(PortForwardManagerLocationResolver.PFM_GLOBAL_SPEC);
++        for (int targetPort : inboundPorts) {
++            HostAndPort mappedPort = pfm.lookup(machine, targetPort);
++            assertNotNull(mappedPort, "no mapping for targetPort " + 
targetPort);
++            assertEquals(mappedPort.getHostText(), publicHostText);
++            assertTrue(mappedPort.hasPort(), "no port-part in " + mappedPort 
+ " for targetPort " + targetPort);
++        }
++    }
++
++    protected void assertOsNameContains(SshMachineLocation machine, String 
expectedNamePart, String expectedVersionPart) {
++        MachineDetails machineDetails = app.getExecutionContext()
++                
.submit(BasicMachineDetails.taskForSshMachineLocation(machine))
++                .getUnchecked();
++        OsDetails osDetails = machineDetails.getOsDetails();
++        String osName = osDetails.getName();
++        String osVersion = osDetails.getVersion();
++        assertTrue(osName != null && 
osName.toLowerCase().contains(expectedNamePart), "osDetails=" + osDetails);
++        assertTrue(osVersion != null && 
osVersion.toLowerCase().contains(expectedVersionPart), "osDetails=" + 
osDetails);
++    }
++
++    protected SshMachineLocation newContainerMachine(KubernetesLocation loc, 
Map<?, ?> flags) throws Exception {
++        KubernetesMachineLocation result = loc.obtain(flags);
++        machines.add(result);
++        assertTrue(result instanceof KubernetesSshMachineLocation);
++        return (SshMachineLocation) result;
++    }
++
++    protected void assertMachinePasswordSecure(SshMachineLocation machine) {
++        String password = machine.config().get(SshMachineLocation.PASSWORD);
++        assertTrue(password.length() > 10, "password=" + password);
++        boolean hasUpper = false;
++        boolean hasLower = false;
++        boolean hasNonAlphabetic = false;
++        for (char c : password.toCharArray()) {
++            if (Character.isUpperCase(c)) hasUpper = true;
++            if (Character.isLowerCase(c)) hasLower = true;
++            if (!Character.isAlphabetic(c)) hasNonAlphabetic = true;
++        }
++        assertTrue(hasUpper && hasLower && hasNonAlphabetic, "password=" + 
password);
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolverTest.java
----------------------------------------------------------------------
diff --cc 
locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolverTest.java
index 0000000,0000000..d8abf0d
new file mode 100644
--- /dev/null
+++ 
b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolverTest.java
@@@ -1,0 -1,0 +1,102 @@@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements.  See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership.  The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License.  You may obtain a copy of the License at
++ *
++ *     http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing,
++ * software distributed under the License is distributed on an
++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
++ * KIND, either express or implied.  See the License for the
++ * specific language governing permissions and limitations
++ * under the License.
++ */
++package org.apache.brooklyn.container.location.kubernetes;
++
++import org.apache.brooklyn.api.location.LocationSpec;
++import org.apache.brooklyn.core.internal.BrooklynProperties;
++import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++import org.testng.annotations.BeforeMethod;
++import org.testng.annotations.Test;
++
++import java.util.Map;
++
++import static org.testng.Assert.assertEquals;
++import static org.testng.Assert.assertTrue;
++
++public class KubernetesLocationResolverTest extends 
BrooklynMgmtUnitTestSupport {
++
++    private static final Logger LOG = 
LoggerFactory.getLogger(KubernetesLocationResolverTest.class);
++
++    private BrooklynProperties brooklynProperties;
++
++    @BeforeMethod(alwaysRun = true)
++    public void setUp() throws Exception {
++        super.setUp();
++        brooklynProperties = mgmt.getBrooklynProperties();
++
++        brooklynProperties.put("brooklyn.location.kubernetes.identity", 
"kubernetes-id");
++        brooklynProperties.put("brooklyn.location.kubernetes.credential", 
"kubernetes-cred");
++    }
++
++    @Test
++    public void testGivesCorrectLocationType() {
++        LocationSpec<?> spec = getLocationSpec("kubernetes");
++        assertEquals(spec.getType(), KubernetesLocation.class);
++
++        KubernetesLocation loc = resolve("kubernetes");
++        assertTrue(loc instanceof KubernetesLocation, "loc=" + loc);
++    }
++
++    @Test
++    public void testParametersInSpecString() {
++        KubernetesLocation loc = resolve("kubernetes(endpoint=myMasterUrl)");
++        assertEquals(loc.getConfig(KubernetesLocation.MASTER_URL), 
"myMasterUrl");
++    }
++
++    @Test
++    public void testTakesDotSeparateProperty() {
++        brooklynProperties.put("brooklyn.location.kubernetes.endpoint", 
"myMasterUrl");
++        KubernetesLocation loc = resolve("kubernetes");
++        assertEquals(loc.getConfig(KubernetesLocation.MASTER_URL), 
"myMasterUrl");
++    }
++
++    @Test
++    public void testPropertiesPrecedence() {
++        // prefer those in "spec" over everything else
++        brooklynProperties.put("brooklyn.location.named.mykubernetes", 
"kubernetes:(loginUser=\"loginUser-inSpec\")");
++
++        
brooklynProperties.put("brooklyn.location.named.mykubernetes.loginUser", 
"loginUser-inNamed");
++        brooklynProperties.put("brooklyn.location.kubernetes.loginUser", 
"loginUser-inDocker");
++
++        // prefer those in "named" over everything else
++        
brooklynProperties.put("brooklyn.location.named.mykubernetes.privateKeyFile", 
"privateKeyFile-inNamed");
++        brooklynProperties.put("brooklyn.location.kubernetes.privateKeyFile", 
"privateKeyFile-inDocker");
++
++        // prefer those in kubernetes-specific
++        brooklynProperties.put("brooklyn.location.kubernetes.publicKeyFile", 
"publicKeyFile-inDocker");
++
++        Map<String, Object> conf = 
resolve("named:mykubernetes").config().getBag().getAllConfig();
++
++        assertEquals(conf.get("loginUser"), "loginUser-inSpec");
++        assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed");
++        assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inDocker");
++    }
++
++    private LocationSpec<?> getLocationSpec(String spec) {
++        LOG.debug("Obtaining location spec '{}'", spec);
++        return mgmt.getLocationRegistry().getLocationSpec(spec).get();
++    }
++
++    private KubernetesLocation resolve(String spec) {
++        LOG.debug("Resolving location spec '{}'", spec);
++        return (KubernetesLocation) 
mgmt.getLocationRegistry().getLocationManaged(spec);
++    }
++}

Reply via email to