From 5bb99770fe4e35206853f1fdb4cdfc66eaa046fd Mon Sep 17 00:00:00 2001 From: Gowtham Shanmugasundaram Date: Tue, 12 Dec 2023 18:15:05 +0530 Subject: [PATCH] Block RDR policy creation for none DR optimized cluster Signed-off-by: Gowtham Shanmugasundaram --- locales/en/plugin__odf-console.json | 2 + .../create-dr-policy.spec.tsx | 142 +++++++++++++++++- .../create-dr-policy/create-dr-policy.tsx | 12 +- .../components/create-dr-policy/reducer.ts | 2 + .../create-dr-policy/select-cluster-list.tsx | 5 + .../select-replication-type.tsx | 28 +++- .../selected-cluster-view.tsx | 37 +++-- packages/mco/constants/acm.ts | 1 + 8 files changed, 211 insertions(+), 18 deletions(-) diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json index 734c32e8c..f8e208225 100644 --- a/locales/en/plugin__odf-console.json +++ b/locales/en/plugin__odf-console.json @@ -42,6 +42,8 @@ "We could not retrieve any information about the managed cluster {{names}}. Check the documentation for potential causes and follow the steps mentioned and try again.": "We could not retrieve any information about the managed cluster {{names}}. Check the documentation for potential causes and follow the steps mentioned and try again.", "{{ names }} has either an unsupported ODF version or the ODF operator is missing, install or update to ODF {{ version }} or the latest version to enable DR protection.": "{{ names }} has either an unsupported ODF version or the ODF operator is missing, install or update to ODF {{ version }} or the latest version to enable DR protection.", "{{ names }} is not connected to RHCS": "{{ names }} is not connected to RHCS", + "Cluster not pre-configured for Regional-DR": "Cluster not pre-configured for Regional-DR", + "The selected cluster(s)[{{clusters}}] is not pre-configured for a Regional-DR setup. Migrate the cluster's OSD to optimise it for Disaster recovery services. To learn more about OSDs migration best practices and its consequences refer to the documentation.": "The selected cluster(s)[{{clusters}}] is not pre-configured for a Regional-DR setup. Migrate the cluster's OSD to optimise it for Disaster recovery services. To learn more about OSDs migration best practices and its consequences refer to the documentation.", "Sync schedule": "Sync schedule", "Replication policy": "Replication policy", "Information unavailable": "Information unavailable", diff --git a/packages/mco/components/create-dr-policy/create-dr-policy.spec.tsx b/packages/mco/components/create-dr-policy/create-dr-policy.spec.tsx index da6d535ba..b7b8d4272 100644 --- a/packages/mco/components/create-dr-policy/create-dr-policy.spec.tsx +++ b/packages/mco/components/create-dr-policy/create-dr-policy.spec.tsx @@ -44,6 +44,10 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'count.storagecluster.odf.openshift.io', value: '1', }, + { + name: 'droptimized.odf.openshift.io', + value: 'true', + }, ], conditions: [ { @@ -95,6 +99,10 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'count.storagecluster.odf.openshift.io', value: '1', }, + { + name: 'droptimized.odf.openshift.io', + value: 'true', + }, ], conditions: [ { @@ -201,6 +209,10 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'count.storagecluster.odf.openshift.io', value: '2', }, + { + name: 'droptimized.odf.openshift.io', + value: 'true', + }, ], conditions: [ { @@ -283,6 +295,10 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'count.storagecluster.odf.openshift.io', value: '1', }, + { + name: 'droptimized.odf.openshift.io', + value: 'true', + }, ], conditions: [ { @@ -334,6 +350,65 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'count.storagecluster.odf.openshift.io', value: '1', }, + { + name: 'droptimized.odf.openshift.io', + value: 'true', + }, + ], + conditions: [ + { + type: 'ManagedClusterJoined', + lastTransitionTime: '2023-11-29T04:30:13Z', + message: 'Managed cluster joined', + reason: 'ManagedClusterJoined', + status: 'True', + }, + { + lastTransitionTime: '2023-11-29T04:30:13Z', + message: 'Managed cluster is available', + reason: 'ManagedClusterAvailable', + status: 'True', + type: 'ManagedClusterConditionAvailable', + }, + ], + }, + }, + { + apiVersion: 'cluster.open-cluster-management.io/v1', + kind: 'ManagedCluster', + metadata: { + name: 'east-5', + }, + status: { + clusterClaims: [ + { + name: 'region.open-cluster-management.io', + value: 'us-east-5', + }, + { + name: 'cephfsid.odf.openshift.io', + value: 'c1ea826f-1dc2-4faa-87b2-f0fc4665b11a', + }, + { + name: 'storageclustername.odf.openshift.io', + value: 'ocs-storagecluster/openshift-storage', + }, + { + name: 'storagesystemname.odf.openshift.io', + value: 'ocs-storagecluster-storagesystem/openshift-storage', + }, + { + name: 'version.odf.openshift.io', + value: '4.14.0-rhodf', + }, + { + name: 'count.storagecluster.odf.openshift.io', + value: '1', + }, + { + name: 'droptimized.odf.openshift.io', + value: 'false', + }, ], conditions: [ { @@ -466,7 +541,7 @@ describe('Test drpolicy list page', () => { expect(screen.getByText('Sync schedule')).toBeInTheDocument(); expect(screen.getByText('minutes')).toBeInTheDocument(); - // Create button should be disabled + // Create button should be enabled expect(screen.getByTestId('create-button')).toBeEnabled(); await waitFor(() => fireEvent.click(screen.getByTestId('create-button'))); expect( @@ -481,6 +556,11 @@ describe('Test drpolicy list page', () => { test('Partially imported cluster test', async () => { let nonExist = false; + // Enter policy name + expect(screen.getByText('Policy name')).toBeInTheDocument(); + fireEvent.change(screen.getByTestId('policy-name'), { + target: { value: 'policy-1' }, + }); try { // east-2 is partially imported cluster screen.getByText('east-2'); @@ -488,9 +568,16 @@ describe('Test drpolicy list page', () => { nonExist = true; } expect(nonExist).toBe(true); + // Create button should be disabled + expect(screen.getByTestId('create-button')).toBeDisabled(); }); test('Down cluser selection test', async () => { + // Enter policy name + expect(screen.getByText('Policy name')).toBeInTheDocument(); + fireEvent.change(screen.getByTestId('policy-name'), { + target: { value: 'policy-1' }, + }); // Select west-2 down cluster await waitFor(() => fireEvent.click(screen.getByTestId('west-2'))); // Error message for down cluster @@ -502,14 +589,28 @@ describe('Test drpolicy list page', () => { 'The status for both the managed clusters must be available for creating a DR policy. To restore a cluster to an available state, refer to the instructions in the ACM documentation.' ) ).toBeInTheDocument(); + // Create button should be disabled + expect(screen.getByTestId('create-button')).toBeDisabled(); }); test('Multiple ODF cluster selection test', async () => { + // Enter policy name + expect(screen.getByText('Policy name')).toBeInTheDocument(); + fireEvent.change(screen.getByTestId('policy-name'), { + target: { value: 'policy-1' }, + }); // East-3 multiple ODF cluster expect(screen.getByTestId('east-3')).toBeDisabled(); + // Create button should be disabled + expect(screen.getByTestId('create-button')).toBeDisabled(); }); test('Non ODF cluster selection test', async () => { + // Enter policy name + expect(screen.getByText('Policy name')).toBeInTheDocument(); + fireEvent.change(screen.getByTestId('policy-name'), { + target: { value: 'policy-1' }, + }); // Select west-3 non ODF cluster await waitFor(() => fireEvent.click(screen.getByTestId('west-3'))); // Error message for non ODF detection @@ -521,18 +622,32 @@ describe('Test drpolicy list page', () => { 'We could not retrieve any information about the managed cluster {{names}}. Check the documentation for potential causes and follow the steps mentioned and try again.' ) ).toBeInTheDocument(); + // Create button should be disabled + expect(screen.getByTestId('create-button')).toBeDisabled(); }); test('Select partially deployed ODF cluster test', async () => { + // Enter policy name + expect(screen.getByText('Policy name')).toBeInTheDocument(); + fireEvent.change(screen.getByTestId('policy-name'), { + target: { value: 'policy-1' }, + }); // Select west-4 ceph cluster creation inprogress await waitFor(() => fireEvent.click(screen.getByTestId('west-4'))); // Error message for cephFSID not found expect( screen.getByText('{{ names }} is not connected to RHCS') ).toBeInTheDocument(); + // Create button should be disabled + expect(screen.getByTestId('create-button')).toBeDisabled(); }); test('Select unsupported ODF version cluster test', async () => { + // Enter policy name + expect(screen.getByText('Policy name')).toBeInTheDocument(); + fireEvent.change(screen.getByTestId('policy-name'), { + target: { value: 'policy-1' }, + }); // Select east-4 unsupported ODF operator version await waitFor(() => fireEvent.click(screen.getByTestId('east-4'))); // Error message for unsupported ODF version @@ -541,5 +656,30 @@ describe('Test drpolicy list page', () => { '{{ names }} has either an unsupported ODF version or the ODF operator is missing, install or update to ODF {{ version }} or the latest version to enable DR protection.' ) ).toBeInTheDocument(); + // Create button should be disabled + expect(screen.getByTestId('create-button')).toBeDisabled(); + }); + + test('Select non-DR optimized cluster test', async () => { + // Enter policy name + expect(screen.getByText('Policy name')).toBeInTheDocument(); + fireEvent.change(screen.getByTestId('policy-name'), { + target: { value: 'policy-1' }, + }); + // Select east-1 DR optimized ODF + await waitFor(() => fireEvent.click(screen.getByTestId('east-1'))); + // Select east-5 none DR optimized ODF + await waitFor(() => fireEvent.click(screen.getByTestId('east-5'))); + // Error message for not DR optimized + expect( + screen.getByText('Cluster not pre-configured for Regional-DR') + ).toBeInTheDocument(); + expect( + screen.getByText( + "The selected cluster(s)[{{clusters}}] is not pre-configured for a Regional-DR setup. Migrate the cluster's OSD to optimise it for Disaster recovery services. To learn more about OSDs migration best practices and its consequences refer to the documentation." + ) + ).toBeInTheDocument(); + // Create button should be disabled + expect(screen.getByTestId('create-button')).toBeDisabled(); }); }); diff --git a/packages/mco/components/create-dr-policy/create-dr-policy.tsx b/packages/mco/components/create-dr-policy/create-dr-policy.tsx index fd03c6c2c..add9b263b 100644 --- a/packages/mco/components/create-dr-policy/create-dr-policy.tsx +++ b/packages/mco/components/create-dr-policy/create-dr-policy.tsx @@ -40,7 +40,7 @@ import { } from './reducer'; import { SelectClusterList } from './select-cluster-list'; import { DRReplicationType } from './select-replication-type'; -import { SelectedCluster } from './selected-cluster-view'; +import { SelectedCluster, checkForErrors } from './selected-cluster-view'; import './create-dr-policy.scss'; import '../../style.scss'; @@ -179,7 +179,8 @@ export const CreateDRPolicy: React.FC<{}> = () => { const areDRPolicyInputsValid = () => !!state.policyName && !!state.replicationType && - state.selectedClusters.length === MAX_ALLOWED_CLUSTERS; + state.selectedClusters.length === MAX_ALLOWED_CLUSTERS && + !checkForErrors(state.selectedClusters, state.replicationType); return (
@@ -239,7 +240,12 @@ export const CreateDRPolicy: React.FC<{}> = () => { {!!state.selectedClusters.length && ( {state.selectedClusters.map((c, i) => ( - + ))} )} diff --git a/packages/mco/components/create-dr-policy/reducer.ts b/packages/mco/components/create-dr-policy/reducer.ts index 38017b790..94b9d4967 100644 --- a/packages/mco/components/create-dr-policy/reducer.ts +++ b/packages/mco/components/create-dr-policy/reducer.ts @@ -7,6 +7,8 @@ export type StorageClusterInfoType = { storageSystemNamespacedName: string; // Ceph FSID to determine RDR/MDR. cephFSID: string; + // OSDs are migrated for the RDR or not. + isDROptimized: boolean; }; export type ODFConfigInfoType = { diff --git a/packages/mco/components/create-dr-policy/select-cluster-list.tsx b/packages/mco/components/create-dr-policy/select-cluster-list.tsx index 4d9e9b3b8..9579b1463 100644 --- a/packages/mco/components/create-dr-policy/select-cluster-list.tsx +++ b/packages/mco/components/create-dr-policy/select-cluster-list.tsx @@ -84,6 +84,10 @@ const getODFInfo = ( clusterClaims, ClusterClaimTypes.STORAGE_CLUSTER_COUNT ); + const isDROptimized = getValueFromClusterClaim( + clusterClaims, + ClusterClaimTypes.DR_OPTIMIZED + ); return { odfVersion: odfVersion, isValidODFVersion: isMinimumSupportedODFVersion( @@ -95,6 +99,7 @@ const getODFInfo = ( storageClusterNamespacedName: storageClusterNamespacedName, storageSystemNamespacedName: storageSystemNamespacedName, cephFSID: cephFsid, + isDROptimized: isDROptimized === 'true', }, }; }; diff --git a/packages/mco/components/create-dr-policy/select-replication-type.tsx b/packages/mco/components/create-dr-policy/select-replication-type.tsx index 8b3b8d120..d954c02f2 100644 --- a/packages/mco/components/create-dr-policy/select-replication-type.tsx +++ b/packages/mco/components/create-dr-policy/select-replication-type.tsx @@ -4,6 +4,7 @@ import { SingleSelectDropdown } from '@odf/shared/dropdown'; import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; import { RequestSizeInput } from '@odf/shared/utils/RequestSizeInput'; import { SelectOption } from '@patternfly/react-core/next'; +import { TFunction } from 'i18next'; import { FormGroup, Alert, AlertVariant } from '@patternfly/react-core'; import { REPLICATION_TYPE, @@ -42,6 +43,9 @@ const getClusterErrorInfo = ( if (!storageClusterInfo?.cephFSID) { acc.clustersWithUnsuccessfulODF.push(cluster.name); } + if (!storageClusterInfo?.isDROptimized) { + acc.clustersWithoutDROptimizedODF.push(cluster.name); + } return acc; }, { @@ -49,13 +53,15 @@ const getClusterErrorInfo = ( clustersWithUnsupportedODF: [], clustersWithoutODF: [], clustersWithUnsuccessfulODF: [], + clustersWithoutDROptimizedODF: [], } ); const getErrorMessage = ( selectedClusters: ManagedClusterInfoType[], requiredODFVersion: string, - t + replicationType: REPLICATION_TYPE, + t: TFunction ): ErrorMessageType => { const clusterErrorInfo = getClusterErrorInfo(selectedClusters); if (!!clusterErrorInfo.unavailableClusters.length) { @@ -89,6 +95,15 @@ const getErrorMessage = ( names: clusterErrorInfo.clustersWithUnsuccessfulODF.join(' & '), }), }; + } else if (!!clusterErrorInfo.clustersWithoutDROptimizedODF.length) { + return { + message: t('Cluster not pre-configured for Regional-DR'), + description: t( + "The selected cluster(s)[{{clusters}}] is not pre-configured for a Regional-DR setup. Migrate the cluster's OSD to optimise it for Disaster recovery services. To learn more about OSDs migration best practices and its consequences refer to the documentation.", + { clusters: clusterErrorInfo.clustersWithoutDROptimizedODF.join(', ') } + ), + isHidden: replicationType !== REPLICATION_TYPE.ASYNC, + }; } return null; }; @@ -130,7 +145,12 @@ export const DRReplicationType: React.FC = ({ dispatch, }) => { const { t } = useCustomTranslation(); - const errorMessage = getErrorMessage(selectedClusters, requiredODFVersion, t); + const errorMessage = getErrorMessage( + selectedClusters, + requiredODFVersion, + replicationType, + t + ); React.useEffect(() => { if (selectedClusters.length === 2) { @@ -174,7 +194,7 @@ export const DRReplicationType: React.FC = ({ return ( <> - {!!errorMessage ? ( + {!!errorMessage && !errorMessage.isHidden ? ( + clusters.some((cluster) => { + const { isManagedClusterAvailable, odfInfo } = cluster; + const { cephFSID, isDROptimized, storageSystemNamespacedName } = + odfInfo.storageClusterInfo; + const [storageSystemName] = parseNamespaceName(storageSystemNamespacedName); + return ( + !isManagedClusterAvailable || + !odfInfo?.isValidODFVersion || + !storageSystemName || + !cephFSID || + (replicationType === REPLICATION_TYPE.ASYNC && !isDROptimized) + ); + }); + export const SelectedCluster: React.FC = ({ id, cluster, + replicationType, }) => { - const { name, region, isManagedClusterAvailable, odfInfo } = cluster; const { t } = useCustomTranslation(); - const { cephFSID, storageSystemNamespacedName } = - odfInfo?.storageClusterInfo || {}; - const [storageSystemName] = !!storageSystemNamespacedName - ? parseNamespaceName(storageSystemNamespacedName) - : []; - const anyError = - !isManagedClusterAvailable || - !odfInfo?.isValidODFVersion || - !storageSystemName || - !cephFSID; + const { name, region, odfInfo } = cluster; + const [storageSystemName] = parseNamespaceName( + odfInfo.storageClusterInfo.storageSystemNamespacedName + ); + const anyError = checkForErrors([cluster], replicationType); return (