Skip to content

Commit 8d73dbe

Browse files
committed
Verify claimref associated with PVs before resizing
1 parent 6190489 commit 8d73dbe

File tree

5 files changed

+234
-4
lines changed

5 files changed

+234
-4
lines changed

pkg/controller/controller.go

+5
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,11 @@ func (ctrl *resizeController) pvNeedResize(pvc *v1.PersistentVolumeClaim, pv *v1
254254
return false
255255
}
256256

257+
if (pv.Spec.ClaimRef == nil) || (pvc.Namespace != pv.Spec.ClaimRef.Namespace) || (pvc.UID != pv.Spec.ClaimRef.UID) {
258+
klog.V(4).Infof("persistent volume is not bound to PVC being updated: %s", util.PVCKey(pvc))
259+
return false
260+
}
261+
257262
pvSize := pv.Spec.Capacity[v1.ResourceStorage]
258263
requestSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
259264
if pvSize.Cmp(requestSize) >= 0 {

pkg/controller/controller_test.go

+212
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package controller
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"testing"
7+
"time"
8+
9+
"github.com/kubernetes-csi/external-resizer/pkg/csi"
10+
"github.com/kubernetes-csi/external-resizer/pkg/resizer"
11+
12+
"k8s.io/api/core/v1"
13+
"k8s.io/apimachinery/pkg/api/resource"
14+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/apimachinery/pkg/runtime"
16+
"k8s.io/apimachinery/pkg/types"
17+
"k8s.io/client-go/informers"
18+
"k8s.io/client-go/kubernetes"
19+
"k8s.io/client-go/kubernetes/fake"
20+
)
21+
22+
func TestController(t *testing.T) {
23+
for _, test := range []struct {
24+
Name string
25+
PVC *v1.PersistentVolumeClaim
26+
PV *v1.PersistentVolume
27+
28+
CreateObjects bool
29+
NodeResize bool
30+
CallCSIExpand bool
31+
}{
32+
{
33+
Name: "Invalid key",
34+
PVC: invalidPVC(),
35+
CallCSIExpand: false,
36+
},
37+
{
38+
Name: "PVC not found",
39+
PVC: createPVC(1, 1),
40+
CallCSIExpand: false,
41+
},
42+
{
43+
Name: "PVC doesn't need resize",
44+
PVC: createPVC(1, 1),
45+
CreateObjects: true,
46+
CallCSIExpand: false,
47+
},
48+
{
49+
Name: "PV not found",
50+
PVC: createPVC(2, 1),
51+
CreateObjects: true,
52+
CallCSIExpand: false,
53+
},
54+
{
55+
Name: "pv claimref does not have pvc UID",
56+
PVC: createPVC(2, 1),
57+
PV: createPV(1, "testPVC" /*pvcName*/, "test" /*pvcNamespace*/, "foobaz" /*pvcUID*/),
58+
CallCSIExpand: false,
59+
},
60+
{
61+
Name: "pv claimref does not have PVC namespace",
62+
PVC: createPVC(2, 1),
63+
PV: createPV(1, "testPVC" /*pvcName*/, "test1" /*pvcNamespace*/, "foobar" /*pvcUID*/),
64+
CallCSIExpand: false,
65+
},
66+
{
67+
Name: "pv claimref is nil",
68+
PVC: createPVC(2, 1),
69+
PV: createPV(1, "" /*pvcName*/, "test1" /*pvcNamespace*/, "foobar" /*pvcUID*/),
70+
CallCSIExpand: false,
71+
},
72+
{
73+
Name: "Resize PVC, no FS resize",
74+
PVC: createPVC(2, 1),
75+
PV: createPV(1, "testPVC", "test", "foobar"),
76+
CreateObjects: true,
77+
CallCSIExpand: true,
78+
},
79+
{
80+
Name: "Resize PVC with FS resize",
81+
PVC: createPVC(2, 1),
82+
PV: createPV(1, "testPVC", "test", "foobar"),
83+
CreateObjects: true,
84+
NodeResize: true,
85+
CallCSIExpand: true,
86+
},
87+
} {
88+
client := csi.NewMockClient(test.NodeResize, true, true)
89+
driverName, _ := client.GetDriverName(context.TODO())
90+
91+
initialObjects := []runtime.Object{}
92+
if test.CreateObjects {
93+
if test.PVC != nil {
94+
initialObjects = append(initialObjects, test.PVC)
95+
}
96+
if test.PV != nil {
97+
test.PV.Spec.PersistentVolumeSource.CSI.Driver = driverName
98+
initialObjects = append(initialObjects, test.PV)
99+
}
100+
}
101+
102+
kubeClient, informerFactory := fakeK8s(initialObjects)
103+
pvInformer := informerFactory.Core().V1().PersistentVolumes()
104+
pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims()
105+
106+
for _, obj := range initialObjects {
107+
switch obj.(type) {
108+
case *v1.PersistentVolume:
109+
pvInformer.Informer().GetStore().Add(obj)
110+
case *v1.PersistentVolumeClaim:
111+
pvcInformer.Informer().GetStore().Add(obj)
112+
default:
113+
t.Fatalf("Test %s: Unknown initalObject type: %+v", test.Name, obj)
114+
}
115+
}
116+
117+
csiResizer, err := resizer.NewResizerFromClient(client, 15*time.Second, kubeClient, informerFactory)
118+
if err != nil {
119+
t.Fatalf("Test %s: Unable to create resizer: %v", test.Name, err)
120+
}
121+
122+
controller := NewResizeController(driverName, csiResizer, kubeClient, time.Second, informerFactory)
123+
err = controller.(*resizeController).syncPVC(fmt.Sprintf("%s/%s", test.PVC.Namespace, test.PVC.Name))
124+
if err != nil {
125+
t.Fatalf("Test %s: Unexpected error: %v", test.Name, err)
126+
}
127+
128+
expandCallCount := client.GetExpandCount()
129+
if test.CallCSIExpand && expandCallCount == 0 {
130+
t.Fatalf("for %s: expected csi expand call, no csi expand call was made", test.Name)
131+
}
132+
133+
if !test.CallCSIExpand && expandCallCount > 0 {
134+
t.Fatalf("for %s: expected no csi expand call, received csi expansion request", test.Name)
135+
}
136+
}
137+
}
138+
139+
func invalidPVC() *v1.PersistentVolumeClaim {
140+
pvc := createPVC(1, 1)
141+
pvc.ObjectMeta.Name = ""
142+
pvc.ObjectMeta.Namespace = ""
143+
144+
return pvc
145+
}
146+
147+
func quantityGB(i int) resource.Quantity {
148+
q := resource.NewQuantity(int64(i*1024*1024), resource.BinarySI)
149+
return *q
150+
}
151+
152+
func createPVC(requestGB, capacityGB int) *v1.PersistentVolumeClaim {
153+
request := quantityGB(requestGB)
154+
capacity := quantityGB(capacityGB)
155+
156+
return &v1.PersistentVolumeClaim{
157+
ObjectMeta: metav1.ObjectMeta{
158+
Name: "testPVC",
159+
Namespace: "test",
160+
UID: "foobar",
161+
},
162+
Spec: v1.PersistentVolumeClaimSpec{
163+
Resources: v1.ResourceRequirements{
164+
Requests: map[v1.ResourceName]resource.Quantity{
165+
v1.ResourceStorage: request,
166+
},
167+
},
168+
VolumeName: "testPV",
169+
},
170+
Status: v1.PersistentVolumeClaimStatus{
171+
Phase: v1.ClaimBound,
172+
Capacity: map[v1.ResourceName]resource.Quantity{
173+
v1.ResourceStorage: capacity,
174+
},
175+
},
176+
}
177+
}
178+
179+
func createPV(capacityGB int, pvcName, pvcNamespace string, pvcUID types.UID) *v1.PersistentVolume {
180+
capacity := quantityGB(capacityGB)
181+
182+
pv := &v1.PersistentVolume{
183+
ObjectMeta: metav1.ObjectMeta{
184+
Name: "testPV",
185+
},
186+
Spec: v1.PersistentVolumeSpec{
187+
Capacity: map[v1.ResourceName]resource.Quantity{
188+
v1.ResourceStorage: capacity,
189+
},
190+
PersistentVolumeSource: v1.PersistentVolumeSource{
191+
CSI: &v1.CSIPersistentVolumeSource{
192+
Driver: "foo",
193+
VolumeHandle: "foo",
194+
},
195+
},
196+
},
197+
}
198+
if len(pvcName) > 0 {
199+
pv.Spec.ClaimRef = &v1.ObjectReference{
200+
Namespace: pvcNamespace,
201+
Name: pvcName,
202+
UID: pvcUID,
203+
}
204+
}
205+
return pv
206+
}
207+
208+
func fakeK8s(objs []runtime.Object) (kubernetes.Interface, informers.SharedInformerFactory) {
209+
client := fake.NewSimpleClientset(objs...)
210+
informerFactory := informers.NewSharedInformerFactory(client, 0)
211+
return client, informerFactory
212+
}

