diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json index 2800d05f9..fc65f04c8 100644 --- a/locales/en/plugin__odf-console.json +++ b/locales/en/plugin__odf-console.json @@ -622,6 +622,8 @@ "Deploys MultiCloud Object Gateway without block and file services.": "Deploys MultiCloud Object Gateway without block and file services.", "Deploys Data Foundation with block, shared fileSystem and object services.": "Deploys Data Foundation with block, shared fileSystem and object services.", "Deployment type": "Deployment type", + "Set Ceph RBD as the default StorageClass": "Set Ceph RBD as the default StorageClass", + "Configure a default RBD StorageClass to eliminate manual annotations within a StorageClass or selecting a specific StorageClass when making storage requests or provisions in your PVCs.": "Configure a default RBD StorageClass to eliminate manual annotations within a StorageClass or selecting a specific StorageClass when making storage requests or provisions in your PVCs.", "If not labeled, the selected nodes are labeled <2>{{label}} to make them target hosts for Data Foundation's components.": "If not labeled, the selected nodes are labeled <2>{{label}} to make them target hosts for Data Foundation's components.", "Taint nodes": "Taint nodes", "Selected nodes will be dedicated to Data Foundation use only": "Selected nodes will be dedicated to Data Foundation use only", @@ -701,10 +703,12 @@ "{{displayName}} connection details": "{{displayName}} connection details", "Prepare cluster for disaster recovery (Regional-DR only)": "Prepare cluster for disaster recovery (Regional-DR only)", "Set up the storage system for disaster recovery service with the essential configurations in place. This will subsequently allows seamless implementation of the disaster recovery strategies for your workloads.": "Set up the storage system for disaster recovery service with the essential configurations in place. This will subsequently allows seamless implementation of the disaster recovery strategies for your workloads.", + "No": "No", "Not connected": "Not connected", "Backing storage": "Backing storage", "Deployment type: {{deployment}}": "Deployment type: {{deployment}}", "Network file system: {{nfsStatus}}": "Network file system: {{nfsStatus}}", + "Set Ceph RBD as the default StorageClass: {{isCephRBDSetAsDefault}}": "Set Ceph RBD as the default StorageClass: {{isCephRBDSetAsDefault}}", "Backing storage type: {{name}}": "Backing storage type: {{name}}", "External storage platform: {{storagePlatform}}": "External storage platform: {{storagePlatform}}", "Capacity and nodes": "Capacity and nodes", diff --git a/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/backing-storage-step.tsx b/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/backing-storage-step.tsx index ba11fe367..91ca11a25 100644 --- a/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/backing-storage-step.tsx +++ b/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/backing-storage-step.tsx @@ -39,6 +39,7 @@ import { ErrorHandler } from '../../error-handler'; import { WizardState, WizardDispatch } from '../../reducer'; import { EnableNFS } from './enable-nfs'; import { SelectDeployment } from './select-deployment'; +import { SetCephRBDStorageClassDefault } from './set-rbd-sc-default'; import './backing-storage-step.scss'; const RHCS_SUPPORTED_INFRA = [ @@ -181,7 +182,13 @@ export const BackingStorage: React.FC = ({ stepIdReached, supportedExternalStorage, }) => { - const { type, enableNFS, externalStorage, deployment } = state; + const { + type, + enableNFS, + isRBDStorageClassDefault, + externalStorage, + deployment, + } = state; const { t } = useCustomTranslation(); const [sc, scLoaded, scLoadError] = @@ -263,6 +270,10 @@ export const BackingStorage: React.FC = ({ dispatch({ type: 'backingStorage/setType', payload: newType }); }; + const doesDefaultSCAlreadyExists = sc?.items?.some((item) => + isDefaultClass(item) + ); + return ( = ({ /> {isFullDeployment && !hasOCS && ( - + <> + + + )} diff --git a/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/set-rbd-sc-default.spec.tsx b/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/set-rbd-sc-default.spec.tsx new file mode 100644 index 000000000..ad20a29b3 --- /dev/null +++ b/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/set-rbd-sc-default.spec.tsx @@ -0,0 +1,76 @@ +import * as React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SetCephRBDStorageClassDefault } from './set-rbd-sc-default'; + +describe('Setting Ceph RBD StorageClass as default, during installation', () => { + it('renders the FC, on infra with existing default StorageClass', async () => { + const uEvent = userEvent.setup(); + const Wrapper = () => { + const [isRBDStorageClassDefault, dispatch] = React.useState(false); + const dispatchWrapper = ({ payload }) => dispatch(payload); + return ( + + ); + }; + + const { container, rerender } = render(); + const checkbox = container.querySelector( + '[data-test="set-rbd-sc-default"]' + ) as HTMLInputElement; + + expect( + screen.getByText('Set Ceph RBD as the default StorageClass') + ).toBeInTheDocument(); + + // by defaut checkbox should not be checked + expect(checkbox.checked).toBe(false); + + // on clicking, checkbox should get checked + await uEvent.click(checkbox); + expect(checkbox.checked).toBe(true); + + // re-render should not change the checkbox state + rerender(); + expect(checkbox.checked).toBe(true); + }); + + it('renders the FC, on infra with non-existing default StorageClass', async () => { + const uEvent = userEvent.setup(); + const Wrapper = () => { + const [isRBDStorageClassDefault, dispatch] = React.useState(false); + const dispatchWrapper = ({ payload }) => dispatch(payload); + return ( + + ); + }; + + const { container, rerender } = render(); + const checkbox = container.querySelector( + '[data-test="set-rbd-sc-default"]' + ) as HTMLInputElement; + + expect( + screen.getByText('Set Ceph RBD as the default StorageClass') + ).toBeInTheDocument(); + + // by defaut checkbox should be checked + expect(checkbox.checked).toBe(true); + + // on clicking, checkbox should get un-checked + await uEvent.click(checkbox); + expect(checkbox.checked).toBe(false); + + // re-render should not change the checkbox state + rerender(); + expect(checkbox.checked).toBe(false); + }); +}); diff --git a/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/set-rbd-sc-default.tsx b/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/set-rbd-sc-default.tsx new file mode 100644 index 000000000..d3b176ad3 --- /dev/null +++ b/packages/odf/components/create-storage-system/create-storage-system-steps/backing-storage-step/set-rbd-sc-default.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; +import { FormGroup, Checkbox } from '@patternfly/react-core'; +import { WizardDispatch, WizardState } from '../../reducer'; +import './backing-storage-step.scss'; + +export const SetCephRBDStorageClassDefault: React.FC = + ({ dispatch, isRBDStorageClassDefault, doesDefaultSCAlreadyExists }) => { + const { t } = useCustomTranslation(); + + // for infra with already existing "default" SC (eg: say gp3-csi): option should be default unchecked. + // for infra with no "default" SC (BM here): option should be default checked. + React.useEffect(() => { + if (!doesDefaultSCAlreadyExists) { + dispatch({ + type: 'backingStorage/setIsRBDStorageClassDefault', + payload: true, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + dispatch({ + type: 'backingStorage/setIsRBDStorageClassDefault', + payload: !isRBDStorageClassDefault, + }) + } + className="odf-backing-store__radio--margin-bottom" + /> + + ); + }; + +type SetCephRBDStorageClassDefaultProps = { + dispatch: WizardDispatch; + isRBDStorageClassDefault: WizardState['backingStorage']['isRBDStorageClassDefault']; + doesDefaultSCAlreadyExists: boolean; +}; diff --git a/packages/odf/components/create-storage-system/create-storage-system-steps/review-and-create-step/review-and-create-step.tsx b/packages/odf/components/create-storage-system/create-storage-system-steps/review-and-create-step/review-and-create-step.tsx index 394df0fb7..4a3c39be9 100644 --- a/packages/odf/components/create-storage-system/create-storage-system-steps/review-and-create-step/review-and-create-step.tsx +++ b/packages/odf/components/create-storage-system/create-storage-system-steps/review-and-create-step/review-and-create-step.tsx @@ -64,7 +64,13 @@ export const ReviewAndCreate: React.FC = ({ enableSingleReplicaPool, } = capacityAndNodes; const { encryption, kms, networkType } = securityAndNetwork; - const { deployment, externalStorage, type, enableNFS } = backingStorage; + const { + deployment, + externalStorage, + type, + enableNFS, + isRBDStorageClassDefault, + } = backingStorage; // NooBaa standalone deployment const isMCG = deployment === DeploymentType.MCG; @@ -93,6 +99,7 @@ export const ReviewAndCreate: React.FC = ({ ? t('Enabled') : t('Disabled'); const nfsStatus = enableNFS ? t('Enabled') : t('Disabled'); + const isCephRBDSetAsDefault = isRBDStorageClassDefault ? t('Yes') : t('No'); const kmsStatus = encryption.advanced ? kms.providerState.name.value @@ -123,6 +130,16 @@ export const ReviewAndCreate: React.FC = ({ })} )} + {deployment === DeploymentType.FULL && !hasOCS && ( + + {t( + 'Set Ceph RBD as the default StorageClass: {{isCephRBDSetAsDefault}}', + { + isCephRBDSetAsDefault, + } + )} + + )} {!isRhcs && ( {t('Backing storage type: {{name}}', { diff --git a/packages/odf/components/create-storage-system/payloads.ts b/packages/odf/components/create-storage-system/payloads.ts index f4ade29ac..85ef7d28f 100644 --- a/packages/odf/components/create-storage-system/payloads.ts +++ b/packages/odf/components/create-storage-system/payloads.ts @@ -61,7 +61,8 @@ export const createStorageCluster = async (state: WizardState) => { enableSingleReplicaPool, } = capacityAndNodes; const { encryption, publicNetwork, clusterNetwork, kms } = securityAndNetwork; - const { type, enableNFS, deployment } = backingStorage; + const { type, enableNFS, isRBDStorageClassDefault, deployment } = + backingStorage; const { enableRDRPreparation } = dataProtection; const isNoProvisioner = storageClass?.provisioner === NO_PROVISIONER; @@ -88,24 +89,28 @@ export const createStorageCluster = async (state: WizardState) => { deployment === DeploymentType.FULL && type !== BackingStorageType.EXTERNAL; - const payload = getOCSRequestData( + const shouldSetCephRBDAsDefault = + isRBDStorageClassDefault && deployment === DeploymentType.FULL; + + const payload = getOCSRequestData({ storageClass, storage, encryption, isMinimal, nodes, - isFlexibleScaling, + flexibleScaling: isFlexibleScaling, publicNetwork, clusterNetwork, - kms.providerState.hasHandled && encryption.advanced, - arbiterLocation, - enableArbiter, - pvCount, + kmsEnable: kms.providerState.hasHandled && encryption.advanced, + selectedArbiterZone: arbiterLocation, + stretchClusterChecked: enableArbiter, + availablePvsCount: pvCount, isMCG, isNFSEnabled, - enableSingleReplicaPool, - enableRDRPreparation - ); + shouldSetCephRBDAsDefault, + isSingleReplicaPoolEnabled: enableSingleReplicaPool, + enableRDRPreparation, + }); return k8sCreate({ model: OCSStorageClusterModel, data: payload }); }; diff --git a/packages/odf/components/create-storage-system/reducer.ts b/packages/odf/components/create-storage-system/reducer.ts index b2efdd07a..9e66c73b6 100644 --- a/packages/odf/components/create-storage-system/reducer.ts +++ b/packages/odf/components/create-storage-system/reducer.ts @@ -39,6 +39,7 @@ export const initialState: CreateStorageSystemState = { backingStorage: { type: BackingStorageType.EXISTING, enableNFS: false, + isRBDStorageClassDefault: false, externalStorage: '', deployment: DeploymentType.FULL, }, @@ -92,6 +93,7 @@ type CreateStorageSystemState = { backingStorage: { type: BackingStorageType; enableNFS: boolean; + isRBDStorageClassDefault: boolean; externalStorage: string; deployment: DeploymentType; }; @@ -174,6 +176,8 @@ const setDeployment = (state: WizardState, deploymentType: DeploymentType) => { state.backingStorage.deployment = deploymentType; state.backingStorage.enableNFS = initialState.backingStorage.enableNFS; + state.backingStorage.isRBDStorageClassDefault = + initialState.backingStorage.isRBDStorageClassDefault; return state; }; @@ -260,6 +264,9 @@ export const reducer: WizardReducer = (prevState, action) => { case 'backingStorage/enableNFS': newState.backingStorage.enableNFS = action.payload; break; + case 'backingStorage/setIsRBDStorageClassDefault': + newState.backingStorage.isRBDStorageClassDefault = action.payload; + break; case 'backingStorage/setDeployment': return setDeployment(newState, action.payload); case 'backingStorage/setExternalStorage': @@ -352,6 +359,10 @@ export type CreateStorageSystemAction = type: 'backingStorage/enableNFS'; payload: WizardState['backingStorage']['enableNFS']; } + | { + type: 'backingStorage/setIsRBDStorageClassDefault'; + payload: WizardState['backingStorage']['isRBDStorageClassDefault']; + } | { type: 'backingStorage/setExternalStorage'; payload: WizardState['backingStorage']['externalStorage']; diff --git a/packages/odf/components/utils/common.ts b/packages/odf/components/utils/common.ts index 8c73470e5..64bd5e3ce 100644 --- a/packages/odf/components/utils/common.ts +++ b/packages/odf/components/utils/common.ts @@ -350,24 +350,45 @@ export const getDeviceSetReplica = ( const generateNetworkCardName = (resource: NetworkAttachmentDefinitionKind) => `${getNamespace(resource)}/${getName(resource)}`; -export const getOCSRequestData = ( - storageClass: WizardState['storageClass'], - storage: string, - encryption: EncryptionType, - isMinimal: boolean, - nodes: WizardNodeState[], - flexibleScaling = false, - publicNetwork?: NetworkAttachmentDefinitionKind, - clusterNetwork?: NetworkAttachmentDefinitionKind, - kmsEnable?: boolean, - selectedArbiterZone?: string, - stretchClusterChecked?: boolean, - availablePvsCount?: number, - isMCG?: boolean, - isNFSEnabled?: boolean, - isSingleReplicaPoolEnabled?: boolean, - enableRDRPreparation?: boolean -): StorageClusterKind => { +type OCSRequestData = { + storageClass: WizardState['storageClass']; + storage: string; + encryption: EncryptionType; + isMinimal: boolean; + nodes: WizardNodeState[]; + flexibleScaling: boolean; + publicNetwork?: NetworkAttachmentDefinitionKind; + clusterNetwork?: NetworkAttachmentDefinitionKind; + kmsEnable?: boolean; + selectedArbiterZone?: string; + stretchClusterChecked?: boolean; + availablePvsCount?: number; + isMCG?: boolean; + isNFSEnabled?: boolean; + shouldSetCephRBDAsDefault?: boolean; + isSingleReplicaPoolEnabled?: boolean; + enableRDRPreparation?: boolean; +}; + +export const getOCSRequestData = ({ + storageClass, + storage, + encryption, + isMinimal, + nodes, + flexibleScaling, + publicNetwork, + clusterNetwork, + kmsEnable, + selectedArbiterZone, + stretchClusterChecked, + availablePvsCount, + isMCG, + isNFSEnabled, + shouldSetCephRBDAsDefault, + isSingleReplicaPoolEnabled, + enableRDRPreparation, +}: OCSRequestData): StorageClusterKind => { const scName: string = storageClass.name; const isNoProvisioner: boolean = storageClass?.provisioner === NO_PROVISIONER; const isPortable: boolean = flexibleScaling ? false : !isNoProvisioner; @@ -432,6 +453,7 @@ export const getOCSRequestData = ( ), managedResources: { cephNonResilientPools: { enable: isSingleReplicaPoolEnabled }, + cephBlockPools: { defaultStorageClass: shouldSetCephRBDAsDefault }, }, };