From 3036d588bb4ceb17670d3487ee3c547bf69ec2a4 Mon Sep 17 00:00:00 2001 From: Gowtham Shanmugasundaram Date: Fri, 28 Mar 2025 20:08:01 +0530 Subject: [PATCH] Check for PVC label selector conflicts and merge conflict conditions Handles the PVC conflict across primary and secondary clusters. And priotize the conflict on primary cluster over the secondary. Signed-off-by: pruthvitd --- internal/controller/status.go | 17 ++- .../volumereplicationgroup_controller.go | 110 +++++++++++++++++- internal/controller/vrg_volsync.go | 48 ++++++++ 3 files changed, 167 insertions(+), 8 deletions(-) diff --git a/internal/controller/status.go b/internal/controller/status.go index a682118b47..e03bbf7d67 100644 --- a/internal/controller/status.go +++ b/internal/controller/status.go @@ -40,14 +40,16 @@ const ( // protected from a disaster by uploading it to the required S3 store(s). VRGConditionTypeClusterDataProtected = "ClusterDataProtected" - VRGConditionTypeNoClusterDataConflict = "NoClusterDataConflict" - // VolSync related conditions. These conditions are only applicable // at individual PVCs and not generic VRG conditions. VRGConditionTypeVolSyncRepSourceSetup = "ReplicationSourceSetup" VRGConditionTypeVolSyncFinalSyncInProgress = "FinalSyncInProgress" VRGConditionTypeVolSyncRepDestinationSetup = "ReplicationDestinationSetup" VRGConditionTypeVolSyncPVsRestored = "PVsRestored" + + // Indicates no conflict in PVC and Kubernetes resource data + // between primary and secondary clusters. + VRGConditionTypeNoClusterDataConflict = "NoClusterDataConflict" ) // VRG condition reasons @@ -76,9 +78,14 @@ const ( VRGConditionReasonClusterDataAnnotationFailed = "AnnotationFailed" VRGConditionReasonPeerClassNotFound = "PeerClassNotFound" VRGConditionReasonStorageIDNotFound = "StorageIDNotFound" - VRGConditionReasonDataConflictPrimary = "ClusterDataConflictPrimary" - VRGConditionReasonDataConflictSecondary = "ClusterDataConflictSecondary" - VRGConditionReasonConflictResolved = "ConflictResolved" + // Indicates a conflict in cluster data detected on the primary cluster. + VRGConditionReasonClusterDataConflictPrimary = "ClusterDataConflictPrimary" + + // Indicates a conflict in cluster data detected on the secondary cluster. + VRGConditionReasonClusterDataConflictSecondary = "ClusterDataConflictSecondary" + + // Indicates no conflict in cluster data detected on both the primary and secondary cluster. + VRGConditionReasonConflictResolved = "ConflictResolved" ) const ( diff --git a/internal/controller/volumereplicationgroup_controller.go b/internal/controller/volumereplicationgroup_controller.go index 557fa9f23c..9958f80e5a 100644 --- a/internal/controller/volumereplicationgroup_controller.go +++ b/internal/controller/volumereplicationgroup_controller.go @@ -1847,10 +1847,11 @@ func (v *VRGInstance) updateVRGConditions() { v.log.Info(msg, "finalCondition", finalCondition) } - var volSyncDataReady, volSyncDataProtected, volSyncClusterDataProtected *metav1.Condition + var volSyncDataReady, volSyncDataProtected, volSyncClusterDataProtected, volSyncClusterDataConflict *metav1.Condition if v.instance.Spec.Sync == nil { volSyncDataReady = v.aggregateVolSyncDataReadyCondition() volSyncDataProtected, volSyncClusterDataProtected = v.aggregateVolSyncDataProtectedConditions() + volSyncClusterDataConflict = v.aggregateVolSyncClusterDataConflictCondition() } logAndSet(VRGConditionTypeDataReady, @@ -1869,6 +1870,7 @@ func (v *VRGInstance) updateVRGConditions() { v.kubeObjectsProtected, ) logAndSet(VRGConditionTypeNoClusterDataConflict, + volSyncClusterDataConflict, v.aggregateVRGNoClusterDataConflictCondition(), ) v.updateVRGLastGroupSyncTime() @@ -2263,6 +2265,60 @@ func (v *VRGInstance) CheckForVMNameConflictOnSecondary(vmNamespaceList, vmList } func (v *VRGInstance) aggregateVRGNoClusterDataConflictCondition() *metav1.Condition { + var vmResourceConflict, pvcResourceConflict bool + vmResourceConflict = false + pvcResourceConflict = false + + vmConflictCondition := v.aggregateVMNoClusterDataConflictCondition() + + if vmConflictCondition != nil { + if vmConflictCondition.Status == metav1.ConditionFalse { + vmResourceConflict = true + } + } + + pvcConflictCondition := v.aggregateVolRepClusterDataConflictCondition() + + if pvcConflictCondition != nil { + if vmConflictCondition.Status == metav1.ConditionFalse { + pvcResourceConflict = true + } + } + + if !vmResourceConflict && !pvcResourceConflict { + return vmConflictCondition + } + + // Priortize the resource conflict condition on Primary cluster + if vmResourceConflict && pvcResourceConflict { + return v.PriortizePrimaryClusterDataConflictCondition(vmConflictCondition, pvcConflictCondition) + } + + if vmResourceConflict { + return vmConflictCondition + } + + return pvcConflictCondition +} + +func (v *VRGInstance) PriortizePrimaryClusterDataConflictCondition( + vmConflictCondition, pvcConflictCondition *metav1.Condition, +) *metav1.Condition { + if vmConflictCondition.Reason == pvcConflictCondition.Reason { + vmConflictCondition.Message = fmt.Sprintf("Both VM and PVC resource conflicting on %s cluster", + v.instance.Spec.ReplicationState) + + return vmConflictCondition + } + + if vmConflictCondition.Reason == VRGConditionReasonClusterDataConflictPrimary { + return vmConflictCondition + } + + return pvcConflictCondition +} + +func (v *VRGInstance) aggregateVMNoClusterDataConflictCondition() *metav1.Condition { var msg string if v.isVMRecipeProtection() { @@ -2282,11 +2338,11 @@ func (v *VRGInstance) aggregateVRGNoClusterDataConflictCondition() *metav1.Condi func (v *VRGInstance) clusterDataConflict(msg string, status metav1.ConditionStatus) *metav1.Condition { if v.instance.Spec.ReplicationState == ramendrv1alpha1.Primary { return updateVRGNoClusterDataConflictCondition( - v.instance.Status.ObservedGeneration, status, VRGConditionReasonDataConflictPrimary, + v.instance.Status.ObservedGeneration, status, VRGConditionReasonClusterDataConflictPrimary, msg) } else if v.instance.Spec.ReplicationState == ramendrv1alpha1.Secondary { return updateVRGNoClusterDataConflictCondition( - v.instance.Status.ObservedGeneration, status, VRGConditionReasonDataConflictSecondary, + v.instance.Status.ObservedGeneration, status, VRGConditionReasonClusterDataConflictSecondary, msg) } @@ -2302,3 +2358,51 @@ func (v *VRGInstance) isVMRecipeProtection() bool { return false } + +func (v *VRGInstance) aggregateVolRepClusterDataConflictCondition() *metav1.Condition { + noClusterDataConflictCondition := &metav1.Condition{ + Status: metav1.ConditionTrue, + Type: VRGConditionTypeNoClusterDataConflict, + Reason: VRGConditionReasonConflictResolved, + ObservedGeneration: v.instance.Generation, + Message: "No PVC conflict detected for VolumeReplication scheme", + } + + if conflictCondition := v.validateSecondaryPVCConflictForVolRep(); conflictCondition != nil { + return conflictCondition + } + + return noClusterDataConflictCondition +} + +func (v *VRGInstance) IsSecondaryVRG() bool { + spec := v.instance.Spec + status := v.instance.Status + + isSecondary := spec.ReplicationState == ramendrv1alpha1.Secondary + isStateSecondary := status.State == ramendrv1alpha1.SecondaryState + isRelocateOrNone := spec.Action == ramendrv1alpha1.VRGActionRelocate || spec.Action == "" + isPrepareForFinalSyncFalse := !spec.PrepareForFinalSync + isRunFinalSyncFalse := !spec.RunFinalSync + + return isSecondary && + isStateSecondary && + isRelocateOrNone && + isPrepareForFinalSyncFalse && + isRunFinalSyncFalse +} + +func (v *VRGInstance) isSecondaryWithVolRepProtectedPVCs() bool { + return v.IsSecondaryVRG() && len(v.volRepPVCs) > 0 +} + +func (v *VRGInstance) validateSecondaryPVCConflictForVolRep() *metav1.Condition { + if v.isSecondaryWithVolRepProtectedPVCs() { + return updateVRGNoClusterDataConflictCondition( + v.instance.Generation, metav1.ConditionFalse, VRGConditionReasonClusterDataConflictSecondary, + "No PVC on the secondary should match the label selector", + ) + } + + return nil +} diff --git a/internal/controller/vrg_volsync.go b/internal/controller/vrg_volsync.go index c8d4f78c25..edc7996aa3 100644 --- a/internal/controller/vrg_volsync.go +++ b/internal/controller/vrg_volsync.go @@ -639,3 +639,51 @@ func (v *VRGInstance) doCleanupResources(name, namespace string) error { return nil } + +func (v *VRGInstance) isSecondaryWithVolSyncProtectedPVCs() bool { + return v.IsSecondaryVRG() && len(v.volSyncPVCs) > 0 +} + +func (v *VRGInstance) validateSecondaryPVCConflictForVolsync() bool { + if !v.isSecondaryWithVolSyncProtectedPVCs() { + return false + } + + // Validate that only replication destination PVCs match the label selector. + for _, pvc := range v.volSyncPVCs { + matchFound := false + + for _, rdSpec := range v.instance.Spec.VolSync.RDSpec { + if pvc.GetName() == rdSpec.ProtectedPVC.Name { + matchFound = true + + break // Found a match, no need to check further for this PVC + } + } + + if !matchFound { + return true // No match found for this PVC, conflict detected! + } + } + + return false // No conflicts found +} + +func (v *VRGInstance) aggregateVolSyncClusterDataConflictCondition() *metav1.Condition { + noClusterDataConflictCondition := &metav1.Condition{ + Status: metav1.ConditionTrue, + Type: VRGConditionTypeNoClusterDataConflict, + Reason: VRGConditionReasonConflictResolved, + ObservedGeneration: v.instance.Generation, + Message: "No PVC conflict detected for VolumeSync scheme", + } + + if v.validateSecondaryPVCConflictForVolsync() { + return updateVRGNoClusterDataConflictCondition(v.instance.Generation, + metav1.ConditionFalse, VRGConditionReasonClusterDataConflictSecondary, + "A PVC that is not a replication destination should not match the label selector.", + ) + } + + return noClusterDataConflictCondition +}