Skip to content

Commit 087aafc

Browse files
authored
Merge pull request kubernetes#80568 from pohly/ephemeral-mode-check
ephemeral mode check
2 parents 1810bc8 + 8270fe8 commit 087aafc

40 files changed

+1295
-400
lines changed

api/api-rules/violation_exceptions.list

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ API rule violation: list_type_missing,k8s.io/api/storage/v1,StorageClassList,Ite
344344
API rule violation: list_type_missing,k8s.io/api/storage/v1,VolumeAttachmentList,Items
345345
API rule violation: list_type_missing,k8s.io/api/storage/v1alpha1,VolumeAttachmentList,Items
346346
API rule violation: list_type_missing,k8s.io/api/storage/v1beta1,CSIDriverList,Items
347+
API rule violation: list_type_missing,k8s.io/api/storage/v1beta1,CSIDriverSpec,VolumeLifecycleModes
347348
API rule violation: list_type_missing,k8s.io/api/storage/v1beta1,CSINodeDriver,TopologyKeys
348349
API rule violation: list_type_missing,k8s.io/api/storage/v1beta1,CSINodeList,Items
349350
API rule violation: list_type_missing,k8s.io/api/storage/v1beta1,CSINodeSpec,Drivers

api/openapi-spec/swagger.json

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

pkg/apis/storage/fuzzer/fuzzer.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package fuzzer
1818

1919
import (
20+
"fmt"
2021
fuzz "github.com/google/gofuzz"
2122

2223
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
@@ -37,6 +38,37 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
3738
func(obj *storage.CSIDriver, c fuzz.Continue) {
3839
c.FuzzNoCustom(obj) // fuzz self without calling this function again
3940

41+
// Custom fuzzing for volume modes.
42+
switch c.Rand.Intn(7) {
43+
case 0:
44+
obj.Spec.VolumeLifecycleModes = nil
45+
case 1:
46+
obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{}
47+
case 2:
48+
// Invalid mode.
49+
obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
50+
storage.VolumeLifecycleMode(fmt.Sprintf("%d", c.Rand.Int31())),
51+
}
52+
case 3:
53+
obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
54+
storage.VolumeLifecyclePersistent,
55+
}
56+
case 4:
57+
obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
58+
storage.VolumeLifecycleEphemeral,
59+
}
60+
case 5:
61+
obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
62+
storage.VolumeLifecyclePersistent,
63+
storage.VolumeLifecycleEphemeral,
64+
}
65+
case 6:
66+
obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
67+
storage.VolumeLifecycleEphemeral,
68+
storage.VolumeLifecyclePersistent,
69+
}
70+
}
71+
4072
// match defaulting
4173
if obj.Spec.AttachRequired == nil {
4274
obj.Spec.AttachRequired = new(bool)
@@ -46,6 +78,11 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
4678
obj.Spec.PodInfoOnMount = new(bool)
4779
*(obj.Spec.PodInfoOnMount) = false
4880
}
81+
if obj.Spec.VolumeLifecycleModes == nil {
82+
obj.Spec.VolumeLifecycleModes = []storage.VolumeLifecycleMode{
83+
storage.VolumeLifecyclePersistent,
84+
}
85+
}
4986
},
5087
}
5188
}

pkg/apis/storage/types.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,47 @@ type CSIDriverSpec struct {
293293
// "csi.storage.k8s.io/pod.uid": string(pod.UID)
294294
// +optional
295295
PodInfoOnMount *bool
296+
297+
// VolumeLifecycleModes defines what kind of volumes this CSI volume driver supports.
298+
// The default if the list is empty is "Persistent", which is the usage
299+
// defined by the CSI specification and implemented in Kubernetes via the usual
300+
// PV/PVC mechanism.
301+
// The other mode is "Ephemeral". In this mode, volumes are defined inline
302+
// inside the pod spec with CSIVolumeSource and their lifecycle is tied to
303+
// the lifecycle of that pod. A driver has to be aware of this
304+
// because it is only going to get a NodePublishVolume call for such a volume.
305+
// For more information about implementing this mode, see
306+
// https://kubernetes-csi.github.io/docs/ephemeral-local-volumes.html
307+
// A driver can support one or more of these mode and
308+
// more modes may be added in the future.
309+
// +optional
310+
VolumeLifecycleModes []VolumeLifecycleMode
296311
}
297312

