Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add VolSync Support for PVCs with Block VolumeMode #1582

Merged
merged 4 commits into from
Nov 12, 2024

Conversation

BenamarMk
Copy link
Member

This PR adds support for snapshotting and replicating RBD PVCs with Block VolumeMode using VolSync. Key changes include:

  • Added the annotation UseVolSyncForRBDAnnotation to the DRPC resource to enable VolSync snapshotting and replication of RBD PVCs with Block VolumeMode on a per-instance basis.
  • Implemented necessary changes to support Block VolumeMode across all relevant components.

@@ -22,6 +22,8 @@ const (
OCMBackupLabelValue string = "ramen"

IsCGEnabledAnnotation = "drplacementcontrol.ramendr.openshift.io/is-cg-enabled"

UseVolSyncForRBDAnnotation = "drplacementcontrol.ramendr.openshift.io/is-rbd-enabled-for-volsync-replication"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name of the annotation is little bit too specific, ramen should not care about the storage (rbd). The value of the annotation does not match the annotation name and also too specific - about storage. There is no concept of RBD enabled or not for volsync replication.

How about:

PreferVolSyncAnnontation = "drplacementcontrol.ramendr.openshift.io/is-volsync-preferred"

Which means, when we have a VRC, prefer volsync. Current code will use the volrep in this case, with this flag will use volsync.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expected some feedback on the annotation name, which is a good thing. However, the change in this PR is focused on individual workloads, even when we have VRCs. Essentially, we want to use VolSync for a specific RBD workload. Your suggestion implies that for this workload, VolSync should be preferred, and if it’s not enabled, it should fall back to VolRep. Is that what your comment was getting at?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ye, isn't this the behavior we want to add?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For CG, when a user chooses to use VolSync, there’s no fallback. This is because if CG is supported for RBD with both VolSync and VolRep, we’ll always default to VolRep. If not, the user can opt for VolSync, but Ramen won’t make that choice automatically.
As for individual PVC protection, why would we need a fallback? I’m not sure I see the need for one.

That said, I’m still exploring better options for naming the annotation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will more clear if we start with design page or documentation showing the various options and when ramen use which replication mode. Starting with documentation helps to get good APIs and names.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So for now I use the same annotation name as the variable.

UseVolSyncForPVCProtection = "drplacementcontrol.ramendr.openshift.io/use-volsync-for-pvc-protection"

@@ -251,3 +253,11 @@ func TrimToK8sResourceNameLength(name string) string {

return name
}

func IsRBDEnabledForVolSyncReplication(annotations map[string]string) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue with the name as the annotation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll address this when the previous comment is addressed.

func IsRBDEnabledForVolSyncReplication(annotations map[string]string) bool {
if annotations == nil {
return false
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to check for nil, accessing nil map works (returns nil for everything).

https://go.dev/play/p/kDPVCGVM64f

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Point taken. Thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

internal/controller/volsync/vshandler_test.go Show resolved Hide resolved
Expect(pvc.GetName()).To(Equal(rdSpec.ProtectedPVC.Name))
Expect(pvc.GetOwnerReferences()[0].Kind).To(Equal("ConfigMap"))
Expect(*pvc.Spec.VolumeMode).To(Equal(corev1.PersistentVolumeFilesystem))
Expect(k8sClient.Delete(ctx, pvc)).ToNot(HaveOccurred())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the tests fail before this line, do we run the delete line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need. If the test fails, the unit test fails and that's what we want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the created pvc has the wrong mode - we still need to delete it when the test ends. Othewise the leftover pvc can cause another test to fail or worse, succeed when the code is broken

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand what you are asking. I don't want to clean up. If the test fails in line 697, I want that line to fail for vshandler_test and no more. When I am debugging the unit test failure, I won't start from the last failure. I start from the first failure and I ignore everything else.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like cleanup after test, not part of the test. Cleanup code should always run regardless of the outcome of the test. We probably have some hook to run cleanup after a test like JustBeforeEach.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I am not sure about the best practice here. AfterEach should be what you want, but not when the test fails.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup must run in all cases. If AfterEach does this we should use it. If not we can do cleanup using defer.

Expect(pvc.GetName()).To(Equal(rdSpecForBlockPVC.ProtectedPVC.Name))
Expect(pvc.GetOwnerReferences()[0].Kind).To(Equal("ConfigMap"))
Expect(*pvc.Spec.VolumeMode).To(Equal(corev1.PersistentVolumeBlock))
Expect(k8sClient.Delete(ctx, pvc)).ToNot(HaveOccurred())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate of the previous test - we have different setup and one different assertion. Can extract the repeated test code to a helper?

Different setup:

util.AddAnnotation(owner, util.UseVolSyncForRBDAnnotation, "true")
Expect(k8sClient.Update(ctx, owner)).To(Succeed())
rdSpecForBlockPVC := rdSpec.DeepCopy()
block := corev1.PersistentVolumeBlock
rdSpecForBlockPVC.ProtectedPVC.VolumeMode = &block

Different check:

Expect(*pvc.Spec.VolumeMode).To(Equal(corev1.PersistentVolumeBlock))

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you miss this comment?

internal/controller/volsync/vshandler.go Outdated Show resolved Hide resolved
internal/controller/volsync/vshandler.go Show resolved Hide resolved
internal/controller/volsync/vshandler.go Outdated Show resolved Hide resolved
internal/controller/volsync/vshandler.go Show resolved Hide resolved
@nirs nirs requested a review from ELENAGER October 7, 2024 10:29
@BenamarMk BenamarMk force-pushed the block-volsync-support branch 4 times, most recently from ab695f1 to 018f295 Compare October 9, 2024 15:35
@@ -22,6 +22,9 @@ const (
OCMBackupLabelValue string = "ramen"

IsCGEnabledAnnotation = "drplacementcontrol.ramendr.openshift.io/is-cg-enabled"

// Annotation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is not useful - maybe describe here how this annotation change the system behavior?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed.

return annotations[IsCGEnabledAnnotation] == "true"
}

func IsRBDEnabledForVolSyncReplication(annotations map[string]string) bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename to UseVolsyncForPVCProtection()?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to IsPVCMarkedForVolSync

DoNotDeletePVCAnnotation: d.instance.GetAnnotations()[DoNotDeletePVCAnnotation],
DRPCUIDAnnotation: string(d.instance.UID),
rmnutil.IsCGEnabledAnnotation: d.instance.GetAnnotations()[rmnutil.IsCGEnabledAnnotation],
rmnutil.UseVolSyncForPVCProtection: d.instance.GetAnnotations()[rmnutil.UseVolSyncForPVCProtection],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other annotation have "Annotation" suffix. Maybe rename to "UseVolSyncAnnotation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack.

@@ -2036,8 +2034,8 @@ func (v *VSHandler) createReadOnlyPVCFromSnapshot(rd *volsyncv1alpha1.Replicatio
pvc.Spec.AccessModes = accessModes
pvc.Spec.StorageClassName = rd.Spec.RsyncTLS.StorageClassName

// Only set when initially creating
pvc.Spec.DataSource = &snapshotRef
pvc.Spec.DataSource = snapshotRef
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why snapshotRef changed? I don't see any other change that explain this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It changed to a pointer

if util.IsRBDEnabledForVolSyncReplication(v.owner.GetAnnotations()) &&
protectedPVC.VolumeMode != nil &&
*protectedPVC.VolumeMode == corev1.PersistentVolumeBlock {
volumeMode = corev1.PersistentVolumeBlock
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation here is confusing - maybe indent the conditions more to make the block more clear?

if util.IsRBDEnabledForVolSyncReplication(v.owner.GetAnnotations()) &&
        protectedPVC.VolumeMode != nil &&
		*protectedPVC.VolumeMode == corev1.PersistentVolumeBlock {
	volumeMode = corev1.PersistentVolumeBlock
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you miss this comment?

Expect(pvc.GetName()).To(Equal(rdSpec.ProtectedPVC.Name))
Expect(pvc.GetOwnerReferences()[0].Kind).To(Equal("ConfigMap"))
Expect(*pvc.Spec.VolumeMode).To(Equal(corev1.PersistentVolumeFilesystem))
Expect(k8sClient.Delete(ctx, pvc)).ToNot(HaveOccurred())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleanup must run in all cases. If AfterEach does this we should use it. If not we can do cleanup using defer.

Copy link
Member

@ShyamsundarR ShyamsundarR left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenamarMk we need a better way forward to support all PVCs as VolSync than using annotations. I am thinking we can create a DRPolicy that would only advertise Async PeerClasses that are VS only. This policy can hence be used, as VRG would be asked to protect PVCs for SCs with the VS and not VR method.

There are possibly other alternatives, but asking PVCs to be labelled would be a bit of a stretch for app devs at present. This is not the final solution I understand, but just throwing it out there for consideration in the future.

Benamar Mekhissi added 3 commits November 11, 2024 17:39
…cross relevant components

Signed-off-by: Benamar Mekhissi <[email protected]>
Signed-off-by: Benamar Mekhissi <[email protected]>
@BenamarMk
Copy link
Member Author

@BenamarMk we need a better way forward to support all PVCs as VolSync than using annotations. I am thinking we can create a DRPolicy that would only advertise Async PeerClasses that are VS only. This policy can hence be used, as VRG would be asked to protect PVCs for SCs with the VS and not VR method.

Agreed - the intent was to explore a better solution for 4.19. Using an annotation to differentiate workloads isn’t necessarily a bad approach; it provides a lightweight, simple option where the user can just check a box when creating the workload. For now, this serves as a quick way to enable RBD workloads with VolSync, and we can think of a better solution for 4.19.


break
if !pvcEnabledForVolSync {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the temporary variable? we can use:

if !util.IsPVCMarkedForVolSync(v.instance.GetAnnotations()) {
    ...
}

@@ -22,6 +22,9 @@ const (
OCMBackupLabelValue string = "ramen"

IsCGEnabledAnnotation = "drplacementcontrol.ramendr.openshift.io/is-cg-enabled"

// When this annotation is set to true, VolSync will protect RBD PVCs.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be any storage, RBD is a good example but we don't care which storage is this.

if util.IsRBDEnabledForVolSyncReplication(v.owner.GetAnnotations()) &&
protectedPVC.VolumeMode != nil &&
*protectedPVC.VolumeMode == corev1.PersistentVolumeBlock {
volumeMode = corev1.PersistentVolumeBlock
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you miss this comment?

Expect(pvc.GetName()).To(Equal(rdSpecForBlockPVC.ProtectedPVC.Name))
Expect(pvc.GetOwnerReferences()[0].Kind).To(Equal("ConfigMap"))
Expect(*pvc.Spec.VolumeMode).To(Equal(corev1.PersistentVolumeBlock))
Expect(k8sClient.Delete(ctx, pvc)).ToNot(HaveOccurred())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you miss this comment?


return nil
}
if !pvcEnabledForVolSync {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, we could eliminate the temporary variable.

@ShyamsundarR
Copy link
Member

Merging changes, open comments do not impact functionality at present.

@ShyamsundarR ShyamsundarR merged commit 1f11958 into RamenDR:main Nov 12, 2024
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants