mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
StorageClass API changes for VolumeBindingMode
This commit is contained in:
parent
1ced91f201
commit
b60bd37114
@ -65,6 +65,13 @@ type StorageClass struct {
|
||||
// for all PVs created from this storageclass.
|
||||
// +optional
|
||||
AllowVolumeExpansion *bool
|
||||
|
||||
// VolumeBindingMode indicates how PersistentVolumeClaims should be
|
||||
// provisioned and bound. When unset, VolumeBindingImmediate is used.
|
||||
// This field is alpha-level and is only honored by servers that enable
|
||||
// the VolumeScheduling feature.
|
||||
// +optional
|
||||
VolumeBindingMode *VolumeBindingMode
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
@ -187,3 +194,18 @@ type VolumeError struct {
|
||||
// +optional
|
||||
Message string
|
||||
}
|
||||
|
||||
// VolumeBindingMode indicates how PersistentVolumeClaims should be bound.
|
||||
type VolumeBindingMode string
|
||||
|
||||
const (
|
||||
// VolumeBindingImmediate indicates that PersistentVolumeClaims should be
|
||||
// immediately provisioned and bound.
|
||||
VolumeBindingImmediate VolumeBindingMode = "Immediate"
|
||||
|
||||
// VolumeBindingWaitForFirstConsumer indicates that PersistentVolumeClaims
|
||||
// should not be provisioned and bound until the first Pod is created that
|
||||
// references the PeristentVolumeClaim. The volume provisioning and
|
||||
// binding will occur during Pod scheduing.
|
||||
VolumeBindingWaitForFirstConsumer VolumeBindingMode = "WaitForFirstConsumer"
|
||||
)
|
||||
|
@ -3,13 +3,22 @@ package(default_visibility = ["//visibility:public"])
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["helpers.go"],
|
||||
srcs = [
|
||||
"helpers.go",
|
||||
"util.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/apis/storage/util",
|
||||
deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/storage:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
@ -24,3 +33,14 @@ filegroup(
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["util_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/apis/storage/util",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/apis/storage:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
30
pkg/apis/storage/util/util.go
Normal file
30
pkg/apis/storage/util/util.go
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2017 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 util
|
||||
|
||||
import (
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// DropDisabledAlphaFields removes disabled fields from the StorageClass object.
|
||||
func DropDisabledAlphaFields(class *storage.StorageClass) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
class.VolumeBindingMode = nil
|
||||
}
|
||||
}
|
52
pkg/apis/storage/util/util_test.go
Normal file
52
pkg/apis/storage/util/util_test.go
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
Copyright 2017 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 util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
)
|
||||
|
||||
func TestDropAlphaFields(t *testing.T) {
|
||||
bindingMode := storage.VolumeBindingWaitForFirstConsumer
|
||||
|
||||
// Test that field gets dropped when feature gate is not set
|
||||
class := &storage.StorageClass{
|
||||
VolumeBindingMode: &bindingMode,
|
||||
}
|
||||
DropDisabledAlphaFields(class)
|
||||
if class.VolumeBindingMode != nil {
|
||||
t.Errorf("VolumeBindingMode field didn't get dropped: %+v", class.VolumeBindingMode)
|
||||
}
|
||||
|
||||
// Test that field does not get dropped when feature gate is set
|
||||
class = &storage.StorageClass{
|
||||
VolumeBindingMode: &bindingMode,
|
||||
}
|
||||
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true"); err != nil {
|
||||
t.Fatalf("Failed to set feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
DropDisabledAlphaFields(class)
|
||||
if class.VolumeBindingMode != &bindingMode {
|
||||
t.Errorf("VolumeBindingMode field got unexpectantly modified: %+v", class.VolumeBindingMode)
|
||||
}
|
||||
if err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false"); err != nil {
|
||||
t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
}
|
@ -3,13 +3,17 @@ package(default_visibility = ["//visibility:public"])
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["helpers.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/apis/storage/v1/util",
|
||||
deps = ["//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/api/storage/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
@ -24,3 +28,11 @@ filegroup(
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["helpers_test.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/apis/storage/v1/util",
|
||||
library = ":go_default_library",
|
||||
deps = ["//vendor/k8s.io/api/storage/v1:go_default_library"],
|
||||
)
|
||||
|
@ -16,7 +16,10 @@ limitations under the License.
|
||||
|
||||
package util
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
import (
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// IsDefaultStorageClassAnnotation represents a StorageClass annotation that
|
||||
// marks a class as the default StorageClass
|
||||
@ -51,3 +54,10 @@ func IsDefaultAnnotation(obj metav1.ObjectMeta) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsBindingModeWaitForFirstConsumer returns true if the VolumeBindingMode is set
|
||||
// to VolumeBindingWaitForFirstConsumer
|
||||
func IsBindingModeWaitForFirstConsumer(class *storagev1.StorageClass) bool {
|
||||
mode := class.VolumeBindingMode
|
||||
return mode != nil && *mode == storagev1.VolumeBindingWaitForFirstConsumer
|
||||
}
|
||||
|
55
pkg/apis/storage/v1/util/helpers_test.go
Normal file
55
pkg/apis/storage/v1/util/helpers_test.go
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2017 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 util
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
storagev1 "k8s.io/api/storage/v1"
|
||||
)
|
||||
|
||||
type bindingTest struct {
|
||||
class *storagev1.StorageClass
|
||||
expected bool
|
||||
}
|
||||
|
||||
func TestIsBindingModeWaitForFirstConsumer(t *testing.T) {
|
||||
immediateMode := storagev1.VolumeBindingImmediate
|
||||
waitingMode := storagev1.VolumeBindingWaitForFirstConsumer
|
||||
cases := map[string]bindingTest{
|
||||
"nil binding mode": {
|
||||
&storagev1.StorageClass{},
|
||||
false,
|
||||
},
|
||||
"immediate binding mode": {
|
||||
&storagev1.StorageClass{VolumeBindingMode: &immediateMode},
|
||||
false,
|
||||
},
|
||||
"waiting binding mode": {
|
||||
&storagev1.StorageClass{VolumeBindingMode: &waitingMode},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range cases {
|
||||
result := IsBindingModeWaitForFirstConsumer(testCase.class)
|
||||
if result != testCase.expected {
|
||||
t.Errorf("Test %q failed. Expected %v, got %v", testName, testCase.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -46,6 +46,7 @@ func ValidateStorageClass(storageClass *storage.StorageClass) field.ErrorList {
|
||||
allErrs = append(allErrs, validateParameters(storageClass.Parameters, field.NewPath("parameters"))...)
|
||||
allErrs = append(allErrs, validateReclaimPolicy(storageClass.ReclaimPolicy, field.NewPath("reclaimPolicy"))...)
|
||||
allErrs = append(allErrs, validateAllowVolumeExpansion(storageClass.AllowVolumeExpansion, field.NewPath("allowVolumeExpansion"))...)
|
||||
allErrs = append(allErrs, validateVolumeBindingMode(storageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
@ -64,6 +65,8 @@ func ValidateStorageClassUpdate(storageClass, oldStorageClass *storage.StorageCl
|
||||
if *storageClass.ReclaimPolicy != *oldStorageClass.ReclaimPolicy {
|
||||
allErrs = append(allErrs, field.Forbidden(field.NewPath("reclaimPolicy"), "updates to reclaimPolicy are forbidden."))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, apivalidation.ValidateImmutableField(storageClass.VolumeBindingMode, oldStorageClass.VolumeBindingMode, field.NewPath("volumeBindingMode"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
@ -218,3 +221,20 @@ func ValidateVolumeAttachmentUpdate(new, old *storage.VolumeAttachment) field.Er
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
var supportedVolumeBindingModes = sets.NewString(string(storage.VolumeBindingImmediate), string(storage.VolumeBindingWaitForFirstConsumer))
|
||||
|
||||
// validateVolumeBindingMode tests that VolumeBindingMode specifies valid values.
|
||||
func validateVolumeBindingMode(mode *storage.VolumeBindingMode, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
if mode != nil {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.VolumeScheduling) {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath, "field is disabled by feature-gate VolumeScheduling"))
|
||||
}
|
||||
if !supportedVolumeBindingModes.Has(string(*mode)) {
|
||||
allErrs = append(allErrs, field.NotSupported(fldPath, mode, supportedVolumeBindingModes.List()))
|
||||
}
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
@ -27,6 +27,14 @@ import (
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
)
|
||||
|
||||
var (
|
||||
deleteReclaimPolicy = api.PersistentVolumeReclaimDelete
|
||||
immediateMode1 = storage.VolumeBindingImmediate
|
||||
immediateMode2 = storage.VolumeBindingImmediate
|
||||
waitingMode = storage.VolumeBindingWaitForFirstConsumer
|
||||
invalidMode = storage.VolumeBindingMode("foo")
|
||||
)
|
||||
|
||||
func TestValidateStorageClass(t *testing.T) {
|
||||
deleteReclaimPolicy := api.PersistentVolumeReclaimPolicy("Delete")
|
||||
retainReclaimPolicy := api.PersistentVolumeReclaimPolicy("Retain")
|
||||
@ -436,3 +444,141 @@ func TestVolumeAttachmentUpdateValidation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeClassWithBinding(mode *storage.VolumeBindingMode) *storage.StorageClass {
|
||||
return &storage.StorageClass{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "foo", ResourceVersion: "foo"},
|
||||
Provisioner: "kubernetes.io/foo-provisioner",
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: mode,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove these tests once feature gate is not required
|
||||
func TestValidateVolumeBindingModeAlphaDisabled(t *testing.T) {
|
||||
errorCases := map[string]*storage.StorageClass{
|
||||
"immediate mode": makeClassWithBinding(&immediateMode1),
|
||||
"waiting mode": makeClassWithBinding(&waitingMode),
|
||||
"invalid mode": makeClassWithBinding(&invalidMode),
|
||||
}
|
||||
|
||||
for testName, storageClass := range errorCases {
|
||||
if errs := ValidateStorageClass(storageClass); len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test: %v", testName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type bindingTest struct {
|
||||
class *storage.StorageClass
|
||||
shouldSucceed bool
|
||||
}
|
||||
|
||||
func TestValidateVolumeBindingMode(t *testing.T) {
|
||||
cases := map[string]bindingTest{
|
||||
"no mode": {
|
||||
class: makeClassWithBinding(nil),
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"immediate mode": {
|
||||
class: makeClassWithBinding(&immediateMode1),
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"waiting mode": {
|
||||
class: makeClassWithBinding(&waitingMode),
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"invalid mode": {
|
||||
class: makeClassWithBinding(&invalidMode),
|
||||
shouldSucceed: false,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: remove when feature gate not required
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
|
||||
for testName, testCase := range cases {
|
||||
errs := ValidateStorageClass(testCase.class)
|
||||
if testCase.shouldSucceed && len(errs) != 0 {
|
||||
t.Errorf("Expected success for test %q, got %v", testName, errs)
|
||||
}
|
||||
if !testCase.shouldSucceed && len(errs) == 0 {
|
||||
t.Errorf("Expected failure for test %q, got success", testName)
|
||||
}
|
||||
}
|
||||
|
||||
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
type updateTest struct {
|
||||
oldClass *storage.StorageClass
|
||||
newClass *storage.StorageClass
|
||||
shouldSucceed bool
|
||||
}
|
||||
|
||||
func TestValidateUpdateVolumeBindingMode(t *testing.T) {
|
||||
noBinding := makeClassWithBinding(nil)
|
||||
immediateBinding1 := makeClassWithBinding(&immediateMode1)
|
||||
immediateBinding2 := makeClassWithBinding(&immediateMode2)
|
||||
waitBinding := makeClassWithBinding(&waitingMode)
|
||||
|
||||
cases := map[string]updateTest{
|
||||
"old and new no mode": {
|
||||
oldClass: noBinding,
|
||||
newClass: noBinding,
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"old and new same mode ptr": {
|
||||
oldClass: immediateBinding1,
|
||||
newClass: immediateBinding1,
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"old and new same mode value": {
|
||||
oldClass: immediateBinding1,
|
||||
newClass: immediateBinding2,
|
||||
shouldSucceed: true,
|
||||
},
|
||||
"old no mode, new mode": {
|
||||
oldClass: noBinding,
|
||||
newClass: waitBinding,
|
||||
shouldSucceed: false,
|
||||
},
|
||||
"old mode, new no mode": {
|
||||
oldClass: waitBinding,
|
||||
newClass: noBinding,
|
||||
shouldSucceed: false,
|
||||
},
|
||||
"old and new different modes": {
|
||||
oldClass: waitBinding,
|
||||
newClass: immediateBinding1,
|
||||
shouldSucceed: false,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: remove when feature gate not required
|
||||
err := utilfeature.DefaultFeatureGate.Set("VolumeScheduling=true")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to enable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
|
||||
for testName, testCase := range cases {
|
||||
errs := ValidateStorageClassUpdate(testCase.newClass, testCase.oldClass)
|
||||
if testCase.shouldSucceed && len(errs) != 0 {
|
||||
t.Errorf("Expected success for %v, got %v", testName, errs)
|
||||
}
|
||||
if !testCase.shouldSucceed && len(errs) == 0 {
|
||||
t.Errorf("Expected failure for %v, got success", testName)
|
||||
}
|
||||
}
|
||||
|
||||
err = utilfeature.DefaultFeatureGate.Set("VolumeScheduling=false")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to disable feature gate for VolumeScheduling: %v", err)
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ const (
|
||||
// the API server as the certificate approaches expiration.
|
||||
RotateKubeletClientCertificate utilfeature.Feature = "RotateKubeletClientCertificate"
|
||||
|
||||
// owner: @msau
|
||||
// owner: @msau42
|
||||
// alpha: v1.7
|
||||
//
|
||||
// A new volume type that supports local disks on a node.
|
||||
@ -175,6 +175,12 @@ const (
|
||||
//
|
||||
// Enable running mount utilities in containers.
|
||||
MountContainers utilfeature.Feature = "MountContainers"
|
||||
|
||||
// owner: @msau42
|
||||
// alpha: v1.9
|
||||
//
|
||||
// Extend the default scheduler to be aware of PV topology and handle PV binding
|
||||
VolumeScheduling utilfeature.Feature = "VolumeScheduling"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -208,6 +214,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
||||
CPUManager: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha},
|
||||
|
||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||
// unintentionally on either side:
|
||||
|
@ -3214,6 +3214,9 @@ func describeStorageClass(sc *storage.StorageClass, events *api.EventList) (stri
|
||||
if sc.ReclaimPolicy != nil {
|
||||
w.Write(LEVEL_0, "ReclaimPolicy:\t%s\n", *sc.ReclaimPolicy)
|
||||
}
|
||||
if sc.VolumeBindingMode != nil {
|
||||
w.Write(LEVEL_0, "VolumeBindingMode:\t%s\n", *sc.VolumeBindingMode)
|
||||
}
|
||||
if events != nil {
|
||||
DescribeEvents(events, w)
|
||||
}
|
||||
|
@ -943,6 +943,7 @@ func TestDescribeDeployment(t *testing.T) {
|
||||
|
||||
func TestDescribeStorageClass(t *testing.T) {
|
||||
reclaimPolicy := api.PersistentVolumeReclaimRetain
|
||||
bindingMode := storage.VolumeBindingMode("bindingmode")
|
||||
f := fake.NewSimpleClientset(&storage.StorageClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
@ -956,14 +957,22 @@ func TestDescribeStorageClass(t *testing.T) {
|
||||
"param1": "value1",
|
||||
"param2": "value2",
|
||||
},
|
||||
ReclaimPolicy: &reclaimPolicy,
|
||||
ReclaimPolicy: &reclaimPolicy,
|
||||
VolumeBindingMode: &bindingMode,
|
||||
})
|
||||
s := StorageClassDescriber{f}
|
||||
out, err := s.Describe("", "foo", printers.DescriberSettings{ShowEvents: true})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "foo") {
|
||||
if !strings.Contains(out, "foo") ||
|
||||
!strings.Contains(out, "my-provisioner") ||
|
||||
!strings.Contains(out, "param1") ||
|
||||
!strings.Contains(out, "param2") ||
|
||||
!strings.Contains(out, "value1") ||
|
||||
!strings.Contains(out, "value2") ||
|
||||
!strings.Contains(out, "Retain") ||
|
||||
!strings.Contains(out, "bindingmode") {
|
||||
t.Errorf("unexpected out: %s", out)
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ go_library(
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/storage:go_default_library",
|
||||
"//pkg/apis/storage/util:go_default_library",
|
||||
"//pkg/apis/storage/validation:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
storageutil "k8s.io/kubernetes/pkg/apis/storage/util"
|
||||
"k8s.io/kubernetes/pkg/apis/storage/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
@ -49,6 +50,8 @@ func (storageClassStrategy) PrepareForCreate(ctx genericapirequest.Context, obj
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.ExpandPersistentVolumes) {
|
||||
class.AllowVolumeExpansion = nil
|
||||
}
|
||||
|
||||
storageutil.DropDisabledAlphaFields(class)
|
||||
}
|
||||
|
||||
func (storageClassStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
|
||||
@ -73,6 +76,8 @@ func (storageClassStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj,
|
||||
newClass.AllowVolumeExpansion = nil
|
||||
oldClass.AllowVolumeExpansion = nil
|
||||
}
|
||||
storageutil.DropDisabledAlphaFields(oldClass)
|
||||
storageutil.DropDisabledAlphaFields(newClass)
|
||||
}
|
||||
|
||||
func (storageClassStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
|
||||
|
@ -35,6 +35,7 @@ func TestStorageClassStrategy(t *testing.T) {
|
||||
}
|
||||
|
||||
deleteReclaimPolicy := api.PersistentVolumeReclaimDelete
|
||||
bindingMode := storage.VolumeBindingWaitForFirstConsumer
|
||||
storageClass := &storage.StorageClass{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "valid-class",
|
||||
@ -43,7 +44,8 @@ func TestStorageClassStrategy(t *testing.T) {
|
||||
Parameters: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &bindingMode,
|
||||
}
|
||||
|
||||
Strategy.PrepareForCreate(ctx, storageClass)
|
||||
@ -62,7 +64,8 @@ func TestStorageClassStrategy(t *testing.T) {
|
||||
Parameters: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
ReclaimPolicy: &deleteReclaimPolicy,
|
||||
VolumeBindingMode: &bindingMode,
|
||||
}
|
||||
|
||||
Strategy.PrepareForUpdate(ctx, newStorageClass, storageClass)
|
||||
|
@ -59,6 +59,13 @@ type StorageClass struct {
|
||||
// AllowVolumeExpansion shows whether the storage class allow volume expand
|
||||
// +optional
|
||||
AllowVolumeExpansion *bool `json:"allowVolumeExpansion,omitempty" protobuf:"varint,6,opt,name=allowVolumeExpansion"`
|
||||
|
||||
// VolumeBindingMode indicates how PersistentVolumeClaims should be
|
||||
// provisioned and bound. When unset, VolumeBindingImmediate is used.
|
||||
// This field is alpha-level and is only honored by servers that enable
|
||||
// the VolumeScheduling feature.
|
||||
// +optional
|
||||
VolumeBindingMode *VolumeBindingMode `json:"volumeBindingMode,omitempty" protobuf:"bytes,7,opt,name=volumeBindingMode"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
@ -74,3 +81,18 @@ type StorageClassList struct {
|
||||
// Items is the list of StorageClasses
|
||||
Items []StorageClass `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
||||
// VolumeBindingMode indicates how PersistentVolumeClaims should be bound.
|
||||
type VolumeBindingMode string
|
||||
|
||||
const (
|
||||
// VolumeBindingImmediate indicates that PersistentVolumeClaims should be
|
||||
// immediately provisioned and bound. This is the default mode.
|
||||
VolumeBindingImmediate VolumeBindingMode = "Immediate"
|
||||
|
||||
// VolumeBindingWaitForFirstConsumer indicates that PersistentVolumeClaims
|
||||
// should not be provisioned and bound until the first Pod is created that
|
||||
// references the PeristentVolumeClaim. The volume provisioning and
|
||||
// binding will occur during Pod scheduing.
|
||||
VolumeBindingWaitForFirstConsumer VolumeBindingMode = "WaitForFirstConsumer"
|
||||
)
|
||||
|
@ -59,6 +59,13 @@ type StorageClass struct {
|
||||
// AllowVolumeExpansion shows whether the storage class allow volume expand
|
||||
// +optional
|
||||
AllowVolumeExpansion *bool `json:"allowVolumeExpansion,omitempty" protobuf:"varint,6,opt,name=allowVolumeExpansion"`
|
||||
|
||||
// VolumeBindingMode indicates how PersistentVolumeClaims should be
|
||||
// provisioned and bound. When unset, VolumeBindingImmediate is used.
|
||||
// This field is alpha-level and is only honored by servers that enable
|
||||
// the VolumeScheduling feature.
|
||||
// +optional
|
||||
VolumeBindingMode *VolumeBindingMode `json:"volumeBindingMode,omitempty" protobuf:"bytes,7,opt,name=volumeBindingMode"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
@ -74,3 +81,18 @@ type StorageClassList struct {
|
||||
// Items is the list of StorageClasses
|
||||
Items []StorageClass `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
||||
// VolumeBindingMode indicates how PersistentVolumeClaims should be bound.
|
||||
type VolumeBindingMode string
|
||||
|
||||
const (
|
||||
// VolumeBindingImmediate indicates that PersistentVolumeClaims should be
|
||||
// immediately provisioned and bound. This is the default mode.
|
||||
VolumeBindingImmediate VolumeBindingMode = "Immediate"
|
||||
|
||||
// VolumeBindingWaitForFirstConsumer indicates that PersistentVolumeClaims
|
||||
// should not be provisioned and bound until the first Pod is created that
|
||||
// references the PeristentVolumeClaim. The volume provisioning and
|
||||
// binding will occur during Pod scheduing.
|
||||
VolumeBindingWaitForFirstConsumer VolumeBindingMode = "WaitForFirstConsumer"
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user