pkg/csi/mock_client.go

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ func NewMockClient(
1010
name: "mock",
1111
supportsNodeResize: supportsNodeResize,
1212
supportsControllerResize: supportsControllerResize,
13+
expandCalled: 0,
1314
supportsPluginControllerService: supportsPluginControllerService,
1415
}
1516
}
@@ -19,6 +20,8 @@ type MockClient struct {
1920
supportsNodeResize bool
2021
supportsControllerResize bool
2122
supportsPluginControllerService bool
23+
expandCalled int
24+
usedSecrets map[string]string
2225
}
2326

2427
func (c *MockClient) GetDriverName(context.Context) (string, error) {
@@ -43,5 +46,16 @@ func (c *MockClient) Expand(
4346
requestBytes int64,
4447
secrets map[string]string) (int64, bool, error) {
4548
// TODO: Determine whether the operation succeeds or fails by parameters.
49+
c.expandCalled++
50+
c.usedSecrets = secrets
4651
return requestBytes, c.supportsNodeResize, nil
4752
}
53+
54+
func (c *MockClient) GetExpandCount() int {
55+
return c.expandCalled
56+
}
57+
58+
// GetSecrets returns secrets used for volume expansion
59+
func (c *MockClient) GetSecrets() map[string]string {
60+
return c.usedSecrets
61+
}

pkg/resizer/csi_resizer.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ func NewResizer(
5656
if err != nil {
5757
return nil, err
5858
}
59-
return newResizer(csiClient, timeout, k8sClient, informerFactory)
59+
return NewResizerFromClient(csiClient, timeout, k8sClient, informerFactory)
6060
}
6161

62-
func newResizer(
62+
func NewResizerFromClient(
6363
csiClient csi.Client,
6464
timeout time.Duration,
6565
k8sClient kubernetes.Interface,
@@ -101,7 +101,6 @@ func newResizer(
101101
timeout: timeout,
102102

103103
k8sClient: k8sClient,
104-
scLister: informerFactory.Storage().V1().StorageClasses().Lister(),
105104
}, nil
106105
}
107106

pkg/resizer/csi_resizer_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func TestNewResizer(t *testing.T) {
5353
} {
5454
client := csi.NewMockClient(c.SupportsNodeResize, c.SupportsControllerResize, c.SupportsPluginControllerService)
5555
k8sClient, informerFactory := fakeK8s()
56-
resizer, err := newResizer(client, 0, k8sClient, informerFactory)
56+
resizer, err := NewResizerFromClient(client, 0, k8sClient, informerFactory)
5757
if err != c.Error {
5858
t.Errorf("Case %d: Unexpected error: wanted %v, got %v", i, c.Error, err)
5959
}

0 commit comments

Comments
 (0)