CSIStorageCapacity: CSIDriver.Spec.StorageCapacity field

This is needed to inform the Kubernetes pod scheduler whether it has
to check CSIStorageCapacity objects for available capacity.
This commit is contained in:
Patrick Ohly 2020-01-21 11:57:55 +01:00
parent 22aeb81e84
commit 1089954fa6
12 changed files with 311 additions and 51 deletions

View File

@ -78,6 +78,10 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
obj.Spec.PodInfoOnMount = new(bool) obj.Spec.PodInfoOnMount = new(bool)
*(obj.Spec.PodInfoOnMount) = false *(obj.Spec.PodInfoOnMount) = false
} }
if obj.Spec.StorageCapacity == nil {
obj.Spec.StorageCapacity = new(bool)
*(obj.Spec.StorageCapacity) = false
}
if len(obj.Spec.VolumeLifecycleModes) == 0 { if len(obj.Spec.VolumeLifecycleModes) == 0 {
obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{ obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
storage.VolumeLifecyclePersistent, storage.VolumeLifecyclePersistent,

View File

@ -309,6 +309,26 @@ type CSIDriverSpec struct {
// more modes may be added in the future. // more modes may be added in the future.
// +optional // +optional
VolumeLifecycleModes []VolumeLifecycleMode VolumeLifecycleModes []VolumeLifecycleMode
// If set to true, storageCapacity indicates that the CSI
// volume driver wants pod scheduling to consider the storage
// capacity that the driver deployment will report by creating
// CSIStorageCapacity objects with capacity information.
//
// The check can be enabled immediately when deploying a driver.
// In that case, provisioning new volumes with late binding
// will pause until the driver deployment has published
// some suitable CSIStorageCapacity object.
//
// Alternatively, the driver can be deployed with the field
// unset or false and it can be flipped later when storage
// capacity information has been published.
//
// This is an alpha field and only available when the CSIStorageCapacity
// feature is enabled. The default is false.
//
// +optional
StorageCapacity *bool
} }
// VolumeLifecycleMode specifies how a CSI volume is used in Kubernetes. // VolumeLifecycleMode specifies how a CSI volume is used in Kubernetes.

View File

@ -49,6 +49,10 @@ func SetDefaults_CSIDriver(obj *storagev1.CSIDriver) {
obj.Spec.PodInfoOnMount = new(bool) obj.Spec.PodInfoOnMount = new(bool)
*(obj.Spec.PodInfoOnMount) = false *(obj.Spec.PodInfoOnMount) = false
} }
if obj.Spec.StorageCapacity == nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity) {
obj.Spec.StorageCapacity = new(bool)
*(obj.Spec.StorageCapacity) = false
}
if len(obj.Spec.VolumeLifecycleModes) == 0 && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { if len(obj.Spec.VolumeLifecycleModes) == 0 && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
obj.Spec.VolumeLifecycleModes = append(obj.Spec.VolumeLifecycleModes, storagev1.VolumeLifecyclePersistent) obj.Spec.VolumeLifecycleModes = append(obj.Spec.VolumeLifecycleModes, storagev1.VolumeLifecyclePersistent)
} }

View File

