brooklyn.location.docker.DockerLocation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of brooklyn-clocker-docker Show documentation
Show all versions of brooklyn-clocker-docker Show documentation
Clocker Brooklyn entities and locations for Docker integration.
/*
* 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.location.docker;
import java.io.Closeable;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import brooklyn.entity.Entity;
import brooklyn.entity.basic.Entities;
import brooklyn.entity.basic.EntityFunctions;
import brooklyn.entity.container.DockerAttributes;
import brooklyn.entity.container.docker.DockerHost;
import brooklyn.entity.container.docker.DockerInfrastructure;
import brooklyn.entity.group.DynamicCluster;
import brooklyn.entity.rebind.BasicLocationRebindSupport;
import brooklyn.entity.rebind.RebindContext;
import brooklyn.entity.rebind.RebindSupport;
import brooklyn.location.MachineLocation;
import brooklyn.location.MachineProvisioningLocation;
import brooklyn.location.NoMachinesAvailableException;
import brooklyn.location.basic.AbstractLocation;
import brooklyn.location.basic.LocationConfigKeys;
import brooklyn.location.basic.SshMachineLocation;
import brooklyn.location.docker.strategy.DockerAwarePlacementStrategy;
import brooklyn.location.docker.strategy.DockerAwareProvisioningStrategy;
import brooklyn.location.dynamic.DynamicLocation;
import brooklyn.mementos.LocationMemento;
import brooklyn.networking.location.NetworkProvisioningExtension;
import brooklyn.util.collections.MutableMap;
import brooklyn.util.exceptions.Exceptions;
import brooklyn.util.flags.SetFromFlag;
import com.google.common.base.Objects.ToStringHelper;
import com.google.common.base.Optional;
import com.google.common.base.Predicates;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
public class DockerLocation extends AbstractLocation implements DockerVirtualLocation, MachineProvisioningLocation,
DynamicLocation, Closeable {
private static final Logger LOG = LoggerFactory.getLogger(DockerLocation.class);
@SetFromFlag("owner")
private DockerInfrastructure infrastructure;
@SetFromFlag("strategies")
private List strategies;
@SetFromFlag("provisioner")
private MachineProvisioningLocation provisioner;
@SetFromFlag("machines")
private final SetMultimap containers = Multimaps.synchronizedSetMultimap(HashMultimap.create());
private transient Semaphore permit = new Semaphore(1);
public DockerLocation() {
this(Maps.newLinkedHashMap());
}
public DockerLocation(Map properties) {
super(properties);
if (isLegacyConstruction()) {
init();
}
}
public MachineProvisioningLocation getProvisioner() {
return provisioner;
}
protected List getDockerHostLocations() {
List> result = Lists.newArrayList();
for (Entity entity : getDockerHostList()) {
DockerHost host = (DockerHost) entity;
DockerHostLocation machine = host.getDynamicLocation();
result.add(Optional.fromNullable(machine));
}
return ImmutableList.copyOf(Optional.presentInstances(result));
}
public MachineLocation obtain() throws NoMachinesAvailableException {
return obtain(Maps.newLinkedHashMap());
}
@Override
public MachineLocation obtain(Map,?> flags) throws NoMachinesAvailableException {
// Check context for entity being deployed
Object context = flags.get(LocationConfigKeys.CALLER_CONTEXT.getName());
if (context != null && !(context instanceof Entity)) {
throw new IllegalStateException("Invalid location context: " + context);
}
Entity entity = (Entity) context;
// Get the available hosts based on placement strategies
List available = getDockerHostLocations();
if (LOG.isDebugEnabled()) {
LOG.debug("Placement for: {}", Iterables.toString(Iterables.transform(available, EntityFunctions.id())));
}
for (DockerAwarePlacementStrategy strategy : strategies) {
available = strategy.filterLocations(available, entity);
if (LOG.isDebugEnabled()) {
LOG.debug("Placement after {}: {}", strategy, Iterables.toString(Iterables.transform(available, EntityFunctions.id())));
}
}
List entityStrategies = entity.config().get(DockerAttributes.PLACEMENT_STRATEGIES);
if (entityStrategies != null && entityStrategies.size() > 0) {
for (DockerAwarePlacementStrategy strategy : entityStrategies) {
available = strategy.filterLocations(available, entity);
}
} else {
entityStrategies = ImmutableList.of();
}
// Use the docker strategy to add a new host
DockerHostLocation machine = null;
DockerHost dockerHost = null;
if (available.size() > 0) {
machine = available.get(0);
dockerHost = machine.getOwner();
} else {
// Get permission to create a new Docker host
if (permit.tryAcquire()) {
try {
Iterable provisioningStrategies = Iterables.filter(Iterables.concat(strategies, entityStrategies), DockerAwareProvisioningStrategy.class);
for (DockerAwareProvisioningStrategy strategy : provisioningStrategies) {
flags = strategy.apply((Map) flags);
}
LOG.info("Provisioning new host with flags: {}", flags);
SshMachineLocation provisioned = getProvisioner().obtain(flags);
Entity added = getDockerInfrastructure().getDockerHostCluster().addNode(provisioned, MutableMap.of());
dockerHost = (DockerHost) added;
machine = dockerHost.getDynamicLocation();
Entities.start(added, ImmutableList.of(provisioned));
} finally {
permit.release();
}
} else {
// Wait until whoever has the permit releases it, and try again
try {
permit.acquire();
} catch (InterruptedException ie) {
Exceptions.propagate(ie);
} finally {
permit.release();
}
return obtain(flags);
}
}
// Now wait until the host has started up
Entities.waitForServiceUp(dockerHost);
// Obtain a new Docker container location, save and return it
if (LOG.isDebugEnabled()) {
LOG.debug("Obtain a new container from {} for {}", machine, entity);
}
Map,?> hostFlags = MutableMap.copyOf(flags);
DockerContainerLocation container = machine.obtain(hostFlags);
containers.put(machine, container.getId());
return container;
}
@Override
public MachineProvisioningLocation newSubLocation(Map, ?> newFlags) {
throw new UnsupportedOperationException();
}
@Override
public void release(MachineLocation machine) {
if (provisioner == null) {
throw new IllegalStateException("No provisioner available to release "+machine);
}
String id = machine.getId();
Set set = Multimaps.filterValues(containers, Predicates.equalTo(id)).keySet();
if (set.isEmpty()) {
throw new IllegalArgumentException("Request to release "+machine+", but this machine is not currently allocated");
}
DockerHostLocation host = Iterables.getOnlyElement(set);
if (LOG.isDebugEnabled()) {
LOG.debug("Request to remove container mapping {} to {}", host, id);
}
host.release((DockerContainerLocation) machine);
if (containers.remove(host, id)) {
if (containers.get(host).isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("Empty Docker host: {}", host);
}
// Remove hosts when it has no containers, except for the last one
if (getOwner().config().get(DockerInfrastructure.REMOVE_EMPTY_DOCKER_HOSTS) && set.size() > 1) {
LOG.info("Removing empty Docker host: {}", host);
remove(host);
}
}
} else {
throw new IllegalArgumentException("Request to release "+machine+", but container mapping not found");
}
}
protected void remove(DockerHostLocation machine) {
LOG.info("Releasing {}", machine);
DynamicCluster cluster = infrastructure.getDockerHostCluster();
DockerHost host = machine.getOwner();
if (cluster.removeMember(host)) {
LOG.info("Docker Host {} released", host.getDockerHostName());
} else {
LOG.warn("Docker Host {} not found for release", host.getDockerHostName());
}
// Now close and unmange the host
try {
machine.close();
host.stop();
} catch (Exception e) {
LOG.warn("Error stopping host: " + host, e);
Exceptions.propagateIfFatal(e);
} finally {
Entities.unmanage(host);
}
}
@Override
public Map getProvisioningFlags(Collection tags) {
return Maps.newLinkedHashMap();
}
@Override
public DockerInfrastructure getOwner() {
return infrastructure;
}
public List getDockerContainerList() {
return infrastructure.getDockerContainerList();
}
public List getDockerHostList() {
return infrastructure.getDockerHostList();
}
public DockerInfrastructure getDockerInfrastructure() {
return infrastructure;
}
@Override
public void close() throws IOException {
LOG.info("Close called on Docker infrastructure: {}", this);
}
// FIXME this should be supported in core Brooklyn for all extension tyoes
@Override
public RebindSupport getRebindSupport() {
NetworkProvisioningExtension networkProvisioningExtension = null;
if (hasExtension(NetworkProvisioningExtension.class)) {
networkProvisioningExtension = getExtension(NetworkProvisioningExtension.class);
}
final Optional extension = Optional.fromNullable(networkProvisioningExtension);
return new BasicLocationRebindSupport(this) {
@Override public LocationMemento getMemento() {
return getMementoWithProperties(MutableMap.of("networkProvisioningExtension", extension));
}
@Override
protected void doReconstruct(RebindContext rebindContext, LocationMemento memento) {
super.doReconstruct(rebindContext, memento);
Optional extension = (Optional) memento.getCustomField("networkProvisioningExtension");
if (extension.isPresent()) {
addExtension(NetworkProvisioningExtension.class, extension.get());
}
}
};
}
@Override
public ToStringHelper string() {
return super.string()
.omitNullValues()
.add("provisioner", provisioner)
.add("infrastructure", infrastructure)
.add("strategies", strategies);
}
}