You are viewing a plain text version of this content. The canonical link for it is here.
Posted to common-commits@hadoop.apache.org by sl...@apache.org on 2026/08/30 10:06:47 UTC
(hadoop) branch trunk updated: YARN-11985: Placement rule does not allow to select Leaf Queue as Parent although Dynamic Queue Creation is turned on (#8686) Contributed by Susheel Gupta.
This is an automated email from the ASF dual-hosted git repository.
slfan1989 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 3a2c2c8c665 YARN-11985: Placement rule does not allow to select Leaf Queue as Parent although Dynamic Queue Creation is turned on (#8686) Contributed by Susheel Gupta.
3a2c2c8c665 is described below
commit 3a2c2c8c6653a58cf8d0d6f6034485577bd2e6ce
Author: Susheel Gupta <38...@users.noreply.github.com>
AuthorDate: Sun Aug 30 15:36:41 2026 +0530
YARN-11985: Placement rule does not allow to select Leaf Queue as Parent although Dynamic Queue Creation is turned on (#8686) Contributed by Susheel Gupta.
* YARN-11985. Allow dynamically creatable queues as placement rule parents
Include queues with legacy or flexible dynamic queue creation enabled in the placement rule parent queue options, even when they have no static children.
Honor staged auto-creation settings over the live scheduler state and add tests covering live and staged enable/disable scenarios.
Co-authored-by: Shilun Fan <sl...@apache.org>
Signed-off-by: Shilun Fan <sl...@apache.org>
---
.../components/PlacementRuleForm.tsx | 3 +-
.../placement-rules/utils/queueOptions.test.ts | 159 +++++++++++++++++++++
.../features/placement-rules/utils/queueOptions.ts | 59 +++++++-
3 files changed, 215 insertions(+), 6 deletions(-)
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/components/PlacementRuleForm.tsx b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/components/PlacementRuleForm.tsx
index b55d082c79c..e9fd093f09a 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/components/PlacementRuleForm.tsx
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/components/PlacementRuleForm.tsx
@@ -77,7 +77,8 @@ interface PlacementRuleFormProps {
export function PlacementRuleForm({ rule, ruleIndex, onSubmit, onCancel }: PlacementRuleFormProps) {
const schedulerData = useSchedulerStore((state) => state.schedulerData);
- const parentQueues = getAllParentQueues(schedulerData);
+ const getQueuePropertyValue = useSchedulerStore((state) => state.getQueuePropertyValue);
+ const parentQueues = getAllParentQueues(schedulerData, getQueuePropertyValue);
const allQueues = getAllQueues(schedulerData);
const form = useForm<PlacementRuleFormData>({
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/utils/queueOptions.test.ts b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/utils/queueOptions.test.ts
new file mode 100644
index 00000000000..5c97803e7c8
--- /dev/null
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/utils/queueOptions.test.ts
@@ -0,0 +1,159 @@
+/**
+ * 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.
+ */
+
+
+import { describe, it, expect } from 'vitest';
+import { getAllParentQueues, type QueuePropertyAccessor } from './queueOptions';
+import { AUTO_CREATION_PROPS } from '~/types/constants/auto-creation';
+import type { QueueInfo, SchedulerInfo } from '~/types';
+
+function makeQueue(overrides: Partial<QueueInfo> & { queuePath: string }): QueueInfo {
+ return {
+ queueType: 'leaf',
+ queueName: overrides.queuePath.split('.').pop() ?? overrides.queuePath,
+ capacity: 0,
+ usedCapacity: 0,
+ maxCapacity: 100,
+ absoluteCapacity: 0,
+ absoluteMaxCapacity: 100,
+ absoluteUsedCapacity: 0,
+ numApplications: 0,
+ numActiveApplications: 0,
+ numPendingApplications: 0,
+ state: 'RUNNING',
+ ...overrides,
+ } as QueueInfo;
+}
+
+function makeScheduler(children: QueueInfo[]): SchedulerInfo {
+ return {
+ queueName: 'root',
+ queues: { queue: children },
+ } as unknown as SchedulerInfo;
+}
+
+/**
+ * Build a QueuePropertyAccessor from a map of `${queuePath}::${property}` to
+ * `{ value, isStaged }`. Anything not present resolves to an unset, non-staged
+ * value, mirroring the store's getQueuePropertyValue.
+ */
+function makeAccessor(
+ entries: Record<string, { value: string; isStaged: boolean }>,
+): QueuePropertyAccessor {
+ return (queuePath, property) =>
+ entries[`${queuePath}::${property}`] ?? { value: '', isStaged: false };
+}
+
+describe('getAllParentQueues', () => {
+ it('includes queues that already have static children', () => {
+ const scheduler = makeScheduler([
+ makeQueue({
+ queuePath: 'root.withKids',
+ queueType: 'parent',
+ queues: { queue: [makeQueue({ queuePath: 'root.withKids.child' })] },
+ }),
+ makeQueue({ queuePath: 'root.withKids.child' }),
+ ]);
+
+ const values = getAllParentQueues(scheduler).map((o) => o.value);
+ expect(values).toContain('root');
+ expect(values).toContain('root.withKids');
+ expect(values).not.toContain('root.withKids.child');
+ });
+
+ it('excludes a plain leaf queue with no children', () => {
+ const scheduler = makeScheduler([makeQueue({ queuePath: 'root.sibling' })]);
+ const values = getAllParentQueues(scheduler).map((o) => o.value);
+ expect(values).not.toContain('root.sibling');
+ });
+
+ it('includes a leaf queue whose live autoCreationEligibility is flexible', () => {
+ const scheduler = makeScheduler([
+ makeQueue({
+ queuePath: 'root.sibling',
+ autoCreationEligibility: AUTO_CREATION_PROPS.ELIGIBILITY_FLEXIBLE,
+ }),
+ ]);
+ const values = getAllParentQueues(scheduler).map((o) => o.value);
+ expect(values).toContain('root.sibling');
+ });
+
+ it('includes a leaf queue whose live autoCreationEligibility is legacy', () => {
+ const scheduler = makeScheduler([
+ makeQueue({
+ queuePath: 'root.sibling',
+ autoCreationEligibility: AUTO_CREATION_PROPS.ELIGIBILITY_LEGACY,
+ }),
+ ]);
+ const values = getAllParentQueues(scheduler).map((o) => o.value);
+ expect(values).toContain('root.sibling');
+ });
+
+ it('includes a leaf queue whose flexible Dynamic Queue Creation is staged on but not applied', () => {
+ const scheduler = makeScheduler([makeQueue({ queuePath: 'root.sibling' })]);
+ const accessor = makeAccessor({
+ [`root.sibling::${AUTO_CREATION_PROPS.FLEXIBLE_ENABLED}`]: { value: 'true', isStaged: true },
+ });
+
+ const values = getAllParentQueues(scheduler, accessor).map((o) => o.value);
+ expect(values).toContain('root.sibling');
+ });
+
+ it('includes a leaf queue whose legacy Dynamic Queue Creation is staged on but not applied', () => {
+ const scheduler = makeScheduler([makeQueue({ queuePath: 'root.sibling' })]);
+ const accessor = makeAccessor({
+ [`root.sibling::${AUTO_CREATION_PROPS.LEGACY_ENABLED}`]: { value: 'true', isStaged: true },
+ });
+
+ const values = getAllParentQueues(scheduler, accessor).map((o) => o.value);
+ expect(values).toContain('root.sibling');
+ });
+
+ it('excludes a queue whose staged disable overrides live flexible eligibility', () => {
+ // Live scheduler still reports the queue as flexible, but the operator has
+ // staged both auto-creation properties to false. The staged view must win.
+ const scheduler = makeScheduler([
+ makeQueue({
+ queuePath: 'root.sibling',
+ autoCreationEligibility: AUTO_CREATION_PROPS.ELIGIBILITY_FLEXIBLE,
+ }),
+ ]);
+ const accessor = makeAccessor({
+ [`root.sibling::${AUTO_CREATION_PROPS.FLEXIBLE_ENABLED}`]: { value: 'false', isStaged: true },
+ [`root.sibling::${AUTO_CREATION_PROPS.LEGACY_ENABLED}`]: { value: 'false', isStaged: true },
+ });
+
+ const values = getAllParentQueues(scheduler, accessor).map((o) => o.value);
+ expect(values).not.toContain('root.sibling');
+ });
+
+ it('falls back to live eligibility when neither property has a staged override', () => {
+ // Accessor returns non staged values only, so the live flexible eligibility
+ // remains the source of truth and the queue stays selectable.
+ const scheduler = makeScheduler([
+ makeQueue({
+ queuePath: 'root.sibling',
+ autoCreationEligibility: AUTO_CREATION_PROPS.ELIGIBILITY_FLEXIBLE,
+ }),
+ ]);
+ const accessor = makeAccessor({});
+
+ const values = getAllParentQueues(scheduler, accessor).map((o) => o.value);
+ expect(values).toContain('root.sibling');
+ });
+});
diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/utils/queueOptions.ts b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/utils/queueOptions.ts
index 1103b4c7935..3a7ff4a487d 100644
--- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/utils/queueOptions.ts
+++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-capacity-scheduler-ui/src/main/webapp/src/features/placement-rules/utils/queueOptions.ts
@@ -18,6 +18,7 @@
import type { QueueInfo, SchedulerInfo } from '~/types';
+import { AUTO_CREATION_PROPS } from '~/types/constants/auto-creation';
import { mapQueueTree } from '~/utils/treeUtils';
export interface QueueOption {
@@ -26,13 +27,53 @@ export interface QueueOption {
}
/**
- * Check if a queue has children (is a parent queue)
+ * Accessor for a queue's effective property value. Matches the store's getQueuePropertyValue.
*/
-function isParentQueue(queue: QueueInfo): boolean {
- return !!(
+export type QueuePropertyAccessor = (
+ queuePath: string,
+ property: string,
+) => { value: string; isStaged: boolean };
+
+/**
+ * Check whether Dynamic Queue Creation is enabled for a queue. A queue with
+ * auto queue creation enabled acts as a parent even though it currently has no
+ * static child queues, so it must be selectable as a parent queue in placement rules.
+ *
+ * Staged, not yet applied changes take precedence, if the operator has staged an
+ * override for either auto creation property, that staged view is the source of
+ * truth. Only when neither property has a staged override do we fall back to the
+ * live scheduler view
+ */
+function hasAutoQueueCreation(
+ queue: QueueInfo,
+ getQueuePropertyValue?: QueuePropertyAccessor,
+): boolean {
+ if (getQueuePropertyValue) {
+ const flexible = getQueuePropertyValue(queue.queuePath, AUTO_CREATION_PROPS.FLEXIBLE_ENABLED);
+ const legacy = getQueuePropertyValue(queue.queuePath, AUTO_CREATION_PROPS.LEGACY_ENABLED);
+
+ if (flexible.isStaged || legacy.isStaged) {
+ return flexible.value === 'true' || legacy.value === 'true';
+ }
+ }
+
+ const eligibility = queue.autoCreationEligibility;
+ return (
+ eligibility === AUTO_CREATION_PROPS.ELIGIBILITY_FLEXIBLE ||
+ eligibility === AUTO_CREATION_PROPS.ELIGIBILITY_LEGACY
+ );
+}
+
+/**
+ * Check if a queue can act as a parent queue. This is true when the queue already
+ * has static child queues, or when Dynamic Queue Creation is enabled for it.
+ */
+function isParentQueue(queue: QueueInfo, getQueuePropertyValue?: QueuePropertyAccessor): boolean {
+ const hasChildren = !!(
queue.queues?.queue &&
(Array.isArray(queue.queues.queue) ? queue.queues.queue.length > 0 : true)
);
+ return hasChildren || hasAutoQueueCreation(queue, getQueuePropertyValue);
}
/**
@@ -78,9 +119,17 @@ function getQueues(
/**
* Get all parent queue paths from the scheduler data
* Returns an array of queue options suitable for use in a combobox
+ *
+ * Queues with Dynamic Queue Creation enabled are included even when they have
+ * no static children, since dynamic child queues will be created under them.
+ * Pass getQueuePropertyValue to also recognise queues whose Dynamic Queue
+ * Creation toggle is staged but not yet applied.
*/
-export function getAllParentQueues(schedulerData: SchedulerInfo | null): QueueOption[] {
- return getQueues(schedulerData, isParentQueue);
+export function getAllParentQueues(
+ schedulerData: SchedulerInfo | null,
+ getQueuePropertyValue?: QueuePropertyAccessor,
+): QueueOption[] {
+ return getQueues(schedulerData, (queue) => isParentQueue(queue, getQueuePropertyValue));
}
/**
---------------------------------------------------------------------
To unsubscribe, e-mail: common-commits-unsubscribe@hadoop.apache.org
For additional commands, e-mail: common-commits-help@hadoop.apache.org