@ -22,8 +22,11 @@ import (
storagev1 "k8s.io/api/storage/v1" storagev1 "k8s.io/api/storage/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
_ "k8s.io/kubernetes/pkg/apis/storage/install" _ "k8s.io/kubernetes/pkg/apis/storage/install"
"k8s.io/kubernetes/pkg/features"
) )
func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
@ -47,6 +50,33 @@ func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
return obj3 return obj3
} }
func TestSetDefaultStorageCapacityEnabled(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIStorageCapacity, true)()
driver := &storagev1.CSIDriver{}
// field should be defaulted
defaultStorageCapacity := false
output := roundTrip(t, runtime.Object(driver)).(*storagev1.CSIDriver)
outStorageCapacity := output.Spec.StorageCapacity
if outStorageCapacity == nil {
t.Errorf("Expected StorageCapacity to be defaulted to: %+v, got: nil", defaultStorageCapacity)
} else if *outStorageCapacity != defaultStorageCapacity {
t.Errorf("Expected StorageCapacity to be defaulted to: %+v, got: %+v", defaultStorageCapacity, outStorageCapacity)
}
}
func TestSetDefaultStorageCapacityDisabled(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIStorageCapacity, false)()
driver := &storagev1.CSIDriver{}
// field should not be defaulted
output := roundTrip(t, runtime.Object(driver)).(*storagev1.CSIDriver)
outStorageCapacity := output.Spec.StorageCapacity
if outStorageCapacity != nil {
t.Errorf("Expected StorageCapacity to remain nil, got: %+v", outStorageCapacity)
}
}
func TestSetDefaultVolumeBindingMode(t *testing.T) { func TestSetDefaultVolumeBindingMode(t *testing.T) {
class := &storagev1.StorageClass{} class := &storagev1.StorageClass{}

View File

@ -49,6 +49,10 @@ func SetDefaults_CSIDriver(obj *storagev1beta1.CSIDriver) {
obj.Spec.PodInfoOnMount = new(bool) obj.Spec.PodInfoOnMount = new(bool)
*(obj.Spec.PodInfoOnMount) = false *(obj.Spec.PodInfoOnMount) = false
} }
if obj.Spec.StorageCapacity == nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity) {
obj.Spec.StorageCapacity = new(bool)
*(obj.Spec.StorageCapacity) = false
}
if len(obj.Spec.VolumeLifecycleModes) == 0 && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { if len(obj.Spec.VolumeLifecycleModes) == 0 && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
obj.Spec.VolumeLifecycleModes = append(obj.Spec.VolumeLifecycleModes, storagev1beta1.VolumeLifecyclePersistent) obj.Spec.VolumeLifecycleModes = append(obj.Spec.VolumeLifecycleModes, storagev1beta1.VolumeLifecyclePersistent)
} }

View File

