From 5d92d68692eae1e46bf7cfa3cb62c081fec1501b Mon Sep 17 00:00:00 2001 From: Xuanqi He Date: Thu, 22 Aug 2024 15:47:36 -0400 Subject: [PATCH 1/5] feat: Support ODCR(On-Demand Capacity Reservations) and CB(Capacity Blocks) for PCUI - Added CapacityReservationTarget select component allowing users to choose between CapacityReservationId, CapacityReservationResourceGroupArn, or none at both queue and compute resource levels. - Implemented dynamic placeholders to guide users on the required inputs for CapacityReservationId and CapacityReservationResourceGroupArn. - Introduced CAPACITY_BLOCK as a new purchase type option in the UI. - Automatically hide the Allocation Strategy selection when CAPACITY_BLOCK is selected as the purchase type, adhering to the expected behavior in ParallelCluster. - Applied CapacityReservationTarget select component in queue and compute resource levels, maintaining consistency with ParallelCluster's behavior. --- frontend/locales/en/strings.json | 15 ++++- .../src/old-pages/Configure/Components.tsx | 59 ++++++++++++++++++- .../Queues/MultiInstanceComputeResource.tsx | 33 +++++++++++ .../src/old-pages/Configure/Queues/Queues.tsx | 56 +++++++++++++++--- 4 files changed, 153 insertions(+), 10 deletions(-) diff --git a/frontend/locales/en/strings.json b/frontend/locales/en/strings.json index ba079895..e9592996 100644 --- a/frontend/locales/en/strings.json +++ b/frontend/locales/en/strings.json @@ -1094,6 +1094,19 @@ }, "rootVolume": { "title": "Root volume" + }, + "capacityType":{ + "label": "CapacityType" + }, + "capacityReservationTarget": { + "title": "Capacity reservations", + "label": "CapacityReservationTarget" + }, + "capacityReservationResourceGroupArn": { + "label": "CapacityReservationResourceGroupArn" + }, + "capacityReservationId": { + "label": "CapacityReservationId" } }, "addQueueButton": { @@ -1448,4 +1461,4 @@ "label": "Choose file" } } -} \ No newline at end of file +} diff --git a/frontend/src/old-pages/Configure/Components.tsx b/frontend/src/old-pages/Configure/Components.tsx index d5953882..266e42c7 100644 --- a/frontend/src/old-pages/Configure/Components.tsx +++ b/frontend/src/old-pages/Configure/Components.tsx @@ -10,7 +10,7 @@ // OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions and // limitations under the License. -// Fameworks +// Frameworks import React, { ReactElement, useCallback, @@ -677,6 +677,62 @@ function IamPoliciesEditor({basePath}: any) { ) } +function OdcrCbSelect({ + selectedOption, + onChange, + inputValue, + onInputChange, +}: { + selectedOption: string + onChange: (event: any) => void + inputValue: string + onInputChange: (event: any) => void +}) { + const { t } = useTranslation() + + const options = [ + { label: 'none', value: 'none' }, + { label: t('wizard.queues.advancedOptions.capacityReservationId.label'), value: 'capacityReservationId' }, + { label: t('wizard.queues.advancedOptions.capacityReservationResourceGroupArn.label'), value: 'capacityReservationResourceGroupArn' }, + ] + + const getPlaceholder = () => { + if (selectedOption === 'capacityReservationId') { + return "cr-" + } + if (selectedOption === 'capacityReservationResourceGroupArn') { + return "arn:aws:resource-groups:::group/" + } + return "" + } + + return ( + + + + + )} + + ) +} + type HelpTextInputProps = { name: string path: string[] @@ -788,4 +844,5 @@ export { IamPoliciesEditor, HelpTextInput, CheckboxWithHelpPanel, + OdcrCbSelect, } diff --git a/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx b/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx index f29e7d88..d68d8e24 100644 --- a/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx +++ b/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx @@ -1,3 +1,4 @@ +import * as React from 'react' import { ColumnLayout, FormField, @@ -17,6 +18,7 @@ import {clearState, setState, useState} from '../../../store' import { CheckboxWithHelpPanel, HelpTextInput, + OdcrCbSelect, useInstanceGroups, } from '../Components' import { @@ -207,6 +209,26 @@ export function ComputeResource({ [efaInstances, instanceTypePath, setEnableEFA], ) + const [odcrCbOption, setOdcrCbOption] = React.useState('none') + const [odcrCbInput, setOdcrCbInput] = React.useState('') + + const capacityReservationTargetPath = useMemo( + () => [...path, 'CapacityReservationTarget'], + [path], + ) + + React.useEffect(() => { + if (odcrCbOption === 'none') { + clearState(capacityReservationTargetPath) + } else { + const updateData = { + CapacityReservationId: odcrCbOption === 'capacityReservationId' ? odcrCbInput : undefined, + CapacityReservationResourceGroupArn: odcrCbOption === 'capacityReservationResourceGroupArn' ? odcrCbInput : undefined, + } + setState(capacityReservationTargetPath, updateData) + } + }, [odcrCbOption, odcrCbInput]) + return (
@@ -282,6 +304,17 @@ export function ComputeResource({ )} + { + setOdcrCbOption(detail.selectedOption.value) + if (detail.selectedOption.value === 'none') { + setOdcrCbInput('') + } + }} + inputValue={odcrCbInput} + onInputChange={({detail}) => setOdcrCbInput(detail.value)} + /> [...queuesPath, index, 'AllocationStrategy'], + [index], + ) + const allocationStrategy: AllocationStrategy = useState(allocationStrategyPath) const capacityTypes: [string, string, string][] = [ ['ONDEMAND', 'On-Demand', '/pcui/img/od.svg'], ['SPOT', 'Spot', '/pcui/img/spot.svg'], + ['CAPACITY_BLOCK', 'Capacity Block', '/pcui/img/cb.svg'], ] const capacityTypePath = [...queuesPath, index, 'CapacityType'] const capacityType: string = useState(capacityTypePath) || 'ONDEMAND' @@ -404,6 +406,29 @@ function Queue({index}: any) { const subnetsList = useState(subnetPath) || [] const isMultiAZActive = useFeatureFlag('multi_az') + const [odcrCbOption, setOdcrCbOption] = React.useState('none') + const [odcrCbInput, setOdcrCbInput] = React.useState('') + + const capacityReservationTargetPath = [...queuesPath, index, 'CapacityReservationTarget'] + + React.useEffect(() => { + if (odcrCbOption === 'none') { + clearState(capacityReservationTargetPath) + } else { + const updateData = { + CapacityReservationId: odcrCbOption === 'capacityReservationId' ? odcrCbInput : undefined, + CapacityReservationResourceGroupArn: odcrCbOption === 'capacityReservationResourceGroupArn' ? odcrCbInput : undefined, + } + setState(capacityReservationTargetPath, updateData) + } + }, [odcrCbOption, odcrCbInput]) + + React.useEffect(() => { + if (capacityType === 'CAPACITY_BLOCK') { + clearState(allocationStrategyPath) + } + }, [capacityType]) + const remove = () => { setState( [...queuesPath], @@ -458,11 +483,11 @@ function Queue({index}: any) { const setAllocationStrategy = React.useCallback( ({detail}) => { setState( - [...queuesPath, index, 'AllocationStrategy'], + allocationStrategyPath, detail.selectedOption.value, ) }, - [index], + [allocationStrategyPath], ) const defaultAllocationStrategy = useDefaultAllocationStrategy() @@ -575,6 +600,20 @@ function Queue({index}: any) { {t('wizard.queues.advancedOptions.iamPolicies.label')} +
+ {t('wizard.queues.advancedOptions.capacityReservationTarget.title')} +
+ { + setOdcrCbOption(detail.selectedOption.value) + if (detail.selectedOption.value === 'none') { + setOdcrCbInput('') + } + }} + inputValue={odcrCbInput} + onInputChange={({ detail }) => setOdcrCbInput(detail.value)} + />
} @@ -659,7 +698,8 @@ function Queue({index}: any) { options={capacityTypes.map(itemToOption)} /> - {isMultiInstanceTypesActive ? ( + {/* If the type is CAPACITY_BLOCK, do not display the AllocationStrategy */} + {isMultiInstanceTypesActive && capacityType !== 'CAPACITY_BLOCK' ? ( Date: Thu, 22 Aug 2024 18:28:54 -0400 Subject: [PATCH 2/5] feat: Support ODCR(On-Demand Capacity Reservations) and CB(Capacity Blocks) for PCUI - Removed the usage of the odcrCbSelection component at the queue level due to conflicts in queues.mapper.ts when converting SingleInstanceComputeResource to MultiInstanceComputeResource. - Implemented logic to automatically hide the instance type selection and exclude the Instances section in the YAML when CapacityReservationId is selected. - Made the Instances property of MultiInstanceComputeResource optional. - Updated the mechanism in queues.mapper.ts to resolve the conversion conflict. --- .../Queues/MultiInstanceComputeResource.tsx | 42 +++++++++++-------- .../src/old-pages/Configure/Queues/Queues.tsx | 32 -------------- .../Configure/Queues/SlurmMemorySettings.tsx | 2 +- .../Configure/Queues/queues.mapper.ts | 26 ++++++++---- .../Configure/Queues/queues.types.ts | 2 +- 5 files changed, 44 insertions(+), 60 deletions(-) diff --git a/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx b/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx index d68d8e24..15d449a8 100644 --- a/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx +++ b/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx @@ -226,6 +226,10 @@ export function ComputeResource({ CapacityReservationResourceGroupArn: odcrCbOption === 'capacityReservationResourceGroupArn' ? odcrCbInput : undefined, } setState(capacityReservationTargetPath, updateData) + + if (odcrCbOption === 'capacityReservationId') { + clearState(instanceTypePath) + } } }, [odcrCbOption, odcrCbInput]) @@ -269,24 +273,26 @@ export function ComputeResource({ /> - - ({ - value: instance.InstanceType, - label: instance.InstanceType, - }))} - placeholder={t( - 'wizard.queues.computeResource.instanceType.placeholder.multiple', - )} - tokenLimit={3} - onChange={setInstances} - options={instanceOptions} - filteringType="auto" - /> - + {odcrCbOption !== 'capacityReservationId' && ( + + ({ + value: instance.InstanceType, + label: instance.InstanceType, + }))} + placeholder={t( + 'wizard.queues.computeResource.instanceType.placeholder.multiple', + )} + tokenLimit={3} + onChange={setInstances} + options={instanceOptions} + filteringType="auto" + /> + + )} {enableMemoryBasedScheduling && ( { - if (odcrCbOption === 'none') { - clearState(capacityReservationTargetPath) - } else { - const updateData = { - CapacityReservationId: odcrCbOption === 'capacityReservationId' ? odcrCbInput : undefined, - CapacityReservationResourceGroupArn: odcrCbOption === 'capacityReservationResourceGroupArn' ? odcrCbInput : undefined, - } - setState(capacityReservationTargetPath, updateData) - } - }, [odcrCbOption, odcrCbInput]) - React.useEffect(() => { if (capacityType === 'CAPACITY_BLOCK') { clearState(allocationStrategyPath) @@ -600,20 +582,6 @@ function Queue({index}: any) { {t('wizard.queues.advancedOptions.iamPolicies.label')} -
- {t('wizard.queues.advancedOptions.capacityReservationTarget.title')} -
- { - setOdcrCbOption(detail.selectedOption.value) - if (detail.selectedOption.value === 'none') { - setOdcrCbInput('') - } - }} - inputValue={odcrCbInput} - onInputChange={({ detail }) => setOdcrCbInput(detail.value)} - /> } diff --git a/frontend/src/old-pages/Configure/Queues/SlurmMemorySettings.tsx b/frontend/src/old-pages/Configure/Queues/SlurmMemorySettings.tsx index f6a7e701..470ee683 100644 --- a/frontend/src/old-pages/Configure/Queues/SlurmMemorySettings.tsx +++ b/frontend/src/old-pages/Configure/Queues/SlurmMemorySettings.tsx @@ -60,7 +60,7 @@ const hasMultipleInstanceTypes = (queues: Queue[]): boolean => { computeResources.map(computeResource => computeResource.Instances), ) .flat() - .filter(instances => instances.length > 1).length > 0 + .filter(instances => instances && instances.length > 1).length > 0 ) } diff --git a/frontend/src/old-pages/Configure/Queues/queues.mapper.ts b/frontend/src/old-pages/Configure/Queues/queues.mapper.ts index 1ffe0b1c..33dd407a 100644 --- a/frontend/src/old-pages/Configure/Queues/queues.mapper.ts +++ b/frontend/src/old-pages/Configure/Queues/queues.mapper.ts @@ -15,21 +15,31 @@ import { } from './queues.types' function mapComputeResource( - computeResource: SingleInstanceComputeResource | MultiInstanceComputeResource, + computeResource: SingleInstanceComputeResource | MultiInstanceComputeResource, ): MultiInstanceComputeResource { - if ('Instances' in computeResource) { + // If it's already a MultiInstanceComputeResource and Instances exist, return it directly + if ('Instances' in computeResource && computeResource.Instances?.length) { return computeResource } - const {InstanceType, ...otherComputeResourceConfig} = computeResource + // If it's of SingleInstanceComputeResource type, convert it to MultiInstanceComputeResource + if ('InstanceType' in computeResource) { + const {InstanceType, ...otherComputeResourceConfig} = computeResource + return { + ...otherComputeResourceConfig, + Instances: [ + { + InstanceType, + }, + ], + } + } + + // If Instances are cleared or do not exist, return the other configurations + const {Instances, ...otherComputeResourceConfig} = computeResource return { ...otherComputeResourceConfig, - Instances: [ - { - InstanceType, - }, - ], } } diff --git a/frontend/src/old-pages/Configure/Queues/queues.types.ts b/frontend/src/old-pages/Configure/Queues/queues.types.ts index 4488f55f..dfa690bf 100644 --- a/frontend/src/old-pages/Configure/Queues/queues.types.ts +++ b/frontend/src/old-pages/Configure/Queues/queues.types.ts @@ -27,7 +27,7 @@ export type AllocationStrategy = 'lowest-price' | 'capacity-optimized' export type ComputeResourceInstance = {InstanceType: string} export type MultiInstanceComputeResource = ComputeResource & { - Instances: ComputeResourceInstance[] + Instances?: ComputeResourceInstance[] } export type Tag = { From bbd6f1f80305d69f64c15871875a5e4c70b7cda1 Mon Sep 17 00:00:00 2001 From: Xuanqi He Date: Thu, 22 Aug 2024 21:01:06 -0400 Subject: [PATCH 3/5] Add CapacityReservationTarget in queues types. Fix validateComputeResources to adapt the new changes. Now if CapacityReservationId is selected, Instances can be empty. --- .../Configure/Queues/MultiInstanceComputeResource.tsx | 5 ++++- frontend/src/old-pages/Configure/Queues/queues.types.ts | 6 ++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx b/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx index 15d449a8..d42f6d50 100644 --- a/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx +++ b/frontend/src/old-pages/Configure/Queues/MultiInstanceComputeResource.tsx @@ -386,7 +386,10 @@ export function validateComputeResources( computeResources: MultiInstanceComputeResource[], ): [boolean, QueueValidationErrors] { let errors = computeResources.reduce((acc, cr, i) => { - if (!cr.Instances || !cr.Instances.length) { + const hasCapacityReservationId = cr.CapacityReservationTarget?.CapacityReservationId + + // Skip instance type validation if CapacityReservationId is set + if (!hasCapacityReservationId && (!cr.Instances || !cr.Instances.length)) { acc[i] = 'instance_types_empty' } return acc diff --git a/frontend/src/old-pages/Configure/Queues/queues.types.ts b/frontend/src/old-pages/Configure/Queues/queues.types.ts index dfa690bf..92865622 100644 --- a/frontend/src/old-pages/Configure/Queues/queues.types.ts +++ b/frontend/src/old-pages/Configure/Queues/queues.types.ts @@ -26,8 +26,14 @@ export type AllocationStrategy = 'lowest-price' | 'capacity-optimized' export type ComputeResourceInstance = {InstanceType: string} +export type CapacityReservationTarget = { + CapacityReservationId?: string + CapacityReservationResourceGroupArn?: string +} + export type MultiInstanceComputeResource = ComputeResource & { Instances?: ComputeResourceInstance[] + CapacityReservationTarget?: CapacityReservationTarget } export type Tag = { From 6654acc35ab495da0b17bc57ca7f7ff4c3c1b9f7 Mon Sep 17 00:00:00 2001 From: Xuanqi He Date: Fri, 23 Aug 2024 11:37:43 -0400 Subject: [PATCH 4/5] Add info panel for Capacity Reservation and Capacity Block. Included documentation links and note on instance type usage. Move related string correctly under compute resources instead of advanced options. Tiny changes on format and comments. --- frontend/locales/en/strings.json | 32 +++++++++------- .../src/old-pages/Configure/Components.tsx | 37 +++++++++++++++++-- .../Queues/MultiInstanceComputeResource.tsx | 1 + .../Configure/Queues/queues.mapper.ts | 2 +- 4 files changed, 53 insertions(+), 19 deletions(-) diff --git a/frontend/locales/en/strings.json b/frontend/locales/en/strings.json index e9592996..9e1f7d4e 100644 --- a/frontend/locales/en/strings.json +++ b/frontend/locales/en/strings.json @@ -1082,7 +1082,24 @@ "description": "Prevent multiple threads from running on an EC2 instance core." } }, - "enableEfa": "Use EFA" + "enableEfa": "Use EFA", + "capacityReservationTarget": { + "title": "Capacity reservations", + "label": "CapacityReservationTarget", + "help": { + "title": "Capacity Reservations and Capacity Blocks", + "description": "Learn how to configure Capacity Reservations and Capacity Blocks in ParallelCluster.", + "odcrLink": "Launch instances using On-Demand Capacity Reservations (ODCR)", + "capacityBlocksLink": "Launch instances using Capacity Blocks", + "note": "When selecting CapacityReservationId, the specified instance type will automatically apply." + } + }, + "capacityReservationResourceGroupArn": { + "label": "CapacityReservationResourceGroupArn" + }, + "capacityReservationId": { + "label": "CapacityReservationId" + } }, "advancedOptions": { "label": "Advanced options", @@ -1094,19 +1111,6 @@ }, "rootVolume": { "title": "Root volume" - }, - "capacityType":{ - "label": "CapacityType" - }, - "capacityReservationTarget": { - "title": "Capacity reservations", - "label": "CapacityReservationTarget" - }, - "capacityReservationResourceGroupArn": { - "label": "CapacityReservationResourceGroupArn" - }, - "capacityReservationId": { - "label": "CapacityReservationId" } }, "addQueueButton": { diff --git a/frontend/src/old-pages/Configure/Components.tsx b/frontend/src/old-pages/Configure/Components.tsx index 266e42c7..8c481226 100644 --- a/frontend/src/old-pages/Configure/Components.tsx +++ b/frontend/src/old-pages/Configure/Components.tsx @@ -692,8 +692,8 @@ function OdcrCbSelect({ const options = [ { label: 'none', value: 'none' }, - { label: t('wizard.queues.advancedOptions.capacityReservationId.label'), value: 'capacityReservationId' }, - { label: t('wizard.queues.advancedOptions.capacityReservationResourceGroupArn.label'), value: 'capacityReservationResourceGroupArn' }, + { label: t('wizard.queues.computeResource.capacityReservationId.label'), value: 'capacityReservationId' }, + { label: t('wizard.queues.computeResource.capacityReservationResourceGroupArn.label'), value: 'capacityReservationResourceGroupArn' }, ] const getPlaceholder = () => { @@ -708,7 +708,36 @@ function OdcrCbSelect({ return ( - + +

{t('wizard.queues.computeResource.capacityReservationTarget.help.description')}

+ +

{t('wizard.queues.computeResource.capacityReservationTarget.help.note')}

+ + } + /> + } + /> + } + >
+ {/* Render the instance type selection field only when 'capacityReservationId' is not selected */} {odcrCbOption !== 'capacityReservationId' && ( Date: Mon, 26 Aug 2024 09:57:15 -0400 Subject: [PATCH 5/5] Minor change on capacityReservationTarget description. --- frontend/locales/en/strings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/locales/en/strings.json b/frontend/locales/en/strings.json index 9e1f7d4e..855dd9e5 100644 --- a/frontend/locales/en/strings.json +++ b/frontend/locales/en/strings.json @@ -1088,7 +1088,7 @@ "label": "CapacityReservationTarget", "help": { "title": "Capacity Reservations and Capacity Blocks", - "description": "Learn how to configure Capacity Reservations and Capacity Blocks in ParallelCluster.", + "description": "Configure your cluster to use Capacity Reservations or Capacity Blocks for better resource management and availability.", "odcrLink": "Launch instances using On-Demand Capacity Reservations (ODCR)", "capacityBlocksLink": "Launch instances using Capacity Blocks", "note": "When selecting CapacityReservationId, the specified instance type will automatically apply."