313+
// VolumeLifecycleMode specifies how a CSI volume is used in Kubernetes.
314+
// More modes may be added in the future.
315+
type VolumeLifecycleMode string
316+
317+
const (
318+
// VolumeLifecyclePersistent explicitly confirms that the driver implements
319+
// the full CSI spec. It is the default when CSIDriverSpec.VolumeLifecycleModes is not
320+
// set. Such volumes are managed in Kubernetes via the persistent volume
321+
// claim mechanism and have a lifecycle that is independent of the pods which
322+
// use them.
323+
VolumeLifecyclePersistent VolumeLifecycleMode = "Persistent"
324+
// VolumeLifecycleEphemeral indicates that the driver can be used for
325+
// ephemeral inline volumes. Such volumes are specified inside the pod
326+
// spec with a CSIVolumeSource and, as far as Kubernetes is concerned, have
327+
// a lifecycle that is tied to the lifecycle of the pod. For example, such
328+
// a volume might contain data that gets created specifically for that pod,
329+
// like secrets.
330+
// But how the volume actually gets created and managed is entirely up to
331+
// the driver. It might also use reference counting to share the same volume
332+
// instance among different pods if the CSIVolumeSource of those pods is
333+
// identical.
334+
VolumeLifecycleEphemeral VolumeLifecycleMode = "Ephemeral"
335+
)
336+
298337
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
299338

300339
// CSINode holds information about all CSI drivers installed on a node.

pkg/apis/storage/v1beta1/BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ go_library(
2020
"//pkg/apis/core:go_default_library",
2121
"//pkg/apis/core/v1:go_default_library",
2222
"//pkg/apis/storage:go_default_library",
23+
"//pkg/features:go_default_library",
2324
"//staging/src/k8s.io/api/core/v1:go_default_library",
2425
"//staging/src/k8s.io/api/storage/v1beta1:go_default_library",
2526
"//staging/src/k8s.io/apimachinery/pkg/conversion:go_default_library",
2627
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
2728
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
29+
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
2830
],
2931
)
3032

@@ -51,7 +53,10 @@ go_test(
5153
deps = [
5254
"//pkg/api/legacyscheme:go_default_library",
5355
"//pkg/apis/storage/install:go_default_library",
56+
"//pkg/features:go_default_library",
5457
"//staging/src/k8s.io/api/storage/v1beta1:go_default_library",
5558
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
59+
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
60+
"//staging/src/k8s.io/component-base/featuregate/testing:go_default_library",
5661
],
5762
)

pkg/apis/storage/v1beta1/defaults.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"k8s.io/api/core/v1"
2121
storagev1beta1 "k8s.io/api/storage/v1beta1"
2222
"k8s.io/apimachinery/pkg/runtime"
23+
utilfeature "k8s.io/apiserver/pkg/util/feature"
24+
"k8s.io/kubernetes/pkg/features"
2325
)
2426

2527
func addDefaultingFuncs(scheme *runtime.Scheme) error {
@@ -47,4 +49,7 @@ func SetDefaults_CSIDriver(obj *storagev1beta1.CSIDriver) {
4749
obj.Spec.PodInfoOnMount = new(bool)
4850
*(obj.Spec.PodInfoOnMount) = false
4951
}
52+
if len(obj.Spec.VolumeLifecycleModes) == 0 && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) {
53+
obj.Spec.VolumeLifecycleModes = append(obj.Spec.VolumeLifecycleModes, storagev1beta1.VolumeLifecyclePersistent)
54+
}
5055
}

