Skip to content

Commit 3f8b79d

Browse files
committed
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: kubernetes-csi#969
1 parent 22117ab commit 3f8b79d

File tree

4 files changed

+226
-11
lines changed

4 files changed

+226
-11
lines changed

pkg/common-controller/groupsnapshot_controller_helper.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -567,14 +567,43 @@ func (ctrl *csiSnapshotCommonController) updateGroupSnapshotStatus(groupSnapshot
567567
var pvcVolumeSnapshotRefList []crdv1alpha1.PVCVolumeSnapshotPair
568568
if groupSnapshotContent.Status != nil && len(groupSnapshotContent.Status.PVVolumeSnapshotContentList) != 0 {
569569
for _, contentRef := range groupSnapshotContent.Status.PVVolumeSnapshotContentList {
570-
groupSnapshotContent, err := ctrl.contentLister.Get(contentRef.VolumeSnapshotContentRef.Name)
570+
var pvcReference v1.LocalObjectReference
571+
pv, err := ctrl.client.CoreV1().PersistentVolumes().Get(context.TODO(), contentRef.PersistentVolumeRef.Name, metav1.GetOptions{})
572+
if err != nil {
573+
if apierrs.IsNotFound(err) {
574+
klog.Errorf(
575+
"updateGroupSnapshotStatus[%s]: PV [%s] not found",
576+
utils.GroupSnapshotKey(groupSnapshot),
577+
contentRef.PersistentVolumeRef.Name,
578+
)
579+
} else {
580+
klog.Errorf(
581+
"updateGroupSnapshotStatus[%s]: unable to get PV [%s] from the API server: %q",
582+
utils.GroupSnapshotKey(groupSnapshot),
583+
contentRef.PersistentVolumeRef.Name,
584+
err.Error(),
585+
)
586+
}
587+
} else {
588+
if pv.Spec.ClaimRef != nil {
589+
pvcReference.Name = pv.Spec.ClaimRef.Name
590+
} else {
591+
klog.Errorf(
592+
"updateGroupSnapshotStatus[%s]: PV [%s] is not bound",
593+
utils.GroupSnapshotKey(groupSnapshot),
594+
contentRef.PersistentVolumeRef.Name)
595+
}
596+
}
597+
598+
volumeSnapshotContent, err := ctrl.contentLister.Get(contentRef.VolumeSnapshotContentRef.Name)
571599
if err != nil {
572600
return nil, fmt.Errorf("failed to get group snapshot content %s from group snapshot content store: %v", contentRef.VolumeSnapshotContentRef.Name, err)
573601
}
574602
pvcVolumeSnapshotRefList = append(pvcVolumeSnapshotRefList, crdv1alpha1.PVCVolumeSnapshotPair{
575603
VolumeSnapshotRef: v1.LocalObjectReference{
576-
Name: groupSnapshotContent.Spec.VolumeSnapshotRef.Name,
604+
Name: volumeSnapshotContent.Spec.VolumeSnapshotRef.Name,
577605
},
606+
PersistentVolumeClaimRef: pvcReference,
578607
})
579608
}
580609
}

pkg/sidecar-controller/groupsnapshot_helper.go

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ import (
3535
"github.com/kubernetes-csi/external-snapshotter/v7/pkg/utils"
3636
)
3737

38+
// snapshotContentNameVolumeHandlePair represent the link between a VolumeSnapshotContent and
39+
// the handle of the volume that was snapshotted
40+
type snapshotContentNameVolumeHandlePair struct {
41+
snapshotContentName string
42+
volumeHandle string
43+
}
44+
3845
func (ctrl *csiSnapshotSideCarController) storeGroupSnapshotContentUpdate(groupSnapshotContent interface{}) (bool, error) {
3946
return utils.StoreObjectUpdate(ctrl.groupSnapshotContentStore, groupSnapshotContent, "groupsnapshotcontent")
4047
}
@@ -430,7 +437,7 @@ func (ctrl *csiSnapshotSideCarController) createGroupSnapshotWrapper(groupSnapsh
430437
return groupSnapshotContent, fmt.Errorf("failed to get secret reference for group snapshot content %s: %v", groupSnapshotContent.Name, err)
431438
}
432439
// Create individual snapshots and snapshot contents
433-
var snapshotContentNames []string
440+
var snapshotContentLinks []snapshotContentNameVolumeHandlePair
434441
for _, snapshot := range snapshots {
435442
volumeSnapshotContentName := GetSnapshotContentNameForVolumeGroupSnapshotContent(string(groupSnapshotContent.UID), snapshot.SourceVolumeId)
436443
volumeSnapshotName := GetSnapshotNameForVolumeGroupSnapshotContent(string(groupSnapshotContent.UID), snapshot.SourceVolumeId)
@@ -484,7 +491,10 @@ func (ctrl *csiSnapshotSideCarController) createGroupSnapshotWrapper(groupSnapsh
484491
if err != nil {
485492
return groupSnapshotContent, err
486493
}
487-
snapshotContentNames = append(snapshotContentNames, vsc.Name)
494+
snapshotContentLinks = append(snapshotContentLinks, snapshotContentNameVolumeHandlePair{
495+
snapshotContentName: vsc.Name,
496+
volumeHandle: snapshot.SourceVolumeId,
497+
})
488498

489499
_, err = ctrl.clientset.SnapshotV1().VolumeSnapshots(volumeSnapshotNamespace).Create(context.TODO(), volumeSnapshot, metav1.CreateOptions{})
490500
if err != nil {
@@ -497,7 +507,7 @@ func (ctrl *csiSnapshotSideCarController) createGroupSnapshotWrapper(groupSnapsh
497507
}
498508
}
499509

500-
newGroupSnapshotContent, err := ctrl.updateGroupSnapshotContentStatus(groupSnapshotContent, groupSnapshotID, readyToUse, creationTime.UnixNano(), snapshotContentNames)
510+
newGroupSnapshotContent, err := ctrl.updateGroupSnapshotContentStatus(groupSnapshotContent, groupSnapshotID, readyToUse, creationTime.UnixNano(), snapshotContentLinks)
501511
if err != nil {
502512
klog.Errorf("error updating status for volume group snapshot content %s: %v.", groupSnapshotContent.Name, err)
503513
return groupSnapshotContent, fmt.Errorf("error updating status for volume group snapshot content %s: %v", groupSnapshotContent.Name, err)
@@ -633,14 +643,20 @@ func (ctrl *csiSnapshotSideCarController) updateGroupSnapshotContentStatus(
633643
groupSnapshotHandle string,
634644
readyToUse bool,
635645
createdAt int64,
636-
snapshotContentNames []string) (*crdv1alpha1.VolumeGroupSnapshotContent, error) {
646+
snapshotContentLinks []snapshotContentNameVolumeHandlePair,
647+
) (*crdv1alpha1.VolumeGroupSnapshotContent, error) {
637648
klog.V(5).Infof("updateGroupSnapshotContentStatus: updating VolumeGroupSnapshotContent [%s], groupSnapshotHandle %s, readyToUse %v, createdAt %v", groupSnapshotContent.Name, groupSnapshotHandle, readyToUse, createdAt)
638649

639650
groupSnapshotContentObj, err := ctrl.clientset.GroupsnapshotV1alpha1().VolumeGroupSnapshotContents().Get(context.TODO(), groupSnapshotContent.Name, metav1.GetOptions{})
640651
if err != nil {
641652
return nil, fmt.Errorf("error get group snapshot content %s from api server: %v", groupSnapshotContent.Name, err)
642653
}
643654

655+
pvs, err := ctrl.client.CoreV1().PersistentVolumes().List(context.TODO(), metav1.ListOptions{})
656+
if err != nil {
657+
return nil, fmt.Errorf("error get PersistentVolumes list from API server: %v", err)
658+
}
659+
644660
var newStatus *crdv1alpha1.VolumeGroupSnapshotContentStatus
645661
updated := false
646662
if groupSnapshotContentObj.Status == nil {
@@ -649,10 +665,24 @@ func (ctrl *csiSnapshotSideCarController) updateGroupSnapshotContentStatus(
649665
ReadyToUse: &readyToUse,
650666
CreationTime: &createdAt,
651667
}
652-
for _, name := range snapshotContentNames {
668+
for _, snapshotContentLink := range snapshotContentLinks {
669+
pv := utils.GetPersistentVolumeFromHandle(pvs, groupSnapshotContent.Spec.Driver, snapshotContentLink.volumeHandle)
670+
pvName := ""
671+
if pv != nil {
672+
pvName = pv.Name
673+
} else {
674+
klog.Errorf(
675+
"updateGroupSnapshotContentStatus: unable to find PV for volumeHandle:[%s] and CSI driver:[%s]",
676+
snapshotContentLink.volumeHandle,
677+
groupSnapshotContent.Spec.Driver)
678+
}
679+
653680
newStatus.PVVolumeSnapshotContentList = append(newStatus.PVVolumeSnapshotContentList, crdv1alpha1.PVVolumeSnapshotContentPair{
654681
VolumeSnapshotContentRef: v1.LocalObjectReference{
655-
Name: name,
682+
Name: snapshotContentLink.snapshotContentName,
683+
},
684+
PersistentVolumeRef: v1.LocalObjectReference{
685+
Name: pvName,
656686
},
657687
})
658688
}
@@ -675,10 +705,24 @@ func (ctrl *csiSnapshotSideCarController) updateGroupSnapshotContentStatus(
675705
updated = true
676706
}
677707
if len(newStatus.PVVolumeSnapshotContentList) == 0 {
678-
for _, name := range snapshotContentNames {
708+
for _, snapshotContentLink := range snapshotContentLinks {
709+
pv := utils.GetPersistentVolumeFromHandle(pvs, groupSnapshotContent.Spec.Driver, snapshotContentLink.volumeHandle)
710+
pvName := ""
711+
if pv != nil {
712+
pvName = pv.Name
713+
} else {
714+
klog.Errorf(
715+
"updateGroupSnapshotContentStatus: unable to find PV for volumeHandle:[%s] and CSI driver:[%s] (existing status)",
716+
snapshotContentLink.volumeHandle,
717+
groupSnapshotContent.Spec.Driver)
718+
}
719+
679720
newStatus.PVVolumeSnapshotContentList = append(newStatus.PVVolumeSnapshotContentList, crdv1alpha1.PVVolumeSnapshotContentPair{
680721
VolumeSnapshotContentRef: v1.LocalObjectReference{
681-
Name: name,
722+
Name: snapshotContentLink.snapshotContentName,
723+
},
724+
PersistentVolumeRef: v1.LocalObjectReference{
725+
Name: pvName,
682726
},
683727
})
684728
}
@@ -842,7 +886,7 @@ func (ctrl *csiSnapshotSideCarController) checkandUpdateGroupSnapshotContentStat
842886
}
843887

844888
// TODO: Get a reference to snapshot contents for this volume group snapshot
845-
updatedContent, err := ctrl.updateGroupSnapshotContentStatus(groupSnapshotContent, groupSnapshotID, readyToUse, creationTime.UnixNano(), []string{})
889+
updatedContent, err := ctrl.updateGroupSnapshotContentStatus(groupSnapshotContent, groupSnapshotID, readyToUse, creationTime.UnixNano(), []snapshotContentNameVolumeHandlePair{})
846890
if err != nil {
847891
return groupSnapshotContent, err
848892
}

pkg/utils/pvs.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package utils
18+
19+
import v1 "k8s.io/api/core/v1"
20+
21+
// GetPersistentVolumeFromHandle looks for the PV having a certain CSI driver name
22+
// and corresponding to a volume with a given handle, in a PV List.
23+
// If the PV is not found, returns nil
24+
func GetPersistentVolumeFromHandle(pvList *v1.PersistentVolumeList, driverName, volumeHandle string) *v1.PersistentVolume {
25+
for i := range pvList.Items {
26+
if pvList.Items[i].Spec.CSI == nil {
27+
continue
28+
}
29+
30+
if pvList.Items[i].Spec.CSI.Driver == driverName && pvList.Items[i].Spec.CSI.VolumeHandle == volumeHandle {
31+
return &pvList.Items[i]
32+
}
33+
}
34+
35+
return nil
36+
}

pkg/utils/pvs_test.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package utils
18+
19+
import (
20+
"testing"
21+
22+
v1 "k8s.io/api/core/v1"
23+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24+
)
25+
26+
func TestGetPersistentVolumeFromHandle(t *testing.T) {
27+
testDriverName := "hostpath.csi.k8s.io"
28+
testVolumeHandle := "df39ea9e-1296-11ef-adde-baf37ed30dae"
29+
testPvName := "pv-name"
30+
31+
pvListTest := v1.PersistentVolumeList{
32+
Items: []v1.PersistentVolume{
33+
{
34+
ObjectMeta: metav1.ObjectMeta{
35+
Name: testPvName,
36+
},
37+
Spec: v1.PersistentVolumeSpec{
38+
PersistentVolumeSource: v1.PersistentVolumeSource{
39+
CSI: &v1.CSIPersistentVolumeSource{
40+
Driver: testDriverName,
41+
VolumeHandle: testVolumeHandle,
42+
},
43+
},
44+
},
45+
},
46+
{
47+
ObjectMeta: metav1.ObjectMeta{
48+
Name: "pv-no-csi",
49+
},
50+
Spec: v1.PersistentVolumeSpec{
51+
PersistentVolumeSource: v1.PersistentVolumeSource{
52+
HostPath: &v1.HostPathVolumeSource{},
53+
},
54+
},
55+
},
56+
},
57+
}
58+
59+
tests := []struct {
60+
testName string
61+
driverName string
62+
volumeHandle string
63+
pvList v1.PersistentVolumeList
64+
pvName string
65+
}{
66+
{
67+
testName: "empty-pv-list",
68+
driverName: testDriverName,
69+
volumeHandle: testVolumeHandle,
70+
pvName: "",
71+
},
72+
{
73+
testName: "pv-in-list",
74+
driverName: testDriverName,
75+
volumeHandle: testVolumeHandle,
76+
pvList: pvListTest,
77+
pvName: testPvName,
78+
},
79+
{
80+
testName: "not-existing-volume-handle",
81+
driverName: testDriverName,
82+
volumeHandle: "not-existing-volume-handle",
83+
pvList: pvListTest,
84+
pvName: "",
85+
},
86+
{
87+
testName: "invalid-driver-name",
88+
driverName: "invalid-driver-name",
89+
volumeHandle: testVolumeHandle,
90+
pvList: pvListTest,
91+
pvName: "",
92+
},
93+
}
94+
for _, tt := range tests {
95+
got := GetPersistentVolumeFromHandle(&tt.pvList, tt.driverName, tt.volumeHandle)
96+
if got == nil {
97+
if len(tt.pvName) != 0 {
98+
t.Errorf("%v: GetPersistentVolumeFromHandle = %v WANT %v", tt.testName, got, tt.pvName)
99+
}
100+
} else {
101+
if tt.pvName != got.Name {
102+
t.Errorf("%v: GetPersistentVolumeFromHandle = %v WANT %v", tt.testName, got.Name, tt.pvName)
103+
}
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)