@ -85,6 +85,33 @@ func TestSetDefaultAttachRequired(t *testing.T) {
} }
} }
func TestSetDefaultStorageCapacityEnabled(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIStorageCapacity, true)()
driver := &storagev1beta1.CSIDriver{}
// field should be defaulted
defaultStorageCapacity := false
output := roundTrip(t, runtime.Object(driver)).(*storagev1beta1.CSIDriver)
outStorageCapacity := output.Spec.StorageCapacity
if outStorageCapacity == nil {
t.Errorf("Expected StorageCapacity to be defaulted to: %+v, got: nil", defaultStorageCapacity)
} else if *outStorageCapacity != defaultStorageCapacity {
t.Errorf("Expected StorageCapacity to be defaulted to: %+v, got: %+v", defaultStorageCapacity, outStorageCapacity)
}
}
func TestSetDefaultStorageCapacityDisabled(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIStorageCapacity, false)()
driver := &storagev1beta1.CSIDriver{}
// field should not be defaulted
output := roundTrip(t, runtime.Object(driver)).(*storagev1beta1.CSIDriver)
outStorageCapacity := output.Spec.StorageCapacity
if outStorageCapacity != nil {
t.Errorf("Expected StorageCapacity to remain nil, got: %+v", outStorageCapacity)
}
}
func TestSetDefaultVolumeLifecycleModesEnabled(t *testing.T) { func TestSetDefaultVolumeLifecycleModesEnabled(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
driver := &storagev1beta1.CSIDriver{} driver := &storagev1beta1.CSIDriver{}

View File

@ -419,6 +419,7 @@ func validateCSIDriverSpec(
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, validateAttachRequired(spec.AttachRequired, fldPath.Child("attachedRequired"))...) allErrs = append(allErrs, validateAttachRequired(spec.AttachRequired, fldPath.Child("attachedRequired"))...)
allErrs = append(allErrs, validatePodInfoOnMount(spec.PodInfoOnMount, fldPath.Child("podInfoOnMount"))...) allErrs = append(allErrs, validatePodInfoOnMount(spec.PodInfoOnMount, fldPath.Child("podInfoOnMount"))...)
allErrs = append(allErrs, validateStorageCapacity(spec.StorageCapacity, fldPath.Child("storageCapacity"))...)
allErrs = append(allErrs, validateVolumeLifecycleModes(spec.VolumeLifecycleModes, fldPath.Child("volumeLifecycleModes"))...) allErrs = append(allErrs, validateVolumeLifecycleModes(spec.VolumeLifecycleModes, fldPath.Child("volumeLifecycleModes"))...)
return allErrs return allErrs
} }
@ -443,6 +444,16 @@ func validatePodInfoOnMount(podInfoOnMount *bool, fldPath *field.Path) field.Err
return allErrs return allErrs
} }
// validateStorageCapacity tests if storageCapacity is set for CSIDriver.
func validateStorageCapacity(storageCapacity *bool, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if storageCapacity == nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity) {
allErrs = append(allErrs, field.Required(fldPath, ""))
}
return allErrs
}
// validateVolumeLifecycleModes tests if mode has one of the allowed values. // validateVolumeLifecycleModes tests if mode has one of the allowed values.
func validateVolumeLifecycleModes(modes []storage.VolumeLifecycleMode, fldPath *field.Path) field.ErrorList { func validateVolumeLifecycleModes(modes []storage.VolumeLifecycleMode, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}

View File

@ -46,6 +46,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/duration" "k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/admissionregistration"
"k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/autoscaling"
@ -64,6 +65,7 @@ import (
"k8s.io/kubernetes/pkg/apis/scheduling" "k8s.io/kubernetes/pkg/apis/scheduling"
"k8s.io/kubernetes/pkg/apis/storage" "k8s.io/kubernetes/pkg/apis/storage"
storageutil "k8s.io/kubernetes/pkg/apis/storage/util" storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/printers" "k8s.io/kubernetes/pkg/printers"
"k8s.io/kubernetes/pkg/util/node" "k8s.io/kubernetes/pkg/util/node"
) )
@ -510,9 +512,16 @@ func AddHandlers(h printers.PrintHandler) {
{Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]}, {Name: "Name", Type: "string", Format: "name", Description: metav1.ObjectMeta{}.SwaggerDoc()["name"]},
{Name: "AttachRequired", Type: "boolean", Description: storagev1.CSIDriverSpec{}.SwaggerDoc()["attachRequired"]}, {Name: "AttachRequired", Type: "boolean", Description: storagev1.CSIDriverSpec{}.SwaggerDoc()["attachRequired"]},
{Name: "PodInfoOnMount", Type: "boolean", Description: storagev1.CSIDriverSpec{}.SwaggerDoc()["podInfoOnMount"]}, {Name: "PodInfoOnMount", Type: "boolean", Description: storagev1.CSIDriverSpec{}.SwaggerDoc()["podInfoOnMount"]},
}
if utilfeature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity) {
csiDriverColumnDefinitions = append(csiDriverColumnDefinitions, metav1.TableColumnDefinition{
Name: "StorageCapacity", Type: "boolean", Description: storagev1.CSIDriverSpec{}.SwaggerDoc()["storageCapacity"],
})
}
csiDriverColumnDefinitions = append(csiDriverColumnDefinitions, []metav1.TableColumnDefinition{
{Name: "Modes", Type: "string", Description: storagev1.CSIDriverSpec{}.SwaggerDoc()["volumeLifecycleModes"]}, {Name: "Modes", Type: "string", Description: storagev1.CSIDriverSpec{}.SwaggerDoc()["volumeLifecycleModes"]},
{Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]}, {Name: "Age", Type: "string", Description: metav1.ObjectMeta{}.SwaggerDoc()["creationTimestamp"]},
} }...)
h.TableHandler(csiDriverColumnDefinitions, printCSIDriver) h.TableHandler(csiDriverColumnDefinitions, printCSIDriver)
h.TableHandler(csiDriverColumnDefinitions, printCSIDriverList) h.TableHandler(csiDriverColumnDefinitions, printCSIDriverList)
@ -1366,7 +1375,15 @@ func printCSIDriver(obj *storage.CSIDriver, options printers.GenerateOptions) ([
modes = "<none>" modes = "<none>"
} }
row.Cells = append(row.Cells, obj.Name, attachRequired, podInfoOnMount, modes, translateTimestampSince(obj.CreationTimestamp)) row.Cells = append(row.Cells, obj.Name, attachRequired, podInfoOnMount)
if utilfeature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity) {
storageCapacity := false
if obj.Spec.StorageCapacity != nil {
storageCapacity = *obj.Spec.StorageCapacity
}
row.Cells = append(row.Cells, storageCapacity)
}
row.Cells = append(row.Cells, modes, translateTimestampSince(obj.CreationTimestamp))
return []metav1.TableRow{row}, nil return []metav1.TableRow{row}, nil
} }

