diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
index 9ec25ae..60d5aa1 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
@@ -1455,6 +1455,23 @@ private static void addDeprecatedKeys() {
public static final String TIMELINE_SERVICE_PREFIX =
YARN_PREFIX + "timeline-service.";
+ /**
+ * Comma seperated list of names for UIs hosted in the timeline server
+ * (For pluggable UIs).
+ */
+ public static final String TIMELINE_SERVICE_UI_NAMES =
+ TIMELINE_SERVICE_PREFIX + "ui-names";
+
+ /** Relative web path that will serve up this UI (For pluggable UIs). */
+ public static final String TIMELINE_SERVICE_UI_WEB_PATH_PREFIX =
+ TIMELINE_SERVICE_PREFIX + "ui-web-path.";
+
+ /**
+ * Path to war file or static content directory for this UI
+ * (For pluggable UIs).
+ */
+ public static final String TIMELINE_SERVICE_UI_ON_DISK_PATH_PREFIX =
+ TIMELINE_SERVICE_PREFIX + "ui-on-disk-path.";
// mark app-history related configs @Private as application history is going
// to be integrated into the timeline service
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
index bda24aa..0c6edad 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
@@ -166,7 +166,7 @@
return this;
}
- public WebApp start(WebApp webapp) {
+ public WebApp build(WebApp webapp) {
if (webapp == null) {
webapp = new WebApp() {
@Override
@@ -271,8 +271,7 @@ public void setup() {
webapp.setConf(conf);
webapp.setHttpServer(server);
- server.start();
- LOG.info("Web app /"+ name +" started at "+ server.getConnectorAddress(0).getPort());
+
} catch (ClassNotFoundException e) {
throw new WebAppException("Error starting http server", e);
} catch (IOException e) {
@@ -300,6 +299,19 @@ public WebApp start() {
return start(null);
}
+ public WebApp start(WebApp webapp) {
+ WebApp webApp = build(webapp);
+ HttpServer2 httpServer = webApp.httpServer();
+ try {
+ httpServer.start();
+ LOG.info("Web app " + name + " started at "
+ + httpServer.getConnectorAddress(0).getPort());
+ } catch (IOException e) {
+ throw new WebAppException("Error starting http server", e);
+ }
+ return webApp;
+ }
+
private String inferHostClass() {
String thisClass = this.getClass().getName();
Throwable t = new Throwable();
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
index b76defb..175f356 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
@@ -1825,6 +1825,12 @@
+ Comma separated list of UIs that will be hosted
+ yarn.timeline-service.ui-names
+
+
+
+
Default maximum number of retires for timeline servive client
and value -1 means no limit.
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
index 0ec27d7..2bcbeba 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/main/java/org/apache/hadoop/yarn/server/applicationhistoryservice/ApplicationHistoryServer.java
@@ -26,6 +26,7 @@
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience.Private;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.http.HttpServer2;
import org.apache.hadoop.metrics2.lib.DefaultMetricsSystem;
import org.apache.hadoop.metrics2.source.JvmMetrics;
import org.apache.hadoop.security.AuthenticationFilterInitializer;
@@ -54,6 +55,8 @@
import org.apache.hadoop.yarn.webapp.WebApp;
import org.apache.hadoop.yarn.webapp.WebApps;
import org.apache.hadoop.yarn.webapp.util.WebAppUtils;
+import org.mortbay.jetty.servlet.FilterHolder;
+import org.mortbay.jetty.webapp.WebAppContext;
import com.google.common.annotations.VisibleForTesting;
@@ -137,6 +140,12 @@ ApplicationHistoryClientService getClientService() {
return this.ahsClientService;
}
+ @Private
+ @VisibleForTesting
+ int getPort() {
+ return this.webApp.httpServer().getConnectorAddress(0).getPort();
+ }
+
/**
* @return ApplicationTimelineStore
*/
@@ -223,6 +232,7 @@ private TimelineDataManager createTimelineDataManager(Configuration conf) {
timelineStore, new TimelineACLsManager(conf));
}
+ @SuppressWarnings("unchecked")
private void startWebApp() {
Configuration conf = getConfig();
TimelineAuthenticationFilter.setTimelineDelegationTokenSecretManager(
@@ -275,14 +285,42 @@ private void startWebApp() {
String bindAddress = WebAppUtils.getWebAppBindURL(conf,
YarnConfiguration.TIMELINE_SERVICE_BIND_HOST,
WebAppUtils.getAHSWebAppURLWithoutScheme(conf));
- LOG.info("Instantiating AHSWebApp at " + bindAddress);
try {
+ AHSWebApp ahsWebApp = new AHSWebApp(timelineDataManager, ahsClientService);
webApp =
WebApps
.$for("applicationhistory", ApplicationHistoryClientService.class,
ahsClientService, "ws")
- .with(conf).at(bindAddress).start(
- new AHSWebApp(timelineDataManager, ahsClientService));
+ .with(conf).withAttribute(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
+ conf.get(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS)).at(bindAddress).build(ahsWebApp);
+ HttpServer2 httpServer = webApp.httpServer();
+
+ String[] names = conf.getTrimmedStrings(YarnConfiguration.TIMELINE_SERVICE_UI_NAMES);
+ WebAppContext webAppContext = httpServer.getWebAppContext();
+
+ for (String name : names) {
+ String webPath = conf.get(
+ YarnConfiguration.TIMELINE_SERVICE_UI_WEB_PATH_PREFIX + name);
+ String onDiskPath = conf.get(
+ YarnConfiguration.TIMELINE_SERVICE_UI_ON_DISK_PATH_PREFIX + name);
+ WebAppContext uiWebAppContext = new WebAppContext();
+ uiWebAppContext.setContextPath(webPath);
+ uiWebAppContext.setWar(onDiskPath);
+ final String[] ALL_URLS = { "/*" };
+ FilterHolder[] filterHolders =
+ webAppContext.getServletHandler().getFilters();
+ for (FilterHolder filterHolder: filterHolders) {
+ if (!"guice".equals(filterHolder.getName())) {
+ HttpServer2.defineFilter(uiWebAppContext, filterHolder.getName(),
+ filterHolder.getClassName(), filterHolder.getInitParameters(),
+ ALL_URLS);
+ }
+ }
+ LOG.info("Hosting " + name + " from " + onDiskPath + " at " + webPath);
+ httpServer.addContext(uiWebAppContext, true);
+ }
+ httpServer.start();
+ LOG.info("Instantiating AHSWebApp at " + getPort());
} catch (Exception e) {
String msg = "AHSWebApp failed to start.";
LOG.error(msg, e);
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/applicationhistoryservice/TestApplicationHistoryServer.java hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/applicationhistoryservice/TestApplicationHistoryServer.java
index a7e7daa..0d213f4 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/applicationhistoryservice/TestApplicationHistoryServer.java
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-applicationhistoryservice/src/test/java/org/apache/hadoop/yarn/server/applicationhistoryservice/TestApplicationHistoryServer.java
@@ -28,16 +28,20 @@
import org.apache.hadoop.service.Service.STATE;
import org.apache.hadoop.util.ExitUtil;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
-import org.apache.hadoop.yarn.server.applicationhistoryservice.webapp.AHSWebApp;
import org.apache.hadoop.yarn.server.timeline.MemoryTimelineStore;
import org.apache.hadoop.yarn.server.timeline.TimelineStore;
import org.apache.hadoop.yarn.server.timeline.recovery.MemoryTimelineStateStore;
import org.apache.hadoop.yarn.server.timeline.recovery.TimelineStateStore;
import org.apache.hadoop.yarn.server.timeline.security.TimelineAuthenticationFilterInitializer;
-import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
import java.util.HashMap;
import java.util.Map;
@@ -173,4 +177,49 @@ public void testFilterOverrides() throws Exception {
}
}
+ @Test(timeout = 240000)
+ public void testHostedUIs() throws Exception {
+
+ ApplicationHistoryServer historyServer = new ApplicationHistoryServer();
+ Configuration config = new YarnConfiguration();
+ config.setClass(YarnConfiguration.TIMELINE_SERVICE_STORE,
+ MemoryTimelineStore.class, TimelineStore.class);
+ config.setClass(YarnConfiguration.TIMELINE_SERVICE_STATE_STORE_CLASS,
+ MemoryTimelineStateStore.class, TimelineStateStore.class);
+ config.set(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS,
+ "localhost:0");
+ final String UI1 = "UI1";
+ String connFileStr = "";
+
+ File diskFile = new File("./pom.xml");
+ String diskFileStr = readInputStream(new FileInputStream(diskFile));
+ try {
+ config.set(YarnConfiguration.TIMELINE_SERVICE_UI_NAMES, UI1);
+ config.set(YarnConfiguration.TIMELINE_SERVICE_UI_WEB_PATH_PREFIX + UI1,
+ "/" + UI1);
+ config.set(YarnConfiguration.TIMELINE_SERVICE_UI_ON_DISK_PATH_PREFIX
+ + UI1, "./");
+ historyServer.init(config);
+ historyServer.start();
+ URL url = new URL("http://localhost:" + historyServer.getPort() + "/"
+ + UI1 + "/pom.xml");
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.connect();
+ assertEquals(HttpURLConnection.HTTP_OK, conn.getResponseCode());
+ connFileStr = readInputStream(conn.getInputStream());
+ } finally {
+ historyServer.stop();
+ }
+ assertEquals("Web file contents should be the same as on disk contents",
+ diskFileStr, connFileStr);
+ }
+ private String readInputStream(InputStream input) throws Exception {
+ ByteArrayOutputStream data = new ByteArrayOutputStream();
+ byte[] buffer = new byte[512];
+ int read;
+ while ((read = input.read(buffer)) >= 0) {
+ data.write(buffer, 0, read);
+ }
+ return new String(data.toByteArray(), "UTF-8");
+ }
}
diff --git hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/TimelineServer.md hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/TimelineServer.md
index 72b813a..de45efa 100644
--- hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/TimelineServer.md
+++ hadoop-yarn-project/hadoop-yarn/hadoop-yarn-site/src/site/markdown/TimelineServer.md
@@ -185,6 +185,14 @@ selected if this policy is either of `HTTPS_ONLY` or `HTTP_AND_HTTPS`.
| `yarn.timeline-service.client.retry-interval-ms` | The interval in milliseconds between retries for the timeline service client. Defaults to `1000`. |
| `yarn.timeline-service.generic-application-history.max-applications` | The max number of applications could be fetched by using REST API or application history protocol and shown in timeline server web ui. Defaults to `10000`. |
+#### UI Hosting Configuration
+
+The timeline service can host multiple UIs if enabled. The service can support both static web sites hosted in a directory or war files bundled. The web UI is then hosted on the timeline service HTTP port under the path configured.
+| Configuration Property | Description |
+|:---- |:---- |
+| `yarn.timeline-service.ui-names` | Comma separated list of UIs that will be hosted. Defaults to `none`. |
+| `yarn.timeline-service.ui-on-disk-path.$name` | For each of the ui-names, an on disk path should be specified to the directory service static content or the location of a web archive (war file). |
+| `yarn.timeline-service.ui-web-path.$name` | For each of the ui-names, the web path should be specified relative to the Timeline server root. Paths should begin with a starting slash. |
#### Security Configuration