From 94dc8c8ff1b3740b3b04111ac7f713fe4dc9a03f Mon Sep 17 00:00:00 2001 From: Mikhail Rodichenko Date: Mon, 18 Sep 2023 18:17:30 +0200 Subject: [PATCH] GUI Launch form, tool form: cluster configuration - set workers instance type --- .../launch/form/LaunchPipelineForm.js | 35 +- .../form/utilities/enable-gpu-scaling.js | 89 +++- .../form/utilities/launch-cluster-tooltips.js | 24 +- .../launch/form/utilities/launch-cluster.js | 461 ++++++++++++------ .../launch/form/utilities/parameters.js | 2 + client/src/components/tools/Tool.js | 2 +- .../components/tools/forms/EditToolForm.js | 31 +- client/src/utils/instance-family.js | 68 +++ 8 files changed, 559 insertions(+), 153 deletions(-) create mode 100644 client/src/utils/instance-family.js diff --git a/client/src/components/pipelines/launch/form/LaunchPipelineForm.js b/client/src/components/pipelines/launch/form/LaunchPipelineForm.js index ae84295715..45b196c680 100644 --- a/client/src/components/pipelines/launch/form/LaunchPipelineForm.js +++ b/client/src/components/pipelines/launch/form/LaunchPipelineForm.js @@ -79,7 +79,9 @@ import { slurmEnabled, kubeEnabled, setClusterParameterValue, - getAutoScaledPriceTypeValue + getAutoScaledPriceTypeValue, + applyChildNodeInstanceParameters, + parseChildNodeInstanceConfiguration } from './utilities/launch-cluster'; import checkModifiedState from './utilities/launch-form-modified-state'; import { @@ -280,6 +282,7 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { autoScaledCluster: false, hybridAutoScaledClusterEnabled: false, gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined, gridEngineEnabled: false, sparkEnabled: false, slurmEnabled: false, @@ -913,6 +916,12 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { }, this.props.preferences ); + const childNodeInstanceConfiguration = parseChildNodeInstanceConfiguration({ + autoScaled: autoScaledCluster, + gpuScaling: !!gpuScalingConfiguration, + hybrid: hybridAutoScaledCluster, + parameters: (this.props.parameters || {}).parameters + }); const gridEngineEnabledValue = gridEngineEnabled(this.props.parameters.parameters); const sparkEnabledValue = sparkEnabled(this.props.parameters.parameters); const slurmEnabledValue = slurmEnabled(this.props.parameters.parameters); @@ -942,6 +951,7 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { autoScaledCluster: autoScaledCluster, hybridAutoScaledClusterEnabled: hybridAutoScaledCluster, gpuScalingConfiguration, + childNodeInstanceConfiguration, gridEngineEnabled: gridEngineEnabledValue, sparkEnabled: sparkEnabledValue, slurmEnabled: slurmEnabledValue, @@ -1000,6 +1010,7 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { autoScaledCluster: autoScaledCluster, hybridAutoScaledClusterEnabled: hybridAutoScaledCluster, gpuScalingConfiguration, + childNodeInstanceConfiguration, gridEngineEnabled: gridEngineEnabledValue, sparkEnabled: sparkEnabledValue, slurmEnabled: slurmEnabledValue, @@ -1190,6 +1201,12 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { this.state.gpuScalingConfiguration, payload[PARAMETERS] ); + } else if (this.state.childNodeInstanceConfiguration) { + applyChildNodeInstanceParameters( + payload[PARAMETERS], + this.state.childNodeInstanceConfiguration, + this.state.hybridAutoScaledClusterEnabled + ); } } if (this.state.launchCluster && this.state.gridEngineEnabled) { @@ -1404,6 +1421,12 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { this.state.gpuScalingConfiguration, payload.params ); + } else if (this.state.childNodeInstanceConfiguration) { + applyChildNodeInstanceParameters( + payload.params, + this.state.childNodeInstanceConfiguration, + this.state.hybridAutoScaledClusterEnabled + ); } } if (this.state.launchCluster && this.state.gridEngineEnabled) { @@ -1613,6 +1636,12 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { }, this.props.preferences ); + const childNodeInstanceConfiguration = parseChildNodeInstanceConfiguration({ + autoScaled: autoScaledCluster, + gpuScaling: !!gpuScalingConfiguration, + hybrid: hybridAutoScaledCluster, + parameters: this.props.parameters.parameters + }); const gridEngineEnabledValue = gridEngineEnabled(this.props.parameters.parameters); const sparkEnabledValue = sparkEnabled(this.props.parameters.parameters); const slurmEnabledValue = slurmEnabled(this.props.parameters.parameters); @@ -1624,6 +1653,7 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { autoScaledCluster: autoScaledCluster, hybridAutoScaledClusterEnabled: hybridAutoScaledCluster, gpuScalingConfiguration, + childNodeInstanceConfiguration, gridEngineEnabled: gridEngineEnabledValue, sparkEnabled: sparkEnabledValue, slurmEnabled: slurmEnabledValue, @@ -3981,6 +4011,7 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { autoScaledCluster, hybridAutoScaledClusterEnabled, gpuScalingConfiguration, + childNodeInstanceConfiguration, nodesCount, maxNodesCount, gridEngineEnabled, @@ -4002,6 +4033,7 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { autoScaledCluster, hybridAutoScaledClusterEnabled, gpuScalingConfiguration, + childNodeInstanceConfiguration, gridEngineEnabled, sparkEnabled, slurmEnabled, @@ -5642,6 +5674,7 @@ class LaunchPipelineForm extends localization.LocalizedReactComponent { autoScaledCluster={this.state.autoScaledCluster} hybridAutoScaledClusterEnabled={this.state.hybridAutoScaledClusterEnabled} gpuScalingConfiguration={this.state.gpuScalingConfiguration} + childNodeInstanceConfiguration={this.state.childNodeInstanceConfiguration} gridEngineEnabled={this.state.gridEngineEnabled} sparkEnabled={this.state.sparkEnabled} slurmEnabled={this.state.slurmEnabled} diff --git a/client/src/components/pipelines/launch/form/utilities/enable-gpu-scaling.js b/client/src/components/pipelines/launch/form/utilities/enable-gpu-scaling.js index 7d89635ca9..e1d339c213 100644 --- a/client/src/components/pipelines/launch/form/utilities/enable-gpu-scaling.js +++ b/client/src/components/pipelines/launch/form/utilities/enable-gpu-scaling.js @@ -17,6 +17,7 @@ import React from 'react'; import {Select} from 'antd'; import {getSelectOptions} from '../../../../special/instance-type-info'; +import {getInstanceFamily} from '../../../../../utils/instance-family'; function parseCPUConfiguration (configuration) { const { @@ -175,27 +176,106 @@ function getSkippedParameters (preferences, includeOther = true) { return [...(new Set(parameters))]; } +const emptyValueKey = '__empty__'; + const InstanceTypeSelector = ( { value, onChange, instanceTypes = [], style = {}, - gpu = false + gpu = false, + allowEmpty = false, + emptyName, + emptyTooltip } ) => { const sorted = instanceTypes.filter(t => !gpu || t.gpu); + const onChangeCallback = (newValue) => { + if (typeof onChange === 'function') { + if (newValue === emptyValueKey) { + onChange(undefined); + } else { + onChange(newValue); + } + } + }; return ( ); }; +const InstanceFamilySelector = ( + { + value, + onChange, + instanceTypes = [], + style = {}, + gpu = false, + provider, + allowEmpty = false, + emptyName, + emptyTooltip + } +) => { + const sorted = instanceTypes.filter(t => !gpu || t.gpu); + const families = [...new Set(sorted.map((i) => getInstanceFamily(i, provider)))] + .filter(Boolean) + .sort(); + const onChangeCallback = (newValue) => { + if (typeof onChange === 'function') { + if (newValue === emptyValueKey) { + onChange(undefined); + } else { + onChange(newValue); + } + } + }; + return ( + + ); +}; + function applyParameters (configuration, parameters) { if (configuration) { const { @@ -267,5 +347,6 @@ export { getScalingConfigurationForProvider, getGPUScalingDefaultConfiguration, readGPUScalingPreference, - InstanceTypeSelector + InstanceTypeSelector, + InstanceFamilySelector }; diff --git a/client/src/components/pipelines/launch/form/utilities/launch-cluster-tooltips.js b/client/src/components/pipelines/launch/form/utilities/launch-cluster-tooltips.js index 919669ad36..cd269598be 100644 --- a/client/src/components/pipelines/launch/form/utilities/launch-cluster-tooltips.js +++ b/client/src/components/pipelines/launch/form/utilities/launch-cluster-tooltips.js @@ -135,6 +135,21 @@ const AUTOSCALE_PRICE_TYPE = ( ); +const CHILD_NODE_INSTANCE = ( +
+ + You can specify instance type to be used for autoscaled workers. + +
+); +const CHILD_NODE_INSTANCE_FAMILY = ( +
+ + You can specify instance family for autoscaled workers and + the exact size will be selected automatically. + +
+); const GPU_SCALING_TOOLTIP = (
@@ -143,11 +158,6 @@ const GPU_SCALING_TOOLTIP = ( cpu.q for CPU-only tasks and gpu.q for CUDA tasks - If enabled, you can specify instance types to be used for the autoscaled workers. - - If selected together with Enable Hybrid cluster - it is possible to specify instance family and the exact size is selected automatically. -
); @@ -164,6 +174,8 @@ export const LaunchClusterTooltip = { defaultNodesCount: 'default nodes count', hybridAutoScaledCluster: 'hybrid', autoScalePriceType: 'auto scale price type', + childNodeInstance: 'child node instance', + childNodeInstanceFamily: 'child node instance family', gpuScaling: 'gpu scaling', enableGridEngine: 'enable grid engine', enableSlurm: 'enable slurm' @@ -188,6 +200,8 @@ const tooltips = { [LaunchClusterTooltip.autoScaledCluster.hybridAutoScaledCluster]: HYBRID_AUTOSCALED_CLUSTER_TOOLTIP, [LaunchClusterTooltip.autoScaledCluster.autoScalePriceType]: AUTOSCALE_PRICE_TYPE, + [LaunchClusterTooltip.autoScaledCluster.childNodeInstance]: CHILD_NODE_INSTANCE, + [LaunchClusterTooltip.autoScaledCluster.childNodeInstanceFamily]: CHILD_NODE_INSTANCE_FAMILY, [LaunchClusterTooltip.autoScaledCluster.gpuScaling]: GPU_SCALING_TOOLTIP }; diff --git a/client/src/components/pipelines/launch/form/utilities/launch-cluster.js b/client/src/components/pipelines/launch/form/utilities/launch-cluster.js index aacd1f6c38..d03e414b51 100644 --- a/client/src/components/pipelines/launch/form/utilities/launch-cluster.js +++ b/client/src/components/pipelines/launch/form/utilities/launch-cluster.js @@ -18,7 +18,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Checkbox, - Input, InputNumber, Icon, Modal, @@ -45,7 +44,9 @@ import { CP_CAP_AUTOSCALE_HYBRID, CP_CAP_AUTOSCALE_PRICE_TYPE, CP_CAP_LIMIT_MOUNTS, - CP_CAP_RESCHEDULE_RUN + CP_CAP_RESCHEDULE_RUN, + CP_CAP_AUTOSCALE_HYBRID_FAMILY, + CP_CAP_AUTOSCALE_INSTANCE_TYPE } from './parameters'; import {getRunCapabilitiesSkippedParameters} from './run-capabilities'; import { @@ -53,8 +54,11 @@ import { getGPUScalingDefaultConfiguration, getScalingConfigurationForProvider, gpuScalingAvailable, - InstanceTypeSelector + InstanceTypeSelector, + InstanceFamilySelector } from './enable-gpu-scaling'; +import {getInstanceFamilyByName} from '../../../../../utils/instance-family'; +import {instanceInfoString} from '../../../../special/instance-type-info'; const PARAMETER_TITLE_WIDTH = 110; const PARAMETER_TITLE_RIGHT_MARGIN = 5; @@ -223,62 +227,6 @@ export function setClusterParameterValue (form, sectionName, configuration) { } } -export function setSingleNodeMode (controller, callback) { - controller.setState({ - launchCluster: false, - autoScaledCluster: false, - setDefaultNodesCount: false, - gridEngineEnabled: false, - sparkEnabled: false, - slurmEnabled: false, - kubeEnabled: false, - autoScaledPriceType: undefined, - hybridAutoScaledClusterEnabled: false, - gpuScalingConfiguration: undefined - }, callback); -} - -export function setFixedClusterMode (controller, callback) { - controller.setState({ - launchCluster: true, - autoScaledCluster: false, - setDefaultNodesCount: false, - gridEngineEnabled: false, - sparkEnabled: false, - slurmEnabled: false, - kubeEnabled: false, - autoScaledPriceType: undefined, - hybridAutoScaledClusterEnabled: false, - gpuScalingConfiguration: undefined, - nodesCount: Math.max(1, - !isNaN(controller.state.maxNodesCount) - ? controller.state.maxNodesCount - : 1 - ) - }, callback); -} - -export function setAutoScaledMode (controller, callback) { - controller.setState({ - launchCluster: true, - autoScaledCluster: true, - setDefaultNodesCount: false, - nodesCount: undefined, - gridEngineEnabled: true, - sparkEnabled: false, - slurmEnabled: false, - kubeEnabled: false, - autoScaledPriceType: undefined, - hybridAutoScaledClusterEnabled: false, - gpuScalingConfiguration: undefined, - maxNodesCount: Math.max(1, - !isNaN(controller.state.nodesCount) - ? controller.state.nodesCount - : 1 - ) - }, callback); -} - const range = ({nodesCount, maxNodesCount}) => { return `${nodesCount} - ${maxNodesCount}`; }; @@ -297,6 +245,57 @@ const lowerCasedString = (string, lowercased) => { return string; }; +export function parseChildNodeInstanceConfiguration (options) { + const { + autoScaled = false, + gpuScaling = false, + hybrid = false, + parameters = {} + } = options || {}; + if (autoScaled && !gpuScaling) { + const readParameterValue = parameter => parameters && parameters[parameter] + ? parameters[parameter].value + : undefined; + if (hybrid) { + return readParameterValue(CP_CAP_AUTOSCALE_HYBRID_FAMILY); + } + return readParameterValue(CP_CAP_AUTOSCALE_INSTANCE_TYPE); + } + return undefined; +} + +export function applyChildNodeInstanceParameters (parameters, value, hybrid) { + if (value && parameters) { + const applyParameter = (parameterName) => { + parameters[parameterName] = { + value + }; + }; + if (hybrid) { + applyParameter(CP_CAP_AUTOSCALE_HYBRID_FAMILY); + } else { + applyParameter(CP_CAP_AUTOSCALE_INSTANCE_TYPE); + } + } +} + +export function applyChildNodeInstanceParametersAsArray (parameters, value, hybrid) { + if (value && parameters) { + const applyParameter = (parameterName) => { + parameters.push({ + name: parameterName, + type: 'string', + value + }); + }; + if (hybrid) { + applyParameter(CP_CAP_AUTOSCALE_HYBRID_FAMILY); + } else { + applyParameter(CP_CAP_AUTOSCALE_INSTANCE_TYPE); + } + } +} + @inject('preferences') @observer class ConfigureClusterDialog extends React.Component { @@ -361,6 +360,7 @@ class ConfigureClusterDialog extends React.Component { autoScaledPriceType: PropTypes.string, hybridAutoScaledClusterEnabled: PropTypes.bool, gpuScalingConfiguration: PropTypes.object, + childNodeInstanceConfiguration: PropTypes.string, nodesCount: PropTypes.number, maxNodesCount: PropTypes.number, onChange: PropTypes.func, @@ -379,6 +379,7 @@ class ConfigureClusterDialog extends React.Component { kubeEnabled: false, hybridAutoScaledClusterEnabled: false, gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined, autoScaledPriceType: undefined, nodesCount: 0, maxNodesCount: 0, @@ -388,7 +389,6 @@ class ConfigureClusterDialog extends React.Component { } }; - @computed get selectedClusterType () { if (this.state.launchCluster) { return this.state.autoScaledCluster && @@ -407,14 +407,61 @@ class ConfigureClusterDialog extends React.Component { } switch (e.target.value) { case CLUSTER_TYPE.fixedCluster: - setFixedClusterMode(this, this.validate); + this.setState({ + launchCluster: true, + autoScaledCluster: false, + setDefaultNodesCount: false, + gridEngineEnabled: false, + sparkEnabled: false, + slurmEnabled: false, + kubeEnabled: false, + autoScaledPriceType: undefined, + hybridAutoScaledClusterEnabled: false, + gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined, + nodesCount: Math.max(1, + !isNaN(this.state.maxNodesCount) + ? this.state.maxNodesCount + : 1 + ) + }, this.validate); break; case CLUSTER_TYPE.autoScaledCluster: - setAutoScaledMode(this, this.validate); + this.setState({ + launchCluster: true, + autoScaledCluster: true, + setDefaultNodesCount: false, + nodesCount: undefined, + gridEngineEnabled: true, + sparkEnabled: false, + slurmEnabled: false, + kubeEnabled: false, + autoScaledPriceType: undefined, + hybridAutoScaledClusterEnabled: false, + gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined, + maxNodesCount: Math.max(1, + !isNaN(this.state.nodesCount) + ? this.state.nodesCount + : 1 + ) + }, this.validate); break; case CLUSTER_TYPE.singleNode: default: - setSingleNodeMode(this, this.validate); + this.setState({ + launchCluster: false, + autoScaledCluster: false, + setDefaultNodesCount: false, + gridEngineEnabled: false, + sparkEnabled: false, + slurmEnabled: false, + kubeEnabled: false, + autoScaledPriceType: undefined, + hybridAutoScaledClusterEnabled: false, + gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined + }, this.validate); break; } }; @@ -469,7 +516,8 @@ class ConfigureClusterDialog extends React.Component { slurmEnabled: false, kubeEnabled: false, hybridAutoScaledClusterEnabled: false, - gpuScalingConfiguration: undefined + gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined }); } }; @@ -481,7 +529,8 @@ class ConfigureClusterDialog extends React.Component { slurmEnabled: false, kubeEnabled: false, hybridAutoScaledClusterEnabled: false, - gpuScalingConfiguration: undefined + gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined }); }; @@ -492,7 +541,8 @@ class ConfigureClusterDialog extends React.Component { sparkEnabled: false, slurmEnabled: e.target.checked, kubeEnabled: false, - gpuScalingConfiguration: undefined + gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined }); } else { this.setState({ @@ -501,7 +551,8 @@ class ConfigureClusterDialog extends React.Component { slurmEnabled: e.target.checked, kubeEnabled: false, hybridAutoScaledClusterEnabled: false, - gpuScalingConfiguration: undefined + gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined }); } }; @@ -513,7 +564,8 @@ class ConfigureClusterDialog extends React.Component { slurmEnabled: false, kubeEnabled: e.target.checked, hybridAutoScaledClusterEnabled: false, - gpuScalingConfiguration: undefined + gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined }); }; @@ -522,7 +574,9 @@ class ConfigureClusterDialog extends React.Component { cloudRegionProvider: provider, preferences } = this.props; - const {gpuScalingConfiguration} = this.state; + const { + gpuScalingConfiguration + } = this.state; const gpuScalingConfigurationEnabled = !!gpuScalingConfiguration; this.setState({ sparkEnabled: false, @@ -530,7 +584,8 @@ class ConfigureClusterDialog extends React.Component { hybridAutoScaledClusterEnabled: e.target.checked, gpuScalingConfiguration: gpuScalingConfigurationEnabled ? getGPUScalingDefaultConfiguration({provider, hybrid: e.target.checked}, preferences) - : undefined + : undefined, + childNodeInstanceConfiguration: undefined }); }; @@ -545,7 +600,8 @@ class ConfigureClusterDialog extends React.Component { this.setState({ gpuScalingConfiguration: e.target.checked ? getGPUScalingDefaultConfiguration({provider: cloudRegionProvider, hybrid}, preferences) - : undefined + : undefined, + childNodeInstanceConfiguration: undefined }); }; @@ -588,15 +644,6 @@ class ConfigureClusterDialog extends React.Component { {renderTooltip(LaunchClusterTooltip.cluster.enableGridEngine)} , - - - Enable Apache Spark - - {renderTooltip(LaunchClusterTooltip.cluster.enableSpark)} - , {renderTooltip(LaunchClusterTooltip.cluster.enableSlurm)} , + + + Enable Apache Spark + + {renderTooltip(LaunchClusterTooltip.cluster.enableSpark)} + , { const {hybridAutoScaledClusterEnabled: hybrid} = this.state; const label = ( - + {gpu ? 'GPU' : 'CPU'} {hybrid ? 'family' : 'instance'}: ); + const { + cloudRegionProvider + } = this.props; return ( - {label} +
+ {label} +
{ hybrid && ( - onChange(e.target.value)} + /> + ) + } + { + !hybrid && ( + + ) + } +
+ ); + }; + + renderChildNodeInstanceParameter = () => { + const { + hybridAutoScaledClusterEnabled: hybrid, + gpuScalingConfiguration, + childNodeInstanceConfiguration: value + } = this.state; + if (gpuScalingConfiguration) { + return null; + } + const { + instanceName, + cloudRegionProvider + } = this.props; + const label = ( + + Workers {hybrid ? 'family' : 'instance'}: + + ); + const onChange = (newValue) => { + this.setState({ + childNodeInstanceConfiguration: newValue + }); + }; + const getEmptyConfigFromString = (str) => ({ + name: str, + tooltip: str + }); + const { + name: emptyName, + tooltip: emptyTooltip + } = (() => { + if (hybrid) { + const family = getInstanceFamilyByName(instanceName, cloudRegionProvider); + if (family) { + return getEmptyConfigFromString(`Master's config - ${family}`) + } + return getEmptyConfigFromString('Master\'s config'); + } + if (instanceName) { + const instance = (this.props.instanceTypes || []).find((o) => o.name === instanceName); + if (instance) { + return { + name: ( +
+ + Master's config + + + - + + {instanceInfoString(instance, {plainText: false})} +
+ ), + tooltip: `Master's config - ${instanceInfoString(instance, {plainText: true})}` + }; + } + return getEmptyConfigFromString(`Master's config - ${instanceName}`); + } + return getEmptyConfigFromString('Master\'s config'); + })(); + return ( + + {label} + { + hybrid && ( + ) } { !hybrid && ( -
- -
+ ) } + {renderTooltip( + hybrid + ? LaunchClusterTooltip.autoScaledCluster.childNodeInstanceFamily + : LaunchClusterTooltip.autoScaledCluster.childNodeInstance, + {marginLeft: 5} + )}
); }; @@ -762,6 +929,32 @@ class ConfigureClusterDialog extends React.Component { )}
); + const renderGPUScalingConfigurationToggle = () => { + const {preferences, cloudRegionProvider} = this.props; + const configuration = getScalingConfigurationForProvider(cloudRegionProvider, preferences); + if (!configuration || this.state.slurmEnabled) { + return []; + } + const { + gpuScalingConfiguration + } = this.state; + return ( + + + Enable GPU scaling + + {renderTooltip(LaunchClusterTooltip.autoScaledCluster.gpuScaling, {marginLeft: 5})} + + ); + }; const renderGPUScalingConfiguration = () => { const {preferences, cloudRegionProvider} = this.props; const configuration = getScalingConfigurationForProvider(cloudRegionProvider, preferences); @@ -771,24 +964,7 @@ class ConfigureClusterDialog extends React.Component { const { gpuScalingConfiguration } = this.state; - const renderers = [ - ( - - - Enable GPU scaling - - {renderTooltip(LaunchClusterTooltip.autoScaledCluster.gpuScaling, {marginLeft: 5})} - - ) - ]; + const renderers = []; if (gpuScalingConfiguration) { const {cpu, gpu} = gpuScalingConfiguration; const onChangeCPU = value => this.setState({ @@ -803,24 +979,6 @@ class ConfigureClusterDialog extends React.Component { return renderers; }; return [ - - - Enable GridEngine - - {renderTooltip(LaunchClusterTooltip.autoScaledCluster.enableGridEngine)} - , - - - Enable Slurm - - {renderTooltip(LaunchClusterTooltip.autoScaledCluster.enableSlurm)} - , Auto-scaled up to: , this.getValidationRow('maxNodesCount'), - + ...renderSetDefaultNodesCount(), + + + Enable GridEngine + + {renderTooltip(LaunchClusterTooltip.autoScaledCluster.enableGridEngine)} + , + + + Enable Slurm + + {renderTooltip(LaunchClusterTooltip.autoScaledCluster.enableSlurm)} + , + {renderTooltip(LaunchClusterTooltip.autoScaledCluster.hybridAutoScaledCluster)} , + renderGPUScalingConfigurationToggle(), + this.renderChildNodeInstanceParameter(), ...renderGPUScalingConfiguration(), - ...renderSetDefaultNodesCount(), this.getValidationRow('nodesCount'), renderAutoScaledPriceTypeSelect() ].filter(r => !!r); @@ -889,6 +1067,7 @@ class ConfigureClusterDialog extends React.Component { gridEngineEnabled: this.state.gridEngineEnabled, hybridAutoScaledClusterEnabled: this.state.hybridAutoScaledClusterEnabled, gpuScalingConfiguration: this.state.gpuScalingConfiguration, + childNodeInstanceConfiguration: this.state.childNodeInstanceConfiguration, sparkEnabled: this.state.sparkEnabled, slurmEnabled: this.state.slurmEnabled, kubeEnabled: this.state.kubeEnabled, @@ -1030,6 +1209,10 @@ class ConfigureClusterDialog extends React.Component { }; updateFromProps () { + const isGPUScalingAvailable = gpuScalingAvailable( + this.props.cloudRegionProvider, + this.props.preferences + ); this.setState({ launchCluster: this.props.launchCluster, autoScaledCluster: this.props.autoScaledCluster, @@ -1039,12 +1222,10 @@ class ConfigureClusterDialog extends React.Component { kubeEnabled: this.props.kubeEnabled, autoScaledPriceType: this.props.autoScaledPriceType, hybridAutoScaledClusterEnabled: this.props.hybridAutoScaledClusterEnabled, - gpuScalingConfiguration: gpuScalingAvailable( - this.props.cloudRegionProvider, - this.props.preferences - ) + gpuScalingConfiguration: isGPUScalingAvailable ? this.props.gpuScalingConfiguration : undefined, + childNodeInstanceConfiguration: this.props.childNodeInstanceConfiguration, setDefaultNodesCount: this.props.nodesCount > 0, nodesCount: this.props.nodesCount && !isNaN(this.props.nodesCount) ? this.props.nodesCount diff --git a/client/src/components/pipelines/launch/form/utilities/parameters.js b/client/src/components/pipelines/launch/form/utilities/parameters.js index 689610d813..e12d2ade3f 100644 --- a/client/src/components/pipelines/launch/form/utilities/parameters.js +++ b/client/src/components/pipelines/launch/form/utilities/parameters.js @@ -22,3 +22,5 @@ export const CP_CAP_DCV_DESKTOP = 'CP_CAP_DCV_DESKTOP'; export const CP_CAP_DCV_WEB = 'CP_CAP_DCV_WEB'; export const CP_CAP_RESCHEDULE_RUN = 'CP_CAP_RESCHEDULE_RUN'; +export const CP_CAP_AUTOSCALE_HYBRID_FAMILY = 'CP_CAP_AUTOSCALE_HYBRID_FAMILY'; +export const CP_CAP_AUTOSCALE_INSTANCE_TYPE = 'CP_CAP_AUTOSCALE_INSTANCE_TYPE'; diff --git a/client/src/components/tools/Tool.js b/client/src/components/tools/Tool.js index e716fd6e26..db0da6ce15 100644 --- a/client/src/components/tools/Tool.js +++ b/client/src/components/tools/Tool.js @@ -2070,7 +2070,7 @@ export default class Tool extends localization.LocalizedReactComponent { return ; } if (this.props.versionSettings.error) { - return ; + return ; } if (!roleModel.readAllowed(this.props.tool.value)) { return ( diff --git a/client/src/components/tools/forms/EditToolForm.js b/client/src/components/tools/forms/EditToolForm.js index 4790623833..9b63982e33 100644 --- a/client/src/components/tools/forms/EditToolForm.js +++ b/client/src/components/tools/forms/EditToolForm.js @@ -56,7 +56,9 @@ import { sparkEnabled, slurmEnabled, kubeEnabled, - getAutoScaledPriceTypeValue + getAutoScaledPriceTypeValue, + applyChildNodeInstanceParametersAsArray, + parseChildNodeInstanceConfiguration } from '../../pipelines/launch/form/utilities/launch-cluster'; import { CP_CAP_LIMIT_MOUNTS, @@ -181,6 +183,7 @@ export default class EditToolForm extends React.Component { autoScaledPriceType: undefined, hybridAutoScaledClusterEnabled: false, gpuScalingConfiguration: undefined, + childNodeInstanceConfiguration: undefined, gridEngineEnabled: false, sparkEnabled: false, slurmEnabled: false, @@ -296,6 +299,12 @@ export default class EditToolForm extends React.Component { } if (this.state.gpuScalingConfiguration) { applyParametersArray(this.state.gpuScalingConfiguration, params); + } else if (this.state.childNodeInstanceConfiguration) { + applyChildNodeInstanceParametersAsArray( + params, + this.state.childNodeInstanceConfiguration, + this.state.hybridAutoScaledClusterEnabled + ); } } if (this.state.launchCluster && this.state.gridEngineEnabled) { @@ -533,6 +542,13 @@ export default class EditToolForm extends React.Component { parameters: props.configuration.parameters }, this.props.preferences) : undefined; + state.childNodeInstanceConfiguration = parseChildNodeInstanceConfiguration({ + gpuScaling: !!state.gpuScalingConfiguration, + autoScaled: state.autoScaledCluster, + hybrid: state.hybridAutoScaledClusterEnabled, + provider, + parameters: props.configuration.parameters + }); state.gridEngineEnabled = props.configuration && gridEngineEnabled(props.configuration.parameters); state.sparkEnabled = props.configuration && sparkEnabled(props.configuration.parameters); state.slurmEnabled = props.configuration && slurmEnabled(props.configuration.parameters); @@ -860,9 +876,16 @@ export default class EditToolForm extends React.Component { autoScaled: autoScaledCluster, hybrid: hybridAutoScaledCluster, provider: this.getCloudProvider(), - parameters: this.props.configuration.parameters + parameters: this.props.configuration ? this.props.configuration.parameters : {} }, this.props.preferences) : undefined; + const childNodeInstanceConfiguration = this.props.configuration + ? parseChildNodeInstanceConfiguration({ + gpuScaling: !!gpuScalingConfiguration, + autoScaled: autoScaledCluster, + hybrid: hybridAutoScaledCluster, + parameters: this.props.configuration.parameters + }) : undefined; const gridEngineEnabledValue = this.props.configuration && gridEngineEnabled(this.props.configuration.parameters); const sparkEnabledValue = this.props.configuration && @@ -899,6 +922,7 @@ export default class EditToolForm extends React.Component { !!autoScaledCluster !== !!this.state.autoScaledCluster || !!hybridAutoScaledCluster !== !!this.state.hybridAutoScaledClusterEnabled || configurationChanged(gpuScalingConfiguration, this.state.gpuScalingConfiguration) || + childNodeInstanceConfiguration !== this.state.childNodeInstanceConfiguration || !!gridEngineEnabledValue !== !!this.state.gridEngineEnabled || !!sparkEnabledValue !== !!this.state.sparkEnabled || !!slurmEnabledValue !== !!this.state.slurmEnabled || @@ -953,6 +977,7 @@ export default class EditToolForm extends React.Component { autoScaledCluster, hybridAutoScaledClusterEnabled, gpuScalingConfiguration, + childNodeInstanceConfiguration, nodesCount, maxNodesCount, gridEngineEnabled, @@ -975,6 +1000,7 @@ export default class EditToolForm extends React.Component { autoScaledCluster, hybridAutoScaledClusterEnabled, gpuScalingConfiguration, + childNodeInstanceConfiguration, maxNodesCount, gridEngineEnabled, sparkEnabled, @@ -1547,6 +1573,7 @@ export default class EditToolForm extends React.Component { autoScaledCluster={this.state.autoScaledCluster} hybridAutoScaledClusterEnabled={this.state.hybridAutoScaledClusterEnabled} gpuScalingConfiguration={this.state.gpuScalingConfiguration} + childNodeInstanceConfiguration={this.state.childNodeInstanceConfiguration} gridEngineEnabled={this.state.gridEngineEnabled} sparkEnabled={this.state.sparkEnabled} slurmEnabled={this.state.slurmEnabled} diff --git a/client/src/utils/instance-family.js b/client/src/utils/instance-family.js new file mode 100644 index 0000000000..419d8db66e --- /dev/null +++ b/client/src/utils/instance-family.js @@ -0,0 +1,68 @@ +/* + * Copyright 2017-2023 EPAM Systems, Inc. (https://www.epam.com/) + * + * Licensed 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. + */ + +function getAWSInstanceFamily (instanceType) { + const e = /^(\w+)\./.exec(instanceType); + if (e && e[1]) { + return e[1]; + } + return undefined; +} + +function getGCPInstanceFamily (instanceType) { + const e = /^(?!custom)(\w+-(?!custom)\w+)-?.*/.exec(instanceType); + if (e && e[1]) { + return e[1]; + } + return undefined; +} + +function getAzureInstanceFamily (instanceType) { + const e = /^([a-zA-Z]+)\d+(.*)/.exec(instanceType); + if (e && e[1] && e[2]) { + return e[1].concat(e[2]); + } + return undefined; +} + +export function getInstanceFamilyByName (instanceName, provider) { + if (!instanceName || !provider) { + return undefined; + } + if (/^aws$/i.test(provider)) { + return getAWSInstanceFamily(instanceName); + } + if (/^gcp$/i.test(provider)) { + return getGCPInstanceFamily(instanceName); + } + if (/^azure$/i.test(provider)) { + return getAzureInstanceFamily(instanceName); + } + return undefined; +} + +export function getInstanceFamily (instance, provider) { + if (!instance) { + return undefined; + } + const { + name + } = instance; + if (!name) { + return undefined; + } + return getInstanceFamilyByName(name, provider); +}