[JCLOUDS-1002] provide access to Docker container Config object in the Node template options
Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/831cdc67 Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/831cdc67 Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/831cdc67 Branch: refs/heads/master Commit: 831cdc67c3977e834bac3c838df0ce2b970a7c9d Parents: 3f1fe27 Author: Josef Cacek <[email protected]> Authored: Thu Feb 18 16:02:37 2016 +0100 Committer: Ignasi Barrera <[email protected]> Committed: Tue Mar 8 23:01:52 2016 +0100 ---------------------------------------------------------------------- .../compute/options/DockerTemplateOptions.java | 92 ++++++++++++-- .../strategy/DockerComputeServiceAdapter.java | 123 +++++++++++-------- .../docker/compute/SshToCustomPortLiveTest.java | 30 +++++ .../options/DockerTemplateOptionsTest.java | 18 +++ 4 files changed, 198 insertions(+), 65 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds/blob/831cdc67/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java ---------------------------------------------------------------------- diff --git a/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java b/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java index 4088a54..4239661 100644 --- a/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java +++ b/apis/docker/src/main/java/org/jclouds/docker/compute/options/DockerTemplateOptions.java @@ -22,8 +22,8 @@ import static com.google.common.base.Preconditions.checkNotNull; import java.util.List; import java.util.Map; -import org.jclouds.compute.ComputeService; import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.docker.domain.Config; import org.jclouds.docker.internal.NullSafeCopies; import org.jclouds.domain.LoginCredentials; import org.jclouds.javax.annotation.Nullable; @@ -34,23 +34,55 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; /** - * Contains options supported by the {@link ComputeService#createNodesInGroup(String, int, TemplateOptions) createNodes} - * operation on the <em>docker</em> provider. - * + * Contains options supported by the + * {@link org.jclouds.compute.ComputeService#createNodesInGroup(String, int, TemplateOptions) + * createNodes} operation on the <em>docker</em> provider. + * * <h2>Usage</h2> * - * The recommended way to instantiate a - * DockerTemplateOptions object is to statically import {@code DockerTemplateOptions.Builder.*} - * and invoke one of the static creation methods, followed by an instance mutator if needed. + * The recommended way to instantiate a DockerTemplateOptions object is to + * statically import {@code DockerTemplateOptions.Builder.*} and invoke one of + * the static creation methods, followed by an instance mutator if needed. * - * <pre>{@code import static org.jclouds.docker.compute.options.DockerTemplateOptions.Builder.*; + * <pre> + * {@code import static org.jclouds.docker.compute.options.DockerTemplateOptions.Builder.*; * * ComputeService api = // get connection * templateBuilder.options(inboundPorts(22, 80, 8080, 443)); - * Set<? extends NodeMetadata> set = api.createNodesInGroup(tag, 2, templateBuilder.build());}</pre> + * Set<? extends NodeMetadata> set = api.createNodesInGroup(tag, 2, templateBuilder.build());} + * </pre> + * + * <h2>Advanced Usage</h2> + * <p> + * In addition to basic configuration through its methods, this class also + * provides possibility to work directly with Docker API configuration object ( + * {@link Config.Builder}). When the + * {@link #configBuilder(org.jclouds.docker.domain.Config.Builder)} is used to + * configure not-<code>null</code> configBuilder, then this configuration object + * takes precedence over the other configuration in this class (i.e. the other + * config entries are not used) + * </p> + * <p> + * Note: The {@code image} property in the provided {@link Config.Builder} is rewritten by a placeholder value. + * The real value is configured by ComputeServiceAdapter. + * </p> + * + * <pre> + * {@code import static org.jclouds.docker.compute.options.DockerTemplateOptions.Builder.*; + * + * ComputeService api = // get connection + * DockerTemplateOptions options = DockerTemplateOptions.Builder + * .configBuilder( + * Config.builder().env(ImmutableList.<String> of("SSH_PORT=8822")) + * .hostConfig(HostConfig.builder().networkMode("host").build())); + * templateBuilder.options(options); + * Set<? extends NodeMetadata> set = api.createNodesInGroup("sample-group", 1, templateBuilder.build());} + * </pre> */ public class DockerTemplateOptions extends TemplateOptions implements Cloneable { + private static final String NO_IMAGE = "jclouds-placeholder-for-image"; + protected List<String> dns = ImmutableList.of(); protected String hostname; protected Integer memory; @@ -63,6 +95,8 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable protected String networkMode; protected Map<String, String> extraHosts = ImmutableMap.of(); + protected Config.Builder configBuilder; + @Override public DockerTemplateOptions clone() { DockerTemplateOptions options = new DockerTemplateOptions(); @@ -94,6 +128,7 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable if (!extraHosts.isEmpty()) { eTo.extraHosts(extraHosts); } + eTo.configBuilder(configBuilder); } } @@ -113,12 +148,22 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable equal(this.cpuShares, that.cpuShares) && equal(this.env, that.env) && equal(this.portBindings, that.portBindings) && - equal(this.extraHosts, that.extraHosts); + equal(this.extraHosts, that.extraHosts) && + buildersEqual(this.configBuilder, that.configBuilder); } + + /** + * Compares two Config.Builder instances. + */ + private boolean buildersEqual(Config.Builder b1, Config.Builder b2) { + return b1 == b2 || (b1 != null && b2 != null && b1.build().equals(b2.build())); + } + @Override public int hashCode() { - return Objects.hashCode(super.hashCode(), volumes, hostname, dns, memory, entrypoint, commands, cpuShares, env, portBindings, extraHosts); + return Objects.hashCode(super.hashCode(), volumes, hostname, dns, memory, entrypoint, commands, cpuShares, env, + portBindings, extraHosts, configBuilder); } @Override @@ -134,6 +179,7 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable .add("env", env) .add("portBindings", portBindings) .add("extraHosts", extraHosts) + .add("configBuilder", configBuilder) .toString(); } @@ -235,6 +281,23 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable return this; } + /** + * This method sets Config.Builder configuration object, which can be used as + * a replacement for all the other settings from this class. Some values in + * the provided Config.Builder instance (the image name for instance) can be + * ignored or their value can be changed. + * + * @param configBuilder + * Config.Builder instance. This instance can be changed in this + * method! + */ + public DockerTemplateOptions configBuilder(Config.Builder configBuilder) { + this.configBuilder = configBuilder != null + ? Config.builder().fromConfig(configBuilder.image(NO_IMAGE).build()) + : null; + return this; + } + public Map<String, String> getVolumes() { return volumes; } public List<String> getDns() { return dns; } @@ -257,6 +320,8 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable public Map<String, String> getExtraHosts() { return extraHosts; } + public Config.Builder getConfigBuilder() { return configBuilder; } + public static class Builder { /** @@ -379,6 +444,11 @@ public class DockerTemplateOptions extends TemplateOptions implements Cloneable return options.extraHosts(extraHosts); } + public static DockerTemplateOptions configBuilder(Config.Builder configBuilder) { + DockerTemplateOptions options = new DockerTemplateOptions(); + return options.configBuilder(configBuilder); + } + /** * @see TemplateOptions#inboundPorts(int...) */ http://git-wip-us.apache.org/repos/asf/jclouds/blob/831cdc67/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java ---------------------------------------------------------------------- diff --git a/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java b/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java index d800b7e..64e9e67 100644 --- a/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java +++ b/apis/docker/src/main/java/org/jclouds/docker/compute/strategy/DockerComputeServiceAdapter.java @@ -32,6 +32,7 @@ import org.jclouds.compute.domain.Hardware; import org.jclouds.compute.domain.HardwareBuilder; import org.jclouds.compute.domain.Processor; import org.jclouds.compute.domain.Template; +import org.jclouds.compute.options.TemplateOptions; import org.jclouds.compute.reference.ComputeServiceConstants; import org.jclouds.docker.DockerApi; import org.jclouds.docker.compute.options.DockerTemplateOptions; @@ -74,83 +75,97 @@ public class DockerComputeServiceAdapter implements this.api = checkNotNull(api, "api"); } + @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public NodeAndInitialCredentials<Container> createNodeWithGroupEncodedIntoName(String group, String name, Template template) { checkNotNull(template, "template was null"); - checkNotNull(template.getOptions(), "template options was null"); + TemplateOptions options = template.getOptions(); + checkNotNull(options, "template options was null"); String imageId = checkNotNull(template.getImage().getId(), "template image id must not be null"); String loginUser = template.getImage().getDefaultCredentials().getUser(); String loginUserPassword = template.getImage().getDefaultCredentials().getOptionalPassword().or("password"); - DockerTemplateOptions templateOptions = DockerTemplateOptions.class.cast(template.getOptions()); - int[] inboundPorts = templateOptions.getInboundPorts(); - - Map<String, Object> exposedPorts = Maps.newHashMap(); - for (int inboundPort : inboundPorts) { - exposedPorts.put(inboundPort + "/tcp", Maps.newHashMap()); - } - - Config.Builder containerConfigBuilder = Config.builder() - .image(imageId) - .exposedPorts(exposedPorts); - - containerConfigBuilder.entrypoint(templateOptions.getEntrypoint()); - containerConfigBuilder.cmd(templateOptions.getCommands()); - containerConfigBuilder.memory(templateOptions.getMemory()); - containerConfigBuilder.hostname(templateOptions.getHostname()); - containerConfigBuilder.cpuShares(templateOptions.getCpuShares()); - containerConfigBuilder.env(templateOptions.getEnv()); - - if (!templateOptions.getVolumes().isEmpty()) { - Map<String, Object> volumes = Maps.newLinkedHashMap(); - for (String containerDir : templateOptions.getVolumes().values()) { - volumes.put(containerDir, Maps.newHashMap()); + DockerTemplateOptions templateOptions = DockerTemplateOptions.class.cast(options); + Config.Builder containerConfigBuilder = templateOptions.getConfigBuilder(); + if (containerConfigBuilder == null) { + containerConfigBuilder = Config.builder(); + + containerConfigBuilder.entrypoint(templateOptions.getEntrypoint()); + containerConfigBuilder.cmd(templateOptions.getCommands()); + containerConfigBuilder.memory(templateOptions.getMemory()); + containerConfigBuilder.hostname(templateOptions.getHostname()); + containerConfigBuilder.cpuShares(templateOptions.getCpuShares()); + containerConfigBuilder.env(templateOptions.getEnv()); + + if (!templateOptions.getVolumes().isEmpty()) { + Map<String, Object> volumes = Maps.newLinkedHashMap(); + for (String containerDir : templateOptions.getVolumes().values()) { + volumes.put(containerDir, Maps.newHashMap()); + } + containerConfigBuilder.volumes(volumes); } - containerConfigBuilder.volumes(volumes); - } - Config containerConfig = containerConfigBuilder.build(); + HostConfig.Builder hostConfigBuilder = HostConfig.builder() + .publishAllPorts(true) + .privileged(true); + + if (!templateOptions.getPortBindings().isEmpty()) { + Map<String, List<Map<String, String>>> portBindings = Maps.newHashMap(); + for (Map.Entry<Integer, Integer> entry : templateOptions.getPortBindings().entrySet()) { + portBindings.put(entry.getValue() + "/tcp", + Lists.<Map<String, String>>newArrayList(ImmutableMap.of("HostPort", Integer.toString(entry.getKey())))); + } + hostConfigBuilder.portBindings(portBindings); + } - logger.debug(">> creating new container with containerConfig(%s)", containerConfig); - Container container = api.getContainerApi().createContainer(name, containerConfig); - logger.trace("<< container(%s)", container.id()); + if (!templateOptions.getDns().isEmpty()) { + hostConfigBuilder.dns(templateOptions.getDns()); + } - HostConfig.Builder hostConfigBuilder = HostConfig.builder() - .publishAllPorts(true) - .privileged(true); + if (!templateOptions.getExtraHosts().isEmpty()) { + List<String> extraHosts = Lists.newArrayList(); + for (Map.Entry<String, String> entry : templateOptions.getExtraHosts().entrySet()) { + extraHosts.add(entry.getKey() + ":" + entry.getValue()); + } + hostConfigBuilder.extraHosts(extraHosts); + } - if (!templateOptions.getPortBindings().isEmpty()) { - Map<String, List<Map<String, String>>> portBindings = Maps.newHashMap(); - for (Map.Entry<Integer, Integer> entry : templateOptions.getPortBindings().entrySet()) { - portBindings.put(entry.getValue() + "/tcp", - Lists.<Map<String, String>>newArrayList(ImmutableMap.of("HostPort", Integer.toString(entry.getKey())))); + if (!templateOptions.getVolumes().isEmpty()) { + for (Map.Entry<String, String> entry : templateOptions.getVolumes().entrySet()) { + hostConfigBuilder.binds(ImmutableList.of(entry.getKey() + ":" + entry.getValue())); + } } - hostConfigBuilder.portBindings(portBindings); - } - if (!templateOptions.getDns().isEmpty()) { - hostConfigBuilder.dns(templateOptions.getDns()); + hostConfigBuilder.networkMode(templateOptions.getNetworkMode()); + containerConfigBuilder.hostConfig(hostConfigBuilder.build()); } - if (!templateOptions.getExtraHosts().isEmpty()) { - List<String> extraHosts = Lists.newArrayList(); - for (Map.Entry<String, String> entry : templateOptions.getExtraHosts().entrySet()) { - extraHosts.add(entry.getKey() + ":" + entry.getValue()); - } - hostConfigBuilder.extraHosts(extraHosts); - } + containerConfigBuilder.image(imageId); - if (!templateOptions.getVolumes().isEmpty()) { - for (Map.Entry<String, String> entry : templateOptions.getVolumes().entrySet()) { - hostConfigBuilder.binds(ImmutableList.of(entry.getKey() + ":" + entry.getValue())); + // add the inbound ports into exposed ports map + Config containerConfig = containerConfigBuilder.build(); + Map<String, Object> exposedPorts = Maps.newHashMap(); + if (containerConfig.exposedPorts() == null) { + exposedPorts.putAll(containerConfig.exposedPorts()); + } + for (int inboundPort : templateOptions.getInboundPorts()) { + String portKey = inboundPort + "/tcp"; + if (!exposedPorts.containsKey(portKey)) { + exposedPorts.put(portKey, Maps.newHashMap()); } } + containerConfigBuilder.exposedPorts(exposedPorts); - hostConfigBuilder.networkMode(templateOptions.getNetworkMode()); + // build once more after setting inboundPorts + containerConfig = containerConfigBuilder.build(); + + logger.debug(">> creating new container with containerConfig(%s)", containerConfig); + Container container = api.getContainerApi().createContainer(name, containerConfig); + logger.trace("<< container(%s)", container.id()); - HostConfig hostConfig = hostConfigBuilder.build(); + HostConfig hostConfig = containerConfig.hostConfig(); api.getContainerApi().startContainer(container.id(), hostConfig); container = api.getContainerApi().inspectContainer(container.id()); http://git-wip-us.apache.org/repos/asf/jclouds/blob/831cdc67/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java ---------------------------------------------------------------------- diff --git a/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java b/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java index cb35173..ba11feb 100644 --- a/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java +++ b/apis/docker/src/test/java/org/jclouds/docker/compute/SshToCustomPortLiveTest.java @@ -35,7 +35,9 @@ import org.jclouds.compute.internal.BaseComputeServiceContextLiveTest; import org.jclouds.docker.DockerApi; import org.jclouds.docker.compute.functions.LoginPortForContainer; import org.jclouds.docker.compute.options.DockerTemplateOptions; +import org.jclouds.docker.domain.Config; import org.jclouds.docker.domain.Container; +import org.jclouds.docker.domain.HostConfig; import org.jclouds.docker.domain.Image; import org.jclouds.docker.domain.ImageSummary; import org.jclouds.docker.options.BuildOptions; @@ -46,6 +48,7 @@ import org.testng.annotations.Test; import com.google.common.base.Optional; import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.inject.AbstractModule; @@ -124,6 +127,33 @@ public class SshToCustomPortLiveTest extends BaseComputeServiceContextLiveTest { } } + + @Test(dependsOnMethods = "testImageCreated") + public void testAdvancedConfig() throws RunNodesException { + final DockerTemplateOptions options = DockerTemplateOptions.Builder + .configBuilder( + Config.builder().env(ImmutableList.<String> of("SSH_PORT=" + SSH_PORT, "ROOT_PASSWORD=jcloudsRulez")) + .hostConfig(HostConfig.builder().networkMode("host").build()) + .image("test-if-this-value-is-correctly-overriden")) + .overrideLoginUser("root").overrideLoginPassword("jcloudsRulez").blockOnPort(SSH_PORT, 30); + + final Template template = view.getComputeService().templateBuilder().imageId(image.id()).options(options).build(); + + String nodeId = null; + try { + NodeMetadata node = Iterables + .getOnlyElement(view.getComputeService().createNodesInGroup("ssh-test-advanced", 1, template)); + + nodeId = node.getId(); + ExecResponse response = view.getComputeService().runScriptOnNode(nodeId, "sh -c 'true'", + wrapInInitScript(false)); + assertEquals(response.getExitStatus(), 0); + } finally { + if (nodeId != null) + view.getComputeService().destroyNode(nodeId); + } + } + /** * Build a new image with 2 tags on it in the test preparation phase. * http://git-wip-us.apache.org/repos/asf/jclouds/blob/831cdc67/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java ---------------------------------------------------------------------- diff --git a/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java b/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java index a1145f5..a1bb321 100644 --- a/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java +++ b/apis/docker/src/test/java/org/jclouds/docker/compute/options/DockerTemplateOptionsTest.java @@ -17,8 +17,11 @@ package org.jclouds.docker.compute.options; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import org.jclouds.compute.options.TemplateOptions; +import org.jclouds.docker.domain.Config; +import org.jclouds.docker.domain.Config.Builder; import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; @@ -92,6 +95,21 @@ public class DockerTemplateOptionsTest { } @Test + public void testConfigBuilder() { + Builder builder = Config.builder().memory(1024) + .cpuShares(100).cmd(ImmutableList.<String> of("/opt/jboss/wildfly/bin/standalone.sh", "-b", "0.0.0.0")) + .env(ImmutableList.<String> of("JAVA_HOME=/opt/jdk-1.8", "MGMT_USER=admin", + "MGMT_PASSWORD=Schroedinger's Cat")); + TemplateOptions options = DockerTemplateOptions.Builder.configBuilder(builder); + Builder builderInOpts = options.as(DockerTemplateOptions.class).getConfigBuilder(); + assertNotNull(builderInOpts); + Config configFromOptions = builderInOpts.build(); + assertEquals(configFromOptions, builder.build()); + assertEquals(configFromOptions.env(), ImmutableList.<String> of("JAVA_HOME=/opt/jdk-1.8", "MGMT_USER=admin", + "MGMT_PASSWORD=Schroedinger's Cat")); + } + + @Test public void testNonDockerOptions() { TemplateOptions options = DockerTemplateOptions.Builder.userMetadata(ImmutableMap.of("key", "value")).cpuShares(1); assertEquals(options.as(DockerTemplateOptions.class).getUserMetadata(), ImmutableMap.of("key", "value"));
