Refactoring of kubernetes location to use KubernetesPod entity
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/422d78da Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/422d78da Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/422d78da Branch: refs/heads/master Commit: 422d78da0e63c10c4816f0570277e9a9b2e00419 Parents: eb94f08 Author: Andrew Donald Kennedy <[email protected]> Authored: Sat Jan 28 03:14:37 2017 +0000 Committer: Andrew Donald Kennedy <[email protected]> Committed: Fri May 19 14:01:20 2017 +0100 ---------------------------------------------------------------------- .../kubernetes/entity/KubernetesPod.java | 84 ++++++++- .../kubernetes/entity/KubernetesResource.java | 12 ++ .../kubernetes/location/KubernetesLocation.java | 185 +++++++++++-------- .../location/KubernetesLocationConfig.java | 73 +------- .../KubernetesLocationYamlLiveTest.java | 148 ++++++++------- 5 files changed, 301 insertions(+), 201 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/422d78da/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 ae9bdb3..475f941 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 @@ -1,10 +1,92 @@ 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 com.google.common.base.Predicates; +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, KubernetesLocationConfig { +public interface KubernetesPod extends DockerContainer { + + ConfigKey<String> NAMESPACE = KubernetesLocationConfig.NAMESPACE; + + ConfigKey<String> POD = ConfigKeys.builder(String.class) + .name("pod") + .description("The name of the pod") + .constraint(Predicates.<String>notNull()) + .build(); + + @SuppressWarnings("serial") + ConfigKey<List<String>> PERSISTENT_VOLUMES = ConfigKeys.builder(new TypeToken<List<String>>() {}) + .name("persistentVolumes") + .description("Persistent volumes used by the pod") + .constraint(Predicates.<List<String>>notNull()) + .build(); + + ConfigKey<String> DEPLOYMENT = ConfigKeys.builder(String.class) + .name("deployment") + .description("The name of the service the deployed pod will use") + .constraint(Predicates.<String>notNull()) + .build(); + + ConfigKey<Integer> REPLICAS = ConfigKeys.builder(Integer.class) + .name("replicas") + .description("Number of replicas in the pod") + .constraint(Predicates.notNull()) + .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(); + + ConfigKey<Boolean> PRIVILEGED = ConfigKeys.builder(Boolean.class) + .name("privileged") + .description("Whether the container is privileged") + .defaultValue(false) + .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(); } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/422d78da/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesResource.java ---------------------------------------------------------------------- diff --git a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesResource.java b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesResource.java index 3274cb2..320c924 100644 --- a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesResource.java +++ b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/entity/KubernetesResource.java @@ -25,4 +25,16 @@ public interface KubernetesResource extends SoftwareProcess { .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/422d78da/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 1cf9545..cd44f37 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 @@ -3,6 +3,7 @@ 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; @@ -17,6 +18,7 @@ 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; @@ -49,6 +51,7 @@ 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; @@ -63,6 +66,7 @@ import com.google.common.net.HostAndPort; import io.cloudsoft.amp.containerservice.ThreadedRepeater; 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.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerBuilder; @@ -98,7 +102,7 @@ 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<MachineLocation>, CloudLocationConfig, KubernetesLocationConfig { +public class KubernetesLocation extends AbstractLocation implements MachineProvisioningLocation<MachineLocation>, KubernetesLocationConfig { /* * TODO @@ -173,6 +177,14 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi if (isKubernetesResource(entity)) { return createKubernetesResourceLocation(entity, setup); } else { + // Heuristic for determining whether KubernetesPod is simply a container + if (isKubernetesPod(entity) && + entity.config().get(DockerContainer.IMAGE_NAME) == null && + entity.config().get(DockerContainer.INBOUND_TCP_PORTS) == null && + entity.config().get(KubernetesPod.POD) == null && + entity.getChildren().size() > 0) { + return null; + } return createKubernetesContainerLocation(entity, setup); } } @@ -188,10 +200,10 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi } protected void deleteKubernetesContainerLocation(Entity entity, MachineLocation machine) { - final String namespace = entity.sensors().get(KUBERNETES_NAMESPACE); - final String deployment = entity.sensors().get(KUBERNETES_DEPLOYMENT); - final String pod = entity.sensors().get(KUBERNETES_POD); - final String service = entity.sensors().get(KUBERNETES_SERVICE); + 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); @@ -215,7 +227,7 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi } protected void deleteKubernetesResourceLocation(Entity entity) { - final String namespace = entity.sensors().get(KUBERNETES_NAMESPACE); + 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); @@ -227,21 +239,21 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi protected boolean handleResourceDelete(String resourceType, String resourceName, String namespace) { try { switch (resourceType) { - case "Deployment": + case KubernetesResource.DEPLOYMENT: return client.extensions().deployments().inNamespace(namespace).withName(resourceName).delete(); - case "ReplicaSet": + case KubernetesResource.REPLICA_SET: return client.extensions().replicaSets().inNamespace(namespace).withName(resourceName).delete(); - case "ConfigMap": + case KubernetesResource.CONFIG_MAP: return client.configMaps().inNamespace(namespace).withName(resourceName).delete(); - case "PersistentVolume": + case KubernetesResource.PERSISTENT_VOLUME: return client.persistentVolumes().withName(resourceName).delete(); - case "Secret": + case KubernetesResource.SECRET: return client.secrets().inNamespace(namespace).withName(resourceName).delete(); - case "Service": + case KubernetesResource.SERVICE: return client.services().inNamespace(namespace).withName(resourceName).delete(); - case "ReplicationController": + case KubernetesResource.REPLICATION_CONTROLLER: return client.replicationControllers().inNamespace(namespace).withName(resourceName).delete(); - case "Namespace": + case KubernetesResource.NAMESPACE: return client.namespaces().withName(resourceName).delete(); } } catch (KubernetesClientException kce) { @@ -330,7 +342,7 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi String namespace = metadata.getMetadata().getNamespace(); LOG.debug("Resource {} (type {}) deployed to {}", resourceName, resourceType, namespace); - entity.sensors().set(KUBERNETES_NAMESPACE, namespace); + entity.sensors().set(KubernetesPod.KUBERNETES_NAMESPACE, namespace); entity.sensors().set(KubernetesResource.RESOURCE_NAME, resourceName); entity.sensors().set(KubernetesResource.RESOURCE_TYPE, resourceType); @@ -343,7 +355,7 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi SshMachineLocation machine = getManagementContext().getLocationManager().createLocation(locationSpec); - if (resourceType.equals("Service")) { + if (resourceType.equals(KubernetesResource.SERVICE)) { Service service = getService(namespace, resourceName); registerPortMappings(machine, entity, service); @@ -353,17 +365,17 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi } protected boolean findResourceAddress(LocationSpec<SshMachineLocation> locationSpec, Entity entity, HasMetadata metadata, String resourceType, String resourceName, String namespace) { - if (resourceType.equals("Deployment") || resourceType.equals("ReplicationController") || resourceType.equals("Pod")) { + if (resourceType.equals(KubernetesResource.DEPLOYMENT) || resourceType.equals(KubernetesResource.REPLICATION_CONTROLLER) || resourceType.equals(KubernetesResource.POD)) { Map<String, String> labels = MutableMap.of(); - if (resourceType.equals("Deployment")) { + if (resourceType.equals(KubernetesResource.DEPLOYMENT)) { Deployment deployment = (Deployment) metadata; labels = deployment.getSpec().getTemplate().getMetadata().getLabels(); - } else if (resourceType.equals("ReplicationController")) { + } else if (resourceType.equals(KubernetesResource.REPLICATION_CONTROLLER)) { ReplicationController replicationController = (ReplicationController) metadata; labels = replicationController.getSpec().getTemplate().getMetadata().getLabels(); } - Pod pod = resourceType.equals("Pod") ? getPod(namespace, resourceName) : getPod(namespace, labels); - entity.sensors().set(KubernetesLocationConfig.KUBERNETES_POD, pod.getMetadata().getName()); + 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(); @@ -372,7 +384,7 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi locationSpec.configure(SshMachineLocation.PRIVATE_ADDRESSES, ImmutableSet.of(podAddress)); return true; - } else if (resourceType.equals("Service")) { + } else if (resourceType.equals(KubernetesResource.SERVICE)) { getService(namespace, resourceName); Endpoints endpoints = client.endpoints().inNamespace(namespace).withName(resourceName).get(); @@ -385,7 +397,7 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi try { Pod pod = getPod(namespace, podName); - entity.sensors().set(KubernetesLocationConfig.KUBERNETES_POD, podName); + entity.sensors().set(KubernetesPod.KUBERNETES_POD, podName); InetAddress node = Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName()); locationSpec.configure("address", node); @@ -400,22 +412,22 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi } protected MachineLocation createKubernetesContainerLocation(Entity entity, ConfigBag setup) { - String deploymentName = findDeploymentName(entity, setup); - Integer replicas = setup.get(REPLICAS); - List<String> volumes = setup.get(KubernetesLocationConfig.PERSISTENT_VOLUMES); - Map<String, String> secrets = setup.get(KubernetesLocationConfig.SECRETS); - Map<String, String> limits = setup.get(KubernetesLocationConfig.LIMITS); - Boolean privileged = setup.get(KubernetesLocationConfig.PRIVILEGED); + 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, deploymentName); + Map<String, String> metadata = findMetadata(entity, setup, deploymentName); if (volumes != null) { createPersistentVolumes(volumes); } - Namespace namespace = createOrGetNamespace(setup.get(NAMESPACE), setup.get(CREATE_NAMESPACE)); + Namespace namespace = createOrGetNamespace(lookup(NAMESPACE, entity, setup), setup.get(CREATE_NAMESPACE)); if (secrets != null) { createSecrets(namespace.getMetadata().getName(), secrets); @@ -426,10 +438,10 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi Service service = exposeService(namespace.getMetadata().getName(), metadata, deploymentName, inboundPorts); Pod pod = getPod(namespace.getMetadata().getName(), metadata); - entity.sensors().set(KubernetesLocationConfig.KUBERNETES_NAMESPACE, namespace.getMetadata().getName()); - entity.sensors().set(KubernetesLocationConfig.KUBERNETES_DEPLOYMENT, deploymentName); - entity.sensors().set(KubernetesLocationConfig.KUBERNETES_POD, pod.getMetadata().getName()); - entity.sensors().set(KubernetesLocationConfig.KUBERNETES_SERVICE, service.getMetadata().getName()); + 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<SshMachineLocation> locationSpec = prepareLocationSpec(entity, setup, namespace, deploymentName, service, pod); SshMachineLocation machine = getManagementContext().getLocationManager().createLocation(locationSpec); @@ -497,10 +509,6 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi entity.enrichers().add(EnricherSpec.create(OnPublicNetworkEnricher.class).configure(OnPublicNetworkEnricher.MAP_MATCHING, "kubernetes.[a-zA-Z0-9][a-zA-Z0-9-_]*.port")); } - protected String findDeploymentName(Entity entity, ConfigBag setup) { - return Optional.fromNullable(setup.get(KubernetesLocationConfig.DEPLOYMENT)).or(entity.getId()); - } - protected synchronized Namespace createOrGetNamespace(final String name, Boolean create) { Namespace namespace = client.namespaces().withName(name).get(); ExitCondition namespaceReady = new ExitCondition() { @@ -573,14 +581,13 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi String json = String.format("{\"https://index.docker.io/v1/\":{\"auth\":\"%s\"}}", auth); String base64encoded = BaseEncoding.base64().encode(json.getBytes(Charset.defaultCharset())); - secret = client.secrets().inNamespace(namespace) - .create(new SecretBuilder() + secret = new SecretBuilder() .withNewMetadata() .withName(secretName) .endMetadata() .withType("kubernetes.io/dockercfg") .withData(ImmutableMap.of(".dockercfg", base64encoded)) - .build()); + .build(); try { client.secrets().inNamespace(namespace).create(secret); } catch (KubernetesClientException e) { @@ -642,6 +649,12 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi .withNewSpec() .addToContainers(container) .endSpec(); + if (isKubernetesPod(entity)) { + String podName = entity.config().get(KubernetesPod.POD); + if (Strings.isNonBlank(podName)) { + podTemplateSpecBuilder.editOrNewMetadata().withName(podName).endMetadata(); + } + } if (secrets != null) { for (String secretName : secrets.keySet()) { podTemplateSpecBuilder.withNewSpec() @@ -797,53 +810,55 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi protected Entity validateCallerContext(ConfigBag setup) { // Lookup entity flags Object callerContext = setup.get(LocationConfigKeys.CALLER_CONTEXT); - if (callerContext == null || !(callerContext instanceof Entity)) { + if (callerContext instanceof Entity) { + return (Entity) callerContext; + } else { throw new IllegalStateException("Invalid caller context: " + callerContext); } - return (Entity) callerContext; } protected Entity validateCallerContext(MachineLocation machine) { // Lookup entity flags Object callerContext = machine.config().get(LocationConfigKeys.CALLER_CONTEXT); - if (callerContext == null || !(callerContext instanceof Entity)) { + if (callerContext instanceof Entity) { + return (Entity) callerContext; + } else { throw new IllegalStateException("Invalid caller context: " + callerContext); } - return (Entity) callerContext; } - - protected Map<String, String> findMetadata(Entity entity, String value) { - Map<String, String> metadata = Maps.newHashMap(); - if (!isDockerContainer(entity)) { - metadata.put(SSHABLE_CONTAINER, value); + 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 { - metadata.put(IMMUTABLE_CONTAINER_KEY, value); + podMetadata.put(SSHABLE_CONTAINER, value); } - return metadata; + + 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()); } /** - * Appends the config's "env" with the {@code CLOUDSOFT_ROOT_PASSWORD} env if appropriate. + * 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 {@code config}, to set the loginPassword if one is auto-generated. - * - * TODO Longer term, we'll add a config key to the `DockerContainer` entity for env variables. - * We'll inject those when launching the container (and will do the same in - * `DockerJcloudsLocation` for Swarm). What would the precedence be? Would we prefer the - * entity's config over the location's (or vice versa)? I think the entity's would take - * precedence, as a location often applies to a whole app so we might well want to override - * or augment it for specific entities. + * 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 config, String imageName) { - String loginUser = config.get(LOGIN_USER); - String loginPassword = config.get(LOGIN_USER_PASSWORD); + 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 = config.get(INJECT_LOGIN_CREDENTIAL); + 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)) { @@ -856,11 +871,11 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi if (Boolean.TRUE.equals(injectLoginCredentials)) { if ((Strings.isBlank(loginUser) || "root".equals(loginUser))) { loginUser = "root"; - config.configure(LOGIN_USER, loginUser); + setup.configure(LOGIN_USER, loginUser); if (Strings.isBlank(loginPassword)) { loginPassword = Identifiers.makeRandomPassword(12); - config.configure(LOGIN_USER_PASSWORD, loginPassword); + setup.configure(LOGIN_USER_PASSWORD, loginPassword); } injections.put("CLOUDSOFT_ROOT_PASSWORD", loginPassword); @@ -868,9 +883,9 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi } Map<String,Object> rawEnv = MutableMap.<String, Object>builder() - .putAll(injections) - .putAll(MutableMap.copyOf(config.get(ENV))) + .putAll(MutableMap.copyOf(setup.get(ENV))) .putAll(MutableMap.copyOf(entity.config().get(DockerContainer.CONTAINER_ENVIRONMENT))) + .putAll(injections) .build(); return Maps.transformValues(rawEnv, Functions.toStringFunction()); } @@ -918,16 +933,40 @@ public class KubernetesLocation extends AbstractLocation implements MachineProvi } protected boolean isDockerContainer(Entity entity) { - return entity.getEntityType().getName().equalsIgnoreCase(DockerContainer.class.getName()); + return implementsInterface(entity, DockerContainer.class); + } + + protected boolean isKubernetesPod(Entity entity) { + return implementsInterface(entity, KubernetesPod.class); } protected boolean isKubernetesResource(Entity entity) { - return entity.getEntityType().getName().equalsIgnoreCase(KubernetesResource.class.getName()); + return implementsInterface(entity, KubernetesResource.class); + } + + protected boolean implementsInterface(Entity entity, Class<?> type) { + return Iterables.tryFind(Arrays.asList(entity.getClass().getInterfaces()), Predicates.instanceOf(type)).isPresent(); } @Override public MachineProvisioningLocation<MachineLocation> newSubLocation(Map<?, ?> newFlags) { - return null; + throw new UnsupportedOperationException(); + } + + /** @see {@link #lookup(ConfigKey, Entity, ConfigBag, Object)} */ + protected <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. + */ + protected <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 entityValue.or(locationValue).or(defaultValue); } protected void waitForExitCondition(ExitCondition exitCondition) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/422d78da/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationConfig.java ---------------------------------------------------------------------- diff --git a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationConfig.java b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationConfig.java index c2e9d89..fdb658b 100644 --- a/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationConfig.java +++ b/kubernetes-location/src/main/java/io/cloudsoft/amp/containerservice/kubernetes/location/KubernetesLocationConfig.java @@ -1,23 +1,21 @@ package io.cloudsoft.amp.containerservice.kubernetes.location; -import java.util.List; import java.util.Map; -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.location.LocationConfigKeys; import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.util.core.flags.SetFromFlag; import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; -public interface KubernetesLocationConfig { +public interface KubernetesLocationConfig extends CloudLocationConfig { - @SetFromFlag("endpoint") ConfigKey<String> MASTER_URL = LocationConfigKeys.CLOUD_ENDPOINT; ConfigKey<String> CA_CERT_DATA = ConfigKeys.builder(String.class) @@ -110,16 +108,11 @@ public interface KubernetesLocationConfig { .build(); @SuppressWarnings("serial") - ConfigKey<List<String>> PERSISTENT_VOLUMES = ConfigKeys.builder(new TypeToken<List<String>>() {}) - .name("persistentVolumes") - .description("Set up persistent volumes.") - .constraint(Predicates.<List<String>>notNull()) - .build(); - - ConfigKey<String> DEPLOYMENT = ConfigKeys.builder(String.class) - .name("deployment") - .description("Deployment where resources will live.") - .constraint(Predicates.<String>notNull()) + 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) @@ -138,41 +131,7 @@ public interface KubernetesLocationConfig { .description("Regular expression for the OS version to load") .build(); - @SuppressWarnings("serial") - ConfigKey<Map<String, ?>> ENV = ConfigKeys.newConfigKey( - new TypeToken<Map<String, ?>>() {}, - "env", - "Environment variables to inject when starting the container", - ImmutableMap.<String, Object>of()); - - ConfigKey<Integer> REPLICAS = ConfigKeys.builder(Integer.class) - .name("replicas") - .description("Number of replicas of the pod") - .constraint(Predicates.notNull()) - .defaultValue(1) - .build(); - - @SuppressWarnings("serial") - ConfigKey<Map<String, String>> SECRETS = ConfigKeys.builder( - new TypeToken<Map<String, String>>() {}) - .name("secrets") - .description("Kubernetes 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("Kubernetes resource limits") - .build(); - - ConfigKey<Boolean> PRIVILEGED = ConfigKeys.builder(Boolean.class) - .name("privileged") - .description("Whether Kubernetes should allow privileged containers") - .defaultValue(false) - .build(); - - ConfigKey<KubernetesClientRegistry> KUBERNETES_CLIENT_REGISTRY = ConfigKeys.builder(KubernetesClientRegistry.class) + 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") @@ -197,21 +156,5 @@ public interface KubernetesLocationConfig { .description("Whether to inject login credentials (if null, will infer from image choice); ignored if explicit 'loginUser.password' supplied") .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(); - } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/422d78da/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 962be36..d623efa 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 @@ -30,7 +30,6 @@ import org.apache.brooklyn.core.sensor.Sensors; import org.apache.brooklyn.entity.software.base.EmptySoftwareProcess; import org.apache.brooklyn.entity.software.base.SoftwareProcess; import org.apache.brooklyn.entity.software.base.VanillaSoftwareProcess; -import org.apache.brooklyn.entity.stock.BasicStartable; import org.apache.brooklyn.location.ssh.SshMachineLocation; import org.apache.brooklyn.util.net.Networking; import org.apache.brooklyn.util.text.Identifiers; @@ -43,6 +42,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.net.HostAndPort; +import io.cloudsoft.amp.containerservice.dockercontainer.DockerContainer; import io.cloudsoft.amp.containerservice.kubernetes.entity.KubernetesPod; import io.cloudsoft.amp.containerservice.kubernetes.entity.KubernetesResource; @@ -82,10 +82,11 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { String yaml = Joiner.on("\n").join( locationYaml, "services:", - "- type: " + EmptySoftwareProcess.class.getName(), - " brooklyn.config:", - " provisioning.properties:", - " " + KubernetesLocationConfig.LOGIN_USER_PASSWORD.getName() + ": " + customPassword); + " - type: " + EmptySoftwareProcess.class.getName(), + " brooklyn.config:", + " provisioning.properties:", + " " + KubernetesLocationConfig.LOGIN_USER_PASSWORD.getName() + ": " + customPassword); + Entity app = createStartWaitAndLogApplication(yaml); EmptySoftwareProcess entity = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, EmptySoftwareProcess.class)); @@ -101,19 +102,17 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { String yaml = Joiner.on("\n").join( locationYaml, "services:", - "- type: " + VanillaSoftwareProcess.class.getName(), - " brooklyn.parameters:", - " - name: netcat.port", - " type: port", - " default: 8081", - " brooklyn.config:", - " install.command: |", - " yum install -y nc", - " launch.command: |", - " echo $MESSAGE | nc -l $NETCAT_PORT &", - " echo $! > $PID_FILE", - " checkRunning.command: |", - " true", + " - type: " + VanillaSoftwareProcess.class.getName(), + " brooklyn.parameters:", + " - name: netcat.port", + " type: port", + " default: 8081", + " brooklyn.config:", + " install.command: |", + " yum install -y nc", + " launch.command: |", + " echo $MESSAGE | nc -l $NETCAT_PORT &", + " echo $! > $PID_FILE", " shell.env:", " MESSAGE: mymessage", " NETCAT_PORT: $brooklyn:attributeWhenReady(\"netcat.port\")", @@ -122,6 +121,7 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { " brooklyn.config:", " " + OnPublicNetworkEnricher.SENSORS.getName() + ":", " - netcat.port"); + Entity app = createStartWaitAndLogApplication(yaml); VanillaSoftwareProcess entity = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, VanillaSoftwareProcess.class)); @@ -139,29 +139,26 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { String yaml = Joiner.on("\n").join( locationYaml, "services:", - "- type: " + VanillaSoftwareProcess.class.getName(), - " name: server1", - " brooklyn.parameters:", - " - name: netcat.port", - " type: port", - " default: " + netcatPort, - " brooklyn.config:", - " install.command: |", - " yum install -y nc", - " launch.command: |", - " echo " + message + " | nc -l " + netcatPort + " > netcat.out &", - " echo $! > $PID_FILE", - " checkRunning.command: |", - " true", - "- type: " + VanillaSoftwareProcess.class.getName(), - " name: server2", - " brooklyn.config:", - " install.command: |", - " yum install -y nc", - " launch.command: |", - " true", - " checkRunning.command: |", - " true"); + " - type: " + VanillaSoftwareProcess.class.getName(), + " name: server1", + " brooklyn.parameters:", + " - name: netcat.port", + " type: port", + " default: " + netcatPort, + " brooklyn.config:", + " install.command: |", + " yum install -y nc", + " launch.command: |", + " echo " + message + " | nc -l " + netcatPort + " > netcat.out &", + " echo $! > $PID_FILE", + " - type: " + VanillaSoftwareProcess.class.getName(), + " name: server2", + " brooklyn.config:", + " install.command: |", + " yum install -y nc", + " launch.command: true", + " checkRunning.command: true"); + Entity app = createStartWaitAndLogApplication(yaml); Entities.dumpInfo(app); @@ -191,16 +188,37 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { } @Test(groups={"Live"}) + public void testTomcatPod() throws Exception { + String yaml = Joiner.on("\n").join( + locationYaml, + "services:", + " - type: " + KubernetesPod.class.getName(), + " brooklyn.config:", + " docker.container.imageName: tomcat", + " docker.container.inboundPorts: [ \"8080\" ]"); + + runTomcat(yaml); + } + + @Test(groups={"Live"}) public void testTomcatContainer() throws Exception { String yaml = Joiner.on("\n").join( locationYaml, "services:", - "- type: " + KubernetesPod.class.getName(), - " brooklyn.config:", - " docker.container.imageName: tomcat", - " docker.container.inboundPorts: [ \"8080\" ]"); + " - type: " + DockerContainer.class.getName(), + " brooklyn.config:", + " docker.container.imageName: tomcat", + " docker.container.inboundPorts: [ \"8080\" ]"); + + runTomcat(yaml); + } + + /** + * Assumes that the {@link DockerContainer} entity uses port 8080. + */ + protected void runTomcat(String yaml) throws Exception { Entity app = createStartWaitAndLogApplication(yaml); - KubernetesPod entity = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, KubernetesPod.class)); + DockerContainer entity = Iterables.getOnlyElement(Entities.descendantsAndSelf(app, DockerContainer.class)); Entities.dumpInfo(app); String publicMapped = assertAttributeEventuallyNonNull(entity, Sensors.newStringSensor("docker.port.8080.mapped.public")); @@ -220,7 +238,7 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { "services:", " - type: " + KubernetesPod.class.getName(), " brooklyn.children:", - " - type: " + KubernetesPod.class.getName(), + " - type: " + DockerContainer.class.getName(), " id: wordpress-mysql", " name: mysql", " brooklyn.config:", @@ -231,7 +249,7 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { " MYSQL_ROOT_PASSWORD: \"password\"", " provisioning.properties:", " deployment: wordpress-mysql-" + randomId, - " - type: " + KubernetesPod.class.getName(), + " - type: " + DockerContainer.class.getName(), " id: wordpress", " name: wordpress", " brooklyn.config:", @@ -239,16 +257,16 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { " docker.container.inboundPorts:", " - \"80\"", " docker.container.environment:", - " WORDPRESS_DB_HOST: \"wordpress-mysql\"", + " WORDPRESS_DB_HOST: \"wordpress-mysql" + randomId + "\"", " WORDPRESS_DB_PASSWORD: \"password\"", " provisioning.properties:", " deployment: wordpress-" + randomId); - runWordpress(yaml); + runWordpress(yaml, randomId); } @Test(groups={"Live"}) - public void testWordpressInSeperatePods() throws Exception { + public void testWordpressInPods() throws Exception { // TODO docker.container.inboundPorts doesn't accept list of ints - need to use quotes String randomId = Identifiers.makeRandomId(4); String yaml = Joiner.on("\n").join( @@ -263,6 +281,8 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { " - \"3306\"", " docker.container.environment:", " MYSQL_ROOT_PASSWORD: \"password\"", + " deployment: wordpress-mysql-" + randomId, + " pod: wordpress-mysql-pod-" + randomId, " - type: " + KubernetesPod.class.getName(), " id: wordpress", " name: wordpress", @@ -271,31 +291,35 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { " docker.container.inboundPorts:", " - \"80\"", " docker.container.environment:", - " WORDPRESS_DB_HOST: \"wordpress-mysql\"", + " WORDPRESS_DB_HOST: \"wordpress-mysql" + randomId + "\"", " WORDPRESS_DB_PASSWORD: \"password\"", - " provisioning.properties:", - " deployment: wordpress-" + randomId); + " deployment: wordpress-" + randomId); - runWordpress(yaml); + runWordpress(yaml, randomId); } /** - * Assumes that the {@link KubernetesPod} entities have display names of "mysql" and "wordpress", + * Assumes that the {@link DockerContainer} entities have display names of "mysql" and "wordpress", * and that they use ports 3306 and 80 respectively. */ - protected void runWordpress(String yaml) throws Exception { + protected void runWordpress(String yaml, String randomId) throws Exception { Entity app = createStartWaitAndLogApplication(yaml); Entities.dumpInfo(app); - Iterable<KubernetesPod> containers = Entities.descendantsAndSelf(app, KubernetesPod.class); - KubernetesPod mysql = Iterables.find(containers, EntityPredicates.displayNameEqualTo("mysql")); - KubernetesPod wordpress = Iterables.find(containers, EntityPredicates.displayNameEqualTo("wordpress")); + Iterable<DockerContainer> containers = Entities.descendantsAndSelf(app, DockerContainer.class); + DockerContainer mysql = Iterables.find(containers, EntityPredicates.displayNameEqualTo("mysql")); + DockerContainer wordpress = Iterables.find(containers, EntityPredicates.displayNameEqualTo("wordpress")); String mysqlPublicPort = assertAttributeEventuallyNonNull(mysql, Sensors.newStringSensor("docker.port.3306.mapped.public")); assertReachableEventually(HostAndPort.fromString(mysqlPublicPort)); + assertAttributeEqualsEventually(mysql, KubernetesPod.KUBERNETES_POD, "wordpress-mysql-pod-" + randomId); + assertAttributeEqualsEventually(mysql, KubernetesPod.KUBERNETES_NAMESPACE, "amp"); + assertAttributeEqualsEventually(mysql, KubernetesPod.KUBERNETES_SERVICE, "wordpress-mysql-" + randomId); String wordpressPublicPort = assertAttributeEventuallyNonNull(wordpress, Sensors.newStringSensor("docker.port.80.mapped.public")); assertReachableEventually(HostAndPort.fromString(wordpressPublicPort)); + assertAttributeEqualsEventually(wordpress, KubernetesPod.KUBERNETES_NAMESPACE, "amp"); + assertAttributeEqualsEventually(wordpress, KubernetesPod.KUBERNETES_SERVICE, "wordpress-" + randomId); // TODO more assertions (e.g. wordpress can successfully reach the database) } @@ -309,7 +333,7 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { " brooklyn.config:", " docker.container.imageName: tomcat", " docker.container.inboundPorts:", - " - \"8080\"", + " - \"8080\"", " shell.env:", " CLUSTER_ID: \"id\"", " CLUSTER_TOKEN: \"token\""); @@ -343,7 +367,7 @@ public class KubernetesLocationYamlLiveTest extends AbstractYamlTest { assertEntityHealthy(entity); assertAttributeEqualsEventually(entity, KubernetesResource.RESOURCE_NAME, "nginx-replication-controller"); assertAttributeEqualsEventually(entity, KubernetesResource.RESOURCE_TYPE, "ReplicationController"); - assertAttributeEqualsEventually(entity, KubernetesLocationConfig.KUBERNETES_NAMESPACE, "default"); + assertAttributeEqualsEventually(entity, KubernetesResource.KUBERNETES_NAMESPACE, "default"); assertAttributeEventually(entity, SoftwareProcess.ADDRESS, and(notNull(), not(equalTo("0.0.0.0")))); assertAttributeEventually(entity, SoftwareProcess.SUBNET_ADDRESS, and(notNull(), not(equalTo("0.0.0.0")))); }
