You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@aurora.apache.org by wf...@apache.org on 2014/07/30 02:01:29 UTC

git commit: Replace HttpModule from twitter.common with our own code.

Repository: incubator-aurora
Updated Branches:
  refs/heads/master 4439be3d7 -> 6449b9200


Replace HttpModule from twitter.common with our own code.

Bugs closed: AURORA-606

Reviewed at https://reviews.apache.org/r/24059/


Project: http://git-wip-us.apache.org/repos/asf/incubator-aurora/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-aurora/commit/6449b920
Tree: http://git-wip-us.apache.org/repos/asf/incubator-aurora/tree/6449b920
Diff: http://git-wip-us.apache.org/repos/asf/incubator-aurora/diff/6449b920

Branch: refs/heads/master
Commit: 6449b9200617c7e0d4f41237ef21489ec9cdd7e3
Parents: 4439be3
Author: Bill Farner <wf...@apache.org>
Authored: Tue Jul 29 16:59:12 2014 -0700
Committer: Bill Farner <wf...@apache.org>
Committed: Tue Jul 29 16:59:12 2014 -0700

----------------------------------------------------------------------
 build.gradle                                    |   4 +-
 .../apache/aurora/scheduler/app/AppModule.java  |   4 +-
 .../aurora/scheduler/app/SchedulerMain.java     |   2 -
 .../aurora/scheduler/http/AbortCallback.java    |  28 ++
 .../scheduler/http/JettyServerModule.java       | 390 +++++++++++++++++++
 .../aurora/scheduler/http/ServletModule.java    | 263 -------------
 .../scheduler/http/JettyServerModuleTest.java   | 159 ++++++++
 .../apache/aurora/scheduler/http/MnameTest.java |   2 +-
 .../scheduler/http/ServletFilterTest.java       |   2 +-
 .../scheduler/http/ServletModuleTest.java       | 160 --------
 .../aurora/scheduler/http/api/ApiBetaTest.java  |   4 +-
 11 files changed, 584 insertions(+), 434 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/build.gradle
