From 263925c9df3a29a921279c6c57f148e1fbd6ceb9 Mon Sep 17 00:00:00 2001 From: Timothy Asir Jeyasingh Date: Tue, 8 Aug 2023 01:37:26 +0530 Subject: [PATCH] Add proper error message when MDR policy creation failure Signed-off-by: Timothy Asir Jeyasingh --- locales/en/plugin__odf-console.json | 10 +- .../create-dr-policy/create-dr-policy.tsx | 5 + .../create-dr-policy/reducer.ts | 1 + .../create-dr-policy/select-cluster-list.tsx | 56 ++++---- .../select-replication-type.tsx | 128 ++++++++++++++---- .../selected-cluster-view.tsx | 34 ++++- .../data-policy/cluster-app-card/cluster.tsx | 8 +- .../data-policy/summary-card/summary-card.tsx | 8 +- .../argo-application-set.tsx | 19 ++- .../subscriptions/target-cluster-selector.tsx | 8 +- packages/mco/constants/acm.ts | 5 + packages/mco/utils/acm.ts | 8 +- 12 files changed, 206 insertions(+), 84 deletions(-) diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json index 596ff134a..5f7a8ede3 100644 --- a/locales/en/plugin__odf-console.json +++ b/locales/en/plugin__odf-console.json @@ -26,6 +26,7 @@ "Policy name": "Policy name", "Connect clusters": "Connect clusters", "Enables mirroring/replication between two selected clusters, ensuring failover or relocation between the two clusters in the event of an outage or planned maintenance.": "Enables mirroring/replication between two selected clusters, ensuring failover or relocation between the two clusters in the event of an outage or planned maintenance.", + "Note: If your cluster isn't visible on this list, verify its import status and refer to the steps outlined in the ACM documentation.": "Note: If your cluster isn't visible on this list, verify its import status and refer to the steps outlined in the ACM documentation.", "Data Foundation {{ version }} or above must be installed on the managed clusters to setup connection for enabling replication/mirroring.": "Data Foundation {{ version }} or above must be installed on the managed clusters to setup connection for enabling replication/mirroring.", "Selected clusters": "Selected clusters", "An error occurred": "An error occurred", @@ -37,10 +38,15 @@ "Select cluster list": "Select cluster list", "Checkbox to select cluster": "Checkbox to select cluster", "Select schedule time format in minutes, hours or days": "Select schedule time format in minutes, hours or days", + "1 or more managed clusters are offline": "1 or more managed clusters are offline", + "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.": "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.", + "Cannot proceed with one or more selected clusters": "Cannot proceed with one or more selected clusters", + "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", "Replication policy": "Replication policy", "Sync schedule": "Sync schedule", - "{{ name }} has either an unsupported ODF version or the ODF operator is missing, install or update to ODF {{ version }} or latest version to enable DR protection.": "{{ name }} has either an unsupported ODF version or the ODF operator is missing, install or update to ODF {{ version }} or latest version to enable DR protection.", - "{{ name }} is not connected to RHCS": "{{ name }} is not connected to RHCS", + "Information unavailable": "Information unavailable", "Validated": "Validated", "Not Validated": "Not Validated", "Application": "Application", diff --git a/packages/mco/components/disaster-recovery/create-dr-policy/create-dr-policy.tsx b/packages/mco/components/disaster-recovery/create-dr-policy/create-dr-policy.tsx index 2845417c8..b27dcb6c8 100644 --- a/packages/mco/components/disaster-recovery/create-dr-policy/create-dr-policy.tsx +++ b/packages/mco/components/disaster-recovery/create-dr-policy/create-dr-policy.tsx @@ -240,6 +240,11 @@ export const CreateDRPolicy: React.FC = ({ dispatch={dispatch} /> + {!!odfMCOVersion && ( ; export type Cluster = { diff --git a/packages/mco/components/disaster-recovery/create-dr-policy/select-cluster-list.tsx b/packages/mco/components/disaster-recovery/create-dr-policy/select-cluster-list.tsx index 9f686a113..99c9b59c0 100644 --- a/packages/mco/components/disaster-recovery/create-dr-policy/select-cluster-list.tsx +++ b/packages/mco/components/disaster-recovery/create-dr-policy/select-cluster-list.tsx @@ -1,5 +1,9 @@ import * as React from 'react'; -import { getMajorVersion, isMinimumSupportedODFVersion } from '@odf/mco/utils'; +import { + getMajorVersion, + getManagedClusterCondition, + isMinimumSupportedODFVersion, +} from '@odf/mco/utils'; import { StatusBox } from '@odf/shared/generic/status-box'; import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; import { referenceForModel } from '@odf/shared/utils'; @@ -27,6 +31,8 @@ import { MANAGED_CLUSTER_REGION_CLAIM, HUB_CLUSTER_NAME, ClusterClaimTypes, + MANAGED_CLUSTER_JOINED, + MANAGED_CLUSTER_CONDITION_AVAILABLE, } from '../../../constants'; import { ACMManagedClusterModel } from '../../../models'; import { ACMManagedClusterKind } from '../../../types'; @@ -81,6 +87,10 @@ const fetchODFInfo = ( storageSystemName: storageSystemNameClaim?.value || '', storageClusterName: storageClusterNameClaim?.value || '', cephFSID: cephFsidClaim?.value || '', + isManagedClusterAvailable: !!getManagedClusterCondition( + cluster, + MANAGED_CLUSTER_CONDITION_AVAILABLE + ), isValidODFVersion: isMinimumSupportedODFVersion( getMajorVersion(odfVersionClaim?.value), requiredODFVersion @@ -140,15 +150,15 @@ export const SelectClusterList: React.FC = ({ !!requiredODFVersion && !acmManagedClustersLoadError ) { - setClusters( - acmManagedClusters?.reduce( - (obj, acmManagedCluster) => [ - ...obj, - getManagedClusterInfo(acmManagedCluster, requiredODFVersion), - ], - [] - ) + const managedClusterInfoList = acmManagedClusters?.reduce( + (acc, cluster) => + !!getManagedClusterCondition(cluster, MANAGED_CLUSTER_JOINED) + ? [...acc, getManagedClusterInfo(cluster, requiredODFVersion)] + : acc, + [] ); + + setClusters(managedClusterInfoList); } }, [ acmManagedClusters, @@ -163,29 +173,13 @@ export const SelectClusterList: React.FC = ({ ); const onSelect: DataListCheckProps['onChange'] = (checked, event) => { - const { - name, - region: clusterRegion, - odfVersion, - storageClusterName, - storageSystemName, - cephFSID, - isValidODFVersion, - } = filteredClusters?.[Number(event.currentTarget.id)]; + const selectedClusterInfo = + filteredClusters?.[Number(event.currentTarget.id)]; const selectedClusterList = checked - ? [ - ...selectedClusters, - { - name, - region: clusterRegion, - cephFSID, - storageSystemName, - storageClusterName, - odfVersion, - isValidODFVersion, - }, - ] - : selectedClusters.filter((cluster) => cluster?.name !== name); + ? [...selectedClusters, selectedClusterInfo] + : selectedClusters.filter( + (cluster) => cluster?.name !== selectedClusterInfo.name + ); dispatch({ type: DRPolicyActionType.SET_SELECTED_CLUSTERS, payload: selectedClusterList, diff --git a/packages/mco/components/disaster-recovery/create-dr-policy/select-replication-type.tsx b/packages/mco/components/disaster-recovery/create-dr-policy/select-replication-type.tsx index c9825a785..2d23ae31a 100644 --- a/packages/mco/components/disaster-recovery/create-dr-policy/select-replication-type.tsx +++ b/packages/mco/components/disaster-recovery/create-dr-policy/select-replication-type.tsx @@ -17,7 +17,12 @@ import { TIME_UNITS, SYNC_SCHEDULE_DISPLAY_TEXT, } from '../../../constants'; -import { DRPolicyState, DRPolicyAction, DRPolicyActionType } from './reducer'; +import { + DRPolicyState, + DRPolicyAction, + DRPolicyActionType, + Cluster, +} from './reducer'; import '../../../style.scss'; type SyncScheduleProps = { @@ -118,6 +123,84 @@ type DRReplicationTypeProps = { dispatch: React.Dispatch; }; +type ErrorMessageType = { + message: string; + description?: string; +}; + +type ClusterErrorType = { + unAvailableClusters: string[]; + clustersWithUnSupportedODF: string[]; + clustersWithoutODF: string[]; + clustersWithUnSuccessfulODF: string[]; +}; + +const getClusterErrorInfo = (selectedClusters: Cluster[]): ClusterErrorType => + selectedClusters.reduce( + (acc, cluster) => { + if (!cluster.isManagedClusterAvailable) { + acc.unAvailableClusters.push(cluster.name); + } + if (!cluster.storageSystemName) { + acc.clustersWithUnSupportedODF.push(cluster.name); + } + if (!cluster.isValidODFVersion) { + acc.clustersWithoutODF.push(cluster.name); + } + if (cluster.cephFSID === '') { + acc.clustersWithUnSuccessfulODF.push(cluster.name); + } + return acc; + }, + { + unAvailableClusters: [], + clustersWithUnSupportedODF: [], + clustersWithoutODF: [], + clustersWithUnSuccessfulODF: [], + } + ); + +const getErrorMessage = ( + selectedClusters: Cluster[], + requiredODFVersion: string, + t +): ErrorMessageType => { + const clusterErrorInfo = getClusterErrorInfo(selectedClusters); + if (!!clusterErrorInfo.unAvailableClusters.length) { + return { + message: t('1 or more managed clusters are offline'), + description: t( + '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.' + ), + }; + } else if (!!clusterErrorInfo.clustersWithUnSupportedODF.length) { + return { + message: t('Cannot proceed with one or more selected clusters'), + description: t( + '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: clusterErrorInfo.clustersWithUnSupportedODF.join(' & ') } + ), + }; + } else if (!!clusterErrorInfo.clustersWithoutODF.length) { + return { + message: t( + '{{ 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: clusterErrorInfo.clustersWithoutODF.join(' & '), + version: requiredODFVersion, + } + ), + }; + } else if (!!clusterErrorInfo.clustersWithUnSuccessfulODF.length) { + return { + message: t('{{ names }} is not connected to RHCS', { + names: clusterErrorInfo.clustersWithUnSuccessfulODF.join(' & '), + }), + }; + } + return null; +}; + export const DRReplicationType: React.FC = ({ state, requiredODFVersion, @@ -125,6 +208,10 @@ export const DRReplicationType: React.FC = ({ }) => { const { t } = useCustomTranslation(); const [isReplicationOpen, setReplicationOpen] = React.useState(false); + const errorMessage = React.useMemo( + () => getErrorMessage(state.selectedClusters, requiredODFVersion, t), + [state.selectedClusters, requiredODFVersion, t] + ); const replicationDropdownItems = React.useMemo( () => @@ -148,19 +235,19 @@ export const DRReplicationType: React.FC = ({ [state.isReplicationInputManual, dispatch, t] ); - const errorMessage = (message: string) => ( - - ); - return ( <> - {state.isODFDetected ? ( + {!!errorMessage ? ( + + {errorMessage?.description} + + ) : ( <> {state.replication && ( = ({ )} - ) : ( - !state.errorMessage && - state.selectedClusters?.map((c) => - !c.isValidODFVersion - ? errorMessage( - t( - '{{ name }} has either an unsupported ODF version or the ODF operator is missing, install or update to ODF {{ version }} or latest version to enable DR protection.', - { name: c?.name, version: requiredODFVersion } - ) - ) - : c.cephFSID === '' && - errorMessage( - t('{{ name }} is not connected to RHCS', { - name: c?.name, - }) - ) - ) )} ); diff --git a/packages/mco/components/disaster-recovery/create-dr-policy/selected-cluster-view.tsx b/packages/mco/components/disaster-recovery/create-dr-policy/selected-cluster-view.tsx index f5fdf7525..8a90fe5db 100644 --- a/packages/mco/components/disaster-recovery/create-dr-policy/selected-cluster-view.tsx +++ b/packages/mco/components/disaster-recovery/create-dr-policy/selected-cluster-view.tsx @@ -1,4 +1,6 @@ import * as React from 'react'; +import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; +import { RedExclamationCircleIcon } from '@openshift-console/dynamic-plugin-sdk'; import { Text, Badge, @@ -21,7 +23,20 @@ export const SelectedCluster: React.FC = ({ cluster, dispatch, // eslint-disable-line @typescript-eslint/no-unused-vars }) => { - const { name, region, storageSystemName } = cluster; + const { + name, + region, + storageSystemName, + isManagedClusterAvailable, + isValidODFVersion, + cephFSID, + } = cluster; + const { t } = useCustomTranslation(); + const anyError = + !isManagedClusterAvailable || + !isValidODFVersion || + !storageSystemName || + !cephFSID; return ( = ({ - {name} - {region} - {storageSystemName} + + {name}   + {!!anyError && } + + {!!storageSystemName ? ( + <> + {region} + {storageSystemName} + + ) : ( + + {t('Information unavailable')} + + )} diff --git a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster.tsx b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster.tsx index 5b967cdec..765a12daf 100644 --- a/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster.tsx +++ b/packages/mco/components/mco-dashboard/data-policy/cluster-app-card/cluster.tsx @@ -16,6 +16,7 @@ import { VOLUME_REPLICATION_HEALTH, OBJECT_NAMESPACE, OBJECT_NAME, + MANAGED_CLUSTER_CONDITION_AVAILABLE, } from '@odf/mco/constants'; import { DrClusterAppsMap, @@ -24,7 +25,7 @@ import { } from '@odf/mco/types'; import { getVolumeReplicationHealth, - getManagedClusterAvailableCondition, + getManagedClusterCondition, } from '@odf/mco/utils'; import { getMax, getMin } from '@odf/shared/charts'; import HealthItem from '@odf/shared/dashboards/status-card/HealthItem'; @@ -129,8 +130,9 @@ export const HealthSection: React.FC = ({ managedCluster?.status?.conditions?.find( (condition) => - condition?.type === 'ManagedClusterConditionAvailable' && + condition?.type === MANAGED_CLUSTER_CONDITION_AVAILABLE && condition.status === 'True' ); diff --git a/packages/mco/constants/acm.ts b/packages/mco/constants/acm.ts index ffb03a8e7..2e907ba3c 100644 --- a/packages/mco/constants/acm.ts +++ b/packages/mco/constants/acm.ts @@ -28,3 +28,8 @@ export enum ClusterClaimTypes { STORAGE_SYSTEM_NAME = 'storagesystemname.odf.openshift.io', CEPH_FSID = 'cephfsid.odf.openshift.io', } + +// Managed cluster status conditions +export const MANAGED_CLUSTER_CONDITION_AVAILABLE = + 'ManagedClusterConditionAvailable'; +export const MANAGED_CLUSTER_JOINED = 'ManagedClusterJoined'; diff --git a/packages/mco/utils/acm.ts b/packages/mco/utils/acm.ts index 37c6dd3a6..85526b10a 100644 --- a/packages/mco/utils/acm.ts +++ b/packages/mco/utils/acm.ts @@ -35,13 +35,13 @@ export const findPlacementDecisionUsingPlacement = ( getNamespace(placementDecision) === getNamespace(placement) ); -export const getManagedClusterAvailableCondition = ( - managedCluster: ACMManagedClusterKind +export const getManagedClusterCondition = ( + managedCluster: ACMManagedClusterKind, + conditionType: string ) => managedCluster?.status?.conditions?.find( (condition) => - condition?.type === 'ManagedClusterConditionAvailable' && - condition.status === 'True' + condition?.type === conditionType && condition.status === 'True' ); export const findSiblingArgoAppSetsFromPlacement = (