[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"));

Reply via email to