Skip to content

Commit 8ba6ab7

Browse files
authored
Merge pull request #526 from ggriffiths/updatecontentstatuserror_patch
Use JSON patch for many VolumeSnapshot and VolumeSnapshotContent updates
2 parents 73f3def + ad7dfe6 commit 8ba6ab7

File tree

12 files changed

+347
-58
lines changed

12 files changed

+347
-58
lines changed

deploy/kubernetes/csi-snapshotter/rbac-csi-snapshotter.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ rules:
3535
verbs: ["get", "list", "watch"]
3636
- apiGroups: ["snapshot.storage.k8s.io"]
3737
resources: ["volumesnapshotcontents"]
38-
verbs: ["create", "get", "list", "watch", "update", "delete"]
38+
verbs: ["create", "get", "list", "watch", "update", "delete", "patch"]
3939
- apiGroups: ["snapshot.storage.k8s.io"]
4040
resources: ["volumesnapshotcontents/status"]
41-
verbs: ["update"]
41+
verbs: ["update", "patch"]
4242

4343
---
4444
kind: ClusterRoleBinding

deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@ rules:
3434
verbs: ["get", "list", "watch"]
3535
- apiGroups: ["snapshot.storage.k8s.io"]
3636
resources: ["volumesnapshotcontents"]
37-
verbs: ["create", "get", "list", "watch", "update", "delete"]
37+
verbs: ["create", "get", "list", "watch", "update", "delete", "patch"]
38+
- apiGroups: ["snapshot.storage.k8s.io"]
39+
resources: ["volumesnapshotcontents/status"]
40+
verbs: ["patch"]
3841
- apiGroups: ["snapshot.storage.k8s.io"]
3942
resources: ["volumesnapshots"]
40-
verbs: ["get", "list", "watch", "update"]
43+
verbs: ["get", "list", "watch", "update", "patch"]
4144
- apiGroups: ["snapshot.storage.k8s.io"]
4245
resources: ["volumesnapshots/status"]
43-
verbs: ["update"]
46+
verbs: ["update", "patch"]
4447

4548
---
4649
kind: ClusterRoleBinding

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.16
44

