|
17 | 17 |
|
18 | 18 | package org.apache.spark.deploy.master.ui |
19 | 19 |
|
| 20 | +import java.net.URL |
| 21 | +import java.util.regex.Pattern |
| 22 | +import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse} |
| 23 | + |
| 24 | +import org.eclipse.jetty.servlet.ServletContextHandler |
| 25 | + |
20 | 26 | import org.apache.spark.Logging |
21 | 27 | import org.apache.spark.deploy.master.Master |
| 28 | +import org.apache.spark.status.api.v1.{ApplicationsListResource, ApplicationInfo} |
22 | 29 | import org.apache.spark.ui.{SparkUI, WebUI} |
23 | 30 | import org.apache.spark.ui.JettyUtils._ |
24 | 31 |
|
@@ -50,6 +57,66 @@ class MasterWebUI( |
50 | 57 | "/app/kill", "/", masterPage.handleAppKillRequest, httpMethods = Set("POST"))) |
51 | 58 | attachHandler(createRedirectHandler( |
52 | 59 | "/driver/kill", "/", masterPage.handleDriverKillRequest, httpMethods = Set("POST"))) |
| 60 | + attachHandler(createApiRootHandler()) |
| 61 | + } |
| 62 | + |
| 63 | + def createApiRootHandler(): ServletContextHandler = { |
| 64 | + |
| 65 | + val servlet = new HttpServlet { |
| 66 | + private lazy val appIdPattern = Pattern.compile("\\/api\\/v\\d+\\/applications\\/([^\\/]+).*") |
| 67 | + |
| 68 | + override def doGet(request: HttpServletRequest, response: HttpServletResponse): Unit = { |
| 69 | + doRequest(request, response) |
| 70 | + } |
| 71 | + override def doPost(request: HttpServletRequest, response: HttpServletResponse): Unit = { |
| 72 | + doRequest(request, response) |
| 73 | + } |
| 74 | + private def doRequest(request: HttpServletRequest, response: HttpServletResponse): Unit = { |
| 75 | + val requestURI = request.getRequestURI |
| 76 | + |
| 77 | + // requesting an application info list |
| 78 | + if (requestURI == "applications") { |
| 79 | + // TODO - Should send ApplicationList response ??? |
| 80 | + } else { |
| 81 | + // forward request to app if it is active, otherwise send error |
| 82 | + getAppId(request) match { |
| 83 | + case Some(appId) => |
| 84 | + val state = masterPage.getMasterState |
| 85 | + state.activeApps.find(appInfo => appInfo.id == appId) match { |
| 86 | + case Some(appInfo) => |
| 87 | + val prefixedDestPath = appInfo.desc.appUiUrl + requestURI |
| 88 | + val newUrl = new URL(new URL(request.getRequestURL.toString), prefixedDestPath).toString |
| 89 | + response.sendRedirect(newUrl) |
| 90 | + request.getPathInfo |
| 91 | + case None => |
| 92 | + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE) |
| 93 | + } |
| 94 | + case None => |
| 95 | + response.sendError(HttpServletResponse.SC_BAD_REQUEST) |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | + |
| 100 | + private def getAppId(request: HttpServletRequest): Option[String] = { |
| 101 | + val m = appIdPattern.matcher(request.getRequestURI) |
| 102 | + if (m.matches) Some(m.group(1)) else None |
| 103 | + } |
| 104 | + |
| 105 | + // SPARK-5983 ensure TRACE is not supported |
| 106 | + protected override def doTrace(req: HttpServletRequest, res: HttpServletResponse): Unit = { |
| 107 | + res.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED) |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + createServletHandler("/api", servlet, "") |
| 112 | + } |
| 113 | + |
| 114 | + def getApplicationInfoList: Iterator[ApplicationInfo] = { |
| 115 | + val state = masterPage.getMasterState |
| 116 | + val activeApps = state.activeApps.sortBy(_.startTime).reverse |
| 117 | + val completedApps = state.completedApps.sortBy(_.endTime).reverse |
| 118 | + activeApps.iterator.map { ApplicationsListResource.convertApplicationInfo(_, false) } ++ |
| 119 | + completedApps.iterator.map { ApplicationsListResource.convertApplicationInfo(_, true) } |
53 | 120 | } |
54 | 121 | } |
55 | 122 |
|
|
0 commit comments