Skip to content

Commit de545e7

Browse files
vijoshiMarcelo Vanzin
authored andcommitted
[SPARK-16808][CORE] History Server main page does not honor APPLICATION_WEB_PROXY_BASE
## What changes were proposed in this pull request? Backport SPARK-16808 (#15742) to branch-2.0. Application links generated on the history server UI no longer (regression from 1.6) contain the configured spark.ui.proxyBase in the links. To address this, made the uiRoot available globally to all javascripts for Web UI. Updated the mustache template (historypage-template.html) to include the uiroot for rendering links to the applications. The existing test was not sufficient to verify the scenario where ajax call is used to populate the application listing template, so added a new selenium test case to cover this scenario. ## How was this patch tested? Existing tests and a new unit test. No visual changes to the UI. Author: Vinayak <vijoshi5@in.ibm.com> Closes #15855 from vijoshi/SPARK-16808_branch-2.0.
1 parent 9abff1b commit de545e7

File tree

7 files changed

+119
-7
lines changed

7 files changed

+119
-7
lines changed

core/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@
272272
<artifactId>oro</artifactId>
273273
<version>${oro.version}</version>
274274
</dependency>
275+
<dependency>
276+
<groupId>org.eclipse.jetty</groupId>
277+
<artifactId>jetty-proxy</artifactId>
278+
<scope>test</scope>
279+
</dependency>
275280
<dependency>
276281
<groupId>org.seleniumhq.selenium</groupId>
277282
<artifactId>selenium-java</artifactId>

