This is an automated email from the ASF dual-hosted git repository.
sandeepk318 pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git
The following commit(s) were added to refs/heads/trunk by this push:
new 94c6389a96 AMBARI-26647: Fix Capacity Scheduler config editing format
and add Refresh YARN Queues prompt (#4204)
94c6389a96 is described below
commit 94c6389a96b38bccef0b6a08269481a086b63ca1
Author: Himanshu Maurya <[email protected]>
AuthorDate: Fri Sep 4 20:13:25 2026 +0530
AMBARI-26647: Fix Capacity Scheduler config editing format and add Refresh
YARN Queues prompt (#4204)
---
ambari-web/latest/src/hooks/useConfigSaver.tsx | 103 +++++++++++++++-
.../src/screens/CommonConfigs/AdvancedConfigs.tsx | 135 ++++++++++++++++++++-
2 files changed, 233 insertions(+), 5 deletions(-)
diff --git a/ambari-web/latest/src/hooks/useConfigSaver.tsx
b/ambari-web/latest/src/hooks/useConfigSaver.tsx
index 574346b545..90ade6a94f 100644
--- a/ambari-web/latest/src/hooks/useConfigSaver.tsx
+++ b/ambari-web/latest/src/hooks/useConfigSaver.tsx
@@ -16,7 +16,7 @@
* limitations under the License.
*/
-import { useState, useContext } from "react";
+import { useState, useContext, useRef } from "react";
import { isEmpty, isArray, get } from "lodash";
import {
ConfigPropertiesType,
@@ -29,6 +29,8 @@ import { trimProperty } from
"../screens/CommonConfigs/ConfigUtils";
import { messages } from "../screens/messages";
import ConfirmationModal from "../components/ConfirmationModal";
import modalManager from "../store/ModalManager";
+import { HostsApi } from "../api/hostsApi";
+import { ActionsApi } from "../api/actionsApi";
interface AttributesType {
final: { [key: string]: string };
@@ -52,6 +54,9 @@ export const useConfigSaver = (
onSaveComplete?: () => void
) => {
const [saveInProgress, setSaveInProgress] = useState(false);
+ // Ember.js ComponentActionsByConfigs#showPopup: capacity-scheduler.xml
+ // changes should prompt a "Refresh YARN Queues" confirmation after save.
+ const yarnQueueRefreshNeededRef = useRef(false);
const heapsizeException = [
"hadoop_heapsize",
@@ -139,6 +144,12 @@ export const useConfigSaver = (
const saveConfigsForDefaultGroup = async () => {
let data: any = [];
+ yarnQueueRefreshNeededRef.current = Object.keys(configProperties).some(
+ (svcName) =>
+ getModifiedConfigs(configProperties, svcName).some(
+ (config) => config.fileName === "capacity-scheduler.xml"
+ )
+ );
Object.keys(configProperties).map((serviceName: string) => {
var serviceConfigs = getServiceConfigToSave(
serviceName,
@@ -671,7 +682,7 @@ export const useConfigSaver = (
_value: string | undefined,
_status: string,
_urlParams: string,
- _doConfigActions: boolean | undefined
+ doConfigActions: boolean | undefined
) => {
modalManager.show(
<ConfirmationModal
@@ -679,7 +690,15 @@ export const useConfigSaver = (
onClose={() => modalManager.hide()}
modalTitle={header}
modalBody={<div className={messageClass}>{message}</div>}
- successCallback={() => modalManager.hide()}
+ successCallback={() => {
+ modalManager.hide();
+ // Ember.js ComponentActionsByConfigs#showPopup: prompt to refresh
+ // YARN queues after a successful capacity-scheduler.xml change.
+ if (isSuccess && doConfigActions &&
yarnQueueRefreshNeededRef.current) {
+ yarnQueueRefreshNeededRef.current = false;
+ showRefreshYarnQueuesPopup();
+ }
+ }}
buttonVariant={isSuccess ? "success" : "danger"}
cancellable={false}
okButtonText="OK"
@@ -687,6 +706,84 @@ export const useConfigSaver = (
);
};
+ const showRefreshYarnQueuesPopup = () => {
+ modalManager.show(
+ <ConfirmationModal
+ isOpen={true}
+ onClose={() => modalManager.hide()}
+ modalTitle={get(messages, "popup.confirmation.commonHeader",
"Confirmation")}
+ modalBody={
+ <div
+ dangerouslySetInnerHTML={{
+ __html: get(messages,
"popup.confirmation.refreshYarnQueues.body", ""),
+ }}
+ />
+ }
+ successCallback={() => {
+ modalManager.hide();
+ refreshYarnQueues();
+ }}
+ cancellable={true}
+ okButtonText={get(
+ messages,
+ "popup.confirmation.refreshYarnQueues.buttonText",
+ "Refresh Yarn Queues"
+ )}
+ />
+ );
+ };
+
+ const refreshYarnQueues = async () => {
+ try {
+ const fields =
+ "fields=Hosts/host_name,host_components/HostRoles/component_name";
+ const hostDetailsResponse = await HostsApi.getHostComponentsDetails(
+ clusterName,
+ fields
+ );
+ type HostComponentDetails = {
+ Hosts: { host_name: string };
+ host_components?: { HostRoles: { component_name: string } }[];
+ };
+ const hostNames = (
+ (hostDetailsResponse?.items || []) as HostComponentDetails[]
+ ).flatMap((host) => {
+ const matches = (host.host_components || []).filter(
+ (component) =>
+ component.HostRoles.component_name === "RESOURCEMANAGER"
+ );
+ return matches.map(() => host.Hosts.host_name);
+ });
+
+ if (!hostNames.length) {
+ return;
+ }
+
+ const payloadData = {
+ RequestInfo: {
+ command: "REFRESHQUEUES",
+ context: get(
+ messages,
+ "services.service.actions.run.yarnRefreshQueues.context",
+ "Refresh YARN Capacity Scheduler"
+ ),
+ "parameters/forceRefreshConfigTags": "capacity-scheduler",
+ },
+ "Requests/resource_filters": [
+ {
+ service_name: "YARN",
+ component_name: "RESOURCEMANAGER",
+ hosts: hostNames.join(","),
+ },
+ ],
+ };
+
+ await ActionsApi.submitActionRequest(clusterName, payloadData);
+ } catch (error) {
+ console.error("Error refreshing YARN queues:", error);
+ }
+ };
+
// const showSavePopup = () => {
// // To be implemented
// };
diff --git a/ambari-web/latest/src/screens/CommonConfigs/AdvancedConfigs.tsx
b/ambari-web/latest/src/screens/CommonConfigs/AdvancedConfigs.tsx
index da30e5c146..ae7d98033d 100644
--- a/ambari-web/latest/src/screens/CommonConfigs/AdvancedConfigs.tsx
+++ b/ambari-web/latest/src/screens/CommonConfigs/AdvancedConfigs.tsx
@@ -295,6 +295,77 @@ function AdvancedConfigs({
);
};
+ // capacity-scheduler.xml properties are edited as one combined "key=value"
+ // per-line textarea (matching the old Ember UI's App.CapacitySceduler
widget)
+ // instead of one field per property. The underlying properties map is still
+ // individual key/value entries - only the presentation is combined here.
+ const CAPACITY_SCHEDULER_SECTION = "capacity-scheduler";
+ // Matches ui/app/models/stack_service.js:381 - the category that shows as
"Scheduler".
+ const CAPACITY_SCHEDULER_CATEGORY = "CapacityScheduler";
+
+ const getCapacitySchedulerTextValue = (properties: {
+ [key: string]: PropertyType;
+ }) => {
+ return Object.keys(properties)
+ .sort()
+ .map((name) => `${name}=${properties[name]?.value ?? ""}`)
+ .join("\n");
+ };
+
+ const handleCapacitySchedulerChange = (section: string, rawValue: string) =>
{
+ let advancedDataCopy = cloneDeep(advancedConfigs);
+ const properties = advancedDataCopy[chosenService][section].properties;
+ const templateProperty = Object.values(properties)[0] as
+ | PropertyType
+ | undefined;
+
+ const seenNames = new Set<string>();
+ rawValue.split("\n").forEach((line) => {
+ if (!line.trim()) {
+ return;
+ }
+ const separatorIndex = line.indexOf("=");
+ const name =
+ separatorIndex === -1 ? line.trim() : line.slice(0,
separatorIndex).trim();
+ if (!name) {
+ return;
+ }
+ const value = separatorIndex === -1 ? "" : line.slice(separatorIndex +
1);
+ seenNames.add(name);
+
+ if (properties[name]) {
+ properties[name].value = value;
+ } else if (templateProperty) {
+ properties[name] = {
+ ...cloneDeep(templateProperty),
+ propertyName: name,
+ propertyDisplayname: name,
+ value,
+ previousValue: value,
+ overrideValues: [],
+ };
+ }
+ });
+
+ Object.keys(properties).forEach((name) => {
+ if (!seenNames.has(name)) {
+ delete properties[name];
+ }
+ });
+
+ advancedDataCopy = updateVisibilityByForeignKeys(advancedDataCopy);
+ advancedDataCopy = validateAllProperties(advancedDataCopy);
+
+ commitAdvancedConfigs(advancedDataCopy);
+
+ const changedProperty = Object.values(properties)[0] as
+ | PropertyType
+ | undefined;
+ if (changedProperty) {
+ onValueUpdate(changedProperty, advancedDataCopy);
+ }
+ };
+
const handleChangeForOverridenValues = (
section: string,
property: string,
@@ -773,8 +844,27 @@ function AdvancedConfigs({
return null;
}
+ // capacity-scheduler.xml properties live in their own catch-all
+ // "capacity-scheduler" bucket, but Ember shows them inside the
+ // "CapacityScheduler" category (displayName "Scheduler") next to
+ // yarn.resourcemanager.scheduler.class - see
+ // ui/app/models/stack_service.js:381. Fold that bucket's
combined
+ // textarea into the CapacityScheduler section below instead of
+ // rendering it as its own accordion item.
+ if (config === CAPACITY_SCHEDULER_SECTION) {
+ return null;
+ }
+ const capacitySchedulerBucket =
+ config === CAPACITY_SCHEDULER_CATEGORY
+ ? advancedConfigs[chosenService][CAPACITY_SCHEDULER_SECTION]
+ : undefined;
+ const capacitySchedulerProperties =
+ capacitySchedulerBucket?.properties || {};
+ const hasCapacitySchedulerProperties =
+ Object.keys(capacitySchedulerProperties).length > 0;
+
// Check if section has no visible properties
- const hasNoVisibleProperties =
isEmpty(currentConfigValue.properties) ||
+ const hasNoVisibleProperties =
isEmpty(currentConfigValue.properties) ||
filteredPropertiesCount === 0;
// For custom sections with search active, hide if no properties
match
@@ -784,7 +874,13 @@ function AdvancedConfigs({
// For NON-custom sections, skip if no visible properties
// Custom sections should always be shown so users can add
properties via "Add Property..."
- if (hasNoVisibleProperties && !config.includes("Custom")) {
+ // Keep the CapacityScheduler section visible when it has
nothing of its
+ // own but the folded-in capacity-scheduler.xml bucket does.
+ if (
+ hasNoVisibleProperties &&
+ !config.includes("Custom") &&
+ !hasCapacitySchedulerProperties
+ ) {
return null;
}
@@ -794,6 +890,8 @@ function AdvancedConfigs({
<div className="d-flex align-items-center fs-18">
{advancedConfigs[chosenService][config].displayName
? advancedConfigs[chosenService][config].displayName
+ : config === CAPACITY_SCHEDULER_CATEGORY
+ ? "Scheduler"
: config}{" "}
{errorCount > 0 && (
<span className="ms-2 badge rounded-pill bg-danger">
@@ -1243,6 +1341,39 @@ function AdvancedConfigs({
return null;
}
)}
+ {config === CAPACITY_SCHEDULER_CATEGORY &&
+ hasCapacitySchedulerProperties && (
+ <Row className="mt-2 align-items-center">
+ <Col md={4}>
+ <Form.Label className="p-2">
+ Capacity Scheduler
+ </Form.Label>
+ </Col>
+ <Col>
+ <Form.Control
+ as="textarea"
+ rows={16}
+ value={getCapacitySchedulerTextValue(
+ capacitySchedulerProperties
+ )}
+ onChange={(e) =>
+ handleCapacitySchedulerChange(
+ CAPACITY_SCHEDULER_SECTION,
+ e.target.value
+ )
+ }
+ disabled={
+ !(
+ canEditProperties &&
+
(Object.values(capacitySchedulerProperties)[0] as
+ | PropertyType
+ | undefined)?.isEditable !== false
+ )
+ }
+ />
+ </Col>
+ </Row>
+ )}
{!hostConfigs && canEditProperties &&
config.includes("Custom") ? (
<h4
className="text-info ms-2 mt-2"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]