azkaban-aplcache

Upgraded Jetty from 6.1.26 to 9.3.14 (#858) Upgrade involved

1/4/2017 7:45:47 PM

Details

diff --git a/azkaban-common/build.gradle b/azkaban-common/build.gradle
index 3618eef..44107da 100644
--- a/azkaban-common/build.gradle
+++ b/azkaban-common/build.gradle
@@ -35,8 +35,9 @@ dependencies {
   compile('org.apache.velocity:velocity:1.7')
   compile('org.codehaus.jackson:jackson-core-asl:1.9.5')
   compile('org.codehaus.jackson:jackson-mapper-asl:1.9.5')
-  compile('org.mortbay.jetty:jetty:6.1.26')
-  compile('org.mortbay.jetty:jetty-util:6.1.26')
+  compile('org.eclipse.jetty:jetty-security:9.3.14+')
+  compile('org.eclipse.jetty:jetty-server:9.3.14+')
+  compile('org.eclipse.jetty:jetty-servlet:9.3.14+')
   compile('org.quartz-scheduler:quartz:2.2.1')
   compile('io.dropwizard.metrics:metrics-core:3.1.0')
   compile('io.dropwizard.metrics:metrics-jvm:3.1.0')
diff --git a/azkaban-exec-server/src/main/java/azkaban/execapp/AzkabanExecutorServer.java b/azkaban-exec-server/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
index 87925c8..093f809 100644
--- a/azkaban-exec-server/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
+++ b/azkaban-exec-server/src/main/java/azkaban/execapp/AzkabanExecutorServer.java
@@ -19,12 +19,15 @@ package azkaban.execapp;
 import com.google.common.base.Throwables;
 
 import org.apache.log4j.Logger;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnectionStatistics;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.joda.time.DateTimeZone;
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.thread.QueuedThreadPool;
 
 import java.io.BufferedWriter;
 import java.io.File;
@@ -61,7 +64,6 @@ import azkaban.executor.Executor;
 import azkaban.executor.ExecutorLoader;
 import azkaban.executor.ExecutorManagerException;
 import azkaban.executor.JdbcExecutorLoader;
-import azkaban.jmx.JmxJettyServer;
 import azkaban.metric.IMetricEmitter;
 import azkaban.metric.MetricException;
 import azkaban.metric.MetricReportManager;
@@ -79,6 +81,14 @@ import static azkaban.constants.ServerInternals.AZKABAN_EXECUTOR_PORT_FILENAME;
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.Objects.requireNonNull;
 
+
+/**
+ * This class is the entry point for Azkaban Executor
+ *
+ * TODO This class needs to be refactored and made smaller.
+ * TODO Investigate if some components can be extracted into separate classes
+ * TODO remove config variables out of this class
+ */
 public class AzkabanExecutorServer {
   private static final String CUSTOM_JMX_ATTRIBUTE_PROCESSOR_PROPERTY = "jmx.attribute.processor.class";
   private static final Logger logger = Logger.getLogger(AzkabanExecutorServer.class);
@@ -147,39 +157,40 @@ public class AzkabanExecutorServer {
   private Server createJettyServer(Props props) {
     int maxThreads = props.getInt("executor.maxThreads", DEFAULT_THREAD_NUMBER);
 
+    // HttpConfiguration object is used to configure the HTTP connector
+    final HttpConfiguration httpConfig = new HttpConfiguration();
+    httpConfig.setRequestHeaderSize(props.getInt("jetty.headerBufferSize", DEFAULT_HEADER_BUFFER_SIZE));
+
+    Server server = new Server(new QueuedThreadPool(maxThreads));
+
+    // Creating a connector for HTTP traffic
+    final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
+
     /*
      * Default to a port number 0 (zero)
      * The Jetty server automatically finds an unused port when the port number is set to zero
-     * TODO: This is using a highly outdated version of jetty [year 2010]. needs to be updated.
      */
-    Server server = new Server(props.getInt("executor.port", 0));
-    QueuedThreadPool httpThreadPool = new QueuedThreadPool(maxThreads);
-    server.setThreadPool(httpThreadPool);
-
-    boolean isStatsOn = props.getBoolean("executor.connector.stats", true);
-    logger.info("Setting up connector with stats on: " + isStatsOn);
+    http.setPort(props.getInt("executor.port", 0));
 
-    for (Connector connector : server.getConnectors()) {
-      connector.setStatsOn(isStatsOn);
-      logger.info(String.format(
-          "Jetty connector name: %s, default header buffer size: %d",
-          connector.getName(), connector.getHeaderBufferSize()));
-      connector.setHeaderBufferSize(props.getInt("jetty.headerBufferSize",
-          DEFAULT_HEADER_BUFFER_SIZE));
-      logger.info(String.format(
-          "Jetty connector name: %s, (if) new header buffer size: %d",
-          connector.getName(), connector.getHeaderBufferSize()));
-    }
+    // Add the HTTP connector
+    server.addConnector(http);
 
-    Context root = new Context(server, "/", Context.SESSIONS);
+    ServletContextHandler root = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
     root.setMaxFormContentSize(MAX_FORM_CONTENT_SIZE);
 
-    root.addServlet(new ServletHolder(new ExecutorServlet()), "/executor");
-    root.addServlet(new ServletHolder(new JMXHttpServlet()), "/jmx");
-    root.addServlet(new ServletHolder(new StatsServlet()), "/stats");
-    root.addServlet(new ServletHolder(new ServerStatisticsServlet()), "/serverStatistics");
+    root.addServlet(ExecutorServlet.class, "/executor");
+    root.addServlet(JMXHttpServlet.class, "/jmx");
+    root.addServlet(StatsServlet.class, "/stats");
+    root.addServlet(ServerStatisticsServlet.class, "/serverStatistics");
 
     root.setAttribute(ServerInternals.AZKABAN_SERVLET_CONTEXT_KEY, this);
+
+    final boolean isStatsOn = props.getBoolean("executor.connector.stats", true);
+    logger.info("Setting up connector with stats on: " + isStatsOn);
+    if (isStatsOn) {
+      ServerConnectionStatistics.addToAllConnectors(server);
+    }
+
     return server;
   }
 
@@ -486,7 +497,6 @@ public class AzkabanExecutorServer {
     logger.info("Registering MBeans...");
     mbeanServer = ManagementFactory.getPlatformMBeanServer();
 
-    registerMbean("executorJetty", new JmxJettyServer(server));
     registerMbean("flowRunnerManager", new JmxFlowRunnerManager(runnerManager));
     registerMbean("jobJMXMBean", JmxJobMBeanManager.getInstance());
 
@@ -570,7 +580,11 @@ public class AzkabanExecutorServer {
     checkState(connectors.length >= 1, "Server must have at least 1 connector");
 
     // The first connector is created upon initializing the server. That's the one that has the port.
-    return connectors[0].getLocalPort();
+    final Connector connector = connectors[0];
+
+    checkState(connector instanceof ServerConnector,
+        "Unexpected Connector instance: " + connector.getClass());
+    return ((ServerConnector) connector).getLocalPort();
   }
 
   /**
diff --git a/azkaban-exec-server/src/test/java/azkaban/execapp/event/JobCallbackRequestMakerTest.java b/azkaban-exec-server/src/test/java/azkaban/execapp/event/JobCallbackRequestMakerTest.java
index bd14da4..75ceab9 100644
--- a/azkaban-exec-server/src/test/java/azkaban/execapp/event/JobCallbackRequestMakerTest.java
+++ b/azkaban-exec-server/src/test/java/azkaban/execapp/event/JobCallbackRequestMakerTest.java
@@ -22,21 +22,18 @@ import javax.servlet.http.HttpServletResponse;
 
 import org.apache.http.client.methods.HttpRequestBase;
 import org.apache.log4j.Logger;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.ServletHolder;
 
 import azkaban.jobcallback.JobCallbackConstants;
 import azkaban.jobcallback.JobCallbackStatusEnum;
 import azkaban.utils.Props;
 
 public class JobCallbackRequestMakerTest {
-
-  private static final Logger logger = Logger
-      .getLogger(JobCallbackRequestMakerTest.class);
+  private static final Logger logger = Logger.getLogger(JobCallbackRequestMakerTest.class);
 
   private static final String SLEEP_DURATION_PARAM = "sleepDuration";
   private static final String STATUS_CODE_PARAM = "returnedStatusCode";
@@ -71,8 +68,8 @@ public class JobCallbackRequestMakerTest {
 
       embeddedJettyServer = new Server(PORT_NUMBER);
 
-      Context context = new Context(embeddedJettyServer, "/", Context.SESSIONS);
-      context.addServlet(new ServletHolder(new DelayServlet()), "/delay");
+      ServletContextHandler context = new ServletContextHandler(embeddedJettyServer, "/", ServletContextHandler.SESSIONS);
+      context.addServlet(DelayServlet.class, "/delay");
 
       System.out.println("Start server");
       embeddedJettyServer.start();
diff --git a/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java b/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
index 2053f0b..c6a149d 100644
--- a/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
+++ b/azkaban-web-server/src/main/java/azkaban/webapp/AzkabanWebServer.java
@@ -18,6 +18,7 @@ package azkaban.webapp;
 
 import com.codahale.metrics.MetricRegistry;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -46,15 +47,19 @@ import org.apache.velocity.app.VelocityEngine;
 import org.apache.velocity.runtime.log.Log4JLogChute;
 import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
 import org.apache.velocity.runtime.resource.loader.JarResourceLoader;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.SecureRequestCustomizer;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnectionStatistics;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.servlet.DefaultServlet;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.joda.time.DateTimeZone;
-import org.mortbay.jetty.Connector;
-import org.mortbay.jetty.Server;
-import org.mortbay.jetty.bio.SocketConnector;
-import org.mortbay.jetty.security.SslSocketConnector;
-import org.mortbay.jetty.servlet.Context;
-import org.mortbay.jetty.servlet.DefaultServlet;
-import org.mortbay.jetty.servlet.ServletHolder;
-import org.mortbay.thread.QueuedThreadPool;
 
 import azkaban.alert.Alerter;
 import azkaban.constants.ServerInternals;
@@ -63,7 +68,6 @@ import azkaban.database.AzkabanDatabaseSetup;
 import azkaban.executor.ExecutorManager;
 import azkaban.executor.JdbcExecutorLoader;
 import azkaban.jmx.JmxExecutorManager;
-import azkaban.jmx.JmxJettyServer;
 import azkaban.jmx.JmxTriggerManager;
 import azkaban.project.JdbcProjectLoader;
 import azkaban.project.ProjectManager;
@@ -127,6 +131,11 @@ import com.linkedin.restli.server.RestliServlet;
  * for sessionizing. jetty.keystore - Jetty keystore . jetty.keypassword - Jetty
  * keystore password jetty.truststore - Jetty truststore jetty.trustpassword -
  * Jetty truststore password
+ *
+ * TODO This class needs to be refactored and made smaller.
+ * TODO Investigate if some components can be extracted into separate classes
+ * TODO extract server creation into separate methods
+ * TODO remove config variables out of this class
  */
 public class AzkabanWebServer extends AzkabanServer {
   private static final String AZKABAN_ACCESS_LOGGER_NAME =
@@ -141,7 +150,6 @@ public class AzkabanWebServer extends AzkabanServer {
       "azkaban.private.properties";
 
   private static final int MAX_FORM_CONTENT_SIZE = 10 * 1024 * 1024;
-  private static final int MAX_HEADER_BUFFER_SIZE = 10 * 1024 * 1024;
   private static AzkabanWebServer app;
 
   private static final String DEFAULT_TIMEZONE_ID = "default.timezone.id";
@@ -644,13 +652,10 @@ public class AzkabanWebServer extends AzkabanServer {
   private VelocityEngine configureVelocityEngine(final boolean devMode) {
     VelocityEngine engine = new VelocityEngine();
     engine.setProperty("resource.loader", "classpath, jar");
-    engine.setProperty("classpath.resource.loader.class",
-        ClasspathResourceLoader.class.getName());
+    engine.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
     engine.setProperty("classpath.resource.loader.cache", !devMode);
-    engine.setProperty("classpath.resource.loader.modificationCheckInterval",
-        5L);
-    engine.setProperty("jar.resource.loader.class",
-        JarResourceLoader.class.getName());
+    engine.setProperty("classpath.resource.loader.modificationCheckInterval", 5L);
+    engine.setProperty("jar.resource.loader.class", JarResourceLoader.class.getName());
     engine.setProperty("jar.resource.loader.cache", !devMode);
     engine.setProperty("resource.manager.logwhenfound", false);
     engine.setProperty("input.encoding", "UTF-8");
@@ -659,15 +664,12 @@ public class AzkabanWebServer extends AzkabanServer {
     engine.setProperty("resource.manager.logwhenfound", false);
     engine.setProperty("velocimacro.permissions.allow.inline", true);
     engine.setProperty("velocimacro.library.autoreload", devMode);
-    engine.setProperty("velocimacro.library",
-        "/azkaban/webapp/servlet/velocity/macros.vm");
-    engine.setProperty(
-        "velocimacro.permissions.allow.inline.to.replace.global", true);
+    engine.setProperty("velocimacro.library", "/azkaban/webapp/servlet/velocity/macros.vm");
+    engine.setProperty("velocimacro.permissions.allow.inline.to.replace.global", true);
     engine.setProperty("velocimacro.arguments.strict", true);
     engine.setProperty("runtime.log.invalid.references", devMode);
     engine.setProperty("runtime.log.logsystem.class", Log4JLogChute.class);
-    engine.setProperty("runtime.log.logsystem.log4j.logger",
-        Logger.getLogger("org.apache.velocity.Logger"));
+    engine.setProperty("runtime.log.logsystem.log4j.logger", Logger.getLogger("org.apache.velocity.Logger"));
     engine.setProperty("parser.pool.size", 3);
     return engine;
   }
@@ -703,67 +705,89 @@ public class AzkabanWebServer extends AzkabanServer {
       return;
     }
 
-    int maxThreads =
-        azkabanSettings.getInt("jetty.maxThreads", DEFAULT_THREAD_NUMBER);
-    boolean isStatsOn =
-        azkabanSettings.getBoolean("jetty.connector.stats", true);
-    logger.info("Setting up connector with stats on: " + isStatsOn);
-
-    boolean ssl;
-    int port;
-    final Server server = new Server();
-    if (azkabanSettings.getBoolean("jetty.use.ssl", true)) {
-      int sslPortNumber =
-          azkabanSettings.getInt("jetty.ssl.port", DEFAULT_SSL_PORT_NUMBER);
-      port = sslPortNumber;
-      ssl = true;
-      logger.info("Setting up Jetty Https Server with port:" + sslPortNumber
-          + " and numThreads:" + maxThreads);
-
-      SslSocketConnector secureConnector = new SslSocketConnector();
-      secureConnector.setPort(sslPortNumber);
-      secureConnector.setKeystore(azkabanSettings.getString("jetty.keystore"));
-      secureConnector.setPassword(azkabanSettings.getString("jetty.password"));
-      secureConnector.setKeyPassword(azkabanSettings
-          .getString("jetty.keypassword"));
-      secureConnector.setTruststore(azkabanSettings
-          .getString("jetty.truststore"));
-      secureConnector.setTrustPassword(azkabanSettings
-          .getString("jetty.trustpassword"));
-      secureConnector.setHeaderBufferSize(MAX_HEADER_BUFFER_SIZE);
+    // Create a Jetty server with custom Thread pool
+    final int maxThreads = azkabanSettings.getInt("jetty.maxThreads", DEFAULT_THREAD_NUMBER);
+    final Server server = new Server(new QueuedThreadPool(maxThreads));
+
+    final int port = azkabanSettings.getInt("jetty.port", DEFAULT_PORT_NUMBER);
+    final int sslPort = azkabanSettings.getInt("jetty.ssl.port", DEFAULT_SSL_PORT_NUMBER);
+    final boolean useSsl = azkabanSettings.getBoolean("jetty.use.ssl", true);
+
+    /*
+     * Configuring HTTP
+     * HttpConfiguration is a configuration holder for http and https.
+     *  - default scheme for http is <code>http</code>,
+     *  - default for secured http is <code>https</code>
+     *
+     * The port for secured communication is set here.
+     */
+    final HttpConfiguration httpConfig = new HttpConfiguration();
+    httpConfig.setSecureScheme("https");
+    httpConfig.setSecurePort(sslPort);
+
+    /*
+     * A Jetty server can have multiple connectors that listens on different ports.
+     * Creating a connector for HTTP traffic
+     */
+    final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
+    http.setPort(port);
+    server.addConnector(http);
+
+    if (useSsl) {
+      logger.info("Setting up Jetty Https Server with port:" + sslPort + " and numThreads:" + maxThreads);
+
+      // SslContextFactory is responsible for holding SSL configuration
+      final SslContextFactory sslContextFactory = new SslContextFactory();
+      sslContextFactory.setKeyStorePath(azkabanSettings.getString("jetty.keystore"));
+      sslContextFactory.setKeyStorePassword(azkabanSettings.getString("jetty.password"));
+      sslContextFactory.setKeyManagerPassword(azkabanSettings.getString("jetty.keypassword"));
+      sslContextFactory.setTrustStorePath(azkabanSettings.getString("jetty.truststore"));
+      sslContextFactory.setTrustStorePassword(azkabanSettings.getString("jetty.trustpassword"));
 
       // set up vulnerable cipher suites to exclude
       List<String> cipherSuitesToExclude = azkabanSettings.getStringList("jetty.excludeCipherSuites");
       logger.info("Excluded Cipher Suites: " + String.valueOf(cipherSuitesToExclude));
       if (cipherSuitesToExclude != null && !cipherSuitesToExclude.isEmpty()) {
-        secureConnector.setExcludeCipherSuites(cipherSuitesToExclude.toArray(new String[0]));
+        sslContextFactory.setExcludeCipherSuites(cipherSuitesToExclude.toArray(new String[0]));
       }
 
-      server.addConnector(secureConnector);
-    } else {
-      ssl = false;
-      port = azkabanSettings.getInt("jetty.port", DEFAULT_PORT_NUMBER);
-      SocketConnector connector = new SocketConnector();
-      connector.setPort(port);
-      connector.setHeaderBufferSize(MAX_HEADER_BUFFER_SIZE);
-      server.addConnector(connector);
+      /*
+       * Configuring HTTPS
+       * A new HttpConfiguration object is required for each connector. Cloning previous configuration here.
+       */
+      final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
+      /*
+       * SecureRequestCustomizer which is how a new connector is able to resolve the https connection before
+       * handing control over to the Jetty Server.
+       */
+      httpsConfig.addCustomizer(new SecureRequestCustomizer());
+
+      // Creating a connector for HTTPS. The SslConnectionFactory is responsible for SSL configuration.
+      final ServerConnector https = new ServerConnector(server,
+          new SslConnectionFactory(sslContextFactory,"http/1.1"),
+          new HttpConnectionFactory(httpsConfig));
+      https.setPort(sslPort);
+      https.setIdleTimeout(500000);
+
+      // Add the HTTPS connector
+      server.addConnector(https);
     }
 
-    // setting stats configuration for connectors
-    for (Connector connector : server.getConnectors()) {
-      connector.setStatsOn(isStatsOn);
+    final boolean statsEnabled = azkabanSettings.getBoolean("jetty.connector.stats", true);
+    logger.info("Setting up connector with stats on: " + statsEnabled);
+
+    if (statsEnabled) {
+      ServerConnectionStatistics.addToAllConnectors(server);
     }
 
     String hostname = azkabanSettings.getString("jetty.hostname", "localhost");
     azkabanSettings.put("server.hostname", hostname);
     azkabanSettings.put("server.port", port);
-    azkabanSettings.put("server.useSSL", String.valueOf(ssl));
+    azkabanSettings.put("server.useSSL", String.valueOf(useSsl));
 
     app = new AzkabanWebServer(server, azkabanSettings);
 
-    boolean checkDB =
-        azkabanSettings.getBoolean(AzkabanDatabaseSetup.DATABASE_CHECK_VERSION,
-            false);
+    final boolean checkDB = azkabanSettings.getBoolean(AzkabanDatabaseSetup.DATABASE_CHECK_VERSION, false);
     if (checkDB) {
       AzkabanDatabaseSetup setup = new AzkabanDatabaseSetup(azkabanSettings);
       setup.loadTableInfo();
@@ -776,38 +800,34 @@ public class AzkabanWebServer extends AzkabanServer {
       }
     }
 
-    QueuedThreadPool httpThreadPool = new QueuedThreadPool(maxThreads);
-    server.setThreadPool(httpThreadPool);
-
-    String staticDir =
-        azkabanSettings.getString("web.resource.dir", DEFAULT_STATIC_DIR);
+    String staticDir = azkabanSettings.getString("web.resource.dir", DEFAULT_STATIC_DIR);
     logger.info("Setting up web resource dir " + staticDir);
-    Context root = new Context(server, "/", Context.SESSIONS);
+
+    ServletContextHandler root = new ServletContextHandler(server, "/", ServletContextHandler.SESSIONS);
+
+    root.setContextPath("/");
     root.setMaxFormContentSize(MAX_FORM_CONTENT_SIZE);
 
-    String defaultServletPath =
-        azkabanSettings.getString("azkaban.default.servlet.path", "/index");
     root.setResourceBase(staticDir);
-    ServletHolder indexRedirect =
-        new ServletHolder(new IndexRedirectServlet(defaultServletPath));
-    root.addServlet(indexRedirect, "/");
-    ServletHolder index = new ServletHolder(new ProjectServlet());
-    root.addServlet(index, "/index");
-
-    ServletHolder staticServlet = new ServletHolder(new DefaultServlet());
-    root.addServlet(staticServlet, "/css/*");
-    root.addServlet(staticServlet, "/js/*");
-    root.addServlet(staticServlet, "/images/*");
-    root.addServlet(staticServlet, "/fonts/*");
-    root.addServlet(staticServlet, "/favicon.ico");
-
-    root.addServlet(new ServletHolder(new ProjectManagerServlet()), "/manager");
-    root.addServlet(new ServletHolder(new ExecutorServlet()), "/executor");
-    root.addServlet(new ServletHolder(new HistoryServlet()), "/history");
-    root.addServlet(new ServletHolder(new ScheduleServlet()), "/schedule");
-    root.addServlet(new ServletHolder(new JMXHttpServlet()), "/jmx");
-    root.addServlet(new ServletHolder(new TriggerManagerServlet()), "/triggers");
-    root.addServlet(new ServletHolder(new StatsServlet()), "/stats");
+
+    final String defaultServletPath = azkabanSettings.getString("azkaban.default.servlet.path", "/index");
+    root.addServlet(new ServletHolder(new IndexRedirectServlet(defaultServletPath)), "/");
+    root.addServlet(new ServletHolder(new ProjectServlet()), "/index");
+
+    // TODO static content should be provided by a separate single servlet
+    root.addServlet(DefaultServlet.class, "/css/*");
+    root.addServlet(DefaultServlet.class, "/js/*");
+    root.addServlet(DefaultServlet.class, "/images/*");
+    root.addServlet(DefaultServlet.class, "/fonts/*");
+    root.addServlet(DefaultServlet.class, "/favicon.ico");
+
+    root.addServlet(ProjectManagerServlet.class, "/manager");
+    root.addServlet(ExecutorServlet.class, "/executor");
+    root.addServlet(HistoryServlet.class, "/history");
+    root.addServlet(ScheduleServlet.class, "/schedule");
+    root.addServlet(JMXHttpServlet.class, "/jmx");
+    root.addServlet(TriggerManagerServlet.class, "/triggers");
+    root.addServlet(StatsServlet.class, "/stats");
 
     ServletHolder restliHolder = new ServletHolder(new RestliServlet());
     restliHolder.setInitParameter("resourcePackages", "azkaban.restli");
@@ -818,10 +838,8 @@ public class AzkabanWebServer extends AzkabanServer {
     loadViewerPlugins(root, viewerPluginDir, app.getVelocityEngine());
 
     // triggerplugin
-    String triggerPluginDir =
-        azkabanSettings.getString("trigger.plugin.dir", "plugins/triggers");
-    Map<String, TriggerPlugin> triggerPlugins =
-        loadTriggerPlugins(root, triggerPluginDir, app);
+    String triggerPluginDir = azkabanSettings.getString("trigger.plugin.dir", "plugins/triggers");
+    Map<String, TriggerPlugin> triggerPlugins = loadTriggerPlugins(root, triggerPluginDir, app);
     app.setTriggerPlugins(triggerPlugins);
     // always have basic time trigger
     // TODO: find something else to do the job
@@ -860,15 +878,13 @@ public class AzkabanWebServer extends AzkabanServer {
             && new File("/usr/bin/head").exists()) {
           logger.info("logging top memeory consumer");
 
-          java.lang.ProcessBuilder processBuilder =
-              new java.lang.ProcessBuilder("/bin/bash", "-c",
-                  "/bin/ps aux --sort -rss | /usr/bin/head");
+          ProcessBuilder processBuilder =
+              new ProcessBuilder("/bin/bash", "-c", "/bin/ps aux --sort -rss | /usr/bin/head");
           Process p = processBuilder.start();
           p.waitFor();
 
           InputStream is = p.getInputStream();
-          java.io.BufferedReader reader =
-              new java.io.BufferedReader(new InputStreamReader(is));
+          BufferedReader reader = new BufferedReader(new InputStreamReader(is));
           String line = null;
           while ((line = reader.readLine()) != null) {
             logger.info(line);
@@ -877,11 +893,10 @@ public class AzkabanWebServer extends AzkabanServer {
         }
       }
     });
-    logger.info("Server running on " + (ssl ? "ssl" : "") + " port " + port
-        + ".");
+    logger.info("Server started. HTTP: " + port + (!useSsl? "": " HTTPS: " + sslPort));
   }
 
-  private static Map<String, TriggerPlugin> loadTriggerPlugins(Context root,
+  private static Map<String, TriggerPlugin> loadTriggerPlugins(ServletContextHandler root,
       String pluginPath, AzkabanWebServer azkabanWebApp) {
     File triggerPluginPath = new File(pluginPath);
     if (!triggerPluginPath.exists()) {
@@ -991,7 +1006,7 @@ public class AzkabanWebServer extends AzkabanServer {
       try {
         constructor =
             triggerClass.getConstructor(String.class, Props.class,
-                Context.class, AzkabanWebServer.class);
+                ServletContextHandler.class, AzkabanWebServer.class);
       } catch (NoSuchMethodException e) {
         logger.error("Constructor not found in " + pluginClass);
         continue;
@@ -1028,7 +1043,7 @@ public class AzkabanWebServer extends AzkabanServer {
     return triggerPlugins;
   }
 
-  private static void loadViewerPlugins(Context root, String pluginPath,
+  private static void loadViewerPlugins(ServletContextHandler root, String pluginPath,
       VelocityEngine ve) {
     File viewerPluginPath = new File(pluginPath);
     if (!viewerPluginPath.exists()) {
@@ -1263,7 +1278,6 @@ public class AzkabanWebServer extends AzkabanServer {
     logger.info("Registering MBeans...");
     mbeanServer = ManagementFactory.getPlatformMBeanServer();
 
-    registerMbean("jetty", new JmxJettyServer(server));
     registerMbean("triggerManager", new JmxTriggerManager(triggerManager));
     if (executorManager instanceof ExecutorManager) {
       registerMbean("executorManager", new JmxExecutorManager(
diff --git a/azkaban-web-server/src/test/java/azkaban/fixture/MockLoginAzkabanServlet.java b/azkaban-web-server/src/test/java/azkaban/fixture/MockLoginAzkabanServlet.java
index e266711..84a9967 100644
--- a/azkaban-web-server/src/test/java/azkaban/fixture/MockLoginAzkabanServlet.java
+++ b/azkaban-web-server/src/test/java/azkaban/fixture/MockLoginAzkabanServlet.java
@@ -16,29 +16,20 @@
 
 package azkaban.fixture;
 
-
-import azkaban.server.AzkabanServer;
 import azkaban.server.session.Session;
 import azkaban.server.session.SessionCache;
-import azkaban.user.UserManager;
 import azkaban.utils.Props;
 import azkaban.webapp.AzkabanWebServer;
 import azkaban.webapp.servlet.LoginAbstractAzkabanServlet;
-import azkaban.user.User;
-import org.apache.velocity.app.VelocityEngine;
-import org.mockito.Spy;
-import org.mortbay.jetty.Server;
-
-import javax.servlet.ServletConfig;
+import java.io.IOException;
 import javax.servlet.ServletException;
-import javax.servlet.http.Cookie;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import org.apache.velocity.app.VelocityEngine;
 
-import java.io.IOException;
-import java.util.UUID;
-
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 public class MockLoginAzkabanServlet extends LoginAbstractAzkabanServlet {
 
@@ -94,10 +85,7 @@ public class MockLoginAzkabanServlet extends LoginAbstractAzkabanServlet {
             throws Exception{
 
         MockLoginAzkabanServlet servlet = new MockLoginAzkabanServlet();
-
-        Server server = mock(Server.class);
         Props props = new Props();
-        UserManager userManager = mock(UserManager.class);
 
         // Need to mock and inject an application instance into the servlet
         AzkabanWebServer app = mock(AzkabanWebServer.class);