Java tutorial
/* * Copyright 2016-2017 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.chronos; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hashids.Hashids; import org.springframework.cloud.deployer.spi.app.AppDeployer; import org.springframework.cloud.deployer.spi.core.AppDeploymentRequest; import org.springframework.cloud.deployer.spi.mesos.constraints.Constraint; import org.springframework.cloud.deployer.spi.task.LaunchState; import org.springframework.cloud.deployer.spi.task.TaskLauncher; import org.springframework.cloud.deployer.spi.task.TaskStatus; import org.springframework.cloud.mesos.chronos.client.Chronos; import org.springframework.cloud.mesos.chronos.client.ChronosException; import org.springframework.cloud.mesos.chronos.client.model.DockerContainer; import org.springframework.cloud.mesos.chronos.client.model.DockerJob; import org.springframework.cloud.mesos.chronos.client.model.Job; import org.springframework.util.StringUtils; /** * A task launcher that targets Mesos Chronos. * * @author Thomas Risberg */ public class ChronosTaskLauncher implements TaskLauncher { private static final Log logger = LogFactory.getLog(ChronosTaskLauncher.class); private ChronosTaskLauncherProperties properties = new ChronosTaskLauncherProperties(); private Chronos chronos; public ChronosTaskLauncher(ChronosTaskLauncherProperties properties, Chronos chronos) { this.properties = properties; this.chronos = chronos; } @Override public String launch(AppDeploymentRequest request) { String jobName = createDeploymentId(request); 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); DockerJob job = new DockerJob(); job.setName(jobName); List<Map<String, String>> envVars = new ArrayList<>(); Map<String, String> springApplicationJson = createSpringApplicationJson(request); if (springApplicationJson.size() > 0) { envVars.add(springApplicationJson); } logger.info("Using env: " + envVars); if (envVars.size() > 0) { job.setEnvironmentVariables(envVars); } job.setShell(false); job.setCommand(""); List<String> args = createCommandArgs(request); if (args.size() > 0) { job.setArguments(args); } job.setSchedule("R1//P"); job.setRetries(properties.getRetries()); DockerContainer container = new DockerContainer(); container.setImage(image); job.setContainer(container); Double cpus = deduceCpus(request); Double memory = deduceMemory(request); Collection<Constraint> constraints = deduceConstraints(request); job.setCpus(cpus); job.setMem(memory); job.setConstraints(constraints.stream().map(Constraint::toStringList).collect(Collectors.toList())); if (StringUtils.hasText(properties.getOwnerEmail())) { job.setOwner(properties.getOwnerEmail()); } if (StringUtils.hasText(properties.getOwnerName())) { job.setOwnerName(properties.getOwnerName()); } if (properties.getUris() != null && properties.getUris().length > 0) { job.setUris(Arrays.asList(properties.getUris())); } try { if (logger.isDebugEnabled()) { logger.debug("Launching Job with definition:\n" + job.toString()); } chronos.createDockerJob(job); } catch (ChronosException e) { logger.error(e.getMessage(), e); throw new IllegalStateException(String.format("Error while creating job '%s'", jobName), e); } return jobName; } @Override public void cancel(String id) { try { chronos.deleteJobTasks(id); } catch (ChronosException e) { throw new IllegalStateException(String.format("Error while canceling job '%s'", id), e); } } @Override public TaskStatus status(String id) { String csv = null; try { csv = chronos.getGraphCsv(); } catch (ChronosException e) { throw new IllegalStateException(String.format("Error while retrieving graph"), e); } List<Job> list = null; try { list = chronos.getJobs(); } catch (ChronosException e) { throw new IllegalStateException(String.format("Error while retrieving jobs"), e); } Job job = null; for (Job j : list) { if (j.getName().equals(id)) { job = j; break; } } TaskStatus status = buildTaskStatus(properties, id, job, csv); logger.debug(String.format("Status for task: %s is %s", id, status)); return status; } @Override public void cleanup(String id) { try { chronos.deleteJob(id); } catch (ChronosException e) { throw new IllegalStateException(String.format("Error while deleting job '%s'", id), e); } } @Override public void destroy(String taskName) { } protected String createDeploymentId(AppDeploymentRequest request) { String name = request.getDefinition().getName(); Hashids hashids = new Hashids(name); String hashid = hashids.encode(System.currentTimeMillis()); return name + "-" + hashid; } protected Map<String, String> createSpringApplicationJson(AppDeploymentRequest request) { String value = "{}"; try { value = new ObjectMapper().writeValueAsString( Optional.ofNullable(request.getDefinition().getProperties()).orElse(Collections.emptyMap())); } catch (JsonProcessingException e) { } Map<String, String> springApp = new HashMap<>(); if (!"{}".equals(value)) { springApp.put("name", "SPRING_APPLICATION_JSON"); springApp.put("value", value); } return springApp; } protected List<String> createCommandArgs(AppDeploymentRequest request) { List<String> cmdArgs = new LinkedList<String>(); // add provided command line args cmdArgs.addAll(request.getCommandlineArguments()); logger.debug("Using command args: " + cmdArgs); return cmdArgs; } protected TaskStatus buildTaskStatus(ChronosTaskLauncherProperties properties, String id, Job job, String csv) { if (job == null) { return new TaskStatus(id, LaunchState.unknown, new HashMap<>()); } String last = null; String state = null; if (StringUtils.hasText(csv)) { List<String> csvLines = Arrays.asList(csv.split("\\r?\\n")); for (String line : csvLines) { if (line.startsWith("node")) { List<String> values = Arrays.asList(line.split("\\s*,\\s*")); if (values.size() >= 4) { if (id.equals(values.get(1))) { last = values.get(2); state = values.get(3); break; } } } } } if ("running".equals(state)) { return new TaskStatus(id, LaunchState.running, new HashMap<>()); } if ("queued".equals(state)) { return new TaskStatus(id, LaunchState.launching, new HashMap<>()); } if ("success".equals(last)) { return new TaskStatus(id, LaunchState.complete, new HashMap<>()); } else { // TODO: state == idle could indicate cancelled? return new TaskStatus(id, LaunchState.failed, new HashMap<>()); } } 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 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 String prefix(String property) { return ChronosTaskLauncherProperties.PREFIX + "." + property; } }