Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ export const queuePropertyDefinitions: PropertyDescriptor[] = [
name: 'auto-queue-creation-v2.enabled',
displayName: 'Flexible Queue Auto-Creation',
description:
'Enable flexible queue auto-creation (parent and leaf queues). In legacy queue mode, root queue requires all child queues to use weight-based capacity.',
'Enable flexible queue auto-creation (parent and leaf queues). In legacy queue mode, root queue requires all child queues to use weight-based capacity; enabling or disabling on root automatically converts root capacity between 100% and 1w.',
type: 'boolean' as PropertyType,
category: 'dynamic-queues' as PropertyCategory,
defaultValue: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ vi.mock('sonner', () => ({
error: vi.fn(),
success: vi.fn(),
warning: vi.fn(),
info: vi.fn(),
},
}));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,14 @@ export function usePropertyEditor({
queuePath,
properties = queuePropertyDefinitions,
}: UsePropertyEditorOptions) {
const { getQueuePropertyValue, stageQueueChange, clearQueueChanges, schedulerData, configData } =
useSchedulerStore();
const {
getQueuePropertyValue,
stageQueueChange,
clearQueueChanges,
schedulerData,
configData,
resolveRootCapacityStagingForFlexibleAutoCreation,
} = useSchedulerStore();

const stagedChanges = useSchedulerStore((state) => state.stagedChanges);
const cleanResetRef = useRef(false);
Expand Down Expand Up @@ -461,6 +467,14 @@ export function usePropertyEditor({
}
});

const stagedRootCapacity = resolveRootCapacityStagingForFlexibleAutoCreation(
queuePath,
changedData,
);
if (stagedRootCapacity) {
changedData.capacity = stagedRootCapacity.capacity;
}

const pendingEntries = Object.entries(changedData);

