This is an automated email from the ASF dual-hosted git repository.

yao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new e9339e60dd41 [SPARK-45774][CORE][UI] Support 
`spark.master.ui.historyServerUrl` in `ApplicationPage`
e9339e60dd41 is described below

commit e9339e60dd418559619e4beae3de0a58ca3b2a31
Author: Dongjoon Hyun <dh...@apple.com>
AuthorDate: Fri Nov 3 23:17:06 2023 +0800

    [SPARK-45774][CORE][UI] Support `spark.master.ui.historyServerUrl` in 
`ApplicationPage`
    
    ### What changes were proposed in this pull request?
    
    This PR aims to support a new configuration 
`spark.master.ui.historyServerUrl` to show a new link, **Application History 
UI**, from `Application Page` to `Spark History Server` in `Spark Standalone 
Cluster`s.
    
    This is useful when `spark.eventLog.enabled=true` and `Spark History 
Server` exists. A user can launch `Spark Master` with 
`spark.master.ui.historyServerUrl` configuration to link to it.
    
    Please note that `Spark History Server` is an orthogonal service from not 
only `Spark Master` but also `Spark Applications`. For example, `Spark 
Application`s know only `spark.eventLog.dir`, not SHS info. Moreover, you can 
launch multiple SHS based on the same event location with load balancer. 
Lastly, SHS can be behind proxy layer too. So, only the users know the exposed 
SHS URL.
    
    ### Why are the changes needed?
    
    **Application Detail UI**
    
    Apache Spark `Master` currently shows `Application Detail UI` link for only 
live Spark jobs.
    
    <img width="436" alt="Screenshot 2023-11-02 at 8 30 20 PM" 
src="https://github.com/apache/spark/assets/9700541/28e6398c-3bb5-4f8c-9713-3a95c451759e";>
    
    **Application History UI**
    This PR adds `Application History UI` link for completed jobs as the best 
effort when `spark.ui.historyServerUrl` is given.
    
    <img width="513" alt="Screenshot 2023-11-02 at 8 38 37 PM" 
src="https://github.com/apache/spark/assets/9700541/ca8b5436-f726-4b79-97c3-416cc59ea01b";>
    
    ### Does this PR introduce _any_ user-facing change?
    
    No. This is disabled by default.
    
    ### How was this patch tested?
    
    Pass the CIs with the newly added test suite.
    
    Also, manual procedure
    ```
    $ SPARK_HISTORY_OPTS="-Dspark.history.fs.logDirectory=$HOME/data/history" 
sbin/start-history-server.sh
    
    $ 
SPARK_MASTER_OPTS=-Dspark.master.ui.historyServerUrl=http://localhost:18080 
sbin/start-master.sh
    
    $ sbin/start-worker.sh spark://localhost:7077
    
    $ bin/spark-shell --master spark://127.0.0.1:7077 -c 
spark.eventLog.enabled=true -c spark.eventLog.dir=/Users/dongjoon/data/history
    ```
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    No.
    
    Closes #43643 from dongjoon-hyun/SPARK-45774.
    
    Authored-by: Dongjoon Hyun <dh...@apple.com>
    Signed-off-by: Kent Yao <y...@apache.org>
---
 .../org/apache/spark/deploy/master/Master.scala    |  1 +
 .../spark/deploy/master/ui/ApplicationPage.scala   |  5 ++
 .../org/apache/spark/internal/config/package.scala |  8 +++
 .../deploy/master/ui/ApplicationPageSuite.scala    | 73 ++++++++++++++++++++++
 4 files changed, 87 insertions(+)

diff --git a/core/src/main/scala/org/apache/spark/deploy/master/Master.scala 
b/core/src/main/scala/org/apache/spark/deploy/master/Master.scala
index 058b944c591a..d5de1366ac05 100644
--- a/core/src/main/scala/org/apache/spark/deploy/master/Master.scala
+++ b/core/src/main/scala/org/apache/spark/deploy/master/Master.scala
@@ -121,6 +121,7 @@ private[deploy] class Master(
   if (defaultCores < 1) {
     throw new SparkException(s"${DEFAULT_CORES.key} must be positive")
   }
+  val historyServerUrl = conf.get(MASTER_UI_HISTORY_SERVER_URL)
 
   // Alternative application submission gateway that is stable across Spark 
versions
   private val restServerEnabled = conf.get(MASTER_REST_SERVER_ENABLED)
diff --git 
a/core/src/main/scala/org/apache/spark/deploy/master/ui/ApplicationPage.scala 
b/core/src/main/scala/org/apache/spark/deploy/master/ui/ApplicationPage.scala
index 202926233d97..3087d5e8c966 100644
--- 
a/core/src/main/scala/org/apache/spark/deploy/master/ui/ApplicationPage.scala
+++ 
b/core/src/main/scala/org/apache/spark/deploy/master/ui/ApplicationPage.scala
@@ -98,6 +98,11 @@ private[ui] class ApplicationPage(parent: MasterWebUI) 
extends WebUIPage("app")
                     <a href={UIUtils.makeHref(parent.master.reverseProxy,
                       app.id, app.desc.appUiUrl)}>Application Detail UI</a>
                 </strong></li>
+              } else if (parent.master.historyServerUrl.nonEmpty) {
+                <li><strong>
+                    <a 
href={s"${parent.master.historyServerUrl.get}/history/${app.id}"}>
+                      Application History UI</a>
+                </strong></li>
               }
             }
           </ul>
