no_entry: Move kubernetes classes to new location

Signed-off-by: Andrew Donald Kennedy <[email protected]>


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/445884b1
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/445884b1
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/445884b1

Branch: refs/heads/master
Commit: 445884b1b11c1da80578f2a4968cb6ccb3c456f7
Parents: e890c00
Author: CloudsoftOps <[email protected]>
Authored: Thu May 18 17:52:06 2017 +0100
Committer: Andrew Donald Kennedy <[email protected]>
Committed: Fri May 19 14:03:37 2017 +0100

----------------------------------------------------------------------
 .../entity/kubernetes/KubernetesPod.java        |   82 ++
 .../entity/kubernetes/KubernetesPodImpl.java    |    7 +
 .../entity/kubernetes/KubernetesResource.java   |   40 +
 .../kubernetes/KubernetesResourceImpl.java      |   18 +
 .../location/kubernetes/ImageChooser.java       |   71 ++
 .../location/kubernetes/KubernetesCerts.java    |   64 ++
 .../kubernetes/KubernetesClientRegistry.java    |   11 +
 .../KubernetesClientRegistryImpl.java           |   78 ++
 .../location/kubernetes/KubernetesLocation.java | 1014 ++++++++++++++++++
 .../kubernetes/KubernetesLocationConfig.java    |  164 +++
 .../kubernetes/KubernetesLocationResolver.java  |   47 +
 .../machine/KubernetesEmptyMachineLocation.java |   68 ++
 .../machine/KubernetesMachineLocation.java      |   27 +
 .../machine/KubernetesSshMachineLocation.java   |   28 +
 .../location/kubernetes/ImageChooserTest.java   |   67 ++
 .../kubernetes/KubernetesCertsTest.java         |  146 +++
 .../kubernetes/KubernetesLocationLiveTest.java  |  226 ++++
 .../KubernetesLocationResolverTest.java         |   84 ++
 .../KubernetesLocationYamlLiveTest.java         |  518 +++++++++
 .../kubernetes/entity/KubernetesPod.java        |   82 --
 .../kubernetes/entity/KubernetesPodImpl.java    |    7 -
 .../kubernetes/entity/KubernetesResource.java   |   40 -
 .../entity/KubernetesResourceImpl.java          |   18 -
 .../kubernetes/location/ImageChooser.java       |   71 --
 .../kubernetes/location/KubernetesCerts.java    |   64 --
 .../location/KubernetesClientRegistry.java      |   11 -
 .../location/KubernetesClientRegistryImpl.java  |   78 --
 .../kubernetes/location/KubernetesLocation.java | 1014 ------------------
 .../location/KubernetesLocationConfig.java      |  164 ---
 .../location/KubernetesLocationResolver.java    |   47 -
 .../machine/KubernetesEmptyMachineLocation.java |   68 --
 .../machine/KubernetesMachineLocation.java      |   27 -
 .../machine/KubernetesSshMachineLocation.java   |   28 -
 .../kubernetes/location/ImageChooserTest.java   |   67 --
 .../location/KubernetesCertsTest.java           |  146 ---
 .../location/KubernetesLocationLiveTest.java    |  226 ----
 .../KubernetesLocationResolverTest.java         |   84 --
 .../KubernetesLocationYamlLiveTest.java         |  518 ---------
 38 files changed, 2760 insertions(+), 2760 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPod.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPod.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPod.java
