brooklyn.entity.container.docker.DockerContainerImpl Maven / Gradle / Ivy
Show all versions of brooklyn-clocker-docker Show documentation
/*
* Copyright 2014-2015 by Cloudsoft Corporation Limited
*
* 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.
*/
package brooklyn.entity.container.docker;
import static java.lang.String.format;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import org.jclouds.compute.domain.Processor;
import org.jclouds.compute.domain.TemplateBuilder;
import org.jclouds.docker.compute.options.DockerTemplateOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import brooklyn.config.ConfigKey;
import brooklyn.config.render.RendererHints;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.Attributes;
import brooklyn.entity.basic.BasicStartableImpl;
import brooklyn.entity.basic.ConfigKeys;
import brooklyn.entity.basic.DelegateEntity;
import brooklyn.entity.basic.Entities;
import brooklyn.entity.basic.EntityInternal;
import brooklyn.entity.basic.EntityLocal;
import brooklyn.entity.basic.Lifecycle;
import brooklyn.entity.basic.ServiceStateLogic;
import brooklyn.entity.basic.SoftwareProcess;
import brooklyn.entity.container.DockerAttributes;
import brooklyn.entity.container.DockerUtils;
import brooklyn.event.basic.PortAttributeSensorAndConfigKey;
import brooklyn.event.basic.Sensors;
import brooklyn.event.feed.ConfigToAttributes;
import brooklyn.event.feed.function.FunctionFeed;
import brooklyn.event.feed.function.FunctionPollConfig;
import brooklyn.location.Location;
import brooklyn.location.LocationSpec;
import brooklyn.location.NoMachinesAvailableException;
import brooklyn.location.OsDetails;
import brooklyn.location.PortRange;
import brooklyn.location.basic.LocationConfigKeys;
import brooklyn.location.basic.PortRanges;
import brooklyn.location.basic.SshMachineLocation;
import brooklyn.location.cloud.CloudLocationConfig;
import brooklyn.location.docker.DockerContainerLocation;
import brooklyn.location.docker.DockerHostLocation;
import brooklyn.location.dynamic.DynamicLocation;
import brooklyn.location.jclouds.JcloudsLocation;
import brooklyn.location.jclouds.JcloudsLocationConfig;
import brooklyn.location.jclouds.JcloudsSshMachineLocation;
import brooklyn.location.jclouds.templates.PortableTemplateBuilder;
import brooklyn.management.LocationManager;
import brooklyn.networking.portforwarding.subnet.JcloudsPortforwardingSubnetLocation;
import brooklyn.networking.sdn.SdnAgent;
import brooklyn.networking.sdn.SdnAttributes;
import brooklyn.networking.subnet.SubnetTier;
import brooklyn.util.collections.MutableList;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.collections.MutableSet;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.internal.ssh.SshTool;
import brooklyn.util.net.Cidr;
import brooklyn.util.net.Urls;
import brooklyn.util.text.Strings;
import brooklyn.util.time.Duration;
import com.google.common.base.CaseFormat;
import com.google.common.base.CharMatcher;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
/**
* A single Docker container.
*/
public class DockerContainerImpl extends BasicStartableImpl implements DockerContainer {
private static final Logger LOG = LoggerFactory.getLogger(DockerContainer.class);
private transient FunctionFeed status;
@Override
public void init() {
LOG.info("Starting Docker container id {}", getId());
super.init();
AtomicInteger counter = config().get(DOCKER_INFRASTRUCTURE).getAttribute(DockerInfrastructure.DOCKER_CONTAINER_COUNTER);
String dockerContainerName = config().get(DOCKER_CONTAINER_NAME);
String dockerContainerNameFormat = config().get(DOCKER_CONTAINER_NAME_FORMAT);
if (Strings.isBlank(dockerContainerName) && Strings.isNonBlank(dockerContainerNameFormat)) {
dockerContainerName = format(dockerContainerNameFormat, getId(), counter.incrementAndGet());
}
if (Strings.isNonBlank(dockerContainerName)) {
dockerContainerName = CharMatcher.BREAKING_WHITESPACE.trimAndCollapseFrom(dockerContainerName, '-');
setDisplayName(CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_CAMEL, dockerContainerName));
setAttribute(DOCKER_CONTAINER_NAME, dockerContainerName);
}
ConfigToAttributes.apply(this, DOCKER_INFRASTRUCTURE);
ConfigToAttributes.apply(this, DOCKER_HOST);
ConfigToAttributes.apply(this, ENTITY);
}
protected void connectSensors() {
status = FunctionFeed.builder()
.entity(this)
.period(Duration.seconds(15))
.poll(new FunctionPollConfig(DOCKER_CONTAINER_NAME)
.period(Duration.minutes(1))
.callable(new Callable() {
@Override
public String call() throws Exception {
String containerId = getContainerId();
if (containerId == null) return "";
String name = getDockerHost().runDockerCommand("inspect -f {{.Name}} " + containerId);
return Strings.removeFromStart(name, "/");
}
})
.onFailureOrException(Functions.constant("")))
.poll(new FunctionPollConfig(SERVICE_UP)
.callable(new Callable() {
@Override
public Boolean call() throws Exception {
String containerId = getContainerId();
if (containerId == null) return false;
return Strings.isNonBlank(getDockerHost().runDockerCommand("inspect -f {{.Id}} " + containerId));
}
})
.onFailureOrException(Functions.constant(Boolean.FALSE)))
.poll(new FunctionPollConfig(CONTAINER_RUNNING)
.callable(new Callable() {
@Override
public Boolean call() throws Exception {
String containerId = getContainerId();
if (containerId == null) return false;
String running = getDockerHost().runDockerCommand("inspect -f {{.State.Running}} " + containerId);
return Strings.isNonBlank(running) && Boolean.parseBoolean(Strings.trim(running));
}
})
.onFailureOrException(Functions.constant(Boolean.FALSE)))
.poll(new FunctionPollConfig(CONTAINER_PAUSED)
.callable(new Callable() {
@Override
public Boolean call() throws Exception {
String containerId = getContainerId();
if (containerId == null) return false;
String running = getDockerHost().runDockerCommand("inspect -f {{.State.Paused}} " + containerId);
return Strings.isNonBlank(running) && Boolean.parseBoolean(Strings.trim(running));
}
})
.onFailureOrException(Functions.constant(Boolean.FALSE)))
.build();
}
public void disconnectSensors() {
if (status != null) status.stop();
}
@Override
public Entity getRunningEntity() {
return getAttribute(ENTITY);
}
public void setRunningEntity(Entity entity) {
setAttribute(ENTITY, entity);
}
@Override
public String getDockerContainerName() {
return getAttribute(DOCKER_CONTAINER_NAME);
}
@Override
public String getContainerId() {
return getAttribute(CONTAINER_ID);
}
@Override
public SshMachineLocation getMachine() {
return getAttribute(SSH_MACHINE_LOCATION);
}
@Override
public DockerHost getDockerHost() {
return (DockerHost) config().get(DOCKER_HOST);
}
@Override
public String getShortName() {
return "Docker Container";
}
@Override
public DockerContainerLocation getDynamicLocation() {
return (DockerContainerLocation) getAttribute(DYNAMIC_LOCATION);
}
@Override
public boolean isLocationAvailable() {
return getDynamicLocation() != null;
}
@Override
public void shutDown() {
String dockerContainerName = getAttribute(DockerContainer.DOCKER_CONTAINER_NAME);
LOG.info("Stopping {}", dockerContainerName);
getDockerHost().runDockerCommand("kill " + getContainerId());
}
@Override
public void pause() {
String dockerContainerName = getAttribute(DockerContainer.DOCKER_CONTAINER_NAME);
LOG.info("Pausing {}", dockerContainerName);
getDockerHost().runDockerCommand("stop " + getContainerId());
}
@Override
public void resume() {
String dockerContainerName = getAttribute(DockerContainer.DOCKER_CONTAINER_NAME);
LOG.info("Resuming {}", dockerContainerName);
getDockerHost().runDockerCommand("start " + getContainerId());
}
/**
* Remove the container from the host.
*
* Should only be called when the container is not running.
*/
private void removeContainer() {
final String dockerContainerName = getAttribute(DockerContainer.DOCKER_CONTAINER_NAME);
LOG.info("Removing {}", dockerContainerName);
getDockerHost().runDockerCommand("rm " + getContainerId());
}
private DockerTemplateOptions getDockerTemplateOptions() {
Entity entity = getRunningEntity();
DockerTemplateOptions options = new DockerTemplateOptions();
// Use DockerHost hostname for the container
Boolean useHostDns = entity.config().get(DOCKER_USE_HOST_DNS_NAME);
if (useHostDns == null) useHostDns = config().get(DOCKER_USE_HOST_DNS_NAME);
if (useHostDns != null && useHostDns) {
// FIXME does not seem to work on Softlayer, should set HOSTNAME or SUBNET_HOSTNAME
String hostname = getDockerHost().getAttribute(Attributes.HOSTNAME);
String address = getDockerHost().getAttribute(Attributes.ADDRESS);
if (hostname.equalsIgnoreCase(address)) {
options.hostname(getDockerContainerName());
} else {
options.hostname(hostname);
}
}
// CPU shares
Integer cpuShares = entity.config().get(DOCKER_CPU_SHARES);
if (cpuShares == null) cpuShares = config().get(DOCKER_CPU_SHARES);
if (cpuShares != null) {
// TODO set based on number of cores available in host divided by cores requested in flags
Integer hostCores = getDockerHost().getDynamicLocation().getMachine().getMachineDetails().getHardwareDetails().getCpuCount();
Integer minCores = entity.config().get(JcloudsLocationConfig.MIN_CORES);
Map flags = entity.config().get(SoftwareProcess.PROVISIONING_PROPERTIES);
if (minCores == null && flags != null) {
minCores = (Integer) flags.get(JcloudsLocationConfig.MIN_CORES.getName());
}
if (minCores == null && flags != null) {
TemplateBuilder template = (TemplateBuilder) flags.get(JcloudsLocationConfig.TEMPLATE_BUILDER.getName());
if (template != null) {
minCores = 0;
for (Processor cpu : template.build().getHardware().getProcessors()) {
minCores = minCores + (int) cpu.getCores();
}
}
}
if (minCores != null) {
double ratio = (double) minCores / (double) (hostCores != null ? hostCores : 1);
LOG.info("Cores: host {}, min {}, ratio {}", new Object[] { hostCores, minCores, ratio });
}
}
if (cpuShares != null) options.cpuShares(cpuShares);
// Memory
Integer memory = entity.config().get(DOCKER_MEMORY);
if (memory == null) memory = config().get(DOCKER_MEMORY);
if (memory != null) {
// TODO set based on memory available in host divided by memory requested in flags
Integer hostRam = getDockerHost().getDynamicLocation().getMachine().getMachineDetails().getHardwareDetails().getRam();
Integer minRam = (Integer) entity.config().get(JcloudsLocationConfig.MIN_RAM);
Map flags = entity.config().get(SoftwareProcess.PROVISIONING_PROPERTIES);
if (minRam == null && flags != null) {
minRam = (Integer) flags.get(JcloudsLocationConfig.MIN_RAM.getName());
}
if (minRam == null && flags != null) {
TemplateBuilder template = (TemplateBuilder) flags.get(JcloudsLocationConfig.TEMPLATE_BUILDER.getName());
if (template != null) {
minRam = template.build().getHardware().getRam();
}
}
if (minRam != null) {
double ratio = (double) minRam / (double) hostRam;
LOG.info("Memory: host {}, min {}, ratio {}", new Object[] { hostRam, minRam, ratio });
}
}
if (memory != null) options.memory(memory);
// Volumes
Map volumes = MutableMap.copyOf(getDockerHost().getAttribute(DockerHost.DOCKER_HOST_VOLUME_MAPPING));
Map mapping = entity.config().get(DockerHost.DOCKER_HOST_VOLUME_MAPPING);
if (mapping != null) {
for (String source : mapping.keySet()) {
if (Urls.isUrlWithProtocol(source)) {
String path = getDockerHost().deployArchive(source);
volumes.put(path, mapping.get(source));
} else {
volumes.put(source, mapping.get(source));
}
}
}
List exports = entity.config().get(DockerContainer.DOCKER_CONTAINER_VOLUME_EXPORT);
if (exports != null) {
for (String dir : exports) {
volumes.put(dir, dir);
}
}
options.volumes(volumes);
// Environment
List environment = MutableList.of();
Map dockerEnvironment = config().get(DockerContainer.DOCKER_CONTAINER_ENVIRONMENT);
if (dockerEnvironment != null) {
environment.add(Joiner.on(":").withKeyValueSeparator("=").join(dockerEnvironment));
}
Map entityEnvironment = entity.config().get(DockerContainer.DOCKER_CONTAINER_ENVIRONMENT);
if (entityEnvironment != null) {
environment.add(Joiner.on(":").withKeyValueSeparator("=").join(entityEnvironment));
}
options.env(environment);
// Direct port mappings
Map bindings = MutableMap.of();
List entityPortConfig = entity.config().get(DockerAttributes.DOCKER_DIRECT_PORT_CONFIG);
if (entityPortConfig != null) {
for (PortAttributeSensorAndConfigKey key : entityPortConfig) {
PortRange range = entity.config().get(key);
if (range != null && !range.isEmpty()) {
Integer port = range.iterator().next();
if (port != null) {
bindings.put(port, port);
}
}
}
}
List entityPorts = entity.config().get(DockerAttributes.DOCKER_DIRECT_PORTS);
if (entityPorts != null) {
for (Integer port : entityPorts) {
bindings.put(port, port);
}
}
if (bindings.size() > 0) {
options.portBindings(bindings);
}
// Inbound ports
Collection entityOpenPorts = getRequiredOpenPorts(entity);
options.inboundPorts(Ints.toArray(entityOpenPorts));
// Log for debugging without password
LOG.debug("Docker options for {}: {}", getDockerHost(), options);
// Set login password from the Docker host
options.overrideLoginPassword(getDockerHost().getPassword());
return options;
}
@Nullable
private String getSshHostAddress() {
DockerHost dockerHost = getDockerHost();
OsDetails osDetails = dockerHost.getDynamicLocation().getMachine().getMachineDetails().getOsDetails();
if (osDetails.isMac()) {
String address = dockerHost.execCommand("boot2docker ip");
LOG.debug("The boot2docker IP address is {}", Strings.trim(address));
return Strings.trim(address);
} else {
return null;
}
}
/**
* Create a new {@link DockerContainerLocation} wrapping a machine from the host's {@link JcloudsLocation}.
*/
@Override
public DockerContainerLocation createLocation(Map flags) {
DockerHost dockerHost = getDockerHost();
DockerHostLocation host = dockerHost.getDynamicLocation();
SubnetTier subnetTier = dockerHost.getSubnetTier();
// Configure the container options based on the host and the running entity
DockerTemplateOptions options = getDockerTemplateOptions();
// Check the running entity for alternative container name
String containerName = getRunningEntity().config().get(DOCKER_CONTAINER_NAME);
if (Strings.isBlank(containerName)) {
containerName = getAttribute(DOCKER_CONTAINER_NAME);
}
if (Strings.isNonBlank(containerName)) {
options.nodeNames(ImmutableList.of(DockerUtils.allowed(containerName)));
}
// put these fields on the location so it has the info it needs to create the subnet
Map, ?> dockerFlags = MutableMap.