/** * 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.mesos; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import javax.inject.Singleton; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.inject.AbstractModule; import org.apache.aurora.common.args.Arg; import org.apache.aurora.common.args.CmdLine; import org.apache.aurora.common.args.constraints.NotNull; import org.apache.aurora.common.quantity.Amount; import org.apache.aurora.common.quantity.Time; import org.apache.mesos.v1.Protos; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.mesos.v1.Protos.FrameworkInfo; import static org.apache.mesos.v1.Protos.FrameworkInfo.Capability; import static org.apache.mesos.v1.Protos.FrameworkInfo.Capability.Type.GPU_RESOURCES; import static org.apache.mesos.v1.Protos.FrameworkInfo.Capability.Type.REVOCABLE_RESOURCES; /** * Creates and binds {@link DriverSettings} based on values found on the command line. */ public class CommandLineDriverSettingsModule extends AbstractModule { private static final Logger LOG = LoggerFactory.getLogger(CommandLineDriverSettingsModule.class); @NotNull @CmdLine(name = "mesos_master_address", help = "Address for the mesos master, can be a socket address or zookeeper path.") private static final Arg<String> MESOS_MASTER_ADDRESS = Arg.create(); @VisibleForTesting static final String PRINCIPAL_KEY = "aurora_authentication_principal"; @VisibleForTesting static final String SECRET_KEY = "aurora_authentication_secret"; @CmdLine(name = "framework_authentication_file", help = "Properties file which contains framework credentials to authenticate with Mesos" + "master. Must contain the properties '" + PRINCIPAL_KEY + "' and " + "'" + SECRET_KEY + "'.") private static final Arg<File> FRAMEWORK_AUTHENTICATION_FILE = Arg.create(); @CmdLine(name = "framework_failover_timeout", help = "Time after which a framework is considered deleted. SHOULD BE VERY HIGH.") private static final Arg<Amount<Long, Time>> FRAMEWORK_FAILOVER_TIMEOUT = Arg.create(Amount.of(21L, Time.DAYS)); @CmdLine(name = "framework_announce_principal", help = "When 'framework_authentication_file' flag is set, the FrameworkInfo " + "registered with the mesos master will also contain the principal. This is " + "necessary if you intend to use mesos authorization via mesos ACLs. " + "The default will change in a future release. Changing this value is backwards " + "incompatible. For details, see MESOS-703.") private static final Arg<Boolean> FRAMEWORK_ANNOUNCE_PRINCIPAL = Arg.create(false); @CmdLine(name = "framework_name", help = "Name used to register the Aurora framework with Mesos.") private static final Arg<String> FRAMEWORK_NAME = Arg.create("Aurora"); @CmdLine(name = "executor_user", help = "User to start the executor. Defaults to \"root\". " + "Set this to an unprivileged user if the mesos master was started with " + "\"--no-root_submissions\". If set to anything other than \"root\", the executor " + "will ignore the \"role\" setting for jobs since it can't use setuid() anymore. " + "This means that all your jobs will run under the specified user and the user has " + "to exist on the Mesos agents.") private static final Arg<String> EXECUTOR_USER = Arg.create("root"); @CmdLine(name = "receive_revocable_resources", help = "Allows receiving revocable resource offers from Mesos.") private static final Arg<Boolean> RECEIVE_REVOCABLE_RESOURCES = Arg.create(false); @CmdLine(name = "mesos_role", help = "The Mesos role this framework will register as. The default is to left this empty, " + "and the framework will register without any role and only receive unreserved " + "resources in offer.") private static final Arg<String> MESOS_ROLE = Arg.create(); private final boolean allowGpuResource; public CommandLineDriverSettingsModule(boolean allowGpuResource) { this.allowGpuResource = allowGpuResource; } @Override protected void configure() { Optional<Protos.Credential> credentials = getCredentials(); Optional<String> principal = Optional.absent(); if (FRAMEWORK_ANNOUNCE_PRINCIPAL.get() && credentials.isPresent()) { principal = Optional.of(credentials.get().getPrincipal()); } Optional<String> role = MESOS_ROLE.hasAppliedValue() ? Optional.of(MESOS_ROLE.get()) : Optional.absent(); DriverSettings settings = new DriverSettings( MESOS_MASTER_ADDRESS.get(), credentials); bind(DriverSettings.class).toInstance(settings); FrameworkInfo base = buildFrameworkInfo( FRAMEWORK_NAME.get(), EXECUTOR_USER.get(), principal, FRAMEWORK_FAILOVER_TIMEOUT.get(), RECEIVE_REVOCABLE_RESOURCES.get(), allowGpuResource, role); bind(FrameworkInfo.class) .annotatedWith(FrameworkInfoFactory.FrameworkInfoFactoryImpl.BaseFrameworkInfo.class) .toInstance(base); bind(FrameworkInfoFactory.class).to(FrameworkInfoFactory.FrameworkInfoFactoryImpl.class); bind(FrameworkInfoFactory.FrameworkInfoFactoryImpl.class).in(Singleton.class); } private static Optional<Protos.Credential> getCredentials() { if (FRAMEWORK_AUTHENTICATION_FILE.hasAppliedValue()) { Properties properties; try { properties = parseCredentials(new FileInputStream(FRAMEWORK_AUTHENTICATION_FILE.get())); } catch (FileNotFoundException e) { LOG.error("Authentication File not Found"); throw new RuntimeException(e); } LOG.info( "Connecting to master using authentication (principal: {}).", properties.get(PRINCIPAL_KEY)); return Optional.of(Protos.Credential.newBuilder() .setPrincipal(properties.getProperty(PRINCIPAL_KEY)) .setSecret(properties.getProperty(SECRET_KEY)) .build()); } else { return Optional.absent(); } } @VisibleForTesting // See: https://github.com/apache/mesos/commit/d06d05c76eca13745ca73039b93ad684b9d07196 // The role field has been deprecated but the replacement is not ready. We'll also have to // turn on MULTI_ROLES capability before we can use the roles field. @SuppressWarnings("deprecation") static FrameworkInfo buildFrameworkInfo( String frameworkName, String executorUser, Optional<String> principal, Amount<Long, Time> failoverTimeout, boolean revocable, boolean allowGpu, Optional<String> role) { FrameworkInfo.Builder infoBuilder = FrameworkInfo.newBuilder() .setName(frameworkName) .setUser(executorUser) // Require slave checkpointing. Assumes slaves have '--checkpoint=true' arg set. .setCheckpoint(true) .setFailoverTimeout(failoverTimeout.as(Time.SECONDS)); if (principal.isPresent()) { infoBuilder.setPrincipal(principal.get()); } if (revocable) { infoBuilder.addCapabilities(Capability.newBuilder().setType(REVOCABLE_RESOURCES)); } if (allowGpu) { infoBuilder.addCapabilities(Capability.newBuilder().setType(GPU_RESOURCES)); } if (role.isPresent()) { infoBuilder.setRole(role.get()); } return infoBuilder.build(); } @VisibleForTesting static Properties parseCredentials(InputStream credentialsStream) { Properties properties = new Properties(); try { properties.load(credentialsStream); } catch (IOException e) { LOG.error("Unable to load authentication file"); throw new RuntimeException(e); } Preconditions.checkState(properties.containsKey(PRINCIPAL_KEY), "The framework authentication file is missing the key: %s", PRINCIPAL_KEY); Preconditions.checkState(properties.containsKey(SECRET_KEY), "The framework authentication file is missing the key: %s", SECRET_KEY); return properties; } }