Java tutorial
/* * Copyright 2014-2016 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 clocker.docker.entity.container; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import clocker.docker.entity.DockerHost; import clocker.docker.entity.util.DockerAttributes; import clocker.docker.entity.util.DockerUtils; import clocker.docker.location.DockerContainerLocation; import clocker.docker.location.DockerHostLocation; import clocker.docker.networking.entity.VirtualNetwork; import clocker.docker.networking.entity.sdn.SdnAgent; import clocker.docker.networking.entity.sdn.SdnProvider; import clocker.docker.networking.entity.sdn.util.SdnAttributes; import clocker.docker.networking.entity.sdn.util.SdnUtils; import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; import org.jclouds.compute.domain.Processor; import org.jclouds.compute.domain.TemplateBuilder; import org.jclouds.docker.compute.options.DockerTemplateOptions; import org.jclouds.net.domain.IpPermission; import org.jclouds.net.domain.IpProtocol; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.location.LocationSpec; import org.apache.brooklyn.api.location.NoMachinesAvailableException; import org.apache.brooklyn.api.location.OsDetails; import org.apache.brooklyn.api.location.PortRange; import org.apache.brooklyn.api.mgmt.LocationManager; import org.apache.brooklyn.core.config.render.RendererHints; import org.apache.brooklyn.core.entity.Attributes; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic; import org.apache.brooklyn.core.entity.trait.Startable; import org.apache.brooklyn.core.feed.ConfigToAttributes; import org.apache.brooklyn.core.location.Locations; import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; import org.apache.brooklyn.core.location.dynamic.DynamicLocation; import org.apache.brooklyn.core.sensor.PortAttributeSensorAndConfigKey; import org.apache.brooklyn.entity.software.base.SoftwareProcess; import org.apache.brooklyn.entity.stock.BasicStartableImpl; import org.apache.brooklyn.entity.stock.DelegateEntity; import org.apache.brooklyn.feed.function.FunctionFeed; import org.apache.brooklyn.feed.function.FunctionPollConfig; import org.apache.brooklyn.location.jclouds.JcloudsLocation; import org.apache.brooklyn.location.jclouds.JcloudsLocationConfig; import org.apache.brooklyn.location.jclouds.JcloudsSshMachineLocation; import org.apache.brooklyn.location.jclouds.templates.PortableTemplateBuilder; import org.apache.brooklyn.location.ssh.SshMachineLocation; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.collections.MutableSet; import org.apache.brooklyn.util.core.internal.ssh.SshTool; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.net.Cidr; import org.apache.brooklyn.util.net.Urls; import org.apache.brooklyn.util.text.StringFunctions; import org.apache.brooklyn.util.text.Strings; import org.apache.brooklyn.util.time.Duration; import brooklyn.networking.portforwarding.subnet.JcloudsPortforwardingSubnetLocation; import brooklyn.networking.subnet.SubnetTier; /** * 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(); ConfigToAttributes.apply(this, DOCKER_CONTAINER_NAME); ConfigToAttributes.apply(this, DOCKER_INFRASTRUCTURE); ConfigToAttributes.apply(this, DOCKER_HOST); ConfigToAttributes.apply(this, ENTITY); } @Override public String getIconUrl() { return "classpath://container.png"; } @Override public String getDisplayName() { return String.format("Container (%s)", DockerUtils.getUniqueContainerName(this)); } protected void connectSensors() { status = FunctionFeed.builder().entity(this).period(Duration.seconds(15)) .poll(new FunctionPollConfig<String, String>(DOCKER_CONTAINER_NAME).period(Duration.minutes(1)) .callable(new Callable<String>() { @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<Boolean, Boolean>(SERVICE_UP).callable(new Callable<Boolean>() { @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<Boolean, Boolean>(CONTAINER_RUNNING).callable(new Callable<Boolean>() { @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<Boolean, Boolean>(CONTAINER_PAUSED).callable(new Callable<Boolean>() { @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.destroy(); } @Override public Entity getRunningEntity() { return sensors().get(ENTITY); } @Override public void setRunningEntity(Entity entity) { sensors().set(ENTITY, entity); } @Override public String getDockerContainerName() { return sensors().get(DOCKER_CONTAINER_NAME); } @Override public String getContainerId() { return sensors().get(DOCKER_CONTAINER_ID); } @Override public SshMachineLocation getMachine() { return sensors().get(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) sensors().get(DYNAMIC_LOCATION); } @Override public boolean isLocationAvailable() { return getDynamicLocation() != null; } @Override public void shutDown() { String dockerContainerName = sensors().get(DockerContainer.DOCKER_CONTAINER_NAME); LOG.info("Stopping {}", dockerContainerName); getDockerHost().runDockerCommand("kill " + getContainerId()); } @Override public void pause() { String dockerContainerName = sensors().get(DockerContainer.DOCKER_CONTAINER_NAME); LOG.info("Pausing {}", dockerContainerName); getDockerHost().runDockerCommand("stop " + getContainerId()); } @Override public void resume() { String dockerContainerName = sensors().get(DockerContainer.DOCKER_CONTAINER_NAME); LOG.info("Resuming {}", dockerContainerName); getDockerHost().runDockerCommand("start " + getContainerId()); } /** * Remove the container from the host. * <p> * Should only be called when the container is not running. */ private void removeContainer() { String dockerContainerName = sensors().get(DockerContainer.DOCKER_CONTAINER_NAME); LOG.info("Removing {}", dockerContainerName); getDockerHost().runDockerCommand("rm " + getContainerId()); } private DockerTemplateOptions getDockerTemplateOptions() { Entity entity = getRunningEntity(); Map<String, Object> entityFlags = MutableMap .copyOf(entity.config().get(SoftwareProcess.PROVISIONING_PROPERTIES)); DockerTemplateOptions options = new DockerTemplateOptions(); // Determine the container name to use Boolean useHostDns = Objects.firstNonNull(entity.config().get(DOCKER_USE_HOST_DNS_NAME), Boolean.FALSE); String hostname = getDockerHost().sensors().get(Attributes.HOSTNAME); String address = getDockerHost().sensors().get(Attributes.ADDRESS); String container = DockerUtils.getUniqueContainerName(entity); String name = (!useHostDns || hostname.equalsIgnoreCase(address)) ? container : hostname; options.hostname(name); options.nodeNames(ImmutableList.of(name)); sensors().set(DOCKER_CONTAINER_NAME, name); entity.sensors().set(DOCKER_CONTAINER_NAME, name); LOG.debug("Container name set to {} for {}", name, entity); // 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); if (minCores == null) { minCores = (Integer) entityFlags.get(JcloudsLocationConfig.MIN_CORES.getName()); } if (minCores == null) { TemplateBuilder template = (TemplateBuilder) entityFlags .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.debug("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); if (minRam == null) { minRam = (Integer) entityFlags.get(JcloudsLocationConfig.MIN_RAM.getName()); } if (minRam == null) { TemplateBuilder template = (TemplateBuilder) entityFlags .get(JcloudsLocationConfig.TEMPLATE_BUILDER.getName()); if (template != null) { minRam = template.build().getHardware().getRam(); } } if (minRam != null) { double ratio = (double) minRam / (double) hostRam; LOG.debug("Memory: host {}, min {}, ratio {}", new Object[] { hostRam, minRam, ratio }); } } if (memory != null) options.memory(memory); // Volumes Map<String, String> volumes = MutableMap .copyOf(getDockerHost().sensors().get(DockerHost.DOCKER_HOST_VOLUME_MAPPING)); Map<String, String> 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<String> exports = entity.config().get(DockerContainer.DOCKER_CONTAINER_VOLUME_EXPORT); if (exports != null) { for (String dir : exports) { volumes.put(dir, dir); } } sensors().set(DockerAttributes.DOCKER_VOLUME_MAPPING, volumes); entity.sensors().set(DockerAttributes.DOCKER_VOLUME_MAPPING, volumes); options.volumes(volumes); List<String> imports = entity.config().get(DockerContainer.DOCKER_CONTAINER_VOLUMES_FROM); if (imports != null) { options.volumesFrom(imports); } // Direct port mappings Map<Integer, Integer> bindings = MutableMap .copyOf(entity.config().get(DockerAttributes.DOCKER_PORT_BINDINGS)); if (bindings == null || bindings.isEmpty()) { bindings = MutableMap.of(); List<PortAttributeSensorAndConfigKey> 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<Integer> entityPorts = entity.config().get(DockerAttributes.DOCKER_DIRECT_PORTS); if (entityPorts != null) { for (Integer port : entityPorts) { bindings.put(port, port); } } } sensors().set(DockerAttributes.DOCKER_CONTAINER_PORT_BINDINGS, bindings); entity.sensors().set(DockerAttributes.DOCKER_CONTAINER_PORT_BINDINGS, bindings); if (bindings.size() > 0) { options.portBindings(bindings); } // Inbound ports Set<Integer> entityOpenPorts = MutableSet.copyOf(DockerUtils.getContainerPorts(entity)); entityOpenPorts.addAll(DockerUtils.getOpenPorts(entity)); options.inboundPorts(Ints.toArray(entityOpenPorts)); if (!config().get(DockerContainer.DOCKER_USE_SSH)) { entityOpenPorts.remove(22); } sensors().set(DockerAttributes.DOCKER_CONTAINER_OPEN_PORTS, ImmutableList.copyOf(entityOpenPorts)); entity.sensors().set(DockerAttributes.DOCKER_CONTAINER_OPEN_PORTS, ImmutableList.copyOf(entityOpenPorts)); // Environment and links MutableMap<String, Object> environment = MutableMap.of(); environment.add(config().get(DockerContainer.DOCKER_CONTAINER_ENVIRONMENT)); environment.add(entity.config().get(DockerContainer.DOCKER_CONTAINER_ENVIRONMENT)); Map<String, Entity> links = entity.config().get(DockerAttributes.DOCKER_LINKS); if (links != null && links.size() > 0) { LOG.debug("Found links: {}", links); Map<String, String> extraHosts = MutableMap.of(); for (Map.Entry<String, Entity> linked : links.entrySet()) { Map<String, Object> linkVars = DockerUtils.generateLinks(getRunningEntity(), linked.getValue(), linked.getKey()); environment.add(linkVars); String targetAddress = DockerUtils.getTargetAddress(getRunningEntity(), linked.getValue()); extraHosts.put(linked.getKey(), targetAddress); } options.extraHosts(extraHosts); } sensors().set(DockerContainer.DOCKER_CONTAINER_ENVIRONMENT, environment); entity.sensors().set(DockerContainer.DOCKER_CONTAINER_ENVIRONMENT, environment); List<String> env = MutableList.of(); if (environment != null && !environment.isEmpty()) { for (Map.Entry<String, Object> entry : environment.entrySet()) { env.add(entry.getKey() + "=" + entry.getValue()); } } options.env(env); // Entrypoint and commands // TODO parse string into list? List<String> entrypoint = entity.config().get(DockerContainer.DOCKER_IMAGE_ENTRYPOINT); if (entrypoint != null && entrypoint.size() > 0) { options.entrypoint(entrypoint); sensors().set(DockerAttributes.DOCKER_IMAGE_ENTRYPOINT, entrypoint); entity.sensors().set(DockerAttributes.DOCKER_IMAGE_ENTRYPOINT, entrypoint); } List<String> commands = entity.config().get(DockerContainer.DOCKER_IMAGE_COMMANDS); if (commands != null && commands.size() > 0) { options.commands(commands); sensors().set(DockerAttributes.DOCKER_IMAGE_COMMANDS, commands); entity.sensors().set(DockerAttributes.DOCKER_IMAGE_COMMANDS, commands); } // Privileged container Boolean privileged = entity.config().get(DockerContainer.PRIVILEGED); options.privileged(privileged); Boolean openStdin = entity.config().get(DockerContainer.INTERACTIVE); options.openStdin(openStdin); // Log for debugging without password LOG.debug("Docker options for {}: {}", entity, options); // Set login password from the Docker host options.overrideLoginPassword(getDockerHost().getLoginPassword()); return options; } private InetAddress getSshHostAddress() { DockerHost dockerHost = getDockerHost(); OsDetails osDetails = dockerHost.getDynamicLocation().getMachine().getMachineDetails().getOsDetails(); if (osDetails.isMac()) { String address = dockerHost.execCommand("boot2docker ip"); if (Strings.isNonBlank(address)) { LOG.debug("The boot2docker IP address is {}", Strings.trim(address)); try { return InetAddress.getByName(Strings.trim(address)); } catch (UnknownHostException e) { throw Exceptions.propagate(e); } } } return dockerHost.getDynamicLocation().getMachine().getAddress(); } public void configurePortBindings(DockerHost host, Entity entity) { Collection<IpPermission> ipPermissions = getIpPermissions(entity); if (ipPermissions.size() > 0) { LOG.debug("Adding security group entries for forwarded ports on {}: {}", entity, Iterables.toString(ipPermissions)); host.addIpPermissions(ipPermissions); } } public void removePortBindings(DockerHost host, Entity entity) { Collection<IpPermission> ipPermissions = getIpPermissions(entity); if (ipPermissions.size() > 0) { LOG.debug("Removing security group entries for forwarded ports on {}: {}", entity, Iterables.toString(ipPermissions)); host.removeIpPermissions(ipPermissions); } } public Collection<IpPermission> getIpPermissions(Entity entity) { Map<Integer, Integer> bindings = entity.sensors().get(DockerAttributes.DOCKER_CONTAINER_PORT_BINDINGS); if (bindings.size() == 0) { return ImmutableList.<IpPermission>of(); } Collection<IpPermission> permissions = MutableList.of(); for (Integer hostPort : bindings.keySet()) { IpPermission portAccess = IpPermission.builder().ipProtocol(IpProtocol.TCP).fromPort(hostPort) .toPort(hostPort).cidrBlock(Cidr.UNIVERSAL.toString()).build(); permissions.add(portAccess); } return permissions; } /** * 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(); Entity entity = getRunningEntity(); // Configure the container options based on the host and the running entity DockerTemplateOptions options = getDockerTemplateOptions(); boolean useSsh = Boolean.TRUE.equals(config().get(DOCKER_USE_SSH)) && Boolean.TRUE.equals(entity.config().get(DOCKER_USE_SSH)); // put these fields on the location so it has the info it needs to create the subnet Map<Object, Object> dockerFlags = MutableMap.<Object, Object>builder().putAll(flags) .put(JcloudsLocationConfig.TEMPLATE_BUILDER, new PortableTemplateBuilder().options(options)) .put(JcloudsLocationConfig.IMAGE_ID, config().get(DOCKER_IMAGE_ID)) .put(JcloudsLocationConfig.HARDWARE_ID, config().get(DOCKER_HARDWARE_ID)) .put(JcloudsLocationConfig.LOGIN_USER, "root") .put(JcloudsLocationConfig.LOGIN_USER_PASSWORD, config().get(DOCKER_LOGIN_PASSWORD)) .put(CloudLocationConfig.WAIT_FOR_SSHABLE, useSsh) .put(JcloudsLocationConfig.INBOUND_PORTS, options.getInboundPorts()) .put(JcloudsLocation.USE_PORT_FORWARDING, true) .put(JcloudsLocation.PORT_FORWARDER, subnetTier.getPortForwarderExtension()) .put(JcloudsLocation.PORT_FORWARDING_MANAGER, subnetTier.getPortForwardManager()) .put(JcloudsPortforwardingSubnetLocation.PORT_FORWARDER, subnetTier.getPortForwarder()) .put(SubnetTier.SUBNET_CIDR, Cidr.CLASS_B).build(); // TODO If auto-checkpointing to create an image after the install() phase, then that will include // whatever ssh credentials were put into the image (e.g. by JcloudsLocation#createUserStatements()). if (Boolean.TRUE.equals(entity.config().get(DockerAttributes.AUTO_CHECKPOINT_DOCKER_IMAGE_POST_INSTALL))) { dockerFlags.put(JcloudsLocationConfig.USER, "root"); dockerFlags.put(JcloudsLocationConfig.PASSWORD, config().get(DOCKER_LOGIN_PASSWORD)); } try { // If SDN is enabled, create networks if (config().get(SdnAttributes.SDN_ENABLE)) { // Determine list of networks for the entity List<String> networks = MutableList.of(); Collection<String> extra = entity.config().get(SdnAttributes.NETWORK_LIST); if (entity.config().get(SdnAttributes.CREATE_APPLICATION_NETWORK)) { networks.add(entity.getApplicationId()); } if (extra != null && extra.size() > 0) { networks.addAll(extra); } if (networks.isEmpty()) { throw new IllegalStateException("No networks configured for container"); } networks = Lists.transform(networks, StringFunctions.toLowerCase()); // Save attached network list sensors().set(SdnAttributes.INITIAL_ATTACHED_NETWORK, networks.get(0)); entity.sensors().set(SdnAttributes.INITIAL_ATTACHED_NETWORK, networks.get(0)); sensors().set(SdnAttributes.ATTACHED_NETWORKS, networks); entity.sensors().set(SdnAttributes.ATTACHED_NETWORKS, networks); } // Create isolated application bridge network for port forwarding synchronized (getDockerHost().getHostMutex()) { String bridgeNetwork = String.format("%s_%s", entity.getApplicationId(), DockerUtils.BRIDGE_NETWORK) .toLowerCase(); if (!getDockerHost().runDockerCommand("network ls").contains(bridgeNetwork)) { getDockerHost().runDockerCommand(String.format("network create --driver bridge " + "-o com.docker.network.bridge.enable_ip_masquerade=true " + "-o com.docker.network.bridge.host_binding_ipv4=0.0.0.0 %s", bridgeNetwork)); } sensors().set(SdnAttributes.BRIDGE_NETWORK_ID, bridgeNetwork); entity.sensors().set(SdnAttributes.BRIDGE_NETWORK_ID, bridgeNetwork); options.networkMode(bridgeNetwork); } // Create a new container using jclouds Docker driver JcloudsSshMachineLocation container = (JcloudsSshMachineLocation) host.getJcloudsLocation() .obtain(dockerFlags); String containerId = container.getJcloudsId(); sensors().set(DOCKER_CONTAINER_ID, containerId); // Configure the host to allow remote access to bound container ports configurePortBindings(dockerHost, entity); // Link the entity to the container entity.sensors().set(DockerContainer.DOCKER_INFRASTRUCTURE, dockerHost.getInfrastructure()); entity.sensors().set(DockerContainer.DOCKER_HOST, dockerHost); entity.sensors().set(DockerContainer.CONTAINER, this); entity.sensors().set(DockerContainer.DOCKER_CONTAINER_ID, containerId); // If SDN is enabled, attach networks if (config().get(SdnAttributes.SDN_ENABLE)) { SdnAgent agent = Entities.attributeSupplierWhenReady(dockerHost, SdnAgent.SDN_AGENT).get(); List<String> networks = sensors().get(SdnAttributes.ATTACHED_NETWORKS); Set<String> addresses = Sets.newHashSet(); // Create and attach networks for (String networkId : networks) { agent.createNetwork(networkId); InetAddress address = agent.attachNetwork(containerId, networkId); addresses.add(address.getHostAddress()); } // Save container addresses sensors().set(CONTAINER_ADDRESSES, addresses); entity.sensors().set(CONTAINER_ADDRESSES, addresses); } // Create our wrapper location around the container LocationSpec<DockerContainerLocation> spec = LocationSpec.create(DockerContainerLocation.class); spec.configure(flags).configure(DynamicLocation.OWNER, this).configure("entity", entity) .configure("machine", container) // the underlying JcloudsLocation .configure(container.config().getBag().getAllConfig()).configure("address", getSshHostAddress()) .configure(SshMachineLocation.SSH_HOST, getSshHostAddress().getHostName()) .configure(SshTool.PROP_HOST, getSshHostAddress().getHostName()) .configure(SshTool.PROP_PORT, container.getSshHostAndPort().getPort()); DockerContainerLocation location = getManagementContext().getLocationManager().createLocation(spec); sensors().set(DYNAMIC_LOCATION, location); sensors().set(LOCATION_NAME, location.getId()); DockerUtils.addExtraPublicKeys(entity, location); DockerUtils.configurePortMappings(entity); DockerUtils.configureEnrichers(subnetTier, entity); LOG.info("New Docker container location {} created", location); return location; } catch (NoMachinesAvailableException e) { throw Exceptions.propagate(e); } } @Override public void deleteLocation() { DockerContainerLocation location = getDynamicLocation(); if (location != null) { try { location.close(); } catch (IOException ioe) { LOG.debug("Error closing container location", ioe); } LocationManager mgr = getManagementContext().getLocationManager(); if (mgr.isManaged(location)) { mgr.unmanage(location); } } sensors().set(DYNAMIC_LOCATION, null); sensors().set(LOCATION_NAME, null); } @Override public void start(Collection<? extends Location> locs) { addLocations(locs); List<Location> locations = MutableList.copyOf(Locations.getLocationsCheckingAncestors(locs, this)); ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING); try { Boolean started = config().get(SoftwareProcess.ENTITY_STARTED); Boolean managed = config().get(DockerContainer.MANAGED); if (Boolean.TRUE.equals(started) || !managed) { DockerHost dockerHost = getDockerHost(); DockerHostLocation host = dockerHost.getDynamicLocation(); sensors().set(DOCKER_IMAGE_ID, config().get(DOCKER_IMAGE_ID)); sensors().set(DOCKER_IMAGE_NAME, config().get(DOCKER_IMAGE_NAME)); sensors().set(SSH_MACHINE_LOCATION, host.getMachine()); } else { Map<String, ?> flags = MutableMap.copyOf(config().get(LOCATION_FLAGS)); DockerContainerLocation location = createLocation(flags); sensors().set(SSH_MACHINE_LOCATION, location.getMachine()); } connectSensors(); super.start(locations); } catch (Exception e) { ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE); throw Exceptions.propagate(e); } ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING); } @Override public void rebind() { super.rebind(); if (status == null) { connectSensors(); } } @Override public void restart() { stop(); start(getLocations()); } @Override public void stop() { Lifecycle state = sensors().get(SERVICE_STATE_ACTUAL); if (Lifecycle.STOPPING.equals(state) || Lifecycle.STOPPED.equals(state)) { LOG.debug("Ignoring request to stop {} when it is already {}", this, state); LOG.trace("Duplicate stop came from: \n" + Joiner.on("\n").join(Thread.getAllStackTraces().get(Thread.currentThread()))); return; } LOG.info("Stopping {} when its state is {}", this, sensors().get(SERVICE_STATE_ACTUAL)); ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING); disconnectSensors(); // Remove IP permissions for running entity if present Entity entity = getRunningEntity(); if (entity != null) { removePortBindings(getDockerHost(), entity); } // Stop and remove the Docker container running on the host if (getContainerId() != null) { shutDown(); } removeContainer(); // If this is a Clocker managd container delete networks and location Boolean managed = config().get(DockerContainer.MANAGED); if (managed) { // Delete application bridge network synchronized (getDockerHost().getHostMutex()) { String bridgeNetwork = sensors().get(SdnAttributes.BRIDGE_NETWORK_ID); try { int attached = Integer.parseInt(getDockerHost().runDockerCommand( String.format("network inspect --format=\"{{ len .Containers }}\" %s", bridgeNetwork))); if (attached == 0) { getDockerHost().runDockerCommand(String.format("network rm %s", bridgeNetwork)); } } catch (IllegalStateException ise) { LOG.warn("Error trying to remove bridge network {}: {}", bridgeNetwork, ise); } } // Delete SDN networks no longer in use if (config().get(SdnAttributes.SDN_ENABLE)) { SdnProvider provider = (SdnProvider) getDockerHost().getInfrastructure().sensors() .get(SdnAttributes.SDN_PROVIDER); List<String> networks = sensors().get(SdnAttributes.ATTACHED_NETWORKS); for (String networkId : networks) { synchronized (getDockerHost().getInfrastructure().getInfrastructureMutex()) { Optional<Integer> attached = SdnUtils.countAttached(getDockerHost(), networkId); LOG.debug("Found {} containers attached to {} when stopping {}", new Object[] { attached.or(-1), networkId, getContainerId() }); if (attached.isPresent() && attached.get() == 0) { VirtualNetwork networkEntity = SdnUtils.lookupNetwork(provider, networkId); Entities.invokeEffector(getDockerHost(), networkEntity, Startable.STOP).getUnchecked(); Entities.unmanage(networkEntity); } } } } sensors().set(SSH_MACHINE_LOCATION, null); Boolean started = config().get(SoftwareProcess.ENTITY_STARTED); if (!started) { deleteLocation(); } } ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED); } @Override public String getHostname() { String containerName = getDockerContainerName(); if (config().get(SdnAttributes.SDN_ENABLE)) { String initialNetwork = sensors().get(SdnAttributes.INITIAL_ATTACHED_NETWORK); return String.format("%s.%s", containerName, initialNetwork); } else { return containerName; } } @Override public Set<String> getPublicAddresses() { return Sets.newHashSet(sensors().get(SoftwareProcess.SUBNET_ADDRESS)); } @Override public Set<String> getPrivateAddresses() { return sensors().get(CONTAINER_ADDRESSES); } static { RendererHints.register(DOCKER_HOST, RendererHints.openWithUrl(DelegateEntity.EntityUrl.entityUrl())); RendererHints.register(ENTITY, RendererHints.openWithUrl(DelegateEntity.EntityUrl.entityUrl())); RendererHints.register(CONTAINER, RendererHints.openWithUrl(DelegateEntity.EntityUrl.entityUrl())); } }