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

bteke pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/hadoop.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 57fd77b9be4 YARN-11888. Serve the Capacity Scheduler UI. (#8082)
57fd77b9be4 is described below

commit 57fd77b9be454e2d6f695121c64af2a4f099a8ab
Author: Benjamin Teke <[email protected]>
AuthorDate: Fri Dec 5 09:59:59 2025 +0100

    YARN-11888. Serve the Capacity Scheduler UI. (#8082)
---
 .gitignore                                         |  1 +
 LICENSE.txt                                        |  1 +
 .../main/resources/assemblies/hadoop-yarn-dist.xml |  8 +++++
 .../apache/hadoop/yarn/conf/YarnConfiguration.java | 15 ++++++++
 .../src/main/webapp/react-router.config.ts         |  1 +
 .../src/main/webapp/src/app/entry.client.tsx       |  7 ++++
 .../src/main/webapp/src/app/root.tsx               | 18 ++++++++++
 .../src/main/webapp/vite.config.ts                 |  1 +
 .../org/apache/hadoop/yarn/webapp/WebApps.java     | 20 +++++++----
 .../src/main/resources/yarn-default.xml            | 24 +++++++++++++
 .../server/resourcemanager/ResourceManager.java    | 41 ++++++++++++++++++++--
 hadoop-yarn-project/pom.xml                        |  7 ++++
 12 files changed, 135 insertions(+), 9 deletions(-)

diff --git a/.gitignore b/.gitignore
index 84d9572cbb5..617d5d2bc98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,7 @@ 
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/npm-debug.log
 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/src/main/webapp/testem.log
 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/dist
 hadoop-yarn-project/hadoop-yarn/hadoop-yarn-ui/tmp
+hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/node_modules/
 yarnregistry.pdf
 patchprocess/
 .history/
diff --git a/LICENSE.txt b/LICENSE.txt
index d9689b47d3f..836887e93cd 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -257,6 +257,7 @@ 
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/webapps/st
 
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/webapps/static/jquery
 
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/webapps/static/jt/jquery.jstree.js
 
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/resources/TERMINAL
+hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/node_modules
 
 =======
 For 
hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/utils/cJSON.[ch]:
diff --git 
a/hadoop-assemblies/src/main/resources/assemblies/hadoop-yarn-dist.xml 
b/hadoop-assemblies/src/main/resources/assemblies/hadoop-yarn-dist.xml
index 7fe5b8e683e..8684ba088b8 100644
--- a/hadoop-assemblies/src/main/resources/assemblies/hadoop-yarn-dist.xml
+++ b/hadoop-assemblies/src/main/resources/assemblies/hadoop-yarn-dist.xml
@@ -211,6 +211,13 @@
         <include>**/*</include>
       </includes>
     </fileSet>
+    <fileSet>
+      
<directory>hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/target/hadoop-yarn-capacity-scheduler-ui-${project.version}</directory>
+      
<outputDirectory>/share/hadoop/${hadoop.component}/webapps/scheduler-ui</outputDirectory>
+      <includes>
+        <include>**/*</include>
+      </includes>
+    </fileSet>
     <!-- Copy dependecies from hadoop-yarn-server-timelineservice as well -->
     <fileSet>
       
<directory>hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice-hbase/hadoop-yarn-server-timelineservice-hbase-client/target/lib</directory>
@@ -280,6 +287,7 @@
       <excludes>
         
<exclude>org.apache.hadoop:hadoop-yarn-server-timelineservice*</exclude>
         <exclude>org.apache.hadoop:hadoop-yarn-ui</exclude>
+        <exclude>org.apache.hadoop:hadoop-yarn-capacity-scheduler-ui</exclude>
         <exclude>org.apache.hadoop:hadoop-yarn-csi</exclude>
       </excludes>
       <binaries>
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
index 777617d7e64..bdeabcaa934 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java
@@ -378,6 +378,21 @@ private static void addDeprecatedKeys() {
 
   public static final String YARN_WEBAPP_UI2_WARFILE_PATH = "yarn."
       + "webapp.ui2.war-file-path";
+
+  /**
+   * Enable YARN Capacity Scheduler UI.
+   */
+  public static final String YARN_WEBAPP_SCHEDULER_UI_ENABLE = "yarn."
+      + "webapp.scheduler-ui.enable";
+  public static final boolean DEFAULT_YARN_WEBAPP_SCHEDULER_UI_ENABLE = false;
+
+  public static final String YARN_WEBAPP_SCHEDULER_UI_WARFILE_PATH = "yarn."
+      + "webapp.scheduler-ui.war-file-path";
+
+  public static final String YARN_WEBAPP_SCHEDULER_UI_READ_ONLY_ENABLE = 
"yarn."
+      + "webapp.scheduler-ui.read-only.enable";
+  public static final boolean DEFAULT_YARN_WEBAPP_SCHEDULER_UI_READ_ONLY = 
false;
+
   public static final String YARN_API_SERVICES_ENABLE = "yarn."
       + "webapp.api-service.enable";
   public static final String YARN_WEBAPP_UI1_ENABLE_TOOLS = "yarn."
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/react-router.config.ts
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/react-router.config.ts
index ac097103584..d6eefc38f8a 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/react-router.config.ts
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/react-router.config.ts
@@ -5,4 +5,5 @@ export default {
   // Server-side render by default, to enable SPA mode set this to `false`
   ssr: false,
   appDirectory: "src/app",
+  basename: "/scheduler-ui",
 } satisfies Config;
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/app/entry.client.tsx
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/app/entry.client.tsx
index 94b18d016a9..0640365b6c0 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/app/entry.client.tsx
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/app/entry.client.tsx
@@ -40,6 +40,13 @@ async function enableMocking() {
 }
 
 enableMocking().then(() => {
+  // Handle servlet welcome-file redirect to index.html
+  // React Router doesn't need index.html in the URL, so redirect without it
+  if (window.location.pathname.endsWith('/index.html')) {
+    const newPath = window.location.pathname.replace(/\/index\.html$/, '/');
+    window.history.replaceState(null, '', newPath + window.location.search + 
window.location.hash);
+  }
+
   startTransition(() => {
     hydrateRoot(
       document,
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/app/root.tsx
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/app/root.tsx
index bebaa128a3c..0a84e2ecac7 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/app/root.tsx
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/app/root.tsx
@@ -55,3 +55,21 @@ export default function App() {
     </ValidationProvider>
   );
 }
+
+export function HydrateFallback() {
+  return (
+    <div className="flex h-screen items-center justify-center bg-background">
+      <div className="text-center space-y-4">
+        <div
+          className="inline-block h-8 w-8 animate-spin rounded-full border-4 
border-solid border-primary border-r-transparent align-[-0.125em] 
motion-reduce:animate-[spin_1.5s_linear_infinite]"
+          role="status"
+        >
+          <span className="sr-only">Loading...</span>
+        </div>
+        <p className="text-sm text-muted-foreground">
+          Loading YARN Capacity Scheduler UI...
+        </p>
+      </div>
+    </div>
+  );
+}
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/vite.config.ts
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/vite.config.ts
index 1946f9ffd19..71958e987fe 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/vite.config.ts
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/vite.config.ts
@@ -14,6 +14,7 @@ export default defineConfig(({ mode }) => {
   const clusterProxyTarget = env.VITE_CLUSTER_PROXY_TARGET;
 
   return {
+    base: '/scheduler-ui/',
     plugins: [
       tailwindcss(),
       reactRouter(),
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
index 63e3679a5d1..27600f24211 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/java/org/apache/hadoop/yarn/webapp/WebApps.java
@@ -481,16 +481,22 @@ public WebApp start() {
     }
 
     public WebApp start(WebApp webapp) {
-      return start(webapp, null);
+      return start(webapp, (WebAppContext[]) null);
     }
 
-    public WebApp start(WebApp webapp, WebAppContext ui2Context) {
+    public WebApp start(WebApp webapp, WebAppContext... additionalContexts) {
       WebApp webApp = build(webapp);
       HttpServer2 httpServer = webApp.httpServer();
-      if (ui2Context != null) {
-        addFiltersForNewContext(ui2Context);
-        httpServer.addHandlerAtFront(ui2Context);
+
+      if (additionalContexts != null) {
+        for (WebAppContext context : additionalContexts) {
+          if (context != null) {
+            addFiltersForNewContext(context);
+            httpServer.addHandlerAtFront(context);
+          }
+        }
       }
+
       try {
         httpServer.start();
         LOG.info("Web app {} started at {}.", name, 
httpServer.getConnectorAddress(0).getPort());
@@ -500,7 +506,7 @@ public WebApp start(WebApp webapp, WebAppContext 
ui2Context) {
       return webApp;
     }
 
-    private void addFiltersForNewContext(WebAppContext ui2Context) {
+    private void addFiltersForNewContext(WebAppContext uiContext) {
       Map<String, String> params = getConfigParameters(csrfConfigPrefix);
 
       if (hasCSRFEnabled(params)) {
@@ -508,7 +514,7 @@ private void addFiltersForNewContext(WebAppContext 
ui2Context) {
             + "Please ensure that there is an authentication mechanism "
             + "enabled (kerberos, custom, etc).", name);
         String restCsrfClassName = RestCsrfPreventionFilter.class.getName();
-        HttpServer2.defineFilter(ui2Context, restCsrfClassName,
+        HttpServer2.defineFilter(uiContext, restCsrfClassName,
             restCsrfClassName, params, new String[]{"/*"});
       }
     }
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
index 8c453cbeb89..efef1e30e3d 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/main/resources/yarn-default.xml
@@ -336,6 +336,30 @@
     <value></value>
   </property>
 
+  <property>
+    <description>To enable YARN Capacity Scheduler UI.</description>
+    <name>yarn.webapp.scheduler-ui.enable</name>
+    <value>false</value>
+  </property>
+
+  <property>
+    <description>
+      The WAR file path for the YARN Capacity Scheduler UI.
+      If not specified, the scheduler UI will be loaded from the classpath.
+    </description>
+    <name>yarn.webapp.scheduler-ui.war-file-path</name>
+    <value></value>
+  </property>
+
+  <property>
+    <description>
+      Enable read-only mode for YARN Capacity Scheduler UI.
+      When set to true, the UI will not allow configuration changes.
+    </description>
+    <name>yarn.webapp.scheduler-ui.read-only.enable</name>
+    <value>false</value>
+  </property>
+
   <property>
     <description>
       Enable services rest api on ResourceManager.
diff --git 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
index f9b41047100..5f723aba3ab 100644
--- 
a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
+++ 
b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/main/java/org/apache/hadoop/yarn/server/resourcemanager/ResourceManager.java
@@ -191,6 +191,11 @@ public class ResourceManager extends CompositeService
    */
   public static final String UI2_WEBAPP_NAME = "/ui2";
 
+  /*
+   * Scheduler UI webapp name
+   */
+  public static final String SCHEDULER_UI_WEBAPP_NAME = "/scheduler-ui";
+
   /**
    * "Always On" services. Services that need to run always irrespective of
    * the HA state of the RM.
@@ -1468,7 +1473,7 @@ protected void startWepApp() {
         }
       }
       if (onDiskPath == null || onDiskPath.isEmpty()) {
-          LOG.error("No war file or webapps found for ui2 !");
+        LOG.error("No war file or webapps found for ui2!");
       } else {
         if (onDiskPath.endsWith(".war")) {
           uiWebAppContext.setWar(onDiskPath);
@@ -1480,6 +1485,38 @@ protected void startWepApp() {
       }
     }
 
+    WebAppContext schedulerUiWebAppContext = null;
+    if 
(getConfig().getBoolean(YarnConfiguration.YARN_WEBAPP_SCHEDULER_UI_ENABLE,
+        YarnConfiguration.DEFAULT_YARN_WEBAPP_SCHEDULER_UI_ENABLE)) {
+      String onDiskPath = getConfig()
+          .get(YarnConfiguration.YARN_WEBAPP_SCHEDULER_UI_WARFILE_PATH);
+
+      schedulerUiWebAppContext = new WebAppContext();
+      schedulerUiWebAppContext.setContextPath(SCHEDULER_UI_WEBAPP_NAME);
+
+      if (onDiskPath == null) {
+        String war = "hadoop-yarn-capacity-scheduler-ui-" + 
VersionInfo.getVersion() + ".war";
+        URL url = getClass().getClassLoader().getResource(war);
+
+        if (url == null) {
+          onDiskPath = getWebAppsPath("scheduler-ui");
+        } else {
+          onDiskPath = url.getFile();
+        }
+      }
+      if (onDiskPath == null || onDiskPath.isEmpty()) {
+        LOG.error("No war file or webapps found for scheduler-ui!");
+      } else {
+        if (onDiskPath.endsWith(".war")) {
+          schedulerUiWebAppContext.setWar(onDiskPath);
+          LOG.info("Using war file at: {}.", onDiskPath);
+        } else {
+          schedulerUiWebAppContext.setResourceBase(onDiskPath);
+          LOG.info("Using webapps at: {}.", onDiskPath);
+        }
+      }
+    }
+
     builder.withAttribute(IsResourceManagerActiveServlet.RM_ATTRIBUTE, this);
     builder.withServlet(IsResourceManagerActiveServlet.SERVLET_NAME,
         IsResourceManagerActiveServlet.PATH_SPEC,
@@ -1488,7 +1525,7 @@ protected void startWepApp() {
     try {
       RMWebApp rmWebApp = new RMWebApp(this);
       builder.withResourceConfig(rmWebApp.resourceConfig());
-      webApp = builder.start(rmWebApp, uiWebAppContext);
+      webApp = builder.start(rmWebApp, uiWebAppContext, 
schedulerUiWebAppContext);
     } catch (WebAppException e) {
       webApp = e.getWebApp();
       throw e;
diff --git a/hadoop-yarn-project/pom.xml b/hadoop-yarn-project/pom.xml
index 657b6ba79c2..437339185f8 100644
--- a/hadoop-yarn-project/pom.xml
+++ b/hadoop-yarn-project/pom.xml
@@ -155,6 +155,13 @@
       <type>${yarn.ui.packaging}</type>
       <scope>provided</scope>
     </dependency>
+    <dependency>
+      <groupId>org.apache.hadoop</groupId>
+      <artifactId>hadoop-yarn-capacity-scheduler-ui</artifactId>
+      <version>${project.version}</version>
+      <type>${yarn.ui.packaging}</type>
+      <scope>provided</scope>
+    </dependency>
     <dependency>
       <groupId>org.apache.hadoop</groupId>
       
<artifactId>hadoop-yarn-server-timelineservice-hbase-server-2</artifactId>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to