Skip to content

Commit 11eb8af

Browse files
author
Cheng Pan
committed
Implement CSI migration logic for volume resize
* Using PVC annotation for sychronication between in-tree resizer and extternal resizer * Add unit tests
1 parent ad72116 commit 11eb8af

File tree

16 files changed

+1367
-11
lines changed

16 files changed

+1367
-11
lines changed

Gopkg.lock

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/controller/controller.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ func (ctrl *resizeController) pvcNeedResize(pvc *v1.PersistentVolumeClaim) bool
249249

250250
// pvNeedResize returns true if a pv supports and also requests resize.
251251
func (ctrl *resizeController) pvNeedResize(pvc *v1.PersistentVolumeClaim, pv *v1.PersistentVolume) bool {
252-
if !ctrl.resizer.CanSupport(pv) {
252+
if !ctrl.resizer.CanSupport(pv, pvc) {
253253
klog.V(4).Infof("Resizer %q doesn't support PV %q", ctrl.name, pv.Name)
254254
return false
255255
}

pkg/controller/controller_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TestController(t *testing.T) {
6565
NodeResize: true,
6666
},
6767
} {
68-
client := csi.NewMockClient(test.NodeResize, true, true)
68+
client := csi.NewMockClient("mock", test.NodeResize, true, true)
6969
driverName, _ := client.GetDriverName(context.TODO())
7070

7171
initialObjects := []runtime.Object{}

pkg/csi/mock_client.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package csi
33
import "context"
44

55
func NewMockClient(
6+
name string,
67
supportsNodeResize bool,
78
supportsControllerResize bool,
89
supportsPluginControllerService bool) *MockClient {
910
return &MockClient{
10-
name: "mock",
11+
name: name,
1112
supportsNodeResize: supportsNodeResize,
1213
supportsControllerResize: supportsControllerResize,
1314
supportsPluginControllerService: supportsPluginControllerService,

pkg/resizer/csi_resizer.go

+29-2
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,19 @@ import (
3333
"k8s.io/client-go/informers"
3434
"k8s.io/client-go/kubernetes"
3535
storagev1listers "k8s.io/client-go/listers/storage/v1"
36+
37+
csitranslationlib "k8s.io/csi-translation-lib"
3638
"k8s.io/klog"
3739
)
3840

3941
const (
4042
resizerSecretNameKey = "csi.storage.k8s.io/resizer-secret-name"
4143
resizerSecretNamespaceKey = "csi.storage.k8s.io/resizer-secret-namespace"
44+
45+
// In-tree resizer populates the key with the value as the storage plugin name
46+
// If CSI migration is enabled, the value will be CSI driver name
47+
// Otherwise, it will be in-tree storage plugin name
48+
volumeResizerKey = "volume.kubernetes.io/storage-resizer"
4249
)
4350

4451
var (
@@ -118,7 +125,14 @@ func (r *csiResizer) Name() string {
118125
return r.name
119126
}
120127

121-
func (r *csiResizer) CanSupport(pv *v1.PersistentVolume) bool {
128+
// CanSupport returns whether the PV is supported by resizer
129+
// Resizer will resize the volume if it is CSI volume or is migration enabled in-tree volume
130+
func (r *csiResizer) CanSupport(pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) bool {
131+
resizerName := pvc.Annotations[volumeResizerKey]
132+
if csitranslationlib.IsMigratedCSIDriverByName(r.name) && resizerName == r.name {
133+
return true
134+
}
135+
122136
source := pv.Spec.CSI
123137
if source == nil {
124138
klog.V(4).Infof("PV %s is not a CSI volume, skip it", pv.Name)
@@ -131,14 +145,27 @@ func (r *csiResizer) CanSupport(pv *v1.PersistentVolume) bool {
131145
return true
132146
}
133147

148+
// Resize resizes the persistence volume given request size
149+
// It supports both CSI volume and migrated in-tree volume
134150
func (r *csiResizer) Resize(pv *v1.PersistentVolume, requestSize resource.Quantity) (resource.Quantity, bool, error) {
135151
oldSize := pv.Spec.Capacity[v1.ResourceStorage]
152+
var err error
153+
154+
if csitranslationlib.IsMigratedCSIDriverByName(r.name) {
155+
// handle migrated in-tree volume
156+
pv, err = csitranslationlib.TranslateInTreePVToCSI(pv)
157+
if err != nil {
158+
return oldSize, false, fmt.Errorf("failed to translate persistent volume: %v", err)
159+
}
160+
}
136161

137162
source := pv.Spec.CSI
138163
if source == nil {
139-
return oldSize, false, errors.New("not a CSI volume")
164+
// in-tree volume that is not migrated yet
165+
return oldSize, false, fmt.Errorf("volume %v is not migrated to CSI", pv.Name)
140166
}
141167
volumeID := source.VolumeHandle
168+
142169
if len(volumeID) == 0 {
143170
return oldSize, false, errors.New("empty volume handle")
144171
}

pkg/resizer/csi_resizer_test.go

+218-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package resizer
22

33
import (
4+
"errors"
45
"testing"
56

67
"github.com/kubernetes-csi/external-resizer/pkg/csi"
8+
"k8s.io/api/core/v1"
9+
"k8s.io/apimachinery/pkg/api/resource"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/runtime"
712
"k8s.io/client-go/informers"
813
"k8s.io/client-go/kubernetes"
914
"k8s.io/client-go/kubernetes/fake"
@@ -51,7 +56,7 @@ func TestNewResizer(t *testing.T) {
5156
Error: resizeNotSupportErr,
5257
},
5358
} {
54-
client := csi.NewMockClient(c.SupportsNodeResize, c.SupportsControllerResize, c.SupportsPluginControllerService)
59+
client := csi.NewMockClient("mock", c.SupportsNodeResize, c.SupportsControllerResize, c.SupportsPluginControllerService)
5560
k8sClient, informerFactory := fakeK8s()
5661
resizer, err := NewResizerFromClient(client, 0, k8sClient, informerFactory)
5762
if err != c.Error {
@@ -66,8 +71,218 @@ func TestNewResizer(t *testing.T) {
6671
}
6772
}
6873

69-
func fakeK8s() (kubernetes.Interface, informers.SharedInformerFactory) {
70-
client := fake.NewSimpleClientset()
74+
func TestResizeMigratedPV(t *testing.T) {
75+
testCases := []struct {
76+
name string
77+
driverName string
78+
pv *v1.PersistentVolume
79+
nodeResizeRequired bool
80+
err error
81+
}{
82+
{
83+
name: "Test AWS EBS CSI Driver",
84+
driverName: "ebs.csi.aws.com",
85+
pv: createEBSPV(1),
86+
nodeResizeRequired: true,
87+
},
88+
{
89+
name: "Test GCE PD Driver",
90+
driverName: "pd.csi.storage.gke.io",
91+
pv: createGCEPDPV(1),
92+
nodeResizeRequired: true,
93+
},
94+
{
95+
name: "Test unknonwn driver",
96+
driverName: "unknown",
97+
pv: createEBSPV(1),
98+
nodeResizeRequired: true,
99+
err: errors.New("volume testEBSPV is not migrated to CSI"),
100+
},
101+
}
102+
103+
for _, tc := range testCases {
104+
t.Run(tc.name, func(t *testing.T) {
105+
driverName := tc.driverName
106+
client := csi.NewMockClient(driverName, true, true, true)
107+
k8sClient, informerFactory := fakeK8s()
108+
resizer, err := NewResizerFromClient(client, 0, k8sClient, informerFactory)
109+
if err != nil {
110+
t.Fatalf("Failed to create resizer: %v", err)
111+
}
112+
113+
pv := tc.pv
114+
expectedSize := quantityGB(2)
115+
newSize, nodeResizeRequired, err := resizer.Resize(pv, expectedSize)
116+
117+
if tc.err != nil {
118+
if err == nil {
119+
t.Fatalf("Got wrong error, wanted: %v, got: %v", tc.err, err)
120+
}
121+
} else {
122+
if err != nil {
123+
t.Fatalf("Failed to resize the PV: %v", err)
124+
}
125+
126+
if newSize != expectedSize {
127+
t.Fatalf("newSize mismatches, wanted: %v, got: %v", expectedSize, newSize)
128+
}
129+
if nodeResizeRequired != tc.nodeResizeRequired {
130+
t.Fatalf("nodeResizeRequired mismatches, wanted: %v, got: %v", tc.nodeResizeRequired, nodeResizeRequired)
131+
}
132+
}
133+
})
134+
}
135+
}
136+
137+
func TestCanSupport(t *testing.T) {
138+
testCases := []struct {
139+
name string
140+
driverName string
141+
pv *v1.PersistentVolume
142+
pvc *v1.PersistentVolumeClaim
143+
canSupport bool
144+
}{
145+
{
146+
name: "EBS PV/PVC is supported",
147+
driverName: "ebs.csi.aws.com",
148+
pv: createEBSPV(1),
149+
pvc: createPVC("ebs.csi.aws.com"),
150+
canSupport: true,
151+
},
152+
{
153+
name: "EBS PV/PVC is not supported when migartion is disabled",
154+
driverName: "ebs.csi.aws.com",
155+
pv: createEBSPV(1),
156+
pvc: createPVC("kubernetes.io/aws-ebs"),
157+
canSupport: false,
158+
},
159+
{
160+
name: "PD PV/PVC is supported",
161+
driverName: "pd.csi.storage.gke.io",
162+
pv: createGCEPDPV(1),
163+
pvc: createPVC("pd.csi.storage.gke.io"),
164+
canSupport: true,
165+
},
166+
{
167+
name: "unknown PV/PVC is not supported",
168+
driverName: "ebs.csi.aws.com",
169+
pv: createEBSPV(1),
170+
pvc: createPVC("unknown"),
171+
canSupport: false,
172+
},
173+
}
174+
for _, tc := range testCases {
175+
t.Run(tc.name, func(t *testing.T) {
176+
driverName := tc.driverName
177+
client := csi.NewMockClient(driverName, true, true, true)
178+
k8sClient, informerFactory := fakeK8s()
179+
resizer, err := NewResizerFromClient(client, 0, k8sClient, informerFactory)
180+
if err != nil {
181+
t.Fatalf("Failed to create resizer: %v", err)
182+
}
183+
184+
canSupport := resizer.CanSupport(tc.pv, tc.pvc)
185+
if canSupport != tc.canSupport {
186+
t.Fatalf("Wrong canSupport, wanted: %v got: %v", tc.canSupport, canSupport)
187+
}
188+
})
189+
}
190+
}
191+
192+
func quantityGB(i int) resource.Quantity {
193+
q := resource.NewQuantity(int64(i*1024*1024), resource.BinarySI)
194+
return *q
195+
}
196+
197+
func createPVC(resizerName string) *v1.PersistentVolumeClaim {
198+
request := quantityGB(2)
199+
capacity := quantityGB(1)
200+
201+
return &v1.PersistentVolumeClaim{
202+
ObjectMeta: metav1.ObjectMeta{
203+
Name: "testPVC",
204+
Namespace: "test",
205+
Annotations: map[string]string{
206+
"volume.kubernetes.io/storage-resizer": resizerName,
207+
},
208+
},
209+
Spec: v1.PersistentVolumeClaimSpec{
210+
Resources: v1.ResourceRequirements{
211+
Requests: map[v1.ResourceName]resource.Quantity{
212+
v1.ResourceStorage: request,
213+
},
214+
},
215+
VolumeName: "testPV",
216+
},
217+
Status: v1.PersistentVolumeClaimStatus{
218+
Phase: v1.ClaimBound,
219+
Capacity: map[v1.ResourceName]resource.Quantity{
220+
v1.ResourceStorage: capacity,
221+
},
222+
},
223+
}
224+
}
225+
226+
func createPV(capacityGB int) *v1.PersistentVolume {
227+
capacity := quantityGB(capacityGB)
228+
229+
return &v1.PersistentVolume{
230+
ObjectMeta: metav1.ObjectMeta{
231+
Name: "testPV",
232+
},
233+
Spec: v1.PersistentVolumeSpec{
234+
Capacity: map[v1.ResourceName]resource.Quantity{
235+
v1.ResourceStorage: capacity,
236+
},
237+
PersistentVolumeSource: v1.PersistentVolumeSource{
238+
CSI: &v1.CSIPersistentVolumeSource{
239+
Driver: "foo",
240+
VolumeHandle: "foo",
241+
},
242+
},
243+
},
244+
}
245+
}
246+
func createEBSPV(capacityGB int) *v1.PersistentVolume {
247+
capacity := quantityGB(capacityGB)
248+
249+
return &v1.PersistentVolume{
250+
ObjectMeta: metav1.ObjectMeta{
251+
Name: "testEBSPV",
252+
},
253+
Spec: v1.PersistentVolumeSpec{
254+
Capacity: map[v1.ResourceName]resource.Quantity{
255+
v1.ResourceStorage: capacity,
256+
},
257+
PersistentVolumeSource: v1.PersistentVolumeSource{
258+
AWSElasticBlockStore: &v1.AWSElasticBlockStoreVolumeSource{
259+
VolumeID: "testVolumeId",
260+
},
261+
},
262+
},
263+
}
264+
}
265+
266+
func createGCEPDPV(capacityGB int) *v1.PersistentVolume {
267+
capacity := quantityGB(capacityGB)
268+
269+
return &v1.PersistentVolume{
270+
ObjectMeta: metav1.ObjectMeta{
271+
Name: "testPDPV",
272+
},
273+
Spec: v1.PersistentVolumeSpec{
274+
Capacity: map[v1.ResourceName]resource.Quantity{
275+
v1.ResourceStorage: capacity,
276+
},
277+
PersistentVolumeSource: v1.PersistentVolumeSource{
278+
GCEPersistentDisk: &v1.GCEPersistentDiskVolumeSource{},
279+
},
280+
},
281+
}
282+
}
283+
284+
func fakeK8s(objects ...runtime.Object) (kubernetes.Interface, informers.SharedInformerFactory) {
285+
client := fake.NewSimpleClientset(objects...)
71286
informerFactory := informers.NewSharedInformerFactory(client, 0)
72287
return client, informerFactory
73288
}

pkg/resizer/resizer.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import (
2525
type Resizer interface {
2626
// Name returns the resizer's name.
2727
Name() string
28-
// CanSupport returns true if resizer supports resize operation of this PV.
29-
CanSupport(pv *v1.PersistentVolume) bool
28+
// CanSupport returns true if resizer supports resize operation of this PV
29+
// with its corresponding PVC.
30+
CanSupport(pv *v1.PersistentVolume, pvc *v1.PersistentVolumeClaim) bool
3031
// Resize executes the resize operation of this PV.
3132
Resize(pv *v1.PersistentVolume, requestSize resource.Quantity) (newSize resource.Quantity, fsResizeRequired bool, err error)
3233
}

0 commit comments

Comments
 (0)