org.springframework.cloud.deployer.spi.mesos.marathon.MarathonAppDeployer.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.cloud.deployer.spi.mesos.marathon.MarathonAppDeployer.java

Source

/*
 * Copyright 2015-2016 the original author or authors.
 *
 * 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 org.springframework.cloud.deployer.spi.mesos.marathon;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import mesosphere.marathon.client.Marathon;
import mesosphere.marathon.client.model.v2.App;
import mesosphere.marathon.client.model.v2.Container;
import mesosphere.marathon.client.model.v2.Docker;
import mesosphere.marathon.client.model.v2.Group;
import mesosphere.marathon.client.model.v2.HealthCheck;
import mesosphere.marathon.client.model.v2.Port;
import mesosphere.marathon.client.model.v2.Task;
import mesosphere.marathon.client.utils.MarathonException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.deployer.spi.app.AppDeployer;
import org.springframework.cloud.deployer.spi.app.AppInstanceStatus;
import org.springframework.cloud.deployer.spi.app.AppStatus;
import org.springframework.cloud.deployer.spi.app.DeploymentState;
import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest;
import org.springframework.cloud.deployer.spi.mesos.constraints.Constraint;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * A deployer implementation for deploying apps on Marathon, using the
 * ModuleLauncher Docker image.
 *
 * @author Eric Bottard
 * @author Ilayaperumal Gopinathan
 * @author Thomas Risberg
 */
public class MarathonAppDeployer implements AppDeployer {

    private static final Log logger = LogFactory.getLog(MarathonAppDeployer.class);

    private MarathonAppDeployerProperties properties = new MarathonAppDeployerProperties();

    Marathon marathon;

    @Autowired
    public MarathonAppDeployer(MarathonAppDeployerProperties properties, Marathon marathon) {
        this.properties = properties;
        this.marathon = marathon;
    }

    @Override
    public String deploy(AppDeploymentRequest request) {

        logger.info(String.format("Deploying app: %s", request.getDefinition().getName()));

        String appId = deduceAppId(request);

        boolean indexed = Boolean.valueOf(request.getDeploymentProperties().get(INDEXED_PROPERTY_KEY));

        if (indexed) {
            try {
                Group group = marathon.getGroup(appId);
                throw new IllegalStateException(
                        String.format("App '%s' is already deployed", request.getDefinition().getName()));
            } catch (MarathonException ignore) {
            }
            Container container = createContainer(request);
            String countProperty = request.getDeploymentProperties().get(COUNT_PROPERTY_KEY);
            int count = (countProperty != null) ? Integer.parseInt(countProperty) : 1;
            for (int i = 0; i < count; i++) {
                String instanceId = appId + "/" + request.getDefinition().getName() + "-" + i;
                createAppDeployment(request, instanceId, container, Integer.valueOf(i));
            }
        } else {
            AppStatus status = status(appId);
            if (!status.getState().equals(DeploymentState.unknown)) {
                throw new IllegalStateException(
                        String.format("App '%s' is already deployed", request.getDefinition().getName()));
            }
            Container container = createContainer(request);
            createAppDeployment(request, appId, container, null);
        }

        return appId;
    }

    private void createAppDeployment(AppDeploymentRequest request, String deploymentId, Container container,
            Integer index) {
        App app = new App();
        app.setContainer(container);
        app.setId(deploymentId);

        Map<String, String> env = new HashMap<>();
        env.putAll(request.getDefinition().getProperties());
        for (String envVar : properties.getEnvironmentVariables()) {
            String[] strings = envVar.split("=", 2);
            Assert.isTrue(strings.length == 2, "Invalid environment variable declared: " + envVar);
            env.put(strings[0], strings[1]);
        }
        if (index != null) {
            env.put(INSTANCE_INDEX_PROPERTY_KEY, index.toString());
        }
        app.setEnv(env);

        Collection<String> uris = deduceUris(request);
        app.setUris(uris);

        Collection<Constraint> constraints = deduceConstraints(request);
        app.setConstraints(constraints.stream().map(Constraint::toStringList).collect(Collectors.toList()));

        Double cpus = deduceCpus(request);
        Double memory = deduceMemory(request);
        Integer instances = index == null ? deduceInstances(request) : 1;

        app.setCpus(cpus);
        app.setMem(memory);
        app.setInstances(instances);

        HealthCheck healthCheck = new HealthCheck();
        healthCheck.setPath("/health");
        healthCheck.setGracePeriodSeconds(300);
        app.setHealthChecks(Arrays.asList(healthCheck));

        logger.debug("Creating app with definition:\n" + app.toString());
        try {
            marathon.createApp(app);
        } catch (MarathonException e) {
            throw new RuntimeException(e);
        }
    }