pkg/apis/storage/v1beta1/defaults_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ import (
2222

2323
storagev1beta1 "k8s.io/api/storage/v1beta1"
2424
"k8s.io/apimachinery/pkg/runtime"
25+
utilfeature "k8s.io/apiserver/pkg/util/feature"
26+
featuregatetesting "k8s.io/component-base/featuregate/testing"
2527
"k8s.io/kubernetes/pkg/api/legacyscheme"
2628
_ "k8s.io/kubernetes/pkg/apis/storage/install"
29+
"k8s.io/kubernetes/pkg/features"
2730
)
2831

2932
func roundTrip(t *testing.T, obj runtime.Object) runtime.Object {
@@ -81,3 +84,30 @@ func TestSetDefaultAttachRequired(t *testing.T) {
8184
t.Errorf("Expected PodInfoOnMount to be defaulted to: %+v, got: %+v", defaultPodInfo, outPodInfo)
8285
}
8386
}
87+
88+
func TestSetDefaultVolumeLifecycleModesEnabled(t *testing.T) {
89+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
90+
driver := &storagev1beta1.CSIDriver{}
91+
92+
// field should be defaulted
93+
defaultMode := storagev1beta1.VolumeLifecyclePersistent
94+
output := roundTrip(t, runtime.Object(driver)).(*storagev1beta1.CSIDriver)
95+
outModes := output.Spec.VolumeLifecycleModes
96+
if len(outModes) != 1 {
97+
t.Errorf("Expected VolumeLifecycleModes to be defaulted to: %+v, got: %+v", defaultMode, outModes)
98+
} else if outModes[0] != defaultMode {
99+
t.Errorf("Expected VolumeLifecycleModes to be defaulted to: %+v, got: %+v", defaultMode, outModes)
100+
}
101+
}
102+
103+
func TestSetDefaultVolumeLifecycleModesDisabled(t *testing.T) {
104+
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, false)()
105+
driver := &storagev1beta1.CSIDriver{}
106+
107+
// field should not be defaulted
108+
output := roundTrip(t, runtime.Object(driver)).(*storagev1beta1.CSIDriver)
109+
outModes := output.Spec.VolumeLifecycleModes
110+
if outModes != nil {
111+
t.Errorf("Expected VolumeLifecycleModes to remain nil, got: %+v", outModes)
112+
}
113+
}

pkg/apis/storage/v1beta1/zz_generated.conversion.go

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

pkg/apis/storage/validation/validation.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ func validateCSIDriverSpec(
419419
allErrs := field.ErrorList{}
420420
allErrs = append(allErrs, validateAttachRequired(spec.AttachRequired, fldPath.Child("attachedRequired"))...)
421421
allErrs = append(allErrs, validatePodInfoOnMount(spec.PodInfoOnMount, fldPath.Child("podInfoOnMount"))...)
422+
allErrs = append(allErrs, validateVolumeLifecycleModes(spec.VolumeLifecycleModes, fldPath.Child("volumeLifecycleModes"))...)
422423
return allErrs
423424
}
424425

@@ -441,3 +442,21 @@ func validatePodInfoOnMount(podInfoOnMount *bool, fldPath *field.Path) field.Err
441442

442443
return allErrs
443444
}
445+
446+
// validateVolumeLifecycleModes tests if mode has one of the allowed values.
447+
func validateVolumeLifecycleModes(modes []storage.VolumeLifecycleMode, fldPath *field.Path) field.ErrorList {
448+
allErrs := field.ErrorList{}
449+
for _, mode := range modes {
450+
switch mode {
451+
case storage.VolumeLifecyclePersistent, storage.VolumeLifecycleEphemeral:
452+
default:
453+
allErrs = append(allErrs, field.NotSupported(fldPath, mode,
454+
[]string{
455+
string(storage.VolumeLifecyclePersistent),
456+
string(storage.VolumeLifecycleEphemeral),
457+
}))
458+
}
459+
}
460+
461+
return allErrs
462+
}

0 commit comments

Comments
 (0)