From b048b86183ac095ef09bfeefa0c78e33f4d09324 Mon Sep 17 00:00:00 2001 From: Britania Rodriguez Reyes Date: Tue, 4 Feb 2025 10:43:17 -0800 Subject: [PATCH] add volume attachment trackability --- .../workapplier/availability_tracker.go | 19 +++++ .../workapplier/availability_tracker_test.go | 77 +++++++++++++++++++ pkg/utils/common.go | 6 ++ 3 files changed, 102 insertions(+) diff --git a/pkg/controllers/workapplier/availability_tracker.go b/pkg/controllers/workapplier/availability_tracker.go index afe7d88f1..87bfea645 100644 --- a/pkg/controllers/workapplier/availability_tracker.go +++ b/pkg/controllers/workapplier/availability_tracker.go @@ -12,6 +12,7 @@ import ( appv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" + storagev1 "k8s.io/api/storage/v1" apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -83,6 +84,8 @@ func trackInMemberClusterObjAvailabilityByGVR( return trackCRDAvailability(inMemberClusterObj) case utils.PodDisruptionBudgetGVR: return trackPDBAvailability(inMemberClusterObj) + case utils.VolumeAttachmentGVR: + return trackVolumeAttachmentAvailability(inMemberClusterObj) default: if isDataResource(*gvr) { klog.V(2).InfoS("The object from the member cluster is a data object, consider it to be immediately available", @@ -247,6 +250,22 @@ func trackPDBAvailability(curObj *unstructured.Unstructured) (ManifestProcessing return ManifestProcessingAvailabilityResultTypeNotYetAvailable, nil } +// trackVolumeAttachmentAvailability tracks the availability of a volume attachment in the member cluster +func trackVolumeAttachmentAvailability(curObj *unstructured.Unstructured) (ManifestProcessingAvailabilityResultType, error) { + var volumeAttachment storagev1.VolumeAttachment + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(curObj.Object, &volumeAttachment); err != nil { + return ManifestProcessingAvailabilityResultTypeFailed, controller.NewUnexpectedBehaviorError(err) + } + + if volumeAttachment.Status.Attached && volumeAttachment.Status.DetachError == nil { + klog.V(2).InfoS("VolumeAttachment is available", "volumeAttachment", klog.KObj(curObj)) + return ManifestProcessingAvailabilityResultTypeAvailable, nil + } + + klog.V(2).InfoS("Still need to wait for PodDisruptionBudget to be available", "pdb", klog.KObj(curObj)) + return ManifestProcessingAvailabilityResultTypeNotYetAvailable, nil +} + // isDataResource checks if the resource is a data resource; such resources are // available immediately after creation. func isDataResource(gvr schema.GroupVersionResource) bool { diff --git a/pkg/controllers/workapplier/availability_tracker_test.go b/pkg/controllers/workapplier/availability_tracker_test.go index c41c2fcc9..cf64e9fde 100644 --- a/pkg/controllers/workapplier/availability_tracker_test.go +++ b/pkg/controllers/workapplier/availability_tracker_test.go @@ -157,6 +157,23 @@ var ( MinAvailable: &minAvailable, }, } + + volumeAttachmentTemplate = &storagev1.VolumeAttachment{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "storage.k8s.io/v1", + Kind: "VolumeAttachment", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test-volume-attachment", + }, + Spec: storagev1.VolumeAttachmentSpec{ + Attacher: "my-attacher", + NodeName: "my-node", + Source: storagev1.VolumeAttachmentSource{ + PersistentVolumeName: ptr.To("my-pv"), + }, + }, + } ) // TestTrackDeploymentAvailability tests the trackDeploymentAvailability function. @@ -775,6 +792,66 @@ func TestTrackPDBAvailability(t *testing.T) { } } +func TestTrackVolumeAttachmentAvailability(t *testing.T) { + availableVolumeAttachment := volumeAttachmentTemplate.DeepCopy() + availableVolumeAttachment.Status = storagev1.VolumeAttachmentStatus{ + Attached: true, + } + + unavailableVolumeAttachmentAttachError := volumeAttachmentTemplate.DeepCopy() + unavailableVolumeAttachmentAttachError.Status = storagev1.VolumeAttachmentStatus{ + Attached: false, + AttachError: &storagev1.VolumeError{ + Time: metav1.Now(), + Message: "attacher error", + }, + } + + unavailableVolumeAttachmentDetachError := volumeAttachmentTemplate.DeepCopy() + unavailableVolumeAttachmentDetachError.Status = storagev1.VolumeAttachmentStatus{ + Attached: true, + DetachError: &storagev1.VolumeError{ + Time: metav1.Now(), + Message: "detacher error", + }, + } + + testCases := []struct { + name string + volumeAttachment *storagev1.VolumeAttachment + wantManifestProcessingAvailabilityResultType ManifestProcessingAvailabilityResultType + }{ + { + name: "available volume attachment", + volumeAttachment: availableVolumeAttachment, + wantManifestProcessingAvailabilityResultType: ManifestProcessingAvailabilityResultTypeAvailable, + }, + { + name: "unavailable attachment (attach error)", + volumeAttachment: unavailableVolumeAttachmentAttachError, + wantManifestProcessingAvailabilityResultType: ManifestProcessingAvailabilityResultTypeNotYetAvailable, + }, + { + name: "unavailable attachment (detach error)", + volumeAttachment: unavailableVolumeAttachmentDetachError, + wantManifestProcessingAvailabilityResultType: ManifestProcessingAvailabilityResultTypeNotYetAvailable, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + gotResTyp, err := trackVolumeAttachmentAvailability(toUnstructured(t, tc.volumeAttachment)) + if err != nil { + t.Fatalf("trackVolumeAttachmentAvailability() = %v, want no error", err) + } + if gotResTyp != tc.wantManifestProcessingAvailabilityResultType { + t.Errorf("manifestProcessingAvailabilityResultType = %v, want %v", gotResTyp, tc.wantManifestProcessingAvailabilityResultType) + } + }) + } + +} + // TestTrackInMemberClusterObjAvailabilityByGVR tests the trackInMemberClusterObjAvailabilityByGVR function. func TestTrackInMemberClusterObjAvailabilityByGVR(t *testing.T) { availableDeploy := deploy.DeepCopy() diff --git a/pkg/utils/common.go b/pkg/utils/common.go index 6d5badaf9..b5d9c3c0c 100644 --- a/pkg/utils/common.go +++ b/pkg/utils/common.go @@ -394,6 +394,12 @@ var ( Resource: "validatingwebhookconfigurations", } + VolumeAttachmentGVR = schema.GroupVersionResource{ + Group: storagev1.SchemeGroupVersion.Group, + Version: storagev1.SchemeGroupVersion.Version, + Resource: "volumeattachments", + } + ClusterResourceOverrideSnapshotKind = schema.GroupVersionKind{ Group: placementv1alpha1.GroupVersion.Group, Version: placementv1alpha1.GroupVersion.Version,