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]