diff --git a/pkg/apis/core/types.go b/pkg/apis/core/types.go index f861925cc7..58c19a79c9 100644 --- a/pkg/apis/core/types.go +++ b/pkg/apis/core/types.go @@ -391,6 +391,9 @@ type PersistentVolumeSource struct { // More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md // +optional StorageOS *StorageOSPersistentVolumeSource + // CSI (Container Storage Interface) represents storage that handled by an external CSI driver + // +optional + CSI *CSIPersistentVolumeSource } type PersistentVolumeClaimVolumeSource struct { @@ -1503,6 +1506,23 @@ type LocalVolumeSource struct { Path string } +// Represents storage that is managed by an external CSI volume driver +type CSIPersistentVolumeSource struct { + // Driver is the name of the driver to use for this volume. + // Required. + Driver string + + // VolumeHandle is the unique volume name returned by the CSI volume + // plugin’s CreateVolume to refer to the volume on all subsequent calls. + // Required. + VolumeHandle string + + // Optional: The value to pass to ControllerPublishVolumeRequest. + // Defaults to false (read/write). + // +optional + ReadOnly bool +} + // ContainerPort represents a network port in a single container type ContainerPort struct { // Optional: If specified, this must be an IANA_SVC_NAME Each named port diff --git a/pkg/apis/core/validation/validation.go b/pkg/apis/core/validation/validation.go index 8fe283ddfe..952fc236ff 100644 --- a/pkg/apis/core/validation/validation.go +++ b/pkg/apis/core/validation/validation.go @@ -1318,6 +1318,24 @@ func validateStorageOSPersistentVolumeSource(storageos *core.StorageOSPersistent return allErrs } +func validateCSIPersistentVolumeSource(csi *api.CSIPersistentVolumeSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if !utilfeature.DefaultFeatureGate.Enabled(features.CSIPersistentVolume) { + allErrs = append(allErrs, field.Forbidden(fldPath, "CSIPersistentVolume disabled by feature-gate")) + } + + if len(csi.Driver) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("driver"), "")) + } + + if len(csi.VolumeHandle) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("volumeHandle"), "")) + } + + return allErrs +} + // ValidatePersistentVolumeName checks that a name is appropriate for a // PersistentVolumeName object. var ValidatePersistentVolumeName = NameIsDNSSubdomain @@ -1541,6 +1559,15 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList { } } + if pv.Spec.CSI != nil { + if numVolumes > 0 { + allErrs = append(allErrs, field.Forbidden(specPath.Child("csi"), "may not specify more than 1 volume type")) + } else { + numVolumes++ + allErrs = append(allErrs, validateCSIPersistentVolumeSource(pv.Spec.CSI, specPath.Child("csi"))...) + } + } + if numVolumes == 0 { allErrs = append(allErrs, field.Required(specPath, "must specify a volume type")) } diff --git a/pkg/apis/core/validation/validation_test.go b/pkg/apis/core/validation/validation_test.go index 3a6121715c..7675cb6f8d 100644 --- a/pkg/apis/core/validation/validation_test.go +++ b/pkg/apis/core/validation/validation_test.go @@ -1353,6 +1353,64 @@ func TestValidateGlusterfs(t *testing.T) { } } +func TestValidateCSIVolumeSource(t *testing.T) { + testCases := []struct { + name string + csi *api.CSIPersistentVolumeSource + errtype field.ErrorType + errfield string + }{ + { + name: "all required fields ok", + csi: &api.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true}, + }, + { + name: "with default values ok", + csi: &api.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"}, + }, + { + name: "missing driver name", + csi: &api.CSIPersistentVolumeSource{VolumeHandle: "test-123"}, + errtype: field.ErrorTypeRequired, + errfield: "driver", + }, + { + name: "missing volume handle", + csi: &api.CSIPersistentVolumeSource{Driver: "my-driver"}, + errtype: field.ErrorTypeRequired, + errfield: "volumeHandle", + }, + } + + err := utilfeature.DefaultFeatureGate.Set("CSIPersistentVolume=true") + if err != nil { + t.Errorf("Failed to enable feature gate for CSIPersistentVolumes: %v", err) + return + } + + for i, tc := range testCases { + errs := validateCSIPersistentVolumeSource(tc.csi, field.NewPath("field")) + + if len(errs) > 0 && tc.errtype == "" { + t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs) + } else if len(errs) == 0 && tc.errtype != "" { + t.Errorf("[%d: %q] expected error type %v", i, tc.name, tc.errtype) + } else if len(errs) >= 1 { + if errs[0].Type != tc.errtype { + t.Errorf("[%d: %q] expected error type %v, got %v", i, tc.name, tc.errtype, errs[0].Type) + } else if !strings.HasSuffix(errs[0].Field, "."+tc.errfield) { + t.Errorf("[%d: %q] expected error on field %q, got %q", i, tc.name, tc.errfield, errs[0].Field) + } + } + } + err = utilfeature.DefaultFeatureGate.Set("CSIPersistentVolume=false") + if err != nil { + t.Errorf("Failed to disable feature gate for CSIPersistentVolumes: %v", err) + return + } + +} + // helper func newInt32(val int) *int32 { p := new(int32) diff --git a/pkg/apis/extensions/types.go b/pkg/apis/extensions/types.go index 01c6de7dcd..79694aae5c 100644 --- a/pkg/apis/extensions/types.go +++ b/pkg/apis/extensions/types.go @@ -919,6 +919,7 @@ var ( Projected FSType = "projected" PortworxVolume FSType = "portworxVolume" ScaleIO FSType = "scaleIO" + CSI FSType = "csi" All FSType = "*" ) diff --git a/pkg/features/kube_features.go b/pkg/features/kube_features.go index 860abbf55e..1489072217 100644 --- a/pkg/features/kube_features.go +++ b/pkg/features/kube_features.go @@ -181,6 +181,12 @@ const ( // // Extend the default scheduler to be aware of PV topology and handle PV binding VolumeScheduling utilfeature.Feature = "VolumeScheduling" + + // owner: @vladimirvivien + // alpha: v1.9 + // + // Enable mount/attachment of Container Storage Interface (CSI) backed PVs + CSIPersistentVolume utilfeature.Feature = "CSIPersistentVolume" ) func init() { @@ -215,6 +221,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS ServiceNodeExclusion: {Default: false, PreRelease: utilfeature.Alpha}, MountContainers: {Default: false, PreRelease: utilfeature.Alpha}, VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha}, + CSIPersistentVolume: {Default: false, PreRelease: utilfeature.Alpha}, // inherited features from generic apiserver, relisted here to get a conflict if it is changed // unintentionally on either side: diff --git a/pkg/printers/internalversion/describe.go b/pkg/printers/internalversion/describe.go index 4392e8e7db..4d660de81e 100644 --- a/pkg/printers/internalversion/describe.go +++ b/pkg/printers/internalversion/describe.go @@ -1073,6 +1073,14 @@ func printFlockerVolumeSource(flocker *api.FlockerVolumeSource, w PrefixWriter) flocker.DatasetName, flocker.DatasetUUID) } +func printCSIPersistentVolumeSource(csi *api.CSIPersistentVolumeSource, w PrefixWriter) { + w.Write(LEVEL_2, "Type:\tCSI (a Container Storage Interface (CSI) volume source)\n"+ + " Driver:\t%v\n"+ + " VolumeHandle:\t%v\n", + " ReadOnly:\t%v\n", + csi.Driver, csi.VolumeHandle, csi.ReadOnly) +} + type PersistentVolumeDescriber struct { clientset.Interface } @@ -1156,6 +1164,8 @@ func describePersistentVolume(pv *api.PersistentVolume, events *api.EventList) ( printFlexVolumeSource(pv.Spec.FlexVolume, w) case pv.Spec.Flocker != nil: printFlockerVolumeSource(pv.Spec.Flocker, w) + case pv.Spec.CSI != nil: + printCSIPersistentVolumeSource(pv.Spec.CSI, w) default: w.Write(LEVEL_1, "\n") } diff --git a/pkg/security/podsecuritypolicy/util/util.go b/pkg/security/podsecuritypolicy/util/util.go index 1e5b673dc3..d654f88c77 100644 --- a/pkg/security/podsecuritypolicy/util/util.go +++ b/pkg/security/podsecuritypolicy/util/util.go @@ -67,6 +67,7 @@ func GetAllFSTypesAsSet() sets.String { string(extensions.Projected), string(extensions.PortworxVolume), string(extensions.ScaleIO), + string(extensions.CSI), ) return fstypes } diff --git a/staging/src/k8s.io/api/core/v1/types.go b/staging/src/k8s.io/api/core/v1/types.go index c5ad91e67d..5865795b1b 100644 --- a/staging/src/k8s.io/api/core/v1/types.go +++ b/staging/src/k8s.io/api/core/v1/types.go @@ -448,6 +448,9 @@ type PersistentVolumeSource struct { // More info: https://releases.k8s.io/HEAD/examples/volumes/storageos/README.md // +optional StorageOS *StorageOSPersistentVolumeSource `json:"storageos,omitempty" protobuf:"bytes,21,opt,name=storageos"` + // CSI represents storage that handled by an external CSI driver + // +optional + CSI *CSIPersistentVolumeSource `json:"csi,omitempty" protobuf:"bytes,22,opt,name=csi"` } const ( @@ -1621,6 +1624,23 @@ type LocalVolumeSource struct { Path string `json:"path" protobuf:"bytes,1,opt,name=path"` } +// Represents storage that is managed by an external CSI volume driver +type CSIPersistentVolumeSource struct { + // Driver is the name of the driver to use for this volume. + // Required. + Driver string `json:"driver" protobuf:"bytes,1,opt,name=driver"` + + // VolumeHandle is the unique volume name returned by the CSI volume + // plugin’s CreateVolume to refer to the volume on all subsequent calls. + // Required. + VolumeHandle string `json:"volumeHandle" protobuf:"bytes,2,opt,name=volumeHandle"` + + // Optional: The value to pass to ControllerPublishVolumeRequest. + // Defaults to false (read/write). + // +optional + ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,3,opt,name=readOnly"` +} + // ContainerPort represents a network port in a single container. type ContainerPort struct { // If specified, this must be an IANA_SVC_NAME and unique within the pod. Each