aurora-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From zma...@apache.org
Subject [31/51] [partial] aurora git commit: Move packages from com.twitter.common to org.apache.aurora.common
Date Wed, 26 Aug 2015 21:00:21 GMT
http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java b/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java
deleted file mode 100644
index a199d1d..0000000
--- a/commons/src/main/java/com/twitter/common/zookeeper/testing/ZooKeeperTestServer.java
+++ /dev/null
@@ -1,222 +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 com.twitter.common.zookeeper.testing;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.util.Arrays;
-
-import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
-
-import org.apache.zookeeper.server.NIOServerCnxn;
-import org.apache.zookeeper.server.ZooKeeperServer;
-import org.apache.zookeeper.server.ZooKeeperServer.BasicDataTreeBuilder;
-import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
-
-import com.twitter.common.application.ShutdownRegistry;
-import com.twitter.common.base.Command;
-import com.twitter.common.base.ExceptionalCommand;
-import com.twitter.common.io.FileUtils;
-import com.twitter.common.quantity.Amount;
-import com.twitter.common.quantity.Time;
-import com.twitter.common.zookeeper.ZooKeeperClient;
-import com.twitter.common.zookeeper.ZooKeeperClient.Credentials;
-
-/**
- * A helper class for starting in-process ZooKeeper server and clients.
- *
- * <p>This is ONLY meant to be used for testing.
- */
-public class ZooKeeperTestServer {
-
-  /**
-   * The default session timeout for clients created by servers constructed with
-   * {@link #ZooKeeperTestServer(int, ShutdownRegistry)}.
-   */
-  public static final Amount<Integer, Time> DEFAULT_SESSION_TIMEOUT =
-      Amount.of(100, Time.MILLISECONDS);
-
-  protected final ZooKeeperServer zooKeeperServer;
-  private final ShutdownRegistry shutdownRegistry;
-  private NIOServerCnxn.Factory connectionFactory;
-  private int port;
-  private final Amount<Integer, Time> defaultSessionTimeout;
-
-  /**
-   * @param port the port to start the zoo keeper server on - {@code 0} picks an ephemeral port
-   * @param shutdownRegistry a registry that will be used to register client and server shutdown
-   *     commands.  It is up to the caller to execute the registered actions at an appropriate time.
-   * @throws IOException if there was aproblem creating the server's database
-   */
-  public ZooKeeperTestServer(int port, ShutdownRegistry shutdownRegistry) throws IOException {
-    this(port, shutdownRegistry, DEFAULT_SESSION_TIMEOUT);
-  }
-
-  /**
-   * @param port the port to start the zoo keeper server on - {@code 0} picks an ephemeral port
-   * @param shutdownRegistry a registry that will be used to register client and server shutdown
-   *     commands.  It is up to the caller to execute the registered actions at an appropriate time.
-   * @param defaultSessionTimeout the default session timeout for clients created with
-   *     {@link #createClient()}.
-   * @throws IOException if there was aproblem creating the server's database
-   */
-  public ZooKeeperTestServer(int port, ShutdownRegistry shutdownRegistry,
-      Amount<Integer, Time> defaultSessionTimeout) throws IOException {
-    Preconditions.checkArgument(0 <= port && port <= 0xFFFF);
-    this.port = port;
-    this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry);
-    this.defaultSessionTimeout = Preconditions.checkNotNull(defaultSessionTimeout);
-
-    zooKeeperServer =
-        new ZooKeeperServer(
-            new FileTxnSnapLog(createTempDir(), createTempDir()),
-            new BasicDataTreeBuilder()) {
-
-          // TODO(John Sirois): Introduce a builder to configure the in-process server if and when
-          // some folks need JMX for in-process tests.
-          @Override protected void registerJMX() {
-            // noop
-          }
-        };
-  }
-
-  /**
-   * Starts zookeeper up on the configured port.  If the configured port is the ephemeral port
-   * (@{code 0}), then the actual chosen port is returned.
-   */
-  public final int startNetwork() throws IOException, InterruptedException {
-    connectionFactory = new NIOServerCnxn.Factory(new InetSocketAddress(port));
-    connectionFactory.startup(zooKeeperServer);
-    shutdownRegistry.addAction(new Command() {
-      @Override public void execute() {
-        shutdownNetwork();
-      }
-    });
-    port = zooKeeperServer.getClientPort();
-    return port;
-  }
-
-  /**
-   * Starts zookeeper back up on the last used port.
-   */
-  public final void restartNetwork() throws IOException, InterruptedException {
-    checkEphemeralPortAssigned();
-    Preconditions.checkState(!connectionFactory.isAlive());
-    startNetwork();
-  }
-
-  /**
-   * Shuts down the in-process zookeeper network server.
-   */
-  public final void shutdownNetwork() {
-    if (connectionFactory != null && connectionFactory.isAlive()) {
-      connectionFactory.shutdown();
-    }
-  }
-
-  /**
-   * Expires the active session for the given client.  The client should be one returned from
-   * {@link #createClient}.
-   *
-   * @param zkClient the client to expire
-   * @throws ZooKeeperClient.ZooKeeperConnectionException if a problem is encountered connecting to
-   *    the local zk server while trying to expire the session
-   * @throws InterruptedException if interrupted while requesting expiration
-   */
-  public final void expireClientSession(ZooKeeperClient zkClient)
-      throws ZooKeeperClient.ZooKeeperConnectionException, InterruptedException {
-    zooKeeperServer.closeSession(zkClient.get().getSessionId());
-  }
-
-  /**
-   * Returns the current port to connect to the in-process zookeeper instance.
-   */
-  public final int getPort() {
-    checkEphemeralPortAssigned();
-    return port;
-  }
-
-  /**
-   * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server
-   * with the default session timeout.
-   */
-  public final ZooKeeperClient createClient() {
-    return createClient(defaultSessionTimeout);
-  }
-
-  /**
-   * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server
-   * with the default session timeout and a custom {@code chrootPath}.
-   */
-  public final ZooKeeperClient createClient(String chrootPath) {
-    return createClient(defaultSessionTimeout, Credentials.NONE, Optional.of(chrootPath));
-  }
-
-  /**
-   * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with
-   * the default session timeout.
-   */
-  public final ZooKeeperClient createClient(Credentials credentials) {
-    return createClient(defaultSessionTimeout, credentials, Optional.<String>absent());
-  }
-
-  /**
-   * Returns a new unauthenticated zookeeper client connected to the in-process zookeeper server
-   * with a custom {@code sessionTimeout}.
-   */
-  public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout) {
-    return createClient(sessionTimeout, Credentials.NONE, Optional.<String>absent());
-  }
-
-  /**
-   * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with
-   * a custom {@code sessionTimeout}.
-   */
-  public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout,
-      Credentials credentials) {
-        return createClient(sessionTimeout, credentials, Optional.<String>absent());
-      }
-
-  /**
-   * Returns a new authenticated zookeeper client connected to the in-process zookeeper server with
-   * a custom {@code sessionTimeout} and a custom {@code chrootPath}.
-   */
-  public final ZooKeeperClient createClient(Amount<Integer, Time> sessionTimeout,
-      Credentials credentials, Optional<String> chrootPath) {
-    final ZooKeeperClient client = new ZooKeeperClient(sessionTimeout, credentials,
-        chrootPath, Arrays.asList(InetSocketAddress.createUnresolved("127.0.0.1", port)));
-    shutdownRegistry.addAction(new ExceptionalCommand<InterruptedException>() {
-      @Override public void execute() {
-        client.close();
-      }
-    });
-    return client;
-  }
-
-  private void checkEphemeralPortAssigned() {
-    Preconditions.checkState(port > 0, "startNetwork must be called first");
-  }
-
-  private File createTempDir() {
-    final File tempDir = FileUtils.createTempDir();
-    shutdownRegistry.addAction(new ExceptionalCommand<IOException>() {
-      @Override public void execute() throws IOException {
-        org.apache.commons.io.FileUtils.deleteDirectory(tempDir);
-      }
-    });
-    return tempDir;
-  }
-}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java b/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.java
new file mode 100644
index 0000000..afb3691
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/AbstractApplication.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.common.application;
+
+import java.util.Collections;
+
+import com.google.inject.Module;
+
+/**
+ * A base application class that provides empty implementations of all but the {@link #run()}
+ * method.
+ */
+public abstract class AbstractApplication implements Application {
+  @Override
+  public Iterable<? extends Module> getModules() {
+    return Collections.emptyList();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java b/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java
new file mode 100644
index 0000000..c842f42
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/AppLauncher.java
@@ -0,0 +1,202 @@
+/**
+ * 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.common.application;
+
+import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.Stage;
+import com.google.inject.util.Modules;
+
+import org.apache.aurora.common.application.modules.AppLauncherModule;
+import org.apache.aurora.common.application.modules.LifecycleModule;
+import org.apache.aurora.common.args.Arg;
+import org.apache.aurora.common.args.ArgFilters;
+import org.apache.aurora.common.args.ArgScanner;
+import org.apache.aurora.common.args.ArgScanner.ArgScanException;
+import org.apache.aurora.common.args.CmdLine;
+import org.apache.aurora.common.args.constraints.NotNull;
+import org.apache.aurora.common.base.ExceptionalCommand;
+
+/**
+ * An application launcher that sets up a framework for pluggable binding modules.  This class
+ * should be called directly as the main class, with a command line argument {@code -app_class}
+ * which is the canonical class name of the application to execute.
+ *
+ * If your application uses command line arguments all {@link Arg} fields annotated with
+ * {@link CmdLine} will be discovered and command line arguments will be validated against this set,
+ * parsed and applied.
+ *
+ * A bootstrap module will be automatically applied ({@link AppLauncherModule}), which provides
+ * overridable default bindings for things like quit/abort hooks and a health check function.
+ * A {@link LifecycleModule} is also automatically applied to perform startup and shutdown
+ * actions.
+ */
+public final class AppLauncher {
+
+  private static final Logger LOG = Logger.getLogger(AppLauncher.class.getName());
+
+  private static final String APP_CLASS_NAME = "app_class";
+  @NotNull
+  @CmdLine(name = APP_CLASS_NAME,
+           help = "Fully-qualified name of the application class, which must implement Runnable.")
+  private static final Arg<Class<? extends Application>> APP_CLASS = Arg.create();
+
+  @CmdLine(name = "guice_stage",
+           help = "Guice development stage to create injector with.")
+  private static final Arg<Stage> GUICE_STAGE = Arg.create(Stage.DEVELOPMENT);
+
+  private static final Predicate<Field> SELECT_APP_CLASS =
+      ArgFilters.selectCmdLineArg(AppLauncher.class, APP_CLASS_NAME);
+
+  @Inject @StartupStage private ExceptionalCommand startupCommand;
+  @Inject private Lifecycle lifecycle;
+
+  private AppLauncher() {
+    // This should not be invoked directly.
+  }
+
+  private void run(Application application) {
+    try {
+      configureInjection(application);
+
+      LOG.info("Executing startup actions.");
+      // We're an app framework and this is the outer shell - it makes sense to handle all errors
+      // before exiting.
+      // SUPPRESS CHECKSTYLE:OFF IllegalCatch
+      try {
+        startupCommand.execute();
+      } catch (Exception e) {
+        LOG.log(Level.SEVERE, "Startup action failed, quitting.", e);
+        throw Throwables.propagate(e);
+      }
+      // SUPPRESS CHECKSTYLE:ON IllegalCatch
+
+      try {
+        application.run();
+      } finally {
+        LOG.info("Application run() exited.");
+      }
+    } finally {
+      if (lifecycle != null) {
+        lifecycle.shutdown();
+      }
+    }
+  }
+
+  private void configureInjection(Application application) {
+    Iterable<Module> modules = ImmutableList.<Module>builder()
+        .add(new LifecycleModule())
+        .add(new AppLauncherModule())
+        .addAll(application.getModules())
+        .build();
+
+    Injector injector = Guice.createInjector(GUICE_STAGE.get(), Modules.combine(modules));
+    injector.injectMembers(this);
+    injector.injectMembers(application);
+  }
+
+  public static void main(String... args) throws IllegalAccessException, InstantiationException {
+    // TODO(John Sirois): Support a META-INF/MANIFEST.MF App-Class attribute to allow java -jar
+    parseArgs(ArgFilters.SELECT_ALL, Arrays.asList(args));
+    new AppLauncher().run(APP_CLASS.get().newInstance());
+  }
+
+  /**
+   * A convenience for main wrappers.  Equivalent to:
+   * <pre>
+   *   AppLauncher.launch(appClass, ArgFilters.SELECT_ALL, Arrays.asList(args));
+   * </pre>
+   *
+   * @param appClass The application class to instantiate and launch.
+   * @param args The command line arguments to parse.
+   * @see ArgFilters
+   */
+  public static void launch(Class<? extends Application> appClass, String... args) {
+    launch(appClass, ArgFilters.SELECT_ALL, Arrays.asList(args));
+  }
+
+  /**
+   * A convenience for main wrappers.  Equivalent to:
+   * <pre>
+   *   AppLauncher.launch(appClass, argFilter, Arrays.asList(args));
+   * </pre>
+   *
+   * @param appClass The application class to instantiate and launch.
+   * @param argFilter A filter that selects the {@literal @CmdLine} {@link Arg}s to enable for
+   *     parsing.
+   * @param args The command line arguments to parse.
+   * @see ArgFilters
+   */
+  public static void launch(Class<? extends Application> appClass, Predicate<Field> argFilter,
+      String... args) {
+    launch(appClass, argFilter, Arrays.asList(args));
+  }
+
+  /**
+   * Used to launch an application with a restricted set of {@literal @CmdLine} {@link Arg}s
+   * considered for parsing.  This is useful if the classpath includes annotated fields you do not
+   * wish arguments to be parsed for.
+   *
+   * @param appClass The application class to instantiate and launch.
+   * @param argFilter A filter that selects the {@literal @CmdLine} {@link Arg}s to enable for
+   *     parsing.
+   * @param args The command line arguments to parse.
+   * @see ArgFilters
+   */
+  public static void launch(Class<? extends Application> appClass, Predicate<Field> argFilter,
+      List<String> args) {
+    Preconditions.checkNotNull(appClass);
+    Preconditions.checkNotNull(argFilter);
+    Preconditions.checkNotNull(args);
+
+    parseArgs(Predicates.<Field>and(Predicates.not(SELECT_APP_CLASS), argFilter), args);
+    try {
+      new AppLauncher().run(appClass.newInstance());
+    } catch (InstantiationException e) {
+      throw new IllegalStateException(e);
+    } catch (IllegalAccessException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  private static void parseArgs(Predicate<Field> filter, List<String> args) {
+    try {
+      if (!new ArgScanner().parse(filter, args)) {
+        System.exit(0);
+      }
+    } catch (ArgScanException e) {
+      exit("Failed to scan arguments", e);
+    } catch (IllegalArgumentException e) {
+      exit("Failed to apply arguments", e);
+    }
+  }
+
+  private static void exit(String message, Exception error) {
+    LOG.log(Level.SEVERE, message + "\n" + error, error);
+    System.exit(1);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/Application.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/Application.java b/commons/src/main/java/org/apache/aurora/common/application/Application.java
new file mode 100644
index 0000000..ebe01e8
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/Application.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.common.application;
+
+import com.google.inject.Module;
+
+/**
+ * An application that supports a limited lifecycle and optional binding of guice modules.
+ */
+public interface Application extends Runnable {
+
+  /**
+   * Returns binding modules for the application.
+   *
+   * @return Application binding modules.
+   */
+  Iterable<? extends Module> getModules();
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java b/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java
new file mode 100644
index 0000000..5d22a2a
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/Lifecycle.java
@@ -0,0 +1,94 @@
+/**
+ * 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.common.application;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.logging.Logger;
+
+import com.google.inject.Inject;
+
+import org.apache.aurora.common.base.Command;
+
+/**
+ * Application lifecycle manager, which coordinates orderly shutdown of an application.  This class
+ * is responsible for executing shutdown commands, and can also be used to allow threads to await
+ * application shutdown.
+ *
+ * @author William Farner
+ */
+public class Lifecycle {
+
+  private static final Logger LOG = Logger.getLogger(Lifecycle.class.getName());
+
+  // Monitor and state for suspending and terminating execution.
+  private final Object waitMonitor = new Object();
+  private boolean destroyed = false;
+
+  private final Command shutdownRegistry;
+
+  @Inject
+  public Lifecycle(@ShutdownStage Command shutdownRegistry,
+      UncaughtExceptionHandler exceptionHandler) {
+
+    this.shutdownRegistry = shutdownRegistry;
+    Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
+  }
+
+  /**
+   * Checks whether this lifecycle is still considered alive.  The lifecycle is still alive until
+   * {@link #shutdown()} has been called and all of the actions registered with the shutdown
+   * controller have completed.
+   *
+   * @return {@code true} if the lifecycle is alive, {@code false} otherwise.
+   *
+   */
+  public final boolean isAlive() {
+    synchronized (waitMonitor) {
+      return !destroyed;
+    }
+  }
+
+  /**
+   * Allows a caller to wait forever; typically used when all work is done in daemon threads.
+   * Will exit on interrupts.
+   */
+  public final void awaitShutdown() {
+    LOG.info("Awaiting shutdown");
+    synchronized (waitMonitor) {
+      while (!destroyed) {
+        try {
+          waitMonitor.wait();
+        } catch (InterruptedException e) {
+          LOG.info("Exiting on interrupt");
+          shutdown();
+          return;
+        }
+      }
+    }
+  }
+
+  /**
+   * Initiates an orderly shutdown of the lifecycle's registered shutdown hooks.
+   */
+  public final void shutdown() {
+    synchronized (waitMonitor) {
+      if (!destroyed) {
+        destroyed = true;
+        LOG.info("Shutting down application");
+        shutdownRegistry.execute();
+        waitMonitor.notifyAll();
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java
new file mode 100644
index 0000000..b440e7e
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/ShutdownRegistry.java
@@ -0,0 +1,99 @@
+/**
+ * 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.common.application;
+
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.base.ExceptionalCommand;
+
+/**
+ * A shutdown action controller. It executes actions in the reverse order they were registered, and
+ * logs a warning for every shutdown action that fails, but doesn't prevent completion of subsequent
+ * actions or the normal completion of the {@code execute()} method.
+ *
+ * @author Attila Szegedi
+ */
+public interface ShutdownRegistry {
+
+  /**
+   * Adds an action to the shutdown registry.
+   *
+   * @param action Action to register.
+   * @param <E> Exception type thrown by the action.
+   * @param <T> Type of command.
+   */
+  <E extends Exception, T extends ExceptionalCommand<E>> void addAction(T action);
+
+  /**
+   * Implementation of a shutdown registry.
+   */
+  public static class ShutdownRegistryImpl implements ShutdownRegistry, Command {
+    private static final Logger LOG = Logger.getLogger(ShutdownRegistry.class.getName());
+
+    private final List<ExceptionalCommand<? extends Exception>> actions = Lists.newLinkedList();
+
+    private boolean completed = false;
+
+    /**
+     * Registers an action to execute during {@link #execute()}. It is an error to call this method
+     * after calling {@link #execute()}.
+     *
+     * @param action the action to add to the list of actions to execute during execution
+     */
+    @Override
+    public synchronized <E extends Exception, T extends ExceptionalCommand<E>> void addAction(
+        T action) {
+      Preconditions.checkState(!completed);
+      actions.add(action);
+    }
+
+    /**
+     * Executes an application shutdown stage by executing all registered actions. This method can
+     * be called multiple times but will only execute the registered actions the first time.
+     *
+     * This sends output to System.out because logging is unreliable during JVM shutdown, which
+     * this class may be used for.
+     */
+    @Override
+    public synchronized void execute() {
+      if (!completed) {
+        LOG.info(String.format("Executing %d shutdown commands.", actions.size()));
+        completed = true;
+        try {
+          for (ExceptionalCommand<? extends Exception> action : Lists.reverse(actions)) {
+            // Part of our contract is ensuring each shutdown action executes so we must catch all
+            // exceptions.
+            // SUPPRESS CHECKSTYLE:OFF IllegalCatch
+            try {
+              action.execute();
+            } catch (Exception e) {
+              LOG.log(Level.WARNING, "Shutdown action failed.", e);
+            }
+            // SUPPRESS CHECKSTYLE:ON IllegalCatch
+          }
+        } finally {
+          actions.clear();
+        }
+      } else {
+        LOG.info("Action controller has already completed, subsequent calls ignored.");
+      }
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java b/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java
new file mode 100644
index 0000000..95f9c58
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/ShutdownStage.java
@@ -0,0 +1,31 @@
+/**
+ * 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.common.application;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Binding annotation used for the shutdown registry.
+ */
+@BindingAnnotation
+@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
+public @interface ShutdownStage { }

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java
new file mode 100644
index 0000000..997ee77
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/StartupRegistry.java
@@ -0,0 +1,52 @@
+/**
+ * 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.common.application;
+
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.logging.Logger;
+
+import com.google.common.base.Preconditions;
+import com.google.inject.Inject;
+
+import org.apache.aurora.common.base.ExceptionalCommand;
+
+/**
+ * A registry that executes a set of commands.  The registry will synchronously execute commands
+ * when {@link #execute()} is invoked, returning early if any action throws an exception.
+ * Only one call to {@link #execute()} will have an effect, all subsequent calls will be ignored.
+ */
+public class StartupRegistry implements ExceptionalCommand<Exception> {
+
+  private static final Logger LOG = Logger.getLogger(StartupRegistry.class.getName());
+
+  private final Set<ExceptionalCommand> startupActions;
+  private final AtomicBoolean started = new AtomicBoolean(false);
+
+  @Inject
+  public StartupRegistry(@StartupStage Set<ExceptionalCommand> startupActions) {
+    this.startupActions = Preconditions.checkNotNull(startupActions);
+  }
+
+  @Override
+  public void execute() throws Exception {
+    if (!started.compareAndSet(false, true)) {
+      LOG.warning("Startup actions cannot be executed more than once, ignoring.");
+    }
+
+    for (ExceptionalCommand<?> startupAction : startupActions) {
+      startupAction.execute();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java b/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java
new file mode 100644
index 0000000..8050901
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/StartupStage.java
@@ -0,0 +1,31 @@
+/**
+ * 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.common.application;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.inject.BindingAnnotation;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Binding annotation used for the startup registry.
+ */
+@BindingAnnotation
+@Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
+public @interface StartupStage { }

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java b/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java
new file mode 100644
index 0000000..6a48b84
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/DefaultQuitHandler.java
@@ -0,0 +1,43 @@
+/**
+ * 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.common.application.http;
+
+import java.util.logging.Logger;
+
+import com.google.inject.Inject;
+
+import org.apache.aurora.common.application.Lifecycle;
+
+/**
+ * The default quit handler to use, which invokes {@link Lifecycle#shutdown()}.
+ *
+ * @author William Farner
+ */
+public class DefaultQuitHandler implements Runnable {
+
+  private static final Logger LOG = Logger.getLogger(DefaultQuitHandler.class.getName());
+
+  private final Lifecycle lifecycle;
+
+  @Inject
+  public DefaultQuitHandler(Lifecycle lifecycle) {
+    this.lifecycle = lifecycle;
+  }
+
+  @Override
+  public void run() {
+    LOG.info("Instructing lifecycle to destroy.");
+    lifecycle.shutdown();
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java b/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java
new file mode 100644
index 0000000..f786f9e
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/GraphViewer.java
@@ -0,0 +1,50 @@
+/**
+ * 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.common.application.http;
+
+import com.google.inject.Binder;
+
+/**
+ * A utility class to register the file resources for the graph viewer.
+ */
+public final class GraphViewer {
+
+  private GraphViewer() {
+    // Utility class.
+  }
+
+  private static void registerJs(Binder binder, String assetName) {
+    Registration.registerHttpAsset(
+        binder,
+        "/graphview/" + assetName,
+        GraphViewer.class,
+        "graphview/" + assetName,
+        "application/javascript",
+        true);
+  }
+
+  /**
+   * Registers required resources with the binder.
+   *
+   * @param binder Binder to register with.
+   */
+  public static void registerResources(Binder binder) {
+    registerJs(binder, "dygraph-combined.js");
+    registerJs(binder, "dygraph-extra.js");
+    registerJs(binder, "grapher.js");
+    registerJs(binder, "parser.js");
+    Registration.registerHttpAsset(binder,
+        "/graphview", GraphViewer.class, "graphview/graphview.html", "text/html", false);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java
new file mode 100644
index 0000000..e2160e7
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpAssetConfig.java
@@ -0,0 +1,51 @@
+/**
+ * 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.common.application.http;
+
+import java.net.URL;
+
+import com.google.common.io.Resources;
+
+import org.apache.aurora.common.net.http.handlers.AssetHandler;
+import org.apache.aurora.common.net.http.handlers.AssetHandler.StaticAsset;
+
+import static org.apache.aurora.common.base.MorePreconditions.checkNotBlank;
+
+/**
+ * Configuration for a static HTTP-served asset.
+ *
+ * TODO(William Farner): Move this to a more appropriate package after initial AppLauncher check-in.
+ *
+ * @author William Farner
+ */
+public class HttpAssetConfig {
+  public final String path;
+  public final AssetHandler handler;
+  public final boolean silent;
+
+  /**
+   * Creates a new asset configuration.
+   *
+   * @param path HTTP path the asset should be accessible from.
+   * @param asset Asset resource URL.
+   * @param contentType HTTP content-type to report for the asset.
+   * @param silent Whether the asset should be visible on the default index page.
+   */
+  public HttpAssetConfig(String path, URL asset, String contentType, boolean silent) {
+    this.path = checkNotBlank(path);
+    this.handler = new AssetHandler(
+        new StaticAsset(Resources.newInputStreamSupplier(asset), contentType, true));
+    this.silent = silent;
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java
new file mode 100644
index 0000000..d096317
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpFilterConfig.java
@@ -0,0 +1,39 @@
+/**
+ * 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.common.application.http;
+
+import javax.servlet.Filter;
+
+import org.apache.aurora.common.base.MorePreconditions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Configuration tuple for an HTTP filter.
+ */
+public class HttpFilterConfig {
+  public final Class<? extends Filter> filterClass;
+  public final String pathSpec;
+
+  /**
+   * Creates a new filter configuration.
+   *
+   * @param filterClass Filter class.
+   * @param pathSpec Path spec that the filter should match.
+   */
+  public HttpFilterConfig(Class<? extends Filter> filterClass, String pathSpec) {
+    this.pathSpec = MorePreconditions.checkNotBlank(pathSpec);
+    this.filterClass = checkNotNull(filterClass);
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java b/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java
new file mode 100644
index 0000000..4444e4c
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/HttpServletConfig.java
@@ -0,0 +1,65 @@
+/**
+ * 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.common.application.http;
+
+import javax.servlet.http.HttpServlet;
+
+import com.google.common.collect.ImmutableMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import static org.apache.aurora.common.base.MorePreconditions.checkNotBlank;
+
+/**
+ * An {@link javax.servlet.http.HttpServlet} configuration used to mount HTTP handlers via
+ * {@link Registration#registerServlet(com.google.inject.Binder, HttpServletConfig)}.
+ *
+ * TODO(William Farner): Move this to a more appropriate package after initial AppLauncher check-in.
+ *
+ */
+public class HttpServletConfig {
+  public final String path;
+  public final Class<? extends HttpServlet> handlerClass;
+  public final ImmutableMap<String, String> params;
+  public final boolean silent;
+
+  /**
+   * Creates a new servlet config.
+   *
+   * @param path the absolute path to mount the handler on
+   * @param servletClass the type of servlet that will render pages at {@code path}
+   * @param silent whether or not to display a link for this handler on the landing page
+   */
+  public HttpServletConfig(String path, Class<? extends HttpServlet> servletClass,
+      boolean silent) {
+    this(path, servletClass, ImmutableMap.<String, String>of(), silent);
+  }
+
+  /**
+   * Registers a new servlet config with servlet initialization parameters.
+   *
+   * @param path the absolute path to mount the handler on
+   * @param servletClass the type of servlet that will render pages at {@code path}
+   * @param params a map of servlet init parameters to initialize the servlet with
+   * @param silent whether or not to display a link for this handler on the landing page
+   */
+  public HttpServletConfig(String path, Class<? extends HttpServlet> servletClass,
+      ImmutableMap<String, String> params, boolean silent) {
+
+    this.path = checkNotBlank(path);
+    this.handlerClass = checkNotNull(servletClass);
+    this.params = checkNotNull(params);
+    this.silent = silent;
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java b/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java
new file mode 100644
index 0000000..b66c8c8
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/http/Registration.java
@@ -0,0 +1,155 @@
+/**
+ * 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.common.application.http;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.net.URL;
+
+import javax.servlet.Filter;
+import javax.servlet.http.HttpServlet;
+
+import com.google.common.io.Resources;
+import com.google.inject.Binder;
+import com.google.inject.BindingAnnotation;
+import com.google.inject.multibindings.Multibinder;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * Utility class for registering HTTP servlets and assets.
+ */
+public final class Registration {
+
+  private Registration() {
+    // Utility class.
+  }
+
+  /**
+   * Equivalent to
+   * {@code registerServlet(binder, new HttpServletConfig(path, servletClass, silent))}.
+   */
+  public static void registerServlet(Binder binder, String path,
+      Class<? extends HttpServlet> servletClass, boolean silent) {
+    registerServlet(binder, new HttpServletConfig(path, servletClass, silent));
+  }
+
+  /**
+   * Registers a binding for an {@link javax.servlet.http.HttpServlet} to be exported at a specified
+   * path.
+   *
+   * @param binder a guice binder to register the handler with
+   * @param config a servlet mounting specification
+   */
+  public static void registerServlet(Binder binder, HttpServletConfig config) {
+    Multibinder.newSetBinder(binder, HttpServletConfig.class).addBinding().toInstance(config);
+  }
+
+  /**
+   * A binding annotation applied to the set of additional index page links bound via
+   * {@link #Registration#registerEndpoint()}
+   */
+  @BindingAnnotation
+  @Target({FIELD, PARAMETER, METHOD})
+  @Retention(RUNTIME)
+  public @interface IndexLink { }
+
+  /**
+   * Gets the multibinder used to bind links on the root servlet.
+   * The resulting {@link java.util.Set} is bound with the {@link IndexLink} annotation.
+   *
+   * @param binder a guice binder to associate the multibinder with.
+   * @return The multibinder to bind index links against.
+   */
+  public static Multibinder<String> getEndpointBinder(Binder binder) {
+    return Multibinder.newSetBinder(binder, String.class, IndexLink.class);
+  }
+
+  /**
+   * Registers a link to display on the root servlet.
+   *
+   * @param binder a guice binder to register the link with.
+   * @param endpoint Endpoint URI to include.
+   */
+  public static void registerEndpoint(Binder binder, String endpoint) {
+    getEndpointBinder(binder).addBinding().toInstance(endpoint);
+  }
+
+  /**
+   * Registers a binding for a URL asset to be served by the HTTP server, with an optional
+   * entity tag for cache control.
+   *
+   * @param binder a guice binder to register the handler with
+   * @param servedPath Path to serve the resource from in the HTTP server.
+   * @param asset Resource to be served.
+   * @param assetType MIME-type for the asset.
+   * @param silent Whether the server should hide this asset on the index page.
+   */
+  public static void registerHttpAsset(Binder binder, String servedPath, URL asset,
+      String assetType, boolean silent) {
+    Multibinder.newSetBinder(binder, HttpAssetConfig.class).addBinding().toInstance(
+        new HttpAssetConfig(servedPath, asset, assetType, silent));
+  }
+
+  /**
+   * Registers a binding for a classpath resource to be served by the HTTP server, using a resource
+   * path relative to a class.
+   *
+   * @param binder a guice binder to register the handler with
+   * @param servedPath Path to serve the asset from in the HTTP server.
+   * @param contextClass Context class for defining the relative path to the asset.
+   * @param assetRelativePath Path to the served asset, relative to {@code contextClass}.
+   * @param assetType MIME-type for the asset.
+   * @param silent Whether the server should hide this asset on the index page.
+   */
+  public static void registerHttpAsset(
+      Binder binder,
+      String servedPath,
+      Class<?> contextClass,
+      String assetRelativePath,
+      String assetType,
+      boolean silent) {
+
+    registerHttpAsset(binder, servedPath, Resources.getResource(contextClass, assetRelativePath),
+        assetType, silent);
+  }
+
+  /**
+   * Gets the multibinder used to bind HTTP filters.
+   *
+   * @param binder a guice binder to associate the multibinder with.
+   * @return The multibinder to bind HTTP filter configurations against.
+   */
+  public static Multibinder<HttpFilterConfig> getFilterBinder(Binder binder) {
+    return Multibinder.newSetBinder(binder, HttpFilterConfig.class);
+  }
+
+  /**
+   * Registers an HTTP servlet filter.
+   *
+   * @param binder a guice binder to register the filter with.
+   * @param filterClass Filter class to register.
+   * @param pathSpec Path spec that the filter should be activated on.
+   */
+  public static void registerServletFilter(
+      Binder binder,
+      Class<? extends Filter> filterClass,
+      String pathSpec) {
+
+    getFilterBinder(binder).addBinding().toInstance(new HttpFilterConfig(filterClass, pathSpec));
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java
new file mode 100644
index 0000000..65e9250
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/AppLauncherModule.java
@@ -0,0 +1,52 @@
+/**
+ * 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.common.application.modules;
+
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
+
+import org.apache.aurora.common.stats.Stats;
+import org.apache.aurora.common.util.BuildInfo;
+
+import org.apache.aurora.common.application.AppLauncher;
+
+/**
+ * Binding module for the bare minimum requirements for the
+ * {@link AppLauncher}.
+ *
+ * @author William Farner
+ */
+public class AppLauncherModule extends AbstractModule {
+
+  private static final Logger LOG = Logger.getLogger(AppLauncherModule.class.getName());
+  private static final AtomicLong UNCAUGHT_EXCEPTIONS = Stats.exportLong("uncaught_exceptions");
+
+  @Override
+  protected void configure() {
+    bind(BuildInfo.class).in(Singleton.class);
+    bind(UncaughtExceptionHandler.class).to(LoggingExceptionHandler.class);
+  }
+
+  public static class LoggingExceptionHandler implements UncaughtExceptionHandler {
+    @Override public void uncaughtException(Thread t, Throwable e) {
+      UNCAUGHT_EXCEPTIONS.incrementAndGet();
+      LOG.log(Level.SEVERE, "Uncaught exception from " + t + ":" + e, e);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java
new file mode 100644
index 0000000..b3e59e7
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LifecycleModule.java
@@ -0,0 +1,194 @@
+/**
+ * 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.common.application.modules;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.inject.AbstractModule;
+import com.google.inject.Binder;
+import com.google.inject.BindingAnnotation;
+import com.google.inject.Inject;
+import com.google.inject.Key;
+import com.google.inject.Singleton;
+import com.google.inject.multibindings.Multibinder;
+
+import org.apache.aurora.common.application.Lifecycle;
+import org.apache.aurora.common.application.ShutdownRegistry;
+import org.apache.aurora.common.application.ShutdownStage;
+import org.apache.aurora.common.application.StartupRegistry;
+import org.apache.aurora.common.application.StartupStage;
+
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.base.ExceptionalCommand;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Binding module for startup and shutdown controller and registries.
+ *
+ * Bindings provided by this module:
+ * <ul>
+ *   <li>{@code @StartupStage ExceptionalCommand} - Command to execute all startup actions.
+ *   <li>{@code ShutdownRegistry} - Registry for adding shutdown actions.
+ *   <li>{@code @ShutdownStage Command} - Command to execute all shutdown commands.
+ * </ul>
+ *
+ * If you would like to register a startup action that starts a local network service, please
+ * consider using {@link LocalServiceRegistry}.
+ *
+ * @author William Farner
+ */
+public class LifecycleModule extends AbstractModule {
+
+  /**
+   * Binding annotation used for local services.
+   * This is used to ensure the LocalService bindings are visibile within the package only, to
+   * prevent injection inadvertently triggering a service launch.
+   */
+  @BindingAnnotation
+  @Target({ FIELD, PARAMETER, METHOD }) @Retention(RUNTIME)
+  @interface Service { }
+
+  @Override
+  protected void configure() {
+    bind(Lifecycle.class).in(Singleton.class);
+
+    bind(Key.get(ExceptionalCommand.class, StartupStage.class)).to(StartupRegistry.class);
+    bind(StartupRegistry.class).in(Singleton.class);
+
+    bind(ShutdownRegistry.class).to(ShutdownRegistry.ShutdownRegistryImpl.class);
+    bind(Key.get(Command.class, ShutdownStage.class)).to(ShutdownRegistry.ShutdownRegistryImpl.class);
+    bind(ShutdownRegistry.ShutdownRegistryImpl.class).in(Singleton.class);
+    bindStartupAction(binder(), ShutdownHookRegistration.class);
+
+    bind(LocalServiceRegistry.class).in(Singleton.class);
+
+    // Ensure that there is at least an empty set for the service runners.
+    runnerBinder(binder());
+
+    bindStartupAction(binder(), LocalServiceLauncher.class);
+  }
+
+  /**
+   * Thrown when a local service fails to launch.
+   */
+  public static class LaunchException extends Exception {
+    public LaunchException(String msg) {
+      super(msg);
+    }
+
+    public LaunchException(String msg, Throwable cause) {
+      super(msg, cause);
+    }
+  }
+
+  /**
+   * Responsible for starting and stopping a local service.
+   */
+  public interface ServiceRunner {
+
+    /**
+     * Launches the local service.
+     *
+     * @return Information about the launched service.
+     * @throws LaunchException If the service failed to launch.
+     */
+    LocalServiceRegistry.LocalService launch() throws LaunchException;
+  }
+
+  @VisibleForTesting
+  static Multibinder<ServiceRunner> runnerBinder(Binder binder) {
+    return Multibinder.newSetBinder(binder, ServiceRunner.class, Service.class);
+  }
+
+  /**
+   * Binds a service runner that will start and stop a local service.
+   *
+   * @param binder Binder to bind against.
+   * @param launcher Launcher class for a service.
+   */
+  public static void bindServiceRunner(Binder binder, Class<? extends ServiceRunner> launcher) {
+    runnerBinder(binder).addBinding().to(launcher);
+    binder.bind(launcher).in(Singleton.class);
+  }
+
+  /**
+   * Binds a local service instance, without attaching an explicit lifecycle.
+   *
+   * @param binder Binder to bind against.
+   * @param service Local service instance to bind.
+   */
+  public static void bindLocalService(Binder binder, final LocalServiceRegistry.LocalService service) {
+    runnerBinder(binder).addBinding().toInstance(
+        new ServiceRunner() {
+          @Override public LocalServiceRegistry.LocalService launch() {
+            return service;
+          }
+        });
+  }
+
+  /**
+   * Adds a startup action to the startup registry binding.
+   *
+   * @param binder Binder to bind against.
+   * @param actionClass Class to bind (and instantiate via guice) for execution at startup.
+   */
+  public static void bindStartupAction(Binder binder,
+      Class<? extends ExceptionalCommand> actionClass) {
+
+    Multibinder.newSetBinder(binder, ExceptionalCommand.class, StartupStage.class)
+        .addBinding().to(actionClass);
+  }
+
+  /**
+   * Startup command to register the shutdown registry as a process shutdown hook.
+   */
+  private static class ShutdownHookRegistration implements Command {
+    private final Command shutdownCommand;
+
+    @Inject ShutdownHookRegistration(@ShutdownStage Command shutdownCommand) {
+      this.shutdownCommand = checkNotNull(shutdownCommand);
+    }
+
+    @Override public void execute() {
+      Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
+        @Override public void run() {
+          shutdownCommand.execute();
+        }
+      }, "ShutdownRegistry-Hook"));
+    }
+  }
+
+  /**
+   * Startup command that ensures startup and shutdown of local services.
+   */
+  private static class LocalServiceLauncher implements Command {
+    private final LocalServiceRegistry serviceRegistry;
+
+    @Inject LocalServiceLauncher(LocalServiceRegistry serviceRegistry) {
+      this.serviceRegistry = checkNotNull(serviceRegistry);
+    }
+
+    @Override public void execute() {
+      serviceRegistry.ensureLaunched();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java
new file mode 100644
index 0000000..027e4a7
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LocalServiceRegistry.java
@@ -0,0 +1,258 @@
+/**
+ * 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.common.application.modules;
+
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.collect.FluentIterable;
+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.Maps;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.apache.aurora.common.application.ShutdownRegistry;
+import org.apache.commons.lang.builder.ToStringBuilder;
+
+import org.apache.aurora.common.application.modules.LifecycleModule.LaunchException;
+import org.apache.aurora.common.application.modules.LifecycleModule.Service;
+import org.apache.aurora.common.application.modules.LifecycleModule.ServiceRunner;
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.base.MorePreconditions;
+import org.apache.aurora.common.net.InetSocketAddressHelper;
+
+/**
+ * Registry for services that should be exported from the application.
+ *
+ * Example of announcing and registering a port:
+ * <pre>
+ * class MyLauncher implements Provider<LocalService> {
+ *   public LocalService get() {
+ *     // Launch service.
+ *   }
+ * }
+ *
+ * class MyServiceModule extends AbstractModule {
+ *   public void configure() {
+ *     LifeCycleModule.bindServiceLauncher(binder(), MyLauncher.class);
+ *   }
+ * }
+ * </pre>
+ */
+public class LocalServiceRegistry {
+
+  private static final Predicate<LocalService> IS_PRIMARY = new Predicate<LocalService>() {
+    @Override public boolean apply(LocalService service) {
+      return service.primary;
+    }
+  };
+
+  private static final Function<LocalService, InetSocketAddress> SERVICE_TO_SOCKET =
+      new Function<LocalService, InetSocketAddress>() {
+        @Override public InetSocketAddress apply(LocalService service) {
+          try {
+            return InetSocketAddressHelper.getLocalAddress(service.port);
+          } catch (UnknownHostException e) {
+            throw new RuntimeException("Failed to resolve local address for " + service, e);
+          }
+        }
+      };
+
+  private static final Function<LocalService, String> GET_NAME =
+      new Function<LocalService, String>() {
+        @Override public String apply(LocalService service) {
+          return Iterables.getOnlyElement(service.names);
+        }
+      };
+
+  private final ShutdownRegistry shutdownRegistry;
+  private final Provider<Set<ServiceRunner>> runnerProvider;
+
+  private Optional<InetSocketAddress> primarySocket = null;
+  private Map<String, InetSocketAddress> auxiliarySockets = null;
+
+  /**
+   * Creates a new local service registry.
+   *
+   * @param runnerProvider provider of registered local services.
+   * @param shutdownRegistry Shutdown registry to tear down launched services.
+   */
+  @Inject
+  public LocalServiceRegistry(@Service Provider<Set<ServiceRunner>> runnerProvider,
+      ShutdownRegistry shutdownRegistry) {
+    this.runnerProvider = Preconditions.checkNotNull(runnerProvider);
+    this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry);
+  }
+
+  private static final Function<LocalService, Iterable<LocalService>> AUX_NAME_BREAKOUT =
+      new Function<LocalService, Iterable<LocalService>>() {
+        @Override public Iterable<LocalService> apply(final LocalService service) {
+          Preconditions.checkArgument(!service.primary);
+          Function<String, LocalService> oneNameService = new Function<String, LocalService>() {
+            @Override public LocalService apply(String name) {
+              return LocalService.auxiliaryService(name, service.port, service.shutdownCommand);
+            }
+          };
+          return Iterables.transform(service.names, oneNameService);
+        }
+      };
+
+  /**
+   * Launches the local services if not already launched, otherwise this is a no-op.
+   */
+  void ensureLaunched() {
+    if (primarySocket == null) {
+      ImmutableList.Builder<LocalService> builder = ImmutableList.builder();
+
+      for (ServiceRunner runner : runnerProvider.get()) {
+        try {
+          LocalService service = runner.launch();
+          builder.add(service);
+          shutdownRegistry.addAction(service.shutdownCommand);
+        } catch (LaunchException e) {
+          throw new IllegalStateException("Failed to launch " + runner, e);
+        }
+      }
+
+      List<LocalService> localServices = builder.build();
+      Iterable<LocalService> primaries = Iterables.filter(localServices, IS_PRIMARY);
+      switch (Iterables.size(primaries)) {
+        case 0:
+          primarySocket = Optional.absent();
+          break;
+
+        case 1:
+          primarySocket = Optional.of(SERVICE_TO_SOCKET.apply(Iterables.getOnlyElement(primaries)));
+          break;
+
+        default:
+          throw new IllegalArgumentException("More than one primary local service: " + primaries);
+      }
+
+      Iterable<LocalService> auxSinglyNamed = Iterables.concat(
+          FluentIterable.from(localServices)
+              .filter(Predicates.not(IS_PRIMARY))
+              .transform(AUX_NAME_BREAKOUT));
+
+      Map<String, LocalService> byName;
+      try {
+        byName = Maps.uniqueIndex(auxSinglyNamed, GET_NAME);
+      } catch (IllegalArgumentException e) {
+        throw new IllegalArgumentException("Auxiliary services with identical names.", e);
+      }
+
+      auxiliarySockets = ImmutableMap.copyOf(Maps.transformValues(byName, SERVICE_TO_SOCKET));
+    }
+  }
+
+  /**
+   * Gets the mapping from auxiliary port name to socket.
+   *
+   * @return Auxiliary port mapping.
+   */
+  public synchronized Map<String, InetSocketAddress> getAuxiliarySockets() {
+    ensureLaunched();
+    return auxiliarySockets;
+  }
+
+  /**
+   * Gets the optional primary socket address, and returns an unresolved local socket address
+   * representing that port.
+   *
+   * @return Local socket address for the primary port.
+   * @throws IllegalStateException If the primary port was not set.
+   */
+  public synchronized Optional<InetSocketAddress> getPrimarySocket() {
+    ensureLaunched();
+    return primarySocket;
+  }
+
+  /**
+   * An individual local service.
+   */
+  public static final class LocalService {
+    private final boolean primary;
+    private final Set<String> names;
+    private final int port;
+    private final Command shutdownCommand;
+
+    private LocalService(boolean primary, Set<String> names, int port,
+        Command shutdownCommand) {
+      this.primary = primary;
+      this.names = names;
+      this.port = port;
+      this.shutdownCommand = Preconditions.checkNotNull(shutdownCommand);
+    }
+
+    @Override
+    public String toString() {
+      return new ToStringBuilder(this)
+          .append("primary", primary)
+          .append("name", names)
+          .append("port", port)
+          .toString();
+    }
+
+    /**
+     * Creates a primary local service.
+     *
+     * @param port Service port.
+     * @param shutdownCommand A command that will shut down the service.
+     * @return A new primary local service.
+     */
+    public static LocalService primaryService(int port, Command shutdownCommand) {
+      return new LocalService(true, ImmutableSet.<String>of(), port, shutdownCommand);
+    }
+
+    /**
+     * Creates a named auxiliary service.
+     *
+     * @param name Service name.
+     * @param port Service port.
+     * @param shutdownCommand A command that will shut down the service.
+     * @return A new auxiliary local service.
+     */
+    public static LocalService auxiliaryService(String name, int port, Command shutdownCommand) {
+      return auxiliaryService(ImmutableSet.of(name), port, shutdownCommand);
+    }
+
+    /**
+     * Creates an auxiliary service identified by multiple names.
+     *
+     * @param names Service names.
+     * @param port Service port.
+     * @param shutdownCommand A command that will shut down the service.
+     * @return A new auxiliary local service.
+     */
+    public static LocalService auxiliaryService(
+        Set<String> names,
+        int port,
+        Command shutdownCommand) {
+
+      MorePreconditions.checkNotBlank(names);
+      return new LocalService(false, names, port, shutdownCommand);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java
new file mode 100644
index 0000000..107e3aa
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/LogModule.java
@@ -0,0 +1,117 @@
+/**
+ * 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.common.application.modules;
+
+import java.io.File;
+import java.util.logging.Logger;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+
+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.args.constraints.IsDirectory;
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.logging.LogUtil;
+import org.apache.aurora.common.logging.RootLogConfig;
+import org.apache.aurora.common.logging.RootLogConfig.Configuration;
+import org.apache.aurora.common.net.http.handlers.LogPrinter;
+import org.apache.aurora.common.stats.StatImpl;
+import org.apache.aurora.common.stats.Stats;
+
+/**
+ * Binding module for logging-related bindings, such as the log directory.
+ *
+ * This module uses a single optional command line argument 'log_dir'.  If unset, the logging
+ * directory will be auto-discovered via:
+ * {@link LogUtil#getLogManagerLogDir()}.
+ *
+ * Bindings provided by this module:
+ * <ul>
+ *   <li>{@code @Named(LogPrinter.LOG_DIR_KEY) File} - Log directory.
+ *   <li>{@code Optional&lt;RootLogConfig.Configuraton&gt;} - If glog is enabled the configuration
+ *       used.
+ * </ul>
+ *
+ * Default bindings that may be overridden:
+ * <ul>
+ *   <li>Log directory: directory where application logs are written.  May be overridden by binding
+ *       to: {@code bind(File.class).annotatedWith(Names.named(LogPrinter.LOG_DIR_KEY))}.
+ * </ul>
+ *
+ * @author William Farner
+ */
+public class LogModule extends AbstractModule {
+
+  private static final Logger LOG = Logger.getLogger(LogModule.class.getName());
+
+  @Exists
+  @CanRead
+  @IsDirectory
+  @CmdLine(name = "log_dir",
+           help = "The directory where application logs are written.")
+  private static final Arg<File> LOG_DIR = Arg.create(null);
+
+  @CmdLine(name = "use_glog",
+           help = "True to use the new glog-based configuration for the root logger.")
+  private static final Arg<Boolean> USE_GLOG = Arg.create(true);
+
+  @Override
+  protected void configure() {
+    // Bind the default log directory.
+    bind(File.class).annotatedWith(Names.named(LogPrinter.LOG_DIR_KEY)).toInstance(getLogDir());
+
+    LifecycleModule.bindStartupAction(binder(), ExportLogDir.class);
+
+    Configuration configuration = null;
+    if (USE_GLOG.get()) {
+      configuration = RootLogConfig.configurationFromFlags();
+      configuration.apply();
+    }
+    bind(new TypeLiteral<Optional<Configuration>>() { })
+        .toInstance(Optional.fromNullable(configuration));
+  }
+
+  private File getLogDir() {
+    File logDir = LOG_DIR.get();
+    if (logDir == null) {
+      logDir = LogUtil.getLogManagerLogDir();
+      LOG.info("From logging properties, parsed log directory " + logDir.getAbsolutePath());
+    }
+    return logDir;
+  }
+
+  public static final class ExportLogDir implements Command {
+    private final File logDir;
+
+    @Inject ExportLogDir(@Named(LogPrinter.LOG_DIR_KEY) final File logDir) {
+      this.logDir = Preconditions.checkNotNull(logDir);
+    }
+
+    @Override public void execute() {
+      Stats.exportStatic(new StatImpl<String>("logging_dir") {
+        @Override public String read() {
+          return logDir.getAbsolutePath();
+        }
+      });
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java
new file mode 100644
index 0000000..ba93d4c
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsExportModule.java
@@ -0,0 +1,85 @@
+/**
+ * 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.common.application.modules;
+
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+
+import org.apache.aurora.common.application.ShutdownRegistry;
+import org.apache.aurora.common.args.Arg;
+import org.apache.aurora.common.args.CmdLine;
+import org.apache.aurora.common.base.Closure;
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.quantity.Amount;
+import org.apache.aurora.common.quantity.Time;
+import org.apache.aurora.common.stats.NumericStatExporter;
+
+/**
+ * Module to enable periodic exporting of registered stats to an external service.
+ *
+ * This modules supports a single command line argument, {@code stat_export_interval}, which
+ * controls the export interval (defaulting to 1 minute).
+ *
+ * Bindings required by this module:
+ * <ul>
+ *   <li>{@code @ShutdownStage ShutdownRegistry} - Shutdown action registry.
+ * </ul>
+ *
+ * @author William Farner
+ */
+public class StatsExportModule extends AbstractModule {
+
+  @CmdLine(name = "stat_export_interval",
+           help = "Amount of time to wait between stat exports.")
+  private static final Arg<Amount<Long, Time>> EXPORT_INTERVAL =
+      Arg.create(Amount.of(1L, Time.MINUTES));
+
+  @Override
+  protected void configure() {
+    requireBinding(Key.get(new TypeLiteral<Closure<Map<String, ? extends Number>>>() { }));
+    LifecycleModule.bindStartupAction(binder(), StartCuckooExporter.class);
+  }
+
+  public static final class StartCuckooExporter implements Command {
+
+    private final Closure<Map<String, ? extends Number>> statSink;
+    private final ShutdownRegistry shutdownRegistry;
+
+    @Inject StartCuckooExporter(
+        Closure<Map<String, ? extends Number>> statSink,
+        ShutdownRegistry shutdownRegistry) {
+
+      this.statSink = Preconditions.checkNotNull(statSink);
+      this.shutdownRegistry = Preconditions.checkNotNull(shutdownRegistry);
+    }
+
+    @Override public void execute() {
+      ThreadFactory threadFactory =
+          new ThreadFactoryBuilder().setNameFormat("CuckooExporter-%d").setDaemon(true).build();
+
+      final NumericStatExporter exporter = new NumericStatExporter(statSink,
+          Executors.newScheduledThreadPool(1, threadFactory), EXPORT_INTERVAL.get());
+
+      exporter.start(shutdownRegistry);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java
new file mode 100644
index 0000000..78d9512
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/StatsModule.java
@@ -0,0 +1,146 @@
+/**
+ * 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.common.application.modules;
+
+import java.util.Properties;
+import java.util.logging.Logger;
+
+import com.google.common.base.Supplier;
+import com.google.common.primitives.Longs;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+
+import org.apache.aurora.common.application.ShutdownRegistry;
+import org.apache.aurora.common.args.Arg;
+import org.apache.aurora.common.args.CmdLine;
+import org.apache.aurora.common.base.Command;
+import org.apache.aurora.common.quantity.Amount;
+import org.apache.aurora.common.quantity.Time;
+import org.apache.aurora.common.stats.JvmStats;
+import org.apache.aurora.common.stats.Stat;
+import org.apache.aurora.common.stats.StatImpl;
+import org.apache.aurora.common.stats.StatRegistry;
+import org.apache.aurora.common.stats.Stats;
+import org.apache.aurora.common.stats.TimeSeriesRepository;
+import org.apache.aurora.common.stats.TimeSeriesRepositoryImpl;
+import org.apache.aurora.common.util.BuildInfo;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Binding module for injections related to the in-process stats system.
+ *
+ * This modules supports two command line arguments:
+ * <ul>
+ *   <li>{@code stat_sampling_interval} - Statistic value sampling interval.
+ *   <li>{@code stat_retention_period} - Time for a stat to be retained in memory before expring.
+ * </ul>
+ *
+ * Bindings required by this module:
+ * <ul>
+ *   <li>{@code ShutdownRegistry} - Shutdown hook registry.
+ *   <li>{@code BuildInfo} - Build information for the application.
+ * </ul>
+ *
+ * @author William Farner
+ */
+public class StatsModule extends AbstractModule {
+
+  @CmdLine(name = "stat_sampling_interval", help = "Statistic value sampling interval.")
+  private static final Arg<Amount<Long, Time>> SAMPLING_INTERVAL =
+      Arg.create(Amount.of(1L, Time.SECONDS));
+
+  @CmdLine(name = "stat_retention_period",
+      help = "Time for a stat to be retained in memory before expiring.")
+  private static final Arg<Amount<Long, Time>> RETENTION_PERIOD =
+      Arg.create(Amount.of(1L, Time.HOURS));
+
+  public static Amount<Long, Time> getSamplingInterval() {
+    return SAMPLING_INTERVAL.get();
+  }
+
+  @Override
+  protected void configure() {
+    requireBinding(ShutdownRegistry.class);
+    requireBinding(BuildInfo.class);
+
+    // Bindings for TimeSeriesRepositoryImpl.
+    bind(StatRegistry.class).toInstance(Stats.STAT_REGISTRY);
+    bind(new TypeLiteral<Amount<Long, Time>>() { })
+        .annotatedWith(Names.named(TimeSeriesRepositoryImpl.SAMPLE_RETENTION_PERIOD))
+        .toInstance(RETENTION_PERIOD.get());
+    bind(new TypeLiteral<Amount<Long, Time>>() { })
+        .annotatedWith(Names.named(TimeSeriesRepositoryImpl.SAMPLE_PERIOD))
+        .toInstance(SAMPLING_INTERVAL.get());
+    bind(TimeSeriesRepository.class).to(TimeSeriesRepositoryImpl.class).in(Singleton.class);
+
+    bind(new TypeLiteral<Supplier<Iterable<Stat<?>>>>() { }).toInstance(
+        new Supplier<Iterable<Stat<?>>>() {
+          @Override public Iterable<Stat<?>> get() {
+            return Stats.getVariables();
+          }
+        }
+    );
+
+    LifecycleModule.bindStartupAction(binder(), StartStatPoller.class);
+  }
+
+  public static final class StartStatPoller implements Command {
+    private static final Logger LOG = Logger.getLogger(StartStatPoller.class.getName());
+    private final ShutdownRegistry shutdownRegistry;
+    private final BuildInfo buildInfo;
+    private final TimeSeriesRepository timeSeriesRepository;
+
+    @Inject StartStatPoller(
+        ShutdownRegistry shutdownRegistry,
+        BuildInfo buildInfo,
+        TimeSeriesRepository timeSeriesRepository) {
+
+      this.shutdownRegistry = checkNotNull(shutdownRegistry);
+      this.buildInfo = checkNotNull(buildInfo);
+      this.timeSeriesRepository = checkNotNull(timeSeriesRepository);
+    }
+
+    @Override public void execute() {
+      Properties properties = buildInfo.getProperties();
+      LOG.info("Build information: " + properties);
+      for (String name : properties.stringPropertyNames()) {
+        final String stringValue = properties.getProperty(name);
+        if (stringValue == null) {
+          continue;
+        }
+        final Long longValue = Longs.tryParse(stringValue);
+        if (longValue != null) {
+          Stats.exportStatic(new StatImpl<Long>(Stats.normalizeName(name)) {
+            @Override public Long read() {
+              return longValue;
+            }
+          });
+        } else {
+          Stats.exportString(new StatImpl<String>(Stats.normalizeName(name)) {
+            @Override public String read() {
+              return stringValue;
+            }
+          });
+        }
+      }
+
+      JvmStats.export();
+      timeSeriesRepository.start(shutdownRegistry);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/aurora/blob/06ddaadb/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java b/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java
new file mode 100644
index 0000000..55236df
--- /dev/null
+++ b/commons/src/main/java/org/apache/aurora/common/application/modules/ThriftModule.java
@@ -0,0 +1,41 @@
+/**
+ * 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.common.application.modules;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+import com.google.inject.name.Names;
+
+import org.apache.aurora.common.application.http.Registration;
+import org.apache.aurora.common.net.http.handlers.ThriftServlet;
+import org.apache.aurora.common.net.monitoring.TrafficMonitor;
+
+/**
+ * Binding module for thrift traffic monitor servlets, to ensure an empty set is available for
+ * the thrift traffic monitor servlet.
+ *
+ * @author William Farner
+ */
+public class ThriftModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    // Make sure that there is at least an empty set bound to client andserver monitors.
+    Multibinder.newSetBinder(binder(), TrafficMonitor.class,
+        Names.named(ThriftServlet.THRIFT_CLIENT_MONITORS));
+    Multibinder.newSetBinder(binder(), TrafficMonitor.class,
+        Names.named(ThriftServlet.THRIFT_SERVER_MONITORS));
+
+    Registration.registerServlet(binder(), "/thrift", ThriftServlet.class, false);
+  }
+}


Mime
View raw message