aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From wfar...@apache.org
Subject [4/8] aurora git commit: Use a simpler command line argument system
Date Wed, 11 Oct 2017 00:29:13 GMT
http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/app/VolumeConverter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/app/VolumeConverter.java b/src/main/java/org/apache/aurora/scheduler/app/VolumeConverter.java
new file mode 100644
index 0000000..d8af1f9
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/app/VolumeConverter.java
@@ -0,0 +1,54 @@
+/**
+ * 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.app;
+
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.converters.BaseConverter;
+import com.google.common.base.Joiner;
+
+import org.apache.aurora.gen.Mode;
+import org.apache.aurora.gen.Volume;
+
+/**
+ * Converter to transform a string in host:container:mode form to a VolumeConfig object.
+ */
+public class VolumeConverter extends BaseConverter<Volume> {
+
+  public VolumeConverter() {
+    super("");
+  }
+
+  public VolumeConverter(String optionName) {
+    super(optionName);
+  }
+
+  @Override
+  public Volume convert(String raw) {
+    String[] split = raw.split(":");
+    if (split.length != 3) {
+      throw new ParameterException(
+          getErrorString(raw, "must be in the format of 'host:container:mode'"));
+    }
+
+    Mode mode;
+    try {
+      mode = Mode.valueOf(split[2].toUpperCase());
+    } catch (IllegalArgumentException e) {
+      throw new ParameterException(
+          getErrorString(raw, "Read/Write spec must be in " + Joiner.on(", ").join(Mode.values())),
+          e);
+    }
+    return new Volume(split[1], split[0], mode);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/app/VolumeParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/app/VolumeParser.java b/src/main/java/org/apache/aurora/scheduler/app/VolumeParser.java
deleted file mode 100644
index c1e99ce..0000000
--- a/src/main/java/org/apache/aurora/scheduler/app/VolumeParser.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * 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.app;
-
-import com.google.common.base.Joiner;
-
-import org.apache.aurora.common.args.ArgParser;
-import org.apache.aurora.common.args.parsers.NonParameterizedTypeParser;
-import org.apache.aurora.gen.Mode;
-import org.apache.aurora.gen.Volume;
-
-/**
- * Parser to transform a string in host:container:mode form to a VolumeConfig
- * object.
- */
-@ArgParser
-public class VolumeParser extends NonParameterizedTypeParser<Volume> {
-  @Override
-  public Volume doParse(String raw) throws IllegalArgumentException {
-    String[] split = raw.split(":");
-    if (split.length != 3) {
-      throw new IllegalArgumentException("Illegal mount string " + raw + ". "
-        + "Mounts must be in the format of 'host:container:mode'");
-    }
-
-    Mode mode;
-    try {
-      mode = Mode.valueOf(split[2].toUpperCase());
-    } catch (IllegalArgumentException e) {
-      throw new IllegalArgumentException("Illegal mount string " + raw + ". "
-        + "Read/Write spec must be in " + Joiner.on(", ").join(Mode.values()), e);
-    }
-    return new Volume(split[1], split[0], mode);
-  }
-}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/async/AsyncModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/async/AsyncModule.java b/src/main/java/org/apache/aurora/scheduler/async/AsyncModule.java
index da07df6..68f7ddb 100644
--- a/src/main/java/org/apache/aurora/scheduler/async/AsyncModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/async/AsyncModule.java
@@ -23,13 +23,13 @@ import javax.inject.Inject;
 import javax.inject.Qualifier;
 import javax.inject.Singleton;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.util.concurrent.AbstractIdleService;
 import com.google.inject.AbstractModule;
 import com.google.inject.PrivateModule;
 
-import org.apache.aurora.common.args.Arg;
-import org.apache.aurora.common.args.CmdLine;
 import org.apache.aurora.common.stats.StatsProvider;
 import org.apache.aurora.scheduler.SchedulerServicesModule;
 import org.apache.aurora.scheduler.base.AsyncUtil;
@@ -48,19 +48,26 @@ import static java.util.Objects.requireNonNull;
 public class AsyncModule extends AbstractModule {
   private static final Logger LOG = LoggerFactory.getLogger(AsyncModule.class);
 
-  @CmdLine(name = "async_worker_threads",
-      help = "The number of worker threads to process async task operations with.")
-  private static final Arg<Integer> ASYNC_WORKER_THREADS = Arg.create(8);
+  @Parameters(separators = "=")
+  public static class Options {
+    @Parameter(names = "-async_worker_threads",
+        description = "The number of worker threads to process async task operations with.")
+    public int asyncWorkerThreads = 8;
+  }
+
   private final ScheduledThreadPoolExecutor afterTransaction;
 
   @Qualifier
   @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
   public @interface AsyncExecutor { }
 
-  public AsyncModule() {
+  public AsyncModule(Options options) {
     // Don't worry about clean shutdown, these can be daemon and cleanup-free.
     // TODO(wfarner): Should we use a bounded caching thread pool executor instead?
-    this(AsyncUtil.loggingScheduledExecutor(ASYNC_WORKER_THREADS.get(), "AsyncProcessor-%d", LOG));
+    this(AsyncUtil.loggingScheduledExecutor(
+        options.asyncWorkerThreads,
+        "AsyncProcessor-%d",
+        LOG));
   }
 
   @VisibleForTesting

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java b/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java
index 186fa1b..60bbe39 100644
--- a/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java
+++ b/src/main/java/org/apache/aurora/scheduler/base/TaskTestUtil.java
@@ -19,7 +19,6 @@ import java.util.Set;
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
 
 import org.apache.aurora.gen.AssignedTask;
@@ -83,7 +82,7 @@ public final class TaskTestUtil {
       new ConfigurationManagerSettings(
           ImmutableSet.of(_Fields.MESOS),
           false,
-          ImmutableMultimap.of(),
+          ImmutableList.of(),
           true,
           true,
           true,

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/CliOptions.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/CliOptions.java b/src/main/java/org/apache/aurora/scheduler/config/CliOptions.java
new file mode 100644
index 0000000..64733e5
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/CliOptions.java
@@ -0,0 +1,114 @@
+/**
+ * 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.config;
+
+import java.util.List;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+
+import org.apache.aurora.scheduler.SchedulerModule;
+import org.apache.aurora.scheduler.TierModule;
+import org.apache.aurora.scheduler.app.AppModule;
+import org.apache.aurora.scheduler.app.SchedulerMain;
+import org.apache.aurora.scheduler.async.AsyncModule;
+import org.apache.aurora.scheduler.configuration.executor.ExecutorModule;
+import org.apache.aurora.scheduler.cron.quartz.CronModule;
+import org.apache.aurora.scheduler.discovery.FlaggedZooKeeperConfig;
+import org.apache.aurora.scheduler.events.WebhookModule;
+import org.apache.aurora.scheduler.http.H2ConsoleModule;
+import org.apache.aurora.scheduler.http.JettyServerModule;
+import org.apache.aurora.scheduler.http.api.ApiModule;
+import org.apache.aurora.scheduler.http.api.security.HttpSecurityModule;
+import org.apache.aurora.scheduler.http.api.security.IniShiroRealmModule;
+import org.apache.aurora.scheduler.http.api.security.Kerberos5ShiroRealmModule;
+import org.apache.aurora.scheduler.log.mesos.MesosLogStreamModule;
+import org.apache.aurora.scheduler.mesos.CommandLineDriverSettingsModule;
+import org.apache.aurora.scheduler.offers.OffersModule;
+import org.apache.aurora.scheduler.preemptor.PreemptorModule;
+import org.apache.aurora.scheduler.pruning.PruningModule;
+import org.apache.aurora.scheduler.reconciliation.ReconciliationModule;
+import org.apache.aurora.scheduler.resources.ResourceSettings;
+import org.apache.aurora.scheduler.scheduling.SchedulingModule;
+import org.apache.aurora.scheduler.sla.SlaModule;
+import org.apache.aurora.scheduler.state.StateModule;
+import org.apache.aurora.scheduler.stats.AsyncStatsModule;
+import org.apache.aurora.scheduler.stats.StatsModule;
+import org.apache.aurora.scheduler.storage.backup.BackupModule;
+import org.apache.aurora.scheduler.storage.db.DbModule;
+import org.apache.aurora.scheduler.storage.log.LogStorageModule;
+import org.apache.aurora.scheduler.thrift.aop.AopModule;
+import org.apache.aurora.scheduler.updater.UpdaterModule;
+
+public class CliOptions {
+  public final ReconciliationModule.Options reconciliation =
+      new ReconciliationModule.Options();
+  public final OffersModule.Options offer = new OffersModule.Options();
+  public final ExecutorModule.Options executor = new ExecutorModule.Options();
+  public final AppModule.Options app = new AppModule.Options();
+  public final SchedulerMain.Options main = new SchedulerMain.Options();
+  public final SchedulingModule.Options scheduling = new SchedulingModule.Options();
+  public final AsyncModule.Options async = new AsyncModule.Options();
+  public final FlaggedZooKeeperConfig.Options zk = new FlaggedZooKeeperConfig.Options();
+  public final UpdaterModule.Options updater = new UpdaterModule.Options();
+  public final StateModule.Options state = new StateModule.Options();
+  public final DbModule.Options db = new DbModule.Options();
+  public final LogStorageModule.Options logStorage = new LogStorageModule.Options();
+  public final BackupModule.Options backup = new BackupModule.Options();
+  public final AopModule.Options aop = new AopModule.Options();
+  public final PruningModule.Options pruning = new PruningModule.Options();
+  public final CommandLineDriverSettingsModule.Options driver =
+      new CommandLineDriverSettingsModule.Options();
+  public final JettyServerModule.Options jetty = new JettyServerModule.Options();
+  public final HttpSecurityModule.Options httpSecurity = new HttpSecurityModule.Options();
+  public final Kerberos5ShiroRealmModule.Options kerberos = new Kerberos5ShiroRealmModule.Options();
+  public final IniShiroRealmModule.Options iniShiroRealm = new IniShiroRealmModule.Options();
+  public final ApiModule.Options api = new ApiModule.Options();
+  public final H2ConsoleModule.Options h2Console = new H2ConsoleModule.Options();
+  public final PreemptorModule.Options preemptor = new PreemptorModule.Options();
+  public final MesosLogStreamModule.Options mesosLog = new MesosLogStreamModule.Options();
+  public final SlaModule.Options sla = new SlaModule.Options();
+  public final WebhookModule.Options webhook = new WebhookModule.Options();
+  public final SchedulerModule.Options scheduler = new SchedulerModule.Options();
+  public final TierModule.Options tiers = new TierModule.Options();
+  public final AsyncStatsModule.Options asyncStats = new AsyncStatsModule.Options();
+  public final StatsModule.Options stats = new StatsModule.Options();
+  public final CronModule.Options cron = new CronModule.Options();
+  public final ResourceSettings resourceSettings = new ResourceSettings();
+  final List<Object> custom;
+
+  public CliOptions() {
+    this(ImmutableList.of());
+  }
+
+  public CliOptions(List<Object> custom) {
+    this.custom = custom;
+  }
+
+  /**
+   * Gets a custom options object of a particular type.
+   *
+   * @param customOptionType Custom option type class.
+   * @param <T> Custom option type.
+   * @return The matching custom option object.
+   */
+  @SuppressWarnings("unchecked")
+  public <T> T getCustom(Class<T> customOptionType) {
+    return (T) FluentIterable.from(custom)
+        .firstMatch(Predicates.instanceOf(customOptionType))
+        .get();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/CommandLine.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/CommandLine.java b/src/main/java/org/apache/aurora/scheduler/config/CommandLine.java
new file mode 100644
index 0000000..2085810
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/CommandLine.java
@@ -0,0 +1,198 @@
+/**
+ * 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.config;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.kerberos.KerberosPrincipal;
+
+import com.beust.jcommander.IStringConverter;
+import com.beust.jcommander.IStringConverterFactory;
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.ParameterException;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+
+import org.apache.aurora.gen.DockerParameter;
+import org.apache.aurora.gen.Volume;
+import org.apache.aurora.scheduler.app.SchedulerMain;
+import org.apache.aurora.scheduler.app.VolumeConverter;
+import org.apache.aurora.scheduler.config.converters.ClassConverter;
+import org.apache.aurora.scheduler.config.converters.DataAmountConverter;
+import org.apache.aurora.scheduler.config.converters.DockerParameterConverter;
+import org.apache.aurora.scheduler.config.converters.InetSocketAddressConverter;
+import org.apache.aurora.scheduler.config.converters.TimeAmountConverter;
+import org.apache.aurora.scheduler.config.types.DataAmount;
+import org.apache.aurora.scheduler.config.types.TimeAmount;
+import org.apache.aurora.scheduler.http.api.security.KerberosPrincipalConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Parses command line options and populates {@link CliOptions}.
+ */
+public final class CommandLine {
+
+  private static final Logger LOG = LoggerFactory.getLogger(CommandLine.class);
+
+  // TODO(wfarner): This can go away if/when options are no longer accessed statically.
+  private static CliOptions instance = null;
+
+  private static List<Object> customOptions = Lists.newArrayList();
+
+  private CommandLine() {
+    // Utility class.
+  }
+
+  /**
+   * Similar to {@link #initializeForTest()}, but resets the class to an un-parsed state.
+   */
+  @VisibleForTesting
+  static void clearForTest() {
+    instance = null;
+    customOptions = Lists.newArrayList();
+  }
+
+  /**
+   * Initializes static command line state - the static parsed instance, and custom options objects.
+   */
+  @VisibleForTesting
+  public static void initializeForTest() {
+    instance = new CliOptions();
+    customOptions = Lists.newArrayList();
+  }
+
+  private static JCommander prepareParser(CliOptions options) {
+    JCommander.Builder builder = JCommander.newBuilder()
+        .programName(SchedulerMain.class.getName());
+
+    builder.addConverterFactory(new IStringConverterFactory() {
+      private Map<Class<?>, Class<? extends IStringConverter<?>>> classConverters =
+          ImmutableMap.<Class<?>, Class<? extends IStringConverter<?>>>builder()
+              .put(Class.class, ClassConverter.class)
+              .put(DataAmount.class, DataAmountConverter.class)
+              .put(DockerParameter.class, DockerParameterConverter.class)
+              .put(InetSocketAddress.class, InetSocketAddressConverter.class)
+              .put(KerberosPrincipal.class, KerberosPrincipalConverter.class)
+              .put(TimeAmount.class, TimeAmountConverter.class)
+              .put(Volume.class, VolumeConverter.class)
+              .build();
+
+      @SuppressWarnings("unchecked")
+      @Override
+      public <T> Class<? extends IStringConverter<T>> getConverter(Class<T> forType) {
+        return (Class<IStringConverter<T>>) classConverters.get(forType);
+      }
+    });
+
+    builder.addObject(getOptionsObjects(options));
+    return builder.build();
+  }
+
+  /**
+   * Applies arg values to the options object.
+   *
+   * @param args Command line arguments.
+   */
+  @VisibleForTesting
+  public static CliOptions parseOptions(String... args) {
+    JCommander parser = null;
+    try {
+      parser = prepareParser(new CliOptions(ImmutableList.copyOf(customOptions)));
+
+      // We first perform a 'dummy' parsing round.  This induces classloading on any third-party
+      // code, where they can statically invoke registerCustomOptions().
+      parser.setAcceptUnknownOptions(true);
+      parser.parseWithoutValidation(args);
+
+      CliOptions options = new CliOptions(ImmutableList.copyOf(customOptions));
+      parser = prepareParser(options);
+      parser.parse(args);
+      instance = options;
+      return options;
+    } catch (ParameterException e) {
+      if (parser != null) {
+        parser.usage();
+      }
+      LOG.error(e.getMessage());
+      System.exit(1);
+      throw new RuntimeException(e);
+    } catch (RuntimeException e) {
+      throw e;
+    }
+  }
+
+  /**
+   * Gets the static and globally-accessible CLI options.  This exists only to support legacy use
+   * cases that cannot yet support injected arguments.  New callers should not be added.
+   *
+   * @return global options
+   */
+  public static CliOptions legacyGetStaticOptions() {
+    if (instance == null) {
+      throw new IllegalStateException("Attempted to fetch command line arguments before parsing.");
+    }
+    return instance;
+  }
+
+  /**
+   * Registers a custom options container for inclusion during command line option parsing.  This
+   * is useful to allow third-party modules to include custom command line options.
+   *
+   * @param options Custom options object.
+   *                See {@link com.beust.jcommander.JCommander.Builder#addObject(Object)} for
+   *                details.
+   */
+  public static void registerCustomOptions(Object options) {
+    Preconditions.checkState(
+        instance == null,
+        "Attempted to register custom options after command line parsing.");
+
+    customOptions.add(options);
+  }
+
+  @VisibleForTesting
+  static List<Object> getOptionsObjects(CliOptions options) {
+    ImmutableList.Builder<Object> objects = ImmutableList.builder();
+
+    // Reflect on fields defined in CliOptions to DRY and avoid mistakes of forgetting to add an
+    // option field here.
+    for (Field field : CliOptions.class.getDeclaredFields()) {
+      if (Modifier.isStatic(field.getModifiers())) {
+        continue;
+      }
+
+      try {
+        if (Iterable.class.isAssignableFrom(field.getType())) {
+          Iterable<?> iterableValue = (Iterable<?>) field.get(options);
+          objects.addAll(iterableValue);
+        } else {
+          objects.add(field.get(options));
+        }
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    return objects.build();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/converters/ClassConverter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/converters/ClassConverter.java b/src/main/java/org/apache/aurora/scheduler/config/converters/ClassConverter.java
new file mode 100644
index 0000000..b2ecfa0
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/converters/ClassConverter.java
@@ -0,0 +1,49 @@
+/**
+ * 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.config.converters;
+
+import java.util.Map;
+
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.converters.BaseConverter;
+import com.google.common.collect.ImmutableMap;
+
+import org.apache.aurora.scheduler.http.api.security.IniShiroRealmModule;
+import org.apache.aurora.scheduler.http.api.security.Kerberos5ShiroRealmModule;
+
+public class ClassConverter extends BaseConverter<Class<?>> {
+
+  private static final Map<String, String> NAME_ALIASES = ImmutableMap.of(
+      "KERBEROS5_AUTHN", Kerberos5ShiroRealmModule.class.getCanonicalName(),
+      "INI_AUTHNZ", IniShiroRealmModule.class.getCanonicalName());
+
+  public ClassConverter(String optionName) {
+    super(optionName);
+  }
+
+  @Override
+  public Class<?> convert(String value) {
+    if (value.isEmpty()) {
+      throw new ParameterException(getErrorString(value, "must not be blank"));
+    }
+
+    String unaliased = NAME_ALIASES.getOrDefault(value, value);
+    try {
+      return Class.forName(unaliased);
+    } catch (ClassNotFoundException e) {
+      throw new ParameterException(e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/converters/DataAmountConverter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/converters/DataAmountConverter.java b/src/main/java/org/apache/aurora/scheduler/config/converters/DataAmountConverter.java
new file mode 100644
index 0000000..a0d2620
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/converters/DataAmountConverter.java
@@ -0,0 +1,57 @@
+/**
+ * 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.config.converters;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.converters.BaseConverter;
+import com.google.common.base.Functions;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+
+import org.apache.aurora.common.quantity.Data;
+import org.apache.aurora.scheduler.config.types.DataAmount;
+
+public class DataAmountConverter extends BaseConverter<DataAmount> {
+  private static final Pattern AMOUNT_PATTERN = Pattern.compile("(\\d+)([A-Za-z]+)");
+
+  public DataAmountConverter(String optionName) {
+    super(optionName);
+  }
+
+  @Override
+  public DataAmount convert(String raw) {
+    Matcher matcher = AMOUNT_PATTERN.matcher(raw);
+
+    if (!matcher.matches()) {
+      throw new ParameterException(getErrorString(raw, "must be of the format 1KB, 2GB, etc."));
+    }
+
+    Optional<Data> unit = FluentIterable.from(Data.values())
+        .firstMatch(Predicates.compose(
+            Predicates.equalTo(matcher.group(2)),
+            Functions.toStringFunction()));
+    if (unit.isPresent()) {
+      return new DataAmount(Integer.parseInt(matcher.group(1)), unit.get());
+    } else {
+      throw new ParameterException(
+          getErrorString(raw, "one of " + ImmutableList.copyOf(Data.values())));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/converters/DockerParameterConverter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/converters/DockerParameterConverter.java b/src/main/java/org/apache/aurora/scheduler/config/converters/DockerParameterConverter.java
new file mode 100644
index 0000000..5c126e3
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/converters/DockerParameterConverter.java
@@ -0,0 +1,36 @@
+/**
+ * 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.config.converters;
+
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.converters.BaseConverter;
+
+import org.apache.aurora.gen.DockerParameter;
+
+public class DockerParameterConverter extends BaseConverter<DockerParameter> {
+  public DockerParameterConverter(String optionName) {
+    super(optionName);
+  }
+
+  @Override
+  public DockerParameter convert(String value) {
+    int pos = value.indexOf('=');
+    if (pos == -1 || pos == 0 || pos == value.length() - 1) {
+      throw new ParameterException(getErrorString(value, "formatted as name=value"));
+    }
+
+    return new DockerParameter(value.substring(0, pos), value.substring(pos + 1));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/converters/InetSocketAddressConverter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/converters/InetSocketAddressConverter.java b/src/main/java/org/apache/aurora/scheduler/config/converters/InetSocketAddressConverter.java
new file mode 100644
index 0000000..1261592
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/converters/InetSocketAddressConverter.java
@@ -0,0 +1,32 @@
+/**
+ * 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.config.converters;
+
+import java.net.InetSocketAddress;
+
+import com.beust.jcommander.converters.BaseConverter;
+
+import org.apache.aurora.common.net.InetSocketAddressHelper;
+
+public class InetSocketAddressConverter extends BaseConverter<InetSocketAddress>  {
+  public InetSocketAddressConverter(String optionName) {
+    super(optionName);
+  }
+
+  @Override
+  public InetSocketAddress convert(String value) {
+    return InetSocketAddressHelper.parse(value);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/converters/TimeAmountConverter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/converters/TimeAmountConverter.java b/src/main/java/org/apache/aurora/scheduler/config/converters/TimeAmountConverter.java
new file mode 100644
index 0000000..7e66cf2
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/converters/TimeAmountConverter.java
@@ -0,0 +1,57 @@
+/**
+ * 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.config.converters;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.beust.jcommander.ParameterException;
+import com.beust.jcommander.converters.BaseConverter;
+import com.google.common.base.Functions;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+import com.google.common.collect.ImmutableList;
+
+import org.apache.aurora.common.quantity.Time;
+import org.apache.aurora.scheduler.config.types.TimeAmount;
+
+public class TimeAmountConverter extends BaseConverter<TimeAmount> {
+  private static final Pattern AMOUNT_PATTERN = Pattern.compile("(\\d+)([A-Za-z]+)");
+
+  public TimeAmountConverter(String optionName) {
+    super(optionName);
+  }
+
+  @Override
+  public TimeAmount convert(String raw) {
+    Matcher matcher = AMOUNT_PATTERN.matcher(raw);
+
+    if (!matcher.matches()) {
+      throw new ParameterException(getErrorString(raw, "must be of the format 1ns, 2secs, etc."));
+    }
+
+    Optional<Time> unit = FluentIterable.from(Time.values())
+        .firstMatch(Predicates.compose(
+            Predicates.equalTo(matcher.group(2)),
+            Functions.toStringFunction()));
+    if (unit.isPresent()) {
+      return new TimeAmount(Long.parseLong(matcher.group(1)), unit.get());
+    } else {
+      throw new ParameterException(
+          getErrorString(raw, "one of " + ImmutableList.copyOf(Time.values())));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/types/DataAmount.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/types/DataAmount.java b/src/main/java/org/apache/aurora/scheduler/config/types/DataAmount.java
new file mode 100644
index 0000000..061234f
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/types/DataAmount.java
@@ -0,0 +1,28 @@
+/**
+ * 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.config.types;
+
+import org.apache.aurora.common.quantity.Amount;
+import org.apache.aurora.common.quantity.Data;
+
+public class DataAmount extends Amount<Integer, Data> {
+  public DataAmount(int number, Data unit) {
+    super(number, unit, Integer.MAX_VALUE);
+  }
+
+  @Override protected Integer scale(double multiplier) {
+    return (int) (getValue() * multiplier);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/types/TimeAmount.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/types/TimeAmount.java b/src/main/java/org/apache/aurora/scheduler/config/types/TimeAmount.java
new file mode 100644
index 0000000..776e411
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/types/TimeAmount.java
@@ -0,0 +1,28 @@
+/**
+ * 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.config.types;
+
+import org.apache.aurora.common.quantity.Amount;
+import org.apache.aurora.common.quantity.Time;
+
+public class TimeAmount extends Amount<Long, Time> {
+  public TimeAmount(long number, Time unit) {
+    super(number, unit, Long.MAX_VALUE);
+  }
+
+  @Override protected Long scale(double multiplier) {
+    return (long) (getValue() * multiplier);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/validators/NotEmptyIterable.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/validators/NotEmptyIterable.java b/src/main/java/org/apache/aurora/scheduler/config/validators/NotEmptyIterable.java
new file mode 100644
index 0000000..83be1dd
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/validators/NotEmptyIterable.java
@@ -0,0 +1,28 @@
+/**
+ * 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.config.validators;
+
+import com.beust.jcommander.IValueValidator;
+import com.beust.jcommander.ParameterException;
+import com.google.common.collect.Iterables;
+
+public class NotEmptyIterable implements IValueValidator<Iterable<?>> {
+  @Override
+  public void validate(String name, Iterable<?> value) throws ParameterException {
+    if (Iterables.isEmpty(value)) {
+      throw new ParameterException(String.format("%s must not be empty", name));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/validators/NotEmptyString.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/validators/NotEmptyString.java b/src/main/java/org/apache/aurora/scheduler/config/validators/NotEmptyString.java
new file mode 100644
index 0000000..4b8f3e3
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/validators/NotEmptyString.java
@@ -0,0 +1,27 @@
+/**
+ * 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.config.validators;
+
+import com.beust.jcommander.IValueValidator;
+import com.beust.jcommander.ParameterException;
+
+public class NotEmptyString implements IValueValidator<String> {
+  @Override
+  public void validate(String name, String value) throws ParameterException {
+    if (value.isEmpty()) {
+      throw new ParameterException(String.format("%s must not be empty", name));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/validators/NotNegativeAmount.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/validators/NotNegativeAmount.java b/src/main/java/org/apache/aurora/scheduler/config/validators/NotNegativeAmount.java
new file mode 100644
index 0000000..1325e60
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/validators/NotNegativeAmount.java
@@ -0,0 +1,29 @@
+/**
+ * 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.config.validators;
+
+import com.beust.jcommander.IValueValidator;
+import com.beust.jcommander.ParameterException;
+
+import org.apache.aurora.common.quantity.Amount;
+
+public class NotNegativeAmount implements IValueValidator<Amount<? extends Number, ?>> {
+  @Override
+  public void validate(String name, Amount<? extends Number, ?> value) throws ParameterException {
+    if (value.getValue().longValue() < 0) {
+      throw new ParameterException(String.format("%s must not be negative", name));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/validators/PositiveAmount.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/validators/PositiveAmount.java b/src/main/java/org/apache/aurora/scheduler/config/validators/PositiveAmount.java
new file mode 100644
index 0000000..db4476f
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/validators/PositiveAmount.java
@@ -0,0 +1,29 @@
+/**
+ * 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.config.validators;
+
+import com.beust.jcommander.IValueValidator;
+import com.beust.jcommander.ParameterException;
+
+import org.apache.aurora.common.quantity.Amount;
+
+public class PositiveAmount implements IValueValidator<Amount<? extends Number, ?>> {
+  @Override
+  public void validate(String name, Amount<? extends Number, ?> value) throws ParameterException {
+    if (value.getValue().longValue() <= 0) {
+      throw new ParameterException(String.format("%s must be positive", name));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/validators/PositiveNumber.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/validators/PositiveNumber.java b/src/main/java/org/apache/aurora/scheduler/config/validators/PositiveNumber.java
new file mode 100644
index 0000000..2c477f1
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/validators/PositiveNumber.java
@@ -0,0 +1,27 @@
+/**
+ * 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.config.validators;
+
+import com.beust.jcommander.IValueValidator;
+import com.beust.jcommander.ParameterException;
+
+public class PositiveNumber implements IValueValidator<Number> {
+  @Override
+  public void validate(String name, Number value) throws ParameterException {
+    if (value.longValue() <= 0) {
+      throw new ParameterException(String.format("%s must be positive", name));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/config/validators/ReadableFile.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/config/validators/ReadableFile.java b/src/main/java/org/apache/aurora/scheduler/config/validators/ReadableFile.java
new file mode 100644
index 0000000..f67fb2f
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/config/validators/ReadableFile.java
@@ -0,0 +1,29 @@
+/**
+ * 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.config.validators;
+
+import java.io.File;
+
+import com.beust.jcommander.IValueValidator;
+import com.beust.jcommander.ParameterException;
+
+public class ReadableFile implements IValueValidator<File> {
+  @Override
+  public void validate(String name, File value) throws ParameterException {
+    if (!value.canRead()) {
+      throw new ParameterException(String.format("File %s for %s is not readable", value, name));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java b/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
index 754fde0..b7f5e35 100644
--- a/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
+++ b/src/main/java/org/apache/aurora/scheduler/configuration/ConfigurationManager.java
@@ -13,7 +13,7 @@
  */
 package org.apache.aurora.scheduler.configuration;
 
-import java.util.Map;
+import java.util.List;
 import java.util.stream.Collectors;
 
 import javax.annotation.Nullable;
@@ -23,10 +23,10 @@ import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Multimap;
 
 import org.apache.aurora.gen.Container;
 import org.apache.aurora.gen.DockerParameter;
@@ -91,7 +91,7 @@ public class ConfigurationManager {
   public static class ConfigurationManagerSettings {
     private final ImmutableSet<Container._Fields> allowedContainerTypes;
     private final boolean allowDockerParameters;
-    private final Multimap<String, String> defaultDockerParameters;
+    private final List<DockerParameter> defaultDockerParameters;
     private final boolean requireDockerUseExecutor;
     private final boolean allowGpuResource;
     private final boolean enableMesosFetcher;
@@ -100,7 +100,7 @@ public class ConfigurationManager {
     public ConfigurationManagerSettings(
         ImmutableSet<Container._Fields> allowedContainerTypes,
         boolean allowDockerParameters,
-        Multimap<String, String> defaultDockerParameters,
+        List<DockerParameter> defaultDockerParameters,
         boolean requireDockerUseExecutor,
         boolean allowGpuResource,
         boolean enableMesosFetcher,
@@ -294,10 +294,8 @@ public class ConfigurationManager {
           throw new TaskDescriptionException("A container must specify an image.");
         }
         if (containerConfig.getDocker().getParameters().isEmpty()) {
-          for (Map.Entry<String, String> e : settings.defaultDockerParameters.entries()) {
-            builder.getContainer().getDocker().addToParameters(
-                new DockerParameter(e.getKey(), e.getValue()));
-          }
+          builder.getContainer().getDocker()
+              .setParameters(ImmutableList.copyOf(settings.defaultDockerParameters));
         } else {
           if (!settings.allowDockerParameters) {
             throw new TaskDescriptionException(NO_DOCKER_PARAMETERS);

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/configuration/executor/ExecutorModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/configuration/executor/ExecutorModule.java b/src/main/java/org/apache/aurora/scheduler/configuration/executor/ExecutorModule.java
index 4dac975..6594325 100644
--- a/src/main/java/org/apache/aurora/scheduler/configuration/executor/ExecutorModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/configuration/executor/ExecutorModule.java
@@ -21,6 +21,8 @@ import java.util.List;
 import java.util.Optional;
 import java.util.stream.Stream;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
@@ -28,15 +30,12 @@ import com.google.common.collect.Iterables;
 import com.google.inject.AbstractModule;
 
 import org.apache.aurora.GuavaUtils;
-import org.apache.aurora.common.args.Arg;
-import org.apache.aurora.common.args.CmdLine;
-import org.apache.aurora.common.args.constraints.CanRead;
-import org.apache.aurora.common.args.constraints.Exists;
 import org.apache.aurora.common.base.MorePreconditions;
-import org.apache.aurora.common.quantity.Amount;
 import org.apache.aurora.common.quantity.Data;
 import org.apache.aurora.gen.Volume;
 import org.apache.aurora.gen.apiConstants;
+import org.apache.aurora.scheduler.config.types.DataAmount;
+import org.apache.aurora.scheduler.config.validators.ReadableFile;
 import org.apache.aurora.scheduler.resources.ResourceType;
 import org.apache.mesos.v1.Protos;
 import org.apache.mesos.v1.Protos.CommandInfo;
@@ -54,56 +53,65 @@ import static org.apache.aurora.scheduler.resources.ResourceType.RAM_MB;
  */
 public class ExecutorModule extends AbstractModule {
 
-  @CmdLine(
-      name = "custom_executor_config",
-      help = "Path to custom executor settings configuration file.")
-  @Exists
-  @CanRead
-  private static final Arg<File> CUSTOM_EXECUTOR_CONFIG = Arg.create(null);
-
-  @CmdLine(name = "thermos_executor_path", help = "Path to the thermos executor entry point.")
-  private static final Arg<String> THERMOS_EXECUTOR_PATH = Arg.create();
-
-  @CmdLine(name = "thermos_executor_resources",
-      help = "A comma separated list of additional resources to copy into the sandbox."
-          + "Note: if thermos_executor_path is not the thermos_executor.pex file itself, "
-          + "this must include it.")
-  private static final Arg<List<String>> THERMOS_EXECUTOR_RESOURCES =
-      Arg.create(ImmutableList.of());
-
-  @CmdLine(name = "thermos_executor_flags",
-      help = "Extra arguments to be passed to the thermos executor")
-  private static final Arg<String> THERMOS_EXECUTOR_FLAGS = Arg.create(null);
-
-  @CmdLine(name = "thermos_home_in_sandbox",
-      help = "If true, changes HOME to the sandbox before running the executor. "
-          + "This primarily has the effect of causing the executor and runner "
-          + "to extract themselves into the sandbox.")
-  private static final Arg<Boolean> THERMOS_HOME_IN_SANDBOX = Arg.create(false);
-
-  /**
-   * Extra CPU allocated for each executor.
-   */
-  @CmdLine(name = "thermos_executor_cpu",
-      help = "The number of CPU cores to allocate for each instance of the executor.")
-  private static final Arg<Double> EXECUTOR_OVERHEAD_CPUS = Arg.create(0.25);
-
-  /**
-   * Extra RAM allocated for the executor.
-   */
-  @CmdLine(name = "thermos_executor_ram",
-      help = "The amount of RAM to allocate for each instance of the executor.")
-  private static final Arg<Amount<Long, Data>> EXECUTOR_OVERHEAD_RAM =
-      Arg.create(Amount.of(128L, Data.MB));
-
-  @CmdLine(name = "global_container_mounts",
-      help = "A comma separated list of mount points (in host:container form) to mount "
-          + "into all (non-mesos) containers.")
-  private static final Arg<List<Volume>> GLOBAL_CONTAINER_MOUNTS = Arg.create(ImmutableList.of());
-
-  @CmdLine(name = "populate_discovery_info",
-      help = "If true, Aurora populates DiscoveryInfo field of Mesos TaskInfo.")
-  private static final Arg<Boolean> POPULATE_DISCOVERY_INFO = Arg.create(false);
+  @Parameters(separators = "=")
+  public static class Options {
+    @Parameter(
+        names = "-custom_executor_config",
+        validateValueWith = ReadableFile.class,
+        description = "Path to custom executor settings configuration file.")
+    public File customExecutorConfig;
+
+    @Parameter(names = "-thermos_executor_path",
+        description = "Path to the thermos executor entry point.")
+    public String thermosExecutorPath;
+
+    @Parameter(names = "-thermos_executor_resources",
+        description = "A comma separated list of additional resources to copy into the sandbox."
+            + "Note: if thermos_executor_path is not the thermos_executor.pex file itself, "
+            + "this must include it.")
+    public List<String> thermosExecutorResources = ImmutableList.of();
+
+    @Parameter(names = "-thermos_executor_flags",
+        description = "Extra arguments to be passed to the thermos executor")
+    public String thermosExecutorFlags;
+
+    @Parameter(names = "-thermos_home_in_sandbox",
+        description = "If true, changes HOME to the sandbox before running the executor. "
+            + "This primarily has the effect of causing the executor and runner "
+            + "to extract themselves into the sandbox.",
+        arity = 1)
+    public boolean thermosHomeInSandbox = false;
+
+    /**
+     * Extra CPU allocated for each executor.
+     */
+    @Parameter(names = "-thermos_executor_cpu",
+        description = "The number of CPU cores to allocate for each instance of the executor.")
+    public double executorOverheadCpus = 0.25;
+
+    /**
+     * Extra RAM allocated for the executor.
+     */
+    @Parameter(names = "-thermos_executor_ram",
+        description = "The amount of RAM to allocate for each instance of the executor.")
+    public DataAmount executorOverheadRam = new DataAmount(128, Data.MB);
+
+    @Parameter(names = "-global_container_mounts",
+        description = "A comma separated list of mount points (in host:container form) to mount "
+            + "into all (non-mesos) containers.")
+    public List<Volume> globalContainerMounts = ImmutableList.of();
+
+    @Parameter(names = "-populate_discovery_info",
+        description = "If true, Aurora populates DiscoveryInfo field of Mesos TaskInfo.",
+        arity = 1)
+    public boolean populateDiscoveryInfo = false;
+  }
+
+  private final Options options;
+
+  public ExecutorModule(Options options) {
+    this.options = options;
+  }
 
   @VisibleForTesting
   static CommandInfo makeExecutorCommand(
@@ -135,11 +143,11 @@ public class ExecutorModule extends AbstractModule {
         .build();
   }
 
-  private static ExecutorConfig makeThermosExecutorConfig()  {
+  private static ExecutorConfig makeThermosExecutorConfig(Options opts)  {
     List<Protos.Volume> volumeMounts =
         ImmutableList.<Protos.Volume>builder()
             .addAll(Iterables.transform(
-                GLOBAL_CONTAINER_MOUNTS.get(),
+                opts.globalContainerMounts,
                 v -> Protos.Volume.newBuilder()
                     .setHostPath(v.getHostPath())
                     .setContainerPath(v.getContainerPath())
@@ -154,33 +162,32 @@ public class ExecutorModule extends AbstractModule {
             .setExecutorId(Executors.PLACEHOLDER_EXECUTOR_ID)
             .setCommand(
                 makeExecutorCommand(
-                    THERMOS_EXECUTOR_PATH.get(),
-                    THERMOS_EXECUTOR_RESOURCES.get(),
-                    THERMOS_HOME_IN_SANDBOX.get(),
-                    THERMOS_EXECUTOR_FLAGS.get()))
-            .addResources(makeResource(CPUS, EXECUTOR_OVERHEAD_CPUS.get()))
-            .addResources(makeResource(RAM_MB, EXECUTOR_OVERHEAD_RAM.get().as(Data.MB)))
+                    opts.thermosExecutorPath,
+                    opts.thermosExecutorResources,
+                    opts.thermosHomeInSandbox,
+                    opts.thermosExecutorFlags))
+            .addResources(makeResource(CPUS, opts.executorOverheadCpus))
+            .addResources(makeResource(RAM_MB, opts.executorOverheadRam.as(Data.MB)))
             .build(),
         volumeMounts,
         "thermos-");
   }
 
-  private static ExecutorSettings makeExecutorSettings() {
+  private static ExecutorSettings makeExecutorSettings(Options opts) {
     try {
-
       ImmutableMap.Builder<String, ExecutorConfig> configsBuilder = ImmutableMap.builder();
 
-      configsBuilder.put(apiConstants.AURORA_EXECUTOR_NAME, makeThermosExecutorConfig());
+      configsBuilder.put(apiConstants.AURORA_EXECUTOR_NAME, makeThermosExecutorConfig(opts));
 
-      if (CUSTOM_EXECUTOR_CONFIG.hasAppliedValue()) {
+      if (opts.customExecutorConfig != null) {
         configsBuilder.putAll(
             ExecutorSettingsLoader.read(
                 Files.newBufferedReader(
-                    CUSTOM_EXECUTOR_CONFIG.get().toPath(),
+                    opts.customExecutorConfig.toPath(),
                     StandardCharsets.UTF_8)));
       }
 
-      return new ExecutorSettings(configsBuilder.build(), POPULATE_DISCOVERY_INFO.get());
+      return new ExecutorSettings(configsBuilder.build(), opts.populateDiscoveryInfo);
 
     } catch (ExecutorSettingsLoader.ExecutorConfigException | IOException e) {
       throw new IllegalArgumentException("Failed to read executor settings: " + e, e);
@@ -189,7 +196,7 @@ public class ExecutorModule extends AbstractModule {
 
   @Override
   protected void configure() {
-    bind(ExecutorSettings.class).toInstance(makeExecutorSettings());
+    bind(ExecutorSettings.class).toInstance(makeExecutorSettings(options));
   }
 
   private static Resource makeResource(ResourceType type, double value) {

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
index 9c88a2a..33e0eaa 100644
--- a/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/cron/quartz/CronModule.java
@@ -19,16 +19,16 @@ import java.util.concurrent.atomic.AtomicLong;
 
 import javax.inject.Singleton;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 import com.google.inject.TypeLiteral;
 
-import org.apache.aurora.common.args.Arg;
-import org.apache.aurora.common.args.CmdLine;
-import org.apache.aurora.common.args.constraints.Positive;
-import org.apache.aurora.common.quantity.Amount;
 import org.apache.aurora.common.quantity.Time;
 import org.apache.aurora.common.util.BackoffHelper;
+import org.apache.aurora.scheduler.config.types.TimeAmount;
+import org.apache.aurora.scheduler.config.validators.PositiveNumber;
 import org.apache.aurora.scheduler.cron.CronJobManager;
 import org.apache.aurora.scheduler.cron.CronPredictor;
 import org.apache.aurora.scheduler.cron.CronScheduler;
@@ -57,31 +57,38 @@ import static org.quartz.impl.StdSchedulerFactory.PROP_THREAD_POOL_PREFIX;
 public class CronModule extends AbstractModule {
   private static final Logger LOG = LoggerFactory.getLogger(CronModule.class);
 
-  @CmdLine(name = "cron_scheduler_num_threads",
-      help = "Number of threads to use for the cron scheduler thread pool.")
-  private static final Arg<Integer> NUM_THREADS = Arg.create(10);
+  @Parameters(separators = "=")
+  public static class Options {
+    @Parameter(names = "-cron_scheduler_num_threads",
+        description = "Number of threads to use for the cron scheduler thread pool.")
+    public int cronSchedulerNumThreads = 10;
 
-  @CmdLine(name = "cron_timezone", help = "TimeZone to use for cron predictions.")
-  private static final Arg<String> CRON_TIMEZONE = Arg.create("GMT");
+    @Parameter(names = "-cron_timezone", description = "TimeZone to use for cron predictions.")
+    public String cronTimezone = "GMT";
 
-  @CmdLine(name = "cron_start_initial_backoff", help =
-      "Initial backoff delay while waiting for a previous cron run to be killed.")
-  public static final Arg<Amount<Long, Time>> CRON_START_INITIAL_BACKOFF =
-      Arg.create(Amount.of(5L, Time.SECONDS));
+    @Parameter(names = "-cron_start_initial_backoff", description =
+        "Initial backoff delay while waiting for a previous cron run to be killed.")
+    public TimeAmount cronStartInitialBackoff = new TimeAmount(5, Time.SECONDS);
 
-  @CmdLine(name = "cron_start_max_backoff", help =
-      "Max backoff delay while waiting for a previous cron run to be killed.")
-  public static final Arg<Amount<Long, Time>> CRON_START_MAX_BACKOFF =
-      Arg.create(Amount.of(1L, Time.MINUTES));
+    @Parameter(names = "-cron_start_max_backoff", description =
+        "Max backoff delay while waiting for a previous cron run to be killed.")
+    public TimeAmount cronStartMaxBackoff = new TimeAmount(1, Time.MINUTES);
 
-  @Positive
-  @CmdLine(name = "cron_scheduling_max_batch_size",
-      help = "The maximum number of triggered cron jobs that can be processed in a batch.")
-  private static final Arg<Integer> CRON_MAX_BATCH_SIZE = Arg.create(10);
+    @Parameter(names = "-cron_scheduling_max_batch_size",
+        validateValueWith = PositiveNumber.class,
+        description = "The maximum number of triggered cron jobs that can be processed in a batch.")
+    public int cronMaxBatchSize = 10;
+  }
 
   // Global per-JVM ID number generator for the provided Quartz Scheduler.
   private static final AtomicLong ID_GENERATOR = new AtomicLong();
 
+  private final Options options;
+
+  public CronModule(Options options) {
+    this.options = options;
+  }
+
   @Override
   protected void configure() {
     bind(CronPredictor.class).to(CronPredictorImpl.class);
@@ -97,7 +104,7 @@ public class CronModule extends AbstractModule {
 
     bind(AuroraCronJob.class).in(Singleton.class);
     bind(AuroraCronJob.Config.class).toInstance(new AuroraCronJob.Config(
-        new BackoffHelper(CRON_START_INITIAL_BACKOFF.get(), CRON_START_MAX_BACKOFF.get())));
+        new BackoffHelper(options.cronStartInitialBackoff, options.cronStartMaxBackoff)));
 
     PubsubEventModule.bindSubscriber(binder(), AuroraCronJob.class);
 
@@ -106,14 +113,14 @@ public class CronModule extends AbstractModule {
 
     bind(new TypeLiteral<Integer>() { })
         .annotatedWith(AuroraCronJob.CronMaxBatchSize.class)
-        .toInstance(CRON_MAX_BATCH_SIZE.get());
+        .toInstance(options.cronMaxBatchSize);
     bind(CronBatchWorker.class).in(Singleton.class);
     addSchedulerActiveServiceBinding(binder()).to(CronBatchWorker.class);
   }
 
   @Provides
   TimeZone provideTimeZone() {
-    TimeZone timeZone = TimeZone.getTimeZone(CRON_TIMEZONE.get());
+    TimeZone timeZone = TimeZone.getTimeZone(options.cronTimezone);
     TimeZone systemTimeZone = TimeZone.getDefault();
     if (!timeZone.equals(systemTimeZone)) {
       LOG.warn("Cron schedules are configured to fire according to timezone "
@@ -126,7 +133,7 @@ public class CronModule extends AbstractModule {
 
   @Provides
   @Singleton
-  static Scheduler provideScheduler(AuroraCronJobFactory jobFactory) throws SchedulerException {
+  Scheduler provideScheduler(AuroraCronJobFactory jobFactory) throws SchedulerException {
     // There are several ways to create a quartz Scheduler instance.  This path was chosen as the
     // simplest to create a Scheduler that uses a *daemon* QuartzSchedulerThread instance.
     Properties props = new Properties();
@@ -134,7 +141,9 @@ public class CronModule extends AbstractModule {
     props.setProperty(PROP_SCHED_NAME, name);
     props.setProperty(PROP_SCHED_INSTANCE_ID, name);
     props.setProperty(PROP_THREAD_POOL_CLASS, SimpleThreadPool.class.getCanonicalName());
-    props.setProperty(PROP_THREAD_POOL_PREFIX + ".threadCount", NUM_THREADS.get().toString());
+    props.setProperty(
+        PROP_THREAD_POOL_PREFIX + ".threadCount",
+        String.valueOf(options.cronSchedulerNumThreads));
     props.setProperty(PROP_THREAD_POOL_PREFIX + ".makeThreadsDaemons", Boolean.TRUE.toString());
 
     props.setProperty(PROP_SCHED_MAKE_SCHEDULER_THREAD_DAEMON, Boolean.TRUE.toString());

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/discovery/FlaggedZooKeeperConfig.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/discovery/FlaggedZooKeeperConfig.java b/src/main/java/org/apache/aurora/scheduler/discovery/FlaggedZooKeeperConfig.java
index e8aafe4..c2e8ce2 100644
--- a/src/main/java/org/apache/aurora/scheduler/discovery/FlaggedZooKeeperConfig.java
+++ b/src/main/java/org/apache/aurora/scheduler/discovery/FlaggedZooKeeperConfig.java
@@ -18,51 +18,59 @@ import java.util.List;
 
 import javax.annotation.Nullable;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.common.base.Optional;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 
-import org.apache.aurora.common.args.Arg;
-import org.apache.aurora.common.args.CmdLine;
-import org.apache.aurora.common.args.constraints.NotEmpty;
 import org.apache.aurora.common.quantity.Amount;
-import org.apache.aurora.common.quantity.Time;
 import org.apache.aurora.common.zookeeper.Credentials;
-import org.apache.aurora.common.zookeeper.ZooKeeperUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.aurora.scheduler.config.types.TimeAmount;
+import org.apache.aurora.scheduler.config.validators.NotEmptyIterable;
+
+import static org.apache.aurora.scheduler.discovery.ZooKeeperConfig.DEFAULT_SESSION_TIMEOUT;
 
 /**
  * A factory that creates a {@link ZooKeeperConfig} instance based on command line argument
  * values.
  */
 public final class FlaggedZooKeeperConfig {
-  private static final Logger LOG = LoggerFactory.getLogger(FlaggedZooKeeperConfig.class);
 
-  @CmdLine(name = "zk_use_curator",
-      help = "DEPRECATED: Uses Apache Curator as the zookeeper client; otherwise a copy of Twitter "
-          + "commons/zookeeper (the legacy library) is used.")
-  private static final Arg<Boolean> USE_CURATOR = Arg.create(true);
+  @Parameters(separators = "=")
+  public static class Options {
+    @Parameter(names = "-zk_use_curator",
+        description =
+            "DEPRECATED: Uses Apache Curator as the zookeeper client; otherwise a copy of Twitter "
+                + "commons/zookeeper (the legacy library) is used.",
+        arity = 1)
+    public boolean useCurator = true;
 
-  @CmdLine(name = "zk_in_proc",
-      help = "Launches an embedded zookeeper server for local testing causing -zk_endpoints "
-          + "to be ignored if specified.")
-  private static final Arg<Boolean> IN_PROCESS = Arg.create(false);
+    @Parameter(names = "-zk_in_proc",
+        description =
+            "Launches an embedded zookeeper server for local testing causing -zk_endpoints "
+                + "to be ignored if specified.",
+        arity = 1)
+    public boolean inProcess = false;
 
-  @NotEmpty
-  @CmdLine(name = "zk_endpoints", help = "Endpoint specification for the ZooKeeper servers.")
-  private static final Arg<List<InetSocketAddress>> ZK_ENDPOINTS = Arg.create();
+    @Parameter(
+        names = "-zk_endpoints",
+        required = true,
+        validateValueWith = NotEmptyIterable.class,
+        description = "Endpoint specification for the ZooKeeper servers.")
+    public List<InetSocketAddress> zkEndpoints;
 
-  @CmdLine(name = "zk_chroot_path", help = "chroot path to use for the ZooKeeper connections")
-  private static final Arg<String> CHROOT_PATH = Arg.create(null);
+    @Parameter(names = "-zk_chroot_path",
+        description = "chroot path to use for the ZooKeeper connections")
+    public String chrootPath;
 
-  @CmdLine(name = "zk_session_timeout", help = "The ZooKeeper session timeout.")
-  private static final Arg<Amount<Integer, Time>> SESSION_TIMEOUT =
-      Arg.create(ZooKeeperUtils.DEFAULT_ZK_SESSION_TIMEOUT);
+    @Parameter(names = "-zk_session_timeout", description = "The ZooKeeper session timeout.")
+    public TimeAmount sessionTimeout = DEFAULT_SESSION_TIMEOUT;
 
-  @CmdLine(name = "zk_digest_credentials",
-           help = "user:password to use when authenticating with ZooKeeper.")
-  private static final Arg<String> DIGEST_CREDENTIALS = Arg.create(null);
+    @Parameter(names = "-zk_digest_credentials",
+        description = "user:password to use when authenticating with ZooKeeper.")
+    public String digestCredentials;
+  }
 
   private FlaggedZooKeeperConfig() {
     // Utility class.
@@ -73,17 +81,14 @@ public final class FlaggedZooKeeperConfig {
    *
    * @return Configuration instance.
    */
-  public static ZooKeeperConfig create() {
-    if (USE_CURATOR.hasAppliedValue()) {
-      LOG.warn("The -zk_use_curator flag is deprecated and will be removed in a future release.");
-    }
+  public static ZooKeeperConfig create(Options opts) {
     return new ZooKeeperConfig(
-        USE_CURATOR.get(),
-        ZK_ENDPOINTS.get(),
-        Optional.fromNullable(CHROOT_PATH.get()),
-        IN_PROCESS.get(),
-        SESSION_TIMEOUT.get(),
-        getCredentials(DIGEST_CREDENTIALS.get()));
+        opts.useCurator,
+        opts.zkEndpoints,
+        Optional.fromNullable(opts.chrootPath),
+        opts.inProcess,
+        Amount.of(opts.sessionTimeout.getValue().intValue(), opts.sessionTimeout.getUnit()),
+        getCredentials(opts.digestCredentials));
   }
 
   private static Optional<Credentials> getCredentials(@Nullable String userAndPass) {

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/discovery/ZooKeeperConfig.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/discovery/ZooKeeperConfig.java b/src/main/java/org/apache/aurora/scheduler/discovery/ZooKeeperConfig.java
index 4014a91..f6faca5 100644
--- a/src/main/java/org/apache/aurora/scheduler/discovery/ZooKeeperConfig.java
+++ b/src/main/java/org/apache/aurora/scheduler/discovery/ZooKeeperConfig.java
@@ -22,6 +22,7 @@ import org.apache.aurora.common.quantity.Amount;
 import org.apache.aurora.common.quantity.Time;
 import org.apache.aurora.common.zookeeper.Credentials;
 import org.apache.aurora.common.zookeeper.ZooKeeperUtils;
+import org.apache.aurora.scheduler.config.types.TimeAmount;
 
 import static java.util.Objects.requireNonNull;
 
@@ -32,6 +33,10 @@ import static java.util.Objects.requireNonNull;
  */
 public class ZooKeeperConfig {
 
+  public static final TimeAmount DEFAULT_SESSION_TIMEOUT = new TimeAmount(
+      ZooKeeperUtils.DEFAULT_ZK_SESSION_TIMEOUT.getValue(),
+        ZooKeeperUtils.DEFAULT_ZK_SESSION_TIMEOUT.getUnit());
+
   /**
    * Creates a new client configuration with defaults for the session timeout and credentials.
    *

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/events/WebhookModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/events/WebhookModule.java b/src/main/java/org/apache/aurora/scheduler/events/WebhookModule.java
index 8c9ea05..8e13341 100644
--- a/src/main/java/org/apache/aurora/scheduler/events/WebhookModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/events/WebhookModule.java
@@ -17,19 +17,17 @@ import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Optional;
 import com.google.common.base.Strings;
 import com.google.common.io.Files;
-import com.google.common.io.Resources;
 import com.google.inject.AbstractModule;
-
 import com.google.inject.Singleton;
 
-import org.apache.aurora.common.args.Arg;
-import org.apache.aurora.common.args.CmdLine;
-import org.apache.aurora.common.args.constraints.CanRead;
-import org.apache.aurora.common.args.constraints.Exists;
 import org.apache.aurora.scheduler.SchedulerServicesModule;
+import org.apache.aurora.scheduler.config.validators.ReadableFile;
 import org.asynchttpclient.AsyncHttpClient;
 import org.asynchttpclient.DefaultAsyncHttpClientConfig;
 import org.asynchttpclient.channel.DefaultKeepAliveStrategy;
@@ -51,26 +49,36 @@ public class WebhookModule extends AbstractModule {
   @VisibleForTesting
   static final String WEBHOOK_CONFIG_PATH = "org/apache/aurora/scheduler/webhook.json";
 
-  @CmdLine(name = "webhook_config", help = "Path to webhook configuration file.")
-  @Exists
-  @CanRead
-  private static final Arg<File> WEBHOOK_CONFIG_FILE = Arg.create();
-
-  private final boolean enableWebhook;
+  @Parameters(separators = "=")
+  public static class Options {
+    @Parameter(names = "-webhook_config",
+        validateValueWith = ReadableFile.class,
+        description = "Path to webhook configuration file.")
+    public File webhookConfigFile = null;
+  }
 
-  public WebhookModule() {
-    this(WEBHOOK_CONFIG_FILE.hasAppliedValue());
+  private final Optional<String> webhookConfig;
+
+  public WebhookModule(Options options) {
+    this.webhookConfig = Optional.fromNullable(options.webhookConfigFile)
+        .transform(f -> {
+          try {
+            return Files.toString(options.webhookConfigFile, StandardCharsets.UTF_8);
+          } catch (IOException e) {
+            throw new RuntimeException(e);
+          }
+        });
   }
 
   @VisibleForTesting
-  private WebhookModule(boolean enableWebhook) {
-    this.enableWebhook = enableWebhook;
+  WebhookModule(Optional<String> webhookConfig) {
+    this.webhookConfig = webhookConfig;
   }
 
   @Override
   protected void configure() {
-    if (enableWebhook) {
-      WebhookInfo webhookInfo = parseWebhookConfig(readWebhookFile());
+    if (webhookConfig.isPresent()) {
+      WebhookInfo webhookInfo = parseWebhookConfig(webhookConfig.get());
       DefaultAsyncHttpClientConfig config = new DefaultAsyncHttpClientConfig.Builder()
           .setConnectTimeout(webhookInfo.getConnectonTimeoutMsec())
           .setHandshakeTimeout(webhookInfo.getConnectonTimeoutMsec())
@@ -92,20 +100,6 @@ public class WebhookModule extends AbstractModule {
   }
 
   @VisibleForTesting
-  static String readWebhookFile() {
-    try {
-      return WEBHOOK_CONFIG_FILE.hasAppliedValue()
-          ? Files.toString(WEBHOOK_CONFIG_FILE.get(), StandardCharsets.UTF_8)
-          : Resources.toString(
-              Webhook.class.getClassLoader().getResource(WEBHOOK_CONFIG_PATH),
-              StandardCharsets.UTF_8);
-    } catch (IOException e) {
-      LOG.error("Error loading webhook configuration file.");
-      throw new RuntimeException(e);
-    }
-  }
-
-  @VisibleForTesting
   static WebhookInfo parseWebhookConfig(String config) {
     checkArgument(!Strings.isNullOrEmpty(config), "Webhook configuration cannot be empty");
     try {

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java b/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java
index 01d6b5d..ee10f47 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/H2ConsoleModule.java
@@ -13,12 +13,12 @@
  */
 package org.apache.aurora.scheduler.http;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.servlet.ServletModule;
 
-import org.apache.aurora.common.args.Arg;
-import org.apache.aurora.common.args.CmdLine;
 import org.h2.server.web.WebServlet;
 
 /**
@@ -30,13 +30,19 @@ public class H2ConsoleModule extends ServletModule {
   public static final String H2_PATH = "/h2console";
   public static final String H2_PERM = "h2_management_console";
 
-  @CmdLine(name = "enable_h2_console", help = "Enable H2 DB management console.")
-  private static final Arg<Boolean> ENABLE_H2_CONSOLE = Arg.create(false);
+  @Parameters(separators = "=")
+  public static class Options {
+    @Parameter(
+        names = "-enable_h2_console",
+        description = "Enable H2 DB management console.",
+        arity = 1)
+    public boolean enableH2Console = false;
+  }
 
   private final boolean enabled;
 
-  public H2ConsoleModule() {
-    this(ENABLE_H2_CONSOLE.get());
+  public H2ConsoleModule(Options options) {
+    this(options.enableH2Console);
   }
 
   @VisibleForTesting

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java b/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
index e29bb41..93cd20a 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
@@ -20,13 +20,14 @@ import java.util.EnumSet;
 import java.util.Map;
 import java.util.Set;
 
-import javax.annotation.Nonnegative;
 import javax.inject.Inject;
 import javax.inject.Singleton;
 import javax.servlet.DispatcherType;
 import javax.servlet.ServletContextListener;
 import javax.ws.rs.HttpMethod;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
@@ -53,8 +54,6 @@ import com.google.inject.util.Modules;
 import com.sun.jersey.guice.JerseyServletModule;
 import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
 
-import org.apache.aurora.common.args.Arg;
-import org.apache.aurora.common.args.CmdLine;
 import org.apache.aurora.common.net.http.handlers.AbortHandler;
 import org.apache.aurora.common.net.http.handlers.ContentionPrinter;
 import org.apache.aurora.common.net.http.handlers.HealthHandler;
@@ -65,9 +64,11 @@ import org.apache.aurora.common.net.http.handlers.VarsHandler;
 import org.apache.aurora.common.net.http.handlers.VarsJsonHandler;
 import org.apache.aurora.scheduler.SchedulerServicesModule;
 import org.apache.aurora.scheduler.app.ServiceGroupMonitor.MonitorException;
+import org.apache.aurora.scheduler.config.CliOptions;
 import org.apache.aurora.scheduler.http.api.ApiModule;
 import org.apache.aurora.scheduler.http.api.security.HttpSecurityModule;
 import org.apache.aurora.scheduler.thrift.ThriftModule;
+import org.apache.aurora.scheduler.thrift.aop.AopModule;
 import org.eclipse.jetty.rewrite.handler.RewriteHandler;
 import org.eclipse.jetty.rewrite.handler.RewriteRegexRule;
 import org.eclipse.jetty.server.Handler;
@@ -102,18 +103,23 @@ public class JettyServerModule extends AbstractModule {
   // rewritten is stored.
   static final String ORIGINAL_PATH_ATTRIBUTE_NAME = "originalPath";
 
-  @CmdLine(name = "hostname",
-      help = "The hostname to advertise in ZooKeeper instead of the locally-resolved hostname.")
-  private static final Arg<String> HOSTNAME_OVERRIDE = Arg.create(null);
-
-  @Nonnegative
-  @CmdLine(name = "http_port",
-      help = "The port to start an HTTP server on.  Default value will choose a random port.")
-  protected static final Arg<Integer> HTTP_PORT = Arg.create(0);
-
-  @CmdLine(name = "ip",
-      help = "The ip address to listen. If not set, the scheduler will listen on all interfaces.")
-  protected static final Arg<String> LISTEN_IP = Arg.create();
+  @Parameters(separators = "=")
+  public static class Options {
+    @Parameter(names = "-hostname",
+        description =
+            "The hostname to advertise in ZooKeeper instead of the locally-resolved hostname.")
+    public String hostnameOverride;
+
+    @Parameter(names = "-http_port",
+        description =
+            "The port to start an HTTP server on.  Default value will choose a random port.")
+    public int httpPort = 0;
+
+    @Parameter(names = "-ip",
+        description =
+            "The ip address to listen. If not set, the scheduler will listen on all interfaces.")
+    public String listenIp;
+  }
 
   public static final Map<String, String> GUICE_CONTAINER_PARAMS = ImmutableMap.of(
       FEATURE_POJO_MAPPING, Boolean.TRUE.toString());
@@ -123,14 +129,16 @@ public class JettyServerModule extends AbstractModule {
       .toString()
       .replace("assets/index.html", "");
 
+  private final CliOptions options;
   private final boolean production;
 
-  public JettyServerModule() {
-    this(true);
+  public JettyServerModule(CliOptions options) {
+    this(options, true);
   }
 
   @VisibleForTesting
-  JettyServerModule(boolean production) {
+  JettyServerModule(CliOptions options, boolean production) {
+    this.options = options;
     this.production = production;
   }
 
@@ -147,7 +155,8 @@ public class JettyServerModule extends AbstractModule {
         .annotatedWith(Names.named(HealthHandler.HEALTH_CHECKER_KEY))
         .toInstance(Suppliers.ofInstance(true));
 
-    final Optional<String> hostnameOverride = Optional.fromNullable(HOSTNAME_OVERRIDE.get());
+    final Optional<String> hostnameOverride =
+        Optional.fromNullable(options.jetty.hostnameOverride);
     if (hostnameOverride.isPresent()) {
       try {
         InetAddress.getByName(hostnameOverride.get());
@@ -173,28 +182,27 @@ public class JettyServerModule extends AbstractModule {
     SchedulerServicesModule.addAppStartupServiceBinding(binder()).to(RedirectMonitor.class);
 
     if (production) {
-      install(PRODUCTION_SERVLET_CONTEXT_LISTENER);
-    }
-  }
-
-  private static final Module PRODUCTION_SERVLET_CONTEXT_LISTENER = new AbstractModule() {
-    @Override
-    protected void configure() {
-      // Provider binding only.
-    }
+      install(new AbstractModule() {
+        @Override
+        protected void configure() {
+          // Provider binding only.
+        }
 
-    @Provides
-    @Singleton
-    ServletContextListener provideServletContextListener(Injector parentInjector) {
-      return makeServletContextListener(
-          parentInjector,
-          Modules.combine(
-              new ApiModule(),
-              new H2ConsoleModule(),
-              new HttpSecurityModule(),
-              new ThriftModule()));
+        @Provides
+        @Singleton
+        ServletContextListener provideServletContextListener(Injector parentInjector) {
+          return makeServletContextListener(
+              parentInjector,
+              Modules.combine(
+                  new ApiModule(options.api),
+                  new H2ConsoleModule(options.h2Console),
+                  new HttpSecurityModule(options),
+                  new ThriftModule(),
+                  new AopModule(options)));
+        }
+      });
     }
-  };
+  }
 
   private static final Set<String> LEADER_ENDPOINTS = ImmutableSet.of(
       "agents",
@@ -304,6 +312,7 @@ public class JettyServerModule extends AbstractModule {
   }
 
   public static final class HttpServerLauncher extends AbstractIdleService implements HttpService {
+    private final CliOptions options;
     private final ServletContextListener servletContextListener;
     private final Optional<String> advertisedHostOverride;
     private volatile Server server;
@@ -311,9 +320,11 @@ public class JettyServerModule extends AbstractModule {
 
     @Inject
     HttpServerLauncher(
+        CliOptions options,
         ServletContextListener servletContextListener,
         Optional<String> advertisedHostOverride) {
 
+      this.options = requireNonNull(options);
       this.servletContextListener = requireNonNull(servletContextListener);
       this.advertisedHostOverride = requireNonNull(advertisedHostOverride);
     }
@@ -380,9 +391,9 @@ public class JettyServerModule extends AbstractModule {
       rootHandler.addHandler(servletHandler);
 
       ServerConnector connector = new ServerConnector(server);
-      connector.setPort(HTTP_PORT.get());
-      if (LISTEN_IP.hasAppliedValue()) {
-        connector.setHost(LISTEN_IP.get());
+      connector.setPort(options.jetty.httpPort);
+      if (options.jetty.listenIp != null) {
+        connector.setHost(options.jetty.listenIp);
       }
 
       server.addConnector(connector);

http://git-wip-us.apache.org/repos/asf/aurora/blob/519e3df7/src/main/java/org/apache/aurora/scheduler/http/api/ApiModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/api/ApiModule.java b/src/main/java/org/apache/aurora/scheduler/http/api/ApiModule.java
index e468209..a19663a 100644
--- a/src/main/java/org/apache/aurora/scheduler/http/api/ApiModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/http/api/ApiModule.java
@@ -16,13 +16,13 @@ package org.apache.aurora.scheduler.http.api;
 import javax.inject.Singleton;
 import javax.ws.rs.core.MediaType;
 
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
 import com.google.common.collect.ImmutableMap;
 import com.google.inject.Provides;
 import com.google.inject.servlet.ServletModule;
 import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
 
-import org.apache.aurora.common.args.Arg;
-import org.apache.aurora.common.args.CmdLine;
 import org.apache.aurora.gen.AuroraAdmin;
 import org.apache.aurora.scheduler.http.CorsFilter;
 import org.apache.aurora.scheduler.http.JettyServerModule;
@@ -46,13 +46,22 @@ public class ApiModule extends ServletModule {
   private static final MediaType THRIFT_BINARY =
       new MediaType("application", "vnd.apache.thrift.binary");
 
-  /**
-   * Set the {@code Access-Control-Allow-Origin} header for API requests. See
-   * http://www.w3.org/TR/cors/
-   */
-  @CmdLine(name = "enable_cors_for",
-      help = "List of domains for which CORS support should be enabled.")
-  private static final Arg<String> ENABLE_CORS_FOR = Arg.create(null);
+  @Parameters(separators = "=")
+  public static class Options {
+    /**
+     * Set the {@code Access-Control-Allow-Origin} header for API requests. See
+     * http://www.w3.org/TR/cors/
+     */
+    @Parameter(names = "-enable_cors_for",
+        description = "List of domains for which CORS support should be enabled.")
+    public String enableCorsFor;
+  }
+
+  private final Options options;
+
+  public ApiModule(Options options) {
+    this.options = options;
+  }
 
   private static final String API_CLIENT_ROOT = Resource
       .newClassPathResource("org/apache/aurora/scheduler/gen/client")
@@ -60,8 +69,8 @@ public class ApiModule extends ServletModule {
 
   @Override
   protected void configureServlets() {
-    if (ENABLE_CORS_FOR.get() != null) {
-      filter(API_PATH).through(new CorsFilter(ENABLE_CORS_FOR.get()));
+    if (options.enableCorsFor != null) {
+      filter(API_PATH).through(new CorsFilter(options.enableCorsFor));
     }
     serve(API_PATH).with(TContentAwareServlet.class);
 


Mime
View raw message