Unverified Commit 3624c74c authored by Kubernetes Prow Robot's avatar Kubernetes Prow Robot Committed by GitHub

Merge pull request #74863 from gnufied/csi-volume-expansion

CSI volume expansion
parents f229aa0a 1bd9ed06
...@@ -975,8 +975,8 @@ ...@@ -975,8 +975,8 @@
}, },
{ {
"ImportPath": "github.com/container-storage-interface/spec/lib/go/csi", "ImportPath": "github.com/container-storage-interface/spec/lib/go/csi",
"Comment": "v1.0.0", "Comment": "v1.1.0-rc1",
"Rev": "ed0bb0e1557548aa028307f48728767cfe8f6345" "Rev": "c751be1edc7952e7aac35720d5b34b669e48f113"
}, },
{ {
"ImportPath": "github.com/containerd/console", "ImportPath": "github.com/containerd/console",
......
...@@ -109,6 +109,11 @@ const ( ...@@ -109,6 +109,11 @@ const (
// Ability to expand persistent volumes' file system without unmounting volumes. // Ability to expand persistent volumes' file system without unmounting volumes.
ExpandInUsePersistentVolumes utilfeature.Feature = "ExpandInUsePersistentVolumes" ExpandInUsePersistentVolumes utilfeature.Feature = "ExpandInUsePersistentVolumes"
// owner: @gnufied
// alpha: v1.14
// Ability to expand CSI volumes
ExpandCSIVolumes utilfeature.Feature = "ExpandCSIVolumes"
// owner: @verb // owner: @verb
// alpha: v1.10 // alpha: v1.10
// //
...@@ -450,6 +455,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS ...@@ -450,6 +455,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
QOSReserved: {Default: false, PreRelease: utilfeature.Alpha}, QOSReserved: {Default: false, PreRelease: utilfeature.Alpha},
ExpandPersistentVolumes: {Default: true, PreRelease: utilfeature.Beta}, ExpandPersistentVolumes: {Default: true, PreRelease: utilfeature.Beta},
ExpandInUsePersistentVolumes: {Default: false, PreRelease: utilfeature.Alpha}, ExpandInUsePersistentVolumes: {Default: false, PreRelease: utilfeature.Alpha},
ExpandCSIVolumes: {Default: false, PreRelease: utilfeature.Alpha},
AttachVolumeLimit: {Default: true, PreRelease: utilfeature.Beta}, AttachVolumeLimit: {Default: true, PreRelease: utilfeature.Beta},
CPUManager: {Default: true, PreRelease: utilfeature.Beta}, CPUManager: {Default: true, PreRelease: utilfeature.Beta},
CPUCFSQuotaPeriod: {Default: false, PreRelease: utilfeature.Alpha}, CPUCFSQuotaPeriod: {Default: false, PreRelease: utilfeature.Alpha},
......
...@@ -542,7 +542,7 @@ func (asw *actualStateOfWorld) MarkFSResizeRequired( ...@@ -542,7 +542,7 @@ func (asw *actualStateOfWorld) MarkFSResizeRequired(
} }
volumePlugin, err := volumePlugin, err :=
asw.volumePluginMgr.FindExpandablePluginBySpec(podObj.volumeSpec) asw.volumePluginMgr.FindNodeExpandablePluginBySpec(podObj.volumeSpec)
if err != nil || volumePlugin == nil { if err != nil || volumePlugin == nil {
// Log and continue processing // Log and continue processing
klog.Errorf( klog.Errorf(
......
...@@ -317,12 +317,15 @@ func (plugin *awsElasticBlockStorePlugin) ExpandVolumeDevice( ...@@ -317,12 +317,15 @@ func (plugin *awsElasticBlockStorePlugin) ExpandVolumeDevice(
return awsVolume.ResizeDisk(volumeID, oldSize, newSize) return awsVolume.ResizeDisk(volumeID, oldSize, newSize)
} }
func (plugin *awsElasticBlockStorePlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error { func (plugin *awsElasticBlockStorePlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), devicePath, deviceMountPath) _, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
return err if err != nil {
return false, err
}
return true, nil
} }
var _ volume.FSResizableVolumePlugin = &awsElasticBlockStorePlugin{} var _ volume.NodeExpandableVolumePlugin = &awsElasticBlockStorePlugin{}
var _ volume.ExpandableVolumePlugin = &awsElasticBlockStorePlugin{} var _ volume.ExpandableVolumePlugin = &awsElasticBlockStorePlugin{}
var _ volume.VolumePluginWithAttachLimits = &awsElasticBlockStorePlugin{} var _ volume.VolumePluginWithAttachLimits = &awsElasticBlockStorePlugin{}
......
...@@ -312,12 +312,15 @@ func (plugin *azureDataDiskPlugin) ExpandVolumeDevice( ...@@ -312,12 +312,15 @@ func (plugin *azureDataDiskPlugin) ExpandVolumeDevice(
return diskController.ResizeDisk(spec.PersistentVolume.Spec.AzureDisk.DataDiskURI, oldSize, newSize) return diskController.ResizeDisk(spec.PersistentVolume.Spec.AzureDisk.DataDiskURI, oldSize, newSize)
} }
func (plugin *azureDataDiskPlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error { func (plugin *azureDataDiskPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), devicePath, deviceMountPath) _, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
return err if err != nil {
return false, err
}
return true, nil
} }
var _ volume.FSResizableVolumePlugin = &azureDataDiskPlugin{} var _ volume.NodeExpandableVolumePlugin = &azureDataDiskPlugin{}
func (plugin *azureDataDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { func (plugin *azureDataDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
mounter := plugin.host.GetMounter(plugin.GetPluginName()) mounter := plugin.host.GetMounter(plugin.GetPluginName())
......
...@@ -304,12 +304,15 @@ func (plugin *cinderPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resour ...@@ -304,12 +304,15 @@ func (plugin *cinderPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resour
return expandedSize, nil return expandedSize, nil
} }
func (plugin *cinderPlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error { func (plugin *cinderPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), devicePath, deviceMountPath) _, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
return err if err != nil {
return false, err
}
return true, nil
} }
var _ volume.FSResizableVolumePlugin = &cinderPlugin{} var _ volume.NodeExpandableVolumePlugin = &cinderPlugin{}
func (plugin *cinderPlugin) RequiresFSResize() bool { func (plugin *cinderPlugin) RequiresFSResize() bool {
return true return true
......
...@@ -10,6 +10,7 @@ go_library( ...@@ -10,6 +10,7 @@ go_library(
"csi_mounter.go", "csi_mounter.go",
"csi_plugin.go", "csi_plugin.go",
"csi_util.go", "csi_util.go",
"expander.go",
], ],
importpath = "k8s.io/kubernetes/pkg/volume/csi", importpath = "k8s.io/kubernetes/pkg/volume/csi",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
...@@ -22,6 +23,7 @@ go_library( ...@@ -22,6 +23,7 @@ go_library(
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/api/storage/v1:go_default_library", "//staging/src/k8s.io/api/storage/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
...@@ -50,6 +52,7 @@ go_test( ...@@ -50,6 +52,7 @@ go_test(
"csi_drivers_store_test.go", "csi_drivers_store_test.go",
"csi_mounter_test.go", "csi_mounter_test.go",
"csi_plugin_test.go", "csi_plugin_test.go",
"expander_test.go",
], ],
embed = [":go_default_library"], embed = [":go_default_library"],
deps = [ deps = [
......
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
"google.golang.org/grpc" "google.golang.org/grpc"
api "k8s.io/api/core/v1" api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
utilversion "k8s.io/apimachinery/pkg/util/version" utilversion "k8s.io/apimachinery/pkg/util/version"
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
...@@ -55,6 +56,7 @@ type csiClient interface { ...@@ -55,6 +56,7 @@ type csiClient interface {
fsType string, fsType string,
mountOptions []string, mountOptions []string,
) error ) error
NodeExpandVolume(ctx context.Context, volumeid, volumePath string, newSize resource.Quantity) (resource.Quantity, error)
NodeUnpublishVolume( NodeUnpublishVolume(
ctx context.Context, ctx context.Context,
volID string, volID string,
...@@ -71,6 +73,7 @@ type csiClient interface { ...@@ -71,6 +73,7 @@ type csiClient interface {
) error ) error
NodeUnstageVolume(ctx context.Context, volID, stagingTargetPath string) error NodeUnstageVolume(ctx context.Context, volID, stagingTargetPath string) error
NodeSupportsStageUnstage(ctx context.Context) (bool, error) NodeSupportsStageUnstage(ctx context.Context) (bool, error)
NodeSupportsNodeExpand(ctx context.Context) (bool, error)
} }
// Strongly typed address // Strongly typed address
...@@ -304,6 +307,41 @@ func (c *csiDriverClient) NodePublishVolume( ...@@ -304,6 +307,41 @@ func (c *csiDriverClient) NodePublishVolume(
} }
func (c *csiDriverClient) NodeExpandVolume(ctx context.Context, volumeID, volumePath string, newSize resource.Quantity) (resource.Quantity, error) {
if c.nodeV1ClientCreator == nil {
return newSize, fmt.Errorf("version of CSI driver does not support volume expansion")
}
if volumeID == "" {
return newSize, errors.New("missing volume id")
}
if volumePath == "" {
return newSize, errors.New("missing volume path")
}
if newSize.Value() < 0 {
return newSize, errors.New("size can not be less than 0")
}
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr)
if err != nil {
return newSize, err
}
defer closer.Close()
req := &csipbv1.NodeExpandVolumeRequest{
VolumeId: volumeID,
VolumePath: volumePath,
CapacityRange: &csipbv1.CapacityRange{RequiredBytes: newSize.Value()},
}
resp, err := nodeClient.NodeExpandVolume(ctx, req)
if err != nil {
return newSize, err
}
updatedQuantity := resource.NewQuantity(resp.CapacityBytes, resource.BinarySI)
return *updatedQuantity, nil
}
func (c *csiDriverClient) nodePublishVolumeV1( func (c *csiDriverClient) nodePublishVolumeV1(
ctx context.Context, ctx context.Context,
volID string, volID string,
...@@ -624,6 +662,41 @@ func (c *csiDriverClient) nodeUnstageVolumeV0(ctx context.Context, volID, stagin ...@@ -624,6 +662,41 @@ func (c *csiDriverClient) nodeUnstageVolumeV0(ctx context.Context, volID, stagin
return err return err
} }
func (c *csiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if Node has EXPAND_VOLUME capability"))
if c.nodeV1ClientCreator != nil {
nodeClient, closer, err := c.nodeV1ClientCreator(c.addr)
if err != nil {
return false, err
}
defer closer.Close()
req := &csipbv1.NodeGetCapabilitiesRequest{}
resp, err := nodeClient.NodeGetCapabilities(ctx, req)
if err != nil {
return false, err
}
capabilities := resp.GetCapabilities()
nodeExpandSet := false
if capabilities == nil {
return false, nil
}
for _, capability := range capabilities {
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME {
nodeExpandSet = true
}
}
return nodeExpandSet, nil
} else if c.nodeV0ClientCreator != nil {
return false, nil
}
return false, fmt.Errorf("failed to call NodeSupportsNodeExpand. Both nodeV1ClientCreator and nodeV0ClientCreator are nil")
}
func (c *csiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) { func (c *csiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsStageUnstage")) klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsStageUnstage"))
......
...@@ -25,6 +25,7 @@ import ( ...@@ -25,6 +25,7 @@ import (
csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
api "k8s.io/api/core/v1" api "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/kubernetes/pkg/volume/csi/fake" "k8s.io/kubernetes/pkg/volume/csi/fake"
) )
...@@ -40,6 +41,13 @@ func newFakeCsiDriverClient(t *testing.T, stagingCapable bool) *fakeCsiDriverCli ...@@ -40,6 +41,13 @@ func newFakeCsiDriverClient(t *testing.T, stagingCapable bool) *fakeCsiDriverCli
} }
} }
func newFakeCsiDriverClientWithExpansion(t *testing.T, stagingCapable bool, expansionSet bool) *fakeCsiDriverClient {
return &fakeCsiDriverClient{
t: t,
nodeClient: fake.NewNodeClientWithExpansion(stagingCapable, expansionSet),
}
}
func (c *fakeCsiDriverClient) NodeGetInfo(ctx context.Context) ( func (c *fakeCsiDriverClient) NodeGetInfo(ctx context.Context) (
nodeID string, nodeID string,
maxVolumePerNode int64, maxVolumePerNode int64,
...@@ -144,6 +152,28 @@ func (c *fakeCsiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stag ...@@ -144,6 +152,28 @@ func (c *fakeCsiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stag
return err return err
} }
func (c *fakeCsiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
c.t.Log("calling fake.NodeSupportsNodeExpand...")
req := &csipbv1.NodeGetCapabilitiesRequest{}
resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
if err != nil {
return false, err
}
capabilities := resp.GetCapabilities()
if capabilities == nil {
return false, nil
}
for _, capability := range capabilities {
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME {
return true, nil
}
}
return false, nil
}
func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) { func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
c.t.Log("calling fake.NodeGetCapabilities for NodeSupportsStageUnstage...") c.t.Log("calling fake.NodeGetCapabilities for NodeSupportsStageUnstage...")
req := &csipbv1.NodeGetCapabilitiesRequest{} req := &csipbv1.NodeGetCapabilitiesRequest{}
...@@ -166,10 +196,29 @@ func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (boo ...@@ -166,10 +196,29 @@ func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (boo
return stageUnstageSet, nil return stageUnstageSet, nil
} }
func (c *fakeCsiDriverClient) NodeExpandVolume(ctx context.Context, volumeid, volumePath string, newSize resource.Quantity) (resource.Quantity, error) {
c.t.Log("calling fake.NodeExpandVolume")
req := &csipbv1.NodeExpandVolumeRequest{
VolumeId: volumeid,
VolumePath: volumePath,
CapacityRange: &csipbv1.CapacityRange{RequiredBytes: newSize.Value()},
}
resp, err := c.nodeClient.NodeExpandVolume(ctx, req)
if err != nil {
return newSize, err
}
updatedQuantity := resource.NewQuantity(resp.CapacityBytes, resource.BinarySI)
return *updatedQuantity, nil
}
func setupClient(t *testing.T, stageUnstageSet bool) csiClient { func setupClient(t *testing.T, stageUnstageSet bool) csiClient {
return newFakeCsiDriverClient(t, stageUnstageSet) return newFakeCsiDriverClient(t, stageUnstageSet)
} }
func setupClientWithExpansion(t *testing.T, stageUnstageSet bool, expansionSet bool) csiClient {
return newFakeCsiDriverClientWithExpansion(t, stageUnstageSet, expansionSet)
}
func checkErr(t *testing.T, expectedAnError bool, actualError error) { func checkErr(t *testing.T, expectedAnError bool, actualError error) {
t.Helper() t.Helper()
...@@ -415,3 +464,59 @@ func TestClientNodeUnstageVolume(t *testing.T) { ...@@ -415,3 +464,59 @@ func TestClientNodeUnstageVolume(t *testing.T) {
} }
} }
} }
func TestNodeExpandVolume(t *testing.T) {
testCases := []struct {
name string
volID string
volumePath string
newSize resource.Quantity
mustFail bool
err error
}{
{
name: "with all correct values",
volID: "vol-abcde",
volumePath: "/foo/bar",
newSize: resource.MustParse("10Gi"),
mustFail: false,
},
{
name: "with missing volume-id",
volumePath: "/foo/bar",
newSize: resource.MustParse("10Gi"),
mustFail: true,
},
{
name: "with missing volume path",
volID: "vol-1234",
newSize: resource.MustParse("10Gi"),
mustFail: true,
},
{
name: "with invalid quantity",
volID: "vol-1234",
volumePath: "/foo/bar",
newSize: *resource.NewQuantity(-10, resource.DecimalSI),
mustFail: true,
},
}
for _, tc := range testCases {
t.Logf("Running test cases : %s", tc.name)
fakeCloser := fake.NewCloser(t)
client := &csiDriverClient{
driverName: "Fake Driver Name",
nodeV1ClientCreator: func(addr csiAddr) (csipbv1.NodeClient, io.Closer, error) {
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
nodeClient.SetNextError(tc.err)
return nodeClient, fakeCloser, nil
},
}
_, err := client.NodeExpandVolume(context.Background(), tc.volID, tc.volumePath, tc.newSize)
checkErr(t, tc.mustFail, err)
if !tc.mustFail {
fakeCloser.Check()
}
}
}
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csi
import (
"context"
"fmt"
api "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume"
)
var _ volume.NodeExpandableVolumePlugin = &csiPlugin{}
func (c *csiPlugin) RequiresFSResize() bool {
// We could check plugin's node capability but we instead are going to rely on
// NodeExpand to do the right thing and return early if plugin does not have
// node expansion capability.
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandCSIVolumes) {
klog.V(4).Infof("Resizing is not enabled for CSI volume")
return false
}
return true
}
func (c *csiPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
klog.V(4).Infof(log("Expander.NodeExpand(%s)", resizeOptions.DeviceMountPath))
csiSource, err := getCSISourceFromSpec(resizeOptions.VolumeSpec)
if err != nil {
klog.Error(log("Expander.NodeExpand failed to get CSI persistent source: %v", err))
return false, err
}
csClient, err := newCsiDriverClient(csiDriverName(csiSource.Driver))
if err != nil {
return false, err
}
return c.nodeExpandWithClient(resizeOptions, csiSource, csClient)
}
func (c *csiPlugin) nodeExpandWithClient(
resizeOptions volume.NodeResizeOptions,
csiSource *api.CSIPersistentVolumeSource,
csClient csiClient) (bool, error) {
driverName := csiSource.Driver
ctx, cancel := context.WithTimeout(context.Background(), csiTimeout)
defer cancel()
nodeExpandSet, err := csClient.NodeSupportsNodeExpand(ctx)
if err != nil {
return false, fmt.Errorf("Expander.NodeExpand failed to check if node supports expansion : %v", err)
}
if !nodeExpandSet {
return false, fmt.Errorf("Expander.NodeExpand found CSI plugin %s/%s to not support node expansion", c.GetPluginName(), driverName)
}
// Check whether "STAGE_UNSTAGE_VOLUME" is set
stageUnstageSet, err := csClient.NodeSupportsStageUnstage(ctx)
if err != nil {
return false, fmt.Errorf("Expander.NodeExpand failed to check if plugins supports stage_unstage %v", err)
}
// if plugin does not support STAGE_UNSTAGE but CSI volume path is staged
// it must mean this was placeholder staging performed by k8s and not CSI staging
// in which case we should return from here so as volume can be node published
// before we can resize
if !stageUnstageSet && resizeOptions.CSIVolumePhase == volume.CSIVolumeStaged {
return false, nil
}
_, err = csClient.NodeExpandVolume(ctx, csiSource.VolumeHandle, resizeOptions.DeviceMountPath, resizeOptions.NewSize)
if err != nil {
return false, fmt.Errorf("Expander.NodeExpand failed to expand the volume : %v", err)
}
return true, nil
}
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csi
import (
"os"
"testing"
"k8s.io/apimachinery/pkg/api/resource"
"k8s.io/kubernetes/pkg/volume"
)
func TestNodeExpand(t *testing.T) {
tests := []struct {
name string
nodeExpansion bool
nodeStageSet bool
volumePhase volume.CSIVolumePhaseType
success bool
}{
{
name: "when node expansion is not set",
success: false,
},
{
name: "when nodeExpansion=on, nodeStage=on, volumePhase=staged",
nodeExpansion: true,
nodeStageSet: true,
volumePhase: volume.CSIVolumeStaged,
success: true,
},
{
name: "when nodeExpansion=on, nodeStage=off, volumePhase=staged",
nodeExpansion: true,
volumePhase: volume.CSIVolumeStaged,
success: false,
},
{
name: "when nodeExpansion=on, nodeStage=on, volumePhase=published",
nodeExpansion: true,
nodeStageSet: true,
volumePhase: volume.CSIVolumePublished,
success: true,
},
{
name: "when nodeExpansion=on, nodeStage=off, volumePhase=published",
nodeExpansion: true,
volumePhase: volume.CSIVolumePublished,
success: true,
},
}
for _, tc := range tests {
plug, tmpDir := newTestPlugin(t, nil)
defer os.RemoveAll(tmpDir)
spec := volume.NewSpecFromPersistentVolume(makeTestPV("test-pv", 10, "expandable", "test-vol"), false)
newSize, _ := resource.ParseQuantity("20Gi")
resizeOptions := volume.NodeResizeOptions{
VolumeSpec: spec,
NewSize: newSize,
DeviceMountPath: "/foo/bar",
CSIVolumePhase: tc.volumePhase,
}
csiSource, _ := getCSISourceFromSpec(resizeOptions.VolumeSpec)
csClient := setupClientWithExpansion(t, tc.nodeStageSet, tc.nodeExpansion)
ok, err := plug.nodeExpandWithClient(resizeOptions, csiSource, csClient)
if ok != tc.success {
if err != nil {
t.Errorf("For %s : expected %v got %v with %v", tc.name, tc.success, ok, err)
} else {
t.Errorf("For %s : expected %v got %v", tc.name, tc.success, ok)
}
}
}
}
...@@ -67,6 +67,7 @@ type NodeClient struct { ...@@ -67,6 +67,7 @@ type NodeClient struct {
nodePublishedVolumes map[string]CSIVolume nodePublishedVolumes map[string]CSIVolume
nodeStagedVolumes map[string]CSIVolume nodeStagedVolumes map[string]CSIVolume
stageUnstageSet bool stageUnstageSet bool
expansionSet bool
nodeGetInfoResp *csipb.NodeGetInfoResponse nodeGetInfoResp *csipb.NodeGetInfoResponse
nextErr error nextErr error
} }
...@@ -80,6 +81,15 @@ func NewNodeClient(stageUnstageSet bool) *NodeClient { ...@@ -80,6 +81,15 @@ func NewNodeClient(stageUnstageSet bool) *NodeClient {
} }
} }
func NewNodeClientWithExpansion(stageUnstageSet bool, expansionSet bool) *NodeClient {
return &NodeClient{
nodePublishedVolumes: make(map[string]CSIVolume),
nodeStagedVolumes: make(map[string]CSIVolume),
stageUnstageSet: stageUnstageSet,
expansionSet: expansionSet,
}
}
// SetNextError injects next expected error // SetNextError injects next expected error
func (f *NodeClient) SetNextError(err error) { func (f *NodeClient) SetNextError(err error) {
f.nextErr = err f.nextErr = err
...@@ -195,6 +205,29 @@ func (f *NodeClient) NodeUnstageVolume(ctx context.Context, req *csipb.NodeUnsta ...@@ -195,6 +205,29 @@ func (f *NodeClient) NodeUnstageVolume(ctx context.Context, req *csipb.NodeUnsta
return &csipb.NodeUnstageVolumeResponse{}, nil return &csipb.NodeUnstageVolumeResponse{}, nil
} }
// NodeExpandVolume implements csi method
func (f *NodeClient) NodeExpandVolume(ctx context.Context, req *csipb.NodeExpandVolumeRequest, opts ...grpc.CallOption) (*csipb.NodeExpandVolumeResponse, error) {
if f.nextErr != nil {
return nil, f.nextErr
}
if req.GetVolumeId() == "" {
return nil, errors.New("missing volume id")
}
if req.GetVolumePath() == "" {
return nil, errors.New("missing volume path")
}
if req.GetCapacityRange().RequiredBytes <= 0 {
return nil, errors.New("required bytes should be greater than 0")
}
resp := &csipb.NodeExpandVolumeResponse{
CapacityBytes: req.GetCapacityRange().RequiredBytes,
}
return resp, nil
}
// NodeGetId implements csi method // NodeGetId implements csi method
func (f *NodeClient) NodeGetInfo(ctx context.Context, in *csipb.NodeGetInfoRequest, opts ...grpc.CallOption) (*csipb.NodeGetInfoResponse, error) { func (f *NodeClient) NodeGetInfo(ctx context.Context, in *csipb.NodeGetInfoRequest, opts ...grpc.CallOption) (*csipb.NodeGetInfoResponse, error) {
if f.nextErr != nil { if f.nextErr != nil {
...@@ -206,20 +239,27 @@ func (f *NodeClient) NodeGetInfo(ctx context.Context, in *csipb.NodeGetInfoReque ...@@ -206,20 +239,27 @@ func (f *NodeClient) NodeGetInfo(ctx context.Context, in *csipb.NodeGetInfoReque
// NodeGetCapabilities implements csi method // NodeGetCapabilities implements csi method
func (f *NodeClient) NodeGetCapabilities(ctx context.Context, in *csipb.NodeGetCapabilitiesRequest, opts ...grpc.CallOption) (*csipb.NodeGetCapabilitiesResponse, error) { func (f *NodeClient) NodeGetCapabilities(ctx context.Context, in *csipb.NodeGetCapabilitiesRequest, opts ...grpc.CallOption) (*csipb.NodeGetCapabilitiesResponse, error) {
resp := &csipb.NodeGetCapabilitiesResponse{ resp := &csipb.NodeGetCapabilitiesResponse{
Capabilities: []*csipb.NodeServiceCapability{ Capabilities: []*csipb.NodeServiceCapability{},
{ }
Type: &csipb.NodeServiceCapability_Rpc{ if f.stageUnstageSet {
Rpc: &csipb.NodeServiceCapability_RPC{ resp.Capabilities = append(resp.Capabilities, &csipb.NodeServiceCapability{
Type: csipb.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, Type: &csipb.NodeServiceCapability_Rpc{
}, Rpc: &csipb.NodeServiceCapability_RPC{
Type: csipb.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
}, },
}, },
}, })
} }
if f.stageUnstageSet { if f.expansionSet {
return resp, nil resp.Capabilities = append(resp.Capabilities, &csipb.NodeServiceCapability{
Type: &csipb.NodeServiceCapability_Rpc{
Rpc: &csipb.NodeServiceCapability_RPC{
Type: csipb.NodeServiceCapability_RPC_EXPAND_VOLUME,
},
},
})
} }
return nil, nil return resp, nil
} }
// NodeGetVolumeStats implements csi method // NodeGetVolumeStats implements csi method
......
...@@ -36,10 +36,13 @@ func (e *expanderDefaults) ExpandVolumeDevice(spec *volume.Spec, newSize resourc ...@@ -36,10 +36,13 @@ func (e *expanderDefaults) ExpandVolumeDevice(spec *volume.Spec, newSize resourc
return newSize, nil return newSize, nil
} }
// the defaults for ExpandFS return a generic resize indicator that will trigger the operation executor to go ahead with // the defaults for NodeExpand return a generic resize indicator that will trigger the operation executor to go ahead with
// generic filesystem resize // generic filesystem resize
func (e *expanderDefaults) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error { func (e *expanderDefaults) NodeExpand(rsOpt volume.NodeResizeOptions) (bool, error) {
klog.Warning(logPrefix(e.plugin), "using default filesystem resize for volume ", spec.Name(), ", at ", devicePath) klog.Warning(logPrefix(e.plugin), "using default filesystem resize for volume ", rsOpt.VolumeSpec.Name(), ", at ", rsOpt.DevicePath)
_, err := util.GenericResizeFS(e.plugin.host, e.plugin.GetPluginName(), devicePath, deviceMountPath) _, err := util.GenericResizeFS(e.plugin.host, e.plugin.GetPluginName(), rsOpt.DevicePath, rsOpt.DeviceMountPath)
return err if err != nil {
return false, err
}
return true, nil
} }
...@@ -18,9 +18,10 @@ package flexvolume ...@@ -18,9 +18,10 @@ package flexvolume
import ( import (
"fmt" "fmt"
"strconv"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"strconv"
) )
func (plugin *flexVolumePlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) { func (plugin *flexVolumePlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) {
...@@ -42,26 +43,29 @@ func (plugin *flexVolumePlugin) ExpandVolumeDevice(spec *volume.Spec, newSize re ...@@ -42,26 +43,29 @@ func (plugin *flexVolumePlugin) ExpandVolumeDevice(spec *volume.Spec, newSize re
return newSize, err return newSize, err
} }
func (plugin *flexVolumePlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, newSize, oldSize resource.Quantity) error { func (plugin *flexVolumePlugin) NodeExpand(rsOpt volume.NodeResizeOptions) (bool, error) {
// This method is called after we spec.PersistentVolume.Spec.Capacity // This method is called after we spec.PersistentVolume.Spec.Capacity
// has been updated to the new size. The underlying driver thus sees // has been updated to the new size. The underlying driver thus sees
// the _new_ (requested) size and can find out the _current_ size from // the _new_ (requested) size and can find out the _current_ size from
// its underlying storage implementation // its underlying storage implementation
if spec.PersistentVolume == nil { if rsOpt.VolumeSpec.PersistentVolume == nil {
return fmt.Errorf("PersistentVolume not found for spec: %s", spec.Name()) return false, fmt.Errorf("PersistentVolume not found for spec: %s", rsOpt.VolumeSpec.Name())
} }
call := plugin.NewDriverCall(expandFSCmd) call := plugin.NewDriverCall(expandFSCmd)
call.AppendSpec(spec, plugin.host, nil) call.AppendSpec(rsOpt.VolumeSpec, plugin.host, nil)
call.Append(devicePath) call.Append(rsOpt.DevicePath)
call.Append(deviceMountPath) call.Append(rsOpt.DeviceMountPath)
call.Append(strconv.FormatInt(newSize.Value(), 10)) call.Append(strconv.FormatInt(rsOpt.NewSize.Value(), 10))
call.Append(strconv.FormatInt(oldSize.Value(), 10)) call.Append(strconv.FormatInt(rsOpt.OldSize.Value(), 10))
_, err := call.Run() _, err := call.Run()
if isCmdNotSupportedErr(err) { if isCmdNotSupportedErr(err) {
return newExpanderDefaults(plugin).ExpandFS(spec, devicePath, deviceMountPath, newSize, oldSize) return newExpanderDefaults(plugin).NodeExpand(rsOpt)
}
if err != nil {
return false, err
} }
return err return true, nil
} }
...@@ -56,7 +56,7 @@ type flexVolumeAttachablePlugin struct { ...@@ -56,7 +56,7 @@ type flexVolumeAttachablePlugin struct {
var _ volume.AttachableVolumePlugin = &flexVolumeAttachablePlugin{} var _ volume.AttachableVolumePlugin = &flexVolumeAttachablePlugin{}
var _ volume.PersistentVolumePlugin = &flexVolumePlugin{} var _ volume.PersistentVolumePlugin = &flexVolumePlugin{}
var _ volume.FSResizableVolumePlugin = &flexVolumePlugin{} var _ volume.NodeExpandableVolumePlugin = &flexVolumePlugin{}
var _ volume.ExpandableVolumePlugin = &flexVolumePlugin{} var _ volume.ExpandableVolumePlugin = &flexVolumePlugin{}
var _ volume.DeviceMountableVolumePlugin = &flexVolumeAttachablePlugin{} var _ volume.DeviceMountableVolumePlugin = &flexVolumeAttachablePlugin{}
......
...@@ -290,12 +290,15 @@ func (plugin *gcePersistentDiskPlugin) ExpandVolumeDevice( ...@@ -290,12 +290,15 @@ func (plugin *gcePersistentDiskPlugin) ExpandVolumeDevice(
return updatedQuantity, nil return updatedQuantity, nil
} }
func (plugin *gcePersistentDiskPlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error { func (plugin *gcePersistentDiskPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
_, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), devicePath, deviceMountPath) _, err := util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
return err if err != nil {
return false, err
}
return true, nil
} }
var _ volume.FSResizableVolumePlugin = &gcePersistentDiskPlugin{} var _ volume.NodeExpandableVolumePlugin = &gcePersistentDiskPlugin{}
func (plugin *gcePersistentDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { func (plugin *gcePersistentDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
mounter := plugin.host.GetMounter(plugin.GetPluginName()) mounter := plugin.host.GetMounter(plugin.GetPluginName())
......
...@@ -46,6 +46,9 @@ type ProbeEvent struct { ...@@ -46,6 +46,9 @@ type ProbeEvent struct {
Op ProbeOperation // The operation to the plugin Op ProbeOperation // The operation to the plugin
} }
// CSIVolumePhaseType stores information about CSI volume path.
type CSIVolumePhaseType string
const ( const (
// Common parameter which can be specified in StorageClass to specify the desired FSType // Common parameter which can be specified in StorageClass to specify the desired FSType
// Provisioners SHOULD implement support for this if they are block device based // Provisioners SHOULD implement support for this if they are block device based
...@@ -55,6 +58,8 @@ const ( ...@@ -55,6 +58,8 @@ const (
ProbeAddOrUpdate ProbeOperation = 1 << iota ProbeAddOrUpdate ProbeOperation = 1 << iota
ProbeRemove ProbeRemove
CSIVolumeStaged CSIVolumePhaseType = "staged"
CSIVolumePublished CSIVolumePhaseType = "published"
) )
// VolumeOptions contains option information about a volume. // VolumeOptions contains option information about a volume.
...@@ -85,6 +90,26 @@ type VolumeOptions struct { ...@@ -85,6 +90,26 @@ type VolumeOptions struct {
Parameters map[string]string Parameters map[string]string
} }
// NodeResizeOptions contain options to be passed for node expansion.
type NodeResizeOptions struct {
VolumeSpec *Spec
// DevicePath - location of actual device on the node. In case of CSI
// this just could be volumeID
DevicePath string
// DeviceMountPath location where device is mounted on the node. If volume type
// is attachable - this would be global mount path otherwise
// it would be location where volume was mounted for the pod
DeviceMountPath string
NewSize resource.Quantity
OldSize resource.Quantity
// CSIVolumePhase contains volume phase on the node
CSIVolumePhase CSIVolumePhaseType
}
type DynamicPluginProber interface { type DynamicPluginProber interface {
Init() error Init() error
...@@ -219,18 +244,20 @@ type DeviceMountableVolumePlugin interface { ...@@ -219,18 +244,20 @@ type DeviceMountableVolumePlugin interface {
} }
// ExpandableVolumePlugin is an extended interface of VolumePlugin and is used for volumes that can be // ExpandableVolumePlugin is an extended interface of VolumePlugin and is used for volumes that can be
// expanded // expanded via control-plane ExpandVolumeDevice call.
type ExpandableVolumePlugin interface { type ExpandableVolumePlugin interface {
VolumePlugin VolumePlugin
ExpandVolumeDevice(spec *Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error) ExpandVolumeDevice(spec *Spec, newSize resource.Quantity, oldSize resource.Quantity) (resource.Quantity, error)
RequiresFSResize() bool RequiresFSResize() bool
} }
// FSResizableVolumePlugin is an extension of ExpandableVolumePlugin and is used for volumes (flex) // NodeExpandableVolumePlugin is an expanded interface of VolumePlugin and is used for volumes that
// that require extra steps on nodes for expansion to complete // require expansion on the node via NodeExpand call.
type FSResizableVolumePlugin interface { type NodeExpandableVolumePlugin interface {
ExpandableVolumePlugin VolumePlugin
ExpandFS(spec *Spec, devicePath, deviceMountPath string, newSize, oldSize resource.Quantity) error RequiresFSResize() bool
// NodeExpand expands volume on given deviceMountPath and returns true if resize is successful.
NodeExpand(resizeOptions NodeResizeOptions) (bool, error)
} }
// VolumePluginWithAttachLimits is an extended interface of VolumePlugin that restricts number of // VolumePluginWithAttachLimits is an extended interface of VolumePlugin that restricts number of
...@@ -951,26 +978,26 @@ func (pm *VolumePluginMgr) FindMapperPluginByName(name string) (BlockVolumePlugi ...@@ -951,26 +978,26 @@ func (pm *VolumePluginMgr) FindMapperPluginByName(name string) (BlockVolumePlugi
return nil, nil return nil, nil
} }
// FindFSResizablePluginBySpec fetches a persistent volume plugin by spec // FindNodeExpandablePluginBySpec fetches a persistent volume plugin by spec
func (pm *VolumePluginMgr) FindFSResizablePluginBySpec(spec *Spec) (FSResizableVolumePlugin, error) { func (pm *VolumePluginMgr) FindNodeExpandablePluginBySpec(spec *Spec) (NodeExpandableVolumePlugin, error) {
volumePlugin, err := pm.FindPluginBySpec(spec) volumePlugin, err := pm.FindPluginBySpec(spec)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if fsResizablePlugin, ok := volumePlugin.(FSResizableVolumePlugin); ok { if fsResizablePlugin, ok := volumePlugin.(NodeExpandableVolumePlugin); ok {
return fsResizablePlugin, nil return fsResizablePlugin, nil
} }
return nil, nil return nil, nil
} }
// FindFSResizablePluginByName fetches a persistent volume plugin by name // FindNodeExpandablePluginByName fetches a persistent volume plugin by name
func (pm *VolumePluginMgr) FindFSResizablePluginByName(name string) (FSResizableVolumePlugin, error) { func (pm *VolumePluginMgr) FindNodeExpandablePluginByName(name string) (NodeExpandableVolumePlugin, error) {
volumePlugin, err := pm.FindPluginByName(name) volumePlugin, err := pm.FindPluginByName(name)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if fsResizablePlugin, ok := volumePlugin.(FSResizableVolumePlugin); ok { if fsResizablePlugin, ok := volumePlugin.(NodeExpandableVolumePlugin); ok {
return fsResizablePlugin, nil return fsResizablePlugin, nil
} }
......
...@@ -201,12 +201,15 @@ func (plugin *rbdPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource. ...@@ -201,12 +201,15 @@ func (plugin *rbdPlugin) ExpandVolumeDevice(spec *volume.Spec, newSize resource.
} }
} }
func (plugin *rbdPlugin) ExpandFS(spec *volume.Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error { func (plugin *rbdPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) {
_, err := volutil.GenericResizeFS(plugin.host, plugin.GetPluginName(), devicePath, deviceMountPath) _, err := volutil.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath)
return err if err != nil {
return false, err
}
return true, nil
} }
var _ volume.FSResizableVolumePlugin = &rbdPlugin{} var _ volume.NodeExpandableVolumePlugin = &rbdPlugin{}
func (expander *rbdVolumeExpander) ResizeImage(oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) { func (expander *rbdVolumeExpander) ResizeImage(oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) {
return expander.manager.ExpandImage(expander, oldSize, newSize) return expander.manager.ExpandImage(expander, oldSize, newSize)
......
...@@ -280,7 +280,7 @@ var _ ProvisionableVolumePlugin = &FakeVolumePlugin{} ...@@ -280,7 +280,7 @@ var _ ProvisionableVolumePlugin = &FakeVolumePlugin{}
var _ AttachableVolumePlugin = &FakeVolumePlugin{} var _ AttachableVolumePlugin = &FakeVolumePlugin{}
var _ VolumePluginWithAttachLimits = &FakeVolumePlugin{} var _ VolumePluginWithAttachLimits = &FakeVolumePlugin{}
var _ DeviceMountableVolumePlugin = &FakeVolumePlugin{} var _ DeviceMountableVolumePlugin = &FakeVolumePlugin{}
var _ FSResizableVolumePlugin = &FakeVolumePlugin{} var _ NodeExpandableVolumePlugin = &FakeVolumePlugin{}
func (plugin *FakeVolumePlugin) getFakeVolume(list *[]*FakeVolume) *FakeVolume { func (plugin *FakeVolumePlugin) getFakeVolume(list *[]*FakeVolume) *FakeVolume {
volumeList := *list volumeList := *list
...@@ -537,8 +537,8 @@ func (plugin *FakeVolumePlugin) RequiresFSResize() bool { ...@@ -537,8 +537,8 @@ func (plugin *FakeVolumePlugin) RequiresFSResize() bool {
return true return true
} }
func (plugin *FakeVolumePlugin) ExpandFS(spec *Spec, devicePath, deviceMountPath string, _, _ resource.Quantity) error { func (plugin *FakeVolumePlugin) NodeExpand(resizeOptions NodeResizeOptions) (bool, error) {
return nil return true, nil
} }
func (plugin *FakeVolumePlugin) GetVolumeLimits() (map[string]int64, error) { func (plugin *FakeVolumePlugin) GetVolumeLimits() (map[string]int64, error) {
......
...@@ -169,36 +169,52 @@ func (h *hostpathCSIDriver) PrepareTest(f *framework.Framework) (*testsuites.Per ...@@ -169,36 +169,52 @@ func (h *hostpathCSIDriver) PrepareTest(f *framework.Framework) (*testsuites.Per
// mockCSI // mockCSI
type mockCSIDriver struct { type mockCSIDriver struct {
driverInfo testsuites.DriverInfo driverInfo testsuites.DriverInfo
manifests []string manifests []string
podInfo *bool podInfo *bool
attachable bool attachable bool
attachLimit int attachLimit int
enableNodeExpansion bool
}
// CSIMockDriverOpts defines options used for csi driver
type CSIMockDriverOpts struct {
RegisterDriver bool
DisableAttach bool
PodInfo *bool
AttachLimit int
EnableResizing bool
EnableNodeExpansion bool
} }
var _ testsuites.TestDriver = &mockCSIDriver{} var _ testsuites.TestDriver = &mockCSIDriver{}
var _ testsuites.DynamicPVTestDriver = &mockCSIDriver{} var _ testsuites.DynamicPVTestDriver = &mockCSIDriver{}
// InitMockCSIDriver returns a mockCSIDriver that implements TestDriver interface // InitMockCSIDriver returns a mockCSIDriver that implements TestDriver interface
func InitMockCSIDriver(registerDriver, driverAttachable bool, podInfo *bool, attachLimit int) testsuites.TestDriver { func InitMockCSIDriver(driverOpts CSIMockDriverOpts) testsuites.TestDriver {
driverManifests := []string{ driverManifests := []string{
"test/e2e/testing-manifests/storage-csi/cluster-driver-registrar/rbac.yaml", "test/e2e/testing-manifests/storage-csi/cluster-driver-registrar/rbac.yaml",
"test/e2e/testing-manifests/storage-csi/driver-registrar/rbac.yaml", "test/e2e/testing-manifests/storage-csi/driver-registrar/rbac.yaml",
"test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml", "test/e2e/testing-manifests/storage-csi/external-attacher/rbac.yaml",
"test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml", "test/e2e/testing-manifests/storage-csi/external-provisioner/rbac.yaml",
"test/e2e/testing-manifests/storage-csi/external-resizer/rbac.yaml",
"test/e2e/testing-manifests/storage-csi/mock/csi-mock-rbac.yaml", "test/e2e/testing-manifests/storage-csi/mock/csi-mock-rbac.yaml",
"test/e2e/testing-manifests/storage-csi/mock/csi-storageclass.yaml", "test/e2e/testing-manifests/storage-csi/mock/csi-storageclass.yaml",
"test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver.yaml", "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver.yaml",
} }
if registerDriver { if driverOpts.RegisterDriver {
driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-cluster-driver-registrar.yaml") driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-cluster-driver-registrar.yaml")
} }
if driverAttachable { if !driverOpts.DisableAttach {
driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-attacher.yaml") driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-attacher.yaml")
} }
if driverOpts.EnableResizing {
driverManifests = append(driverManifests, "test/e2e/testing-manifests/storage-csi/mock/csi-mock-driver-resizer.yaml")
}
return &mockCSIDriver{ return &mockCSIDriver{
driverInfo: testsuites.DriverInfo{ driverInfo: testsuites.DriverInfo{
Name: "csi-mock", Name: "csi-mock",
...@@ -213,10 +229,11 @@ func InitMockCSIDriver(registerDriver, driverAttachable bool, podInfo *bool, att ...@@ -213,10 +229,11 @@ func InitMockCSIDriver(registerDriver, driverAttachable bool, podInfo *bool, att
testsuites.CapExec: false, testsuites.CapExec: false,
}, },
}, },
manifests: driverManifests, manifests: driverManifests,
podInfo: podInfo, podInfo: driverOpts.PodInfo,
attachable: driverAttachable, attachable: !driverOpts.DisableAttach,
attachLimit: attachLimit, attachLimit: driverOpts.AttachLimit,
enableNodeExpansion: driverOpts.EnableNodeExpansion,
} }
} }
...@@ -264,6 +281,10 @@ func (m *mockCSIDriver) PrepareTest(f *framework.Framework) (*testsuites.PerTest ...@@ -264,6 +281,10 @@ func (m *mockCSIDriver) PrepareTest(f *framework.Framework) (*testsuites.PerTest
containerArgs = append(containerArgs, "--attach-limit", strconv.Itoa(m.attachLimit)) containerArgs = append(containerArgs, "--attach-limit", strconv.Itoa(m.attachLimit))
} }
if m.enableNodeExpansion {
containerArgs = append(containerArgs, "--node-expand-required=true")
}
// TODO (?): the storage.csi.image.version and storage.csi.image.registry // TODO (?): the storage.csi.image.version and storage.csi.image.registry
// settings are ignored for this test. We could patch the image definitions. // settings are ignored for this test. We could patch the image definitions.
o := utils.PatchCSIOptions{ o := utils.PatchCSIOptions{
......
...@@ -159,7 +159,7 @@ var _ = utils.SIGDescribe("Mounted flexvolume expand[Slow]", func() { ...@@ -159,7 +159,7 @@ var _ = utils.SIGDescribe("Mounted flexvolume expand[Slow]", func() {
} }
By("Waiting for cloudprovider resize to finish") By("Waiting for cloudprovider resize to finish")
err = waitForControllerVolumeResize(pvc, c) err = waitForControllerVolumeResize(pvc, c, totalResizeWaitPeriod)
Expect(err).NotTo(HaveOccurred(), "While waiting for pvc resize to finish") Expect(err).NotTo(HaveOccurred(), "While waiting for pvc resize to finish")
By("Getting a pod from deployment") By("Getting a pod from deployment")
......
...@@ -164,7 +164,7 @@ var _ = utils.SIGDescribe("Mounted flexvolume volume expand [Slow] [Feature:Expa ...@@ -164,7 +164,7 @@ var _ = utils.SIGDescribe("Mounted flexvolume volume expand [Slow] [Feature:Expa
} }
By("Waiting for cloudprovider resize to finish") By("Waiting for cloudprovider resize to finish")
err = waitForControllerVolumeResize(pvc, c) err = waitForControllerVolumeResize(pvc, c, totalResizeWaitPeriod)
Expect(err).NotTo(HaveOccurred(), "While waiting for pvc resize to finish") Expect(err).NotTo(HaveOccurred(), "While waiting for pvc resize to finish")
By("Waiting for file system resize to finish") By("Waiting for file system resize to finish")
......
...@@ -136,7 +136,7 @@ var _ = utils.SIGDescribe("Mounted volume expand", func() { ...@@ -136,7 +136,7 @@ var _ = utils.SIGDescribe("Mounted volume expand", func() {
} }
By("Waiting for cloudprovider resize to finish") By("Waiting for cloudprovider resize to finish")
err = waitForControllerVolumeResize(pvc, c) err = waitForControllerVolumeResize(pvc, c, totalResizeWaitPeriod)
Expect(err).NotTo(HaveOccurred(), "While waiting for pvc resize to finish") Expect(err).NotTo(HaveOccurred(), "While waiting for pvc resize to finish")
By("Getting a pod from deployment") By("Getting a pod from deployment")
......
...@@ -136,7 +136,7 @@ var _ = utils.SIGDescribe("Volume expand", func() { ...@@ -136,7 +136,7 @@ var _ = utils.SIGDescribe("Volume expand", func() {
} }
By("Waiting for cloudprovider resize to finish") By("Waiting for cloudprovider resize to finish")
err = waitForControllerVolumeResize(pvc, c) err = waitForControllerVolumeResize(pvc, c, totalResizeWaitPeriod)
Expect(err).NotTo(HaveOccurred(), "While waiting for pvc resize to finish") Expect(err).NotTo(HaveOccurred(), "While waiting for pvc resize to finish")
By("Checking for conditions on pvc") By("Checking for conditions on pvc")
...@@ -198,9 +198,29 @@ func expandPVCSize(origPVC *v1.PersistentVolumeClaim, size resource.Quantity, c ...@@ -198,9 +198,29 @@ func expandPVCSize(origPVC *v1.PersistentVolumeClaim, size resource.Quantity, c
return updatedPVC, waitErr return updatedPVC, waitErr
} }
func waitForControllerVolumeResize(pvc *v1.PersistentVolumeClaim, c clientset.Interface) error { func waitForResizingCondition(pvc *v1.PersistentVolumeClaim, c clientset.Interface, duration time.Duration) error {
waitErr := wait.PollImmediate(resizePollInterval, duration, func() (bool, error) {
var err error
updatedPVC, err := c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(pvc.Name, metav1.GetOptions{})
if err != nil {
return false, fmt.Errorf("error fetching pvc %q for checking for resize status : %v", pvc.Name, err)
}
pvcConditions := updatedPVC.Status.Conditions
if len(pvcConditions) > 0 {
if pvcConditions[0].Type == v1.PersistentVolumeClaimResizing {
return true, nil
}
}
return false, nil
})
return waitErr
}
func waitForControllerVolumeResize(pvc *v1.PersistentVolumeClaim, c clientset.Interface, duration time.Duration) error {
pvName := pvc.Spec.VolumeName pvName := pvc.Spec.VolumeName
return wait.PollImmediate(resizePollInterval, totalResizeWaitPeriod, func() (bool, error) { return wait.PollImmediate(resizePollInterval, duration, func() (bool, error) {
pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage] pvcSize := pvc.Spec.Resources.Requests[v1.ResourceStorage]
pv, err := c.CoreV1().PersistentVolumes().Get(pvName, metav1.GetOptions{}) pv, err := c.CoreV1().PersistentVolumes().Get(pvName, metav1.GetOptions{})
......
The original file is (or will be) https://github.com/kubernetes-csi/external-resizer/blob/master/deploy/kubernetes/rbac.yaml
# This YAML file contains all RBAC objects that are necessary to run external
# CSI resizer.
#
# In production, each CSI driver deployment has to be customized:
# - to avoid conflicts, use non-default namespace and different names
# for non-namespaced entities like the ClusterRole
# - decide whether the deployment replicates the external CSI
# resizer, in which case leadership election must be enabled;
# this influences the RBAC setup, see below
apiVersion: v1
kind: ServiceAccount
metadata:
name: csi-resizer
# replace with non-default namespace name
namespace: default
---
# Resizer must be able to work with PVCs, PVs, SCs.
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: external-resizer-runner
rules:
# The following rule should be uncommented for plugins that require secrets
# for provisioning.
# - apiGroups: [""]
# resources: ["secrets"]
# verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumeclaims/status"]
verbs: ["update", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["list", "watch", "create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-resizer-role
subjects:
- kind: ServiceAccount
name: csi-resizer
# replace with non-default namespace name
namespace: default
roleRef:
kind: ClusterRole
name: external-resizer-runner
apiGroup: rbac.authorization.k8s.io
---
# Resizer must be able to work with end point in current namespace
# if (and only if) leadership election is enabled
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
# replace with non-default namespace name
namespace: default
name: external-resizer-cfg
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "watch", "list", "delete", "update", "create"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-resizer-role-cfg
# replace with non-default namespace name
namespace: default
subjects:
- kind: ServiceAccount
name: csi-resizer
# replace with non-default namespace name
namespace: default
roleRef:
kind: Role
name: external-resizer-cfg
apiGroup: rbac.authorization.k8s.io
kind: StatefulSet
apiVersion: apps/v1
metadata:
name: csi-mockplugin-resizer
spec:
selector:
matchLabels:
app: csi-mockplugin-resizer
replicas: 1
template:
metadata:
labels:
app: csi-mockplugin-resizer
spec:
serviceAccountName: csi-mock
containers:
- name: csi-resizer
image: quay.io/k8scsi/csi-resizer:canary
args:
- "--v=5"
- "--csi-address=$(ADDRESS)"
env:
- name: ADDRESS
value: /csi/csi.sock
imagePullPolicy: Always
volumeMounts:
- mountPath: /csi
name: socket-dir
volumes:
- hostPath:
path: /var/lib/kubelet/plugins/csi-mock
type: DirectoryOrCreate
name: socket-dir
...@@ -54,7 +54,7 @@ spec: ...@@ -54,7 +54,7 @@ spec:
- name: mock - name: mock
image: quay.io/k8scsi/mock-driver:v1.0.0-1 image: quay.io/k8scsi/mock-driver:v1.1.1
env: env:
- name: CSI_ENDPOINT - name: CSI_ENDPOINT
value: /csi/csi.sock value: /csi/csi.sock
......
...@@ -59,3 +59,16 @@ roleRef: ...@@ -59,3 +59,16 @@ roleRef:
kind: ClusterRole kind: ClusterRole
name: e2e-test-privileged-psp name: e2e-test-privileged-psp
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: csi-controller-resizer-role
subjects:
- kind: ServiceAccount
name: csi-mock
namespace: default
roleRef:
kind: ClusterRole
name: external-resizer-runner
apiGroup: rbac.authorization.k8s.io
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment