All Downloads are FREE. Search and download functionalities are using the official Maven repository.

brooklyn.location.docker.DockerLocation Maven / Gradle / Ivy

There is a newer version: 1.2.0
Show newest version
/*
 * 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);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy