Add KubernetesMachineLocation interface and implementations
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/fcb0db07 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/fcb0db07 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/fcb0db07 Branch: refs/heads/master Commit: fcb0db07b5afa3de7718975977349b5144e1a389 Parents: 90123e4 Author: Andrew Donald Kennedy <[email protected]> Authored: Wed Feb 1 19:32:36 2017 +0000 Committer: Andrew Donald Kennedy <[email protected]> Committed: Fri May 19 14:01:20 2017 +0100 ---------------------------------------------------------------------- .../kubernetes/entity/KubernetesPod.java | 2 + .../KubernetesEmptyMachineLocation.java | 68 ++++++++++++++++++ .../kubernetes/location/KubernetesLocation.java | 75 ++++++++++++++------ .../location/KubernetesMachineLocation.java | 27 +++++++ .../location/KubernetesSshMachineLocation.java | 27 +++++++ .../KubernetesLocationYamlLiveTest.java | 39 +++++++++- 6 files changed, 215 insertions(+), 23 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/fcb0db07/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesPod.java ---------------------------------------------------------------------- diff --git a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesPod.java b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesPod.java index 4c334ec..35da4b8 100644 --- a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesPod.java +++ b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesPod.java @@ -77,4 +77,6 @@ public interface KubernetesPod extends DockerContainer { 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/fcb0db07/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesEmptyMachineLocation.java ---------------------------------------------------------------------- diff --git a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesEmptyMachineLocation.java b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesEmptyMachineLocation.java new file mode 100644 index 0000000..ffda23f --- /dev/null +++ b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesEmptyMachineLocation.java @@ -0,0 +1,68 @@ +package io.cloudsoft.amp.containerservice.kubernetes.location; + +import java.net.InetAddress; +import java.util.Set; + +import org.apache.brooklyn.api.location.MachineDetails; +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.api.location.OsDetails; +import org.apache.brooklyn.core.location.AbstractLocation; +import org.apache.brooklyn.util.net.Networking; + +import com.google.common.collect.ImmutableSet; + +/** + * A {@link MachineLocation} represemnting a Kubernetes resource that does not support SSH access. + * + * @see {@link KubernetesSshMachineLocation} + */ +public class KubernetesEmptyMachineLocation extends AbstractLocation implements KubernetesMachineLocation { + + @Override + public String getHostname() { + return getResourceName(); + } + + @Override + public Set<String> getPublicAddresses() { + return ImmutableSet.of("0.0.0.0"); + } + + @Override + public Set<String> getPrivateAddresses() { + return ImmutableSet.of("0.0.0.0"); + } + + @Override + public InetAddress getAddress() { + return Networking.getInetAddressWithFixedName("0.0.0.0"); + } + + @Override + public OsDetails getOsDetails() { + return null; + // throw new UnsupportedOperationException("No OS details for empty KubernetesMachineLocation"); + } + + @Override + public MachineDetails getMachineDetails() { + return null; + // throw new UnsupportedOperationException("No machine details for empty KubernetesMachineLocation"); + } + + @Override + public String getResourceName() { + return config().get(KUBERNETES_RESOURCE_NAME); + } + + @Override + public String getResourceType() { + return config().get(KUBERNETES_RESOURCE_TYPE); + } + + @Override + public String getNamespace() { + return config().get(KUBERNETES_NAMESPACE); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/fcb0db07/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocation.java ---------------------------------------------------------------------- diff --git a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocation.java b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocation.java index 28f193c..f5123b9 100644 --- a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocation.java +++ b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocation.java @@ -128,6 +128,7 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi 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"; @@ -185,15 +186,41 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi if (isKubernetesResource(entity)) { return createKubernetesResourceLocation(entity, setup); } else { + // Heuristic for determining whether KubernetesPod is simply a parent entity + if (isKubernetesPod(entity) && + entity.config().get(DockerContainer.IMAGE_NAME) == null && + entity.config().get(DockerContainer.INBOUND_TCP_PORTS) == null && + entity.getChildren().size() > 0) { + return createEmptyKubernetesMachineLocation(entity); + } return createKubernetesContainerLocation(entity, setup); } } + /** @deprecated This mechanism will be removed in future releases */ + @Deprecated + public KubernetesEmptyMachineLocation createEmptyKubernetesMachineLocation(Entity entity) { + LOG.warn("IMPORTANT: Deprecated behaviour: Use of KubernetesPod as a parent entity is deprecated and support will be removed in future versions"); + + LocationSpec<KubernetesEmptyMachineLocation> locationSpec = LocationSpec.create(KubernetesEmptyMachineLocation.class) + .configure(KubernetesLocationConfig.CALLER_CONTEXT, entity) + .configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_TYPE, KubernetesPod.EMPTY) + .configure(KubernetesMachineLocation.KUBERNETES_RESOURCE_NAME, entity.getId()); + + KubernetesEmptyMachineLocation machine = getManagementContext().getLocationManager().createLocation(locationSpec); + + return machine; + } + @Override public void release(MachineLocation machine) { Entity entity = validateCallerContext(machine); if (isKubernetesResource(entity)) { - deleteKubernetesResourceLocation(entity); + if (machine instanceof KubernetesEmptyMachineLocation && KubernetesPod.EMPTY.equals(machine.config().get(KubernetesMachineLocation.KUBERNETES_RESOURCE_TYPE))) { + // Nothing to do, the location does not represent a Kubernetes resource + } else { + deleteKubernetesResourceLocation(entity); + } } else { deleteKubernetesContainerLocation(entity, machine); } @@ -346,25 +373,27 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi entity.sensors().set(KubernetesResource.RESOURCE_NAME, resourceName); entity.sensors().set(KubernetesResource.RESOURCE_TYPE, resourceType); - LocationSpec<SshMachineLocation> locationSpec = LocationSpec.create(SshMachineLocation.class) - .configure(CALLER_CONTEXT, setup.get(CALLER_CONTEXT)); + 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.configure("address", "0.0.0.0"); + 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); - SshMachineLocation machine = getManagementContext().getLocationManager().createLocation(locationSpec); + KubernetesMachineLocation machine = getManagementContext().getLocationManager().createLocation(locationSpec); - if (resourceType.equals(KubernetesResource.SERVICE)) { + if (resourceType.equals(KubernetesResource.SERVICE) && machine instanceof KubernetesSshMachineLocation) { Service service = getService(namespace, resourceName); - registerPortMappings(machine, entity, service); - + registerPortMappings((KubernetesSshMachineLocation) machine, entity, service); } return machine; } - protected boolean findResourceAddress(LocationSpec<SshMachineLocation> locationSpec, Entity entity, HasMetadata metadata, String resourceType, String resourceName, String namespace) { + 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)) { @@ -443,8 +472,12 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi entity.sensors().set(KubernetesPod.KUBERNETES_POD, pod.getMetadata().getName()); entity.sensors().set(KubernetesPod.KUBERNETES_SERVICE, service.getMetadata().getName()); - LocationSpec<SshMachineLocation> locationSpec = prepareLocationSpec(entity, setup, namespace, deploymentName, service, pod); - SshMachineLocation machine = getManagementContext().getLocationManager().createLocation(locationSpec); + 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, KubernetesResource.DEPLOYMENT); + + KubernetesSshMachineLocation machine = getManagementContext().getLocationManager().createLocation(locationSpec); registerPortMappings(machine, entity, service); if (!isDockerContainer(entity)) { waitForSshable(machine, Duration.FIVE_MINUTES); @@ -482,11 +515,11 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi } } - protected void registerPortMappings(SshMachineLocation machine, Entity entity, Service service) { + 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 = machine.getSshHostAndPort().getHostText(); + String publicHostText = ((SshMachineLocation) machine).getSshHostAndPort().getHostText(); LOG.debug("Recording port-mappings for container {} of {}: {}", new Object[] { machine, this, ports }); for (ServicePort port : ports) { @@ -741,10 +774,10 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi return client.services().inNamespace(namespace).withName(serviceName).get(); } - protected LocationSpec<SshMachineLocation> prepareLocationSpec(Entity entity, ConfigBag setup, Namespace namespace, String deploymentName, Service service, Pod pod) { + 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<SshMachineLocation> locationSpec = LocationSpec.create(SshMachineLocation.class) + LocationSpec<KubernetesSshMachineLocation> locationSpec = LocationSpec.create(KubernetesSshMachineLocation.class) .configure("address", node) .configure(SshMachineLocation.PRIVATE_ADDRESSES, ImmutableSet.of(podAddress)) .configure(CALLER_CONTEXT, setup.get(CALLER_CONTEXT)); @@ -775,13 +808,13 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi for (final String persistentVolume : volumes) { PersistentVolume volume = new PersistentVolumeBuilder() .withNewMetadata() - .withName(persistentVolume) - .withLabels(ImmutableMap.of("type", "local")) // TODO make it configurable + .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 + .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); @@ -790,7 +823,7 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi public Boolean call() { PersistentVolume pv = client.persistentVolumes().withName(persistentVolume).get(); return pv != null && pv.getStatus() != null - && pv.getStatus().getPhase().equals("Available"); + && pv.getStatus().getPhase().equals(PHASE_AVAILABLE); } @Override public String getFailureMessage() { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/fcb0db07/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesMachineLocation.java ---------------------------------------------------------------------- diff --git a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesMachineLocation.java b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesMachineLocation.java new file mode 100644 index 0000000..9c620bb --- /dev/null +++ b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesMachineLocation.java @@ -0,0 +1,27 @@ +package io.cloudsoft.amp.containerservice.kubernetes.location; + +import org.apache.brooklyn.api.location.MachineLocation; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.config.ConfigKeys; + +public interface KubernetesMachineLocation extends MachineLocation { + + ConfigKey<String> KUBERNETES_NAMESPACE = ConfigKeys.builder(String.class, "kubernetes.namespace") + .description("Namespace for the KubernetesMachineLocation") + .build(); + + ConfigKey<String> KUBERNETES_RESOURCE_NAME = ConfigKeys.builder(String.class, "kubernetes.name") + .description("Name of the resource represented by the KubernetesMachineLocation") + .build(); + + ConfigKey<String> KUBERNETES_RESOURCE_TYPE = ConfigKeys.builder(String.class, "kubernetes.type") + .description("Type of the resource represented by the KubernetesMachineLocation") + .build(); + + public String getResourceName(); + + public String getResourceType(); + + public String getNamespace(); + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/fcb0db07/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesSshMachineLocation.java ---------------------------------------------------------------------- diff --git a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesSshMachineLocation.java b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesSshMachineLocation.java new file mode 100644 index 0000000..97fabbb --- /dev/null +++ b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesSshMachineLocation.java @@ -0,0 +1,27 @@ +package io.cloudsoft.amp.containerservice.kubernetes.location; + +import org.apache.brooklyn.location.ssh.SshMachineLocation; + +/** + * A {@link MachineLocation} represemnting a Kubernetes resource that allows SSH access. + * + * @see {@link KubernetesSshMachineLocation} + */ +public class KubernetesSshMachineLocation extends SshMachineLocation implements KubernetesMachineLocation { + + @Override + public String getResourceName() { + return config().get(KUBERNETES_RESOURCE_NAME); + } + + @Override + public String getResourceType() { + return config().get(KUBERNETES_RESOURCE_TYPE); + } + + @Override + public String getNamespace() { + return config().get(KUBERNETES_NAMESPACE); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/fcb0db07/kubernetes-location/src/test/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationYamlLiveTest.java ---------------------------------------------------------------------- diff --git a/kubernetes-location/src/test/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationYamlLiveTest.java b/kubernetes-location/src/test/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationYamlLiveTest.java index 9654d6d..f783630 100644 --- a/kubernetes-location/src/test/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationYamlLiveTest.java +++ b/kubernetes-location/src/test/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationYamlLiveTest.java @@ -259,9 +259,8 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { return entity; } - @Test(groups={"Live"}) - public void testWordpressInContainers() throws Exception { + 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( @@ -297,6 +296,42 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { } @Test(groups={"Live"}) + public void testWordpressInContainersWithPodParent() 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(), + " 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.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 testWordpressInPods() throws Exception { // TODO docker.container.inboundPorts doesn't accept list of ints - need to use quotes String randomId = Identifiers.makeRandomLowercaseId(4);