const previewConfigData = new Map(configData);
Expand Down Expand Up @@ -550,6 +564,12 @@ export function usePropertyEditor({
message: `${stagedCount} change${stagedCount !== 1 ? 's' : ''} staged successfully!`,
};

if (stagedRootCapacity?.direction === 'to-weight') {
toast.info('Root queue capacity was automatically converted from 100% to 1w for weight mode.');
} else if (stagedRootCapacity?.direction === 'to-percentage') {
toast.info('Root queue capacity was automatically converted from 1w to 100%.');
}

if (stagedCount > 0) {
const latestValues = form.getValues();
reset(latestValues, {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* 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 { resolveRootCapacityStagingWhenAutoQueueCreationIsToggled } from '~/stores/slices/rootCapacityAutoStaging';
import { AUTO_CREATION_PROPS } from '~/types/constants/auto-creation';
import { SPECIAL_VALUES } from '~/types/constants/special-values';

describe('resolveRootCapacityStagingWhenAutoQueueCreationIsToggled', () => {
const createGetQueuePropertyValue =
(values: Record<string, string>) => (_queuePath: string, propertyName: string) => ({
value: values[propertyName] ?? '',
isStaged: false,
});

const legacyConfig = new Map([[SPECIAL_VALUES.LEGACY_MODE_PROPERTY, 'true']]);

it('returns 1w when enabling flexible auto-creation on root with percentage capacity', () => {
const result = resolveRootCapacityStagingWhenAutoQueueCreationIsToggled({
queuePath: SPECIAL_VALUES.ROOT_QUEUE_NAME,
changedData: { [AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'true' },
getQueuePropertyValue: createGetQueuePropertyValue({ capacity: '100' }),
configData: legacyConfig,
});

expect(result).toEqual({
capacity: '1w',
direction: 'to-weight',
});
});

it('returns 100% when disabling flexible auto-creation on root with weight capacity', () => {
const result = resolveRootCapacityStagingWhenAutoQueueCreationIsToggled({
queuePath: SPECIAL_VALUES.ROOT_QUEUE_NAME,
changedData: { [AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'false' },
getQueuePropertyValue: createGetQueuePropertyValue({
capacity: '1w',
[AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'true',
}),
configData: legacyConfig,
});

expect(result).toEqual({
capacity: '100%',
direction: 'to-percentage',
});
});

it('returns null for non-root queues', () => {
const result = resolveRootCapacityStagingWhenAutoQueueCreationIsToggled({
queuePath: 'root.default',
changedData: { [AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'true' },
getQueuePropertyValue: createGetQueuePropertyValue({ capacity: '2w' }),
configData: legacyConfig,
});

expect(result).toBeNull();
});

it('returns null when legacy mode is disabled', () => {
const result = resolveRootCapacityStagingWhenAutoQueueCreationIsToggled({
queuePath: SPECIAL_VALUES.ROOT_QUEUE_NAME,
changedData: { [AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'true' },
getQueuePropertyValue: createGetQueuePropertyValue({ capacity: '100' }),
configData: new Map([[SPECIAL_VALUES.LEGACY_MODE_PROPERTY, 'false']]),
});

expect(result).toBeNull();
});

it('returns null when root already uses weight capacity while enabling', () => {
const result = resolveRootCapacityStagingWhenAutoQueueCreationIsToggled({
queuePath: SPECIAL_VALUES.ROOT_QUEUE_NAME,
changedData: { [AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'true' },
getQueuePropertyValue: createGetQueuePropertyValue({ capacity: '2w' }),
configData: legacyConfig,
});

expect(result).toBeNull();
});

it('returns null when flexible auto-creation was not enabled while disabling', () => {
const result = resolveRootCapacityStagingWhenAutoQueueCreationIsToggled({
queuePath: SPECIAL_VALUES.ROOT_QUEUE_NAME,
changedData: { [AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'false' },
getQueuePropertyValue: createGetQueuePropertyValue({
capacity: '1w',
[AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'false',
}),
configData: legacyConfig,
});

expect(result).toBeNull();
});

it('returns null when root already uses percentage capacity while disabling', () => {
const result = resolveRootCapacityStagingWhenAutoQueueCreationIsToggled({
queuePath: SPECIAL_VALUES.ROOT_QUEUE_NAME,
changedData: { [AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'false' },
getQueuePropertyValue: createGetQueuePropertyValue({
capacity: '100',
[AUTO_CREATION_PROPS.FLEXIBLE_ENABLED]: 'true',
}),
configData: legacyConfig,
});

expect(result).toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* 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 { AUTO_CREATION_PROPS } from '~/types/constants/auto-creation';
import { SPECIAL_VALUES } from '~/types/constants/special-values';
import { getCapacityType } from '~/utils/capacityUtils';

export interface RootCapacityAutoStagingResult {
capacity: string;
direction: 'to-weight' | 'to-percentage';
}

export interface RootCapacityAutoStagingContext {
queuePath: string;
changedData: Record<string, string>;
getQueuePropertyValue: (
queuePath: string,
propertyName: string,
) => { value: string; isStaged: boolean };
configData: Map<string, string>;
}

function canAutoStageRootCapacity({
queuePath,
changedData,
configData,
}: RootCapacityAutoStagingContext): boolean {
if (queuePath !== SPECIAL_VALUES.ROOT_QUEUE_NAME) {
return false;
}

if (changedData.capacity !== undefined) {
return false;
}

return configData.get(SPECIAL_VALUES.LEGACY_MODE_PROPERTY) !== 'false';
}

function resolveRootCapacityStagingWhenEnablingFlexibleAutoCreation(
options: RootCapacityAutoStagingContext,
): RootCapacityAutoStagingResult | null {
if (!canAutoStageRootCapacity(options)) {
return null;
}

if (options.changedData[AUTO_CREATION_PROPS.FLEXIBLE_ENABLED] !== 'true') {
return null;
}

const { value: currentCapacity } = options.getQueuePropertyValue(options.queuePath, 'capacity');
if (getCapacityType(currentCapacity) !== 'percentage') {
return null;
}

return {
capacity: '1w',
direction: 'to-weight',
};
}

function resolveRootCapacityStagingWhenDisablingFlexibleAutoCreation(
options: RootCapacityAutoStagingContext,
): RootCapacityAutoStagingResult | null {
if (!canAutoStageRootCapacity(options)) {
return null;
}

const flexibleEnabledChange = options.changedData[AUTO_CREATION_PROPS.FLEXIBLE_ENABLED];
if (flexibleEnabledChange === undefined || flexibleEnabledChange === 'true') {
return null;
}

const { value: currentFlexibleEnabled } = options.getQueuePropertyValue(
options.queuePath,
AUTO_CREATION_PROPS.FLEXIBLE_ENABLED,
);
if (currentFlexibleEnabled !== 'true') {
return null;
}

const { value: currentCapacity } = options.getQueuePropertyValue(options.queuePath, 'capacity');
if (getCapacityType(currentCapacity) !== 'weight') {
return null;
}

return {
capacity: '100%',
direction: 'to-percentage',
};
}

/**
* Restore root capacity when flexible auto-queue-creation is toggled.
* Convert 100% -> 1w when the toggle is turned on.
* Convert 1w -> 100% when the toggle is turned off.
*/
export function resolveRootCapacityStagingWhenAutoQueueCreationIsToggled(
context: RootCapacityAutoStagingContext,
): RootCapacityAutoStagingResult | null {
return (
resolveRootCapacityStagingWhenEnablingFlexibleAutoCreation(context) ??
resolveRootCapacityStagingWhenDisablingFlexibleAutoCreation(context)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import { assertWritable } from '~/lib/errors/readOnlyGuard';
import type { StagedChangesSlice, SchedulerStore } from './types';
import { getAffectedQueuesForValidation } from '~/features/validation/utils/affectedQueues';
import { validateStagedChanges, validatePropertyChange } from '~/features/validation/crossQueue';
import { resolveRootCapacityStagingWhenAutoQueueCreationIsToggled } from './rootCapacityAutoStaging';

type MutationErrorState = Pick<SchedulerStore, 'applyError' | 'error' | 'errorContext'>;
const clearMutationError = (state: MutationErrorState) => {
Expand Down Expand Up @@ -582,6 +583,14 @@ export const createStagedChangesSlice: StateCreator<
});
},

resolveRootCapacityStagingForFlexibleAutoCreation: (queuePath, changedData) =>
resolveRootCapacityStagingWhenAutoQueueCreationIsToggled({
queuePath,
changedData,
getQueuePropertyValue: get().getQueuePropertyValue,
configData: get().configData,
}),

refreshAffectedValidationErrors: (triggeringQueuePath: string, triggeringProperty: string) => {
const { stagedChanges, schedulerData, configData } = get();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
} from '~/types';
import type { PlacementRulesSlice } from './placementRulesSlice';
import type { CapacityEditorSlice } from './capacityEditorSlice';
import type { RootCapacityAutoStagingResult } from './rootCapacityAutoStaging';

export interface BaseStoreSlice {
apiClient: YarnApiClient;
Expand Down Expand Up @@ -107,6 +108,10 @@ export interface StagedChangesSlice {
triggeringQueuePath: string,
triggeringProperty: string,
) => void;
resolveRootCapacityStagingForFlexibleAutoCreation: (
queuePath: string,
changedData: Record<string, string>,
) => RootCapacityAutoStagingResult | null;
}

export interface QueueSelectionSlice {
Expand Down
Loading