/**
* 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;
}
}