Skip to content

Commit 3624c74

Browse files
authored
Merge pull request kubernetes#74863 from gnufied/csi-volume-expansion
CSI volume expansion
2 parents f229aa0 + 1bd9ed0 commit 3624c74

File tree

32 files changed

+1813
-403
lines changed

32 files changed

+1813
-403
lines changed

Godeps/Godeps.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/features/kube_features.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ const (
109109
// Ability to expand persistent volumes' file system without unmounting volumes.
110110
ExpandInUsePersistentVolumes utilfeature.Feature = "ExpandInUsePersistentVolumes"
111111

112+
// owner: @gnufied
113+
// alpha: v1.14
114+
// Ability to expand CSI volumes
115+
ExpandCSIVolumes utilfeature.Feature = "ExpandCSIVolumes"
116+
112117
// owner: @verb
113118
// alpha: v1.10
114119
//
@@ -450,6 +455,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
450455
QOSReserved: {Default: false, PreRelease: utilfeature.Alpha},
451456
ExpandPersistentVolumes: {Default: true, PreRelease: utilfeature.Beta},
452457
ExpandInUsePersistentVolumes: {Default: false, PreRelease: utilfeature.Alpha},
458+
ExpandCSIVolumes: {Default: false, PreRelease: utilfeature.Alpha},
453459
AttachVolumeLimit: {Default: true, PreRelease: utilfeature.Beta},
454460
CPUManager: {Default: true, PreRelease: utilfeature.Beta},
455461
CPUCFSQuotaPeriod: {Default: false, PreRelease: utilfeature.Alpha},

pkg/kubelet/volumemanager/cache/actual_state_of_world.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ func (asw *actualStateOfWorld) MarkFSResizeRequired(
542542
}
543543

544544
volumePlugin, err :=
545-
asw.volumePluginMgr.FindExpandablePluginBySpec(podObj.volumeSpec)
545+
asw.volumePluginMgr.FindNodeExpandablePluginBySpec(podObj.volumeSpec)
546546
if err != nil || volumePlugin == nil {
547547
// Log and continue processing
548548
klog.Errorf(

pkg/volume/awsebs/aws_ebs.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -317,12 +317,15 @@ func (plugin *awsElasticBlockStorePlugin) ExpandVolumeDevice(
317317
return awsVolume.ResizeDisk(volumeID, oldSize, newSize)
318318
}
319319

320-
func (plugin *awsElasticBlockStorePlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error {
321-
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), devicePath, deviceMountPath)
322-
return err
320+
func (plugin *awsElasticBlockStorePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
321+
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
322+
if err != nil {
323+
return false, err
324+
}
325+
return true, nil
323326
}
324327

325-
var _ volume.FSResizableVolumePlugin = &awsElasticBlockStorePlugin{}
328+
var _ volume.NodeExpandableVolumePlugin = &awsElasticBlockStorePlugin{}
326329
var _ volume.ExpandableVolumePlugin = &awsElasticBlockStorePlugin{}
327330
var _ volume.VolumePluginWithAttachLimits = &awsElasticBlockStorePlugin{}
328331

pkg/volume/azure_dd/azure_dd.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,15 @@ func (plugin *azureDataDiskPlugin) ExpandVolumeDevice(
312312
return diskController.ResizeDisk(spec.PersistentVolume.Spec.AzureDisk.DataDiskURI, oldSize, newSize)
313313
}
314314

315-
func (plugin *azureDataDiskPlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error {
316-
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), devicePath, deviceMountPath)
317-
return err
315+
func (plugin *azureDataDiskPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
316+
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
317+
if err != nil {
318+
return false, err
319+
}
320+
return true, nil
318321
}
319322

320-
var _ volume.FSResizableVolumePlugin = &azureDataDiskPlugin{}
323+
var _ volume.NodeExpandableVolumePlugin = &azureDataDiskPlugin{}
321324

322325
func (plugin *azureDataDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
323326
mounter := plugin.host.GetMounter(plugin.GetPluginName())

pkg/volume/cinder/cinder.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,15 @@ func (plugin *cinderPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resour
304304
return expandedSize, nil
305305
}
306306

307-
func (plugin *cinderPlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error {
308-
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), devicePath, deviceMountPath)
309-
return err
307+
func (plugin *cinderPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
308+
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
309+
if err != nil {
310+
return false, err
311+
}
312+
return true, nil
310313
}
311314

312-
var _ volume.FSResizableVolumePlugin = &cinderPlugin{}
315+
var _ volume.NodeExpandableVolumePlugin = &cinderPlugin{}
313316

314317
func (plugin *cinderPlugin) RequiresFSResize() bool {
315318
return true

pkg/volume/csi/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ go_library(
1010
"csi_mounter.go",
1111
"csi_plugin.go",
1212
"csi_util.go",
13+
"expander.go",
1314
],
1415
importpath = "k8s.io/kubernetes/pkg/volume/csi",
1516
visibility = ["//visibility:public"],
@@ -22,6 +23,7 @@ go_library(
2223
"//staging/src/k8s.io/api/core/v1:go_default_library",
2324
"//staging/src/k8s.io/api/storage/v1:go_default_library",
2425
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
26+
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
2527
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
2628
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
2729
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
@@ -50,6 +52,7 @@ go_test(
5052
"csi_drivers_store_test.go",
5153
"csi_mounter_test.go",
5254
"csi_plugin_test.go",
55+
"expander_test.go",
5356
],
5457
embed = [":go_default_library"],
5558
deps = [

pkg/volume/csi/csi_client.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
2929
"google.golang.org/grpc"
3030
api "k8s.io/api/core/v1"
31+
"k8s.io/apimachinery/pkg/api/resource"
3132
utilversion "k8s.io/apimachinery/pkg/util/version"
3233
"k8s.io/apimachinery/pkg/util/wait"
3334
utilfeature "k8s.io/apiserver/pkg/util/feature"
@@ -55,6 +56,7 @@ type csiClient interface {
5556
fsType string,
5657
mountOptions []string,
5758
) error
59+
NodeExpandVolume(ctx context.Context, volumeid, volumePath string, newSize resource.Quantity) (resource.Quantity, error)
5860
NodeUnpublishVolume(
5961
ctx context.Context,
6062
volID string,
@@ -71,6 +73,7 @@ type csiClient interface {
7173
) error
7274
NodeUnstageVolume(ctx context.Context, volID, stagingTargetPath string) error
7375
NodeSupportsStageUnstage(ctx context.Context) (bool, error)
76+
NodeSupportsNodeExpand(ctx context.Context) (bool, error)
7477
}
7578

7679
// Strongly typed address
@@ -304,6 +307,41 @@ func (c *csiDriverClient) NodePublishVolume(
304307

305308
}
306309

310+
func (c *csiDriverClient) NodeExpandVolume(ctx context.Context, volumeID, volumePath string, newSize resource.Quantity) (resource.Quantity, error) {
311+
if c.nodeV1ClientCreator == nil {
312+
return newSize, fmt.Errorf("version of CSI driver does not support volume expansion")
313+
}
314+
315+
if volumeID == "" {
316+
return newSize, errors.New("missing volume id")
317+
}
318+
if volumePath == "" {
319+
return newSize, errors.New("missing volume path")
320+
}
321+
322+
if newSize.Value() < 0 {
323+
return newSize, errors.New("size can not be less than 0")
324+
}
325+
326+
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr)
327+
if err != nil {
328+
return newSize, err
329+
}
330+
defer closer.Close()
331+
332+
req := &csipbv1.NodeExpandVolumeRequest{
333+
VolumeId: volumeID,
334+
VolumePath: volumePath,
335+
CapacityRange: &csipbv1.CapacityRange{RequiredBytes: newSize.Value()},
336+
}
337+
resp, err := nodeClient.NodeExpandVolume(ctx, req)
338+
if err != nil {
339+
return newSize, err
340+
}
341+
updatedQuantity := resource.NewQuantity(resp.CapacityBytes, resource.BinarySI)
342+
return *updatedQuantity, nil
343+
}
344+
307345
func (c *csiDriverClient) nodePublishVolumeV1(
308346
ctx context.Context,
309347
volID string,
@@ -624,6 +662,41 @@ func (c *csiDriverClient) nodeUnstageVolumeV0(ctx context.Context, volID, stagin
624662
return err
625663
}
626664

665+
func (c *csiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
666+
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if Node has EXPAND_VOLUME capability"))
667+
668+
if c.nodeV1ClientCreator != nil {
669+
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr)
670+
if err != nil {
671+
return false, err
672+
}
673+
defer closer.Close()
674+
675+
req := &csipbv1.NodeGetCapabilitiesRequest{}
676+
resp, err := nodeClient.NodeGetCapabilities(ctx, req)
677+
if err != nil {
678+
return false, err
679+
}
680+
681+
capabilities := resp.GetCapabilities()
682+
683+
nodeExpandSet := false
684+
if capabilities == nil {
685+
return false, nil
686+
}
687+
for _, capability := range capabilities {
688+
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME {
689+
nodeExpandSet = true
690+
}
691+
}
692+
return nodeExpandSet, nil
693+
} else if c.nodeV0ClientCreator != nil {
694+
return false, nil
695+
}
696+
return false, fmt.Errorf("failed to call NodeSupportsNodeExpand. Both nodeV1ClientCreator and nodeV0ClientCreator are nil")
697+
698+
}
699+
627700
func (c *csiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
628701
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsStageUnstage"))
629702

pkg/volume/csi/csi_client_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525

2626
csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
2727
api "k8s.io/api/core/v1"
28+
"k8s.io/apimachinery/pkg/api/resource"
2829
"k8s.io/kubernetes/pkg/volume/csi/fake"
2930
)
3031

@@ -40,6 +41,13 @@ func newFakeCsiDriverClient(t *testing.T, stagingCapable bool) *fakeCsiDriverCli
4041
}
4142
}
4243

44+
func newFakeCsiDriverClientWithExpansion(t *testing.T, stagingCapable bool, expansionSet bool) *fakeCsiDriverClient {
45+
return &fakeCsiDriverClient{
46+
t: t,
47+
nodeClient: fake.NewNodeClientWithExpansion(stagingCapable, expansionSet),
48+
}
49+
}
50+
4351
func (c *fakeCsiDriverClient) NodeGetInfo(ctx context.Context) (
4452
nodeID string,
4553
maxVolumePerNode int64,
@@ -144,6 +152,28 @@ func (c *fakeCsiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stag
144152
return err
145153
}
146154

155+
func (c *fakeCsiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
156+
c.t.Log("calling fake.NodeSupportsNodeExpand...")
157+
req := &csipbv1.NodeGetCapabilitiesRequest{}
158+
159+
resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
160+
if err != nil {
161+
return false, err
162+
}
163+
164+
capabilities := resp.GetCapabilities()
165+
166+
if capabilities == nil {
167+
return false, nil
168+
}
169+
for _, capability := range capabilities {
170+
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME {
171+
return true, nil
172+
}
173+
}
174+
return false, nil
175+
}
176+
147177
func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
148178
c.t.Log("calling fake.NodeGetCapabilities for NodeSupportsStageUnstage...")
149179
req := &csipbv1.NodeGetCapabilitiesRequest{}
@@ -166,10 +196,29 @@ func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (boo
166196
return stageUnstageSet, nil
167197
}
168198

199+
func (c *fakeCsiDriverClient) NodeExpandVolume(ctx context.Context, volumeid, volumePath string, newSize resource.Quantity) (resource.Quantity, error) {
200+
c.t.Log("calling fake.NodeExpandVolume")
201+
req := &csipbv1.NodeExpandVolumeRequest{
202+
VolumeId: volumeid,
203+
VolumePath: volumePath,
204+
CapacityRange: &csipbv1.CapacityRange{RequiredBytes: newSize.Value()},
205+
}
206+
resp, err := c.nodeClient.NodeExpandVolume(ctx, req)
207+
if err != nil {
208+
return newSize, err
209+
}
210+
updatedQuantity := resource.NewQuantity(resp.CapacityBytes, resource.BinarySI)
211+
return *updatedQuantity, nil
212+
}
213+
169214
func setupClient(t *testing.T, stageUnstageSet bool) csiClient {
170215
return newFakeCsiDriverClient(t, stageUnstageSet)
171216
}
172217

218+
func setupClientWithExpansion(t *testing.T, stageUnstageSet bool, expansionSet bool) csiClient {
219+
return newFakeCsiDriverClientWithExpansion(t, stageUnstageSet, expansionSet)
220+
}
221+
173222
func checkErr(t *testing.T, expectedAnError bool, actualError error) {
174223
t.Helper()
175224

@@ -415,3 +464,59 @@ func TestClientNodeUnstageVolume(t *testing.T) {
415464
}
416465
}
417466
}
467+
468+
func TestNodeExpandVolume(t *testing.T) {
469+
testCases := []struct {
470+
name string
471+
volID string
472+
volumePath string
473+
newSize resource.Quantity
474+
mustFail bool
475+
err error
476+
}{
477+
{
478+
name: "with all correct values",
479+
volID: "vol-abcde",
480+
volumePath: "/foo/bar",
481+
newSize: resource.MustParse("10Gi"),
482+
mustFail: false,
483+
},
484+
{
485+
name: "with missing volume-id",
486+
volumePath: "/foo/bar",
487+
newSize: resource.MustParse("10Gi"),
488+
mustFail: true,
489+
},
490+
{
491+
name: "with missing volume path",
492+
volID: "vol-1234",
493+
newSize: resource.MustParse("10Gi"),
494+
mustFail: true,
495+
},
496+
{
497+
name: "with invalid quantity",
498+
volID: "vol-1234",
499+
volumePath: "/foo/bar",
500+
newSize: *resource.NewQuantity(-10, resource.DecimalSI),
501+
mustFail: true,
502+
},
503+
}
504+
505+
for _, tc := range testCases {
506+
t.Logf("Running test cases : %s", tc.name)
507+
fakeCloser := fake.NewCloser(t)
508+
client := &csiDriverClient{
509+
driverName: "Fake Driver Name",
510+
nodeV1ClientCreator: func(addr csiAddr) (csipbv1.NodeClient, io.Closer, error) {
511+
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
512+
nodeClient.SetNextError(tc.err)
513+
return nodeClient, fakeCloser, nil
514+
},
515+
}
516+
_, err := client.NodeExpandVolume(context.Background(), tc.volID, tc.volumePath, tc.newSize)
517+
checkErr(t, tc.mustFail, err)
518+
if !tc.mustFail {
519+
fakeCloser.Check()
520+
}
521+
}
522+
}

0 commit comments

Comments
 (0)