/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.flink.mesos.runtime.clusterframework; import com.netflix.fenzo.ConstraintEvaluator; import com.netflix.fenzo.functions.Func1; import com.netflix.fenzo.plugins.HostAttrValueConstraint; import org.apache.flink.configuration.ConfigConstants; import org.apache.flink.configuration.ConfigOption; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.IllegalConfigurationException; import org.apache.flink.runtime.clusterframework.ContaineredTaskManagerParameters; import org.apache.flink.util.Preconditions; import org.apache.mesos.Protos; import scala.Option; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.regex.Pattern; import static org.apache.flink.configuration.ConfigOptions.key; /** * This class describes the Mesos-specific parameters for launching a TaskManager process. * * These parameters are in addition to the common parameters * provided by {@link ContaineredTaskManagerParameters}. */ public class MesosTaskManagerParameters { /** Pattern replaced in the {@link #MESOS_TM_HOSTNAME} by the actual task id of the Mesos task */ public static final Pattern TASK_ID_PATTERN = Pattern.compile("_TASK_", Pattern.LITERAL); public static final ConfigOption<Integer> MESOS_RM_TASKS_SLOTS = key(ConfigConstants.TASK_MANAGER_NUM_TASK_SLOTS) .defaultValue(1); public static final ConfigOption<Integer> MESOS_RM_TASKS_MEMORY_MB = key("mesos.resourcemanager.tasks.mem") .defaultValue(1024); public static final ConfigOption<Double> MESOS_RM_TASKS_CPUS = key("mesos.resourcemanager.tasks.cpus") .defaultValue(0.0); public static final ConfigOption<String> MESOS_RM_CONTAINER_TYPE = key("mesos.resourcemanager.tasks.container.type") .defaultValue("mesos"); public static final ConfigOption<String> MESOS_RM_CONTAINER_IMAGE_NAME = key("mesos.resourcemanager.tasks.container.image.name") .noDefaultValue(); public static final ConfigOption<String> MESOS_TM_HOSTNAME = key("mesos.resourcemanager.tasks.hostname") .noDefaultValue(); public static final ConfigOption<String> MESOS_TM_BOOTSTRAP_CMD = key("mesos.resourcemanager.tasks.bootstrap-cmd") .noDefaultValue(); public static final ConfigOption<String> MESOS_RM_CONTAINER_VOLUMES = key("mesos.resourcemanager.tasks.container.volumes") .noDefaultValue(); public static final ConfigOption<String> MESOS_CONSTRAINTS_HARD_HOSTATTR = key("mesos.constraints.hard.hostattribute") .noDefaultValue(); /** * Value for {@code MESOS_RESOURCEMANAGER_TASKS_CONTAINER_TYPE} setting. Tells to use the Mesos containerizer. */ public static final String MESOS_RESOURCEMANAGER_TASKS_CONTAINER_TYPE_MESOS = "mesos"; /** * Value for {@code MESOS_RESOURCEMANAGER_TASKS_CONTAINER_TYPE} setting. Tells to use the Docker containerizer. */ public static final String MESOS_RESOURCEMANAGER_TASKS_CONTAINER_TYPE_DOCKER = "docker"; private final double cpus; private final ContainerType containerType; private final Option<String> containerImageName; private final ContaineredTaskManagerParameters containeredParameters; private final List<Protos.Volume> containerVolumes; private final List<ConstraintEvaluator> constraints; private final Option<String> bootstrapCommand; private final Option<String> taskManagerHostname; public MesosTaskManagerParameters( double cpus, ContainerType containerType, Option<String> containerImageName, ContaineredTaskManagerParameters containeredParameters, List<Protos.Volume> containerVolumes, List<ConstraintEvaluator> constraints, Option<String> bootstrapCommand, Option<String> taskManagerHostname) { this.cpus = cpus; this.containerType = Preconditions.checkNotNull(containerType); this.containerImageName = Preconditions.checkNotNull(containerImageName); this.containeredParameters = Preconditions.checkNotNull(containeredParameters); this.containerVolumes = Preconditions.checkNotNull(containerVolumes); this.constraints = Preconditions.checkNotNull(constraints); this.bootstrapCommand = Preconditions.checkNotNull(bootstrapCommand); this.taskManagerHostname = Preconditions.checkNotNull(taskManagerHostname); } /** * Get the CPU units to use for the TaskManager process. */ public double cpus() { return cpus; } /** * Get the container type (Mesos or Docker). The default is Mesos. * * Mesos provides a facility for a framework to specify which containerizer to use. */ public ContainerType containerType() { return containerType; } /** * Get the container image name. */ public Option<String> containerImageName() { return containerImageName; } /** * Get the common containered parameters. */ public ContaineredTaskManagerParameters containeredParameters() { return containeredParameters; } /** * Get the container volumes string */ public List<Protos.Volume> containerVolumes() { return containerVolumes; } /** * Get the placement constraints */ public List<ConstraintEvaluator> constraints() { return constraints; } /** * Get the taskManager hostname. */ public Option<String> getTaskManagerHostname() { return taskManagerHostname; } /** * Get the bootstrap command. */ public Option<String> bootstrapCommand() { return bootstrapCommand; } @Override public String toString() { return "MesosTaskManagerParameters{" + "cpus=" + cpus + ", containerType=" + containerType + ", containerImageName=" + containerImageName + ", containeredParameters=" + containeredParameters + ", containerVolumes=" + containerVolumes + ", constraints=" + constraints + ", taskManagerHostName=" + taskManagerHostname + ", bootstrapCommand=" + bootstrapCommand + '}'; } /** * Create the Mesos TaskManager parameters. * @param flinkConfig the TM configuration. */ public static MesosTaskManagerParameters create(Configuration flinkConfig) { List<ConstraintEvaluator> constraints = parseConstraints(flinkConfig.getString(MESOS_CONSTRAINTS_HARD_HOSTATTR)); // parse the common parameters ContaineredTaskManagerParameters containeredParameters = ContaineredTaskManagerParameters.create( flinkConfig, flinkConfig.getInteger(MESOS_RM_TASKS_MEMORY_MB), flinkConfig.getInteger(MESOS_RM_TASKS_SLOTS)); double cpus = flinkConfig.getDouble(MESOS_RM_TASKS_CPUS); if(cpus <= 0.0) { cpus = Math.max(containeredParameters.numSlots(), 1.0); } // parse the containerization parameters String imageName = flinkConfig.getString(MESOS_RM_CONTAINER_IMAGE_NAME); ContainerType containerType; String containerTypeString = flinkConfig.getString(MESOS_RM_CONTAINER_TYPE); switch(containerTypeString) { case MESOS_RESOURCEMANAGER_TASKS_CONTAINER_TYPE_MESOS: containerType = ContainerType.MESOS; break; case MESOS_RESOURCEMANAGER_TASKS_CONTAINER_TYPE_DOCKER: containerType = ContainerType.DOCKER; if(imageName == null || imageName.length() == 0) { throw new IllegalConfigurationException(MESOS_RM_CONTAINER_IMAGE_NAME.key() + " must be specified for docker container type"); } break; default: throw new IllegalConfigurationException("invalid container type: " + containerTypeString); } Option<String> containerVolOpt = Option.<String>apply(flinkConfig.getString(MESOS_RM_CONTAINER_VOLUMES)); List<Protos.Volume> containerVolumes = buildVolumes(containerVolOpt); //obtain Task Manager Host Name from the configuration Option<String> taskManagerHostname = Option.apply(flinkConfig.getString(MESOS_TM_HOSTNAME)); //obtain bootstrap command from the configuration Option<String> tmBootstrapCommand = Option.apply(flinkConfig.getString(MESOS_TM_BOOTSTRAP_CMD)); return new MesosTaskManagerParameters( cpus, containerType, Option.apply(imageName), containeredParameters, containerVolumes, constraints, tmBootstrapCommand, taskManagerHostname); } private static List<ConstraintEvaluator> parseConstraints(String mesosConstraints) { if (mesosConstraints == null || mesosConstraints.isEmpty()) { return Collections.emptyList(); } else { List<ConstraintEvaluator> constraints = new ArrayList<>(); for (String constraint : mesosConstraints.split(",")) { if (constraint.isEmpty()) { continue; } final String[] constraintList = constraint.split(":"); if (constraintList.length != 2) { continue; } addHostAttrValueConstraint(constraints, constraintList[0], constraintList[1]); } return constraints; } } private static void addHostAttrValueConstraint(List<ConstraintEvaluator> constraints, String constraintKey, final String constraintValue) { constraints.add(new HostAttrValueConstraint(constraintKey, new Func1<String, String>() { @Override public String call(String s) { return constraintValue; } })); } /** * Used to build volume specs for mesos. This allows for mounting additional volumes into a container * * @param containerVolumes a comma delimited optional string of [host_path:]container_path[:RO|RW] that * defines mount points for a container volume. If None or empty string, returns * an empty iterator */ public static List<Protos.Volume> buildVolumes(Option<String> containerVolumes) { if (containerVolumes.isEmpty()) { return Collections.emptyList(); } else { String[] volumeSpecifications = containerVolumes.get().split(","); List<Protos.Volume> volumes = new ArrayList<>(volumeSpecifications.length); for (String volumeSpecification : volumeSpecifications) { if (!volumeSpecification.trim().isEmpty()) { Protos.Volume.Builder volume = Protos.Volume.newBuilder(); volume.setMode(Protos.Volume.Mode.RW); String[] parts = volumeSpecification.split(":"); switch (parts.length) { case 1: volume.setContainerPath(parts[0]); break; case 2: try { Protos.Volume.Mode mode = Protos.Volume.Mode.valueOf(parts[1].trim().toUpperCase()); volume.setMode(mode) .setContainerPath(parts[0]); } catch (IllegalArgumentException e) { volume.setHostPath(parts[0]) .setContainerPath(parts[1]); } break; case 3: Protos.Volume.Mode mode = Protos.Volume.Mode.valueOf(parts[2].trim().toUpperCase()); volume.setMode(mode) .setHostPath(parts[0]) .setContainerPath(parts[1]); break; default: throw new IllegalArgumentException("volume specification is invalid, given: " + volumeSpecification); } volumes.add(volume.build()); } } return volumes; } } public enum ContainerType { MESOS, DOCKER } }