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 a66c5e852..6136407a2 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 @@ -24,26 +24,6 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'region.open-cluster-management.io', value: 'us-east-1', }, - { - name: 'cephfsid.odf.openshift.io', - value: 'e6928945-2b95-4ff8-8400-0914ee5cb2ec', - }, - { - 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', - }, ], conditions: [ { @@ -75,26 +55,6 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'region.open-cluster-management.io', value: 'us-west-1', }, - { - name: 'cephfsid.odf.openshift.io', - value: '84eea701-7d87-4f1f-a594-6d0c899327e3', - }, - { - 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', - }, ], conditions: [ { @@ -181,26 +141,6 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'region.open-cluster-management.io', value: 'us-east-3', }, - { - name: 'cephfsid.odf.openshift.io', - value: 'bc2965d7-f9b1-4c08-b8fa-dd4fdfa38f71', - }, - { - 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: '2', - }, ], conditions: [ { @@ -263,26 +203,6 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'region.open-cluster-management.io', value: 'us-east-4', }, - { - name: 'cephfsid.odf.openshift.io', - value: '3e121eef-d363-42fd-93d4-744044b6e4cc', - }, - { - 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.13.0-rhodf', - }, - { - name: 'count.storagecluster.odf.openshift.io', - value: '1', - }, ], conditions: [ { @@ -314,26 +234,6 @@ const managedClusters: ACMManagedClusterKind[] = [ name: 'region.open-cluster-management.io', value: 'us-west-4', }, - { - name: 'cephfsid.odf.openshift.io', - value: '', - }, - { - 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', - }, ], conditions: [ { @@ -365,26 +265,6 @@ const managedClusters: ACMManagedClusterKind[] = [ 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', - }, ], conditions: [ { 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 af74b2f28..e9e13af49 100644 --- a/packages/mco/components/create-dr-policy/select-cluster-list.tsx +++ b/packages/mco/components/create-dr-policy/select-cluster-list.tsx @@ -1,16 +1,22 @@ import * as React from 'react'; import { getManagedClusterResourceObj } from '@odf/mco/hooks'; +import { ODFInfoYamlObject } from '@odf/mco/types'; import { getMajorVersion, ValidateManagedClusterCondition, getValueFromClusterClaim, isMinimumSupportedODFVersion, + getManagedClusterViewName, + getNameNamespace, } from '@odf/mco/utils'; import { StatusBox } from '@odf/shared/generic/status-box'; import { getName, getNamespace } from '@odf/shared/selectors'; +import { ConfigMapKind } from '@odf/shared/types'; import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; +import { referenceForModel } from '@odf/shared/utils'; import { useK8sWatchResource } from '@openshift-console/dynamic-plugin-sdk'; import { Select, SelectOption } from '@patternfly/react-core/deprecated'; +import { safeLoad } from 'js-yaml'; import { DataList, DataListItem, @@ -33,9 +39,11 @@ import { MANAGED_CLUSTER_REGION_CLAIM, MANAGED_CLUSTER_JOINED, MANAGED_CLUSTER_CONDITION_AVAILABLE, - ClusterClaimTypes, + MCO_CREATED_BY_LABEL_KEY, + MCO_CREATED_BY_MC_CONTROLLER, } from '../../constants'; -import { ACMManagedClusterKind } from '../../types'; +import { ACMManagedClusterViewModel } from '../../models'; +import { ACMManagedClusterKind, ACMManagedClusterViewKind } from '../../types'; import { DRPolicyAction, DRPolicyActionType, @@ -59,43 +67,61 @@ const getFilteredClusters = ( }; const getODFInfo = ( - managedCluster: ACMManagedClusterKind, - requiredODFVersion: string + requiredODFVersion: string, + odfInfoConfigData: { [key: string]: string } ): ODFConfigInfoType => { - const clusterClaims = managedCluster?.status?.clusterClaims; - const odfVersion = getValueFromClusterClaim( - clusterClaims, - ClusterClaimTypes.ODF_VERSION - ); - const storageClusterNamespacedName = getValueFromClusterClaim( - clusterClaims, - ClusterClaimTypes.STORAGE_CLUSTER_NAME - ); - const storageSystemNamespacedName = getValueFromClusterClaim( - clusterClaims, - ClusterClaimTypes.STORAGE_SYSTEM_NAME - ); - const cephFsid = getValueFromClusterClaim( - clusterClaims, - ClusterClaimTypes.CEPH_FSID - ); - const storageClusterCount = getValueFromClusterClaim( - clusterClaims, - ClusterClaimTypes.STORAGE_CLUSTER_COUNT - ); - return { - odfVersion: odfVersion, - isValidODFVersion: isMinimumSupportedODFVersion( - getMajorVersion(odfVersion), - requiredODFVersion - ), - storageClusterCount: Number(storageClusterCount || '0'), - storageClusterInfo: { - storageClusterNamespacedName: storageClusterNamespacedName, - storageSystemNamespacedName: storageSystemNamespacedName, - cephFSID: cephFsid, - }, - }; + try { + // Managed cluster with multiple StorageSystems is not currently supported for DR + // ToDo: Update this once we add support for multiple clusters + const odfInfoKey = Object.keys(odfInfoConfigData)[0]; + const odfInfoYaml = odfInfoConfigData[odfInfoKey]; + const odfInfo: ODFInfoYamlObject = safeLoad(odfInfoYaml); + + const storageClusterName = odfInfo?.storageCluster?.namespacedName?.name; + const storageClusterNamespace = + odfInfo?.storageCluster?.namespacedName?.namespace; + const storageSystemName = odfInfo?.storageSystemName; + + const odfVersion = odfInfo?.version; + const storageClusterCount = Object.keys(odfInfoConfigData).length; + const storageClusterNamespacedName = getNameNamespace( + storageClusterName, + storageClusterNamespace + ); + const storageSystemNamespacedName = getNameNamespace( + storageSystemName, + storageClusterNamespace + ); + const cephFSID = odfInfo?.storageCluster?.cephClusterFSID; + + return { + odfVersion, + isValidODFVersion: isMinimumSupportedODFVersion( + getMajorVersion(odfVersion), + requiredODFVersion + ), + storageClusterCount, + storageClusterInfo: { + storageClusterNamespacedName, + storageSystemNamespacedName, + cephFSID, + }, + }; + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + + return { + odfVersion: '', + isValidODFVersion: false, + storageClusterCount: 0, + storageClusterInfo: { + storageClusterNamespacedName: '', + storageSystemNamespacedName: '', + cephFSID: '', + }, + }; + } }; const filterRegions = (filteredClusters: ManagedClusterInfoType[]) => @@ -108,7 +134,8 @@ const filterRegions = (filteredClusters: ManagedClusterInfoType[]) => const getManagedClusterInfo = ( cluster: ACMManagedClusterKind, - requiredODFVersion: string + requiredODFVersion: string, + odfInfoConfigData: { [key: string]: string } ): ManagedClusterInfoType => ({ name: getName(cluster), namesapce: getNamespace(cluster), @@ -120,9 +147,36 @@ const getManagedClusterInfo = ( cluster, MANAGED_CLUSTER_CONDITION_AVAILABLE ), - odfInfo: getODFInfo(cluster, requiredODFVersion), + odfInfo: getODFInfo(requiredODFVersion, odfInfoConfigData), }); +const getManagedClusterInfoTypes = ( + managedClusters: ACMManagedClusterKind[], + mcvs: ACMManagedClusterViewKind[], + requiredODFVersion: string +): ManagedClusterInfoType[] => + managedClusters?.reduce((acc, cluster) => { + if (ValidateManagedClusterCondition(cluster, MANAGED_CLUSTER_JOINED)) { + // OCS creates a ConfigMap on the managed clusters, with details about StorageClusters, Clients. + // MCO creates ManagedClusterView on the hub cluster, referencing that ConfigMap. + const managedClusterName = getName(cluster); + const mcv = + mcvs.find( + (obj: ACMManagedClusterViewKind) => + getName(obj) === getManagedClusterViewName(managedClusterName) && + getNamespace(obj) === managedClusterName + ) || {}; + const odfInfoConfigData = + (mcv.status?.result as ConfigMapKind)?.data || {}; + return [ + ...acc, + getManagedClusterInfo(cluster, requiredODFVersion, odfInfoConfigData), + ]; + } + + return acc; + }, []); + const isChecked = (clusters: ManagedClusterInfoType[], clusterName: string) => clusters?.some((cluster) => cluster?.name === clusterName); @@ -149,18 +203,30 @@ export const SelectClusterList: React.FC = ({ ACMManagedClusterKind[] >(getManagedClusterResourceObj()); + const [mcvs, mcvsLoaded, mcvsLoadError] = useK8sWatchResource< + ACMManagedClusterViewKind[] + >({ + kind: referenceForModel(ACMManagedClusterViewModel), + selector: { + // https://github.com/red-hat-storage/odf-multicluster-orchestrator/blob/release-4.17/controllers/utils/managedclusterview.go#L43 + matchLabels: { [MCO_CREATED_BY_LABEL_KEY]: MCO_CREATED_BY_MC_CONTROLLER }, + }, + isList: true, + }); + + const allLoaded = loaded && mcvsLoaded; + const anyError = loadError || mcvsLoadError; + const clusters: ManagedClusterInfoType[] = React.useMemo(() => { - if (!!requiredODFVersion && loaded && !loadError) { - return managedClusters?.reduce( - (acc, cluster) => - ValidateManagedClusterCondition(cluster, MANAGED_CLUSTER_JOINED) - ? [...acc, getManagedClusterInfo(cluster, requiredODFVersion)] - : acc, - [] + if (!!requiredODFVersion && allLoaded && !anyError) + return getManagedClusterInfoTypes( + managedClusters, + mcvs, + requiredODFVersion ); - } + return []; - }, [requiredODFVersion, managedClusters, loaded, loadError]); + }, [requiredODFVersion, managedClusters, mcvs, allLoaded, anyError]); const filteredClusters: ManagedClusterInfoType[] = React.useMemo( () => getFilteredClusters(clusters, region, nameSearch), @@ -245,8 +311,8 @@ export const SelectClusterList: React.FC = ({ - // Multiple namespace can have recipe with same name, - // Converting into name/namespace for unique identification in dropdown option - !!name && !!namespace - ? `${name}${NAME_NAMESPACE_SPLIT_CHAR}${namespace}` - : ''; - const getRecipeOptions = ( searchResultItem: SearchResultItemType[] ): JSX.Element[] => @@ -74,6 +66,8 @@ export const RecipeSelection: React.FC = ({ ? getRecipeOptions(searchResult?.data.searchResult?.[0]?.items || []) : []; + // Multiple namespace can have recipe with same name, + // Converting into name/namespace for unique identification in dropdown option const recipeNameNamespace = getNameNamespace(recipeName, recipeNamespace); // For no recipe found has different validation message // For recipe not selected has different validation message diff --git a/packages/mco/constants/acm.ts b/packages/mco/constants/acm.ts index 5e3c78f41..09061b2d7 100644 --- a/packages/mco/constants/acm.ts +++ b/packages/mco/constants/acm.ts @@ -36,15 +36,6 @@ export const APPLICATION_TYPE_DISPLAY_TEXT = ( [APPLICATION_TYPE.DISCOVERED]: t('Discovered'), }); -// Please refer to clusterclaims.go in github.com/red-hat-storage/ocs-operator before changing anything here -export enum ClusterClaimTypes { - ODF_VERSION = 'version.odf.openshift.io', - STORAGE_CLUSTER_NAME = 'storageclustername.odf.openshift.io', - STORAGE_SYSTEM_NAME = 'storagesystemname.odf.openshift.io', - CEPH_FSID = 'cephfsid.odf.openshift.io', - STORAGE_CLUSTER_COUNT = 'count.storagecluster.odf.openshift.io', -} - // Managed cluster status conditions export const MANAGED_CLUSTER_CONDITION_AVAILABLE = 'ManagedClusterConditionAvailable'; diff --git a/packages/mco/constants/common.ts b/packages/mco/constants/common.ts index c911e3ef7..b02a2dc6f 100644 --- a/packages/mco/constants/common.ts +++ b/packages/mco/constants/common.ts @@ -21,3 +21,5 @@ export const ADMIN_FLAG = 'ADMIN'; // Discovered application namespace export const DISCOVERED_APP_NS = 'openshift-dr-ops'; + +export const NAME_NAMESPACE_SPLIT_CHAR = '/'; diff --git a/packages/mco/constants/disaster-recovery.ts b/packages/mco/constants/disaster-recovery.ts index 6ec80616f..67bcedcfc 100644 --- a/packages/mco/constants/disaster-recovery.ts +++ b/packages/mco/constants/disaster-recovery.ts @@ -104,3 +104,10 @@ export const EnrollDiscoveredApplicationStepNames = (t: TFunction) => ({ [EnrollDiscoveredApplicationSteps.Replication]: t('Replication'), [EnrollDiscoveredApplicationSteps.Review]: t('Review'), }); + +export const MCV_NAME_TEMPLATE = 'odf-multicluster-mcv-'; + +export const MCO_CREATED_BY_LABEL_KEY = + 'multicluster.odf.openshift.io/created-by'; +export const MCO_CREATED_BY_MC_CONTROLLER = + 'odf-multicluster-managedcluster-controller'; diff --git a/packages/mco/types/odf-mco.ts b/packages/mco/types/odf-mco.ts index 92036b7b2..3993dc479 100644 --- a/packages/mco/types/odf-mco.ts +++ b/packages/mco/types/odf-mco.ts @@ -13,3 +13,25 @@ export type MirrorPeerKind = K8sResourceCommon & { type: string; }; }; + +export type ConnectedClient = { + name: string; + clusterId: string; +}; + +export type InfoStorageCluster = { + namespacedName: { + name: string; + namespace: string; + }; + storageProviderEndpoint: string; + cephClusterFSID: string; +}; + +export type ODFInfoYamlObject = { + version: string; + deploymentType: string; + clients: ConnectedClient[]; + storageCluster: InfoStorageCluster; + storageSystemName: string; +}; diff --git a/packages/mco/utils/disaster-recovery.tsx b/packages/mco/utils/disaster-recovery.tsx index 3f47e948a..5c09e68c8 100644 --- a/packages/mco/utils/disaster-recovery.tsx +++ b/packages/mco/utils/disaster-recovery.tsx @@ -34,6 +34,8 @@ import { LABELS_SPLIT_CHAR, DR_BLOCK_LISTED_LABELS, PLACEMENT_RULE_REF_LABEL, + MCV_NAME_TEMPLATE, + NAME_NAMESPACE_SPLIT_CHAR, } from '../constants'; import { DRPC_NAMESPACE_ANNOTATION, @@ -630,6 +632,11 @@ export const getValueFromClusterClaim = ( export const parseNamespaceName = (namespaceName: string) => namespaceName.split('/'); +export const getNameNamespace = (name: string, namespace: string) => + !!name && !!namespace + ? `${name}${NAME_NAMESPACE_SPLIT_CHAR}${namespace}` + : ''; + export const getLabelsFromSearchResult = ( item: SearchResultItemType ): { [key in string]: string[] } => { @@ -643,3 +650,6 @@ export const getLabelsFromSearchResult = ( return acc; }, {}); }; + +export const getManagedClusterViewName = (managedClusterName: string): string => + MCV_NAME_TEMPLATE + managedClusterName;