diff --git a/core/src/main/scala/org/apache/spark/internal/config/package.scala 
b/core/src/main/scala/org/apache/spark/internal/config/package.scala
index 143dd0c44ce8..93a42eec8326 100644
--- a/core/src/main/scala/org/apache/spark/internal/config/package.scala
+++ b/core/src/main/scala/org/apache/spark/internal/config/package.scala
@@ -1837,6 +1837,14 @@ package object config {
     .intConf
     .createWithDefault(8080)
 
+  private[spark] val MASTER_UI_HISTORY_SERVER_URL =
+    ConfigBuilder("spark.master.ui.historyServerUrl")
+      .doc("The URL where Spark history server is running. Please note that 
this assumes " +
+        "that all Spark jobs share the same event log location where the 
history server accesses.")
+      .version("4.0.0")
+      .stringConf
+      .createOptional
+
   private[spark] val IO_COMPRESSION_SNAPPY_BLOCKSIZE =
     ConfigBuilder("spark.io.compression.snappy.blockSize")
       .doc("Block size in bytes used in Snappy compression, in the case when " 
+
diff --git 
a/core/src/test/scala/org/apache/spark/deploy/master/ui/ApplicationPageSuite.scala
 
b/core/src/test/scala/org/apache/spark/deploy/master/ui/ApplicationPageSuite.scala
new file mode 100644
index 000000000000..9890ac24e168
--- /dev/null
+++ 
b/core/src/test/scala/org/apache/spark/deploy/master/ui/ApplicationPageSuite.scala
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+package org.apache.spark.deploy.master.ui
+
+import java.util.Date
+import javax.servlet.http.HttpServletRequest
+
+import org.mockito.Mockito.{mock, when}
+
+import org.apache.spark.SparkFunSuite
+import org.apache.spark.deploy.ApplicationDescription
+import org.apache.spark.deploy.DeployMessages.{MasterStateResponse, 
RequestMasterState}
+import org.apache.spark.deploy.master.{ApplicationInfo, ApplicationState, 
Master}
+import org.apache.spark.resource.ResourceProfile
+import org.apache.spark.rpc.RpcEndpointRef
+
+class ApplicationPageSuite extends SparkFunSuite {
+
+  private val master = mock(classOf[Master])
+  
when(master.historyServerUrl).thenReturn(Some("http://my-history-server:18080";))
+
+  private val rp = new ResourceProfile(Map.empty, Map.empty)
+  private val desc = ApplicationDescription("name", Some(4), null, "appUiUrl", 
rp)
+  private val appFinished = new ApplicationInfo(0, "app-finished", desc, new 
Date, null, 1)
+  appFinished.markFinished(ApplicationState.FINISHED)
+  private val appLive = new ApplicationInfo(0, "app-live", desc, new Date, 
null, 1)
+
+  private val state = mock(classOf[MasterStateResponse])
+  when(state.completedApps).thenReturn(Array(appFinished))
+  when(state.activeApps).thenReturn(Array(appLive))
+
+  private val rpc = mock(classOf[RpcEndpointRef])
+  when(rpc.askSync[MasterStateResponse](RequestMasterState)).thenReturn(state)
+
+  private val masterWebUI = mock(classOf[MasterWebUI])
+  when(masterWebUI.master).thenReturn(master)
+  when(masterWebUI.masterEndpointRef).thenReturn(rpc)
+
+  test("SPARK-45774: Application Detail UI") {
+    val request = mock(classOf[HttpServletRequest])
+    when(request.getParameter("appId")).thenReturn("app-live")
+
+    val result = new ApplicationPage(masterWebUI).render(request).toString()
+    assert(result.contains("Application Detail UI"))
+    assert(!result.contains("Application History UI"))
+    assert(!result.contains(master.historyServerUrl.get))
+  }
+
+  test("SPARK-45774: Application History UI") {
+    val request = mock(classOf[HttpServletRequest])
+    when(request.getParameter("appId")).thenReturn("app-finished")
+
+    val result = new ApplicationPage(masterWebUI).render(request).toString()
+    assert(!result.contains("Application Detail UI"))
+    assert(result.contains("Application History UI"))
+    assert(result.contains(master.historyServerUrl.get))
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@spark.apache.org
For additional commands, e-mail: commits-h...@spark.apache.org

Reply via email to