http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java ---------------------------------------------------------------------- diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java index 0000000,0000000..bcd8a07 new file mode 100644 --- /dev/null +++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocation.java @@@ -1,0 -1,0 +1,261 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.openshift; ++ ++import com.google.common.collect.ImmutableSet; ++import io.fabric8.kubernetes.api.model.Container; ++import io.fabric8.kubernetes.api.model.HasMetadata; ++import io.fabric8.kubernetes.api.model.Namespace; ++import io.fabric8.kubernetes.api.model.Pod; ++import io.fabric8.kubernetes.api.model.PodTemplateSpec; ++import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder; ++import io.fabric8.kubernetes.client.KubernetesClient; ++import io.fabric8.kubernetes.client.KubernetesClientException; ++import io.fabric8.openshift.api.model.DeploymentConfig; ++import io.fabric8.openshift.api.model.DeploymentConfigBuilder; ++import io.fabric8.openshift.api.model.DeploymentConfigStatus; ++import io.fabric8.openshift.api.model.Project; ++import io.fabric8.openshift.api.model.ProjectBuilder; ++import io.fabric8.openshift.client.OpenShiftClient; ++import org.apache.brooklyn.api.entity.Entity; ++import org.apache.brooklyn.api.location.LocationSpec; ++import org.apache.brooklyn.container.entity.openshift.OpenShiftPod; ++import org.apache.brooklyn.container.entity.openshift.OpenShiftResource; ++import org.apache.brooklyn.container.location.kubernetes.KubernetesClientRegistry; ++import org.apache.brooklyn.container.location.kubernetes.KubernetesLocation; ++import org.apache.brooklyn.container.location.kubernetes.machine.KubernetesMachineLocation; ++import org.apache.brooklyn.location.ssh.SshMachineLocation; ++import org.apache.brooklyn.util.core.config.ConfigBag; ++import org.apache.brooklyn.util.core.config.ResolvingConfigBag; ++import org.apache.brooklyn.util.net.Networking; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++ ++import java.net.InetAddress; ++import java.util.Map; ++ ++public class OpenShiftLocation extends KubernetesLocation implements OpenShiftLocationConfig { ++ ++ public static final String OPENSHIFT_GENERATED_BY = "openshift.io/generated-by"; ++ private static final Logger LOG = LoggerFactory.getLogger(OpenShiftLocation.class); ++ private OpenShiftClient client; ++ ++ public OpenShiftLocation() { ++ super(); ++ } ++ ++ public OpenShiftLocation(Map<?, ?> properties) { ++ super(properties); ++ } ++ ++ @Override ++ protected KubernetesClient getClient(ConfigBag config) { ++ if (client == null) { ++ KubernetesClientRegistry registry = getConfig(OPENSHIFT_CLIENT_REGISTRY); ++ client = (OpenShiftClient) registry.getKubernetesClient(ResolvingConfigBag.newInstanceExtending(getManagementContext(), config)); ++ } ++ return client; ++ } ++ ++ @Override ++ protected boolean handleResourceDelete(String resourceType, String resourceName, String namespace) { ++ if (super.handleResourceDelete(resourceType, resourceName, namespace)) { ++ return true; ++ } ++ ++ try { ++ switch (resourceType) { ++ case OpenShiftResource.DEPLOYMENT_CONFIG: ++ return client.deploymentConfigs().inNamespace(namespace).withName(resourceName).delete(); ++ case OpenShiftResource.PROJECT: ++ return client.projects().withName(resourceName).delete(); ++ case OpenShiftResource.TEMPLATE: ++ return client.templates().inNamespace(namespace).withName(resourceName).delete(); ++ case OpenShiftResource.BUILD_CONFIG: ++ return client.buildConfigs().inNamespace(namespace).withName(resourceName).delete(); ++ } ++ } catch (KubernetesClientException kce) { ++ LOG.warn("Error deleting resource {}: {}", resourceName, kce); ++ } ++ return false; ++ } ++ ++ @Override ++ protected boolean findResourceAddress(LocationSpec<? extends KubernetesMachineLocation> locationSpec, Entity entity, HasMetadata metadata, String resourceType, String resourceName, String namespace) { ++ if (super.findResourceAddress(locationSpec, entity, metadata, resourceType, resourceName, namespace)) { ++ return true; ++ } ++ ++ if (resourceType.equals(OpenShiftResource.DEPLOYMENT_CONFIG)) { ++ DeploymentConfig deploymentConfig = (DeploymentConfig) metadata; ++ Map<String, String> labels = deploymentConfig.getSpec().getTemplate().getMetadata().getLabels(); ++ Pod pod = getPod(namespace, labels); ++ entity.sensors().set(OpenShiftPod.KUBERNETES_POD, pod.getMetadata().getName()); ++ ++ InetAddress node = Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName()); ++ String podAddress = pod.getStatus().getPodIP(); ++ ++ locationSpec.configure("address", node); ++ locationSpec.configure(SshMachineLocation.PRIVATE_ADDRESSES, ImmutableSet.of(podAddress)); ++ ++ return true; ++ } else { ++ return false; ++ } ++ } ++ ++ @Override ++ protected synchronized Namespace createOrGetNamespace(final String name, Boolean create) { ++ Project project = client.projects().withName(name).get(); ++ ExitCondition projectReady = new ExitCondition() { ++ @Override ++ public Boolean call() { ++ Project actualProject = client.projects().withName(name).get(); ++ return actualProject != null && actualProject.getStatus().getPhase().equals(PHASE_ACTIVE); ++ } ++ ++ @Override ++ public String getFailureMessage() { ++ Project actualProject = client.projects().withName(name).get(); ++ return "Project for " + name + " " + (actualProject == null ? "absent" : " status " + actualProject.getStatus()); ++ } ++ }; ++ if (project != null) { ++ LOG.debug("Found project {}, returning it.", project); ++ } else if (create) { ++ project = client.projects().create(new ProjectBuilder().withNewMetadata().withName(name).endMetadata().build()); ++ LOG.debug("Created project {}.", project); ++ } else { ++ throw new IllegalStateException("Project " + name + " does not exist and namespace.create is not set"); ++ } ++ waitForExitCondition(projectReady); ++ return client.namespaces().withName(name).get(); ++ } ++ ++ @Override ++ protected synchronized void deleteEmptyNamespace(final String name) { ++ if (!name.equals("default") && isNamespaceEmpty(name)) { ++ if (client.projects().withName(name).get() != null && ++ !client.projects().withName(name).get().getStatus().getPhase().equals(PHASE_TERMINATING)) { ++ client.projects().withName(name).delete(); ++ ExitCondition exitCondition = new ExitCondition() { ++ @Override ++ public Boolean call() { ++ return client.projects().withName(name).get() == null; ++ } ++ ++ @Override ++ public String getFailureMessage() { ++ return "Project " + name + " still present"; ++ } ++ }; ++ waitForExitCondition(exitCondition); ++ } ++ } ++ } ++ ++ @Override ++ protected boolean isNamespaceEmpty(String namespace) { ++ return client.deploymentConfigs().inNamespace(namespace).list().getItems().isEmpty() && ++ client.services().inNamespace(namespace).list().getItems().isEmpty() && ++ client.secrets().inNamespace(namespace).list().getItems().isEmpty(); ++ } ++ ++ @Override ++ protected void deploy(final String namespace, Entity entity, Map<String, String> metadata, final String deploymentName, Container container, final Integer replicas, Map<String, String> secrets) { ++ PodTemplateSpecBuilder podTemplateSpecBuilder = new PodTemplateSpecBuilder() ++ .withNewMetadata() ++ .addToLabels("name", deploymentName) ++ .addToLabels(metadata) ++ .endMetadata() ++ .withNewSpec() ++ .addToContainers(container) ++ .endSpec(); ++ if (secrets != null) { ++ for (String secretName : secrets.keySet()) { ++ podTemplateSpecBuilder.withNewSpec() ++ .addToContainers(container) ++ .addNewImagePullSecret(secretName) ++ .endSpec(); ++ } ++ } ++ PodTemplateSpec template = podTemplateSpecBuilder.build(); ++ DeploymentConfig deployment = new DeploymentConfigBuilder() ++ .withNewMetadata() ++ .withName(deploymentName) ++ .addToAnnotations(OPENSHIFT_GENERATED_BY, "Apache Brooklyn") ++ .addToAnnotations(CLOUDSOFT_ENTITY_ID, entity.getId()) ++ .addToAnnotations(CLOUDSOFT_APPLICATION_ID, entity.getApplicationId()) ++ .endMetadata() ++ .withNewSpec() ++ .withNewStrategy() ++ .withType("Recreate") ++ .endStrategy() ++ .addNewTrigger() ++ .withType("ConfigChange") ++ .endTrigger() ++ .withReplicas(replicas) ++ .addToSelector("name", deploymentName) ++ .withTemplate(template) ++ .endSpec() ++ .build(); ++ client.deploymentConfigs().inNamespace(namespace).create(deployment); ++ ExitCondition exitCondition = new ExitCondition() { ++ @Override ++ public Boolean call() { ++ DeploymentConfig dc = client.deploymentConfigs().inNamespace(namespace).withName(deploymentName).get(); ++ DeploymentConfigStatus status = (dc == null) ? null : dc.getStatus(); ++ Integer replicas = (status == null) ? null : status.getAvailableReplicas(); ++ return replicas != null && replicas.intValue() == replicas; ++ } ++ ++ @Override ++ public String getFailureMessage() { ++ DeploymentConfig dc = client.deploymentConfigs().inNamespace(namespace).withName(deploymentName).get(); ++ DeploymentConfigStatus status = (dc == null) ? null : dc.getStatus(); ++ return "Namespace=" + namespace + "; deploymentName= " + deploymentName + "; Deployment=" + dc + "; status=" + status; ++ } ++ }; ++ waitForExitCondition(exitCondition); ++ LOG.debug("Deployed {} to namespace {}.", deployment, namespace); ++ } ++ ++ @Override ++ protected String getContainerResourceType() { ++ return OpenShiftResource.DEPLOYMENT_CONFIG; ++ } ++ ++ @Override ++ protected void undeploy(final String namespace, final String deployment, final String pod) { ++ client.deploymentConfigs().inNamespace(namespace).withName(deployment).delete(); ++ ExitCondition exitCondition = new ExitCondition() { ++ @Override ++ public Boolean call() { ++ return client.deploymentConfigs().inNamespace(namespace).withName(deployment).get() == null; ++ } ++ ++ @Override ++ public String getFailureMessage() { ++ return "No deployment with namespace=" + namespace + ", deployment=" + deployment; ++ } ++ }; ++ waitForExitCondition(exitCondition); ++ } ++ ++}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationConfig.java ---------------------------------------------------------------------- diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationConfig.java index 0000000,0000000..e089b93 new file mode 100644 --- /dev/null +++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationConfig.java @@@ -1,0 -1,0 +1,32 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.openshift; ++ ++import org.apache.brooklyn.config.ConfigKey; ++import org.apache.brooklyn.container.location.kubernetes.KubernetesClientRegistry; ++import org.apache.brooklyn.core.config.ConfigKeys; ++ ++public interface OpenShiftLocationConfig { ++ ++ ConfigKey<KubernetesClientRegistry> OPENSHIFT_CLIENT_REGISTRY = ConfigKeys.newConfigKey( ++ KubernetesClientRegistry.class, "openShiftClientRegistry", ++ "Registry/Factory for creating OpenShift client; default is almost always fine, " + ++ "except where tests want to customize behaviour", OpenShiftClientRegistryImpl.INSTANCE); ++} ++ http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolver.java ---------------------------------------------------------------------- diff --cc locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolver.java index 0000000,0000000..3dc129f new file mode 100644 --- /dev/null +++ b/locations/container/src/main/java/org/apache/brooklyn/container/location/openshift/OpenShiftLocationResolver.java @@@ -1,0 -1,0 +1,65 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.openshift; ++ ++import org.apache.brooklyn.api.location.Location; ++import org.apache.brooklyn.api.location.LocationResolver; ++import org.apache.brooklyn.core.location.AbstractLocationResolver; ++import org.apache.brooklyn.core.location.LocationConfigUtils; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++ ++/** ++ * Locations starting with the given prefix (@code "openshift") will use this resolver, to instantiate ++ * a {@link OpenShiftLocation}. ++ * <p> ++ * We ensure that config will be picked up from brooklyn.properties using the appropriate precedence: ++ * <ol> ++ * <li>named location config ++ * <li>Prefix {@code brooklyn.location.openshift.} ++ * <li>Prefix {@code brooklyn.openshift.} ++ * </ol> ++ */ ++public class OpenShiftLocationResolver extends AbstractLocationResolver implements LocationResolver { ++ ++ public static final Logger log = LoggerFactory.getLogger(OpenShiftLocationResolver.class); ++ ++ public static final String PREFIX = "openshift"; ++ ++ @Override ++ public boolean isEnabled() { ++ return LocationConfigUtils.isResolverPrefixEnabled(managementContext, getPrefix()); ++ } ++ ++ @Override ++ public String getPrefix() { ++ return PREFIX; ++ } ++ ++ @Override ++ protected Class<? extends Location> getLocationType() { ++ return OpenShiftLocation.class; ++ } ++ ++ @Override ++ protected SpecParser getSpecParser() { ++ return new SpecParser(getPrefix()).setExampleUsage("\"openshift\""); ++ } ++ ++} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/resources/META-INF/services/org.apache.brooklyn.api.location.LocationResolver ---------------------------------------------------------------------- diff --cc locations/container/src/main/resources/META-INF/services/org.apache.brooklyn.api.location.LocationResolver index 0000000,0000000..25d27bb new file mode 100644 --- /dev/null +++ b/locations/container/src/main/resources/META-INF/services/org.apache.brooklyn.api.location.LocationResolver @@@ -1,0 -1,0 +1,21 @@@ ++# ++# Licensed to the Apache Software Foundation (ASF) under one ++# or more contributor license agreements. See the NOTICE file ++# distributed with this work for additional information ++# regarding copyright ownership. The ASF licenses this file ++# to you under the Apache License, Version 2.0 (the ++# "License"); you may not use this file except in compliance ++# with the License. You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, ++# software distributed under the License is distributed on an ++# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++# KIND, either express or implied. See the License for the ++# specific language governing permissions and limitations ++# under the License. ++# ++org.apache.brooklyn.container.location.docker.DockerLocationResolver ++org.apache.brooklyn.container.location.kubernetes.KubernetesLocationResolver ++org.apache.brooklyn.container.location.openshift.OpenShiftLocationResolver http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/main/resources/OSGI-INF/blueprint/blueprint.xml ---------------------------------------------------------------------- diff --cc locations/container/src/main/resources/OSGI-INF/blueprint/blueprint.xml index 0000000,0000000..a831291 new file mode 100644 --- /dev/null +++ b/locations/container/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@@ -1,0 -1,0 +1,37 @@@ ++<?xml version="1.0" encoding="UTF-8"?> ++<!-- ++Copyright 2015 The Apache Software Foundation. ++ ++Licensed under the Apache License, Version 2.0 (the "License"); ++you may not use this file except in compliance with the License. ++You may obtain a copy of the License at ++ ++ http://www.apache.org/licenses/LICENSE-2.0 ++ ++Unless required by applicable law or agreed to in writing, software ++distributed under the License is distributed on an "AS IS" BASIS, ++WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++See the License for the specific language governing permissions and ++limitations under the License. ++--> ++<blueprint xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ++ xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" ++ xsi:schemaLocation=" ++ http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd"> ++ ++ ++ <bean id="dockerLocationResolver" scope="prototype" ++ class="org.apache.brooklyn.container.location.docker.DockerLocationResolver"/> ++ <bean id="kubernetesLocationResolver" scope="prototype" ++ class="org.apache.brooklyn.container.location.kubernetes.KubernetesLocationResolver"/> ++ <bean id="openshiftLocationResolver" scope="prototype" ++ class="org.apache.brooklyn.container.location.openshift.OpenShiftLocationResolver"/> ++ ++ <service id="dockerLocationResolverService" ref="dockerLocationResolver" ++ interface="org.apache.brooklyn.api.location.LocationResolver"/> ++ <service id="kubernetesLocationResolverService" ref="kubernetesLocationResolver" ++ interface="org.apache.brooklyn.api.location.LocationResolver"/> ++ <service id="openshiftLocationResolverService" ref="openshiftLocationResolver" ++ interface="org.apache.brooklyn.api.location.LocationResolver"/> ++ ++</blueprint> http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocationLiveTest.java ---------------------------------------------------------------------- diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocationLiveTest.java index 0000000,0000000..dcb3324 new file mode 100644 --- /dev/null +++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerJcloudsLocationLiveTest.java @@@ -1,0 -1,0 +1,265 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.docker; ++ ++import com.google.common.base.Optional; ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Lists; ++import org.apache.brooklyn.api.location.MachineLocation; ++import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport; ++import org.apache.brooklyn.location.jclouds.BasicJcloudsLocationCustomizer; ++import org.apache.brooklyn.location.jclouds.JcloudsLocation; ++import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig; ++import org.apache.brooklyn.location.jclouds.JcloudsLocationCustomizer; ++import org.apache.brooklyn.location.jclouds.JcloudsSshMachineLocation; ++import org.apache.brooklyn.util.collections.MutableMap; ++import org.apache.brooklyn.util.os.Os; ++import org.jclouds.compute.ComputeService; ++import org.jclouds.compute.domain.Image; ++import org.jclouds.compute.domain.OsFamily; ++import org.jclouds.compute.options.TemplateOptions; ++import org.jclouds.docker.compute.options.DockerTemplateOptions; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import org.testng.annotations.AfterMethod; ++import org.testng.annotations.BeforeMethod; ++import org.testng.annotations.Test; ++ ++import java.lang.reflect.Method; ++import java.util.List; ++import java.util.Map; ++ ++import static com.google.common.base.Preconditions.checkNotNull; ++import static org.testng.Assert.*; ++ ++/** ++ * Assumes that a pre-existing swarm endpoint is available. See system properties and the defaults ++ * below. ++ */ ++public class DockerJcloudsLocationLiveTest extends BrooklynAppLiveTestSupport { ++ ++ private static final Logger LOG = LoggerFactory.getLogger(DockerJcloudsLocationLiveTest.class); ++ ++ private static final String SWARM_ENDPOINT = System.getProperty("test.brooklyn-container-service.docker.swarmEndpoint", "https://10.104.0.162:3376/"); ++ private static final String IDENTITY_FILE_PATH = System.getProperty("test.brooklyn-container-service.docker.identity", Os.tidyPath("~/.docker/.certs/cert.pem")); ++ private static final String CREDENTIAL_FILE_PATH = System.getProperty("test.brooklyn-container-service.docker.credential", Os.tidyPath("~/.docker/.certs/key.pem")); ++ private static final String SWARM_NETWORK_NAME = System.getProperty("test.brooklyn-container-service.docker.networkName", Os.tidyPath("brooklyn")); ++ ++ protected DockerJcloudsLocation loc; ++ protected List<MachineLocation> machines; ++ protected DockerTemplateOptions templateOptions; ++ ++ @BeforeMethod(alwaysRun = true) ++ @Override ++ public void setUp() throws Exception { ++ super.setUp(); ++ machines = Lists.newCopyOnWriteArrayList(); ++ } ++ ++ @AfterMethod(alwaysRun = true) ++ @Override ++ public void tearDown() throws Exception { ++ for (MachineLocation machine : machines) { ++ try { ++ loc.release(machine); ++ } catch (Exception e) { ++ LOG.error("Error releasing machine " + machine + " in location " + loc, e); ++ } ++ } ++ super.tearDown(); ++ } ++ ++ protected DockerJcloudsLocation newDockerLocation(Map<String, ?> flags) throws Exception { ++ JcloudsLocationCustomizer locationCustomizer = new BasicJcloudsLocationCustomizer() { ++ @Override ++ public void customize(JcloudsLocation location, ComputeService computeService, TemplateOptions templateOptions) { ++ DockerJcloudsLocationLiveTest.this.templateOptions = (DockerTemplateOptions) templateOptions; ++ } ++ }; ++ Map<String, ?> templateOptionsOverrides = (Map<String, ?>) flags.get(JcloudsLocation.TEMPLATE_OPTIONS.getName()); ++ Map<String, ?> templateOptions = MutableMap.<String, Object>builder() ++ .put("networkMode", SWARM_NETWORK_NAME) ++ .putAll(templateOptionsOverrides != null ? templateOptionsOverrides : ImmutableMap.<String, Object>of()) ++ .build(); ++ Map<String, ?> allFlags = MutableMap.<String, Object>builder() ++ .put("identity", IDENTITY_FILE_PATH) ++ .put("credential", CREDENTIAL_FILE_PATH) ++ .put("endpoint", SWARM_ENDPOINT) ++ .put("tags", ImmutableList.of(getClass().getName())) ++ .put(JcloudsLocation.WAIT_FOR_SSHABLE.getName(), false) ++ .put(JcloudsLocation.JCLOUDS_LOCATION_CUSTOMIZERS.getName(), ImmutableList.of(locationCustomizer)) ++ .putAll(flags) ++ .put(JcloudsLocation.TEMPLATE_OPTIONS.getName(), templateOptions) ++ .build(); ++ return (DockerJcloudsLocation) mgmt.getLocationRegistry().getLocationManaged("docker", allFlags); ++ } ++ ++ private JcloudsSshMachineLocation newDockerMachine(DockerJcloudsLocation loc, Map<?, ?> flags) throws Exception { ++ MachineLocation result = loc.obtain(flags); ++ machines.add(result); ++ return (JcloudsSshMachineLocation) result; ++ } ++ ++ @Test(groups = {"Live", "Live-sanity"}) ++ public void testDefaultImageHasAutoGeneratedCredentials() throws Exception { ++ loc = newDockerLocation(ImmutableMap.<String, Object>of()); ++ JcloudsSshMachineLocation machine = newDockerMachine(loc, ImmutableMap.<String, Object>of( ++ JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m")); ++ ++ assertMachineSshableSecureAndFromImage(machine, "cloudsoft/centos:7"); ++ } ++ ++ @Test(groups = {"Live", "Live-sanity"}) ++ public void testExplicitCredentialsNotOverwritten() throws Exception { ++ loc = newDockerLocation(ImmutableMap.<String, Object>of()); ++ JcloudsSshMachineLocation machine = newDockerMachine(loc, MutableMap.of( ++ JcloudsLocationConfig.LOGIN_USER, "myuser", ++ JcloudsLocationConfig.LOGIN_USER_PASSWORD, "mypassword")); ++ Image image = getOptionalImage(machine).get(); ++ assertEquals(image.getDescription(), "cloudsoft/centos:7"); ++ assertEquals(templateOptions.getLoginUser(), "myuser"); ++ assertEquals(templateOptions.getLoginPassword(), "mypassword"); ++ assertEquals(templateOptions.getLoginPassword(), "mypassword"); ++ assertEnvNotContainsKey(templateOptions, "CLOUDSOFT_ROOT_PASSWORD"); ++ } ++ ++ @Test(groups = {"Live", "Live-sanity"}) ++ public void testExplicitImageIdNotOverwritten() throws Exception { ++ // TODO This id will likely change sometimes; once CCS-29 is done, then use an image name. ++ // Assumes we have executed: ++ // docker run ubuntu /bin/echo 'Hello world' ++ // which will have downloaded the ubuntu image with the given id. ++ String imageId = "sha256:2fa927b5cdd31cdec0027ff4f45ef4343795c7a2d19a9af4f32425132a222330"; ++ loc = newDockerLocation(ImmutableMap.<String, Object>of()); ++ JcloudsSshMachineLocation machine = newDockerMachine(loc, MutableMap.of( ++ JcloudsLocation.IMAGE_ID, imageId, ++ JcloudsLocation.TEMPLATE_OPTIONS, ImmutableMap.of( ++ "entrypoint", ImmutableList.of("/bin/sleep", "1000")))); ++ Image image = getOptionalImage(machine).get(); ++ assertEquals(image.getId(), imageId); ++ } ++ ++ @Test(groups = {"Live", "Live-sanity"}) ++ public void testMatchingImageDescriptionHasAutoGeneratedCredentials() throws Exception { ++ loc = newDockerLocation(ImmutableMap.<String, Object>of()); ++ JcloudsSshMachineLocation machine = newDockerMachine(loc, ImmutableMap.<String, Object>of( ++ JcloudsLocation.IMAGE_DESCRIPTION_REGEX.getName(), "cloudsoft/centos:7", ++ JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m")); ++ ++ assertTrue(machine.isSshable(), "machine=" + machine); ++ } ++ ++ @Test(groups = {"Live", "Live-sanity"}) ++ public void testMatchingOsFamilyCentosHasAutoGeneratedCredentials() throws Exception { ++ loc = newDockerLocation(ImmutableMap.<String, Object>of()); ++ JcloudsSshMachineLocation machine = newDockerMachine(loc, ImmutableMap.<String, Object>of( ++ JcloudsLocation.OS_FAMILY.getName(), OsFamily.CENTOS, ++ JcloudsLocation.OS_VERSION_REGEX.getName(), "7.*", ++ JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m")); ++ ++ assertMachineSshableSecureAndFromImage(machine, "cloudsoft/centos:7"); ++ } ++ ++ @Test(groups = {"Live", "Live-sanity"}) ++ public void testMatchingOsFamilyUbuntu14HasAutoGeneratedCredentials() throws Exception { ++ loc = newDockerLocation(ImmutableMap.<String, Object>of()); ++ JcloudsSshMachineLocation machine = newDockerMachine(loc, ImmutableMap.<String, Object>of( ++ JcloudsLocation.OS_FAMILY.getName(), OsFamily.UBUNTU, ++ JcloudsLocation.OS_VERSION_REGEX.getName(), "14.04.*", ++ JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m")); ++ ++ assertMachineSshableSecureAndFromImage(machine, "cloudsoft/ubuntu:14.04"); ++ } ++ ++ @Test(groups = {"Live", "Live-sanity"}) ++ public void testMatchingOsFamilyUbuntu16HasAutoGeneratedCredentials() throws Exception { ++ loc = newDockerLocation(ImmutableMap.<String, Object>of()); ++ JcloudsSshMachineLocation machine = newDockerMachine(loc, ImmutableMap.<String, Object>of( ++ JcloudsLocation.OS_FAMILY.getName(), OsFamily.UBUNTU, ++ JcloudsLocation.OS_VERSION_REGEX.getName(), "16.04.*", ++ JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m")); ++ ++ assertMachineSshableSecureAndFromImage(machine, "cloudsoft/ubuntu:16.04"); ++ } ++ ++ @Test(groups = {"Live", "Live-sanity"}) ++ public void testMatchingOsFamilyConfiguredOnLocationHasAutoGeneratedCredentials() throws Exception { ++ loc = newDockerLocation(ImmutableMap.<String, Object>of( ++ JcloudsLocation.OS_FAMILY.getName(), OsFamily.UBUNTU, ++ JcloudsLocation.OS_VERSION_REGEX.getName(), "16.04.*", ++ JcloudsLocation.WAIT_FOR_SSHABLE.getName(), "1m")); ++ JcloudsSshMachineLocation machine = newDockerMachine(loc, ImmutableMap.<String, Object>of()); ++ ++ assertMachineSshableSecureAndFromImage(machine, "cloudsoft/ubuntu:16.04"); ++ } ++ ++ protected void assertMachineSshableSecureAndFromImage(JcloudsSshMachineLocation machine, String expectedImageDescription) throws Exception { ++ Image image = getOptionalImage(machine).get(); ++ assertEquals(image.getDescription(), expectedImageDescription); ++ assertEquals(templateOptions.getLoginUser(), "root"); ++ assertEnvContainsKeyValue(templateOptions, "CLOUDSOFT_ROOT_PASSWORD", templateOptions.getLoginPassword()); ++ assertPasswordIsSecure(templateOptions.getLoginPassword()); ++ ++ assertTrue(machine.isSshable(), "machine=" + machine); ++ } ++ ++ protected void assertEnvNotContainsKey(DockerTemplateOptions templateOptions, String key) { ++ List<String> env = templateOptions.getEnv(); ++ if (env == null) return; ++ for (String keyval : env) { ++ if (keyval.startsWith(key + "=")) { ++ fail("has key " + key + "; env=" + env); ++ } ++ } ++ } ++ ++ protected void assertEnvContainsKeyValue(DockerTemplateOptions templateOptions, String key, String value) { ++ String keyval = key + "=" + value; ++ List<String> env = templateOptions.getEnv(); ++ if (env == null) { ++ fail("env is null; does not contain " + keyval); ++ } ++ if (!env.contains(keyval)) { ++ fail("env does not contain " + keyval + "; env=" + env); ++ } ++ } ++ ++ protected void assertPasswordIsSecure(String val) { ++ if (!val.matches(".*[0-9].*")) { ++ fail("Password '" + val + "' does not contain a digit"); ++ } ++ if (!val.matches(".*[A-Z].*")) { ++ fail("Password '" + val + "' does not contain an upper-case letter"); ++ } ++ if (val.trim().length() < 7) { ++ fail("Password '" + val + "' is too short"); ++ } ++ ++ LOG.debug("Password '" + val + "' passes basic security check"); ++ } ++ ++ @SuppressWarnings("unchecked") ++ protected Optional<Image> getOptionalImage(JcloudsSshMachineLocation machine) throws Exception { ++ Method method = machine.getClass().getDeclaredMethod("getOptionalImage"); ++ method.setAccessible(true); ++ Optional<Image> result = (Optional<Image>) method.invoke(machine); ++ return checkNotNull(result, "null must not be returned by getOptionalImage, for %s", machine); ++ } ++} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerLocationResolverTest.java ---------------------------------------------------------------------- diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerLocationResolverTest.java index 0000000,0000000..117d9d9 new file mode 100644 --- /dev/null +++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/docker/DockerLocationResolverTest.java @@@ -1,0 -1,0 +1,118 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.docker; ++ ++import org.apache.brooklyn.api.location.LocationSpec; ++import org.apache.brooklyn.core.internal.BrooklynProperties; ++import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import org.testng.annotations.BeforeMethod; ++import org.testng.annotations.Test; ++ ++import java.util.Map; ++ ++import static org.testng.Assert.assertEquals; ++import static org.testng.Assert.assertTrue; ++ ++public class DockerLocationResolverTest extends BrooklynMgmtUnitTestSupport { ++ ++ @SuppressWarnings("unused") ++ private static final Logger log = LoggerFactory.getLogger(DockerLocationResolverTest.class); ++ ++ private BrooklynProperties brooklynProperties; ++ ++ @BeforeMethod(alwaysRun = true) ++ @Override ++ public void setUp() throws Exception { ++ super.setUp(); ++ brooklynProperties = mgmt.getBrooklynProperties(); ++ ++ brooklynProperties.put("brooklyn.location.docker.identity", "docker-id"); ++ brooklynProperties.put("brooklyn.location.docker.credential", "docker-cred"); ++ } ++ ++ @Test ++ public void testGivesCorrectLocationType() { ++ LocationSpec<?> spec = getLocationSpec("docker"); ++ assertEquals(spec.getType(), DockerJcloudsLocation.class); ++ ++ DockerJcloudsLocation loc = resolve("docker"); ++ assertTrue(loc instanceof DockerJcloudsLocation, "loc=" + loc); ++ } ++ ++ @Test ++ public void testParametersInSpecString() { ++ DockerJcloudsLocation loc = resolve("docker(loginUser=myLoginUser,imageId=myImageId)"); ++ assertEquals(loc.getConfig(DockerJcloudsLocation.LOGIN_USER), "myLoginUser"); ++ assertEquals(loc.getConfig(DockerJcloudsLocation.IMAGE_ID), "myImageId"); ++ } ++ ++ @Test ++ public void testTakesDotSeparateProperty() { ++ brooklynProperties.put("brooklyn.location.docker.loginUser", "myLoginUser"); ++ DockerJcloudsLocation loc = resolve("docker"); ++ assertEquals(loc.getConfig(DockerJcloudsLocation.LOGIN_USER), "myLoginUser"); ++ } ++ ++ @Test ++ public void testPropertiesPrecedence() { ++ // prefer those in "spec" over everything else ++ brooklynProperties.put("brooklyn.location.named.mydocker", "docker:(loginUser=\"loginUser-inSpec\")"); ++ ++ brooklynProperties.put("brooklyn.location.named.mydocker.loginUser", "loginUser-inNamed"); ++ brooklynProperties.put("brooklyn.location.docker.loginUser", "loginUser-inDocker"); ++ brooklynProperties.put("brooklyn.location.jclouds.docker.loginUser", "loginUser-inJcloudsProviderSpecific"); ++ brooklynProperties.put("brooklyn.location.jclouds.loginUser", "loginUser-inJcloudsGeneric"); ++ ++ // prefer those in "named" over everything else ++ brooklynProperties.put("brooklyn.location.named.mydocker.privateKeyFile", "privateKeyFile-inNamed"); ++ brooklynProperties.put("brooklyn.location.docker.privateKeyFile", "privateKeyFile-inDocker"); ++ brooklynProperties.put("brooklyn.location.jclouds.docker.privateKeyFile", "privateKeyFile-inJcloudsProviderSpecific"); ++ brooklynProperties.put("brooklyn.location.jclouds.privateKeyFile", "privateKeyFile-inJcloudsGeneric"); ++ ++ // prefer those in docker-specific ++ brooklynProperties.put("brooklyn.location.docker.publicKeyFile", "publicKeyFile-inDocker"); ++ brooklynProperties.put("brooklyn.location.jclouds.docker.publicKeyFile", "publicKeyFile-inJcloudsProviderSpecific"); ++ brooklynProperties.put("brooklyn.location.jclouds.publicKeyFile", "publicKeyFile-inJcloudsGeneric"); ++ ++ // prefer those in jclouds provider-specific ++ brooklynProperties.put("brooklyn.location.jclouds.docker.privateKeyPassphrase", "privateKeyPassphrase-inJcloudsProviderSpecific"); ++ brooklynProperties.put("brooklyn.location.jclouds.privateKeyPassphrase", "privateKeyPassphrase-inJcloudsGeneric"); ++ ++ // accept those in jclouds generic ++ brooklynProperties.put("brooklyn.location.jclouds.privateKeyData", "privateKeyData-inJcloudsGeneric"); ++ ++ Map<String, Object> conf = resolve("named:mydocker").config().getBag().getAllConfig(); ++ ++ assertEquals(conf.get("loginUser"), "loginUser-inSpec"); ++ assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed"); ++ assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inDocker"); ++ assertEquals(conf.get("privateKeyPassphrase"), "privateKeyPassphrase-inJcloudsProviderSpecific"); ++ assertEquals(conf.get("privateKeyData"), "privateKeyData-inJcloudsGeneric"); ++ } ++ ++ private LocationSpec<?> getLocationSpec(String spec) { ++ return mgmt.getLocationRegistry().getLocationSpec(spec).get(); ++ } ++ ++ private DockerJcloudsLocation resolve(String spec) { ++ return (DockerJcloudsLocation) mgmt.getLocationRegistry().getLocationManaged(spec); ++ } ++} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/ImageChooserTest.java ---------------------------------------------------------------------- diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/ImageChooserTest.java index 0000000,0000000..cb041aa new file mode 100644 --- /dev/null +++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/ImageChooserTest.java @@@ -1,0 -1,0 +1,85 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.kubernetes; ++ ++import org.testng.annotations.BeforeMethod; ++import org.testng.annotations.Test; ++ ++import static org.testng.Assert.assertEquals; ++import static org.testng.Assert.assertFalse; ++ ++public class ImageChooserTest { ++ ++ private ImageChooser chooser; ++ ++ @BeforeMethod(alwaysRun = true) ++ public void setUp() { ++ chooser = new ImageChooser(); ++ } ++ ++ @Test ++ public void testDefault() throws Exception { ++ assertEquals(chooser.chooseImage((String) null, null).get(), "cloudsoft/centos:7"); ++ } ++ ++ @Test ++ public void testCentos() throws Exception { ++ assertEquals(chooser.chooseImage("cEnToS", null).get(), "cloudsoft/centos:7"); ++ } ++ ++ @Test ++ public void testCentos7() throws Exception { ++ assertEquals(chooser.chooseImage("cEnToS", "7").get(), "cloudsoft/centos:7"); ++ } ++ ++ @Test ++ public void testUbnutu() throws Exception { ++ assertEquals(chooser.chooseImage("uBuNtU", null).get(), "cloudsoft/ubuntu:14.04"); ++ } ++ ++ @Test ++ public void testUbnutu14() throws Exception { ++ assertEquals(chooser.chooseImage("uBuNtU", "14.*").get(), "cloudsoft/ubuntu:14.04"); ++ } ++ ++ @Test ++ public void testUbnutu16() throws Exception { ++ assertEquals(chooser.chooseImage("uBuNtU", "16.*").get(), "cloudsoft/ubuntu:16.04"); ++ } ++ ++ @Test ++ public void testAbsentForCentos6() throws Exception { ++ assertFalse(chooser.chooseImage("cEnToS", "6").isPresent()); ++ } ++ ++ @Test ++ public void testAbsentForUbuntu15() throws Exception { ++ assertFalse(chooser.chooseImage("uBuNtU", "15").isPresent()); ++ } ++ ++ @Test ++ public void testAbsentForDebian() throws Exception { ++ assertFalse(chooser.chooseImage("debian", null).isPresent()); ++ } ++ ++ @Test ++ public void testAbsentForWrongOsFamily() throws Exception { ++ assertFalse(chooser.chooseImage("weirdOsFamily", null).isPresent()); ++ } ++} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCertsTest.java ---------------------------------------------------------------------- diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCertsTest.java index 0000000,0000000..b216db6 new file mode 100644 --- /dev/null +++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesCertsTest.java @@@ -1,0 -1,0 +1,162 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.kubernetes; ++ ++import ch.qos.logback.classic.spi.ILoggingEvent; ++import com.google.common.base.Charsets; ++import com.google.common.base.Predicate; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Lists; ++import com.google.common.io.Files; ++import org.apache.brooklyn.test.Asserts; ++import org.apache.brooklyn.test.LogWatcher; ++import org.apache.brooklyn.test.LogWatcher.EventPredicates; ++import org.apache.brooklyn.util.core.config.ConfigBag; ++import org.apache.brooklyn.util.text.Identifiers; ++import org.testng.annotations.AfterMethod; ++import org.testng.annotations.BeforeMethod; ++import org.testng.annotations.Test; ++ ++import java.io.File; ++import java.util.List; ++ ++import static org.testng.Assert.assertEquals; ++import static org.testng.Assert.assertFalse; ++ ++public class KubernetesCertsTest { ++ ++ private List<File> tempFiles; ++ ++ @BeforeMethod(alwaysRun = true) ++ public void setUp() throws Exception { ++ tempFiles = Lists.newArrayList(); ++ } ++ ++ @AfterMethod(alwaysRun = true) ++ public void tearDown() throws Exception { ++ if (tempFiles != null) { ++ for (File tempFile : tempFiles) { ++ tempFile.delete(); ++ } ++ } ++ } ++ ++ @Test ++ public void testCertsAbsent() throws Exception { ++ ConfigBag config = ConfigBag.newInstance(); ++ KubernetesCerts certs = new KubernetesCerts(config); ++ ++ assertFalse(certs.caCertData.isPresent()); ++ assertFalse(certs.clientCertData.isPresent()); ++ assertFalse(certs.clientKeyData.isPresent()); ++ assertFalse(certs.clientKeyAlgo.isPresent()); ++ assertFalse(certs.clientKeyPassphrase.isPresent()); ++ } ++ ++ @Test ++ public void testCertsFromData() throws Exception { ++ ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder() ++ .put(KubernetesLocationConfig.CA_CERT_DATA, "myCaCertData") ++ .put(KubernetesLocationConfig.CLIENT_CERT_DATA, "myClientCertData") ++ .put(KubernetesLocationConfig.CLIENT_KEY_DATA, "myClientKeyData") ++ .put(KubernetesLocationConfig.CLIENT_KEY_ALGO, "myClientKeyAlgo") ++ .put(KubernetesLocationConfig.CLIENT_KEY_PASSPHRASE, "myClientKeyPassphrase") ++ .build()); ++ KubernetesCerts certs = new KubernetesCerts(config); ++ ++ assertEquals(certs.caCertData.get(), "myCaCertData"); ++ assertEquals(certs.clientCertData.get(), "myClientCertData"); ++ assertEquals(certs.clientKeyData.get(), "myClientKeyData"); ++ assertEquals(certs.clientKeyAlgo.get(), "myClientKeyAlgo"); ++ assertEquals(certs.clientKeyPassphrase.get(), "myClientKeyPassphrase"); ++ } ++ ++ @Test ++ public void testCertsFromFile() throws Exception { ++ ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder() ++ .put(KubernetesLocationConfig.CA_CERT_FILE, newTempFile("myCaCertData").getAbsolutePath()) ++ .put(KubernetesLocationConfig.CLIENT_CERT_FILE, newTempFile("myClientCertData").getAbsolutePath()) ++ .put(KubernetesLocationConfig.CLIENT_KEY_FILE, newTempFile("myClientKeyData").getAbsolutePath()) ++ .build()); ++ KubernetesCerts certs = new KubernetesCerts(config); ++ ++ assertEquals(certs.caCertData.get(), "myCaCertData"); ++ assertEquals(certs.clientCertData.get(), "myClientCertData"); ++ assertEquals(certs.clientKeyData.get(), "myClientKeyData"); ++ } ++ ++ @Test ++ public void testCertsFailsIfConflictingConfig() throws Exception { ++ ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder() ++ .put(KubernetesLocationConfig.CA_CERT_DATA, "myCaCertData") ++ .put(KubernetesLocationConfig.CA_CERT_FILE, newTempFile("differentCaCertData").getAbsolutePath()) ++ .build()); ++ try { ++ new KubernetesCerts(config); ++ Asserts.shouldHaveFailedPreviously(); ++ } catch (Exception e) { ++ Asserts.expectedFailureContains(e, "Duplicate conflicting configuration for caCertData and caCertFile"); ++ } ++ } ++ ++ @Test ++ public void testCertsWarnsIfConflictingConfig() throws Exception { ++ ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder() ++ .put(KubernetesLocationConfig.CA_CERT_DATA, "myCaCertData") ++ .put(KubernetesLocationConfig.CA_CERT_FILE, newTempFile("myCaCertData").getAbsolutePath()) ++ .build()); ++ ++ String loggerName = KubernetesCerts.class.getName(); ++ ch.qos.logback.classic.Level logLevel = ch.qos.logback.classic.Level.WARN; ++ Predicate<ILoggingEvent> filter = EventPredicates.containsMessage("Duplicate (matching) configuration for " ++ + "caCertData and caCertFile (continuing)"); ++ LogWatcher watcher = new LogWatcher(loggerName, logLevel, filter); ++ ++ watcher.start(); ++ KubernetesCerts certs; ++ try { ++ certs = new KubernetesCerts(config); ++ watcher.assertHasEvent(); ++ } finally { ++ watcher.close(); ++ } ++ ++ assertEquals(certs.caCertData.get(), "myCaCertData"); ++ } ++ ++ @Test ++ public void testCertsFailsIfFileNotFound() throws Exception { ++ ConfigBag config = ConfigBag.newInstance(ImmutableMap.builder() ++ .put(KubernetesLocationConfig.CA_CERT_FILE, "/path/to/fileDoesNotExist-" + Identifiers.makeRandomId(8)) ++ .build()); ++ try { ++ new KubernetesCerts(config); ++ Asserts.shouldHaveFailedPreviously(); ++ } catch (Exception e) { ++ Asserts.expectedFailureContains(e, "not found on classpath or filesystem"); ++ } ++ } ++ ++ private File newTempFile(String contents) throws Exception { ++ File file = File.createTempFile("KubernetesCertsTest", ".txt"); ++ tempFiles.add(file); ++ Files.write(contents, file, Charsets.UTF_8); ++ return file; ++ } ++} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java ---------------------------------------------------------------------- diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java index 0000000,0000000..6a30650 new file mode 100644 --- /dev/null +++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationLiveTest.java @@@ -1,0 -1,0 +1,235 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.kubernetes; ++ ++import com.google.common.collect.ImmutableList; ++import com.google.common.collect.ImmutableMap; ++import com.google.common.collect.Lists; ++import com.google.common.net.HostAndPort; ++import org.apache.brooklyn.api.location.MachineDetails; ++import org.apache.brooklyn.api.location.OsDetails; ++import org.apache.brooklyn.container.location.kubernetes.machine.KubernetesMachineLocation; ++import org.apache.brooklyn.container.location.kubernetes.machine.KubernetesSshMachineLocation; ++import org.apache.brooklyn.core.location.BasicMachineDetails; ++import org.apache.brooklyn.core.location.LocationConfigKeys; ++import org.apache.brooklyn.core.location.access.PortForwardManager; ++import org.apache.brooklyn.core.location.access.PortForwardManagerLocationResolver; ++import org.apache.brooklyn.core.test.BrooklynAppLiveTestSupport; ++import org.apache.brooklyn.location.ssh.SshMachineLocation; ++import org.apache.brooklyn.test.Asserts; ++import org.apache.brooklyn.util.collections.MutableMap; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import org.testng.annotations.AfterMethod; ++import org.testng.annotations.BeforeMethod; ++import org.testng.annotations.Test; ++ ++import java.util.List; ++import java.util.Map; ++ ++import static org.testng.Assert.*; ++ ++/** ++ * /** ++ * Live tests for deploying simple containers. Particularly useful during dev, but not so useful ++ * after that (because assumes the existence of a kubernetes endpoint). It needs configured with ++ * something like: ++ * <p> ++ * {@code -Dtest.brooklyn-container-service.kubernetes.endpoint=http://10.104.2.206:8080} ++ */ ++public class KubernetesLocationLiveTest extends BrooklynAppLiveTestSupport { ++ ++ public static final String KUBERNETES_ENDPOINT = System.getProperty("test.brooklyn-container-service.kubernetes.endpoint", ""); ++ public static final String IDENTITY = System.getProperty("test.brooklyn-container-service.kubernetes.identity", ""); ++ public static final String CREDENTIAL = System.getProperty("test.brooklyn-container-service.kubernetes.credential", ""); ++ private static final Logger LOG = LoggerFactory.getLogger(KubernetesLocationLiveTest.class); ++ protected KubernetesLocation loc; ++ protected List<KubernetesMachineLocation> machines; ++ ++ @BeforeMethod(alwaysRun = true) ++ @Override ++ public void setUp() throws Exception { ++ super.setUp(); ++ machines = Lists.newCopyOnWriteArrayList(); ++ } ++ ++ // FIXME: Clear up properly: Test leaves deployment, replicas and pods behind if obtain fails. ++ @AfterMethod(alwaysRun = true) ++ @Override ++ public void tearDown() throws Exception { ++ for (KubernetesMachineLocation machine : machines) { ++ try { ++ loc.release(machine); ++ } catch (Exception e) { ++ LOG.error("Error releasing machine " + machine + " in location " + loc, e); ++ } ++ } ++ super.tearDown(); ++ } ++ ++ protected KubernetesLocation newKubernetesLocation(Map<String, ?> flags) throws Exception { ++ Map<String, ?> allFlags = MutableMap.<String, Object>builder() ++ .put("identity", IDENTITY) ++ .put("credential", CREDENTIAL) ++ .put("endpoint", KUBERNETES_ENDPOINT) ++ .putAll(flags) ++ .build(); ++ return (KubernetesLocation) mgmt.getLocationRegistry().getLocationManaged("kubernetes", allFlags); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testDefault() throws Exception { ++ // Default is "cloudsoft/centos:7" ++ runImage(ImmutableMap.<String, Object>of(), "centos", "7"); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testMatchesCentos() throws Exception { ++ runImage(ImmutableMap.<String, Object>of(KubernetesLocationConfig.OS_FAMILY.getName(), "centos"), "centos", "7"); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testMatchesCentos7() throws Exception { ++ ImmutableMap<String, Object> conf = ImmutableMap.<String, Object>of( ++ KubernetesLocationConfig.OS_FAMILY.getName(), "centos", ++ KubernetesLocationConfig.OS_VERSION_REGEX.getName(), "7.*"); ++ runImage(conf, "centos", "7"); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testMatchesUbuntu() throws Exception { ++ runImage(ImmutableMap.<String, Object>of(KubernetesLocationConfig.OS_FAMILY.getName(), "ubuntu"), "ubuntu", "14.04"); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testMatchesUbuntu16() throws Exception { ++ ImmutableMap<String, Object> conf = ImmutableMap.<String, Object>of( ++ KubernetesLocationConfig.OS_FAMILY.getName(), "ubuntu", ++ KubernetesLocationConfig.OS_VERSION_REGEX.getName(), "16.*"); ++ runImage(conf, "ubuntu", "16.04"); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testCloudsoftCentos7() throws Exception { ++ runImage(ImmutableMap.of(KubernetesLocationConfig.IMAGE.getName(), "cloudsoft/centos:7"), "centos", "7"); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testCloudsoftUbuntu14() throws Exception { ++ runImage(ImmutableMap.of(KubernetesLocationConfig.IMAGE.getName(), "cloudsoft/ubuntu:14.04"), "ubuntu", "14.04"); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testCloudsoftUbuntu16() throws Exception { ++ runImage(ImmutableMap.of(KubernetesLocationConfig.IMAGE.getName(), "cloudsoft/ubuntu:16.04"), "ubuntu", "16.04"); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testFailsForNonMatching() throws Exception { ++ ImmutableMap<String, Object> conf = ImmutableMap.<String, Object>of( ++ KubernetesLocationConfig.OS_FAMILY.getName(), "weirdOsFamiliy"); ++ try { ++ runImage(conf, null, null); ++ Asserts.shouldHaveFailedPreviously(); ++ } catch (Exception e) { ++ Asserts.expectedFailureContains(e, "No matching image found"); ++ } ++ } ++ ++ protected void runImage(Map<String, ?> config, String expectedOs, String expectedVersion) throws Exception { ++ loc = newKubernetesLocation(ImmutableMap.<String, Object>of()); ++ SshMachineLocation machine = newContainerMachine(loc, ImmutableMap.<String, Object>builder() ++ .putAll(config) ++ .put(LocationConfigKeys.CALLER_CONTEXT.getName(), app) ++ .build()); ++ ++ assertTrue(machine.isSshable(), "not sshable machine=" + machine); ++ assertOsNameContains(machine, expectedOs, expectedVersion); ++ assertMachinePasswordSecure(machine); ++ } ++ ++ @Test(groups = {"Live"}) ++ protected void testUsesSuppliedLoginPassword() throws Exception { ++ // Because defaulting to "cloudsoft/centos:7", it knows to set the loginUserPassword ++ // on container creation. ++ String password = "myCustomP4ssword"; ++ loc = newKubernetesLocation(ImmutableMap.<String, Object>of()); ++ SshMachineLocation machine = newContainerMachine(loc, ImmutableMap.<String, Object>builder() ++ .put(KubernetesLocationConfig.LOGIN_USER_PASSWORD.getName(), password) ++ .put(LocationConfigKeys.CALLER_CONTEXT.getName(), app) ++ .build()); ++ ++ assertTrue(machine.isSshable(), "not sshable machine=" + machine); ++ assertEquals(machine.config().get(SshMachineLocation.PASSWORD), password); ++ } ++ ++ @Test(groups = {"Live"}) ++ public void testOpenPorts() throws Exception { ++ List<Integer> inboundPorts = ImmutableList.of(22, 443, 8000, 8081); ++ loc = newKubernetesLocation(ImmutableMap.<String, Object>of()); ++ SshMachineLocation machine = newContainerMachine(loc, ImmutableMap.<String, Object>builder() ++ .put(KubernetesLocationConfig.IMAGE.getName(), "cloudsoft/centos:7") ++ .put(KubernetesLocationConfig.LOGIN_USER_PASSWORD.getName(), "p4ssw0rd") ++ .put(KubernetesLocationConfig.INBOUND_PORTS.getName(), inboundPorts) ++ .put(LocationConfigKeys.CALLER_CONTEXT.getName(), app) ++ .build()); ++ assertTrue(machine.isSshable()); ++ ++ String publicHostText = machine.getSshHostAndPort().getHostText(); ++ PortForwardManager pfm = (PortForwardManager) mgmt.getLocationRegistry().getLocationManaged(PortForwardManagerLocationResolver.PFM_GLOBAL_SPEC); ++ for (int targetPort : inboundPorts) { ++ HostAndPort mappedPort = pfm.lookup(machine, targetPort); ++ assertNotNull(mappedPort, "no mapping for targetPort " + targetPort); ++ assertEquals(mappedPort.getHostText(), publicHostText); ++ assertTrue(mappedPort.hasPort(), "no port-part in " + mappedPort + " for targetPort " + targetPort); ++ } ++ } ++ ++ protected void assertOsNameContains(SshMachineLocation machine, String expectedNamePart, String expectedVersionPart) { ++ MachineDetails machineDetails = app.getExecutionContext() ++ .submit(BasicMachineDetails.taskForSshMachineLocation(machine)) ++ .getUnchecked(); ++ OsDetails osDetails = machineDetails.getOsDetails(); ++ String osName = osDetails.getName(); ++ String osVersion = osDetails.getVersion(); ++ assertTrue(osName != null && osName.toLowerCase().contains(expectedNamePart), "osDetails=" + osDetails); ++ assertTrue(osVersion != null && osVersion.toLowerCase().contains(expectedVersionPart), "osDetails=" + osDetails); ++ } ++ ++ protected SshMachineLocation newContainerMachine(KubernetesLocation loc, Map<?, ?> flags) throws Exception { ++ KubernetesMachineLocation result = loc.obtain(flags); ++ machines.add(result); ++ assertTrue(result instanceof KubernetesSshMachineLocation); ++ return (SshMachineLocation) result; ++ } ++ ++ protected void assertMachinePasswordSecure(SshMachineLocation machine) { ++ String password = machine.config().get(SshMachineLocation.PASSWORD); ++ assertTrue(password.length() > 10, "password=" + password); ++ boolean hasUpper = false; ++ boolean hasLower = false; ++ boolean hasNonAlphabetic = false; ++ for (char c : password.toCharArray()) { ++ if (Character.isUpperCase(c)) hasUpper = true; ++ if (Character.isLowerCase(c)) hasLower = true; ++ if (!Character.isAlphabetic(c)) hasNonAlphabetic = true; ++ } ++ assertTrue(hasUpper && hasLower && hasNonAlphabetic, "password=" + password); ++ } ++} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/92a65d45/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolverTest.java ---------------------------------------------------------------------- diff --cc locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolverTest.java index 0000000,0000000..d8abf0d new file mode 100644 --- /dev/null +++ b/locations/container/src/test/java/org/apache/brooklyn/container/location/kubernetes/KubernetesLocationResolverTest.java @@@ -1,0 -1,0 +1,102 @@@ ++/* ++ * Licensed to the Apache Software Foundation (ASF) under one ++ * or more contributor license agreements. See the NOTICE file ++ * distributed with this work for additional information ++ * regarding copyright ownership. The ASF licenses this file ++ * to you under the Apache License, Version 2.0 (the ++ * "License"); you may not use this file except in compliance ++ * with the License. You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, ++ * software distributed under the License is distributed on an ++ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY ++ * KIND, either express or implied. See the License for the ++ * specific language governing permissions and limitations ++ * under the License. ++ */ ++package org.apache.brooklyn.container.location.kubernetes; ++ ++import org.apache.brooklyn.api.location.LocationSpec; ++import org.apache.brooklyn.core.internal.BrooklynProperties; ++import org.apache.brooklyn.core.test.BrooklynMgmtUnitTestSupport; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++import org.testng.annotations.BeforeMethod; ++import org.testng.annotations.Test; ++ ++import java.util.Map; ++ ++import static org.testng.Assert.assertEquals; ++import static org.testng.Assert.assertTrue; ++ ++public class KubernetesLocationResolverTest extends BrooklynMgmtUnitTestSupport { ++ ++ private static final Logger LOG = LoggerFactory.getLogger(KubernetesLocationResolverTest.class); ++ ++ private BrooklynProperties brooklynProperties; ++ ++ @BeforeMethod(alwaysRun = true) ++ public void setUp() throws Exception { ++ super.setUp(); ++ brooklynProperties = mgmt.getBrooklynProperties(); ++ ++ brooklynProperties.put("brooklyn.location.kubernetes.identity", "kubernetes-id"); ++ brooklynProperties.put("brooklyn.location.kubernetes.credential", "kubernetes-cred"); ++ } ++ ++ @Test ++ public void testGivesCorrectLocationType() { ++ LocationSpec<?> spec = getLocationSpec("kubernetes"); ++ assertEquals(spec.getType(), KubernetesLocation.class); ++ ++ KubernetesLocation loc = resolve("kubernetes"); ++ assertTrue(loc instanceof KubernetesLocation, "loc=" + loc); ++ } ++ ++ @Test ++ public void testParametersInSpecString() { ++ KubernetesLocation loc = resolve("kubernetes(endpoint=myMasterUrl)"); ++ assertEquals(loc.getConfig(KubernetesLocation.MASTER_URL), "myMasterUrl"); ++ } ++ ++ @Test ++ public void testTakesDotSeparateProperty() { ++ brooklynProperties.put("brooklyn.location.kubernetes.endpoint", "myMasterUrl"); ++ KubernetesLocation loc = resolve("kubernetes"); ++ assertEquals(loc.getConfig(KubernetesLocation.MASTER_URL), "myMasterUrl"); ++ } ++ ++ @Test ++ public void testPropertiesPrecedence() { ++ // prefer those in "spec" over everything else ++ brooklynProperties.put("brooklyn.location.named.mykubernetes", "kubernetes:(loginUser=\"loginUser-inSpec\")"); ++ ++ brooklynProperties.put("brooklyn.location.named.mykubernetes.loginUser", "loginUser-inNamed"); ++ brooklynProperties.put("brooklyn.location.kubernetes.loginUser", "loginUser-inDocker"); ++ ++ // prefer those in "named" over everything else ++ brooklynProperties.put("brooklyn.location.named.mykubernetes.privateKeyFile", "privateKeyFile-inNamed"); ++ brooklynProperties.put("brooklyn.location.kubernetes.privateKeyFile", "privateKeyFile-inDocker"); ++ ++ // prefer those in kubernetes-specific ++ brooklynProperties.put("brooklyn.location.kubernetes.publicKeyFile", "publicKeyFile-inDocker"); ++ ++ Map<String, Object> conf = resolve("named:mykubernetes").config().getBag().getAllConfig(); ++ ++ assertEquals(conf.get("loginUser"), "loginUser-inSpec"); ++ assertEquals(conf.get("privateKeyFile"), "privateKeyFile-inNamed"); ++ assertEquals(conf.get("publicKeyFile"), "publicKeyFile-inDocker"); ++ } ++ ++ private LocationSpec<?> getLocationSpec(String spec) { ++ LOG.debug("Obtaining location spec '{}'", spec); ++ return mgmt.getLocationRegistry().getLocationSpec(spec).get(); ++ } ++ ++ private KubernetesLocation resolve(String spec) { ++ LOG.debug("Resolving location spec '{}'", spec); ++ return (KubernetesLocation) mgmt.getLocationRegistry().getLocationManaged(spec); ++ } ++}
