/** * 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.apache.aurora.scheduler.configuration.executor; import java.io.IOException; import java.util.List; import java.util.Map; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.io.CharStreams; import com.hubspot.jackson.datatype.protobuf.ProtobufModule; import org.apache.aurora.GuavaUtils; import org.apache.mesos.v1.Protos.ExecutorID; import org.apache.mesos.v1.Protos.ExecutorInfo; import org.apache.mesos.v1.Protos.Volume; import static com.fasterxml.jackson.databind.PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES; /** * A utility class to read JSON-formatted executor configurations. */ public final class ExecutorSettingsLoader { public static final ExecutorID PLACEHOLDER_EXECUTOR_ID = ExecutorID.newBuilder() .setValue("PLACEHOLDER") .build(); private ExecutorSettingsLoader() { // Utility class } /** * Thrown when an executor configuration could not be read. */ public static class ExecutorConfigException extends Exception { public ExecutorConfigException(Throwable cause) { super(cause); } } /** * Reads an executor configuration from a JSON-encoded source. * * @param input The configuration data source. * @return A map of executor configurations. * @throws ExecutorConfigException If the input cannot be read or is not properly formatted. */ public static Map<String, ExecutorConfig> read(Readable input) throws ExecutorConfigException { String configContents; try { configContents = CharStreams.toString(input); } catch (IOException e) { throw new ExecutorConfigException(e); } ObjectMapper mapper = new ObjectMapper() .registerModule(new ProtobufModule()) .setPropertyNamingStrategy(CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); List<Schema> parsed; try { parsed = mapper.readValue(configContents, new TypeReference<List<Schema>>() { }); } catch (IOException e) { throw new ExecutorConfigException(e); } Map<String, ExecutorConfig> customExecutors; try { // We apply a placeholder value for the executor ID so that we can construct and validate // the protobuf schema. This allows us to catch many validation errors here rather than // later on when launching tasks. customExecutors = parsed.stream().collect( GuavaUtils.toImmutableMap( m -> m.executor.getName(), m -> new ExecutorConfig( m.executor.setExecutorId(PLACEHOLDER_EXECUTOR_ID).build(), Optional.fromNullable(m.volumeMounts).or(ImmutableList.of()), m.taskPrefix))); } catch (RuntimeException e) { throw new ExecutorConfigException(e); } return customExecutors; } /** * The JSON schema. This is separated from the public {@link ExecutorConfig} so we can read * objects that do not have all fields required by the protobuf set in the JSON config. */ private static class Schema { private ExecutorInfo.Builder executor; private List<Volume> volumeMounts; private String taskPrefix; ExecutorInfo.Builder getExecutor() { return executor; } void setExecutor(ExecutorInfo.Builder executor) { this.executor = executor; } List<Volume> getVolumeMounts() { return volumeMounts; } void setVolumeMounts(List<Volume> volumeMounts) { this.volumeMounts = volumeMounts; } String getTaskPrefix() { return taskPrefix; } void setTaskPrefix(String taskPrefix) { this.taskPrefix = taskPrefix; } } }