diff --git a/core/pom.xml b/core/pom.xml
index 55cbe898537c..837f889650c2 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -272,6 +272,11 @@
oro
${oro.version}
+
+ org.eclipse.jetty
+ jetty-proxy
+ test
+
org.seleniumhq.selenium
selenium-java
diff --git a/core/src/main/resources/org/apache/spark/ui/static/historypage-template.html b/core/src/main/resources/org/apache/spark/ui/static/historypage-template.html
index a2b3826dd324..4b8644199967 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/historypage-template.html
+++ b/core/src/main/resources/org/apache/spark/ui/static/historypage-template.html
@@ -64,10 +64,10 @@
{{#applications}}
- | {{id}} |
+ {{id}} |
{{name}} |
{{#attempts}}
- {{attemptId}} |
+ {{attemptId}} |
{{startTime}} |
{{endTime}} |
{{duration}} |
diff --git a/core/src/main/resources/org/apache/spark/ui/static/historypage.js b/core/src/main/resources/org/apache/spark/ui/static/historypage.js
index 177120aaa6c1..ae6d41faa0ce 100644
--- a/core/src/main/resources/org/apache/spark/ui/static/historypage.js
+++ b/core/src/main/resources/org/apache/spark/ui/static/historypage.js
@@ -141,7 +141,11 @@ $(document).ready(function() {
}
}
- var data = {"applications": array}
+ var data = {
+ "uiroot": uiRoot,
+ "applications": array
+ }
+
$.get("static/historypage-template.html", function(template) {
historySummary.append(Mustache.render($(template).filter("#history-summary-template").html(),data));
var selector = "#history-summary-table";
diff --git a/core/src/main/resources/org/apache/spark/ui/static/webui.js b/core/src/main/resources/org/apache/spark/ui/static/webui.js
new file mode 100644
index 000000000000..eb0b748a51ff
--- /dev/null
+++ b/core/src/main/resources/org/apache/spark/ui/static/webui.js
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+var uiRoot = "";
+
+function setUIRoot(val) {
+ uiRoot = val;
+}
diff --git a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
index 1aa85d60ea81..f7d37f6d9c6a 100644
--- a/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
+++ b/core/src/main/scala/org/apache/spark/ui/UIUtils.scala
@@ -168,7 +168,8 @@ private[spark] object UIUtils extends Logging {
-
+
+
}
def vizHeaderNodes: Seq[Node] = {
diff --git a/core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala b/core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala
index ae3f5d9c012e..51efdd0778b5 100644
--- a/core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala
+++ b/core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala
@@ -29,6 +29,8 @@ import com.codahale.metrics.Counter
import com.google.common.io.{ByteStreams, Files}
import org.apache.commons.io.{FileUtils, IOUtils}
import org.apache.hadoop.fs.{FileStatus, FileSystem, Path}
+import org.eclipse.jetty.proxy.ProxyServlet
+import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
import org.json4s.JsonAST._
import org.json4s.jackson.JsonMethods
import org.json4s.jackson.JsonMethods._
@@ -258,8 +260,7 @@ class HistoryServerSuite extends SparkFunSuite with BeforeAndAfter with Matchers
getContentAndCode("foobar")._1 should be (HttpServletResponse.SC_NOT_FOUND)
}
- test("relative links are prefixed with uiRoot (spark.ui.proxyBase)") {
- val proxyBaseBeforeTest = System.getProperty("spark.ui.proxyBase")
+ test("static relative links are prefixed with uiRoot (spark.ui.proxyBase)") {
val uiRoot = Option(System.getenv("APPLICATION_WEB_PROXY_BASE")).getOrElse("/testwebproxybase")
val page = new HistoryPage(server)
val request = mock[HttpServletRequest]
@@ -267,7 +268,6 @@ class HistoryServerSuite extends SparkFunSuite with BeforeAndAfter with Matchers
// when
System.setProperty("spark.ui.proxyBase", uiRoot)
val response = page.render(request)
- System.setProperty("spark.ui.proxyBase", Option(proxyBaseBeforeTest).getOrElse(""))
// then
val urls = response \\ "@href" map (_.toString)
@@ -275,6 +275,80 @@ class HistoryServerSuite extends SparkFunSuite with BeforeAndAfter with Matchers
all (siteRelativeLinks) should startWith (uiRoot)
}
+ test("ajax rendered relative links are prefixed with uiRoot (spark.ui.proxyBase)") {
+ val uiRoot = "/testwebproxybase"
+ System.setProperty("spark.ui.proxyBase", uiRoot)
+
+ server.stop()
+
+ val conf = new SparkConf()
+ .set("spark.history.fs.logDirectory", logDir.getAbsolutePath)
+ .set("spark.history.fs.update.interval", "0")
+ .set("spark.testing", "true")
+
+ provider = new FsHistoryProvider(conf)
+ provider.checkForLogs()
+ val securityManager = new SecurityManager(conf)
+
+ server = new HistoryServer(conf, provider, securityManager, 18080)
+ server.initialize()
+ server.bind()
+
+ val port = server.boundPort
+
+ val servlet = new ProxyServlet {
+ override def rewriteTarget(request: HttpServletRequest): String = {
+ // servlet acts like a proxy that redirects calls made on
+ // spark.ui.proxyBase context path to the normal servlet handlers operating off "/"
+ val sb = request.getRequestURL()
+
+ if (request.getQueryString() != null) {
+ sb.append(s"?${request.getQueryString()}")
+ }
+
+ val proxyidx = sb.indexOf(uiRoot)
+ sb.delete(proxyidx, proxyidx + uiRoot.length).toString
+ }
+ }
+
+ val contextHandler = new ServletContextHandler
+ val holder = new ServletHolder(servlet)
+ contextHandler.setContextPath(uiRoot)
+ contextHandler.addServlet(holder, "/")
+ server.attachHandler(contextHandler)
+
+ implicit val webDriver: WebDriver = new HtmlUnitDriver(true) {
+ getWebClient.getOptions.setThrowExceptionOnScriptError(false)
+ }
+
+ try {
+ val url = s"http://localhost:$port"
+
+ go to s"$url$uiRoot"
+
+ // expect the ajax call to finish in 5 seconds
+ implicitlyWait(org.scalatest.time.Span(5, org.scalatest.time.Seconds))
+
+ // once this findAll call returns, we know the ajax load of the table completed
+ findAll(ClassNameQuery("odd"))
+
+ val links = findAll(TagNameQuery("a"))
+ .map(_.attribute("href"))
+ .filter(_.isDefined)
+ .map(_.get)
+ .filter(_.startsWith(url)).toList
+
+ // there are atleast some URL links that were generated via javascript,
+ // and they all contain the spark.ui.proxyBase (uiRoot)
+ links.length should be > 4
+ all(links) should startWith(url + uiRoot)
+ } finally {
+ contextHandler.stop()
+ quit()
+ }
+
+ }
+
test("incomplete apps get refreshed") {
implicit val webDriver: WebDriver = new HtmlUnitDriver
diff --git a/pom.xml b/pom.xml
index d97f348f6d54..ac2ec0e66e10 100644
--- a/pom.xml
+++ b/pom.xml
@@ -431,6 +431,12 @@
httpcore
${commons.httpcore.version}
+
+ org.eclipse.jetty
+ jetty-proxy
+ ${jetty.version}
+ test
+
org.seleniumhq.selenium
selenium-java