----------------------------------------------------------------------
diff --git a/build.gradle b/build.gradle
index 22ac386..2756e84 100644
--- a/build.gradle
+++ b/build.gradle
@@ -176,8 +176,6 @@ dependencies {
   compile "org.slf4j:slf4j-api:${slf4jRev}"
   compile "org.slf4j:slf4j-jdk14:${slf4jRev}"
   compile 'com.twitter.common.logging:log4j:0.0.7'
-  compile 'com.twitter.common.webassets:bootstrap:0.0.4'
-  compile 'com.twitter.common.webassets:jquery:0.0.4'
   compile 'com.twitter.common.zookeeper.guice:client-flagged:0.0.5'
   compile 'com.twitter.common.zookeeper.guice:client:0.0.5'
   compile 'com.twitter.common.zookeeper:candidate:0.0.64'
@@ -187,7 +185,6 @@ dependencies {
   compile 'com.twitter.common.zookeeper:singleton-service:0.0.85'
   compile 'com.twitter.common:application-http:0.0.59'
   compile 'com.twitter.common:application-module-applauncher:0.0.51'
-  compile 'com.twitter.common:application-module-http:0.0.62'
   compile 'com.twitter.common:application-module-lifecycle:0.0.48'
   compile 'com.twitter.common:application-module-log:0.0.56'
   compile 'com.twitter.common:application-module-stats:0.0.44'
@@ -201,6 +198,7 @@ dependencies {
   compile 'com.twitter.common:io:0.0.53'
   compile 'com.twitter.common:jdk-logging:0.0.44'
   compile 'com.twitter.common:logging:0.0.61'
+  compile 'com.twitter.common:net-http-handlers-time-series:0.0.51'
   compile 'com.twitter.common:net-util:0.0.80'
   compile 'com.twitter.common:quantity:0.0.71'
   compile 'com.twitter.common:stats:0.0.91'

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/main/java/org/apache/aurora/scheduler/app/AppModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/app/AppModule.java b/src/main/java/org/apache/aurora/scheduler/app/AppModule.java
index 30b1ba6..61ba4b2 100644
--- a/src/main/java/org/apache/aurora/scheduler/app/AppModule.java
+++ b/src/main/java/org/apache/aurora/scheduler/app/AppModule.java
@@ -48,7 +48,7 @@ import org.apache.aurora.scheduler.SchedulerModule;
 import org.apache.aurora.scheduler.async.AsyncModule;
 import org.apache.aurora.scheduler.events.PubsubEventModule;
 import org.apache.aurora.scheduler.filter.SchedulingFilterImpl;
-import org.apache.aurora.scheduler.http.ServletModule;
+import org.apache.aurora.scheduler.http.JettyServerModule;
 import org.apache.aurora.scheduler.metadata.MetadataModule;
 import org.apache.aurora.scheduler.quota.QuotaModule;
 import org.apache.aurora.scheduler.sla.SlaModule;
@@ -113,7 +113,7 @@ class AppModule extends AbstractModule {
     install(new AsyncStatsModule());
     install(new MetadataModule());
     install(new QuotaModule());
-    install(new ServletModule());
+    install(new JettyServerModule());
     install(new SchedulerModule());
     install(new StateModule());
     install(new SlaModule());

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java b/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
index f429eda..ec31c49 100644
--- a/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
+++ b/src/main/java/org/apache/aurora/scheduler/app/SchedulerMain.java
@@ -29,7 +29,6 @@ import com.google.inject.Module;
 import com.twitter.common.application.AbstractApplication;
 import com.twitter.common.application.AppLauncher;
 import com.twitter.common.application.Lifecycle;
-import com.twitter.common.application.modules.HttpModule;
 import com.twitter.common.application.modules.LocalServiceRegistry;
 import com.twitter.common.application.modules.LogModule;
 import com.twitter.common.application.modules.StatsModule;
@@ -132,7 +131,6 @@ public class SchedulerMain extends AbstractApplication {
 
     return ImmutableList.<Module>builder()
         .add(new LogModule())
-        .add(new HttpModule())
         .add(new StatsModule())
         .add(new AppModule(clusterName, serverSetPath, zkClientConfig, statsURLPrefix))
         .addAll(getExtraModules())

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/main/java/org/apache/aurora/scheduler/http/AbortCallback.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/AbortCallback.java b/src/main/java/org/apache/aurora/scheduler/http/AbortCallback.java
new file mode 100644
index 0000000..b5eed69
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/http/AbortCallback.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.http;
+
+import java.util.logging.Logger;
+
+/**
+ * Default handler to invoke when the process is being instructed to exit immediately.
+ */
+class AbortCallback implements Runnable {
+  private static final Logger LOG = Logger.getLogger(AbortCallback.class.getName());
+
+  @Override public void run() {
+    LOG.info("ABORTING PROCESS IMMEDIATELY!");
+    Runtime.getRuntime().halt(0);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/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
new file mode 100644
index 0000000..de49a1c
--- /dev/null
+++ b/src/main/java/org/apache/aurora/scheduler/http/JettyServerModule.java
@@ -0,0 +1,390 @@
+/**
+ * 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.http;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.logging.Logger;
+
+import javax.annotation.Nonnegative;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import javax.servlet.http.HttpServlet;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Resources;
+import com.google.common.net.MediaType;
+import com.google.inject.AbstractModule;
+import com.google.inject.Injector;
+import com.google.inject.TypeLiteral;
+import com.google.inject.name.Names;
+import com.google.inject.servlet.GuiceFilter;
+import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
+import com.sun.jersey.guice.JerseyServletModule;
+import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
+import com.twitter.common.application.http.DefaultQuitHandler;
+import com.twitter.common.application.http.GraphViewer;
+import com.twitter.common.application.http.HttpAssetConfig;
+import com.twitter.common.application.http.HttpFilterConfig;
+import com.twitter.common.application.http.HttpServletConfig;
+import com.twitter.common.application.http.Registration;
+import com.twitter.common.application.modules.LifecycleModule;
+import com.twitter.common.args.Arg;
+import com.twitter.common.args.CmdLine;
+import com.twitter.common.base.Command;
+import com.twitter.common.base.ExceptionalCommand;
+import com.twitter.common.base.ExceptionalSupplier;
+import com.twitter.common.base.MoreSuppliers;
+import com.twitter.common.net.http.HttpServerDispatch;
+import com.twitter.common.net.http.RequestLogger;
+import com.twitter.common.net.http.handlers.AbortHandler;
+import com.twitter.common.net.http.handlers.ContentionPrinter;
+import com.twitter.common.net.http.handlers.HealthHandler;
+import com.twitter.common.net.http.handlers.LogConfig;
+import com.twitter.common.net.http.handlers.QuitHandler;
+import com.twitter.common.net.http.handlers.StringTemplateServlet;
+import com.twitter.common.net.http.handlers.ThreadStackPrinter;
+import com.twitter.common.net.http.handlers.TimeSeriesDataSource;
+import com.twitter.common.net.http.handlers.VarsHandler;
+import com.twitter.common.net.http.handlers.VarsJsonHandler;
+import com.twitter.common.net.pool.DynamicHostSet.MonitorException;
+
+import org.apache.aurora.scheduler.http.api.ApiBeta;
+import org.mortbay.jetty.RequestLog;
+import org.mortbay.servlet.GzipFilter;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.sun.jersey.api.core.ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS;
+import static com.sun.jersey.api.core.ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS;
+import static com.sun.jersey.api.json.JSONConfiguration.FEATURE_POJO_MAPPING;
+import static com.twitter.common.application.modules.LocalServiceRegistry.LocalService;
+
+/**
+ * Binding module for scheduler HTTP servlets.
+ * <p>
+ * TODO(wfarner): Continue improvements here by simplifying serving of static assets.  Jetty's
+ * DefaultServlet can take over this responsibility.
+ */
+public class JettyServerModule extends AbstractModule {
+
+  private static final Logger LOG = Logger.getLogger(JettyServerModule.class.getName());
+
+  @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 = "enable_cors_support", help = "Enable CORS support for thrift end points.")
+  private static final Arg<Boolean> ENABLE_CORS_SUPPORT = Arg.create(false);
+
+  // More info on CORS can be found at http://enable-cors.org/index.html
+  @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("*");
+
+  private static final Map<String, String> CONTAINER_PARAMS = ImmutableMap.of(
+      FEATURE_POJO_MAPPING, Boolean.TRUE.toString(),
+      PROPERTY_CONTAINER_REQUEST_FILTERS, GZIPContentEncodingFilter.class.getName(),
+      PROPERTY_CONTAINER_RESPONSE_FILTERS, GZIPContentEncodingFilter.class.getName());
+
+  @Override
+  protected void configure() {
+    bind(Runnable.class)
+        .annotatedWith(Names.named(AbortHandler.ABORT_HANDLER_KEY))
+        .to(AbortCallback.class);
+    bind(AbortCallback.class).in(Singleton.class);
+    bind(Runnable.class).annotatedWith(Names.named(QuitHandler.QUIT_HANDLER_KEY))
+        .to(DefaultQuitHandler.class);
+    bind(DefaultQuitHandler.class).in(Singleton.class);
+    bind(new TypeLiteral<ExceptionalSupplier<Boolean, ?>>() { })
+        .annotatedWith(Names.named(HealthHandler.HEALTH_CHECKER_KEY))
+        .toInstance(MoreSuppliers.ofInstance(true));
+
+    bindConstant().annotatedWith(StringTemplateServlet.CacheTemplates.class).to(true);
+
+    bind(HttpServerDispatch.class).in(Singleton.class);
+    bind(RequestLog.class).to(RequestLogger.class);
+    Registration.registerServlet(binder(), "/abortabortabort", AbortHandler.class, true);
+    Registration.registerServlet(binder(), "/contention", ContentionPrinter.class, false);
+    Registration.registerServlet(binder(), "/graphdata", TimeSeriesDataSource.class, true);
+    Registration.registerServlet(binder(), "/health", HealthHandler.class, true);
+    Registration.registerServlet(binder(), "/healthz", HealthHandler.class, true);
+    Registration.registerServlet(binder(), "/logconfig", LogConfig.class, false);
+    Registration.registerServlet(binder(), "/quitquitquit", QuitHandler.class, true);
+    Registration.registerServlet(binder(), "/threads", ThreadStackPrinter.class, false);
+    Registration.registerServlet(binder(), "/vars", VarsHandler.class, false);
+    Registration.registerServlet(binder(), "/vars.json", VarsJsonHandler.class, false);
+
+    GraphViewer.registerResources(binder());
+
+    LifecycleModule.bindServiceRunner(binder(), HttpServerLauncher.class);
+
+    // Ensure at least an empty filter set is bound.
+    Registration.getFilterBinder(binder());
+
+    // Ensure at least an empty set of additional links is bound.
+    Registration.getEndpointBinder(binder());
+
+    // Register /api end point
+    Registration.registerServlet(binder(), "/api", SchedulerAPIServlet.class, true);
+
+    // NOTE: GzipFilter is applied only to /api instead of globally because the Jersey-managed
+    // servlets have a conflicting filter applied to them.
+    Registration.registerServletFilter(binder(), GzipFilter.class, "/api/*");
+    Registration.registerServletFilter(binder(), GzipFilter.class, "/apibetabeta/*");
+    Registration.registerServletFilter(binder(), GuiceFilter.class, "/*");
+
+    install(new JerseyServletModule() {
+      private void registerJerseyEndpoint(String indexPath, Class<?>... servlets) {
+        filter(indexPath + "*").through(LeaderRedirectFilter.class);
+        filter(indexPath + "*").through(GuiceContainer.class, CONTAINER_PARAMS);
+        Registration.registerEndpoint(binder(), indexPath);
+        for (Class<?> servlet : servlets) {
+          bind(servlet);
+        }
+      }
+
+      @Override
+      protected void configureServlets() {
+        bind(HttpStatsFilter.class).in(Singleton.class);
+        filter("/scheduler*").through(HttpStatsFilter.class);
+        bind(LeaderRedirectFilter.class).in(Singleton.class);
+        filterRegex("/scheduler(?:/.*)?").through(LeaderRedirectFilter.class);
+
+        // Add CORS support for all /api end points.
+        if (ENABLE_CORS_SUPPORT.get()) {
+          bind(CorsFilter.class).toInstance(new CorsFilter(ENABLE_CORS_FOR.get()));
+          filter("/api*").through(CorsFilter.class);
+        }
+
+        registerJerseyEndpoint("/apibeta", ApiBeta.class);
+        registerJerseyEndpoint("/cron", Cron.class);
+        registerJerseyEndpoint("/locks", Locks.class);
+        registerJerseyEndpoint("/maintenance", Maintenance.class);
+        registerJerseyEndpoint("/mname", Mname.class);
+        registerJerseyEndpoint("/offers", Offers.class);
+        registerJerseyEndpoint("/pendingtasks", PendingTasks.class);
+        registerJerseyEndpoint("/quotas", Quotas.class);
+        registerJerseyEndpoint("/slaves", Slaves.class);
+        registerJerseyEndpoint("/structdump", StructDump.class);
+        registerJerseyEndpoint("/utilization", Utilization.class);
+      }
+    });
+
+    // Static assets.
+    registerJQueryAssets();
+    registerBootstrapAssets();
+
+    registerAsset("assets/images/viz.png", "/images/viz.png");
+    registerAsset("assets/images/aurora.png", "/images/aurora.png");
+    registerAsset("assets/images/aurora_logo.png", "/images/aurora_logo.png");
+
+    registerUIClient();
+
+    bind(LeaderRedirect.class).in(Singleton.class);
+    LifecycleModule.bindStartupAction(binder(), RedirectMonitor.class);
+  }
+
+  private void registerJQueryAssets() {
+    registerAsset("bower_components/jquery/dist/jquery.js", "/js/jquery.min.js", false);
+  }
+
+  private void registerBootstrapAssets() {
+    final String BOOTSTRAP_PATH = "bower_components/bootstrap/";
+
+    registerAsset(BOOTSTRAP_PATH + "dist/js/bootstrap.min.js", "/js/bootstrap.min.js", false);
+    registerAsset(BOOTSTRAP_PATH + "dist/css/bootstrap.min.css", "/css/bootstrap.min.css", false);
+
+    registerAsset(BOOTSTRAP_PATH + "dist/fonts/glyphicons-halflings-regular.eot",
+        "/fonts/glyphicons-halflings-regular.eot",
+        false);
+    registerAsset(BOOTSTRAP_PATH + "dist/fonts/glyphicons-halflings-regular.svg",
+        "/fonts/glyphicons-halflings-regular.svg",
+        false);
+    registerAsset(BOOTSTRAP_PATH + "dist/fonts/glyphicons-halflings-regular.ttf",
+        "/fonts/glyphicons-halflings-regular.ttf",
+        false);
+    registerAsset(BOOTSTRAP_PATH + "dist/fonts/glyphicons-halflings-regular.woff",
+        "/fonts/glyphicons-halflings-regular.woff",
+        false);
+  }
+
+  /**
+   * A function to handle all assets related to the UI client.
+   */
+  private void registerUIClient() {
+    registerAsset("bower_components/smart-table/Smart-Table.debug.js", "/js/smartTable.js", false);
+    registerAsset("bower_components/angular/angular.js", "/js/angular.js", false);
+    registerAsset("bower_components/angular-route/angular-route.js", "/js/angular-route.js", false);
+    registerAsset("bower_components/underscore/underscore.js", "/js/underscore.js", false);
+    registerAsset("bower_components/momentjs/moment.js", "/js/moment.js", false);
+    registerAsset("bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js",
+        "/js/ui-bootstrap-tpls.js",
+        false);
+
+    registerAsset("ReadOnlyScheduler.js", "/js/readOnlyScheduler.js", false);
+    registerAsset("api_types.js", "/js/apiTypes.js", false);
+    registerAsset("thrift.js", "/js/thrift.js", false);
+
+    registerAsset("ui/index.html", "/scheduler", true);
+    Registration.registerEndpoint(binder(), "/scheduler");
+
+    registerAsset("ui/roleLink.html", "/roleLink.html");
+    registerAsset("ui/roleEnvLink.html", "/roleEnvLink.html");
+    registerAsset("ui/jobLink.html", "/jobLink.html");
+    registerAsset("ui/home.html", "/home.html");
+    registerAsset("ui/role.html", "/role.html");
+    registerAsset("ui/breadcrumb.html", "/breadcrumb.html");
+    registerAsset("ui/error.html", "/error.html");
+    registerAsset("ui/job.html", "/job.html");
+    registerAsset("ui/taskSandbox.html", "/taskSandbox.html");
+    registerAsset("ui/taskStatus.html", "/taskStatus.html");
+    registerAsset("ui/taskLink.html", "/taskLink.html");
+    registerAsset("ui/schedulingDetail.html", "/schedulingDetail.html");
+    registerAsset("ui/groupSummary.html", "/groupSummary.html");
+    registerAsset("ui/configSummary.html", "/configSummary.html");
+
+    registerAsset("ui/css/app.css", "/css/app.css");
+
+    registerAsset("ui/js/app.js", "/js/app.js");
+    registerAsset("ui/js/controllers.js", "/js/controllers.js");
+    registerAsset("ui/js/directives.js", "/js/directives.js");
+    registerAsset("ui/js/services.js", "/js/services.js");
+    registerAsset("ui/js/filters.js", "/js/filters.js");
+  }
+
+  private void registerAsset(String resourceLocation, String registerLocation) {
+    registerAsset(resourceLocation, registerLocation, true);
+  }
+
+  private void registerAsset(String resourceLocation, String registerLocation, boolean isRelative) {
+    String mediaType = getMediaType(resourceLocation).toString();
+
+    if (isRelative) {
+      Registration.registerHttpAsset(
+          binder(),
+          registerLocation,
+          JettyServerModule.class,
+          resourceLocation,
+          mediaType,
+          true);
+    } else {
+      Registration.registerHttpAsset(
+          binder(),
+          registerLocation,
+          Resources.getResource(resourceLocation),
+          mediaType,
+          true);
+    }
+  }
+
+  private MediaType getMediaType(String filePath) {
+    if (filePath.endsWith(".png")) {
+      return MediaType.PNG;
+    } else if (filePath.endsWith(".js")) {
+      return MediaType.JAVASCRIPT_UTF_8;
+    } else if (filePath.endsWith(".html")) {
+      return MediaType.HTML_UTF_8;
+    } else if (filePath.endsWith(".css")) {
+      return MediaType.CSS_UTF_8;
+    } else if (filePath.endsWith(".svg")) {
+      return MediaType.SVG_UTF_8;
+    } else if (filePath.endsWith(".ttf")
+        || filePath.endsWith(".eot")
+        || filePath.endsWith(".woff")) {
+
+      // MediaType doesn't have any mime types for fonts. Instead of magic strings, we let the
+      // browser interpret the mime type and modern browsers can do this well.
+      return MediaType.ANY_TYPE;
+    } else {
+      throw new IllegalArgumentException("Could not determine media type for " + filePath);
+    }
+  }
+
+  static class RedirectMonitor implements ExceptionalCommand<MonitorException> {
+
+    private final LeaderRedirect redirector;
+
+    @Inject
+    RedirectMonitor(LeaderRedirect redirector) {
+      this.redirector = Objects.requireNonNull(redirector);
+    }
+
+    @Override
+    public void execute() throws MonitorException {
+      redirector.monitor();
+    }
+  }
+
+  // TODO(wfarner): Use guava's Service to enforce the lifecycle of this.
+  public static final class HttpServerLauncher implements LifecycleModule.ServiceRunner {
+    private final HttpServerDispatch httpServer;
+    private final Set<HttpServletConfig> httpServlets;
+    private final Set<HttpAssetConfig> httpAssets;
+    private final Set<HttpFilterConfig> httpFilters;
+    private final Set<String> additionalIndexLinks;
+    private final Injector injector;
+
+    @Inject
+    HttpServerLauncher(
+        HttpServerDispatch httpServer,
+        Set<HttpServletConfig> httpServlets,
+        Set<HttpAssetConfig> httpAssets,
+        Set<HttpFilterConfig> httpFilters,
+        @Registration.IndexLink Set<String> additionalIndexLinks,
+        Injector injector) {
+
+      this.httpServer = checkNotNull(httpServer);
+      this.httpServlets = checkNotNull(httpServlets);
+      this.httpAssets = checkNotNull(httpAssets);
+      this.httpFilters = checkNotNull(httpFilters);
+      this.additionalIndexLinks = checkNotNull(additionalIndexLinks);
+      this.injector = checkNotNull(injector);
+    }
+
+    @Override
+    public LocalService launch() {
+      if (!httpServer.listen(HTTP_PORT.get())) {
+        throw new IllegalStateException("Failed to start HTTP server, all servlets disabled.");
+      }
+
+      for (HttpServletConfig config : httpServlets) {
+        HttpServlet handler = injector.getInstance(config.handlerClass);
+        httpServer.registerHandler(config.path, handler, config.params, config.silent);
+      }
+
+      for (HttpAssetConfig config : httpAssets) {
+        httpServer.registerHandler(config.path, config.handler, null, config.silent);
+      }
+
+      for (HttpFilterConfig filter : httpFilters) {
+        httpServer.registerFilter(filter.filterClass, filter.pathSpec, filter.dispatch);
+      }
+
+      for (String indexLink : additionalIndexLinks) {
+        httpServer.registerIndexLink(indexLink);
+      }
+
+      Command shutdown = new Command() {
+        @Override public void execute() {
+          LOG.info("Shutting down embedded http server");
+          httpServer.stop();
+        }
+      };
+
+      return LocalService.auxiliaryService("http", httpServer.getPort(), shutdown);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java b/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java
deleted file mode 100644
index 5c0fc2c..0000000
--- a/src/main/java/org/apache/aurora/scheduler/http/ServletModule.java
+++ /dev/null
@@ -1,263 +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.http;
-
-import java.util.Map;
-import java.util.Objects;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.common.io.Resources;
-import com.google.common.net.MediaType;
-import com.google.inject.AbstractModule;
-import com.google.inject.Key;
-import com.google.inject.TypeLiteral;
-import com.google.inject.servlet.GuiceFilter;
-import com.sun.jersey.api.container.filter.GZIPContentEncodingFilter;
-import com.sun.jersey.guice.JerseyServletModule;
-import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
-import com.twitter.common.application.http.Registration;
-import com.twitter.common.application.modules.LifecycleModule;
-import com.twitter.common.application.modules.LocalServiceRegistry;
-import com.twitter.common.args.Arg;
-import com.twitter.common.args.CmdLine;
-import com.twitter.common.base.ExceptionalCommand;
-import com.twitter.common.net.pool.DynamicHostSet;
-import com.twitter.common.net.pool.DynamicHostSet.MonitorException;
-import com.twitter.thrift.ServiceInstance;
-
-import org.apache.aurora.scheduler.http.api.ApiBeta;
-import org.mortbay.servlet.GzipFilter;
-
-import static com.sun.jersey.api.core.ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS;
-import static com.sun.jersey.api.core.ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS;
-import static com.sun.jersey.api.json.JSONConfiguration.FEATURE_POJO_MAPPING;
-
-/**
- * Binding module for scheduler HTTP servlets.
- */
-public class ServletModule extends AbstractModule {
-
-  @CmdLine(name = "enable_cors_support", help = "Enable CORS support for thrift end points.")
-  private static final Arg<Boolean> ENABLE_CORS_SUPPORT = Arg.create(false);
-
-  // More info on CORS can be found at http://enable-cors.org/index.html
-  @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("*");
-
-  private static final Map<String, String> CONTAINER_PARAMS = ImmutableMap.of(
-      FEATURE_POJO_MAPPING, Boolean.TRUE.toString(),
-      PROPERTY_CONTAINER_REQUEST_FILTERS, GZIPContentEncodingFilter.class.getName(),
-      PROPERTY_CONTAINER_RESPONSE_FILTERS, GZIPContentEncodingFilter.class.getName());
-
-  @Override
-  protected void configure() {
-    // Register /api end point
-    Registration.registerServlet(binder(), "/api", SchedulerAPIServlet.class, true);
-
-    // NOTE: GzipFilter is applied only to /api instead of globally because the Jersey-managed
-    // servlets have a conflicting filter applied to them.
-    Registration.registerServletFilter(binder(), GzipFilter.class, "/api/*");
-    // TODO(wfarner): Add a unit test to validate gzip behavior.
-    Registration.registerServletFilter(binder(), GzipFilter.class, "/apibeta");
-
-    // Bindings required for the leader redirector.
-    requireBinding(LocalServiceRegistry.class);
-    requireBinding(Key.get(new TypeLiteral<DynamicHostSet<ServiceInstance>>() { }));
-    Registration.registerServletFilter(binder(), GuiceFilter.class, "/*");
-    install(new JerseyServletModule() {
-      private void registerJerseyEndpoint(String indexPath, Class<?>... servlets) {
-        filter(indexPath + "*").through(LeaderRedirectFilter.class);
-        filter(indexPath + "*").through(GuiceContainer.class, CONTAINER_PARAMS);
-        Registration.registerEndpoint(binder(), indexPath);
-        for (Class<?> servlet : servlets) {
-          bind(servlet);
-        }
-      }
-
-      @Override
-      protected void configureServlets() {
-        bind(HttpStatsFilter.class).in(Singleton.class);
-        filter("/scheduler*").through(HttpStatsFilter.class);
-        bind(LeaderRedirectFilter.class).in(Singleton.class);
-        filterRegex("/scheduler(?:/.*)?").through(LeaderRedirectFilter.class);
-
-        // Add CORS support for all /api end points.
-        if (ENABLE_CORS_SUPPORT.get()) {
-          bind(CorsFilter.class).toInstance(new CorsFilter(ENABLE_CORS_FOR.get()));
-          filter("/api*").through(CorsFilter.class);
-        }
-
-        registerJerseyEndpoint("/apibeta", ApiBeta.class);
-        registerJerseyEndpoint("/cron", Cron.class);
-        registerJerseyEndpoint("/locks", Locks.class);
-        registerJerseyEndpoint("/maintenance", Maintenance.class);
-        registerJerseyEndpoint("/mname", Mname.class);
-        registerJerseyEndpoint("/offers", Offers.class);
-        registerJerseyEndpoint("/pendingtasks", PendingTasks.class);
-        registerJerseyEndpoint("/quotas", Quotas.class);
-        registerJerseyEndpoint("/slaves", Slaves.class);
-        registerJerseyEndpoint("/structdump", StructDump.class);
-        registerJerseyEndpoint("/utilization", Utilization.class);
-      }
-    });
-
-    // Static assets.
-    registerJQueryAssets();
-    registerBootstrapAssets();
-
-    registerAsset("assets/images/viz.png", "/images/viz.png");
-    registerAsset("assets/images/aurora.png", "/images/aurora.png");
-    registerAsset("assets/images/aurora_logo.png", "/images/aurora_logo.png");
-
-    registerUIClient();
-
-    bind(LeaderRedirect.class).in(Singleton.class);
-    LifecycleModule.bindStartupAction(binder(), RedirectMonitor.class);
-  }
-
-  private void registerJQueryAssets() {
-    registerAsset("bower_components/jquery/dist/jquery.js", "/js/jquery.min.js", false);
-  }
-
-  private void registerBootstrapAssets() {
-    final String BOOTSTRAP_PATH = "bower_components/bootstrap/";
-
-    registerAsset(BOOTSTRAP_PATH + "dist/js/bootstrap.min.js", "/js/bootstrap.min.js", false);
-    registerAsset(BOOTSTRAP_PATH + "dist/css/bootstrap.min.css", "/css/bootstrap.min.css", false);
-
-    registerAsset(BOOTSTRAP_PATH + "dist/fonts/glyphicons-halflings-regular.eot",
-        "/fonts/glyphicons-halflings-regular.eot",
-        false);
-    registerAsset(BOOTSTRAP_PATH + "dist/fonts/glyphicons-halflings-regular.svg",
-        "/fonts/glyphicons-halflings-regular.svg",
-        false);
-    registerAsset(BOOTSTRAP_PATH + "dist/fonts/glyphicons-halflings-regular.ttf",
-        "/fonts/glyphicons-halflings-regular.ttf",
-        false);
-    registerAsset(BOOTSTRAP_PATH + "dist/fonts/glyphicons-halflings-regular.woff",
-        "/fonts/glyphicons-halflings-regular.woff",
-        false);
-  }
-
-  /**
-   * A function to handle all assets related to the UI client.
-   */
-  private void registerUIClient() {
-    registerAsset("bower_components/smart-table/Smart-Table.debug.js", "/js/smartTable.js", false);
-    registerAsset("bower_components/angular/angular.js", "/js/angular.js", false);
-    registerAsset("bower_components/angular-route/angular-route.js", "/js/angular-route.js", false);
-    registerAsset("bower_components/underscore/underscore.js", "/js/underscore.js", false);
-    registerAsset("bower_components/momentjs/moment.js", "/js/moment.js", false);
-    registerAsset("bower_components/angular-bootstrap/ui-bootstrap-tpls.min.js",
-        "/js/ui-bootstrap-tpls.js",
-        false);
-
-    registerAsset("ReadOnlyScheduler.js", "/js/readOnlyScheduler.js", false);
-    registerAsset("api_types.js", "/js/apiTypes.js", false);
-    registerAsset("thrift.js", "/js/thrift.js", false);
-
-    registerAsset("ui/index.html", "/scheduler", true);
-    Registration.registerEndpoint(binder(), "/scheduler");
-
-    registerAsset("ui/roleLink.html", "/roleLink.html");
-    registerAsset("ui/roleEnvLink.html", "/roleEnvLink.html");
-    registerAsset("ui/jobLink.html", "/jobLink.html");
-    registerAsset("ui/home.html", "/home.html");
-    registerAsset("ui/role.html", "/role.html");
-    registerAsset("ui/breadcrumb.html", "/breadcrumb.html");
-    registerAsset("ui/error.html", "/error.html");
-    registerAsset("ui/job.html", "/job.html");
-    registerAsset("ui/taskSandbox.html", "/taskSandbox.html");
-    registerAsset("ui/taskStatus.html", "/taskStatus.html");
-    registerAsset("ui/taskLink.html", "/taskLink.html");
-    registerAsset("ui/schedulingDetail.html", "/schedulingDetail.html");
-    registerAsset("ui/groupSummary.html", "/groupSummary.html");
-    registerAsset("ui/configSummary.html", "/configSummary.html");
-
-    registerAsset("ui/css/app.css", "/css/app.css");
-
-    registerAsset("ui/js/app.js", "/js/app.js");
-    registerAsset("ui/js/controllers.js", "/js/controllers.js");
-    registerAsset("ui/js/directives.js", "/js/directives.js");
-    registerAsset("ui/js/services.js", "/js/services.js");
-    registerAsset("ui/js/filters.js", "/js/filters.js");
-  }
-
-  private void registerAsset(String resourceLocation, String registerLocation) {
-    registerAsset(resourceLocation, registerLocation, true);
-  }
-
-  private void registerAsset(String resourceLocation, String registerLocation, boolean isRelative) {
-    String mediaType = getMediaType(resourceLocation).toString();
-
-    if (isRelative) {
-      Registration.registerHttpAsset(
-          binder(),
-          registerLocation,
-          ServletModule.class,
-          resourceLocation,
-          mediaType,
-          true);
-    } else {
-      Registration.registerHttpAsset(
-          binder(),
-          registerLocation,
-          Resources.getResource(resourceLocation),
-          mediaType,
-          true);
-    }
-  }
-
-  private MediaType getMediaType(String filePath) {
-    if (filePath.endsWith(".png")) {
-      return MediaType.PNG;
-    } else if (filePath.endsWith(".js")) {
-      return MediaType.JAVASCRIPT_UTF_8;
-    } else if (filePath.endsWith(".html")) {
-      return MediaType.HTML_UTF_8;
-    } else if (filePath.endsWith(".css")) {
-      return MediaType.CSS_UTF_8;
-    } else if (filePath.endsWith(".svg")) {
-      return MediaType.SVG_UTF_8;
-    } else if (filePath.endsWith(".ttf")
-        || filePath.endsWith(".eot")
-        || filePath.endsWith(".woff")) {
-
-      // MediaType doesn't have any mime types for fonts. Instead of magic strings, we let the
-      // browser interpret the mime type and modern browsers can do this well.
-      return MediaType.ANY_TYPE;
-    } else {
-      throw new IllegalArgumentException("Could not determine media type for " + filePath);
-    }
-  }
-
-  static class RedirectMonitor implements ExceptionalCommand<MonitorException> {
-
-    private final LeaderRedirect redirector;
-
-    @Inject
-    RedirectMonitor(LeaderRedirect redirector) {
-      this.redirector = Objects.requireNonNull(redirector);
-    }
-
-    @Override
-    public void execute() throws MonitorException {
-      redirector.monitor();
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/test/java/org/apache/aurora/scheduler/http/JettyServerModuleTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/JettyServerModuleTest.java b/src/test/java/org/apache/aurora/scheduler/http/JettyServerModuleTest.java
new file mode 100644
index 0000000..62dce07
--- /dev/null
+++ b/src/test/java/org/apache/aurora/scheduler/http/JettyServerModuleTest.java
@@ -0,0 +1,159 @@
+/**
+ * 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.http;
+
+import java.net.InetSocketAddress;
+
+import javax.ws.rs.core.MediaType;
+
+import com.google.common.base.Throwables;
+import com.google.common.testing.TearDown;
+import com.google.common.util.concurrent.RateLimiter;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.TypeLiteral;
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.api.client.config.ClientConfig;
+import com.sun.jersey.api.client.config.DefaultClientConfig;
+import com.sun.jersey.api.json.JSONConfiguration;
+import com.twitter.common.application.ShutdownRegistry.ShutdownRegistryImpl;
+import com.twitter.common.application.StartupRegistry;
+import com.twitter.common.application.modules.LifecycleModule;
+import com.twitter.common.application.modules.LocalServiceRegistry;
+import com.twitter.common.application.modules.LogModule;
+import com.twitter.common.application.modules.StatsModule;
+import com.twitter.common.base.Command;
+import com.twitter.common.net.pool.DynamicHostSet;
+import com.twitter.common.net.pool.DynamicHostSet.HostChangeMonitor;
+import com.twitter.common.testing.easymock.EasyMockTest;
+import com.twitter.common.util.BackoffStrategy;
+import com.twitter.thrift.ServiceInstance;
+
+import org.apache.aurora.gen.AuroraAdmin;
+import org.apache.aurora.gen.ServerInfo;
+import org.apache.aurora.scheduler.async.OfferQueue;
+import org.apache.aurora.scheduler.async.RescheduleCalculator;
+import org.apache.aurora.scheduler.async.TaskGroups.TaskGroupsSettings;
+import org.apache.aurora.scheduler.async.TaskScheduler;
+import org.apache.aurora.scheduler.cron.CronJobManager;
+import org.apache.aurora.scheduler.http.api.GsonMessageBodyHandler;
+import org.apache.aurora.scheduler.quota.QuotaManager;
+import org.apache.aurora.scheduler.state.LockManager;
+import org.apache.aurora.scheduler.state.SchedulerCore;
+import org.apache.aurora.scheduler.storage.Storage;
+import org.apache.aurora.scheduler.storage.entities.IServerInfo;
+import org.apache.aurora.scheduler.storage.testing.StorageTestUtil;
+import org.easymock.Capture;
+import org.junit.Before;
+
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * TODO(wfarner): Break apart ServletModule so test setup isn't so involved.
+ * TODO(wfarner): Come up with an approach for these tests that doesn't require starting an actual
+ * HTTP server for each test case.
+ *
+ */
+public abstract class JettyServerModuleTest extends EasyMockTest {
+
+  private Injector injector;
+  protected StorageTestUtil storage;
+  protected InetSocketAddress httpServer;
+  protected Capture<HostChangeMonitor<ServiceInstance>> schedulerWatcher;
+  protected AuroraAdmin.Iface thrift;
+
+  @Before
+  public void setUp() throws Exception {
+    storage = new StorageTestUtil(this);
+    final DynamicHostSet<ServiceInstance> schedulers =
+        createMock(new Clazz<DynamicHostSet<ServiceInstance>>() {
+        });
+
+    injector = Guice.createInjector(
+        new JettyServerModule(),
+        new LogModule(),
+        new StatsModule(),
+        new LifecycleModule(),
+        new AbstractModule() {
+          <T> T bindMock(Class<T> clazz) {
+            T mock = createMock(clazz);
+            bind(clazz).toInstance(mock);
+            return mock;
+          }
+
+          @Override
+          protected void configure() {
+            bind(Storage.class).toInstance(storage.storage);
+            bind(IServerInfo.class).toInstance(IServerInfo.build(new ServerInfo()
+                .setClusterName("unittest")
+                .setThriftAPIVersion(100)
+                .setStatsUrlPrefix("none")));
+            bind(TaskGroupsSettings.class).toInstance(
+                new TaskGroupsSettings(bindMock(BackoffStrategy.class), RateLimiter.create(1000)));
+
+            bind(new TypeLiteral<DynamicHostSet<ServiceInstance>>() { }).toInstance(schedulers);
+            thrift = bindMock(AuroraAdmin.Iface.class);
+            bindMock(CronJobManager.class);
+            bindMock(LockManager.class);
+            bindMock(OfferQueue.class);
+            bindMock(QuotaManager.class);
+            bindMock(RescheduleCalculator.class);
+            bindMock(SchedulerCore.class);
+            bindMock(TaskScheduler.class);
+            bindMock(Thread.UncaughtExceptionHandler.class);
+          }
+        });
+
+    schedulerWatcher = createCapture();
+    expect(schedulers.watch(capture(schedulerWatcher))).andReturn(createMock(Command.class));
+  }
+
+  protected void replayAndStart() {
+    control.replay();
+
+    final ShutdownRegistryImpl shutdownRegistry = injector.getInstance(ShutdownRegistryImpl.class);
+    addTearDown(new TearDown() {
+      @Override
+      public void tearDown() {
+        shutdownRegistry.execute();
+      }
+    });
+    try {
+      injector.getInstance(StartupRegistry.class).execute();
+    } catch (Exception e) {
+      throw Throwables.propagate(e);
+    }
+    LocalServiceRegistry serviceRegistry = injector.getInstance(LocalServiceRegistry.class);
+    httpServer = serviceRegistry.getAuxiliarySockets().get("http");
+  }
+
+  protected String makeUrl(String path) {
+    return String.format("http://%s:%s%s", httpServer.getHostName(), httpServer.getPort(), path);
+  }
+
+  protected WebResource.Builder getRequestBuilder(String path) {
+    assertNotNull("HTTP server must be started first", httpServer);
+    ClientConfig config = new DefaultClientConfig();
+    config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
+    config.getClasses().add(GsonMessageBodyHandler.class);
+    Client client = Client.create(config);
+    // Disable redirects so we can unit test them.
+    client.setFollowRedirects(false);
+    return client.resource(makeUrl(path)).getRequestBuilder().accept(MediaType.APPLICATION_JSON);
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/test/java/org/apache/aurora/scheduler/http/MnameTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/MnameTest.java b/src/test/java/org/apache/aurora/scheduler/http/MnameTest.java
index b7b6a3e..afa83f2 100644
--- a/src/test/java/org/apache/aurora/scheduler/http/MnameTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/http/MnameTest.java
@@ -36,7 +36,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 
-public class MnameTest extends ServletModuleTest {
+public class MnameTest extends JettyServerModuleTest {
 
   private static final String SLAVE_HOST = "fakehost";
   private static final int PORT = 50000;

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/test/java/org/apache/aurora/scheduler/http/ServletFilterTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/ServletFilterTest.java b/src/test/java/org/apache/aurora/scheduler/http/ServletFilterTest.java
index 0399ec0..2c31df6 100644
--- a/src/test/java/org/apache/aurora/scheduler/http/ServletFilterTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/http/ServletFilterTest.java
@@ -27,7 +27,7 @@ import org.junit.Test;
 
 import static org.junit.Assert.assertEquals;
 
-public class ServletFilterTest extends ServletModuleTest {
+public class ServletFilterTest extends JettyServerModuleTest {
 
   protected ClientResponse get(String path) {
     return getRequestBuilder(path)

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/test/java/org/apache/aurora/scheduler/http/ServletModuleTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/ServletModuleTest.java b/src/test/java/org/apache/aurora/scheduler/http/ServletModuleTest.java
deleted file mode 100644
index 63c504e..0000000
--- a/src/test/java/org/apache/aurora/scheduler/http/ServletModuleTest.java
+++ /dev/null
@@ -1,160 +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.http;
-
-import java.net.InetSocketAddress;
-
-import javax.ws.rs.core.MediaType;
-
-import com.google.common.base.Throwables;
-import com.google.common.testing.TearDown;
-import com.google.common.util.concurrent.RateLimiter;
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.TypeLiteral;
-import com.sun.jersey.api.client.Client;
-import com.sun.jersey.api.client.WebResource;
-import com.sun.jersey.api.client.config.ClientConfig;
-import com.sun.jersey.api.client.config.DefaultClientConfig;
-import com.sun.jersey.api.json.JSONConfiguration;
-import com.twitter.common.application.ShutdownRegistry.ShutdownRegistryImpl;
-import com.twitter.common.application.StartupRegistry;
-import com.twitter.common.application.modules.HttpModule;
-import com.twitter.common.application.modules.LifecycleModule;
-import com.twitter.common.application.modules.LocalServiceRegistry;
-import com.twitter.common.application.modules.LogModule;
-import com.twitter.common.application.modules.StatsModule;
-import com.twitter.common.base.Command;
-import com.twitter.common.net.pool.DynamicHostSet;
-import com.twitter.common.net.pool.DynamicHostSet.HostChangeMonitor;
-import com.twitter.common.testing.easymock.EasyMockTest;
-import com.twitter.common.util.BackoffStrategy;
-import com.twitter.thrift.ServiceInstance;
-
-import org.apache.aurora.gen.AuroraAdmin;
-import org.apache.aurora.gen.ServerInfo;
-import org.apache.aurora.scheduler.async.OfferQueue;
-import org.apache.aurora.scheduler.async.RescheduleCalculator;
-import org.apache.aurora.scheduler.async.TaskGroups.TaskGroupsSettings;
-import org.apache.aurora.scheduler.async.TaskScheduler;
-import org.apache.aurora.scheduler.cron.CronJobManager;
-import org.apache.aurora.scheduler.http.api.GsonMessageBodyHandler;
-import org.apache.aurora.scheduler.quota.QuotaManager;
-import org.apache.aurora.scheduler.state.LockManager;
-import org.apache.aurora.scheduler.state.SchedulerCore;
-import org.apache.aurora.scheduler.storage.Storage;
-import org.apache.aurora.scheduler.storage.entities.IServerInfo;
-import org.apache.aurora.scheduler.storage.testing.StorageTestUtil;
-import org.easymock.Capture;
-import org.junit.Before;
-
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.expect;
-import static org.junit.Assert.assertNotNull;
-
-/**
- * TODO(wfarner): Break apart ServletModule so test setup isn't so involved.
- * TODO(wfarner): Come up with an approach for these tests that doesn't require starting an actual
- * HTTP server for each test case.
- *
- */
-public abstract class ServletModuleTest extends EasyMockTest {
-
-  private Injector injector;
-  protected StorageTestUtil storage;
-  protected InetSocketAddress httpServer;
-  protected Capture<HostChangeMonitor<ServiceInstance>> schedulerWatcher;
-  protected AuroraAdmin.Iface thrift;
-
-  @Before
-  public final void setUpServletModuleTest() throws Exception {
-    storage = new StorageTestUtil(this);
-    final DynamicHostSet<ServiceInstance> schedulers =
-        createMock(new Clazz<DynamicHostSet<ServiceInstance>>() { });
-
-    injector = Guice.createInjector(
-        new ServletModule(),
-        new LogModule(),
-        new StatsModule(),
-        new HttpModule(),
-        new LifecycleModule(),
-        new AbstractModule() {
-          <T> T bindMock(Class<T> clazz) {
-            T mock = createMock(clazz);
-            bind(clazz).toInstance(mock);
-            return mock;
-          }
-
-          @Override
-          protected void configure() {
-            bind(Storage.class).toInstance(storage.storage);
-            bind(IServerInfo.class).toInstance(IServerInfo.build(new ServerInfo()
-                .setClusterName("unittest")
-                .setThriftAPIVersion(100)
-                .setStatsUrlPrefix("none")));
-            bind(TaskGroupsSettings.class).toInstance(
-                new TaskGroupsSettings(bindMock(BackoffStrategy.class), RateLimiter.create(1000)));
-
-            bind(new TypeLiteral<DynamicHostSet<ServiceInstance>>() { }).toInstance(schedulers);
-            thrift = bindMock(AuroraAdmin.Iface.class);
-            bindMock(CronJobManager.class);
-            bindMock(LockManager.class);
-            bindMock(OfferQueue.class);
-            bindMock(QuotaManager.class);
-            bindMock(RescheduleCalculator.class);
-            bindMock(SchedulerCore.class);
-            bindMock(TaskScheduler.class);
-            bindMock(Thread.UncaughtExceptionHandler.class);
-          }
-        });
-
-    schedulerWatcher = createCapture();
-    expect(schedulers.watch(capture(schedulerWatcher))).andReturn(createMock(Command.class));
-  }
-
-  protected void replayAndStart() {
-    control.replay();
-
-    final ShutdownRegistryImpl shutdownRegistry = injector.getInstance(ShutdownRegistryImpl.class);
-    addTearDown(new TearDown() {
-      @Override
-      public void tearDown() {
-        shutdownRegistry.execute();
-      }
-    });
-    try {
-      injector.getInstance(StartupRegistry.class).execute();
-    } catch (Exception e) {
-      throw Throwables.propagate(e);
-    }
-    LocalServiceRegistry serviceRegistry = injector.getInstance(LocalServiceRegistry.class);
-    httpServer = serviceRegistry.getAuxiliarySockets().get("http");
-  }
-
-  protected String makeUrl(String path) {
-    return String.format("http://%s:%s%s", httpServer.getHostName(), httpServer.getPort(), path);
-  }
-
-  protected WebResource.Builder getRequestBuilder(String path) {
-    assertNotNull("HTTP server must be started first", httpServer);
-    ClientConfig config = new DefaultClientConfig();
-    config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
-    config.getClasses().add(GsonMessageBodyHandler.class);
-    Client client = Client.create(config);
-    // Disable redirects so we can unit test them.
-    client.setFollowRedirects(false);
-    return client.resource(makeUrl(path)).getRequestBuilder().accept(MediaType.APPLICATION_JSON);
-  }
-}

http://git-wip-us.apache.org/repos/asf/incubator-aurora/blob/6449b920/src/test/java/org/apache/aurora/scheduler/http/api/ApiBetaTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/aurora/scheduler/http/api/ApiBetaTest.java b/src/test/java/org/apache/aurora/scheduler/http/api/ApiBetaTest.java
index 0bcc015..ec467aa 100644
--- a/src/test/java/org/apache/aurora/scheduler/http/api/ApiBetaTest.java
+++ b/src/test/java/org/apache/aurora/scheduler/http/api/ApiBetaTest.java
@@ -50,7 +50,7 @@ import org.apache.aurora.gen.SessionKey;
 import org.apache.aurora.gen.TaskConfig;
 import org.apache.aurora.gen.TaskConstraint;
 import org.apache.aurora.gen.TaskQuery;
-import org.apache.aurora.scheduler.http.ServletModuleTest;
+import org.apache.aurora.scheduler.http.JettyServerModuleTest;
 import org.apache.aurora.scheduler.storage.entities.IJobConfiguration;
 import org.apache.aurora.scheduler.storage.entities.ITaskConfig;
 import org.junit.Test;
@@ -61,7 +61,7 @@ import static org.easymock.EasyMock.expect;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
-public class ApiBetaTest extends ServletModuleTest {
+public class ApiBetaTest extends JettyServerModuleTest {
 
   private static final ITaskConfig TASK_CONFIG = ITaskConfig.build(
       new TaskConfig()