
com.groupon.deployment.host.Docker Maven / Gradle / Ivy
/**
* Copyright 2015 Groupon.com
*
* 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 com.groupon.deployment.host;
import akka.actor.AbstractActor;
import akka.actor.ActorRef;
import akka.dispatch.Futures;
import akka.dispatch.OnComplete;
import client.DockerDeploymentClient;
import client.DockerDeploymentClient.ContainerDescription;
import client.docker.PortMapping;
import client.docker.inspectionbeans.ImageInspection;
import com.arpnetworking.steno.Logger;
import com.arpnetworking.steno.LoggerFactory;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Maps;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.groupon.deployment.HostDeploymentCommands;
import com.groupon.deployment.HostDeploymentNotifications;
import models.Host;
import models.Manifest;
import models.PackageVersion;
import models.Stage;
import scala.compat.java8.JFunction;
import scala.compat.java8.JFunction1;
import scala.concurrent.ExecutionContext;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Implements the DeploymentManager actor interface by accepting HostDeploymentCommands.StartDeployment messages.
*
* @author Matthew Hayter (mhayter at groupon dot com)
*/
public class Docker extends AbstractActor {
/**
* Public constructor.
*
* @param deploymentClient a deployment client
* @param futuresContext a context to run futures in
*/
@AssistedInject
public Docker(@Assisted final DockerDeploymentClient deploymentClient, final ExecutionContext futuresContext) {
_deploymentClient = deploymentClient;
_futuresContext = futuresContext;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(HostDeploymentCommands.StartDeployment.class, deployStage ->
deploy(deployStage.getManifest(), deployStage.getHost(), deployStage.getStage()))
.match(RunningContainersMsg.class, message -> {
final List containerDescriptions = message._containerDescriptions;
containerDescriptions.forEach(d -> LOGGER.info().setMessage("Running container").addData("name", d.getName()).log());
})
.build();
}
@Override
public void postStop() throws Exception {
super.postStop();
_deploymentClient.shutdown();
}
private void deploy(final Manifest manifest, final Host host, final Stage stage) {
final ActorRef parent = context().parent();
final LoggerToParent logger = new LoggerToParent(context().parent(), self(), host);
context().parent().tell(new HostDeploymentNotifications.DeploymentStarted(host), self());
// TODO(mhayter): andThen always runs in the provided _futuresContext, right? .map sometimes runs in the calling thread. [Artemis-?]
final String environmentId = String.valueOf(stage.getEnvironment().getId());
Futures.future(_deploymentClient::getRunningContainers, _futuresContext)
.map(JFunction.func(containers -> {
context().parent().tell(
new HostDeploymentNotifications.DeploymentLog(
host,
String.format("Found %d running containers", containers.size())),
self());
context().parent().tell(
new HostDeploymentNotifications.DeploymentLog(
host,
"Looking for extraneous containers"),
self());
return findContainersForRemoval(manifest, containers, environmentId);
}), _futuresContext)
.map(JFunction.func(new RmContainersCb(logger)), _futuresContext)
.map(JFunction.func(new PullImagesCb(manifest, _deploymentClient, logger)), _futuresContext)
.map(JFunction.func(new GetPortsCb(manifest, _deploymentClient, logger)), _futuresContext)
.map(JFunction.func(new StartImagesCb(manifest, _deploymentClient, logger, environmentId)), _futuresContext)
.andThen(new OnComplete() {
@Override
public void onComplete(final Throwable failure, final Void success) throws Throwable {
if (failure == null) {
// Deploy success!
parent.tell(new HostDeploymentNotifications.DeploymentSucceeded(host), self());
} else {
// Deploy failure!
parent.tell(new HostDeploymentNotifications.DeploymentFailed(host, failure), self());
}
}
}, _futuresContext);
}
/**
*
* @return Containers that are running as part of this Environment, which have images that are not specified in
* the manifest that is being deployed.
*/
private List findContainersForRemoval(
final Manifest manifest,
final List runningContainers,
final String envId)
throws DockerDeployFailureException {
return runningContainers.stream()
.filter(container -> isInEnv(envId, container))
.collect(Collectors.toList());
}
private boolean isInEnv(final String envName, final ContainerDescription container) {
final Matcher matcher = CONTAINER_NAME_PATTERN.matcher(container.getName());
if (!matcher.find()) {
// If the name of the image does not match artemis' format, it mustn't be in our artemis environment.
return false;
}
return envName.equals(matcher.group(CONTAINER_NAME_PATTERN_ENVIRONMENT_GROUP));
}
private final DockerDeploymentClient _deploymentClient;
private final ExecutionContext _futuresContext;
private Manifest _manifest;
/**
* This string should be prepended to the containers created by artemis.
*/
// TODO(mhayter): Use 'labels' on containers instead of name prefixes. [Artemis-?]
private static final String CONTAINER_NAME_PREFIX = "artemis-";
private static final Pattern CONTAINER_NAME_PATTERN = Pattern.compile(CONTAINER_NAME_PREFIX + "(\\d+)");
private static final int CONTAINER_NAME_PATTERN_ENVIRONMENT_GROUP = 1;
private static final Logger LOGGER = LoggerFactory.getLogger(Docker.class);
// TODO(mhayter): make this a checked exception [Artemis-?]
private static final class DockerDeployFailureException extends RuntimeException {
private static final long serialVersionUID = 1L;
DockerDeployFailureException(final String message) {
super(message);
}
DockerDeployFailureException(final String message, final Throwable cause) {
super(message, cause);
}
}
private static final class RunningContainersMsg {
private final List _containerDescriptions;
private RunningContainersMsg(final List containerDescriptions) {
_containerDescriptions = containerDescriptions;
}
}
private static final class StartImagesCb implements JFunction1
© 2015 - 2025 Weber Informatics LLC | Privacy Policy