From d37bf90760c6773d21583a7fa6a549dcd1f27577 Mon Sep 17 00:00:00 2001 From: Leonardo Cecchi Date: Mon, 15 Apr 2024 18:18:58 +0200 Subject: [PATCH] Link PVCs and PVs in VolumeGroupSnapshot and VolumeGroupSnapshotContent This use the update API to set `persistentVolumeClaimRef` in `VolumeGroupSnapshot` and `persistentVolumeName` in `VolumeGroupSnapshotContent` to the corresponding objects. This makes restoring volumes from a VolumeGroupSnapshot easier. Related: #969 --- .../groupsnapshot_controller_helper.go | 25 ++++- .../groupsnapshot_helper.go | 62 ++++++++-- pkg/utils/pvs.go | 36 ++++++ pkg/utils/pvs_test.go | 106 ++++++++++++++++++ 4 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 pkg/utils/pvs.go create mode 100644 pkg/utils/pvs_test.go diff --git a/pkg/common-controller/groupsnapshot_controller_helper.go b/pkg/common-controller/groupsnapshot_controller_helper.go index c66ff4814..740bd9c11 100644 --- a/pkg/common-controller/groupsnapshot_controller_helper.go +++ b/pkg/common-controller/groupsnapshot_controller_helper.go @@ -567,14 +567,35 @@ func (ctrl *csiSnapshotCommonController) updateGroupSnapshotStatus(groupSnapshot var pvcVolumeSnapshotRefList []crdv1alpha1.PVCVolumeSnapshotPair if groupSnapshotContent.Status != nil && len(groupSnapshotContent.Status.PVVolumeSnapshotContentList) != 0 { for _, contentRef := range groupSnapshotContent.Status.PVVolumeSnapshotContentList { - groupSnapshotContent, err := ctrl.contentLister.Get(contentRef.VolumeSnapshotContentRef.Name) + pv, err := ctrl.client.CoreV1().PersistentVolumes().Get(context.TODO(), contentRef.PersistentVolumeRef.Name, metav1.GetOptions{}) + if err != nil { + klog.Errorf( + "updateGroupSnapshotStatus[%s]: unable to get PV [%s] from the API server", + utils.GroupSnapshotKey(groupSnapshot), + contentRef.PersistentVolumeRef.Name) + } + + var pvcReference v1.LocalObjectReference + if pv != nil { + if pv.Spec.ClaimRef != nil { + pvcReference.Name = pv.Spec.ClaimRef.Name + } else { + klog.Errorf( + "updateGroupSnapshotStatus[%s]: PV [%s] is not bound", + utils.GroupSnapshotKey(groupSnapshot), + contentRef.PersistentVolumeRef.Name) + } + } + + volumeSnapshotContent, err := ctrl.contentLister.Get(contentRef.VolumeSnapshotContentRef.Name) if err != nil { return nil, fmt.Errorf("failed to get group snapshot content %s from group snapshot content store: %v", contentRef.VolumeSnapshotContentRef.Name, err) } pvcVolumeSnapshotRefList = append(pvcVolumeSnapshotRefList, crdv1alpha1.PVCVolumeSnapshotPair{ VolumeSnapshotRef: v1.LocalObjectReference{ - Name: groupSnapshotContent.Spec.VolumeSnapshotRef.Name, + Name: volumeSnapshotContent.Spec.VolumeSnapshotRef.Name, }, + PersistentVolumeClaimRef: pvcReference, }) } } diff --git a/pkg/sidecar-controller/groupsnapshot_helper.go b/pkg/sidecar-controller/groupsnapshot_helper.go index c50bb13e6..1eaf4ec82 100644 --- a/pkg/sidecar-controller/groupsnapshot_helper.go +++ b/pkg/sidecar-controller/groupsnapshot_helper.go @@ -35,6 +35,13 @@ import ( "github.com/kubernetes-csi/external-snapshotter/v7/pkg/utils" ) +// snapshotContentNameVolumeHandlePair represent the link between a VolumeSnapshotContent and +// the handle of the volume that was snapshotted +type snapshotContentNameVolumeHandlePair struct { + snapshotContentName string + volumeHandle string +} + func (ctrl *csiSnapshotSideCarController) storeGroupSnapshotContentUpdate(groupSnapshotContent interface{}) (bool, error) { return utils.StoreObjectUpdate(ctrl.groupSnapshotContentStore, groupSnapshotContent, "groupsnapshotcontent") } @@ -430,7 +437,7 @@ func (ctrl *csiSnapshotSideCarController) createGroupSnapshotWrapper(groupSnapsh return groupSnapshotContent, fmt.Errorf("failed to get secret reference for group snapshot content %s: %v", groupSnapshotContent.Name, err) } // Create individual snapshots and snapshot contents - var snapshotContentNames []string + var snapshotContentLinks []snapshotContentNameVolumeHandlePair for _, snapshot := range snapshots { volumeSnapshotContentName := GetSnapshotContentNameForVolumeGroupSnapshotContent(string(groupSnapshotContent.UID), snapshot.SourceVolumeId) volumeSnapshotName := GetSnapshotNameForVolumeGroupSnapshotContent(string(groupSnapshotContent.UID), snapshot.SourceVolumeId) @@ -484,7 +491,10 @@ func (ctrl *csiSnapshotSideCarController) createGroupSnapshotWrapper(groupSnapsh if err != nil { return groupSnapshotContent, err } - snapshotContentNames = append(snapshotContentNames, vsc.Name) + snapshotContentLinks = append(snapshotContentLinks, snapshotContentNameVolumeHandlePair{ + snapshotContentName: vsc.Name, + volumeHandle: snapshot.SourceVolumeId, + }) _, err = ctrl.clientset.SnapshotV1().VolumeSnapshots(volumeSnapshotNamespace).Create(context.TODO(), volumeSnapshot, metav1.CreateOptions{}) if err != nil { @@ -497,7 +507,7 @@ func (ctrl *csiSnapshotSideCarController) createGroupSnapshotWrapper(groupSnapsh } } - newGroupSnapshotContent, err := ctrl.updateGroupSnapshotContentStatus(groupSnapshotContent, groupSnapshotID, readyToUse, creationTime.UnixNano(), snapshotContentNames) + newGroupSnapshotContent, err := ctrl.updateGroupSnapshotContentStatus(groupSnapshotContent, groupSnapshotID, readyToUse, creationTime.UnixNano(), snapshotContentLinks) if err != nil { klog.Errorf("error updating status for volume group snapshot content %s: %v.", groupSnapshotContent.Name, err) return groupSnapshotContent, fmt.Errorf("error updating status for volume group snapshot content %s: %v", groupSnapshotContent.Name, err) @@ -633,7 +643,8 @@ func (ctrl *csiSnapshotSideCarController) updateGroupSnapshotContentStatus( groupSnapshotHandle string, readyToUse bool, createdAt int64, - snapshotContentNames []string) (*crdv1alpha1.VolumeGroupSnapshotContent, error) { + snapshotContentLinks []snapshotContentNameVolumeHandlePair, +) (*crdv1alpha1.VolumeGroupSnapshotContent, error) { klog.V(5).Infof("updateGroupSnapshotContentStatus: updating VolumeGroupSnapshotContent [%s], groupSnapshotHandle %s, readyToUse %v, createdAt %v", groupSnapshotContent.Name, groupSnapshotHandle, readyToUse, createdAt) groupSnapshotContentObj, err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshotContents().Get(context.TODO(), groupSnapshotContent.Name, metav1.GetOptions{}) @@ -641,6 +652,11 @@ func (ctrl *csiSnapshotSideCarController) updateGroupSnapshotContentStatus( return nil, fmt.Errorf("error get group snapshot content %s from api server: %v", groupSnapshotContent.Name, err) } + pvs, err := ctrl.client.CoreV1().PersistentVolumes().List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("error get PersistentVolumes list from API server: %v", err) + } + var newStatus *crdv1alpha1.VolumeGroupSnapshotContentStatus updated := false if groupSnapshotContentObj.Status == nil { @@ -649,10 +665,24 @@ func (ctrl *csiSnapshotSideCarController) updateGroupSnapshotContentStatus( ReadyToUse: &readyToUse, CreationTime: &createdAt, } - for _, name := range snapshotContentNames { + for _, snapshotContentLink := range snapshotContentLinks { + pv := utils.GetPersistentVolumeFromHandle(pvs, groupSnapshotContent.Spec.Driver, snapshotContentLink.volumeHandle) + pvName := "" + if pv != nil { + pvName = pv.Name + } else { + klog.Errorf( + "updateGroupSnapshotContentStatus: unable to find PV for volumeHandle:[%s] and CSI driver:[%s]", + snapshotContentLink.volumeHandle, + groupSnapshotContent.Spec.Driver) + } + newStatus.PVVolumeSnapshotContentList = append(newStatus.PVVolumeSnapshotContentList, crdv1alpha1.PVVolumeSnapshotContentPair{ VolumeSnapshotContentRef: v1.LocalObjectReference{ - Name: name, + Name: snapshotContentLink.snapshotContentName, + }, + PersistentVolumeRef: v1.LocalObjectReference{ + Name: pvName, }, }) } @@ -675,10 +705,24 @@ func (ctrl *csiSnapshotSideCarController) updateGroupSnapshotContentStatus( updated = true } if len(newStatus.PVVolumeSnapshotContentList) == 0 { - for _, name := range snapshotContentNames { + for _, snapshotContentLink := range snapshotContentLinks { + pv := utils.GetPersistentVolumeFromHandle(pvs, groupSnapshotContent.Spec.Driver, snapshotContentLink.volumeHandle) + pvName := "" + if pv != nil { + pvName = pv.Name + } else { + klog.Errorf( + "updateGroupSnapshotContentStatus: unable to find PV for volumeHandle:[%s] and CSI driver:[%s] (existing status)", + snapshotContentLink.volumeHandle, + groupSnapshotContent.Spec.Driver) + } + newStatus.PVVolumeSnapshotContentList = append(newStatus.PVVolumeSnapshotContentList, crdv1alpha1.PVVolumeSnapshotContentPair{ VolumeSnapshotContentRef: v1.LocalObjectReference{ - Name: name, + Name: snapshotContentLink.snapshotContentName, + }, + PersistentVolumeRef: v1.LocalObjectReference{ + Name: pvName, }, }) } @@ -842,7 +886,7 @@ func (ctrl *csiSnapshotSideCarController) checkandUpdateGroupSnapshotContentStat } // TODO: Get a reference to snapshot contents for this volume group snapshot - updatedContent, err := ctrl.updateGroupSnapshotContentStatus(groupSnapshotContent, groupSnapshotID, readyToUse, creationTime.UnixNano(), []string{}) + updatedContent, err := ctrl.updateGroupSnapshotContentStatus(groupSnapshotContent, groupSnapshotID, readyToUse, creationTime.UnixNano(), []snapshotContentNameVolumeHandlePair{}) if err != nil { return groupSnapshotContent, err } diff --git a/pkg/utils/pvs.go b/pkg/utils/pvs.go new file mode 100644 index 000000000..675488a37 --- /dev/null +++ b/pkg/utils/pvs.go @@ -0,0 +1,36 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import v1 "k8s.io/api/core/v1" + +// GetPersistentVolumeFromHandle looks for the PV having a certain CSI driver name +// and corresponding to a volume with a given handle, in a PV List. +// If the PV is not found, returns nil +func GetPersistentVolumeFromHandle(pvList *v1.PersistentVolumeList, driverName, volumeHandle string) *v1.PersistentVolume { + for i := range pvList.Items { + if pvList.Items[i].Spec.CSI == nil { + continue + } + + if pvList.Items[i].Spec.CSI.Driver == driverName && pvList.Items[i].Spec.CSI.VolumeHandle == volumeHandle { + return &pvList.Items[i] + } + } + + return nil +} diff --git a/pkg/utils/pvs_test.go b/pkg/utils/pvs_test.go new file mode 100644 index 000000000..f7fff7ff0 --- /dev/null +++ b/pkg/utils/pvs_test.go @@ -0,0 +1,106 @@ +/* +Copyright 2024 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "testing" + + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestGetPersistentVolumeFromHandle(t *testing.T) { + testDriverName := "hostpath.csi.k8s.io" + testVolumeHandle := "df39ea9e-1296-11ef-adde-baf37ed30dae" + testPvName := "pv-name" + + pvListTest := v1.PersistentVolumeList{ + Items: []v1.PersistentVolume{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: testPvName, + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + CSI: &v1.CSIPersistentVolumeSource{ + Driver: testDriverName, + VolumeHandle: testVolumeHandle, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pv-no-csi", + }, + Spec: v1.PersistentVolumeSpec{ + PersistentVolumeSource: v1.PersistentVolumeSource{ + HostPath: &v1.HostPathVolumeSource{}, + }, + }, + }, + }, + } + + tests := []struct { + testName string + driverName string + volumeHandle string + pvList v1.PersistentVolumeList + pvName string + }{ + { + testName: "empty-pv-list", + driverName: testDriverName, + volumeHandle: testVolumeHandle, + pvName: "", + }, + { + testName: "pv-in-list", + driverName: testDriverName, + volumeHandle: testVolumeHandle, + pvList: pvListTest, + pvName: testPvName, + }, + { + testName: "not-existing-volume-handle", + driverName: testDriverName, + volumeHandle: "not-existing-volume-handle", + pvList: pvListTest, + pvName: "", + }, + { + testName: "invalid-driver-name", + driverName: "invalid-driver-name", + volumeHandle: testVolumeHandle, + pvList: pvListTest, + pvName: "", + }, + } + for _, tt := range tests { + got := GetPersistentVolumeFromHandle(&tt.pvList, tt.driverName, tt.volumeHandle) + if got == nil { + if len(tt.pvName) != 0 { + t.Errorf("%v: GetPersistentVolumeFromHandle = %v WANT %v", tt.testName, got, tt.pvName) + } + } else { + if tt.pvName != got.Name { + t.Errorf("%v: GetPersistentVolumeFromHandle = %v WANT %v", tt.testName, got.Name, tt.pvName) + } + } + } +}