new file mode 100644
index 0000000..bf5444d
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPod.java
@@ -0,0 +1,82 @@
+package io.cloudsoft.amp.containerservice.kubernetes.entity;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.BasicConfigInheritance;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.config.MapConfigKey;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.util.math.MathPredicates;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.TypeToken;
+
+import io.cloudsoft.amp.containerservice.dockercontainer.DockerContainer;
+import 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig;
+
+@ImplementedBy(KubernetesPodImpl.class)
+public interface KubernetesPod extends DockerContainer {
+
+    ConfigKey<String> NAMESPACE = KubernetesLocationConfig.NAMESPACE;
+
+    ConfigKey<Boolean> PRIVILEGED = KubernetesLocationConfig.PRIVILEGED;
+
+    @SuppressWarnings("serial")
+    ConfigKey<List<String>> PERSISTENT_VOLUMES = ConfigKeys.builder(new 
TypeToken<List<String>>() {})
+            .name("persistentVolumes")
+            .description("Persistent volumes used by the pod")
+            .build();
+
+    ConfigKey<String> DEPLOYMENT = ConfigKeys.builder(String.class)
+            .name("deployment")
+            .description("The name of the service the deployed pod will use")
+            .build();
+
+    ConfigKey<Integer> REPLICAS = ConfigKeys.builder(Integer.class)
+            .name("replicas")
+            .description("Number of replicas in the pod")
+            .constraint(MathPredicates.greaterThanOrEqual(1d))
+            .defaultValue(1)
+            .build();
+
+    @SuppressWarnings("serial")
+    ConfigKey<Map<String, String>> SECRETS = ConfigKeys.builder(new 
TypeToken<Map<String, String>>() {})
+            .name("secrets")
+            .description("Secrets to be added to the pod")
+            .build();
+
+    @SuppressWarnings("serial")
+    ConfigKey<Map<String, String>> LIMITS = ConfigKeys.builder(new 
TypeToken<Map<String, String>>() {})
+            .name("limits")
+            .description("Container resource limits for the pod")
+            .build();
+
+    MapConfigKey<Object> METADATA = new 
MapConfigKey.Builder<Object>(Object.class, "metadata")
+            .description("Metadata to set on the pod")
+            .defaultValue(ImmutableMap.<String, Object>of())
+            .typeInheritance(BasicConfigInheritance.DEEP_MERGE)
+            
.runtimeInheritance(BasicConfigInheritance.NOT_REINHERITED_ELSE_DEEP_MERGE)
+            .build();
+
+    AttributeSensor<String> KUBERNETES_DEPLOYMENT = 
Sensors.builder(String.class, "kubernetes.deployment")
+            .description("Deployment resources run in")
+            .build();
+
+    AttributeSensor<String> KUBERNETES_NAMESPACE = 
Sensors.builder(String.class, "kubernetes.namespace")
+            .description("Namespace that resources run in")
+            .build();
+
+    AttributeSensor<String> KUBERNETES_SERVICE = Sensors.builder(String.class, 
"kubernetes.service")
+            .description("Service that exposes the deployment")
+            .build();
+
+    AttributeSensor<String> KUBERNETES_POD = Sensors.builder(String.class, 
"kubernetes.pod")
+            .description("Pod running the deployment")
+            .build();
+
+    String EMPTY = "Empty";
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPodImpl.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPodImpl.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPodImpl.java
new file mode 100644
index 0000000..2acf734
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesPodImpl.java
@@ -0,0 +1,7 @@
+package io.cloudsoft.amp.containerservice.kubernetes.entity;
+
+import io.cloudsoft.amp.containerservice.dockercontainer.DockerContainerImpl;
+
+public class KubernetesPodImpl extends DockerContainerImpl implements 
KubernetesPod {
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResource.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResource.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResource.java
new file mode 100644
index 0000000..320c924
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResource.java
@@ -0,0 +1,40 @@
+package io.cloudsoft.amp.containerservice.kubernetes.entity;
+
+import org.apache.brooklyn.api.entity.ImplementedBy;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.entity.software.base.SoftwareProcess;
+import org.apache.brooklyn.util.core.ResourcePredicates;
+
+@ImplementedBy(KubernetesResourceImpl.class)
+public interface KubernetesResource extends SoftwareProcess {
+
+    ConfigKey<String> RESOURCE_FILE = ConfigKeys.builder(String.class)
+                                             .name("resource")
+                                             .description("Kubernetes resource 
YAML file URI")
+                                             
.constraint(ResourcePredicates.urlExists())
+                                             .build();
+
+    AttributeSensor<String> RESOURCE_TYPE = Sensors.builder(String.class, 
"kubernetes.resource.type")
+                                             .description("Kubernetes resource 
type")
+                                             .build();
+
+    AttributeSensor<String> RESOURCE_NAME = Sensors.builder(String.class, 
"kubernetes.resource.name")
+                                             .description("Kubernetes resource 
name")
+                                             .build();
+
+    AttributeSensor<String> KUBERNETES_NAMESPACE = 
KubernetesPod.KUBERNETES_NAMESPACE;
+
+    String POD = "Pod";
+    String DEPLOYMENT = "Deployment";
+    String REPLICA_SET = "ReplicaSet";
+    String CONFIG_MAP = "ConfigMap";
+    String PERSISTENT_VOLUME = "PersistentVolume";
+    String SECRET = "Secret";
+    String SERVICE = "Service";
+    String REPLICATION_CONTROLLER = "ReplicationController";
+    String NAMESPACE = "Namespace";
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResourceImpl.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResourceImpl.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResourceImpl.java
new file mode 100644
index 0000000..3e7dad3
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/entity/kubernetes/KubernetesResourceImpl.java
@@ -0,0 +1,18 @@
+package io.cloudsoft.amp.containerservice.kubernetes.entity;
+
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.entity.software.base.EmptySoftwareProcessImpl;
+
+public class KubernetesResourceImpl extends EmptySoftwareProcessImpl 
implements KubernetesResource {
+
+    @Override
+    public void init() {
+        super.init();
+
+        config().set(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true);
+        config().set(PROVISIONING_PROPERTIES.subKey("useJcloudsSshInit"), 
false);
+        config().set(PROVISIONING_PROPERTIES.subKey("waitForSshable"), false);
+        
config().set(PROVISIONING_PROPERTIES.subKey("pollForFirstReachableAddress"), 
false);
+        config().set(EmptySoftwareProcessImpl.USE_SSH_MONITORING, false);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/ImageChooser.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/ImageChooser.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/ImageChooser.java
new file mode 100644
index 0000000..1bda432
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/ImageChooser.java
@@ -0,0 +1,71 @@
+package io.cloudsoft.amp.containerservice.kubernetes.location;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.jclouds.compute.domain.OsFamily;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+public class ImageChooser {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(ImageChooser.class);
+
+    public static class ImageMetadata {
+        private final OsFamily osFamily;
+        private final String osVersion;
+        private final String imageName;
+        
+        public ImageMetadata(OsFamily osFamily, String osVersion, String 
imageName) {
+            this.osFamily = checkNotNull(osFamily, "osFamily");
+            this.osVersion = checkNotNull(osVersion, "osVersion");
+            this.imageName = checkNotNull(imageName, "imageName");
+        }
+        
+        public boolean matches(@Nullable OsFamily osFamily, @Nullable String 
osVersionRegex) {
+            if (osFamily != null && osFamily != this.osFamily) return false;
+            if (osVersionRegex != null && !osVersion.matches(osVersionRegex)) 
return false;
+            return true;
+        }
+        
+        public String getImageName() {
+            return imageName;
+        }
+    }
+
+    private static final List<ImageMetadata> DEFAULT_IMAGES = ImmutableList.of(
+            new ImageMetadata(OsFamily.CENTOS, "7", "cloudsoft/centos:7"),
+            new ImageMetadata(OsFamily.UBUNTU, "14.04", 
"cloudsoft/ubuntu:14.04"),
+            new ImageMetadata(OsFamily.UBUNTU, "16.04", 
"cloudsoft/ubuntu:16.04"));
+    
+    private final List<ImageMetadata> images;
+
+    public ImageChooser() {
+        this.images = DEFAULT_IMAGES;
+    }
+    
+    public ImageChooser(List<? extends ImageMetadata> images) {
+        this.images = ImmutableList.copyOf(images);
+    }
+
+    public Optional<String> chooseImage(String osFamily, String 
osVersionRegex) {
+        return chooseImage((osFamily == null ? (OsFamily)null : 
OsFamily.fromValue(osFamily)), osVersionRegex);
+    }
+    
+    public Optional<String> chooseImage(OsFamily osFamily, String 
osVersionRegex) {
+        for (ImageMetadata imageMetadata : images) {
+            if (imageMetadata.matches(osFamily, osVersionRegex)) {
+                String imageName = imageMetadata.getImageName();
+                LOG.debug("Choosing container image {}, for osFamily={} and 
osVersionRegex={}", new Object[] {imageName, osFamily, osVersionRegex});
+                return Optional.of(imageName);
+            }
+        }
+        return Optional.absent();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCerts.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCerts.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCerts.java
new file mode 100644
index 0000000..9bb840f
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCerts.java
@@ -0,0 +1,64 @@
+package io.cloudsoft.amp.containerservice.kubernetes.location;
+
+import static 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig.CA_CERT_DATA;
+import static 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig.CA_CERT_FILE;
+import static 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig.CLIENT_CERT_DATA;
+import static 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig.CLIENT_CERT_FILE;
+import static 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig.CLIENT_KEY_ALGO;
+import static 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig.CLIENT_KEY_DATA;
+import static 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig.CLIENT_KEY_FILE;
+import static 
io.cloudsoft.amp.containerservice.kubernetes.location.KubernetesLocationConfig.CLIENT_KEY_PASSPHRASE;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+
+class KubernetesCerts {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(KubernetesCerts.class);
+
+    public final Optional<String> caCertData;
+    public final Optional<String> clientCertData;
+    public final Optional<String> clientKeyData;
+    public final Optional<String> clientKeyAlgo;
+    public final Optional<String> clientKeyPassphrase;
+    
+    public KubernetesCerts(ConfigBag config) {
+        caCertData = getData(CA_CERT_DATA, CA_CERT_FILE, config);
+        clientCertData = getData(CLIENT_CERT_DATA, CLIENT_CERT_FILE, config);
+        clientKeyData = getData(CLIENT_KEY_DATA, CLIENT_KEY_FILE, config);
+        clientKeyAlgo = getNonBlankOptional(CLIENT_KEY_ALGO, config);
+        clientKeyPassphrase = getNonBlankOptional(CLIENT_KEY_PASSPHRASE, 
config);
+    }
+
+    protected Optional<String> getData(ConfigKey<String> dataKey, 
ConfigKey<String> fileKey, ConfigBag config) {
+        String data = Strings.isNonBlank(config.get(dataKey)) ? 
config.get(dataKey).trim() : null;
+        String file = config.get(fileKey);
+        String fileData = Strings.isNonBlank(file) ? 
getFileContents(file).trim() : null;
+        
+        if (Strings.isNonBlank(data) && Strings.isNonBlank(fileData)) {
+            if (data.equals(fileData)) {
+                LOG.warn("Duplicate (matching) configuration for " + 
dataKey.getName() + " and " + fileKey.getName() + " (continuing)");
+            } else {
+                throw new IllegalStateException("Duplicate conflicting 
configuration for " + dataKey.getName() + " and " + fileKey.getName());
+            }
+        }
+        
+        String result = Strings.isNonBlank(data) ? data : 
(Strings.isNonBlank(fileData) ? fileData : null);
+        return Optional.fromNullable(result);
+    }
+
+    protected Optional<String> getNonBlankOptional(ConfigKey<? extends String> 
key, ConfigBag config) {
+        String result = config.get(key);
+        return Optional.fromNullable(Strings.isNonBlank(result) ? result : 
null);
+    }
+    
+    protected String getFileContents(String file) {
+        return ResourceUtils.create(this).getResourceAsString(file);
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistry.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistry.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistry.java
new file mode 100644
index 0000000..c240378
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistry.java
@@ -0,0 +1,11 @@
+package io.cloudsoft.amp.containerservice.kubernetes.location;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+public interface KubernetesClientRegistry {
+
+    KubernetesClient getKubernetesClient(ConfigBag conf);
+
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistryImpl.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistryImpl.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistryImpl.java
new file mode 100644
index 0000000..32bfee6
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesClientRegistryImpl.java
@@ -0,0 +1,78 @@
+package io.cloudsoft.amp.containerservice.kubernetes.location;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.google.common.base.Throwables;
+import com.google.common.io.BaseEncoding;
+
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+public class KubernetesClientRegistryImpl implements KubernetesClientRegistry {
+
+    public static final KubernetesClientRegistryImpl INSTANCE = new 
KubernetesClientRegistryImpl();
+
+    @Override
+    public KubernetesClient getKubernetesClient(ConfigBag conf) {
+        String masterUrl = 
checkNotNull(conf.get(KubernetesLocationConfig.MASTER_URL), "master url must 
not be null");
+
+        URL url;
+        try {
+            url = new URL(masterUrl);
+        } catch (MalformedURLException e) {
+            throw Throwables.propagate(e);
+        }
+
+        ConfigBuilder configBuilder = new ConfigBuilder()
+                .withMasterUrl(masterUrl)
+                .withTrustCerts(false);
+
+        if (url.getProtocol().equals("https")) {
+            KubernetesCerts certs = new KubernetesCerts(conf);
+            if (certs.caCertData.isPresent()) 
configBuilder.withCaCertData(toBase64Encoding(certs.caCertData.get()));
+            if (certs.clientCertData.isPresent()) 
configBuilder.withClientCertData(toBase64Encoding(certs.clientCertData.get()));
+            if (certs.clientKeyData.isPresent()) 
configBuilder.withClientKeyData(toBase64Encoding(certs.clientKeyData.get()));
+            if (certs.clientKeyAlgo.isPresent()) 
configBuilder.withClientKeyAlgo(certs.clientKeyAlgo.get());
+            if (certs.clientKeyPassphrase.isPresent()) 
configBuilder.withClientKeyPassphrase(certs.clientKeyPassphrase.get());
+            // TODO Should we also set configBuilder.withTrustCerts(true) here?
+        }
+
+        String username = conf.get(KubernetesLocationConfig.ACCESS_IDENTITY);
+        if (Strings.isNonBlank(username)) configBuilder.withUsername(username);
+
+        String password = conf.get(KubernetesLocationConfig.ACCESS_CREDENTIAL);
+        if (Strings.isNonBlank(password)) configBuilder.withPassword(password);
+
+        String token = conf.get(KubernetesLocationConfig.OAUTH_TOKEN);
+        if (Strings.isNonBlank(token)) configBuilder.withOauthToken(token);
+
+        Duration clientTimeout = 
conf.get(KubernetesLocationConfig.CLIENT_TIMEOUT);
+        if (clientTimeout.isPositive()) {
+            configBuilder.withConnectionTimeout((int) 
clientTimeout.toMilliseconds());
+            configBuilder.withRequestTimeout((int) 
clientTimeout.toMilliseconds());
+        } else {
+            throw new IllegalArgumentException("Kubernetes client timeout 
should be a positive duration: " + clientTimeout.toString());
+        }
+        Duration actionTimeout = 
conf.get(KubernetesLocationConfig.ACTION_TIMEOUT);
+        if (actionTimeout.isPositive()) {
+            configBuilder.withRollingTimeout(actionTimeout.toMilliseconds());
+            configBuilder.withScaleTimeout(actionTimeout.toMilliseconds());
+        } else {
+            throw new IllegalArgumentException("Kubernetes action timeout 
should be a positive duration: " + actionTimeout.toString());
+        }
+
+        return new DefaultKubernetesClient(configBuilder.build());
+    }
+
+    private String toBase64Encoding(String val) {
+        return BaseEncoding.base64().encode(val.getBytes());
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
new file mode 100644
index 0000000..e87866c
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocation.java
@@ -0,0 +1,1014 @@
+package io.cloudsoft.amp.containerservice.kubernetes.location;
+
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.location.LocationSpec;
+import org.apache.brooklyn.api.location.MachineLocation;
+import org.apache.brooklyn.api.location.MachineProvisioningLocation;
+import org.apache.brooklyn.api.location.NoMachinesAvailableException;
+import org.apache.brooklyn.api.location.PortRange;
+import org.apache.brooklyn.api.sensor.AttributeSensor;
+import org.apache.brooklyn.api.sensor.EnricherSpec;
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.entity.BrooklynConfigKeys;
+import org.apache.brooklyn.core.entity.EntityInternal;
+import org.apache.brooklyn.core.location.AbstractLocation;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.core.location.PortRanges;
+import org.apache.brooklyn.core.location.access.PortForwardManager;
+import 
org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver;
+import org.apache.brooklyn.core.location.cloud.CloudLocationConfig;
+import org.apache.brooklyn.core.network.OnPublicNetworkEnricher;
+import org.apache.brooklyn.core.sensor.Sensors;
+import org.apache.brooklyn.location.ssh.SshMachineLocation;
+import org.apache.brooklyn.util.collections.MutableList;
+import org.apache.brooklyn.util.collections.MutableMap;
+import org.apache.brooklyn.util.core.ResourceUtils;
+import org.apache.brooklyn.util.core.config.ConfigBag;
+import org.apache.brooklyn.util.core.config.ResolvingConfigBag;
+import org.apache.brooklyn.util.core.internal.ssh.SshTool;
+import org.apache.brooklyn.util.core.text.TemplateProcessor;
+import org.apache.brooklyn.util.exceptions.ReferenceWithError;
+import org.apache.brooklyn.util.net.Networking;
+import org.apache.brooklyn.util.repeat.Repeater;
+import org.apache.brooklyn.util.stream.Streams;
+import org.apache.brooklyn.util.text.Identifiers;
+import org.apache.brooklyn.util.text.Strings;
+import org.apache.brooklyn.util.time.Duration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Functions;
+import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.io.BaseEncoding;
+import com.google.common.net.HostAndPort;
+
+import io.cloudsoft.amp.containerservice.dockercontainer.DockerContainer;
+import io.cloudsoft.amp.containerservice.dockerlocation.DockerJcloudsLocation;
+import io.cloudsoft.amp.containerservice.kubernetes.entity.KubernetesPod;
+import io.cloudsoft.amp.containerservice.kubernetes.entity.KubernetesResource;
+import 
io.cloudsoft.amp.containerservice.kubernetes.location.machine.KubernetesEmptyMachineLocation;
+import 
io.cloudsoft.amp.containerservice.kubernetes.location.machine.KubernetesMachineLocation;
+import 
io.cloudsoft.amp.containerservice.kubernetes.location.machine.KubernetesSshMachineLocation;
+import io.fabric8.kubernetes.api.model.Container;
+import io.fabric8.kubernetes.api.model.ContainerBuilder;
+import io.fabric8.kubernetes.api.model.ContainerPort;
+import io.fabric8.kubernetes.api.model.ContainerPortBuilder;
+import io.fabric8.kubernetes.api.model.EndpointAddress;
+import io.fabric8.kubernetes.api.model.EndpointSubset;
+import io.fabric8.kubernetes.api.model.Endpoints;
+import io.fabric8.kubernetes.api.model.EnvVar;
+import io.fabric8.kubernetes.api.model.EnvVarBuilder;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.api.model.Namespace;
+import io.fabric8.kubernetes.api.model.NamespaceBuilder;
+import io.fabric8.kubernetes.api.model.PersistentVolume;
+import io.fabric8.kubernetes.api.model.PersistentVolumeBuilder;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.api.model.PodList;
+import io.fabric8.kubernetes.api.model.PodTemplateSpec;
+import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder;
+import io.fabric8.kubernetes.api.model.QuantityBuilder;
+import io.fabric8.kubernetes.api.model.ReplicationController;
+import io.fabric8.kubernetes.api.model.ResourceRequirements;
+import io.fabric8.kubernetes.api.model.ResourceRequirementsBuilder;
+import io.fabric8.kubernetes.api.model.Secret;
+import io.fabric8.kubernetes.api.model.SecretBuilder;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.ServiceBuilder;
+import io.fabric8.kubernetes.api.model.ServicePort;
+import io.fabric8.kubernetes.api.model.ServicePortBuilder;
+import io.fabric8.kubernetes.api.model.extensions.Deployment;
+import io.fabric8.kubernetes.api.model.extensions.DeploymentBuilder;
+import io.fabric8.kubernetes.api.model.extensions.DeploymentStatus;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientException;
+
+public class KubernetesLocation extends AbstractLocation implements 
MachineProvisioningLocation<KubernetesMachineLocation>, 
KubernetesLocationConfig {
+
+    /*
+     * TODO
+     *
+     *  - Ignores config such as 'user' and 'password', just uses 'loginUser'
+     *    and 'loginUser.password' for connecting to the container.
+     *  - Does not create a user, so behaves differently from things that use
+     *    JcloudsLocation.
+     *  - Does not use ssh keys only passwords.
+     *  - The 'cloudsoft/*' images use root which is discouraged.
+     *  - Error handling needs revisited. For example, if provisioning fails 
then
+     *    it waits for five minutes and then fails without a reason why.
+     *    e.g. try launching a container with an incorrect image name.
+     */
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(KubernetesLocation.class);
+
+    public static final String NODE_PORT = "NodePort";
+
+    public static final String IMMUTABLE_CONTAINER_KEY = "immutable-container";
+    public static final String SSHABLE_CONTAINER = "sshable-container";
+    public static final String CLOUDSOFT_ENTITY_ID = "cloudsoft.io/entity-id";
+    public static final String CLOUDSOFT_APPLICATION_ID = 
"cloudsoft.io/application-id";
+    public static final String KUBERNETES_DOCKERCFG = 
"kubernetes.io/dockercfg";
+
+    public static final String PHASE_AVAILABLE = "Available";
+    public static final String PHASE_TERMINATING = "Terminating";
+    public static final String PHASE_ACTIVE = "Active";
+
+    /**
+     * The regex for the image descriptions that support us injecting login 
credentials.
+     */
+    public static final List<String> 
IMAGE_DESCRIPTION_REGEXES_REQUIRING_INJECTED_LOGIN_CREDS = ImmutableList.of(
+            "cloudsoft/centos.*",
+            "cloudsoft/ubuntu.*");
+
+    /** The environment variable for injecting login credentials. */
+    public static final String CLOUDSOFT_ROOT_PASSWORD = 
"CLOUDSOFT_ROOT_PASSWORD";
+
+    private KubernetesClient client;
+
+    public KubernetesLocation() {
+        super();
+    }
+
+    public KubernetesLocation(Map<?, ?> properties) {
+        super(properties);
+    }
+
+    @Override
+    public void init() {
+        super.init();
+    }
+
+    protected KubernetesClient getClient() {
+        return getClient(MutableMap.of());
+    }
+
+    protected KubernetesClient getClient(Map<?, ?> flags) {
+        ConfigBag conf = (flags == null || flags.isEmpty())
+                ? config().getBag()
+                : ConfigBag.newInstanceExtending(config().getBag(), flags);
+        return getClient(conf);
+    }
+
+    protected KubernetesClient getClient(ConfigBag config) {
+        if (client == null) {
+            KubernetesClientRegistry registry = 
getConfig(KUBERNETES_CLIENT_REGISTRY);
+            client = 
registry.getKubernetesClient(ResolvingConfigBag.newInstanceExtending(getManagementContext(),
 config));
+        }
+        return client;
+    }
+
+    @Override
+    public KubernetesMachineLocation obtain(Map<?, ?> flags) throws 
NoMachinesAvailableException {
+        ConfigBag setupRaw = ConfigBag.newInstanceExtending(config().getBag(), 
flags);
+        ConfigBag setup = 
ResolvingConfigBag.newInstanceExtending(getManagementContext(), setupRaw);
+
+        client = getClient(setup);
+        Entity entity = validateCallerContext(setup);
+        if (isKubernetesResource(entity)) {
+            return createKubernetesResourceLocation(entity, setup);
+        } else {
+            return createKubernetesContainerLocation(entity, setup);
+        }
+    }
+
+    @Override
+    public void release(KubernetesMachineLocation machine) {
+        Entity entity = validateCallerContext(machine);
+        if (isKubernetesResource(entity)) {
+            deleteKubernetesResourceLocation(entity);
+        } else {
+            deleteKubernetesContainerLocation(entity, machine);
+        }
+    }
+
+    protected void deleteKubernetesContainerLocation(Entity entity, 
MachineLocation machine) {
+        final String namespace = 
entity.sensors().get(KubernetesPod.KUBERNETES_NAMESPACE);
+        final String deployment = 
entity.sensors().get(KubernetesPod.KUBERNETES_DEPLOYMENT);
+        final String pod = entity.sensors().get(KubernetesPod.KUBERNETES_POD);
+        final String service = 
entity.sensors().get(KubernetesPod.KUBERNETES_SERVICE);
+
+        undeploy(namespace, deployment, pod);
+
+        client.services().inNamespace(namespace).withName(service).delete();
+        ExitCondition exitCondition = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                return 
client.services().inNamespace(namespace).withName(service).get() == null;
+            }
+            @Override
+            public String getFailureMessage() {
+                return "No service with namespace=" + namespace + ", 
serviceName=" + service;
+            }
+        };
+        waitForExitCondition(exitCondition);
+
+        Boolean delete = machine.config().get(DELETE_EMPTY_NAMESPACE);
+        if (delete) {
+            deleteEmptyNamespace(namespace);
+        }
+    }
+
+    protected void deleteKubernetesResourceLocation(Entity entity) {
+        final String namespace = 
entity.sensors().get(KubernetesPod.KUBERNETES_NAMESPACE);
+        final String resourceType = 
entity.sensors().get(KubernetesResource.RESOURCE_TYPE);
+        final String resourceName = 
entity.sensors().get(KubernetesResource.RESOURCE_NAME);
+
+        if (!handleResourceDelete(resourceType, resourceName, namespace)) {
+            LOG.warn("Resource {}: {} not deleted", resourceName, 
resourceType);
+        }
+    }
+
+    protected boolean handleResourceDelete(String resourceType, String 
resourceName, String namespace) {
+        try {
+            switch (resourceType) {
+                case KubernetesResource.DEPLOYMENT:
+                    return 
client.extensions().deployments().inNamespace(namespace).withName(resourceName).delete();
+                case KubernetesResource.REPLICA_SET:
+                    return 
client.extensions().replicaSets().inNamespace(namespace).withName(resourceName).delete();
+                case KubernetesResource.CONFIG_MAP:
+                    return 
client.configMaps().inNamespace(namespace).withName(resourceName).delete();
+                case KubernetesResource.PERSISTENT_VOLUME:
+                    return 
client.persistentVolumes().withName(resourceName).delete();
+                case KubernetesResource.SECRET:
+                    return 
client.secrets().inNamespace(namespace).withName(resourceName).delete();
+                case KubernetesResource.SERVICE:
+                    return 
client.services().inNamespace(namespace).withName(resourceName).delete();
+                case KubernetesResource.REPLICATION_CONTROLLER:
+                    return 
client.replicationControllers().inNamespace(namespace).withName(resourceName).delete();
+                case KubernetesResource.NAMESPACE:
+                    return client.namespaces().withName(resourceName).delete();
+            }
+        } catch (KubernetesClientException kce) {
+            LOG.warn("Error deleting resource {}: {}", resourceName, kce);
+        }
+        return false;
+    }
+
+    protected void undeploy(final String namespace, final String deployment, 
final String pod) {
+        
client.extensions().deployments().inNamespace(namespace).withName(deployment).delete();
+        ExitCondition exitCondition = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                return 
client.extensions().deployments().inNamespace(namespace).withName(deployment).get()
 == null;
+            }
+            @Override
+            public String getFailureMessage() {
+                return "No deployment with namespace=" + namespace + ", 
deployment=" + deployment;
+            }
+        };
+        waitForExitCondition(exitCondition);
+    }
+
+    protected synchronized void deleteEmptyNamespace(final String name) {
+        if (!name.equals("default") && isNamespaceEmpty(name)) {
+            if (client.namespaces().withName(name).get() != null &&
+                    
!client.namespaces().withName(name).get().getStatus().getPhase().equals(PHASE_TERMINATING))
 {
+                client.namespaces().withName(name).delete();
+                ExitCondition exitCondition = new ExitCondition() {
+                    @Override
+                    public Boolean call() {
+                        return client.namespaces().withName(name).get() == 
null;
+                    }
+                    @Override
+                    public String getFailureMessage() {
+                        return "Namespace " + name + " still present";
+                    }
+                };
+                waitForExitCondition(exitCondition);
+            }
+        }
+    }
+
+    protected boolean isNamespaceEmpty(String name) {
+        return 
client.extensions().deployments().inNamespace(name).list().getItems().isEmpty() 
&&
+               client.services().inNamespace(name).list().getItems().isEmpty() 
&&
+               client.secrets().inNamespace(name).list().getItems().isEmpty();
+    }
+
+    @Override
+    public Map<String, Object> getProvisioningFlags(Collection<String> tags) {
+        return null;
+    }
+
+    protected KubernetesMachineLocation 
createKubernetesResourceLocation(Entity entity, ConfigBag setup) {
+        String resourceUri = 
entity.config().get(KubernetesResource.RESOURCE_FILE);
+        InputStream resource = 
ResourceUtils.create(entity).getResourceFromUrl(resourceUri);
+        String templateContents = Streams.readFullyString(resource);
+        String processedContents = 
TemplateProcessor.processTemplateContents(templateContents, (EntityInternal) 
entity, setup.getAllConfig());
+        InputStream processedResource = 
Streams.newInputStreamWithContents(processedContents);
+
+        final List<HasMetadata> result = 
getClient().load(processedResource).createOrReplace();
+
+        ExitCondition exitCondition = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                if (result.isEmpty()) {
+                    return false;
+                }
+                List<HasMetadata> check = 
client.resource(result.get(0)).inNamespace(result.get(0).getMetadata().getNamespace()).get();
+                if (result.size() > 1 || check.size() != 1 || 
check.get(0).getMetadata() == null) {
+                    return false;
+                }
+                return true;
+            }
+            @Override
+            public String getFailureMessage() {
+                return "Cannot find created resources";
+            }
+        };
+        waitForExitCondition(exitCondition);
+
+        HasMetadata metadata = result.get(0);
+        String resourceType = metadata.getKind();
+        String resourceName = metadata.getMetadata().getName();
+        String namespace = metadata.getMetadata().getNamespace();
+        LOG.debug("Resource {} (type {}) deployed to {}", resourceName, 
resourceType, namespace);
+
+        entity.sensors().set(KubernetesPod.KUBERNETES_NAMESPACE, namespace);
+        entity.sensors().set(KubernetesResource.RESOURCE_NAME, resourceName);
+        entity.sensors().set(KubernetesResource.RESOURCE_TYPE, resourceType);
+
+        LocationSpec<? extends KubernetesMachineLocation> locationSpec = 
LocationSpec.create(KubernetesSshMachineLocation.class);
+        if (!findResourceAddress(locationSpec, entity, metadata, resourceType, 
resourceName, namespace)) {
+            LOG.info("Resource {} with type {} has no associated address", 
resourceName, resourceType);
+            locationSpec = 
LocationSpec.create(KubernetesEmptyMachineLocation.class);
+        }
+        locationSpec.configure(CALLER_CONTEXT, setup.get(CALLER_CONTEXT))
+                .configure(KubernetesMachineLocation.KUBERNETES_NAMESPACE, 
namespace)
+                .configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_NAME, 
resourceName)
+                .configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_TYPE, 
resourceType);
+
+        KubernetesMachineLocation machine = 
getManagementContext().getLocationManager().createLocation(locationSpec);
+
+        if (resourceType.equals(KubernetesResource.SERVICE) && machine 
instanceof KubernetesSshMachineLocation) {
+            Service service = getService(namespace, resourceName);
+            registerPortMappings((KubernetesSshMachineLocation) machine, 
entity, service);
+        }
+
+        return machine;
+    }
+
+    protected boolean findResourceAddress(LocationSpec<? extends 
KubernetesMachineLocation> locationSpec, Entity entity, HasMetadata metadata, 
String resourceType, String resourceName, String namespace) {
+        if (resourceType.equals(KubernetesResource.DEPLOYMENT) || 
resourceType.equals(KubernetesResource.REPLICATION_CONTROLLER) || 
resourceType.equals(KubernetesResource.POD)) {
+            Map<String, String> labels = MutableMap.of();
+            if (resourceType.equals(KubernetesResource.DEPLOYMENT)) {
+                Deployment deployment = (Deployment) metadata;
+                labels = 
deployment.getSpec().getTemplate().getMetadata().getLabels();
+            } else if 
(resourceType.equals(KubernetesResource.REPLICATION_CONTROLLER)) {
+                ReplicationController replicationController = 
(ReplicationController) metadata;
+                labels = 
replicationController.getSpec().getTemplate().getMetadata().getLabels();
+            }
+            Pod pod = resourceType.equals(KubernetesResource.POD) ? 
getPod(namespace, resourceName) : getPod(namespace, labels);
+            entity.sensors().set(KubernetesPod.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 if (resourceType.equals(KubernetesResource.SERVICE)) {
+            getService(namespace, resourceName);
+            Endpoints endpoints = 
client.endpoints().inNamespace(namespace).withName(resourceName).get();
+            Set<String> privateIps = Sets.newLinkedHashSet();
+            Set<String> podNames = Sets.newLinkedHashSet();
+            for (EndpointSubset subset : endpoints.getSubsets()) {
+                for (EndpointAddress address : subset.getAddresses()) {
+                    String podName = address.getTargetRef().getName();
+                    podNames.add(podName);
+                    String privateIp = address.getIp();
+                    privateIps.add(privateIp);
+                }
+            }
+            locationSpec.configure(SshMachineLocation.PRIVATE_ADDRESSES, 
ImmutableSet.copyOf(privateIps));
+
+            if (podNames.size() > 0) {
+                // Use the first pod name from the list; warn when multiple 
pods are referenced
+                String podName = Iterables.get(podNames, 0);
+                if (podNames.size() > 1) {
+                    LOG.warn("Multiple pods referenced by service {} in 
namespace {}, using {}: {}",
+                            new Object[] { resourceName, namespace, podName, 
Iterables.toString(podNames) });
+                }
+                try {
+                    Pod pod = getPod(namespace, podName);
+                    entity.sensors().set(KubernetesPod.KUBERNETES_POD, 
podName);
+
+                    InetAddress node = 
Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName());
+                    locationSpec.configure("address", node);
+                } catch (KubernetesClientException kce) {
+                    LOG.warn("Cannot find pod {} in namespace {} for service 
{}", new Object[] { podName, namespace, resourceName });
+                }
+            }
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    protected KubernetesMachineLocation 
createKubernetesContainerLocation(Entity entity, ConfigBag setup) {
+        String deploymentName = lookup(KubernetesPod.DEPLOYMENT, entity, 
setup, entity.getId());
+        Integer replicas = lookup(KubernetesPod.REPLICAS, entity, setup);
+        List<String> volumes = lookup(KubernetesPod.PERSISTENT_VOLUMES, 
entity, setup);
+        Map<String, String> secrets = lookup(KubernetesPod.SECRETS, entity, 
setup);
+        Map<String, String> limits = lookup(KubernetesPod.LIMITS, entity, 
setup);
+        Boolean privileged = lookup(KubernetesPod.PRIVILEGED, entity, setup);
+        String imageName = findImageName(entity, setup);
+        Iterable<Integer> inboundPorts = findInboundPorts(entity, setup);
+        Map<String, String> env = findEnvironmentVariables(entity, setup, 
imageName);
+        Map<String, String> metadata = findMetadata(entity, setup, 
deploymentName);
+
+        if (volumes != null) {
+            createPersistentVolumes(volumes);
+        }
+
+        Namespace namespace = createOrGetNamespace(lookup(NAMESPACE, entity, 
setup), setup.get(CREATE_NAMESPACE));
+
+        if (secrets != null) {
+            createSecrets(namespace.getMetadata().getName(), secrets);
+        }
+
+        Container container = 
buildContainer(namespace.getMetadata().getName(), metadata, deploymentName, 
imageName, inboundPorts, env, limits, privileged);
+        deploy(namespace.getMetadata().getName(), entity, metadata, 
deploymentName, container, replicas, secrets);
+        Service service = exposeService(namespace.getMetadata().getName(), 
metadata, deploymentName, inboundPorts);
+        Pod pod = getPod(namespace.getMetadata().getName(), metadata);
+
+        entity.sensors().set(KubernetesPod.KUBERNETES_NAMESPACE, 
namespace.getMetadata().getName());
+        entity.sensors().set(KubernetesPod.KUBERNETES_DEPLOYMENT, 
deploymentName);
+        entity.sensors().set(KubernetesPod.KUBERNETES_POD, 
pod.getMetadata().getName());
+        entity.sensors().set(KubernetesPod.KUBERNETES_SERVICE, 
service.getMetadata().getName());
+
+        LocationSpec<KubernetesSshMachineLocation> locationSpec = 
prepareSshableLocationSpec(entity, setup, namespace, deploymentName, service, 
pod)
+                .configure(KubernetesMachineLocation.KUBERNETES_NAMESPACE, 
namespace.getMetadata().getName())
+                .configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_NAME, 
deploymentName)
+                .configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_TYPE, 
getContainerResourceType());
+
+        KubernetesSshMachineLocation machine = 
getManagementContext().getLocationManager().createLocation(locationSpec);
+        registerPortMappings(machine, entity, service);
+        if (!isDockerContainer(entity)) {
+            waitForSshable(machine, Duration.FIVE_MINUTES);
+        }
+
+        return machine;
+    }
+
+    protected String getContainerResourceType() {
+        return KubernetesResource.DEPLOYMENT;
+    }
+
+    protected void waitForSshable(final SshMachineLocation machine, Duration 
timeout) {
+        Callable<Boolean> checker = new Callable<Boolean>() {
+            public Boolean call() {
+                int exitstatus = machine.execScript(
+                        ImmutableMap.of( // TODO investigate why SSH 
connection does not time out with this config
+                                SshTool.PROP_CONNECT_TIMEOUT.getName(), 
Duration.TEN_SECONDS.toMilliseconds(),
+                                SshTool.PROP_SESSION_TIMEOUT.getName(), 
Duration.TEN_SECONDS.toMilliseconds(),
+                                SshTool.PROP_SSH_TRIES_TIMEOUT.getName(), 
Duration.TEN_SECONDS.toMilliseconds(),
+                                SshTool.PROP_SSH_TRIES.getName(), 1),
+                        "check-sshable",
+                        ImmutableList.of("true"));
+                boolean success = (exitstatus == 0);
+                return success;
+            }};
+
+        Stopwatch stopwatch = Stopwatch.createStarted();
+        ReferenceWithError<Boolean> reachable = Repeater.create("reachable")
+                .threaded()
+                .backoff(Duration.FIVE_SECONDS, 2, Duration.TEN_SECONDS) // 
Exponential backoff, to 10 seconds
+                .until(checker)
+                .limitTimeTo(timeout)
+                .runKeepingError();
+        if (!reachable.getWithoutError()) {
+            throw new IllegalStateException("Connection failed for 
"+machine.getSshHostAndPort()+" after waiting 
"+stopwatch.elapsed(TimeUnit.SECONDS), reachable.getError());
+        } else {
+            LOG.debug("Connection succeeded for {} after {}", 
machine.getSshHostAndPort(), stopwatch.elapsed(TimeUnit.SECONDS));
+        }
+    }
+
+    protected void registerPortMappings(KubernetesSshMachineLocation machine, 
Entity entity, Service service) {
+        PortForwardManager portForwardManager = (PortForwardManager) 
getManagementContext().getLocationRegistry()
+                
.getLocationManaged(PortForwardManagerLocationResolver.PFM_GLOBAL_SPEC);
+        List<ServicePort> ports = service.getSpec().getPorts();
+        String publicHostText = ((SshMachineLocation) 
machine).getSshHostAndPort().getHostText();
+        LOG.debug("Recording port-mappings for container {} of {}: {}", new 
Object[] { machine, this, ports });
+
+        for (ServicePort port : ports) {
+            String protocol = port.getProtocol();
+            Integer targetPort = port.getTargetPort().getIntVal();
+
+            if (!"TCP".equalsIgnoreCase(protocol)) {
+                LOG.debug("Ignoring port mapping {} for {} because only TCP is 
currently supported", port, machine);
+            } else if (targetPort == null) {
+                LOG.debug("Ignoring port mapping {} for {} because 
targetPort.intValue is null", port, machine);
+            } else if (port.getNodePort() == null) {
+                LOG.debug("Ignoring port mapping {} to {} because 
port.getNodePort() is null", targetPort, machine);
+            } else {
+                portForwardManager.associate(publicHostText, 
HostAndPort.fromParts(publicHostText, port.getNodePort()), machine, targetPort);
+                AttributeSensor<Integer> sensor = 
Sensors.newIntegerSensor("kubernetes." + 
Strings.maybeNonBlank(port.getName()).or(targetPort.toString()) + ".port");
+                entity.sensors().set(sensor, targetPort);
+            }
+        }
+
+        
entity.enrichers().add(EnricherSpec.create(OnPublicNetworkEnricher.class).configure(OnPublicNetworkEnricher.MAP_MATCHING,
 "kubernetes.[a-zA-Z0-9][a-zA-Z0-9-_]*.port"));
+    }
+
+    protected synchronized Namespace createOrGetNamespace(final String name, 
Boolean create) {
+        Namespace namespace = client.namespaces().withName(name).get();
+        ExitCondition namespaceReady = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                Namespace actualNamespace = 
client.namespaces().withName(name).get();
+                return actualNamespace != null && 
actualNamespace.getStatus().getPhase().equals(PHASE_ACTIVE);
+            }
+            @Override
+            public String getFailureMessage() {
+                Namespace actualNamespace = 
client.namespaces().withName(name).get();
+                return "Namespace for " + name + " " + (actualNamespace == 
null ? "absent" : " status " + actualNamespace.getStatus());
+            }
+        };
+        if (namespace != null) {
+            LOG.debug("Found namespace {}, returning it.", namespace);
+        } else if (create) {
+            namespace = client.namespaces().create(new 
NamespaceBuilder().withNewMetadata().withName(name).endMetadata().build());
+            LOG.debug("Created namespace {}.", namespace);
+        } else {
+            throw new IllegalStateException("Namespace " + name + " does not 
exist and namespace.create is not set");
+        }
+        waitForExitCondition(namespaceReady);
+        return client.namespaces().withName(name).get();
+    }
+
+    protected Pod getPod(final String namespace, final String name) {
+        ExitCondition exitCondition = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                Pod result = 
client.pods().inNamespace(namespace).withName(name).get();
+                return result != null && result.getStatus().getPodIP() != null;
+            }
+            @Override
+            public String getFailureMessage() {
+                return "Cannot find pod with name: " + name;
+            }
+        };
+        waitForExitCondition(exitCondition);
+        Pod result = client.pods().inNamespace(namespace).withName(name).get();
+        return result;
+    }
+
+    protected Pod getPod(final String namespace, final Map<String, String> 
metadata) {
+        ExitCondition exitCondition = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                PodList result = 
client.pods().inNamespace(namespace).withLabels(metadata).list();
+                return result.getItems().size() >= 1 && 
result.getItems().get(0).getStatus().getPodIP() != null;
+            }
+            @Override
+            public String getFailureMessage() {
+                return "Cannot find pod with metadata: " + Joiner.on(" 
").withKeyValueSeparator("=").join(metadata);
+            }
+        };
+        waitForExitCondition(exitCondition);
+        PodList result = 
client.pods().inNamespace(namespace).withLabels(metadata).list();
+        return result.getItems().get(0);
+    }
+
+    protected void createSecrets(String namespace, Map<String, String> 
secrets) {
+        for (Map.Entry<String, String> nameAuthEntry : secrets.entrySet()) {
+            createSecret(namespace, nameAuthEntry.getKey(), 
nameAuthEntry.getValue());
+        }
+    }
+
+    protected Secret createSecret(final String namespace, final String 
secretName, String auth) {
+        Secret secret = 
client.secrets().inNamespace(namespace).withName(secretName).get();
+        if (secret != null) return secret;
+
+        String json = 
String.format("{\"https://index.docker.io/v1/\":{\"auth\":\"%s\"}}";, auth);
+        String base64encoded = 
BaseEncoding.base64().encode(json.getBytes(Charset.defaultCharset()));
+        secret = new SecretBuilder()
+                        .withNewMetadata()
+                            .withName(secretName)
+                        .endMetadata()
+                        .withType(KUBERNETES_DOCKERCFG)
+                        .withData(ImmutableMap.of(".dockercfg", base64encoded))
+                        .build();
+        try {
+            client.secrets().inNamespace(namespace).create(secret);
+        } catch (KubernetesClientException e) {
+            if (e.getCode() == 500 && e.getMessage().contains("Message: 
resourceVersion may not be set on objects to be created")) {
+                // ignore exception as per 
https://github.com/fabric8io/kubernetes-client/issues/451
+            } else {
+                throw Throwables.propagate(e);
+            }
+        }
+        ExitCondition exitCondition = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                return 
client.secrets().inNamespace(namespace).withName(secretName).get() != null;
+            }
+            @Override
+            public String getFailureMessage() {
+                return "Absent namespace=" + namespace + ", secretName=" + 
secretName;
+            }
+        };
+        waitForExitCondition(exitCondition);
+        return 
client.secrets().inNamespace(namespace).withName(secretName).get();
+    }
+
+    protected Container buildContainer(String namespace, Map<String, String> 
metadata, String deploymentName, String imageName, Iterable<Integer> 
inboundPorts, Map<String, ?> env, Map<String, String> limits, boolean 
privileged) {
+        List<ContainerPort> containerPorts = Lists.newArrayList();
+        for (Integer inboundPort : inboundPorts) {
+            containerPorts.add(new 
ContainerPortBuilder().withContainerPort(inboundPort).build());
+        }
+
+        List<EnvVar> envVars = Lists.newArrayList();
+        for (Map.Entry<String, ?> envVarEntry : env.entrySet()) {
+            envVars.add(new 
EnvVarBuilder().withName(envVarEntry.getKey()).withValue(envVarEntry.getValue().toString()).build());
+        }
+
+        ContainerBuilder containerBuilder = new ContainerBuilder()
+                .withName(deploymentName)
+                .withImage(imageName)
+                .addToPorts(Iterables.toArray(containerPorts, 
ContainerPort.class))
+                .addToEnv(Iterables.toArray(envVars, EnvVar.class))
+                .withNewSecurityContext()
+                     .withPrivileged(privileged)
+                .endSecurityContext();
+
+        if (limits != null) {
+            for (Map.Entry<String, String> nameValueEntry : limits.entrySet()) 
{
+                ResourceRequirements resourceRequirements = new 
ResourceRequirementsBuilder().addToLimits(nameValueEntry.getKey(), new 
QuantityBuilder().withAmount(nameValueEntry.getValue()).build()).build();
+                containerBuilder.withResources(resourceRequirements);
+            }
+        }
+        LOG.debug("Built container {} to be deployed in namespace {} with 
metadata {}.", containerBuilder.build(), namespace, metadata);
+        return containerBuilder.build();
+    }
+
+    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();
+        Deployment deployment = new DeploymentBuilder()
+                .withNewMetadata()
+                    .withName(deploymentName)
+                    .addToAnnotations(CLOUDSOFT_ENTITY_ID, entity.getId())
+                    .addToAnnotations(CLOUDSOFT_APPLICATION_ID, 
entity.getApplicationId())
+                .endMetadata()
+                .withNewSpec()
+                    .withReplicas(replicas)
+                    .withTemplate(template)
+                .endSpec()
+                .build();
+        
client.extensions().deployments().inNamespace(namespace).create(deployment);
+        ExitCondition exitCondition = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                Deployment dep = 
client.extensions().deployments().inNamespace(namespace).withName(deploymentName).get();
+                DeploymentStatus status = (dep == null) ? null : 
dep.getStatus();
+                Integer replicas = (status == null) ? null : 
status.getAvailableReplicas();
+                return replicas != null && replicas.intValue() == replicas;
+            }
+            @Override
+            public String getFailureMessage() {
+                Deployment dep = 
client.extensions().deployments().inNamespace(namespace).withName(deploymentName).get();
+                DeploymentStatus status = (dep == null) ? null : 
dep.getStatus();
+                return "Namespace=" + namespace + "; deploymentName= " + 
deploymentName + "; Deployment=" + dep
+                        + "; status=" + status
+                        + "; availableReplicas=" + (status == null ? "null" : 
status.getAvailableReplicas());
+            }
+        };
+        waitForExitCondition(exitCondition);
+        LOG.debug("Deployed deployment {} in namespace {}.", deployment, 
namespace);
+    }
+
+    protected Service exposeService(String namespace, Map<String, String> 
metadata, String serviceName, Iterable<Integer> inboundPorts) {
+        List<ServicePort> servicePorts = Lists.newArrayList();
+        for (Integer inboundPort : inboundPorts) {
+            servicePorts.add(new 
ServicePortBuilder().withName(Integer.toString(inboundPort)).withPort(inboundPort).build());
+        }
+        Service service = new 
ServiceBuilder().withNewMetadata().withName(serviceName).endMetadata()
+                .withNewSpec()
+                    .addToSelector(metadata)
+                    .addToPorts(Iterables.toArray(servicePorts, 
ServicePort.class))
+                    .withType(NODE_PORT)
+                .endSpec()
+                .build();
+        client.services().inNamespace(namespace).create(service);
+
+        service = getService(namespace, serviceName);
+        LOG.debug("Exposed service {} in namespace {}.", service, namespace);
+        return service;
+    }
+
+    protected Service getService(final String namespace, final String 
serviceName) {
+        ExitCondition exitCondition = new ExitCondition() {
+            @Override
+            public Boolean call() {
+                Service svc = 
client.services().inNamespace(namespace).withName(serviceName).get();
+                if (svc == null || svc.getStatus() == null) {
+                    return false;
+                }
+                Endpoints endpoints = 
client.endpoints().inNamespace(namespace).withName(serviceName).get();
+                if (endpoints == null || endpoints.getSubsets().isEmpty()) {
+                    return false;
+                }
+                for (EndpointSubset subset : endpoints.getSubsets()) {
+                    if (subset.getNotReadyAddresses().size() > 0) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+            @Override
+            public String getFailureMessage() {
+                Endpoints endpoints = 
client.endpoints().inNamespace(namespace).withName(serviceName).get();
+                return "Service endpoints in " + namespace + " for 
serviceName= " + serviceName + " not ready: " + endpoints;
+            }
+        };
+        waitForExitCondition(exitCondition);
+
+        return 
client.services().inNamespace(namespace).withName(serviceName).get();
+    }
+
+    protected LocationSpec<KubernetesSshMachineLocation> 
prepareSshableLocationSpec(Entity entity, ConfigBag setup, Namespace namespace, 
String deploymentName, Service service, Pod pod) {
+        InetAddress node = 
Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName());
+        String podAddress = pod.getStatus().getPodIP();
+        LocationSpec<KubernetesSshMachineLocation> locationSpec = 
LocationSpec.create(KubernetesSshMachineLocation.class)
+                .configure("address", node)
+                .configure(SshMachineLocation.PRIVATE_ADDRESSES, 
ImmutableSet.of(podAddress))
+                .configure(CALLER_CONTEXT, setup.get(CALLER_CONTEXT));
+        if (!isDockerContainer(entity)) {
+            Optional<ServicePort> sshPort = 
Iterables.tryFind(service.getSpec().getPorts(), new Predicate<ServicePort>() {
+                    @Override
+                    public boolean apply(ServicePort input) {
+                        return input.getProtocol().equalsIgnoreCase("TCP") && 
input.getPort().intValue() == 22;
+                    }
+                });
+            Optional<Integer> sshPortNumber;
+            if (sshPort.isPresent()) {
+                sshPortNumber = Optional.of(sshPort.get().getNodePort());
+            } else {
+                LOG.warn("No port-mapping found to ssh port 22, for container 
{}", service);
+                sshPortNumber = Optional.absent();
+            }
+            locationSpec.configure(CloudLocationConfig.USER, 
setup.get(KubernetesLocationConfig.LOGIN_USER))
+                    .configure(SshMachineLocation.PASSWORD, 
setup.get(KubernetesLocationConfig.LOGIN_USER_PASSWORD))
+                    .configureIfNotNull(SshMachineLocation.SSH_PORT, 
sshPortNumber.orNull())
+                    
.configure(BrooklynConfigKeys.SKIP_ON_BOX_BASE_DIR_RESOLUTION, true)
+                    .configure(BrooklynConfigKeys.ONBOX_BASE_DIR, "/tmp");
+        }
+        return locationSpec;
+    }
+
+    protected void createPersistentVolumes(List<String> volumes) {
+        for (final String persistentVolume : volumes) {
+            PersistentVolume volume = new PersistentVolumeBuilder()
+                    .withNewMetadata()
+                        .withName(persistentVolume)
+                        .withLabels(ImmutableMap.of("type", "local")) // TODO 
make it configurable
+                    .endMetadata()
+                    .withNewSpec()
+                        .addToCapacity("storage", new 
QuantityBuilder().withAmount("20").build()) // TODO make it configurable
+                        .addToAccessModes("ReadWriteOnce") // TODO make it 
configurable
+                        .withNewHostPath().withPath("/tmp/pv-1").endHostPath() 
// TODO make it configurable
+                    .endSpec()
+                    .build();
+            client.persistentVolumes().create(volume);
+            ExitCondition exitCondition = new ExitCondition() {
+                @Override
+                public Boolean call() {
+                    PersistentVolume pv = 
client.persistentVolumes().withName(persistentVolume).get();
+                    return pv != null && pv.getStatus() != null
+                            && 
pv.getStatus().getPhase().equals(PHASE_AVAILABLE);
+                }
+                @Override
+                public String getFailureMessage() {
+                    PersistentVolume pv = 
client.persistentVolumes().withName(persistentVolume).get();
+                    return "PersistentVolume for " + persistentVolume + " " + 
(pv == null ? "absent" : "pv=" + pv);
+                }
+            };
+            waitForExitCondition(exitCondition);
+        }
+    }
+
+    protected Entity validateCallerContext(ConfigBag setup) {
+        // Lookup entity flags
+        Object callerContext = setup.get(LocationConfigKeys.CALLER_CONTEXT);
+        if (callerContext instanceof Entity) {
+            return (Entity) callerContext;
+        } else {
+            throw new IllegalStateException("Invalid caller context: " + 
callerContext);
+        }
+    }
+
+    protected Entity validateCallerContext(MachineLocation machine) {
+        // Lookup entity flags
+        Object callerContext = 
machine.config().get(LocationConfigKeys.CALLER_CONTEXT);
+        if (callerContext instanceof Entity) {
+            return (Entity) callerContext;
+        } else {
+            throw new IllegalStateException("Invalid caller context: " + 
callerContext);
+        }
+    }
+
+    protected Map<String, String> findMetadata(Entity entity, ConfigBag setup, 
String value) {
+        Map<String, String> podMetadata = Maps.newLinkedHashMap();
+        if (isDockerContainer(entity)) {
+            podMetadata.put(IMMUTABLE_CONTAINER_KEY, value);
+        } else {
+            podMetadata.put(SSHABLE_CONTAINER, value);
+        }
+
+        Map<String, Object> metadata = MutableMap.<String, Object>builder()
+                .putAll(MutableMap.copyOf(setup.get(KubernetesPod.METADATA)))
+                
.putAll(MutableMap.copyOf(entity.config().get(KubernetesPod.METADATA)))
+                .putAll(podMetadata)
+                .build();
+        return Maps.transformValues(metadata, Functions.toStringFunction());
+    }
+
+    /**
+     * Sets the {@code CLOUDSOFT_ROOT_PASSWORD} variable in the container 
environment if appropriate.
+     * This is (approximately) the same behaviour as the {@link 
DockerJcloudsLocation} used for
+     * Swarm.
+     *
+     * Side-effects the location {@code config} to set the {@link 
KubernetesLocationConfig#LOGIN_USER_PASSWORD loginUser.password}
+     * if one is auto-generated. Note that this injected value overrides any 
other settings configured for the
+     * container environment.
+     */
+    protected Map<String, String> findEnvironmentVariables(Entity entity, 
ConfigBag setup, String imageName) {
+        String loginUser = setup.get(LOGIN_USER);
+        String loginPassword = setup.get(LOGIN_USER_PASSWORD);
+        Map<String, String> injections = Maps.newLinkedHashMap();
+
+        // Check if login credentials should be injected
+        Boolean injectLoginCredentials = setup.get(INJECT_LOGIN_CREDENTIAL);
+        if (injectLoginCredentials == null) {
+            for (String regex : 
IMAGE_DESCRIPTION_REGEXES_REQUIRING_INJECTED_LOGIN_CREDS) {
+                if (imageName != null && imageName.matches(regex)) {
+                    injectLoginCredentials = true;
+                    break;
+                }
+            }
+        }
+
+        if (Boolean.TRUE.equals(injectLoginCredentials)) {
+            if ((Strings.isBlank(loginUser) || "root".equals(loginUser))) {
+                loginUser = "root";
+                setup.configure(LOGIN_USER, loginUser);
+
+                if (Strings.isBlank(loginPassword)) {
+                    loginPassword = Identifiers.makeRandomPassword(12);
+                    setup.configure(LOGIN_USER_PASSWORD, loginPassword);
+                }
+
+                injections.put(CLOUDSOFT_ROOT_PASSWORD, loginPassword);
+            }
+        }
+
+        Map<String,Object> rawEnv = MutableMap.<String, Object>builder()
+                .putAll(MutableMap.copyOf(setup.get(ENV)))
+                
.putAll(MutableMap.copyOf(entity.config().get(DockerContainer.CONTAINER_ENVIRONMENT)))
+                .putAll(injections)
+                .build();
+        return Maps.transformValues(rawEnv, Functions.toStringFunction());
+    }
+
+    protected Iterable<Integer> findInboundPorts(Entity entity, ConfigBag 
setup) {
+        Iterable<String> inboundTcpPorts = 
entity.config().get(DockerContainer.INBOUND_TCP_PORTS);
+        if (inboundTcpPorts != null) {
+            List<Integer> inboundPorts = Lists.newArrayList();
+            List<String> portRanges = 
MutableList.copyOf(entity.config().get(DockerContainer.INBOUND_TCP_PORTS));
+            for (String portRange : portRanges) {
+                for (Integer port : PortRanges.fromString(portRange)) {
+                    inboundPorts.add(port);
+                }
+            }
+            return inboundPorts;
+        } else {
+            if (setup.containsKey(INBOUND_PORTS)) {
+                return toIntPortList(setup.get(INBOUND_PORTS));
+            } else {
+                return ImmutableList.of(22);
+            }
+        }
+    }
+
+    protected List<Integer> toIntPortList(Object v) {
+        if (v == null) return ImmutableList.of();
+        PortRange portRange = PortRanges.fromIterable(ImmutableList.of(v));
+        return ImmutableList.copyOf(portRange);
+    }
+
+    protected String findImageName(Entity entity, ConfigBag setup) {
+        String result = entity.config().get(DockerContainer.IMAGE_NAME);
+        if (Strings.isNonBlank(result)) return result;
+
+        result = setup.get(IMAGE);
+        if (Strings.isNonBlank(result)) return result;
+
+        String osFamily = setup.get(OS_FAMILY);
+        String osVersion = setup.get(OS_VERSION_REGEX);
+        Optional<String> imageName = new ImageChooser().chooseImage(osFamily, 
osVersion);
+        if (imageName.isPresent()) return imageName.get();
+
+        throw new IllegalStateException("No matching image found for " + entity
+                + " (no explicit image name, osFamily=" + osFamily + "; 
osVersion=" + osVersion + ")");
+    }
+
+    protected boolean isDockerContainer(Entity entity) {
+        return implementsInterface(entity, DockerContainer.class);
+    }
+
+    protected boolean isKubernetesPod(Entity entity) {
+        return implementsInterface(entity, KubernetesPod.class);
+    }
+
+    protected boolean isKubernetesResource(Entity entity) {
+        return implementsInterface(entity, KubernetesResource.class);
+    }
+
+    public boolean implementsInterface(Entity entity, Class<?> type) {
+        return 
Iterables.tryFind(Arrays.asList(entity.getClass().getInterfaces()), 
Predicates.assignableFrom(type)).isPresent();
+    }
+
+    @Override
+    public MachineProvisioningLocation<KubernetesMachineLocation> 
newSubLocation(Map<?, ?> newFlags) {
+        throw new UnsupportedOperationException();
+    }
+
+    /** @see {@link #lookup(ConfigKey, Entity, ConfigBag, Object)} */
+    public <T> T lookup(ConfigKey<T> config, Entity entity, ConfigBag setup) {
+        return lookup(config, entity, setup, null);
+    }
+
+    /**
+     * Looks up {@link ConfigKey configuration} with the entity value taking 
precedence over the
+     * location, and returning a default value (normally {@literal null}) if 
neither is present.
+     */
+    public <T> T lookup(ConfigKey<T> config, Entity entity, ConfigBag setup, T 
defaultValue) {
+        Optional<T> entityValue = 
Optional.fromNullable(entity.config().get(config));
+        Optional<T> locationValue = Optional.fromNullable(setup.get(config));
+
+        return 
Iterables.getFirst(Optional.presentInstances(Arrays.asList(entityValue, 
locationValue)), defaultValue);
+    }
+
+    public void waitForExitCondition(ExitCondition exitCondition) {
+        waitForExitCondition(exitCondition, Duration.ONE_SECOND, 
Duration.FIVE_MINUTES);
+    }
+
+    public void waitForExitCondition(ExitCondition exitCondition, Duration 
initial, Duration duration) {
+        ReferenceWithError<Boolean> result = Repeater.create()
+                .backoff(initial, 1.2, duration)
+                .limitTimeTo(duration)
+                .until(exitCondition)
+                .runKeepingError();
+        if (!result.get()) {
+            String err = "Exit condition unsatisfied after " + duration + ": " 
+ exitCondition.getFailureMessage();
+            LOG.info(err + " (rethrowing)");
+            throw new IllegalStateException(err);
+        }
+    }
+
+    public static interface ExitCondition extends Callable<Boolean> {
+        public String getFailureMessage();
+    }
+}

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationConfig.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationConfig.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationConfig.java
new file mode 100644
index 0000000..0571fc2
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationConfig.java
@@ -0,0 +1,164 @@
+package io.cloudsoft.amp.containerservice.kubernetes.location;
+
+import java.util.Map;
+
+import org.apache.brooklyn.config.ConfigKey;
+import org.apache.brooklyn.core.config.ConfigKeys;
+import org.apache.brooklyn.core.location.LocationConfigKeys;
+import org.apache.brooklyn.core.location.cloud.CloudLocationConfig;
+import org.apache.brooklyn.util.time.Duration;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.reflect.TypeToken;
+
+public interface KubernetesLocationConfig extends CloudLocationConfig {
+
+    ConfigKey<String> MASTER_URL = LocationConfigKeys.CLOUD_ENDPOINT;
+
+    ConfigKey<String> CA_CERT_DATA = ConfigKeys.builder(String.class)
+            .name("caCertData")
+            .description("Data for CA certificate")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> CA_CERT_FILE = ConfigKeys.builder(String.class)
+            .name("caCertFile")
+            .description("URL of resource containing CA certificate data")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> CLIENT_CERT_DATA = ConfigKeys.builder(String.class)
+            .name("clientCertData")
+            .description("Data for client certificate")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> CLIENT_CERT_FILE = ConfigKeys.builder(String.class)
+            .name("clientCertFile")
+            .description("URL of resource containing client certificate data")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> CLIENT_KEY_DATA = ConfigKeys.builder(String.class)
+            .name("clientKeyData")
+            .description("Data for client key")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> CLIENT_KEY_FILE = ConfigKeys.builder(String.class)
+            .name("clientKeyFile")
+            .description("URL of resource containing client key data")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> CLIENT_KEY_ALGO = ConfigKeys.builder(String.class)
+            .name("clientKeyAlgo")
+            .description("Algorithm used for the client key")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> CLIENT_KEY_PASSPHRASE = ConfigKeys.builder(String.class)
+            .name("clientKeyPassphrase")
+            .description("Passphrase used for the client key")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> OAUTH_TOKEN = ConfigKeys.builder(String.class)
+            .name("oauthToken")
+            .description("The OAuth token data for the current user")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<Duration> CLIENT_TIMEOUT = ConfigKeys.builder(Duration.class)
+            .name("timeout")
+            .description("The timeout for the client")
+            .defaultValue(Duration.seconds(10))
+            .constraint(Predicates.<Duration>notNull())
+            .build();
+
+    ConfigKey<Duration> ACTION_TIMEOUT = ConfigKeys.builder(Duration.class)
+            .name("actionTimeout")
+            .description("The timeout for Kubernetes actions")
+            .defaultValue(Duration.ONE_MINUTE)
+            .constraint(Predicates.<Duration>notNull())
+            .build();
+
+    ConfigKey<Boolean> CREATE_NAMESPACE = ConfigKeys.builder(Boolean.class)
+            .name("namespace.create")
+            .description("Whether to create the namespace if it does not 
exist")
+            .defaultValue(true)
+            .constraint(Predicates.<Boolean>notNull())
+            .build();
+
+    ConfigKey<Boolean> DELETE_EMPTY_NAMESPACE = 
ConfigKeys.builder(Boolean.class)
+            .name("namespace.deleteEmpty")
+            .description("Whether to delete an empty namespace when releasing 
resources")
+            .defaultValue(false)
+            .constraint(Predicates.<Boolean>notNull())
+            .build();
+
+    ConfigKey<String> NAMESPACE = ConfigKeys.builder(String.class)
+            .name("namespace")
+            .description("Namespace where resources will live; the default is 
'amp'")
+            .defaultValue("amp")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<Boolean> PRIVILEGED = ConfigKeys.builder(Boolean.class)
+            .name("privileged")
+            .description("Whether the pods use privileged containers")
+            .defaultValue(false)
+            .build();
+
+    @SuppressWarnings("serial")
+    ConfigKey<Map<String, ?>> ENV = ConfigKeys.builder(new 
TypeToken<Map<String, ?>>() {})
+            .name("env")
+            .description("Environment variables to inject when starting the 
container")
+            .defaultValue(ImmutableMap.<String, Object>of())
+            .constraint(Predicates.<Map<String, ?>>notNull())
+            .build();
+
+    ConfigKey<String> IMAGE = ConfigKeys.builder(String.class)
+            .name("image")
+            .description("Docker image to be deployed into the pod")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> OS_FAMILY = ConfigKeys.builder(String.class)
+            .name("osFamily")
+            .description("OS family, e.g. CentOS, Ubuntu")
+            .build();
+
+    ConfigKey<String> OS_VERSION_REGEX = ConfigKeys.builder(String.class)
+            .name("osVersionRegex")
+            .description("Regular expression for the OS version to load")
+            .build();
+
+    ConfigKey<KubernetesClientRegistry> KUBERNETES_CLIENT_REGISTRY = 
ConfigKeys.builder(KubernetesClientRegistry.class) 
+            .name("kubernetesClientRegistry")
+            .description("Registry/Factory for creating Kubernetes client; 
default is almost always fine, "
+                    + "except where tests want to customize behaviour")
+            .defaultValue(KubernetesClientRegistryImpl.INSTANCE)
+            .build();
+
+    ConfigKey<String> LOGIN_USER = ConfigKeys.builder(String.class)
+            .name("loginUser")
+            .description("Override the user who logs in initially to perform 
setup")
+            .defaultValue("root")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<String> LOGIN_USER_PASSWORD = ConfigKeys.builder(String.class)
+            .name("loginUser.password")
+            .description("Custom password for the user who logs in initially")
+            .constraint(Predicates.<String>notNull())
+            .build();
+
+    ConfigKey<Boolean> INJECT_LOGIN_CREDENTIAL = 
ConfigKeys.builder(Boolean.class)
+            .name("injectLoginCredential")
+            .description("Whether to inject login credentials (if null, will 
infer from image choice); ignored if explicit 'loginUser.password' supplied")
+            .build();
+
+}
+

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/445884b1/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolver.java
----------------------------------------------------------------------
diff --git 
a/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolver.java
 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolver.java
new file mode 100644
index 0000000..79e7c64
--- /dev/null
+++ 
b/brooklyn-server/locations/container/src/main/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolver.java
@@ -0,0 +1,47 @@
+package io.cloudsoft.amp.containerservice.kubernetes.location;
+
+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 "kubernetes") will use this 
resolver, to instantiate
+ * a {@link KubernetesLocation}.
+ * 
+ * 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.kubernetes.}
+ *   <li>Prefix {@code brooklyn.kubernetes.}
+ * </ol>
+ */
+public class KubernetesLocationResolver extends AbstractLocationResolver 
implements LocationResolver {
+
+    public static final Logger log = 
LoggerFactory.getLogger(KubernetesLocationResolver.class);
+
+    public static final String PREFIX = "kubernetes";
+
+    @Override
+    public boolean isEnabled() {
+        return LocationConfigUtils.isResolverPrefixEnabled(managementContext, 
getPrefix());
+    }
+
+    @Override
+    public String getPrefix() {
+        return PREFIX;
+    }
+
+    @Override
+    protected Class<? extends Location> getLocationType() {
+        return KubernetesLocation.class;
+    }
+
+    @Override
+    protected SpecParser getSpecParser() {
+        return new SpecParser(getPrefix()).setExampleUsage("\"kubernetes\"");
+    }
+
+}

Reply via email to