core/src/main/resources/org/apache/spark/ui/static/historypage-template.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@
6464
<tbody>
6565
{{#applications}}
6666
<tr>
67-
<td class="rowGroupColumn"><span title="{{id}}"><a href="/history/{{id}}/{{num}}/jobs/">{{id}}</a></span></td>
67+
<td class="rowGroupColumn"><span title="{{id}}"><a href="{{uiroot}}/history/{{id}}/{{num}}/jobs/">{{id}}</a></span></td>
6868
<td class="rowGroupColumn">{{name}}</td>
6969
{{#attempts}}
70-
<td class="attemptIDSpan"><a href="/history/{{id}}/{{attemptId}}/jobs/">{{attemptId}}</a></td>
70+
<td class="attemptIDSpan"><a href="{{uiroot}}/history/{{id}}/{{attemptId}}/jobs/">{{attemptId}}</a></td>
7171
<td>{{startTime}}</td>
7272
<td>{{endTime}}</td>
7373
<td><span title="{{duration}}" class="durationClass">{{duration}}</span></td>

core/src/main/resources/org/apache/spark/ui/static/historypage.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,11 @@ $(document).ready(function() {
141141
}
142142
}
143143

144-
var data = {"applications": array}
144+
var data = {
145+
"uiroot": uiRoot,
146+
"applications": array
147+
}
148+
145149
$.get("static/historypage-template.html", function(template) {
146150
historySummary.append(Mustache.render($(template).filter("#history-summary-template").html(),data));
147151
var selector = "#history-summary-table";
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
var uiRoot = "";
19+
20+
function setUIRoot(val) {
21+
uiRoot = val;
22+
}

core/src/main/scala/org/apache/spark/ui/UIUtils.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@ private[spark] object UIUtils extends Logging {
168168
<script src={prependBaseUri("/static/table.js")}></script>
169169
<script src={prependBaseUri("/static/additional-metrics.js")}></script>
170170
<script src={prependBaseUri("/static/timeline-view.js")}></script>
171-
<script src={prependBaseUri("/static/log-view.js")}></script>
171+
<script src={prependBaseUri("/static/webui.js")}></script>
172+
<script>setUIRoot('{UIUtils.uiRoot}')</script>
172173
}
173174

174175
def vizHeaderNodes: Seq[Node] = {

core/src/test/scala/org/apache/spark/deploy/history/HistoryServerSuite.scala

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import com.codahale.metrics.Counter
2929
import com.google.common.io.{ByteStreams, Files}
3030
import org.apache.commons.io.{FileUtils, IOUtils}
3131
import org.apache.hadoop.fs.{FileStatus, FileSystem, Path}
32+
import org.eclipse.jetty.proxy.ProxyServlet
33+
import org.eclipse.jetty.servlet.{ServletContextHandler, ServletHolder}
3234
import org.json4s.JsonAST._
3335
import org.json4s.jackson.JsonMethods
3436
import org.json4s.jackson.JsonMethods._
@@ -258,23 +260,95 @@ class HistoryServerSuite extends SparkFunSuite with BeforeAndAfter with Matchers
258260
getContentAndCode("foobar")._1 should be (HttpServletResponse.SC_NOT_FOUND)
259261
}
260262

261-
test("relative links are prefixed with uiRoot (spark.ui.proxyBase)") {
262-
val proxyBaseBeforeTest = System.getProperty("spark.ui.proxyBase")
263+
test("static relative links are prefixed with uiRoot (spark.ui.proxyBase)") {
263264
val uiRoot = Option(System.getenv("APPLICATION_WEB_PROXY_BASE")).getOrElse("/testwebproxybase")
264265
val page = new HistoryPage(server)
265266
val request = mock[HttpServletRequest]
266267

267268
// when
268269
System.setProperty("spark.ui.proxyBase", uiRoot)
269270
val response = page.render(request)
270-
System.setProperty("spark.ui.proxyBase", Option(proxyBaseBeforeTest).getOrElse(""))
271271

272272
// then
273273
val urls = response \\ "@href" map (_.toString)
274274
val siteRelativeLinks = urls filter (_.startsWith("/"))
275275
all (siteRelativeLinks) should startWith (uiRoot)
276276
}
277277

278+
test("ajax rendered relative links are prefixed with uiRoot (spark.ui.proxyBase)") {
279+
val uiRoot = "/testwebproxybase"
280+
System.setProperty("spark.ui.proxyBase", uiRoot)
281+
282+
server.stop()
283+
284+
val conf = new SparkConf()
285+
.set("spark.history.fs.logDirectory", logDir.getAbsolutePath)
286+
.set("spark.history.fs.update.interval", "0")
287+
.set("spark.testing", "true")
288+
289+
provider = new FsHistoryProvider(conf)
290+
provider.checkForLogs()
291+
val securityManager = new SecurityManager(conf)
292+
293+
server = new HistoryServer(conf, provider, securityManager, 18080)
294+
server.initialize()
295+
server.bind()
296+
297+
val port = server.boundPort
298+
299+
val servlet = new ProxyServlet {
300+
override def rewriteTarget(request: HttpServletRequest): String = {
301+
// servlet acts like a proxy that redirects calls made on
302+
// spark.ui.proxyBase context path to the normal servlet handlers operating off "/"
303+
val sb = request.getRequestURL()
304+
305+
if (request.getQueryString() != null) {
306+
sb.append(s"?${request.getQueryString()}")
307+
}
308+
309+
val proxyidx = sb.indexOf(uiRoot)
310+
sb.delete(proxyidx, proxyidx + uiRoot.length).toString
311+
}
312+
}
313+
314+
val contextHandler = new ServletContextHandler
315+
val holder = new ServletHolder(servlet)
316+
contextHandler.setContextPath(uiRoot)
317+
contextHandler.addServlet(holder, "/")
318+
server.attachHandler(contextHandler)
319+
320+
implicit val webDriver: WebDriver = new HtmlUnitDriver(true) {
321+
getWebClient.getOptions.setThrowExceptionOnScriptError(false)
322+
}
323+
324+
try {
325+
val url = s"http://localhost:$port"
326+
327+
go to s"$url$uiRoot"
328+
329+
// expect the ajax call to finish in 5 seconds
330+
implicitlyWait(org.scalatest.time.Span(5, org.scalatest.time.Seconds))
331+
332+
// once this findAll call returns, we know the ajax load of the table completed
333+
findAll(ClassNameQuery("odd"))
334+
335+
val links = findAll(TagNameQuery("a"))
336+
.map(_.attribute("href"))
337+
.filter(_.isDefined)
338+
.map(_.get)
339+
.filter(_.startsWith(url)).toList
340+
341+
// there are atleast some URL links that were generated via javascript,
342+
// and they all contain the spark.ui.proxyBase (uiRoot)
343+
links.length should be > 4
344+
all(links) should startWith(url + uiRoot)
345+
} finally {
346+
contextHandler.stop()
347+
quit()
348+
}
349+
350+
}
351+
278352
test("incomplete apps get refreshed") {
279353

280354
implicit val webDriver: WebDriver = new HtmlUnitDriver

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,12 @@
431431
<artifactId>httpcore</artifactId>
432432
<version>${commons.httpcore.version}</version>
433433
</dependency>
434+
<dependency>
435+
<groupId>org.eclipse.jetty</groupId>
436+
<artifactId>jetty-proxy</artifactId>
437+
<version>${jetty.version}</version>
438+
<scope>test</scope>
439+
</dependency>
434440
<dependency>
435441
<groupId>org.seleniumhq.selenium</groupId>
436442
<artifactId>selenium-java</artifactId>

0 commit comments

Comments
 (0)