View File

@ -43,8 +43,12 @@ func (csiDriverStrategy) NamespaceScoped() bool {
return false return false
} }
// PrepareForCreate clears the VolumeLifecycleModes field if the corresponding feature is disabled. // PrepareForCreate clears the fields for which the corresponding feature is disabled.
func (csiDriverStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { func (csiDriverStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
if !utilfeature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity) {
csiDriver := obj.(*storage.CSIDriver)
csiDriver.Spec.StorageCapacity = nil
}
if !utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { if !utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
csiDriver := obj.(*storage.CSIDriver) csiDriver := obj.(*storage.CSIDriver)
csiDriver.Spec.VolumeLifecycleModes = nil csiDriver.Spec.VolumeLifecycleModes = nil
@ -68,10 +72,15 @@ func (csiDriverStrategy) AllowCreateOnUpdate() bool {
return false return false
} }
// PrepareForUpdate clears the VolumeLifecycleModes field if the corresponding feature is disabled and // PrepareForUpdate clears the fields for which the corresponding feature is disabled and
// existing object does not already have that field set. This allows the field to remain when // existing object does not already have that field set. This allows the field to remain when
// downgrading to a version that has the feature disabled. // downgrading to a version that has the feature disabled.
func (csiDriverStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { func (csiDriverStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
if old.(*storage.CSIDriver).Spec.StorageCapacity == nil &&
!utilfeature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity) {
newCSIDriver := obj.(*storage.CSIDriver)
newCSIDriver.Spec.StorageCapacity = nil
}
if old.(*storage.CSIDriver).Spec.VolumeLifecycleModes == nil && if old.(*storage.CSIDriver).Spec.VolumeLifecycleModes == nil &&
!utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { !utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
newCSIDriver := obj.(*storage.CSIDriver) newCSIDriver := obj.(*storage.CSIDriver)

View File

@ -30,15 +30,15 @@ import (
) )
func getValidCSIDriver(name string) *storage.CSIDriver { func getValidCSIDriver(name string) *storage.CSIDriver {
attachRequired := true enabled := true
podInfoOnMount := true
return &storage.CSIDriver{ return &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: name, Name: name,
}, },
Spec: storage.CSIDriverSpec{ Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired, AttachRequired: &enabled,
PodInfoOnMount: &podInfoOnMount, PodInfoOnMount: &enabled,
StorageCapacity: &enabled,
}, },
} }
} }
@ -87,22 +87,12 @@ func TestCSIDriverPrepareForCreate(t *testing.T) {
attachRequired := true attachRequired := true
podInfoOnMount := true podInfoOnMount := true
csiDriver := &storage.CSIDriver{ storageCapacity := true
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecyclePersistent,
},
},
}
tests := []struct { tests := []struct {
name string name string
withInline bool withCapacity bool
withInline bool
}{ }{
{ {
name: "inline enabled", name: "inline enabled",
@ -112,17 +102,48 @@ func TestCSIDriverPrepareForCreate(t *testing.T) {
name: "inline disabled", name: "inline disabled",
withInline: false, withInline: false,
}, },
{
name: "capacity enabled",
withCapacity: true,
},
{
name: "capacity disabled",
withCapacity: false,
},
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIStorageCapacity, test.withCapacity)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, test.withInline)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, test.withInline)()
csiDriver := &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired,
PodInfoOnMount: &podInfoOnMount,
StorageCapacity: &storageCapacity,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecyclePersistent,
},
},
}
Strategy.PrepareForCreate(ctx, csiDriver) Strategy.PrepareForCreate(ctx, csiDriver)
errs := Strategy.Validate(ctx, csiDriver) errs := Strategy.Validate(ctx, csiDriver)
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("unexpected validating errors: %v", errs) t.Errorf("unexpected validating errors: %v", errs)
} }
if test.withCapacity {
if csiDriver.Spec.StorageCapacity == nil || *csiDriver.Spec.StorageCapacity != storageCapacity {
t.Errorf("StorageCapacity modified: %v", csiDriver.Spec.StorageCapacity)
}
} else {
if csiDriver.Spec.StorageCapacity != nil {
t.Errorf("StorageCapacity not stripped: %v", csiDriver.Spec.StorageCapacity)
}
}
if test.withInline { if test.withInline {
if len(csiDriver.Spec.VolumeLifecycleModes) != 1 { if len(csiDriver.Spec.VolumeLifecycleModes) != 1 {
t.Errorf("VolumeLifecycleModes modified: %v", csiDriver.Spec) t.Errorf("VolumeLifecycleModes modified: %v", csiDriver.Spec)
@ -178,15 +199,69 @@ func TestCSIDriverPrepareForUpdate(t *testing.T) {
}, },
}, },
} }
enabled := true
disabled := false
driverWithoutCapacity := &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
}
driverWithCapacityEnabled := &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: storage.CSIDriverSpec{
StorageCapacity: &enabled,
},
}
driverWithCapacityDisabled := &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
},
Spec: storage.CSIDriverSpec{
StorageCapacity: &disabled,
},
}
var resultEmpty []storage.VolumeLifecycleMode var resultEmpty []storage.VolumeLifecycleMode
resultPersistent := []storage.VolumeLifecycleMode{storage.VolumeLifecyclePersistent} resultPersistent := []storage.VolumeLifecycleMode{storage.VolumeLifecyclePersistent}
resultEphemeral := []storage.VolumeLifecycleMode{storage.VolumeLifecycleEphemeral} resultEphemeral := []storage.VolumeLifecycleMode{storage.VolumeLifecycleEphemeral}
tests := []struct { tests := []struct {
name string name string
old, update *storage.CSIDriver old, update *storage.CSIDriver
withInline, withoutInline []storage.VolumeLifecycleMode withCapacity, withoutCapacity *bool
withInline, withoutInline []storage.VolumeLifecycleMode
}{ }{
{
name: "before: no capacity, update: no capacity",
old: driverWithoutCapacity,
update: driverWithoutCapacity,
withCapacity: nil,
withoutCapacity: nil,
},
{
name: "before: no capacity, update: enabled",
old: driverWithoutCapacity,
update: driverWithCapacityEnabled,
withCapacity: &enabled,
withoutCapacity: nil,
},
{
name: "before: capacity enabled, update: disabled",
old: driverWithCapacityEnabled,
update: driverWithCapacityDisabled,
withCapacity: &disabled,
withoutCapacity: &disabled,
},
{
name: "before: capacity enabled, update: no capacity",
old: driverWithCapacityEnabled,
update: driverWithoutCapacity,
withCapacity: nil,
withoutCapacity: nil,
},
{ {
name: "before: no mode, update: no mode", name: "before: no mode, update: no mode",
old: driverWithoutModes, old: driverWithoutModes,
@ -217,35 +292,46 @@ func TestCSIDriverPrepareForUpdate(t *testing.T) {
}, },
} }
runAll := func(t *testing.T, withInline bool) { runAll := func(t *testing.T, withCapacity, withInline bool) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIStorageCapacity, withCapacity)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, withInline)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, withInline)()
csiDriver := test.update.DeepCopy() csiDriver := test.update.DeepCopy()
Strategy.PrepareForUpdate(ctx, csiDriver, test.old) Strategy.PrepareForUpdate(ctx, csiDriver, test.old)
if withInline { if withCapacity {
require.Equal(t, csiDriver.Spec.VolumeLifecycleModes, test.withInline) require.Equal(t, test.withCapacity, csiDriver.Spec.StorageCapacity)
} else { } else {
require.Equal(t, csiDriver.Spec.VolumeLifecycleModes, test.withoutInline) require.Equal(t, test.withoutCapacity, csiDriver.Spec.StorageCapacity)
}
if withInline {
require.Equal(t, test.withInline, csiDriver.Spec.VolumeLifecycleModes)
} else {
require.Equal(t, test.withoutInline, csiDriver.Spec.VolumeLifecycleModes)
} }
}) })
} }
} }
t.Run("with capacity", func(t *testing.T) {
runAll(t, true, false)
})
t.Run("without capacity", func(t *testing.T) {
runAll(t, false, false)
})
t.Run("with inline volumes", func(t *testing.T) { t.Run("with inline volumes", func(t *testing.T) {
runAll(t, true) runAll(t, false, true)
}) })
t.Run("without inline volumes", func(t *testing.T) { t.Run("without inline volumes", func(t *testing.T) {
runAll(t, false) runAll(t, false, false)
}) })
} }
func TestCSIDriverValidation(t *testing.T) { func TestCSIDriverValidation(t *testing.T) {
attachRequired := true enabled := true
notAttachRequired := false disabled := true
podInfoOnMount := true
notPodInfoOnMount := false
tests := []struct { tests := []struct {
name string name string
@ -258,27 +344,29 @@ func TestCSIDriverValidation(t *testing.T) {
false, false,
}, },
{ {
"true PodInfoOnMount and AttachRequired", "true for all flags",
&storage.CSIDriver{ &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "foo", Name: "foo",
}, },
Spec: storage.CSIDriverSpec{ Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired, AttachRequired: &enabled,
PodInfoOnMount: &podInfoOnMount, PodInfoOnMount: &enabled,
StorageCapacity: &enabled,
}, },
}, },
false, false,
}, },
{ {
"false PodInfoOnMount and AttachRequired", "false for all flags",
&storage.CSIDriver{ &storage.CSIDriver{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "foo", Name: "foo",
}, },
Spec: storage.CSIDriverSpec{ Spec: storage.CSIDriverSpec{
AttachRequired: &notAttachRequired, AttachRequired: &disabled,
PodInfoOnMount: &notPodInfoOnMount, PodInfoOnMount: &disabled,
StorageCapacity: &disabled,
}, },
}, },
false, false,
@ -290,8 +378,9 @@ func TestCSIDriverValidation(t *testing.T) {
Name: "*foo#", Name: "*foo#",
}, },
Spec: storage.CSIDriverSpec{ Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired, AttachRequired: &enabled,
PodInfoOnMount: &podInfoOnMount, PodInfoOnMount: &enabled,
StorageCapacity: &enabled,
}, },
}, },
true, true,
@ -303,8 +392,9 @@ func TestCSIDriverValidation(t *testing.T) {
Name: "foo", Name: "foo",
}, },
Spec: storage.CSIDriverSpec{ Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired, AttachRequired: &enabled,
PodInfoOnMount: &podInfoOnMount, PodInfoOnMount: &enabled,
StorageCapacity: &enabled,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{ VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecycleMode("no-such-mode"), storage.VolumeLifecycleMode("no-such-mode"),
}, },
@ -319,8 +409,9 @@ func TestCSIDriverValidation(t *testing.T) {
Name: "foo", Name: "foo",
}, },
Spec: storage.CSIDriverSpec{ Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired, AttachRequired: &enabled,
PodInfoOnMount: &podInfoOnMount, PodInfoOnMount: &enabled,
StorageCapacity: &enabled,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{ VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecyclePersistent, storage.VolumeLifecyclePersistent,
}, },
@ -335,8 +426,9 @@ func TestCSIDriverValidation(t *testing.T) {
Name: "foo", Name: "foo",
}, },
Spec: storage.CSIDriverSpec{ Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired, AttachRequired: &enabled,
PodInfoOnMount: &podInfoOnMount, PodInfoOnMount: &enabled,
StorageCapacity: &enabled,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{ VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecycleEphemeral, storage.VolumeLifecycleEphemeral,
}, },
@ -351,8 +443,9 @@ func TestCSIDriverValidation(t *testing.T) {
Name: "foo", Name: "foo",
}, },
Spec: storage.CSIDriverSpec{ Spec: storage.CSIDriverSpec{
AttachRequired: &attachRequired, AttachRequired: &enabled,
PodInfoOnMount: &podInfoOnMount, PodInfoOnMount: &enabled,
StorageCapacity: &enabled,
VolumeLifecycleModes: []storage.VolumeLifecycleMode{ VolumeLifecycleModes: []storage.VolumeLifecycleMode{
storage.VolumeLifecyclePersistent, storage.VolumeLifecyclePersistent,
storage.VolumeLifecycleEphemeral, storage.VolumeLifecycleEphemeral,

View File

@ -316,6 +316,26 @@ type CSIDriverSpec struct {
// +optional // +optional
// +listType=set // +listType=set
VolumeLifecycleModes []VolumeLifecycleMode `json:"volumeLifecycleModes,omitempty" protobuf:"bytes,3,opt,name=volumeLifecycleModes"` VolumeLifecycleModes []VolumeLifecycleMode `json:"volumeLifecycleModes,omitempty" protobuf:"bytes,3,opt,name=volumeLifecycleModes"`
// If set to true, storageCapacity indicates that the CSI
// volume driver wants pod scheduling to consider the storage
// capacity that the driver deployment will report by creating
// CSIStorageCapacity objects with capacity information.
//
// The check can be enabled immediately when deploying a driver.
// In that case, provisioning new volumes with late binding
// will pause until the driver deployment has published
// some suitable CSIStorageCapacity object.
//
// Alternatively, the driver can be deployed with the field
// unset or false and it can be flipped later when storage
// capacity information has been published.
//
// This is an alpha field and only available when the CSIStorageCapacity
// feature is enabled. The default is false.
//
// +optional
StorageCapacity *bool `json:"storageCapacity,omitempty" protobuf:"bytes,4,opt,name=storageCapacity"`
} }
// VolumeLifecycleMode is an enumeration of possible usage modes for a volume // VolumeLifecycleMode is an enumeration of possible usage modes for a volume

View File

@ -335,6 +335,27 @@ type CSIDriverSpec struct {
// more modes may be added in the future. // more modes may be added in the future.
// +optional // +optional
VolumeLifecycleModes []VolumeLifecycleMode `json:"volumeLifecycleModes,omitempty" protobuf:"bytes,3,opt,name=volumeLifecycleModes"` VolumeLifecycleModes []VolumeLifecycleMode `json:"volumeLifecycleModes,omitempty" protobuf:"bytes,3,opt,name=volumeLifecycleModes"`
// If set to true, storageCapacity indicates that the CSI
// volume driver wants pod scheduling to consider the storage
// capacity that the driver deployment will report by creating
// CSIStorageCapacity objects with capacity information.
//
//
// The check can be enabled immediately when deploying a driver.
// In that case, provisioning new volumes with late binding
// will pause until the driver deployment has published
// some suitable CSIStorageCapacity object.
//
// Alternatively, the driver can be deployed with the field
// unset or false and it can be flipped later when storage
// capacity information has been published.
//
// This is an alpha field and only available when the CSIStorageCapacity
// feature is enabled. The default is false.
//
// +optional
StorageCapacity *bool `json:"storageCapacity,omitempty" protobuf:"bytes,4,opt,name=storageCapacity"`
} }
// VolumeLifecycleMode is an enumeration of possible usage modes for a volume // VolumeLifecycleMode is an enumeration of possible usage modes for a volume