// Copyright 2016 Twitter. All rights reserved. // // 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.twitter.heron.scheduler.mesos.framework; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import org.apache.mesos.Protos; import com.twitter.heron.scheduler.utils.SchedulerUtils; import com.twitter.heron.spi.common.Config; /** * A structure to group container info and mesos info, * representing a task ready to launch */ public class LaunchableTask { private static final Logger LOG = Logger.getLogger(LaunchableTask.class.getName()); public final String taskId; public final BaseContainer baseContainer; public final Protos.Offer offer; public final List<Integer> freePorts; public LaunchableTask(String taskId, BaseContainer container, Protos.Offer offer, List<Integer> freePorts) { this.taskId = taskId; this.baseContainer = container; this.offer = offer; this.freePorts = freePorts; } protected Protos.Resource scalarResource(String name, double value) { // For a given named resource and value, // find and return the role that matches the name and exceeds the value. // Give preference to reserved offers first (those whose roles do not match "*") List<Protos.Resource> reservedResources = new LinkedList<>(); for (Protos.Resource resource : offer.getResourcesList()) { if (resource.hasRole() && !resource.getRole().equals("*")) { reservedResources.add(resource); } } String role = "*"; for (Protos.Resource resource : reservedResources) { if (resource.getName() == name && resource.getScalar().getValue() >= value) { role = resource.getRole(); break; } } return Protos.Resource.newBuilder() .setName(name) .setType(Protos.Value.Type.SCALAR) .setScalar(Protos.Value.Scalar.newBuilder().setValue(value)) .setRole(role) .build(); } protected Protos.Resource rangeResource(String name, long begin, long end) { // For a given named resource and value, // find and return the role that matches the name and exceeds the value. // Give preference to reserved offers first (those whose roles do not match "*") List<Protos.Resource> reservedResources = new LinkedList<>(); for (Protos.Resource resource : offer.getResourcesList()) { if (resource.hasRole() && !resource.getRole().equals("*")) { reservedResources.add(resource); } } String role = "*"; for (Protos.Resource resource : reservedResources) { if (resource.getName() == name) { Protos.Value.Ranges ranges = resource.getRanges(); for (Protos.Value.Range range : ranges.getRangeList()) { if (range.getBegin() <= begin && range.getEnd() >= end) { role = resource.getRole(); break; } } } } return Protos.Resource.newBuilder() .setType(Protos.Value.Type.RANGES) .setName(name) .setRanges(Protos.Value.Ranges.newBuilder() .addRange(Protos.Value.Range.newBuilder() .setBegin(begin) .setEnd(end) ).build() ) .setRole(role) .build(); } protected Protos.Environment environment(Map<String, String> var) { Protos.Environment.Builder builder = Protos.Environment.newBuilder(); for (Map.Entry<String, String> kv : var.entrySet()) { String key = kv.getKey(); String value = kv.getValue(); Protos.Environment.Variable variable = Protos.Environment.Variable.newBuilder().setName(key).setValue(value).build(); builder.addVariables(variable); } return builder.build(); } /** * Construct the Mesos TaskInfo in Protos to launch basing on the LaunchableTask * * @param heronConfig the heron config * @param heronRuntime the heron runtime * @return Mesos TaskInfo in Protos to launch */ public Protos.TaskInfo constructMesosTaskInfo(Config heronConfig, Config heronRuntime) { //String taskIdStr, BaseContainer task, Offer offer String taskIdStr = this.taskId; Protos.TaskID mesosTaskID = Protos.TaskID.newBuilder().setValue(taskIdStr).build(); Protos.TaskInfo.Builder taskInfo = Protos.TaskInfo.newBuilder() .setName(baseContainer.name) .setTaskId(mesosTaskID); Protos.Environment.Builder environment = Protos.Environment.newBuilder(); // If the job defines custom environment variables, add them to the builder // Don't add them if they already exist to prevent overwriting the defaults Set<String> builtinEnvNames = new HashSet<>(); for (Protos.Environment.Variable variable : environment.getVariablesList()) { builtinEnvNames.add(variable.getName()); } for (BaseContainer.EnvironmentVariable ev : baseContainer.environmentVariables) { environment.addVariables( Protos.Environment.Variable.newBuilder().setName(ev.name).setValue(ev.value)); } taskInfo .addResources(scalarResource(TaskResources.CPUS_RESOURCE_NAME, baseContainer.cpu)) .addResources(scalarResource(TaskResources.MEM_RESOURCE_NAME, baseContainer.memInMB)) .addResources(scalarResource(TaskResources.DISK_RESOURCE_NAME, baseContainer.diskInMB)) .addResources(rangeResource(TaskResources.PORT_RESOURCE_NAME, this.freePorts.get(0), this.freePorts.get(this.freePorts.size() - 1))). setSlaveId(this.offer.getSlaveId()); int containerIndex = TaskUtils.getContainerIndexForTaskId(taskIdStr); String commandStr = executorCommand(heronConfig, heronRuntime, containerIndex); Protos.CommandInfo.Builder command = Protos.CommandInfo.newBuilder(); List<Protos.CommandInfo.URI> uriProtos = new ArrayList<>(); for (String uri : baseContainer.dependencies) { uriProtos.add(Protos.CommandInfo.URI.newBuilder() .setValue(uri) .setExtract(true) .build()); } command.setValue(commandStr) .setShell(baseContainer.shell) .setEnvironment(environment) .addAllUris(uriProtos); if (!baseContainer.runAsUser.isEmpty()) { command.setUser(baseContainer.runAsUser); } taskInfo.setCommand(command); return taskInfo.build(); } protected String join(String[] array, String delimiter) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < array.length - 1; i++) { sb.append(array[i]); sb.append(delimiter); } sb.append(array[array.length - 1]); return sb.toString(); } protected String executorCommand( Config config, Config runtime, int containerIndex) { String[] executorCmd = SchedulerUtils.executorCommand(config, runtime, containerIndex, freePorts); return join(executorCmd, " "); } }