You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by dr...@apache.org on 2017/06/29 15:36:24 UTC

[42/50] [abbrv] brooklyn-server git commit: Merge tag 'apache-brooklyn' into feature/container-service

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationYamlLiveTest.java
----------------------------------------------------------------------
diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationYamlLiveTest.java
index 0000000,0000000..f8b2645
new file mode 100644
--- /dev/null
+++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationYamlLiveTest.java
@@@ -1,0 -1,0 +1,521 @@@
++/*
++ * 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.base.Joiner;
++import com.google.common.collect.ImmutableList;
++import com.google.common.collect.Iterables;
++import com.google.common.net.HostAndPort;
++import io.fabric8.kubernetes.api.model.Pod;
++import io.fabric8.kubernetes.client.KubernetesClient;
++import org.apache.brooklyn.api.entity.Entity;
++import org.apache.brooklyn.api.location.MachineLocation;
++import org.apache.brooklyn.api.location.MachineProvisioningLocation;
++import org.apache.brooklyn.camp.brooklyn.AbstractYamlTest;
++import org.apache.brooklyn.container.entity.docker.DockerContainer;
++import org.apache.brooklyn.container.entity.kubernetes.KubernetesPod;
++import org.apache.brooklyn.container.entity.kubernetes.KubernetesResource;
++import org.apache.brooklyn.core.entity.Attributes;
++import org.apache.brooklyn.core.entity.Entities;
++import org.apache.brooklyn.core.entity.EntityPredicates;
++import org.apache.brooklyn.core.location.Machines;
++import org.apache.brooklyn.core.network.OnPublicNetworkEnricher;
++import org.apache.brooklyn.core.sensor.Sensors;
++import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess;
++import org.apache.brooklyn.entity.software.base.SoftwareProcess;
++import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess;
++import org.apache.brooklyn.entity.stock.BasicStartable;
++import org.apache.brooklyn.location.ssh.SshMachineLocation;
++import org.apache.brooklyn.util.core.config.ConfigBag;
++import org.apache.brooklyn.util.net.Networking;
++import org.apache.brooklyn.util.text.Identifiers;
++import org.apache.commons.lang3.StringUtils;
++import org.testng.annotations.BeforeMethod;
++import org.testng.annotations.Test;
++
++import java.util.List;
++import java.util.Map;
++
++import static com.google.common.base.Predicates.*;
++import static org.apache.brooklyn.container.location.kubernetes.KubernetesLocationLiveTest.*;
++import static org.apache.brooklyn.core.entity.EntityAsserts.*;
++import static org.apache.brooklyn.test.Asserts.succeedsEventually;
++import static org.apache.brooklyn.util.http.HttpAsserts.assertHttpStatusCodeEventuallyEquals;
++import static org.testng.Assert.assertEquals;
++import static org.testng.Assert.assertTrue;
++
++/**
++ * Live tests for deploying simple blueprints. 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 KubernetesLocationYamlLiveTest extends AbstractYamlTest {
++
++    protected KubernetesLocation loc;
++    protected List<MachineLocation> machines;
++    protected String locationYaml;
++
++    @BeforeMethod(alwaysRun = true)
++    @Override
++    public void setUp() throws Exception {
++        super.setUp();
++
++        locationYaml = Joiner.on("\n").join(
++                "location:",
++                "  kubernetes:",
++                "    " + KubernetesLocationConfig.MASTER_URL.getName() + ": \"" + KUBERNETES_ENDPOINT + "\"",
++                "    " + (StringUtils.isBlank(IDENTITY) ? "" : "identity: " + IDENTITY),
++                "    " + (StringUtils.isBlank(CREDENTIAL) ? "" : "credential: " + CREDENTIAL));
++    }
++
++    @Test(groups = {"Live"})
++    public void testLoginPasswordOverride() throws Exception {
++        String customPassword = "myDifferentPassword";
++
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + EmptySoftwareProcess.class.getName(),
++                "    brooklyn.config:",
++                "      provisioning.properties:",
++                "        " + KubernetesLocationConfig.LOGIN_USER_PASSWORD.getName() + ": " + customPassword);
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        EmptySoftwareProcess entity = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, EmptySoftwareProcess.class));
++
++        SshMachineLocation machine = Machines.findUniqueMachineLocation(entity.getLocations(), SshMachineLocation.class).get();
++        assertEquals(machine.config().get(SshMachineLocation.PASSWORD), customPassword);
++        assertTrue(machine.isSshable());
++    }
++
++    @Test(groups = {"Live"})
++    public void testNetcatServer() throws Exception {
++        // Runs as root user (hence not `sudo yum install ...`)
++        // breaks if shell.env uses attributeWhenReady, so not doing that - see testNetcatServerWithDslInShellEnv()
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + VanillaSoftwareProcess.class.getName(),
++                "    brooklyn.parameters:",
++                "      - name: netcat.port",
++                "        type: port",
++                "        default: 8081",
++                "    brooklyn.config:",
++                "      install.command: |",
++                "        yum install -y nc",
++                "      launch.command: |",
++                "        echo $MESSAGE | nc -l $NETCAT_PORT &",
++                "        echo $! > $PID_FILE",
++                "      shell.env:",
++                "        MESSAGE: mymessage",
++                "        NETCAT_PORT: $brooklyn:attributeWhenReady(\"netcat.port\")",
++                "    brooklyn.enrichers:",
++                "      - type: " + OnPublicNetworkEnricher.class.getName(),
++                "        brooklyn.config:",
++                "          " + OnPublicNetworkEnricher.SENSORS.getName() + ":",
++                "            - netcat.port");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        VanillaSoftwareProcess entity = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, VanillaSoftwareProcess.class));
++
++        String publicMapped = assertAttributeEventuallyNonNull(entity, Sensors.newStringSensor("netcat.endpoint.mapped.public"));
++        HostAndPort publicPort = HostAndPort.fromString(publicMapped);
++
++        assertTrue(Networking.isReachable(publicPort), "publicPort=" + publicPort);
++    }
++
++    @Test(groups = {"Live"})
++    public void testInterContainerNetworking() throws Exception {
++        String message = "mymessage";
++        int netcatPort = 8081;
++
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + VanillaSoftwareProcess.class.getName(),
++                "    name: server1",
++                "    brooklyn.parameters:",
++                "      - name: netcat.port",
++                "        type: port",
++                "        default: " + netcatPort,
++                "    brooklyn.config:",
++                "      install.command: |",
++                "        yum install -y nc",
++                "      launch.command: |",
++                "        echo " + message + " | nc -l " + netcatPort + " > netcat.out &",
++                "        echo $! > $PID_FILE",
++                "  - type: " + VanillaSoftwareProcess.class.getName(),
++                "    name: server2",
++                "    brooklyn.config:",
++                "      install.command: |",
++                "        yum install -y nc",
++                "      launch.command: true",
++                "      checkRunning.command: true");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        Entities.dumpInfo(app);
++
++        Entity server1 = Iterables.find(Entities.descendantsAndSelf(app), EntityPredicates.displayNameEqualTo("server1"));
++        Entity server2 = Iterables.find(Entities.descendantsAndSelf(app), EntityPredicates.displayNameEqualTo("server2"));
++
++        SshMachineLocation machine1 = Machines.findUniqueMachineLocation(server1.getLocations(), SshMachineLocation.class).get();
++        SshMachineLocation machine2 = Machines.findUniqueMachineLocation(server2.getLocations(), SshMachineLocation.class).get();
++
++        String addr1 = server1.sensors().get(Attributes.SUBNET_ADDRESS);
++        String addr2 = server2.sensors().get(Attributes.SUBNET_ADDRESS);
++
++        // Ping between containers
++        int result1 = machine1.execCommands("ping-server2", ImmutableList.of("ping -c 4 " + addr2));
++        int result2 = machine2.execCommands("ping-server1", ImmutableList.of("ping -c 4 " + addr1));
++
++        // Reach netcat port from other container
++        int result3 = machine2.execCommands("nc-to-server1", ImmutableList.of(
++                "echo \"fromServer2\" | nc " + addr1 + " " + netcatPort + " > netcat.out",
++                "cat netcat.out",
++                "grep " + message + " netcat.out"));
++
++        String errMsg = "result1=" + result1 + "; result2=" + result2 + "; result3=" + result3;
++        assertEquals(result1, 0, errMsg);
++        assertEquals(result2, 0, errMsg);
++        assertEquals(result3, 0, errMsg);
++    }
++
++    @Test(groups = {"Live"})
++    public void testTomcatPod() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + KubernetesPod.class.getName(),
++                "    brooklyn.config:",
++                "      docker.container.imageName: tomcat",
++                "      docker.container.inboundPorts: [ \"8080\" ]");
++
++        runTomcat(yaml, KubernetesPod.class);
++    }
++
++    @Test(groups = {"Live"})
++    public void testTomcatPodExtras() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + KubernetesPod.class.getName(),
++                "    brooklyn.config:",
++                "      docker.container.imageName: tomcat",
++                "      docker.container.inboundPorts: [ \"8080\" ]",
++                "      metadata:",
++                "        extra: test");
++
++        KubernetesPod entity = runTomcat(yaml, KubernetesPod.class);
++
++        String namespace = entity.sensors().get(KubernetesPod.KUBERNETES_NAMESPACE);
++        String podName = entity.sensors().get(KubernetesPod.KUBERNETES_POD);
++        KubernetesClient client = getClient(entity);
++        Pod pod = client.pods().inNamespace(namespace).withName(podName).get();
++        Map<String, String> labels = pod.getMetadata().getLabels();
++        assertTrue(labels.containsKey("extra"));
++        assertEquals(labels.get("extra"), "test");
++    }
++
++    @Test(groups = {"Live"})
++    public void testTomcatContainer() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + DockerContainer.class.getName(),
++                "    brooklyn.config:",
++                "      docker.container.imageName: tomcat",
++                "      docker.container.inboundPorts: [ \"8080\" ]");
++
++        runTomcat(yaml, DockerContainer.class);
++    }
++
++    /**
++     * Assumes that the container entity uses port 8080.
++     */
++    protected <T extends Entity> T runTomcat(String yaml, Class<T> type) throws Exception {
++        Entity app = createStartWaitAndLogApplication(yaml);
++        T entity = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, type));
++
++        Entities.dumpInfo(app);
++        String publicMapped = assertAttributeEventuallyNonNull(entity, Sensors.newStringSensor("docker.port.8080.mapped.public"));
++        HostAndPort publicPort = HostAndPort.fromString(publicMapped);
++
++        assertReachableEventually(publicPort);
++        assertHttpStatusCodeEventuallyEquals("http://" + publicPort.getHostText() + ":" + publicPort.getPort(), 200);
++
++        return entity;
++    }
++
++    @Test(groups = {"Live"})
++    public void testWordpressInContainersWithStartableParent() throws Exception {
++        // TODO docker.container.inboundPorts doesn't accept list of ints - need to use quotes
++        String randomId = Identifiers.makeRandomLowercaseId(4);
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + BasicStartable.class.getName(),
++                "    brooklyn.children:",
++                "      - type: " + DockerContainer.class.getName(),
++                "        id: wordpress-mysql",
++                "        name: mysql",
++                "        brooklyn.config:",
++                "          docker.container.imageName: mysql:5.6",
++                "          docker.container.inboundPorts:",
++                "            - \"3306\"",
++                "          docker.container.environment:",
++                "            MYSQL_ROOT_PASSWORD: \"password\"",
++                "          provisioning.properties:",
++                "            deployment: wordpress-mysql-" + randomId,
++                "      - type: " + DockerContainer.class.getName(),
++                "        id: wordpress",
++                "        name: wordpress",
++                "        brooklyn.config:",
++                "          docker.container.imageName: wordpress:4-apache",
++                "          docker.container.inboundPorts:",
++                "            - \"80\"",
++                "          docker.container.environment:",
++                "            WORDPRESS_DB_HOST: \"wordpress-mysql-" + randomId + "\"",
++                "            WORDPRESS_DB_PASSWORD: \"password\"",
++                "          provisioning.properties:",
++                "            deployment: wordpress-" + randomId);
++
++        runWordpress(yaml, randomId);
++    }
++
++    @Test(groups = {"Live"})
++    public void testWordpressInPodsWithStartableParent() throws Exception {
++        // TODO docker.container.inboundPorts doesn't accept list of ints - need to use quotes
++        String randomId = Identifiers.makeRandomLowercaseId(4);
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + BasicStartable.class.getName(),
++                "    brooklyn.children:",
++                "      - type: " + KubernetesPod.class.getName(),
++                "        id: wordpress-mysql",
++                "        name: mysql",
++                "        brooklyn.config:",
++                "          docker.container.imageName: mysql:5.6",
++                "          docker.container.inboundPorts:",
++                "            - \"3306\"",
++                "          docker.container.environment:",
++                "            MYSQL_ROOT_PASSWORD: \"password\"",
++                "          deployment: wordpress-mysql-" + randomId,
++                "      - type: " + KubernetesPod.class.getName(),
++                "        id: wordpress",
++                "        name: wordpress",
++                "        brooklyn.config:",
++                "          docker.container.imageName: wordpress:4-apache",
++                "          docker.container.inboundPorts:",
++                "            - \"80\"",
++                "          docker.container.environment:",
++                "            WORDPRESS_DB_HOST: \"wordpress-mysql-" + randomId + "\"",
++                "            WORDPRESS_DB_PASSWORD: \"password\"",
++                "          deployment: wordpress-" + randomId);
++
++        runWordpress(yaml, randomId);
++    }
++
++    @Test(groups = {"Live"})
++    public void testWordpressInPods() throws Exception {
++        // TODO docker.container.inboundPorts doesn't accept list of ints - need to use quotes
++        String randomId = Identifiers.makeRandomLowercaseId(4);
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + KubernetesPod.class.getName(),
++                "    id: wordpress-mysql",
++                "    name: mysql",
++                "    brooklyn.config:",
++                "      docker.container.imageName: mysql:5.6",
++                "      docker.container.inboundPorts:",
++                "        - \"3306\"",
++                "      docker.container.environment:",
++                "        MYSQL_ROOT_PASSWORD: \"password\"",
++                "      deployment: wordpress-mysql-" + randomId,
++                "  - type: " + KubernetesPod.class.getName(),
++                "    id: wordpress",
++                "    name: wordpress",
++                "    brooklyn.config:",
++                "      docker.container.imageName: wordpress:4-apache",
++                "      docker.container.inboundPorts:",
++                "        - \"80\"",
++                "      docker.container.environment:",
++                "        WORDPRESS_DB_HOST: \"wordpress-mysql-" + randomId + "\"",
++                "        WORDPRESS_DB_PASSWORD: \"password\"",
++                "      deployment: wordpress-" + randomId);
++
++        runWordpress(yaml, randomId);
++    }
++
++    /**
++     * Assumes that the {@link DockerContainer} entities have display names of "mysql" and "wordpress",
++     * and that they use ports 3306 and 80 respectively.
++     */
++    protected void runWordpress(String yaml, String randomId) throws Exception {
++        Entity app = createStartWaitAndLogApplication(yaml);
++        Entities.dumpInfo(app);
++
++        Iterable<DockerContainer> containers = Entities.descendantsAndSelf(app, DockerContainer.class);
++        DockerContainer mysql = Iterables.find(containers, EntityPredicates.displayNameEqualTo("mysql"));
++        DockerContainer wordpress = Iterables.find(containers, EntityPredicates.displayNameEqualTo("wordpress"));
++
++        String mysqlPublicPort = assertAttributeEventuallyNonNull(mysql, Sensors.newStringSensor("docker.port.3306.mapped.public"));
++        assertReachableEventually(HostAndPort.fromString(mysqlPublicPort));
++        assertAttributeEquals(mysql, KubernetesPod.KUBERNETES_NAMESPACE, "brooklyn");
++        assertAttributeEquals(mysql, KubernetesPod.KUBERNETES_SERVICE, "wordpress-mysql-" + randomId);
++
++        String wordpressPublicPort = assertAttributeEventuallyNonNull(wordpress, Sensors.newStringSensor("docker.port.80.mapped.public"));
++        assertReachableEventually(HostAndPort.fromString(wordpressPublicPort));
++        assertAttributeEquals(wordpress, KubernetesPod.KUBERNETES_NAMESPACE, "brooklyn");
++        assertAttributeEquals(wordpress, KubernetesPod.KUBERNETES_SERVICE, "wordpress-" + randomId);
++
++        // TODO more assertions (e.g. wordpress can successfully reach the database)
++    }
++
++    @Test(groups = {"Live"})
++    public void testPod() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + KubernetesPod.class.getName(),
++                "    brooklyn.config:",
++                "      docker.container.imageName: tomcat",
++                "      docker.container.inboundPorts:",
++                "        - \"8080\"",
++                "      shell.env:",
++                "        CLUSTER_ID: \"id\"",
++                "        CLUSTER_TOKEN: \"token\"");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        checkPod(app, KubernetesPod.class);
++    }
++
++    @Test(groups = {"Live"}, enabled = false)
++    public void testPodCatalogEntry() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: kubernetes-pod-entity",
++                "    brooklyn.config:",
++                "      docker.container.imageName: tomcat",
++                "      docker.container.inboundPorts:",
++                "        - \"8080\"",
++                "      shell.env:",
++                "        CLUSTER_ID: \"id\"",
++                "        CLUSTER_TOKEN: \"token\"");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        checkPod(app, KubernetesPod.class);
++    }
++
++    protected <T extends Entity> void checkPod(Entity app, Class<T> type) {
++        T container = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, type));
++
++        Entities.dumpInfo(app);
++
++        String publicMapped = assertAttributeEventuallyNonNull(container, Sensors.newStringSensor("docker.port.8080.mapped.public"));
++        HostAndPort publicPort = HostAndPort.fromString(publicMapped);
++
++        assertReachableEventually(publicPort);
++        assertHttpStatusCodeEventuallyEquals("http://" + publicPort.getHostText() + ":" + publicPort.getPort(), 200);
++    }
++
++    @Test(groups = {"Live"})
++    public void testNginxReplicationController() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + KubernetesResource.class.getName(),
++                "    id: nginx-replication-controller",
++                "    name: \"nginx-replication-controller\"",
++                "    brooklyn.config:",
++                "      resource: classpath://nginx-replication-controller.yaml");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        checkNginxResource(app, KubernetesResource.class);
++    }
++
++    protected <T extends Entity> void checkNginxResource(Entity app, Class<T> type) {
++        T entity = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, type));
++
++        Entities.dumpInfo(app);
++
++        assertEntityHealthy(entity);
++        assertAttributeEqualsEventually(entity, KubernetesResource.RESOURCE_NAME, "nginx-replication-controller");
++        assertAttributeEqualsEventually(entity, KubernetesResource.RESOURCE_TYPE, "ReplicationController");
++        assertAttributeEqualsEventually(entity, KubernetesResource.KUBERNETES_NAMESPACE, "default");
++        assertAttributeEventually(entity, SoftwareProcess.ADDRESS, and(notNull(), not(equalTo("0.0.0.0"))));
++        assertAttributeEventually(entity, SoftwareProcess.SUBNET_ADDRESS, and(notNull(), not(equalTo("0.0.0.0"))));
++    }
++
++    @Test(groups = {"Live"})
++    public void testNginxService() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + KubernetesResource.class.getName(),
++                "    id: nginx-replication-controller",
++                "    name: \"nginx-replication-controller\"",
++                "    brooklyn.config:",
++                "      resource: classpath://nginx-replication-controller.yaml",
++                "  - type: " + KubernetesResource.class.getName(),
++                "    id: nginx-service",
++                "    name: \"nginx-service\"",
++                "    brooklyn.config:",
++                "      resource: classpath://nginx-service.yaml");
++        Entity app = createStartWaitAndLogApplication(yaml);
++
++        Iterable<KubernetesResource> resources = Entities.descendantsAndSelf(app, KubernetesResource.class);
++        KubernetesResource nginxReplicationController = Iterables.find(resources, EntityPredicates.displayNameEqualTo("nginx-replication-controller"));
++        KubernetesResource nginxService = Iterables.find(resources, EntityPredicates.displayNameEqualTo("nginx-service"));
++
++        assertEntityHealthy(nginxReplicationController);
++        assertEntityHealthy(nginxService);
++
++        Entities.dumpInfo(app);
++
++        Integer httpPort = assertAttributeEventuallyNonNull(nginxService, Sensors.newIntegerSensor("kubernetes.http.port"));
++        assertEquals(httpPort, Integer.valueOf(80));
++        String httpPublicPort = assertAttributeEventuallyNonNull(nginxService, Sensors.newStringSensor("kubernetes.http.endpoint.mapped.public"));
++        assertReachableEventually(HostAndPort.fromString(httpPublicPort));
++    }
++
++    protected void assertReachableEventually(final HostAndPort hostAndPort) {
++        succeedsEventually(new Runnable() {
++            public void run() {
++                assertTrue(Networking.isReachable(hostAndPort), "publicPort=" + hostAndPort);
++            }
++        });
++    }
++
++    public KubernetesClient getClient(Entity entity) {
++        MachineProvisioningLocation location = entity.sensors().get(SoftwareProcess.PROVISIONING_LOCATION);
++        if (location instanceof KubernetesLocation) {
++            KubernetesLocation kubernetes = (KubernetesLocation) location;
++            ConfigBag config = kubernetes.config().getBag();
++            KubernetesClientRegistry registry = kubernetes.config().get(KubernetesLocationConfig.KUBERNETES_CLIENT_REGISTRY);
++            KubernetesClient client = registry.getKubernetesClient(config);
++            return client;
++        }
++        throw new IllegalStateException("Cannot find KubernetesLocation on entity: " + Iterables.toString(entity.getLocations()));
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationLiveTest.java
----------------------------------------------------------------------
diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationLiveTest.java
index 0000000,0000000..0606f68
new file mode 100644
--- /dev/null
+++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationLiveTest.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.container.location.kubernetes.KubernetesLocationLiveTest;
++import org.apache.brooklyn.util.collections.MutableMap;
++import org.apache.brooklyn.util.os.Os;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++
++import java.util.Map;
++
++/**
++ * Tests deploying containers via the {@code openshift} location, to an OpenShift endpoint.
++ * By extending {@link KubernetesLocationLiveTest}, we get all the k8s tests.
++ * <p>
++ * It needs configured with something like:
++ * <p>
++ * <pre>{@code
++ * -Dtest.brooklyn-container-service.openshift.endpoint=https://192.168.99.100:8443/
++ * -Dtest.brooklyn-container-service.openshift.certsBaseDir=~/repos/grkvlt/40bdf09b09d5896e19a9d287f41d39bb
++ * }</pre>
++ */
++public class OpenShiftLocationLiveTest extends KubernetesLocationLiveTest {
++
++    public static final String OPENSHIFT_ENDPOINT = System.getProperty("test.brooklyn-container-service.openshift.endpoint", "");
++    public static final String CERTS_BASE_DIR = System.getProperty("test.brooklyn-container-service.openshift.certsBaseDir", Os.mergePaths(System.getProperty("user.home"), "openshift-certs"));
++    public static final String CA_CERT_FILE = System.getProperty("test.brooklyn-container-service.openshift.caCert", Os.mergePaths(CERTS_BASE_DIR, "ca.crt"));
++    public static final String CLIENT_CERT_FILE = System.getProperty("test.brooklyn-container-service.openshift.clientCert", Os.mergePaths(CERTS_BASE_DIR, "admin.crt"));
++    public static final String CLIENT_KEY_FILE = System.getProperty("test.brooklyn-container-service.openshift.clientKey", Os.mergePaths(CERTS_BASE_DIR, "admin.key"));
++    public static final String NAMESPACE = System.getProperty("test.brooklyn-container-service.openshift.namespace", "");
++
++    @SuppressWarnings("unused")
++    private static final Logger LOG = LoggerFactory.getLogger(OpenShiftLocationLiveTest.class);
++
++    @Override
++    protected OpenShiftLocation newKubernetesLocation(Map<String, ?> flags) throws Exception {
++        Map<String, ?> allFlags = MutableMap.<String, Object>builder()
++                .put("endpoint", OPENSHIFT_ENDPOINT)
++                .put("caCert", CA_CERT_FILE)
++                .put("clientCert", CLIENT_CERT_FILE)
++                .put("clientKey", CLIENT_KEY_FILE)
++                .put("namespace", NAMESPACE)
++                .put("privileged", true)
++                .putAll(flags)
++                .build();
++        return (OpenShiftLocation) mgmt.getLocationRegistry().getLocationManaged("openshift", allFlags);
++    }
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolverTest.java
----------------------------------------------------------------------
diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolverTest.java
index 0000000,0000000..88f5d86
new file mode 100644
--- /dev/null
+++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolverTest.java
@@@ -1,0 -1,0 +1,103 @@@
++/*
++ * 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.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 OpenShiftLocationResolverTest extends BrooklynMgmtUnitTestSupport {
++
++    private static final Logger LOG = LoggerFactory.getLogger(OpenShiftLocationResolverTest.class);
++
++    private BrooklynProperties brooklynProperties;
++
++    @BeforeMethod(alwaysRun = true)
++    @Override
++    public void setUp() throws Exception {
++        super.setUp();
++        brooklynProperties = mgmt.getBrooklynProperties();
++
++        brooklynProperties.put("brooklyn.location.openshift.identity", "openshift-id");
++        brooklynProperties.put("brooklyn.location.openshift.credential", "openshift-cred");
++    }
++
++    @Test
++    public void testGivesCorrectLocationType() {
++        LocationSpec<?> spec = getLocationSpec("openshift");
++        assertEquals(spec.getType(), OpenShiftLocation.class);
++
++        OpenShiftLocation loc = resolve("openshift");
++        assertTrue(loc instanceof OpenShiftLocation, "loc=" + loc);
++    }
++
++    @Test
++    public void testParametersInSpecString() {
++        OpenShiftLocation loc = resolve("openshift(endpoint=myMasterUrl)");
++        assertEquals(loc.getConfig(OpenShiftLocation.MASTER_URL), "myMasterUrl");
++    }
++
++    @Test
++    public void testTakesDotSeparateProperty() {
++        brooklynProperties.put("brooklyn.location.openshift.endpoint", "myMasterUrl");
++        OpenShiftLocation loc = resolve("openshift");
++        assertEquals(loc.getConfig(OpenShiftLocation.MASTER_URL), "myMasterUrl");
++    }
++
++    @Test
++    public void testPropertiesPrecedence() {
++        // prefer those in "spec" over everything else
++        brooklynProperties.put("brooklyn.location.named.myopenshift", "openshift:(loginUser=\"loginUser-inSpec\")");
++
++        brooklynProperties.put("brooklyn.location.named.myopenshift.loginUser", "loginUser-inNamed");
++        brooklynProperties.put("brooklyn.location.openshift.loginUser", "loginUser-inDocker");
++
++        // prefer those in "named" over everything else
++        brooklynProperties.put("brooklyn.location.named.myopenshift.privateKeyFile", "privateKeyFile-inNamed");
++        brooklynProperties.put("brooklyn.location.openshift.privateKeyFile", "privateKeyFile-inDocker");
++
++        // prefer those in openshift-specific
++        brooklynProperties.put("brooklyn.location.openshift.publicKeyFile", "publicKeyFile-inDocker");
++
++        Map<String, Object> conf = resolve("named:myopenshift").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 OpenShiftLocation resolve(String spec) {
++        LOG.debug("Resolving location spec '{}'", spec);
++        return (OpenShiftLocation) 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/openshift/OpenShiftLocationYamlLiveTest.java
----------------------------------------------------------------------
diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationYamlLiveTest.java
index 0000000,0000000..fae87de
new file mode 100644
--- /dev/null
+++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationYamlLiveTest.java
@@@ -1,0 -1,0 +1,141 @@@
++/*
++ * 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.base.Joiner;
++import org.apache.brooklyn.api.entity.Entity;
++import org.apache.brooklyn.container.entity.openshift.OpenShiftPod;
++import org.apache.brooklyn.container.entity.openshift.OpenShiftResource;
++import org.apache.brooklyn.container.location.kubernetes.KubernetesLocationYamlLiveTest;
++import org.testng.annotations.BeforeMethod;
++import org.testng.annotations.Test;
++
++import static org.apache.brooklyn.container.location.openshift.OpenShiftLocationLiveTest.*;
++
++/**
++ * Tests YAML apps via the {@code openshift} location, to an OpenShift endpoint.
++ * By extending {@link KubernetesLocationYamlLiveTest}, we get all the k8s tests.
++ * <p>
++ * It needs configured with something like:
++ * <p>
++ * <pre>{@code
++ * -Dtest.brooklyn-container-service.openshift.endpoint=https://master.example.com:8443/
++ * -Dtest.brooklyn-container-service.openshift.certsBaseDir=/Users/aled/repos/grkvlt/40bdf09b09d5896e19a9d287f41d39bb
++ * -Dtest.brooklyn-container-service.openshift.namespace=test
++ * }</pre>
++ */
++public class OpenShiftLocationYamlLiveTest extends KubernetesLocationYamlLiveTest {
++
++    @BeforeMethod(alwaysRun = true)
++    @Override
++    public void setUp() throws Exception {
++        super.setUp();
++
++        locationYaml = Joiner.on("\n").join(
++                "location:",
++                "  openshift:",
++                "    " + OpenShiftLocation.CLOUD_ENDPOINT.getName() + ": \"" + OPENSHIFT_ENDPOINT + "\"",
++                "    " + OpenShiftLocation.CA_CERT_FILE.getName() + ": \"" + CA_CERT_FILE + "\"",
++                "    " + OpenShiftLocation.CLIENT_CERT_FILE.getName() + ": \"" + CLIENT_CERT_FILE + "\"",
++                "    " + OpenShiftLocation.CLIENT_KEY_FILE.getName() + ": \"" + CLIENT_KEY_FILE + "\"",
++                "    " + OpenShiftLocation.NAMESPACE.getName() + ": \"" + NAMESPACE + "\"",
++                "    " + OpenShiftLocation.PRIVILEGED.getName() + ": true",
++                "    " + OpenShiftLocation.LOGIN_USER_PASSWORD.getName() + ": p4ssw0rd");
++    }
++
++    @Test(groups = {"Live"})
++    public void testTomcatOpenShiftPod() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + OpenShiftPod.class.getName(),
++                "    brooklyn.config:",
++                "      docker.container.imageName: tomcat",
++                "      docker.container.inboundPorts: [ \"8080\" ]");
++
++        runTomcat(yaml, OpenShiftPod.class);
++    }
++
++    @Test(groups = {"Live"})
++    public void testOpenShiftPod() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + OpenShiftPod.class.getName(),
++                "    brooklyn.config:",
++                "      docker.container.imageName: tomcat",
++                "      docker.container.inboundPorts:",
++                "        - \"8080\"",
++                "      shell.env:",
++                "        CLUSTER_ID: \"id\"",
++                "        CLUSTER_TOKEN: \"token\"");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        checkPod(app, OpenShiftPod.class);
++    }
++
++    @Test(groups = {"Live"}, enabled = false)
++    public void testOpenShiftPodCatalogEntry() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: openshift-pod-entity",
++                "    brooklyn.config:",
++                "      docker.container.imageName: tomcat",
++                "      docker.container.inboundPorts:",
++                "        - \"8080\"",
++                "      shell.env:",
++                "        CLUSTER_ID: \"id\"",
++                "        CLUSTER_TOKEN: \"token\"");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        checkPod(app, OpenShiftPod.class);
++    }
++
++    @Test(groups = {"Live"})
++    public void testNginxOpenShiftResource() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: " + OpenShiftResource.class.getName(),
++                "    id: nginx",
++                "    name: \"nginx\"",
++                "    brooklyn.config:",
++                "      resource: classpath://nginx.yaml");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        checkNginxResource(app, OpenShiftResource.class);
++    }
++
++    @Test(groups = {"Live"}, enabled = false)
++    public void testNginxOpenShiftResourceCatalogEntry() throws Exception {
++        String yaml = Joiner.on("\n").join(
++                locationYaml,
++                "services:",
++                "  - type: openshift-resource-entity",
++                "    id: nginx",
++                "    name: \"nginx\"",
++                "    brooklyn.config:",
++                "      resource: classpath://nginx.yaml");
++
++        Entity app = createStartWaitAndLogApplication(yaml);
++        checkNginxResource(app, OpenShiftResource.class);
++    }
++
++}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/resources/nginx-replication-controller.yaml
----------------------------------------------------------------------
diff --cc locations/container/src/test/resources/nginx-replication-controller.yaml
index 0000000,0000000..8aeafac
new file mode 100644
--- /dev/null
+++ b/locations/container/src/test/resources/nginx-replication-controller.yaml
@@@ -1,0 -1,0 +1,37 @@@
++# 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.
++
++apiVersion: v1
++kind: ReplicationController
++metadata:
++  name: nginx-replication-controller
++  namespace: default
++spec:
++  replicas: 2
++  selector:
++    app: nginx
++  template:
++    metadata:
++      name: nginx
++      labels:
++        app: nginx
++    spec:
++      containers:
++        - name: nginx
++          image: nginx
++          ports:
++            - containerPort: 80

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/resources/nginx-service.yaml
----------------------------------------------------------------------
diff --cc locations/container/src/test/resources/nginx-service.yaml
index 0000000,0000000..db16d5b
new file mode 100644
--- /dev/null
+++ b/locations/container/src/test/resources/nginx-service.yaml
@@@ -1,0 -1,0 +1,29 @@@
++# 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.
++
++apiVersion: v1
++kind: Service
++metadata:
++  name: nginx-service
++  namespace: default
++spec:
++  type: NodePort
++  ports:
++    - name: "http"
++      port: 80
++  selector:
++    app: nginx