Repository: red-hat-storage/odf-console
Pull request #1923: RHSTOR-7113: Disable DR for Standalone and Shared VMs
Author: login: GowthamShanmugam
Labels: name: approved
name: lgtm
name: jira/valid-reference
description: Indicates that this PR references a valid jira ticket of any type
Created: 2025-03-26T14:20:36Z | Merged: 2025-03-27T10:22:37Z

### **Disable DR Behavior for VMs**  

We have disabled Disaster Recovery (DR) for **both** managed and discovered VMs.  

#### **Managed VMs**  
- The behavior is the same as managed applications.  
- DR is fully disabled when required.  

#### **Discovered VMs**  
The behavior differs slightly from discovered applications:  

1. **Discovered Applications**  
   - DR is disabled by deleting the **DRPC (DRPlacementControl)** and the **dummy placement**.  

2. **Discovered VMs** (Classified into Shared and Standalone types)  
   - **Shared VMs** (VMs belonging to a group):  
     - **Do not delete DRPC** unless it's the last VM in the group.  
     - Remove the **ramen unique label**.  from VM and its PVCs.
     - Remove the VM from the **DRPC VM list**.  
   - **Standalone VMs** (Independent VMs):  
     - Remove all relevant **labels** from VM and its PVCs.
     - **Delete the DRPC**.  
     - **Delete the dummy placement**.  


Diff:
diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json
index 893203977..295c175f5 100644
--- a/locales/en/plugin__odf-console.json
+++ b/locales/en/plugin__odf-console.json
@@ -420,13 +420,13 @@
   "Label selector:": "Label selector:",
   "Recipe name: {{recipeName}}": "Recipe name: {{recipeName}}",
   "Recipe namespace: {{recipeNamespace}}": "Recipe namespace: {{recipeNamespace}}",
+  "Replication details": "Replication details",
+  "Volume:": "Volume:",
+  "Kubernetes object:": "Kubernetes object:",
   "Volume group replication: ": "Volume group replication: ",
   "Enabled": "Enabled",
   "Disabled": "Disabled",
   "View volume groups": "View volume groups",
-  "Replication details": "Replication details",
-  "Volume:": "Volume:",
-  "Kubernetes object:": "Kubernetes object:",
   "Confirm remove": "Confirm remove",
   "Go back": "Go back",
   "Enroll ACM managed application": "Enroll ACM managed application",