    private Container createContainer(AppDeploymentRequest request) {
        Container container = new Container();
        Docker docker = new Docker();
        String image = null;
        try {
            image = request.getResource().getURI().getSchemeSpecificPart();
        } catch (IOException e) {
            throw new IllegalArgumentException("Unable to get URI for " + request.getResource(), e);
        }
        logger.info("Using Docker image: " + image);
        docker.setImage(image);
        Port port = new Port(8080);
        port.setHostPort(0);
        docker.setPortMappings(Arrays.asList(port));
        docker.setNetwork("BRIDGE");
        container.setDocker(docker);
        return container;
    }

    @Override
    public void undeploy(String id) {
        logger.info(String.format("Undeploying app: %s", id));
        Group group = null;
        try {
            group = marathon.getGroup(id);
        } catch (MarathonException ignore) {
        }
        if (group != null) {
            logger.info(String.format("Undeploying application deployments for group: %s", group.getId()));
            try {
                if (group.getGroups().size() > 0) {
                    for (Group g : group.getGroups()) {
                        deleteAppsForGroupDeployment(g.getId());
                    }
                } else {
                    deleteAppsForGroupDeployment(group.getId());
                }
            } catch (MarathonException e) {
                throw new RuntimeException(e);
            }
        } else {
            logger.info(String.format("Undeploying application deployment: %s", id));
            try {
                App app = marathon.getApp(id).getApp();
                logger.debug(String.format("Deleting application: %s", app.getId()));
                marathon.deleteApp(id);
                deleteTopLevelGroupForDeployment(id);
            } catch (MarathonException e) {
                if (e.getMessage().contains("Not Found")) {
                    logger.debug(String.format("Caught: %s", e.getMessage()));
                    try {
                        deleteAppsForGroupDeployment(id);
                    } catch (MarathonException e2) {
                        throw new RuntimeException(e2);
                    }
                } else {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private void deleteAppsForGroupDeployment(String groupId) throws MarathonException {
        Group group = marathon.getGroup(groupId);
        for (App app : group.getApps()) {
            logger.debug(String.format("Deleting application %s in group %s", app.getId(), groupId));
            marathon.deleteApp(app.getId());
        }
        group = marathon.getGroup(groupId);
        if (logger.isDebugEnabled()) {
            logger.debug(String.format("Group %s has %d applications and %d groups", group.getId(),
                    group.getApps().size(), group.getGroups().size()));
        }
        if (group.getApps().size() == 0 && group.getGroups().size() == 0) {
            logger.info(String.format("Deleting group: %s", groupId));
            marathon.deleteGroup(groupId);
        }
        deleteTopLevelGroupForDeployment(groupId);
    }

    private void deleteTopLevelGroupForDeployment(String id) throws MarathonException {
        String topLevelGroupId = extractGroupId(id);
        if (topLevelGroupId != null) {
            Group topGroup = marathon.getGroup(topLevelGroupId);
            if (logger.isDebugEnabled()) {
                logger.debug(String.format("Top level group %s has %d applications and %d groups", topGroup.getId(),
                        topGroup.getApps().size(), topGroup.getGroups().size()));
            }
            if (topGroup.getApps().size() == 0 && topGroup.getGroups().size() == 0) {
                logger.info(String.format("Deleting group: %s", topLevelGroupId));
                marathon.deleteGroup(topLevelGroupId);
            }
        }
    }

    @Override
    public AppStatus status(String id) {
        AppStatus status;
        try {
            App app = marathon.getApp(id).getApp();
            logger.debug(String.format("Building status for app: %s", id));
            status = buildAppStatus(id, app);
        } catch (MarathonException e) {
            if (e.getMessage().contains("Not Found")) {
                try {
                    Group group = marathon.getGroup(id);
                    logger.debug(String.format("Building status for group: %s", id));
                    AppStatus.Builder result = AppStatus.of(id);
                    for (App app : group.getApps()) {
                        result.with(buildInstanceStatus(app.getId()));
                    }
                    status = result.build();
                } catch (MarathonException e1) {
                    status = AppStatus.of(id).build();
                }
            } else {
                status = AppStatus.of(id).build();
            }
        }
        logger.debug(String.format("Status for app: %s is %s", id, status));
        return status;
    }

    private String deduceAppId(AppDeploymentRequest request) {
        String groupId = request.getDeploymentProperties().get(GROUP_PROPERTY_KEY);
        String name = request.getDefinition().getName();
        if (groupId != null) {
            return "/" + groupId + "/" + name;
        } else {
            return "/" + name;
        }
    }

    private String extractGroupId(String appId) {
        int index = appId.lastIndexOf('/');
        String groupId = null;
        if (index > 0) {
            groupId = appId.substring(0, index);
        }
        return groupId;
    }

    private Collection<Constraint> deduceConstraints(AppDeploymentRequest request) {
        Set<Constraint> requestSpecific = StringUtils
                .commaDelimitedListToSet(request.getDeploymentProperties().get(prefix("constraints"))).stream()
                .map(Constraint::new).collect(Collectors.toSet());
        Set<Constraint> result = new HashSet<>(properties.getConstraints());
        result.addAll(requestSpecific);
        return result;
    }

    private Collection<String> deduceUris(AppDeploymentRequest request) {
        Set<String> additional = StringUtils
                .commaDelimitedListToSet(request.getDeploymentProperties().get(prefix("uris")));
        HashSet<String> result = new HashSet<>(additional);
        result.addAll(properties.getUris());
        return result;
    }

    private Double deduceMemory(AppDeploymentRequest request) {
        String override = request.getDeploymentProperties().get(AppDeployer.MEMORY_PROPERTY_KEY);
        return override != null ? Double.valueOf(override) : properties.getMemory();
    }

    private Double deduceCpus(AppDeploymentRequest request) {
        String override = request.getDeploymentProperties().get(AppDeployer.CPU_PROPERTY_KEY);
        return override != null ? Double.valueOf(override) : properties.getCpu();
    }

    private Integer deduceInstances(AppDeploymentRequest request) {
        String value = request.getDeploymentProperties().get(COUNT_PROPERTY_KEY);
        return value != null ? Integer.valueOf(value) : Integer.valueOf("1");
    }

    private AppInstanceStatus buildInstanceStatus(String id) throws MarathonException {
        App appInstance = marathon.getApp(id).getApp();
        logger.debug("Deployment " + id + " has " + appInstance.getTasksRunning() + "/" + appInstance.getInstances()
                + " tasks running");
        if (appInstance.getTasks() != null) {
            // there should only be one task for this type of deployment
            MarathonAppInstanceStatus status = null;
            for (Task task : appInstance.getTasks()) {
                if (status == null) {
                    status = MarathonAppInstanceStatus.up(appInstance, task);
                }
            }
            if (status == null) {
                status = MarathonAppInstanceStatus.down(appInstance);
            }
            return status;
        } else {
            return MarathonAppInstanceStatus.down(appInstance);
        }
    }

    private AppStatus buildAppStatus(String id, App app) {
        logger.debug(
                "Deployment " + id + " has " + app.getTasksRunning() + "/" + app.getInstances() + " tasks running");
        AppStatus.Builder result = AppStatus.of(id);
        int requestedInstances = app.getInstances();
        int actualInstances = 0;
        if (app.getTasks() != null) {
            for (Task task : app.getTasks()) {
                result.with(MarathonAppInstanceStatus.up(app, task));
                actualInstances++;
            }
        }
        for (int i = actualInstances; i < requestedInstances; i++) {
            result.with(MarathonAppInstanceStatus.down(app));
        }
        return result.build();
    }

    private String prefix(String property) {
        return MarathonAppDeployerProperties.PREFIX + "." + property;
    }
}