[JCLOUDS-1002] provide access to Docker container Config object in the Node template options

This commit is contained in:
Josef Cacek 2016-02-18 16:02:37 +01:00 committed by Ignasi Barrera
parent 3f1fe271ed
commit 831cdc67c3
4 changed files with 202 additions and 69 deletions

View File

@ -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...)
*/

View File

@ -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();
DockerTemplateOptions templateOptions = DockerTemplateOptions.class.cast(options);
Config.Builder containerConfigBuilder = templateOptions.getConfigBuilder();
if (containerConfigBuilder == null) {
containerConfigBuilder = Config.builder();
Map<String, Object> exposedPorts = Maps.newHashMap();
for (int inboundPort : inboundPorts) {
exposedPorts.put(inboundPort + "/tcp", Maps.newHashMap());
}
containerConfigBuilder.entrypoint(templateOptions.getEntrypoint());
containerConfigBuilder.cmd(templateOptions.getCommands());
containerConfigBuilder.memory(templateOptions.getMemory());
containerConfigBuilder.hostname(templateOptions.getHostname());
containerConfigBuilder.cpuShares(templateOptions.getCpuShares());
containerConfigBuilder.env(templateOptions.getEnv());
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());
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);
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);
}
if (!templateOptions.getDns().isEmpty()) {
hostConfigBuilder.dns(templateOptions.getDns());
}
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.getVolumes().isEmpty()) {
for (Map.Entry<String, String> entry : templateOptions.getVolumes().entrySet()) {
hostConfigBuilder.binds(ImmutableList.of(entry.getKey() + ":" + entry.getValue()));
}
}
hostConfigBuilder.networkMode(templateOptions.getNetworkMode());
containerConfigBuilder.hostConfig(hostConfigBuilder.build());
}
Config containerConfig = containerConfigBuilder.build();
containerConfigBuilder.image(imageId);
// 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);
// 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.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);
}
if (!templateOptions.getDns().isEmpty()) {
hostConfigBuilder.dns(templateOptions.getDns());
}
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.getVolumes().isEmpty()) {
for (Map.Entry<String, String> entry : templateOptions.getVolumes().entrySet()) {
hostConfigBuilder.binds(ImmutableList.of(entry.getKey() + ":" + entry.getValue()));
}
}
hostConfigBuilder.networkMode(templateOptions.getNetworkMode());
HostConfig hostConfig = hostConfigBuilder.build();
HostConfig hostConfig = containerConfig.hostConfig();
api.getContainerApi().startContainer(container.id(), hostConfig);
container = api.getContainerApi().inspectContainer(container.id());

View File

@ -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.
*

View File

@ -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;
@ -91,6 +94,21 @@ public class DockerTemplateOptionsTest {
assertEquals(options.as(DockerTemplateOptions.class).getNetworkMode(), "host");
}
@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);