55
require (
66
github.com/container-storage-interface/spec v1.5.0
7+
github.com/evanphx/json-patch v4.11.0+incompatible
78
github.com/fsnotify/fsnotify v1.4.9
89
github.com/golang/mock v1.4.4
910
github.com/golang/protobuf v1.5.2

pkg/common-controller/framework_test.go

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package common_controller
1818

1919
import (
20+
"encoding/json"
2021
"errors"
2122
"fmt"
2223
"net/http"
@@ -29,8 +30,7 @@ import (
2930
"testing"
3031
"time"
3132

32-
"k8s.io/client-go/util/workqueue"
33-
33+
jsonpatch "github.com/evanphx/json-patch"
3434
crdv1 "github.com/kubernetes-csi/external-snapshotter/client/v4/apis/volumesnapshot/v1"
3535
clientset "github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned"
3636
"github.com/kubernetes-csi/external-snapshotter/client/v4/clientset/versioned/fake"
@@ -55,6 +55,7 @@ import (
5555
core "k8s.io/client-go/testing"
5656
"k8s.io/client-go/tools/cache"
5757
"k8s.io/client-go/tools/record"
58+
"k8s.io/client-go/util/workqueue"
5859
klog "k8s.io/klog/v2"
5960
)
6061

@@ -258,6 +259,46 @@ func (r *snapshotReactor) React(action core.Action) (handled bool, ret runtime.O
258259
klog.V(4).Infof("saved updated content %s", content.Name)
259260
return true, content, nil
260261

262+
case action.Matches("patch", "volumesnapshotcontents"):
263+
content := &crdv1.VolumeSnapshotContent{}
264+
action := action.(core.PatchAction)
265+
266+
// Check and bump object version
267+
storedSnapshotContent, found := r.contents[action.GetName()]
268+
if found {
269+
// Apply patch
270+
storedSnapshotBytes, err := json.Marshal(storedSnapshotContent)
271+
if err != nil {
272+
return true, nil, err
273+
}
274+
contentPatch, err := jsonpatch.DecodePatch(action.GetPatch())
275+
if err != nil {
276+
return true, nil, err
277+
}
278+
279+
modified, err := contentPatch.Apply(storedSnapshotBytes)
280+
if err != nil {
281+
return true, nil, err
282+
}
283+
284+
err = json.Unmarshal(modified, content)
285+
if err != nil {
286+
return true, nil, err
287+
}
288+
289+
storedVer, _ := strconv.Atoi(content.ResourceVersion)
290+
content.ResourceVersion = strconv.Itoa(storedVer + 1)
291+
} else {
292+
return true, nil, fmt.Errorf("cannot update snapshot content %s: snapshot content not found", action.GetName())
293+
}
294+
295+
// Store the updated object to appropriate places.
296+
r.contents[content.Name] = content
297+
r.changedObjects = append(r.changedObjects, content)
298+
r.changedSinceLastSync++
299+
klog.V(4).Infof("saved updated content %s", content.Name)
300+
return true, content, nil
301+
261302
case action.Matches("update", "volumesnapshots"):
262303
obj := action.(core.UpdateAction).GetObject()
263304
snapshot := obj.(*crdv1.VolumeSnapshot)
@@ -284,6 +325,45 @@ func (r *snapshotReactor) React(action core.Action) (handled bool, ret runtime.O
284325
klog.V(4).Infof("saved updated snapshot %s", snapshot.Name)
285326
return true, snapshot, nil
286327

328+
case action.Matches("patch", "volumesnapshots"):
329+
action := action.(core.PatchAction)
330+
// Check and bump object version
331+
storedSnapshot, found := r.snapshots[action.GetName()]
332+
if found {
333+
// Apply patch
334+
storedSnapshotBytes, err := json.Marshal(storedSnapshot)
335+
if err != nil {
336+
return true, nil, err
337+
}
338+
snapPatch, err := jsonpatch.DecodePatch(action.GetPatch())
339+
if err != nil {
340+
return true, nil, err
341+
}
342+
343+
modified, err := snapPatch.Apply(storedSnapshotBytes)
344+
if err != nil {
345+
return true, nil, err
346+
}
347+
348+
err = json.Unmarshal(modified, storedSnapshot)
349+
if err != nil {
350+
return true, nil, err
351+
}
352+
353+
storedVer, _ := strconv.Atoi(storedSnapshot.ResourceVersion)
354+
storedSnapshot.ResourceVersion = strconv.Itoa(storedVer + 1)
355+
} else {
356+
return true, nil, fmt.Errorf("cannot update snapshot %s: snapshot not found", action.GetName())
357+
}
358+
359+
// Store the updated object to appropriate places.
360+
r.snapshots[storedSnapshot.Name] = storedSnapshot
361+
r.changedObjects = append(r.changedObjects, storedSnapshot)
362+
r.changedSinceLastSync++
363+
364+
klog.V(4).Infof("saved updated snapshot %s", storedSnapshot.Name)
365+
return true, storedSnapshot, nil
366+
287367
case action.Matches("get", "volumesnapshotcontents"):
288368
name := action.(core.GetAction).GetName()
289369
content, found := r.contents[name]
@@ -437,6 +517,7 @@ func (r *snapshotReactor) checkContents(expectedContents []*crdv1.VolumeSnapshot
437517
}
438518
gotMap[v.Name] = v
439519
}
520+
440521
if !reflect.DeepEqual(expectedMap, gotMap) {
441522
// Print ugly but useful diff of expected and received objects for
442523
// easier debugging.
@@ -714,6 +795,8 @@ func newSnapshotReactor(kubeClient *kubefake.Clientset, client *fake.Clientset,
714795
client.AddReactor("create", "volumesnapshotcontents", reactor.React)
715796
client.AddReactor("update", "volumesnapshotcontents", reactor.React)
716797
client.AddReactor("update", "volumesnapshots", reactor.React)
798+
client.AddReactor("patch", "volumesnapshotcontents", reactor.React)
799+
client.AddReactor("patch", "volumesnapshots", reactor.React)
717800
client.AddReactor("update", "volumesnapshotclasses", reactor.React)
718801
client.AddReactor("get", "volumesnapshotcontents", reactor.React)
719802
client.AddReactor("get", "volumesnapshots", reactor.React)
@@ -1169,6 +1252,10 @@ func testAddSnapshotFinalizer(ctrl *csiSnapshotCommonController, reactor *snapsh
11691252
return ctrl.addSnapshotFinalizer(test.initialSnapshots[0], true, true)
11701253
}
11711254

1255+
func testAddSingleSnapshotFinalizer(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error {
1256+
return ctrl.addSnapshotFinalizer(test.initialSnapshots[0], false, true)
1257+
}
1258+
11721259
func testRemoveSnapshotFinalizer(ctrl *csiSnapshotCommonController, reactor *snapshotReactor, test controllerTest) error {
11731260
return ctrl.removeSnapshotFinalizer(test.initialSnapshots[0], true, true)
11741261
}
@@ -1426,24 +1513,29 @@ func evaluateFinalizerTests(ctrl *csiSnapshotCommonController, reactor *snapshot
14261513
if funcName == "testAddSnapshotFinalizer" {
14271514
for _, snapshot := range reactor.snapshots {
14281515
if test.initialSnapshots[0].Name == snapshot.Name {
1429-
if !utils.ContainsString(test.initialSnapshots[0].ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer) && utils.ContainsString(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer) &&
1430-
!utils.ContainsString(test.initialSnapshots[0].ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer) && utils.ContainsString(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer) {
1516+
if !utils.ContainsString(test.initialSnapshots[0].ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer) &&
1517+
utils.ContainsString(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer) &&
1518+
!utils.ContainsString(test.initialSnapshots[0].ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer) &&
1519+
utils.ContainsString(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer) {
14311520
klog.V(4).Infof("test %q succeeded. Finalizers are added to snapshot %s", test.name, snapshot.Name)
14321521
bHasSnapshotFinalizer = true
14331522
}
14341523
break
14351524
}
14361525
}
14371526
if test.expectSuccess && !bHasSnapshotFinalizer {
1438-
t.Errorf("Test %q: failed to add finalizer to Snapshot %s", test.name, test.initialSnapshots[0].Name)
1527+
t.Errorf("Test %q: failed to add finalizer to Snapshot %s. Finalizers: %s", test.name, test.initialSnapshots[0].Name, test.initialSnapshots[0].GetFinalizers())
14391528
}
14401529
}
14411530
bHasSnapshotFinalizer = true
14421531
if funcName == "testRemoveSnapshotFinalizer" {
14431532
for _, snapshot := range reactor.snapshots {
14441533
if test.initialSnapshots[0].Name == snapshot.Name {
1445-
if utils.ContainsString(test.initialSnapshots[0].ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer) && !utils.ContainsString(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer) &&
1446-
utils.ContainsString(test.initialSnapshots[0].ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer) && !utils.ContainsString(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer) {
1534+
if utils.ContainsString(test.initialSnapshots[0].ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer) &&
1535+
!utils.ContainsString(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer) &&
1536+
utils.ContainsString(test.initialSnapshots[0].ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer) &&
1537+
!utils.ContainsString(snapshot.ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer) {
1538+
14471539
klog.V(4).Infof("test %q succeeded. SnapshotFinalizer is removed from Snapshot %s", test.name, snapshot.Name)
14481540
bHasSnapshotFinalizer = false
14491541
}

pkg/common-controller/snapshot_controller.go

Lines changed: 86 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -809,10 +809,25 @@ func (ctrl *csiSnapshotCommonController) updateSnapshotErrorStatusWithEvent(snap
809809

810810
// addContentFinalizer adds a Finalizer for VolumeSnapshotContent.
811811
func (ctrl *csiSnapshotCommonController) addContentFinalizer(content *crdv1.VolumeSnapshotContent) error {
812-
contentClone := content.DeepCopy()
813-
contentClone.ObjectMeta.Finalizers = append(contentClone.ObjectMeta.Finalizers, utils.VolumeSnapshotContentFinalizer)
814812

815-
newContent, err := ctrl.clientset.SnapshotV1().VolumeSnapshotContents().Update(context.TODO(), contentClone, metav1.UpdateOptions{})
813+
var patches []utils.PatchOp
814+
if len(content.Finalizers) > 0 {
815+
// Add to the end of the finalizers if we have any other finalizers
816+
patches = append(patches, utils.PatchOp{
817+
Op: "add",
818+
Path: "/metadata/finalizers/-",
819+
Value: utils.VolumeSnapshotContentFinalizer,
820+
})
821+
822+
} else {
823+
// Replace finalizers with new array if there are no other finalizers
824+
patches = append(patches, utils.PatchOp{
825+
Op: "add",
826+
Path: "/metadata/finalizers",
827+
Value: []string{utils.VolumeSnapshotContentFinalizer},
828+
})
829+
}
830+
newContent, err := utils.PatchVolumeSnapshotContent(content, patches, ctrl.clientset)
816831
if err != nil {
817832
return newControllerUpdateError(content.Name, err.Error())
818833
}
@@ -983,15 +998,26 @@ func (ctrl *csiSnapshotCommonController) checkandBindSnapshotContent(snapshot *c
983998
} else if content.Spec.VolumeSnapshotRef.UID != "" && content.Spec.VolumeSnapshotClassName != nil {
984999
return content, nil
9851000
}
986-
contentClone := content.DeepCopy()
987-
contentClone.Spec.VolumeSnapshotRef.UID = snapshot.UID
1001+
1002+
patches := []utils.PatchOp{
1003+
{
1004+
Op: "replace",
1005+
Path: "/spec/volumeSnapshotRef/uid",
1006+
Value: string(snapshot.UID),
1007+
},
1008+
}
9881009
if snapshot.Spec.VolumeSnapshotClassName != nil {
9891010
className := *(snapshot.Spec.VolumeSnapshotClassName)
990-
contentClone.Spec.VolumeSnapshotClassName = &className
1011+
patches = append(patches, utils.PatchOp{
1012+
Op: "replace",
1013+
Path: "/spec/volumeSnapshotClassName",
1014+
Value: className,
1015+
})
9911016
}
992-
newContent, err := ctrl.clientset.SnapshotV1().VolumeSnapshotContents().Update(context.TODO(), contentClone, metav1.UpdateOptions{})
1017+
1018+
newContent, err := utils.PatchVolumeSnapshotContent(content, patches, ctrl.clientset)
9931019
if err != nil {
994-
klog.V(4).Infof("updating VolumeSnapshotContent[%s] error status failed %v", contentClone.Name, err)
1020+
klog.V(4).Infof("updating VolumeSnapshotContent[%s] error status failed %v", content.Name, err)
9951021
return content, err
9961022
}
9971023

@@ -1392,24 +1418,55 @@ func isControllerUpdateFailError(err *crdv1.VolumeSnapshotError) bool {
13921418

13931419
// addSnapshotFinalizer adds a Finalizer for VolumeSnapshot.
13941420
func (ctrl *csiSnapshotCommonController) addSnapshotFinalizer(snapshot *crdv1.VolumeSnapshot, addSourceFinalizer bool, addBoundFinalizer bool) error {
1395-
snapshotClone := snapshot.DeepCopy()
1396-
if addSourceFinalizer {
1397-
snapshotClone.ObjectMeta.Finalizers = append(snapshotClone.ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer)
1398-
}
1399-
if addBoundFinalizer {
1400-
snapshotClone.ObjectMeta.Finalizers = append(snapshotClone.ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer)
1401-
}
1402-
newSnapshot, err := ctrl.clientset.SnapshotV1().VolumeSnapshots(snapshotClone.Namespace).Update(context.TODO(), snapshotClone, metav1.UpdateOptions{})
1403-
if err != nil {
1404-
return newControllerUpdateError(utils.SnapshotKey(snapshot), err.Error())
1421+
var updatedSnapshot *crdv1.VolumeSnapshot
1422+
var err error
1423+
1424+
// NOTE(ggriffiths): Must perform an update if no finalizers exist.
1425+
// Unable to find a patch that correctly updated the finalizers if none currently exist.
1426+
if len(snapshot.ObjectMeta.Finalizers) == 0 {
1427+
snapshotClone := snapshot.DeepCopy()
1428+
if addSourceFinalizer {
1429+
snapshotClone.ObjectMeta.Finalizers = append(snapshotClone.ObjectMeta.Finalizers, utils.VolumeSnapshotAsSourceFinalizer)
1430+
}
1431+
if addBoundFinalizer {
1432+
snapshotClone.ObjectMeta.Finalizers = append(snapshotClone.ObjectMeta.Finalizers, utils.VolumeSnapshotBoundFinalizer)
1433+
}
1434+
updatedSnapshot, err = ctrl.clientset.SnapshotV1().VolumeSnapshots(snapshotClone.Namespace).Update(context.TODO(), snapshotClone, metav1.UpdateOptions{})
1435+
if err != nil {
1436+
return newControllerUpdateError(utils.SnapshotKey(snapshot), err.Error())
1437+
}
1438+
} else {
1439+
// Otherwise, perform a patch
1440+
var patches []utils.PatchOp
1441+
1442+
// If finalizers exist already, add new ones to the end of the array
1443+
if addSourceFinalizer {
1444+
patches = append(patches, utils.PatchOp{
1445+
Op: "add",
1446+
Path: "/metadata/finalizers/-",
1447+
Value: utils.VolumeSnapshotAsSourceFinalizer,
1448+
})
1449+
}
1450+
if addBoundFinalizer {
1451+
patches = append(patches, utils.PatchOp{
1452+
Op: "add",
1453+
Path: "/metadata/finalizers/-",
1454+
Value: utils.VolumeSnapshotBoundFinalizer,
1455+
})
1456+
}
1457+
1458+
updatedSnapshot, err = utils.PatchVolumeSnapshot(snapshot, patches, ctrl.clientset)
1459+
if err != nil {
1460+
return newControllerUpdateError(utils.SnapshotKey(snapshot), err.Error())
1461+
}
14051462
}
14061463

1407-
_, err = ctrl.storeSnapshotUpdate(newSnapshot)
1464+
_, err = ctrl.storeSnapshotUpdate(updatedSnapshot)
14081465
if err != nil {
14091466
klog.Errorf("failed to update snapshot store %v", err)
14101467
}
14111468

1412-
klog.V(5).Infof("Added protection finalizer to volume snapshot %s", utils.SnapshotKey(newSnapshot))
1469+
klog.V(5).Infof("Added protection finalizer to volume snapshot %s", utils.SnapshotKey(updatedSnapshot))
14131470
return nil
14141471
}
14151472

@@ -1489,14 +1546,21 @@ func (ctrl *csiSnapshotCommonController) setAnnVolumeSnapshotBeingDeleted(conten
14891546
// Set AnnVolumeSnapshotBeingDeleted if it is not set yet
14901547
if !metav1.HasAnnotation(content.ObjectMeta, utils.AnnVolumeSnapshotBeingDeleted) {
14911548
klog.V(5).Infof("setAnnVolumeSnapshotBeingDeleted: set annotation [%s] on content [%s].", utils.AnnVolumeSnapshotBeingDeleted, content.Name)
1549+
var patches []utils.PatchOp
14921550
metav1.SetMetaDataAnnotation(&content.ObjectMeta, utils.AnnVolumeSnapshotBeingDeleted, "yes")
1551+
patches = append(patches, utils.PatchOp{
1552+
Op: "replace",
1553+
Path: "/metadata/annotations",
1554+
Value: content.ObjectMeta.GetAnnotations(),
1555+
})
14931556

1494-
updateContent, err := ctrl.clientset.SnapshotV1().VolumeSnapshotContents().Update(context.TODO(), content, metav1.UpdateOptions{})
1557+
patchedContent, err := utils.PatchVolumeSnapshotContent(content, patches, ctrl.clientset)
14951558
if err != nil {
14961559
return content, newControllerUpdateError(content.Name, err.Error())
14971560
}
1561+
14981562
// update content if update is successful
1499-
content = updateContent
1563+
content = patchedContent
15001564

15011565
_, err = ctrl.storeContentUpdate(content)
15021566
if err != nil {

pkg/common-controller/snapshot_finalizer_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package common_controller
1919
import (
2020
"testing"
2121

22+
"github.com/kubernetes-csi/external-snapshotter/v4/pkg/utils"
2223
v1 "k8s.io/api/core/v1"
2324
)
2425

@@ -70,7 +71,14 @@ func TestSnapshotFinalizer(t *testing.T) {
7071
expectSuccess: true,
7172
},
7273
{
73-
name: "2-1 - successful remove Snapshot finalizer",
74+
name: "2-2 - successful add single Snapshot finalizer with patch",
75+
initialSnapshots: withSnapshotFinalizers(newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil, false, false, nil), utils.VolumeSnapshotBoundFinalizer),
76+
initialClaims: newClaimArray("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
77+
test: testAddSingleSnapshotFinalizer,
78+
expectSuccess: true,
79+
},
80+
{
81+
name: "2-3 - successful remove Snapshot finalizer",
7482
initialSnapshots: newSnapshotArray("snap6-2", "snapuid6-2", "claim6-2", "", classSilver, "", &False, nil, nil, nil, false, true, nil),
7583
initialClaims: newClaimArray("claim6-2", "pvc-uid6-2", "1Gi", "volume6-2", v1.ClaimBound, &classEmpty),
7684
test: testRemoveSnapshotFinalizer,

0 commit comments

Comments
 (0)