diff --git a/packages/mco/components/modals/app-manage-policies/assign-policy-view.tsx b/packages/mco/components/modals/app-manage-policies/assign-policy-view.tsx
index 97c457bb8..506a90d7e 100644
--- a/packages/mco/components/modals/app-manage-policies/assign-policy-view.tsx
+++ b/packages/mco/components/modals/app-manage-policies/assign-policy-view.tsx
@@ -110,7 +110,6 @@ export const createSteps = ({
           policy={state.replication.policy}
           k8sResourceSyncInterval={state.replication.k8sSyncInterval}
           isValidationEnabled={isValidationEnabled}
-          pvcQueryFilter={pvcQueryFilter}
           dispatch={dispatch}
           isSharedVMProtection={
             state.protectionType.protectionType === VMProtectionType.SHARED
@@ -222,6 +221,7 @@ export const AssignPolicyView: React.FC<AssignPolicyViewProps> = ({
     drInfo,
     pvcQueryFilter,
     workloadNamespace,
+    discoveredVMPVCs,
   } = applicationInfo;
 
   const protectedPVCSelectors: PVCSelectorType[] = isEditMode
@@ -246,7 +246,8 @@ export const AssignPolicyView: React.FC<AssignPolicyViewProps> = ({
         appType,
         workloadNamespace,
         getName(applicationInfo),
-        t
+        t,
+        discoveredVMPVCs
       );
 
       // Success actions
diff --git a/packages/mco/components/modals/app-manage-policies/helper/replication-wizard-content.tsx b/packages/mco/components/modals/app-manage-policies/helper/replication-wizard-content.tsx
index 356010013..9925a19bb 100644
--- a/packages/mco/components/modals/app-manage-policies/helper/replication-wizard-content.tsx
+++ b/packages/mco/components/modals/app-manage-policies/helper/replication-wizard-content.tsx
@@ -3,9 +3,6 @@ import {
   PolicyInfo,
   ReplicationSelectionHelper,
 } from '@odf/mco/components/discovered-application-wizard/wizard-steps/replication-step/replication-selection-helper';
-import { useACMSafeFetch } from '@odf/mco/hooks';
-import { queryAppWorkloadPVCs } from '@odf/mco/utils';
-import { StatusBox } from '@odf/shared';
 import { getName } from '@odf/shared/selectors';
 import * as _ from 'lodash-es';
 import {
@@ -13,7 +10,7 @@ import {
   ManagePolicyStateType,
   ModalViewContext,
 } from '../utils/reducer';
-import { DRPolicyType, PVCQueryFilter } from '../utils/types';
+import { DRPolicyType } from '../utils/types';
 import { findPolicy } from './select-policy-wizard-content';
 
 // Convert DRPolicyType to PolicyInfo
@@ -34,33 +31,9 @@ export const ReplicationTypeWizardContent: React.FC<
   k8sResourceSyncInterval,
   matchingPolicies,
   isValidationEnabled,
-  pvcQueryFilter,
   dispatch,
   isSharedVMProtection,
 }) => {
-  // TODO: Temporary place for the PVC search API in Discovered apps
-  // ACM search proxy API call
-  const searchQuery = React.useMemo(
-    () => queryAppWorkloadPVCs(pvcQueryFilter),
-    [pvcQueryFilter]
-  );
-  const [searchResult, searchLoadError, searchLoaded] =
-    useACMSafeFetch(searchQuery);
-
-  // VM Persistent Volume Claims (PVCs)
-  React.useEffect(() => {
-    if (searchLoaded && !searchLoadError) {
-      dispatch({
-        type: ManagePolicyStateType.SET_VM_PVCS,
-        context: ModalViewContext.ASSIGN_POLICY_VIEW,
-        payload:
-          searchResult?.data?.searchResult?.[0]?.related?.[0]?.items?.map(
-            (vm) => vm.name
-          ) || [],
-      });
-    }
-  }, [searchResult, searchLoadError, searchLoaded, dispatch]);
-
   // Convert all policies to PolicyInfoBase before filtering
   const convertedPolicies = matchingPolicies.map(convertToPolicyInfo);
 
@@ -80,7 +53,7 @@ export const ReplicationTypeWizardContent: React.FC<
     });
   };
 
-  return searchLoaded && !searchLoadError ? (
+  return (
     <ReplicationSelectionHelper
       policy={convertToPolicyInfo(policy)}
       eligiblePolicies={convertedPolicies}
@@ -90,8 +63,6 @@ export const ReplicationTypeWizardContent: React.FC<
       onPolicyChange={setSelectedPolicy}
       isDisable={isSharedVMProtection}
     />
-  ) : (
-    <StatusBox loaded={searchLoaded} loadError={searchLoadError} />
   );
 };
 
@@ -100,7 +71,6 @@ type ReplicationTypeWizardContentProps = {
   k8sResourceSyncInterval: string;
   matchingPolicies: DRPolicyType[];
   isValidationEnabled: boolean;
-  pvcQueryFilter: PVCQueryFilter;
   dispatch: React.Dispatch<ManagePolicyStateAction>;
   isSharedVMProtection: boolean;
 };
diff --git a/packages/mco/components/modals/app-manage-policies/manage-policy-view.tsx b/packages/mco/components/modals/app-manage-policies/manage-policy-view.tsx
index b7fd48142..03046b0ff 100644
--- a/packages/mco/components/modals/app-manage-policies/manage-policy-view.tsx
+++ b/packages/mco/components/modals/app-manage-policies/manage-policy-view.tsx
@@ -41,10 +41,7 @@ import {
   VM_RECIPE_NAME,
 } from '../../../constants';
 import { getDRPlacementControlResourceObj } from '../../../hooks';
-import {
-  doNotDeletePVCAnnotationPromises,
-  unAssignPromises,
-} from './utils/k8s-utils';
+import { unAssignPromises } from './utils/k8s-utils';
 import {
   ModalActionContext,
   ModalViewContext,
@@ -446,6 +443,8 @@ export const ManagePolicyView: React.FC<ManagePolicyViewProps> = ({
   setModalContext,
   setModalActionContext,
   modalType,
+  appName,
+  discoveredVMPVCs,
 }) => {
   const { t } = useCustomTranslation();
   const [localModalActionContext, setLocalModalActionContext] =
@@ -486,32 +485,27 @@ export const ManagePolicyView: React.FC<ManagePolicyViewProps> = ({
   };
 
   const onRemove = async () => {
-    setInProgress(true);
-    await Promise.all(
-      doNotDeletePVCAnnotationPromises(drInfo.placementControlInfo)
-    )
-      .then(() =>
-        // Delete DRPC after adding the PVC annotation successfully
-        Promise.all(unAssignPromises(drInfo.placementControlInfo))
-          .then(() =>
-            setLocalModalActionContext(
-              ModalActionContext.DISABLE_DR_PROTECTION_SUCCEEDED
-            )
-          )
-          .catch((error) => {
-            setLocalModalActionContext(
-              ModalActionContext.DISABLE_DR_PROTECTION_FAILED
-            );
-            setErrorMessage(getErrorMessage(error) || error);
-          })
-      )
-      .catch((error) => {
-        setLocalModalActionContext(
-          ModalActionContext.DISABLE_DR_PROTECTION_FAILED
-        );
-        setErrorMessage(getErrorMessage(error) || error);
-      });
-    setInProgress(false);
+    try {
+      setInProgress(true);
+      await unAssignPromises(
+        drInfo.placementControlInfo,
+        appName,
+        workloadNamespace,
+        appType,
+        t,
+        discoveredVMPVCs
+      );
+      setLocalModalActionContext(
+        ModalActionContext.DISABLE_DR_PROTECTION_SUCCEEDED
+      );
+    } catch (error) {
+      setLocalModalActionContext(
+        ModalActionContext.DISABLE_DR_PROTECTION_FAILED
+      );
+      setErrorMessage(getErrorMessage(error) || error);
+    } finally {
+      setInProgress(false);
+    }
   };
 
   return (
@@ -585,6 +579,8 @@ type ManagePolicyViewProps = {
   setModalContext: (modalViewContext: ModalViewContext) => void;
   setModalActionContext: (modalActionContext: ModalActionContext) => void;
   modalType: ModalType;
+  appName: string;
+  discoveredVMPVCs: string[];
 };
 
 type DRInformationProps = {
diff --git a/packages/mco/components/modals/app-manage-policies/modal-context-viewer.tsx b/packages/mco/components/modals/app-manage-policies/modal-context-viewer.tsx
index 45aea4575..fba33688e 100644
--- a/packages/mco/components/modals/app-manage-policies/modal-context-viewer.tsx
+++ b/packages/mco/components/modals/app-manage-policies/modal-context-viewer.tsx
@@ -1,4 +1,5 @@
 import * as React from 'react';
+import { getName } from '@odf/shared';
 import { AssignPolicyView } from './assign-policy-view';
 import { ManagePolicyView } from './manage-policy-view';
 import {
@@ -70,6 +71,8 @@ export const ModalContextViewer: React.FC<ModalContextViewerProps> = ({
           loadError={loadError}
           modalActionContext={state.modalActionContext}
           modalType={modalType}
+          appName={getName(application)}
+          discoveredVMPVCs={application?.discoveredVMPVCs}
         />
       );
     }
diff --git a/packages/mco/components/modals/app-manage-policies/parsers/discovered-vm-parser.tsx b/packages/mco/components/modals/app-manage-policies/parsers/discovered-vm-parser.tsx
index 5b9089422..d210e0904 100644
--- a/packages/mco/components/modals/app-manage-policies/parsers/discovered-vm-parser.tsx
+++ b/packages/mco/components/modals/app-manage-policies/parsers/discovered-vm-parser.tsx
@@ -8,9 +8,14 @@ import {
 import {
   getDRPlacementControlResourceObj,
   getDRPolicyResourceObj,
+  useACMSafeFetch,
 } from '@odf/mco/hooks';
 import { DRPlacementControlKind, DRPolicyKind } from '@odf/mco/types';
-import { findDRPolicyUsingDRPC } from '@odf/mco/utils';
+import {
+  findDRPolicyUsingDRPC,
+  getSearchResultItems,
+  queryAppWorkloadPVCs,
+} from '@odf/mco/utils';
 import { getName, getNamespace, VirtualMachineModel } from '@odf/shared';
 import {
   K8sResourceCommon,
@@ -60,12 +65,28 @@ export const DiscoveredVMParser: React.FC<DiscoveredVMParserProps> = ({
   const [drPolicies, drPoliciesLoaded, drPoliciesLoadError] =
     useK8sWatchResource<DRPolicyKind[]>(getDRPolicyResourceObj());
 
+  // ACM search proxy API call
+  const searchQuery = React.useMemo(
+    () => queryAppWorkloadPVCs(pvcQueryFilter),
+    [pvcQueryFilter]
+  );
+  const [searchResult, searchLoadError, searchLoaded] =
+    useACMSafeFetch(searchQuery);
+
+  // VM Persistent Volume Claims (PVCs)
+  const discoveredVMPVCs = React.useMemo(() => {
+    if (searchLoaded && !searchLoadError) {
+      return getSearchResultItems(searchResult).map((vm) => vm.name);
+    }
+    return [];
+  }, [searchResult, searchLoadError, searchLoaded]);
+
   const vmNamespace = getNamespace(virtualMachine);
   const vmName = getName(virtualMachine);
 
   // Compute loading states
-  const isLoaded = drpcsLoaded && drPoliciesLoaded;
-  const loadError = drPoliciesLoadError || drpcsLoadError;
+  const isLoaded = drpcsLoaded && drPoliciesLoaded && searchLoaded;
+  const loadError = drPoliciesLoadError || drpcsLoadError || searchLoadError;
   const isLoadedWOError = isLoaded && !loadError;
 
   const applicationInfo: ApplicationInfoType = React.useMemo(() => {
@@ -87,7 +108,8 @@ export const DiscoveredVMParser: React.FC<DiscoveredVMParserProps> = ({
       vmNamespace,
       drpcInfo.length ? [] : [placementInfo], // Skip placement if DRPC exists
       generateDRInfo(drPolicy, drpcInfo),
-      pvcQueryFilter
+      pvcQueryFilter,
+      discoveredVMPVCs
     );
   }, [
     vmName,
@@ -98,6 +120,7 @@ export const DiscoveredVMParser: React.FC<DiscoveredVMParserProps> = ({
     drPolicies,
     isLoadedWOError,
     pvcQueryFilter,
+    discoveredVMPVCs,
   ]);
 
   const matchingPolicies: DRPolicyType[] = React.useMemo(() => {
diff --git a/packages/mco/components/modals/app-manage-policies/utils/k8s-utils.ts b/packages/mco/components/modals/app-manage-policies/utils/k8s-utils.ts
index 82da8be6c..843b76c02 100644
--- a/packages/mco/components/modals/app-manage-policies/utils/k8s-utils.ts
+++ b/packages/mco/components/modals/app-manage-policies/utils/k8s-utils.ts
@@ -43,6 +43,7 @@ import {
   k8sDelete,
   k8sPatch,
   k8sCreate,
+  Patch,
 } from '@openshift-console/dynamic-plugin-sdk';
 import * as _ from 'lodash-es';
 import { TFunction } from 'react-i18next';
@@ -101,43 +102,118 @@ export const doNotDeletePVCAnnotationPromises = (
     },
   ];
   drpcs.forEach((drpc) => {
-    promises.push(
-      k8sPatch({
-        model: DRPlacementControlModel,
-        resource: {
-          metadata: {
-            name: getName(drpc),
-            namespace: getNamespace(drpc),
-          },
-        },
-        data: patch,
-      })
-    );
+    promises.push(updateDRPC(getName(drpc), getNamespace(drpc), patch));
   });
   return promises;
 };
 
-// ToDo(Gowtham): https://github.com/red-hat-storage/odf-console/issues/1449
-export const unAssignPromises = (drpcs: DRPlacementControlType[]) => {
-  const promises: Promise<K8sResourceKind>[] = [];
-  drpcs.forEach((drpc) => {
-    promises.push(
-      k8sDelete({
-        model: DRPlacementControlModel,
-        resource: {
-          metadata: {
-            name: getName(drpc),
-            namespace: getNamespace(drpc),
-          },
-        },
-        requestInit: null,
-        json: null,
-      })
-    );
+const updateDRPC = (drpcName: string, drpcNamespace: string, patch: Patch[]) =>
+  k8sPatch({
+    model: DRPlacementControlModel,
+    resource: {
+      metadata: {
+        name: drpcName,
+        namespace: drpcNamespace,
+      },
+    },
+    data: patch,
   });
-  return promises;
+
+const deleteDRPC = (drpcName: string, drpcNamespace: string) =>
+  k8sDelete({
+    model: DRPlacementControlModel,
+    resource: {
+      metadata: {
+        name: drpcName,
+        namespace: drpcNamespace,
+      },
+    },
+    requestInit: null,
+    json: null,
+  });
+
+const deleteDummyPlacement = (placementName: string) =>
+  k8sDelete({
+    model: ACMPlacementModel,
+    resource: {
+      metadata: {
+        name: placementName,
+        namespace: DISCOVERED_APP_NS,
+      },
+    },
+    requestInit: null,
+    json: null,
+  });
+
+const unAssignPromisesForDiscovered = async (
+  drpc: DRPlacementControlType,
+  vmName: string,
+  vmNamespace: string,
+  t: TFunction,
+  discoveredVMPVCs: string[]
+) => {
+  const protectionName = drpc.vmSharedGroupName;
+  const clusterName = drpc.placementInfo.deploymentClusters[0];
+  const protectedVMNames = drpc.vmSharedGroup;
+  const drpcName = getName(drpc);
+  const placementName = getName(drpc.placementInfo);
+
+  // Step 1 & 2: Remove label from VM and PVCs
+  await Promise.all([
+    updateVMLabels(vmName, vmNamespace, protectionName, clusterName, t, true),
+    updatePVCLabels(
+      discoveredVMPVCs,
+      vmNamespace,
+      protectionName,
+      clusterName,
+      t,
+      true
+    ),
+  ]);
+
+  if (drpc.vmSharedGroup.length > 1) {
+    const patch = [
+      {
+        op: 'replace',
+        path: `/spec/kubeObjectProtection/recipeParameters/${PROTECTED_VMS}`,
+        value: protectedVMNames.filter((name) => name !== vmName),
+      },
+    ];
+    await updateDRPC(drpcName, DISCOVERED_APP_NS, patch);
+  } else {
+    await Promise.all([
+      deleteDRPC(drpcName, DISCOVERED_APP_NS),
+      deleteDummyPlacement(placementName),
+    ]);
+  }
 };
 
+export const unAssignPromises = async (
+  drpcs: DRPlacementControlType[],
+  appName: string,
+  appNamespace: string,
+  appType: DRApplication,
+  t: TFunction,
+  discoveredVMPVCs: string[]
+) => {
+  if (appType !== DRApplication.DISCOVERED) {
+    await Promise.all(unAssignPromisesForManaged(drpcs));
+  } else {
+    await unAssignPromisesForDiscovered(
+      drpcs[0],
+      appName,
+      appNamespace,
+      t,
+      discoveredVMPVCs
+    );
+  }
+};
+
+export const unAssignPromisesForManaged = (drpcs: DRPlacementControlType[]) => [
+  doNotDeletePVCAnnotationPromises(drpcs),
+  drpcs.map((drpc) => deleteDRPC(getName(drpc), getNamespace(drpc))),
+];
+
 const placementAssignPromise = (placement: PlacementType) => {
   const patch = [];
   if (_.isEmpty(getAnnotations(placement))) {
@@ -233,7 +309,8 @@ export const updateVMLabels = async (
   vmNamespace: string,
   protectionName: string,
   clusterName: string,
-  t: TFunction
+  t: TFunction,
+  deleteLabel?: boolean
 ): Promise<K8sResourceKind> => {
   // Fetch VM details
   const mcvResponse = await fireManagedClusterView(
@@ -246,10 +323,17 @@ export const updateVMLabels = async (
     t
   );
 
+  const vmData = mcvResponse.result;
+
   // Ensure labels exist and update
-  mcvResponse.result.metadata.labels ||= {};
-  mcvResponse.result.metadata.labels[K8S_RESOURCE_SELECTOR_LABEL_KEY] =
-    protectionName;
+  vmData.metadata.labels ||= {};
+  if (deleteLabel) {
+    // Remove label if exists
+    delete vmData.metadata.labels[K8S_RESOURCE_SELECTOR_LABEL_KEY];
+  } else {
+    // Add or update the label
+    vmData.metadata.labels[K8S_RESOURCE_SELECTOR_LABEL_KEY] = protectionName;
+  }
 
   // Apply the updated labels and return updated VM object
   const mcaResponse = await fireManagedClusterAction(
@@ -272,7 +356,8 @@ export const updatePVCLabels = async (
   pvcNamespace: string,
   protectionName: string,
   clusterName: string,
-  t: TFunction
+  t: TFunction,
+  deleteLabel?: boolean
 ): Promise<K8sResourceKind[]> => {
   return Promise.all(
     pvcNames.map(async (pvcName) => {
@@ -286,10 +371,18 @@ export const updatePVCLabels = async (
         t
       );
 
+      const pvcData = mcvResponse.result;
+
       // Ensure labels exist and update
-      mcvResponse.result.metadata.labels ||= {};
-      mcvResponse.result.metadata.labels[K8S_RESOURCE_SELECTOR_LABEL_KEY] =
-        protectionName;
+      pvcData.metadata.labels ||= {};
+      if (deleteLabel) {
+        // Remove label if exists
+        delete pvcData.metadata.labels[K8S_RESOURCE_SELECTOR_LABEL_KEY];
+      } else {
+        // Add or update the label
+        pvcData.metadata.labels[K8S_RESOURCE_SELECTOR_LABEL_KEY] =
+          protectionName;
+      }
 
       // Apply updated labels and return updated PVC object
       const mcaResponse = await fireManagedClusterAction(
@@ -314,11 +407,12 @@ export const assignPromisesForDiscovered = async (
   placements: PlacementType[],
   vmNamespace: string,
   vmName: string,
-  t: TFunction
+  t: TFunction,
+  discoveredVMPVCs: string[]
 ): Promise<void> => {
   const {
     protectionType: { protectionName, protectionType, protectedVMNames },
-    replication: { k8sSyncInterval, policy, vmPVCs },
+    replication: { k8sSyncInterval, policy },
   } = state;
   const drpcName = `${protectionName}-drpc`;
 
@@ -327,7 +421,13 @@ export const assignPromisesForDiscovered = async (
   // Step 1 & 2: Label VM and PVCs
   await Promise.all([
     updateVMLabels(vmName, vmNamespace, protectionName, clusterName, t),
-    updatePVCLabels(vmPVCs, vmNamespace, protectionName, clusterName, t),
+    updatePVCLabels(
+      discoveredVMPVCs,
+      vmNamespace,
+      protectionName,
+      clusterName,
+      t
+    ),
   ]);
 
   if (protectionType === VMProtectionType.STANDALONE) {
@@ -390,7 +490,8 @@ export const assignPromises = async (
   appType: DRApplication,
   workloadNamespace: string,
   appName: string,
-  t: TFunction
+  t: TFunction,
+  discoveredVMPVCs: string[]
 ) => {
   if (appType === DRApplication.DISCOVERED) {
     await assignPromisesForDiscovered(
@@ -398,7 +499,8 @@ export const assignPromises = async (
       placements,
       workloadNamespace,
       appName,
-      t
+      t,
+      discoveredVMPVCs
     );
   } else {
     await assignPromisesForManaged(state, placements);
diff --git a/packages/mco/components/modals/app-manage-policies/utils/parser-utils.ts b/packages/mco/components/modals/app-manage-policies/utils/parser-utils.ts
index d0e7d735a..e39e62d90 100644
--- a/packages/mco/components/modals/app-manage-policies/utils/parser-utils.ts
+++ b/packages/mco/components/modals/app-manage-policies/utils/parser-utils.ts
@@ -119,7 +119,8 @@ export const generateApplicationInfo = (
   workloadNamespace: string,
   plsInfo: PlacementType[],
   drInfo: DRInfoType | {},
-  pvcQueryFilter: PVCQueryFilter
+  pvcQueryFilter: PVCQueryFilter,
+  discoveredVMPVCs?: string[]
 ): ApplicationType => ({
   type: appType,
   apiVersion: application.apiVersion,
@@ -129,6 +130,7 @@ export const generateApplicationInfo = (
   placements: plsInfo,
   drInfo: drInfo,
   pvcQueryFilter: pvcQueryFilter,
+  discoveredVMPVCs: discoveredVMPVCs,
 });
 
 export const getClusterNamesFromPlacements = (placements: PlacementType[]) =>
diff --git a/packages/mco/components/modals/app-manage-policies/utils/reducer.ts b/packages/mco/components/modals/app-manage-policies/utils/reducer.ts
index 7ea1fd55d..cf6242397 100644
--- a/packages/mco/components/modals/app-manage-policies/utils/reducer.ts
+++ b/packages/mco/components/modals/app-manage-policies/utils/reducer.ts
@@ -25,7 +25,6 @@ export enum ManagePolicyStateType {
   SET_VM_PROTECTION_NAME = 'SET_VM_PROTECTION_NAME',
   SET_SELECTED_POLICY_FOR_REPLICATION = 'SET_SELECTED_POLICY_FOR_REPLICATION',
   SET_K8S_SYNC_INTERVAL = 'SET_K8S_SYNC_INTERVAL',
-  SET_VM_PVCS = 'SET_VM_PVCS',
   SET_SHARED_VM_GROUP_INFO = 'SET_SHARED_VM_GROUP_INFO',
 }
 
@@ -47,7 +46,6 @@ export type AssignPolicyViewState = {
   replication?: {
     policy: DRPolicyType;
     k8sSyncInterval: string;
-    vmPVCs: string[];
   };
 };
 
@@ -72,7 +70,6 @@ export const initialPolicyState: ManagePolicyState = {
     replication: {
       policy: undefined,
       k8sSyncInterval: '5m',
-      vmPVCs: [],
     },
   },
 };
@@ -120,11 +117,6 @@ export type ManagePolicyStateAction =
       context: ModalViewContext;
       payload: string;
     }
-  | {
-      type: ManagePolicyStateType.SET_VM_PVCS;
-      context: ModalViewContext;
-      payload: string[];
-    }
   | {
       type: ManagePolicyStateType.SET_SHARED_VM_GROUP_INFO;
       context: ModalViewContext;
@@ -232,18 +224,6 @@ export const managePolicyStateReducer = (
         },
       };
     }
-    case ManagePolicyStateType.SET_VM_PVCS: {
-      return {
-        ...state,
-        [action.context]: {
-          ...state[action.context],
-          replication: {
-            ...state[action.context]['replication'],
-            vmPVCs: action.payload,
-          },
-        },
-      };
-    }
     case ManagePolicyStateType.SET_SHARED_VM_GROUP_INFO: {
       const { policy, k8sSyncInterval, protectionName, sharedVMGroup } =
         action.payload;
diff --git a/packages/mco/components/modals/app-manage-policies/utils/types.ts b/packages/mco/components/modals/app-manage-policies/utils/types.ts
index e52ba4c3a..18cb53609 100644
--- a/packages/mco/components/modals/app-manage-policies/utils/types.ts
+++ b/packages/mco/components/modals/app-manage-policies/utils/types.ts
@@ -64,6 +64,8 @@ export type ApplicationType = K8sResourceCommon & {
   drInfo?: DRInfoType | {};
   // ACM search API filter to read application PVCs
   pvcQueryFilter: PVCQueryFilter;
+  // List of discovered VM PVCs
+  discoveredVMPVCs?: string[];
 };
 
 export type ApplicationInfoType = ApplicationType | {};
diff --git a/packages/mco/utils/disaster-recovery.tsx b/packages/mco/utils/disaster-recovery.tsx
index 956c0d839..0efe962a2 100644
--- a/packages/mco/utils/disaster-recovery.tsx
+++ b/packages/mco/utils/disaster-recovery.tsx
@@ -73,6 +73,7 @@ import {
   ClusterClaim,
   SearchResultItemType,
   ACMPlacementType,
+  SearchResult,
 } from '../types';
 
 export type PlacementMap = {
@@ -670,3 +671,7 @@ export const getLabelsFromSearchResult = (
 
 export const getManagedClusterViewName = (managedClusterName: string): string =>
   MCV_NAME_TEMPLATE + managedClusterName;
+
+export const getSearchResultItems = (searchResult: SearchResult) => {
+  return searchResult?.data?.searchResult?.[0]?.related?.[0]?.items || [];
+};
diff --git a/packages/mco/utils/managed-cluster-action.ts b/packages/mco/utils/managed-cluster-action.ts
index 0f3fd6776..331f5618f 100644
--- a/packages/mco/utils/managed-cluster-action.ts
+++ b/packages/mco/utils/managed-cluster-action.ts
@@ -43,7 +43,7 @@ export const fireManagedClusterAction = async (
   resourceBody: any,
   t: TFunction
 ): Promise<PollManagedClusterActionResult> => {
-  const resourceType = `${resourceKind.toLowerCase()}.${resourceApiVersion}.${resourceApiGroup}`;
+  const resourceType = `${resourceKind.toLowerCase()}.${resourceApiGroup}.${resourceApiVersion}`;
 
   const res = await k8sCreate<ACMManagedClusterActionKind>({
     model: ACMManagedClusterActionModel,


Reviews:
user: login: SanjalKatiyar
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T05:59:46Z
user: login: SanjalKatiyar
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-27T06:05:38Z
user: login: GowthamShanmugam
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T06:31:50Z
user: login: GowthamShanmugam
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-27T06:32:00Z

Review comments:
diff_hunk: @@ -486,32 +485,28 @@ export const ManagePolicyView: React.FC<ManagePolicyViewProps> = ({
   };
 
   const onRemove = async () => {
+    try {
+      setInProgress(true);
+      await unAssignPromises(
+        drInfo.placementControlInfo,
+        appName,
+        workloadNamespace,
+        appType,
+        t,
+        discoveredVMPVCs
+      );
+      setLocalModalActionContext(
+        ModalActionContext.DISABLE_DR_PROTECTION_SUCCEEDED
+      );
+    } catch (error) {
+      setLocalModalActionContext(
+        ModalActionContext.DISABLE_DR_PROTECTION_FAILED
+      );
+      setErrorMessage(getErrorMessage(error) || error);
+    } finally {
+      setInProgress(false);
+    }
     setInProgress(true);
path: packages/mco/components/modals/app-manage-policies/manage-policy-view.tsx
user: login: SanjalKatiyar
body: this is a bug :)
```suggestion
```
created_at: 2025-03-27T05:59:45Z
author_association: COLLABORATOR
diff_hunk: @@ -101,43 +102,121 @@ export const doNotDeletePVCAnnotationPromises = (
     },
   ];
   drpcs.forEach((drpc) => {
-    promises.push(
-      k8sPatch({
-        model: DRPlacementControlModel,
-        resource: {
-          metadata: {
-            name: getName(drpc),
-            namespace: getNamespace(drpc),
-          },
-        },
-        data: patch,
-      })
-    );
+    promises.push(updateDRPC(getName(drpc), getNamespace(drpc), patch));
   });
   return promises;
 };
 
-// ToDo(Gowtham): https://github.com/red-hat-storage/odf-console/issues/1449
-export const unAssignPromises = (drpcs: DRPlacementControlType[]) => {
-  const promises: Promise<K8sResourceKind>[] = [];
-  drpcs.forEach((drpc) => {
-    promises.push(
-      k8sDelete({
-        model: DRPlacementControlModel,
-        resource: {
-          metadata: {
-            name: getName(drpc),
-            namespace: getNamespace(drpc),
-          },
-        },
-        requestInit: null,
-        json: null,
-      })
-    );
+const updateDRPC = (drpcName: string, drpcNamespace: string, patch: Patch[]) =>
+  k8sPatch({
+    model: DRPlacementControlModel,
+    resource: {
+      metadata: {
+        name: drpcName,
+        namespace: drpcNamespace,
+      },
+    },
+    data: patch,
   });
+
+const deleteDRPC = (drpcName: string, drpcNamespace: string) =>
+  k8sDelete({
+    model: DRPlacementControlModel,
+    resource: {
+      metadata: {
+        name: drpcName,
+        namespace: drpcNamespace,
+      },
+    },
+    requestInit: null,
+    json: null,
+  });
+
+const deleteDummyPlacement = (placementName: string) =>
+  k8sDelete({
+    model: ACMPlacementModel,
+    resource: {
+      metadata: {
+        name: placementName,
+        namespace: DISCOVERED_APP_NS,
+      },
+    },
+    requestInit: null,
+    json: null,
+  });
+
+const unAssignPromisesForDiscovered = async (
+  drpc: DRPlacementControlType,
+  vmName: string,
+  vmNamespace: string,
+  t: TFunction,
+  discoveredVMPVCs: string[]
+) => {
+  const promises: Promise<K8sResourceKind>[] = [];
+  const protectionName = drpc.vmSharedGroupName;
+  const clusterName = drpc.placementInfo.deploymentClusters[0];
+  const protectedVMNames = drpc.vmSharedGroup;
+  const drpcName = getName(drpc);
+  const placementName = getName(drpc.placementInfo);
+
+  // Step 1 & 2: Remove label from VM and PVCs
+  await Promise.all([
+    updateVMLabels(vmName, vmNamespace, protectionName, clusterName, t, true),
+    updatePVCLabels(
+      discoveredVMPVCs,
+      vmNamespace,
+      protectionName,
+      clusterName,
+      t,
+      true
+    ),
+  ]);
+
+  if (drpc.vmSharedGroup.length > 1) {
+    const patch = [
+      {
+        op: 'replace',
+        path: `/spec/kubeObjectProtection/recipeParameters/${PROTECTED_VMS}`,
+        value: protectedVMNames.filter((name) => name !== vmName),
+      },
+    ];
+    await updateDRPC(drpcName, DISCOVERED_APP_NS, patch);
+  } else {
+    await Promise.all([
+      deleteDRPC(drpcName, DISCOVERED_APP_NS),
+      deleteDummyPlacement(placementName),
+    ]);
+  }
+
   return promises;
path: packages/mco/components/modals/app-manage-policies/utils/k8s-utils.ts
user: login: SanjalKatiyar
body: u r returning empty array...
```suggestion
```
created_at: 2025-03-27T06:05:38Z
author_association: COLLABORATOR
diff_hunk: @@ -486,32 +485,28 @@ export const ManagePolicyView: React.FC<ManagePolicyViewProps> = ({
   };
 
   const onRemove = async () => {
+    try {
+      setInProgress(true);
+      await unAssignPromises(
+        drInfo.placementControlInfo,
+        appName,
+        workloadNamespace,
+        appType,
+        t,
+        discoveredVMPVCs
+      );
+      setLocalModalActionContext(
+        ModalActionContext.DISABLE_DR_PROTECTION_SUCCEEDED
+      );
+    } catch (error) {
+      setLocalModalActionContext(
+        ModalActionContext.DISABLE_DR_PROTECTION_FAILED
+      );
+      setErrorMessage(getErrorMessage(error) || error);
+    } finally {
+      setInProgress(false);
+    }
     setInProgress(true);
path: packages/mco/components/modals/app-manage-policies/manage-policy-view.tsx
user: login: GowthamShanmugam
body: ack
created_at: 2025-03-27T06:31:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -101,43 +102,121 @@ export const doNotDeletePVCAnnotationPromises = (
     },
   ];
   drpcs.forEach((drpc) => {
-    promises.push(
-      k8sPatch({
-        model: DRPlacementControlModel,
-        resource: {
-          metadata: {
-            name: getName(drpc),
-            namespace: getNamespace(drpc),
-          },
-        },
-        data: patch,
-      })
-    );
+    promises.push(updateDRPC(getName(drpc), getNamespace(drpc), patch));
   });
   return promises;
 };
 
-// ToDo(Gowtham): https://github.com/red-hat-storage/odf-console/issues/1449
-export const unAssignPromises = (drpcs: DRPlacementControlType[]) => {
-  const promises: Promise<K8sResourceKind>[] = [];
-  drpcs.forEach((drpc) => {
-    promises.push(
-      k8sDelete({
-        model: DRPlacementControlModel,
-        resource: {
-          metadata: {
-            name: getName(drpc),
-            namespace: getNamespace(drpc),
-          },
-        },
-        requestInit: null,
-        json: null,
-      })
-    );
+const updateDRPC = (drpcName: string, drpcNamespace: string, patch: Patch[]) =>
+  k8sPatch({
+    model: DRPlacementControlModel,
+    resource: {
+      metadata: {
+        name: drpcName,
+        namespace: drpcNamespace,
+      },
+    },
+    data: patch,
   });
+
+const deleteDRPC = (drpcName: string, drpcNamespace: string) =>
+  k8sDelete({
+    model: DRPlacementControlModel,
+    resource: {
+      metadata: {
+        name: drpcName,
+        namespace: drpcNamespace,
+      },
+    },
+    requestInit: null,
+    json: null,
+  });
+
+const deleteDummyPlacement = (placementName: string) =>
+  k8sDelete({
+    model: ACMPlacementModel,
+    resource: {
+      metadata: {
+        name: placementName,
+        namespace: DISCOVERED_APP_NS,
+      },
+    },
+    requestInit: null,
+    json: null,
+  });
+
+const unAssignPromisesForDiscovered = async (
+  drpc: DRPlacementControlType,
+  vmName: string,
+  vmNamespace: string,
+  t: TFunction,
+  discoveredVMPVCs: string[]
+) => {
+  const promises: Promise<K8sResourceKind>[] = [];
+  const protectionName = drpc.vmSharedGroupName;
+  const clusterName = drpc.placementInfo.deploymentClusters[0];
+  const protectedVMNames = drpc.vmSharedGroup;
+  const drpcName = getName(drpc);
+  const placementName = getName(drpc.placementInfo);
+
+  // Step 1 & 2: Remove label from VM and PVCs
+  await Promise.all([
+    updateVMLabels(vmName, vmNamespace, protectionName, clusterName, t, true),
+    updatePVCLabels(
+      discoveredVMPVCs,
+      vmNamespace,
+      protectionName,
+      clusterName,
+      t,
+      true
+    ),
+  ]);
+
+  if (drpc.vmSharedGroup.length > 1) {
+    const patch = [
+      {
+        op: 'replace',
+        path: `/spec/kubeObjectProtection/recipeParameters/${PROTECTED_VMS}`,
+        value: protectedVMNames.filter((name) => name !== vmName),
+      },
+    ];
+    await updateDRPC(drpcName, DISCOVERED_APP_NS, patch);
+  } else {
+    await Promise.all([
+      deleteDRPC(drpcName, DISCOVERED_APP_NS),
+      deleteDummyPlacement(placementName),
+    ]);
+  }
+
   return promises;
path: packages/mco/components/modals/app-manage-policies/utils/k8s-utils.ts
user: login: GowthamShanmugam
body: ah! my bad
created_at: 2025-03-27T06:32:00Z
author_association: CONTRIBUTOR

PR comments:
user: login: openshift-ci-robot
created_at: 2025-03-26T14:20:40Z
body: @GowthamShanmugam: This pull request references [RHSTOR-7113](https://issues.redhat.com//browse/RHSTOR-7113) which is a valid jira issue.

<details>

In response to [this](https://github.com/red-hat-storage/odf-console/pull/1923):

>


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=red-hat-storage%2Fodf-console).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: SanjalKatiyar
created_at: 2025-03-27T06:08:54Z
body: Do we support disable DR for managed/discovered apps too ??
author_association: COLLABORATOR
user: login: openshift-ci-robot
created_at: 2025-03-27T06:31:02Z
body: @GowthamShanmugam: This pull request references [RHSTOR-7113](https://issues.redhat.com//browse/RHSTOR-7113) which is a valid jira issue.

<details>

In response to [this](https://github.com/red-hat-storage/odf-console/pull/1923):

>### **Disable DR Behavior for VMs**  
>
>We have disabled Disaster Recovery (DR) for **both** managed and discovered VMs.  
>
>#### **Managed VMs**  
>- The behavior is the same as managed applications.  
>- DR is fully disabled when required.  
>
>#### **Discovered VMs**  
>The behavior differs slightly from discovered applications:  
>
>1. **Discovered Applications**  
>   - DR is disabled by deleting the **DRPC (DRPlacementControl)** and the **dummy placement**.  
>
>2. **Discovered VMs** (Classified into Shared and Standalone types)  
>   - **Shared VMs** (VMs belonging to a group):  
>     - **Do not delete DRPC** unless it's the last VM in the group.  
>     - Remove the **ramen unique label**.  from VM and its PVCs.
>     - Remove the VM from the **DRPC VM list**.  
>   - **Standalone VMs** (Independent VMs):  
>     - Remove all relevant **labels** from VM and its PVCs.
>     - **Delete the DRPC**.  
>     - **Delete the dummy placement**.  


Instructions for interacting with me using PR comments are available [here](https://prow.ci.openshift.org/command-help?repo=red-hat-storage%2Fodf-console).  If you have questions or suggestions related to my behavior, please file an issue against the [openshift-eng/jira-lifecycle-plugin](https://github.com/openshift-eng/jira-lifecycle-plugin/issues/new) repository.
</details>
author_association: NONE
user: login: GowthamShanmugam
created_at: 2025-03-27T06:31:38Z
body: > Do we support disable DR for managed/discovered apps too ??

Yes, we have disabled DR for both—Disable DR for the managed VM behaviour, which is the same as the managed application.  https://github.com/red-hat-storage/odf-console/pull/1923#issue-2949799757
author_association: CONTRIBUTOR
user: login: SanjalKatiyar
created_at: 2025-03-27T10:19:31Z
body: /lgtm
author_association: COLLABORATOR
user: login: openshift-ci[bot]
created_at: 2025-03-27T10:19:39Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/red-hat-storage/odf-console/pull/1923#" title="Author self-approved">GowthamShanmugam</a>*, *<a href="https://github.com/red-hat-storage/odf-console/pull/1923#issuecomment-2757504082" title="LGTM">SanjalKatiyar</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=red-hat-storage%2Fodf-console).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/red-hat-storage/odf-console/blob/master/OWNERS)~~ [GowthamShanmugam,SanjalKatiyar]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: CONTRIBUTOR