Add VolumeType api to PV and PVC

This commit is contained in:
Scott Creeley 2017-08-09 13:51:46 -04:00
parent 842518d336
commit 36f30bc689
19 changed files with 1215 additions and 54 deletions

View File

@ -1,7 +1,9 @@
- baseImportPath: "./pkg/apis/core/"
allowedImports:
- k8s.io/apimachinery
- k8s.io/apiserver/pkg/util/feature
- k8s.io/kubernetes/pkg/apis/core
- k8s.io/kubernetes/pkg/features
- k8s.io/kubernetes/pkg/util
- k8s.io/api/core/v1

View File

@ -17,7 +17,9 @@ limitations under the License.
package persistentvolume
import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
func getClaimRefNamespace(pv *api.PersistentVolume) string {
@ -96,3 +98,11 @@ func VisitPVSecretNames(pv *api.PersistentVolume, visitor Visitor) bool {
}
return true
}
// DropDisabledAlphaFields removes disabled fields from the pv spec.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pv spec.
func DropDisabledAlphaFields(pvSpec *api.PersistentVolumeSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
pvSpec.VolumeMode = nil
}
}

View File

@ -24,7 +24,9 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
func TestPVSecrets(t *testing.T) {
@ -204,3 +206,62 @@ func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.
return secretPaths
}
func newHostPathType(pathType string) *api.HostPathType {
hostPathType := new(api.HostPathType)
*hostPathType = api.HostPathType(pathType)
return hostPathType
}
func TestDropAlphaPVVolumeMode(t *testing.T) {
vmode := api.PersistentVolumeFilesystem
// PersistentVolume with VolumeMode set
pv := api.PersistentVolume{
Spec: api.PersistentVolumeSpec{
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
},
},
StorageClassName: "test-storage-class",
VolumeMode: &vmode,
},
}
// Enable alpha feature BlockVolume
err1 := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err1 != nil {
t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err1)
}
// now test dropping the fields - should not be dropped
DropDisabledAlphaFields(&pv.Spec)
// check to make sure VolumeDevices is still present
// if featureset is set to true
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
if pv.Spec.VolumeMode == nil {
t.Error("VolumeMode in pv.Spec should not have been dropped based on feature-gate")
}
}
// Disable alpha feature BlockVolume
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
if err != nil {
t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err)
}
// now test dropping the fields
DropDisabledAlphaFields(&pv.Spec)
// check to make sure VolumeDevices is nil
// if featureset is set to false
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
if pv.Spec.VolumeMode != nil {
t.Error("DropDisabledAlphaFields VolumeMode for pv.Spec failed")
}
}
}

View File

@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["util.go"],
deps = ["//pkg/api:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["util_test.go"],
library = ":go_default_library",
deps = [
"//pkg/api:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
],
)

View File

@ -0,0 +1,4 @@
reviewers:
- smarterclayton
- jsafrane
- david-mcmahon

View File

@ -0,0 +1,31 @@
/*
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 persistentvolumeclaim
import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
// DropDisabledAlphaFields removes disabled fields from the pvc spec.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a pvc spec.
func DropDisabledAlphaFields(pvcSpec *core.PersistentVolumeClaimSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
pvcSpec.VolumeMode = nil
}
}

View File

@ -0,0 +1,71 @@
/*
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 persistentvolumeclaim
import (
"testing"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
func TestDropAlphaPVCVolumeMode(t *testing.T) {
vmode := core.PersistentVolumeFilesystem
// PersistentVolume with VolumeMode set
pvc := core.PersistentVolumeClaim{
Spec: core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
VolumeMode: &vmode,
},
}
// Enable alpha feature BlockVolume
err1 := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err1 != nil {
t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err1)
}
// now test dropping the fields - should not be dropped
DropDisabledAlphaFields(&pvc.Spec)
// check to make sure VolumeDevices is still present
// if featureset is set to true
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
if pvc.Spec.VolumeMode == nil {
t.Error("VolumeMode in pvc.Spec should not have been dropped based on feature-gate")
}
}
// Disable alpha feature BlockVolume
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
if err != nil {
t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err)
}
// now test dropping the fields
DropDisabledAlphaFields(&pvc.Spec)
// check to make sure VolumeDevices is nil
// if featureset is set to false
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
if pvc.Spec.VolumeMode != nil {
t.Error("DropDisabledAlphaFields VolumeMode for pvc.Spec failed")
}
}
}

View File

@ -243,12 +243,15 @@ func DropDisabledAlphaFields(podSpec *api.PodSpec) {
}
}
}
for i := range podSpec.Containers {
DropDisabledVolumeMountsAlphaFields(podSpec.Containers[i].VolumeMounts)
}
for i := range podSpec.InitContainers {
DropDisabledVolumeMountsAlphaFields(podSpec.InitContainers[i].VolumeMounts)
}
DropDisabledVolumeDevicesAlphaFields(podSpec)
}
// DropDisabledVolumeMountsAlphaFields removes disabled fields from []VolumeMount.
@ -260,3 +263,16 @@ func DropDisabledVolumeMountsAlphaFields(volumeMounts []api.VolumeMount) {
}
}
}
// DropDisabledVolumeDevicesAlphaFields removes disabled fields from []VolumeDevice.
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a VolumeDevice
func DropDisabledVolumeDevicesAlphaFields(podSpec *api.PodSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
for i := range podSpec.Containers {
podSpec.Containers[i].VolumeDevices = nil
}
for i := range podSpec.InitContainers {
podSpec.InitContainers[i].VolumeDevices = nil
}
}
}

View File

@ -24,7 +24,9 @@ import (
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/features"
)
func TestPodSecrets(t *testing.T) {
@ -254,3 +256,85 @@ func TestPodConfigmaps(t *testing.T) {
t.Error("Extra names extracted. Verify VisitPodConfigmapNames() is correctly extracting resource names")
}
}
func TestDropAlphaVolumeDevices(t *testing.T) {
testPod := api.Pod{
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyNever,
Containers: []api.Container{
{
Name: "container1",
Image: "testimage",
VolumeDevices: []api.VolumeDevice{
{
Name: "myvolume",
DevicePath: "/usr/test",
},
},
},
},
InitContainers: []api.Container{
{
Name: "container1",
Image: "testimage",
VolumeDevices: []api.VolumeDevice{
{
Name: "myvolume",
DevicePath: "/usr/test",
},
},
},
},
Volumes: []api.Volume{
{
Name: "myvolume",
VolumeSource: api.VolumeSource{
HostPath: &api.HostPathVolumeSource{
Path: "/dev/xvdc",
},
},
},
},
},
}
// Enable alpha feature BlockVolume
err1 := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err1 != nil {
t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err1)
}
// now test dropping the fields - should not be dropped
DropDisabledAlphaFields(&testPod.Spec)
// check to make sure VolumeDevices is still present
// if featureset is set to true
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
if testPod.Spec.Containers[0].VolumeDevices == nil {
t.Error("VolumeDevices in Container should not have been dropped based on feature-gate")
}
if testPod.Spec.InitContainers[0].VolumeDevices == nil {
t.Error("VolumeDevices in Container should not have been dropped based on feature-gate")
}
}
// Disable alpha feature BlockVolume
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
if err != nil {
t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err)
}
// now test dropping the fields
DropDisabledAlphaFields(&testPod.Spec)
// check to make sure VolumeDevices is nil
// if featureset is set to false
if !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
if testPod.Spec.Containers[0].VolumeDevices != nil {
t.Error("DropDisabledAlphaFields for Containers failed")
}
if testPod.Spec.InitContainers[0].VolumeDevices != nil {
t.Error("DropDisabledAlphaFields for InitContainers failed")
}
}
}

View File

@ -462,6 +462,11 @@ type PersistentVolumeSpec struct {
// simply fail if one is invalid.
// +optional
MountOptions []string
// volumeMode defines if a volume is intended to be used with a formatted filesystem
// or to remain in raw block state. Value of Filesystem is implied when not included in spec.
// This is an alpha feature and may change in the future.
// +optional
VolumeMode *PersistentVolumeMode
}
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes
@ -479,6 +484,16 @@ const (
PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain"
)
// PersistentVolumeMode describes how a volume is intended to be consumed, either Block or Filesystem.
type PersistentVolumeMode string
const (
// PersistentVolumeBlock means the volume will not be formatted with a filesystem and will remain a raw block device.
PersistentVolumeBlock PersistentVolumeMode = "Block"
// PersistentVolumeFilesystem means the volume will be or is formatted with a filesystem.
PersistentVolumeFilesystem PersistentVolumeMode = "Filesystem"
)
type PersistentVolumeStatus struct {
// Phase indicates if a volume is available, bound to a claim, or released by a claim
// +optional
@ -548,6 +563,11 @@ type PersistentVolumeClaimSpec struct {
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#class-1
// +optional
StorageClassName *string
// volumeMode defines what type of volume is required by the claim.
// Value of Filesystem is implied when not included in claim spec.
// This is an alpha feature and may change in the future.
// +optional
VolumeMode *PersistentVolumeMode
}
type PersistentVolumeClaimConditionType string
@ -1586,6 +1606,14 @@ const (
MountPropagationBidirectional MountPropagationMode = "Bidirectional"
)
// VolumeDevice describes a mapping of a raw block device within a container.
type VolumeDevice struct {
// name must match the name of a persistentVolumeClaim in the pod
Name string
// devicePath is the path inside of the container that the device will be mapped to.
DevicePath string
}
// EnvVar represents an environment variable present in a Container.
type EnvVar struct {
// Required: This must be a C_IDENTIFIER.
@ -1879,6 +1907,10 @@ type Container struct {
Resources ResourceRequirements
// +optional
VolumeMounts []VolumeMount
// volumeDevices is the list of block devices to be used by the container.
// This is an alpha feature and may change in the future.
// +optional
VolumeDevices []VolumeDevice
// +optional
LivenessProbe *Probe
// +optional

View File

@ -20,6 +20,8 @@ import (
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/util/parsers"
utilpointer "k8s.io/kubernetes/pkg/util/pointer"
)
@ -228,11 +230,19 @@ func SetDefaults_PersistentVolume(obj *v1.PersistentVolume) {
if obj.Spec.PersistentVolumeReclaimPolicy == "" {
obj.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimRetain
}
if obj.Spec.VolumeMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
obj.Spec.VolumeMode = new(v1.PersistentVolumeMode)
*obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem
}
}
func SetDefaults_PersistentVolumeClaim(obj *v1.PersistentVolumeClaim) {
if obj.Status.Phase == "" {
obj.Status.Phase = v1.ClaimPending
}
if obj.Spec.VolumeMode == nil && utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
obj.Spec.VolumeMode = new(v1.PersistentVolumeMode)
*obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem
}
}
func SetDefaults_ISCSIVolumeSource(obj *v1.ISCSIVolumeSource) {
if obj.ISCSIInterface == "" {

View File

@ -26,6 +26,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme"
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
@ -817,6 +818,33 @@ func TestSetDefaultPersistentVolume(t *testing.T) {
if pv2.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimRetain {
t.Errorf("Expected pv reclaim policy %v, got %v", v1.PersistentVolumeReclaimRetain, pv2.Spec.PersistentVolumeReclaimPolicy)
}
// When feature gate is disabled, field should not be defaulted
defaultMode := v1.PersistentVolumeFilesystem
outputMode := pv2.Spec.VolumeMode
if outputMode != nil {
t.Errorf("Expected VolumeMode to not be defaulted, got: %+v", outputMode)
}
// When feature gate is enabled, field should be defaulted
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err != nil {
t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err)
}
obj3 := roundTrip(t, runtime.Object(pv)).(*v1.PersistentVolume)
outputMode3 := obj3.Spec.VolumeMode
if outputMode3 == nil {
t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: nil", defaultMode)
} else if *outputMode3 != defaultMode {
t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: %+v", defaultMode, outputMode3)
}
err = utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
if err != nil {
t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err)
}
}
func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
@ -827,6 +855,32 @@ func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
if pvc2.Status.Phase != v1.ClaimPending {
t.Errorf("Expected claim phase %v, got %v", v1.ClaimPending, pvc2.Status.Phase)
}
// When feature gate is disabled, field should not be defaulted
defaultMode := v1.PersistentVolumeFilesystem
outputMode := pvc2.Spec.VolumeMode
if outputMode != nil {
t.Errorf("Expected VolumeMode to not be defaulted, got: %+v", outputMode)
}
// When feature gate is enabled, field should be defaulted
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err != nil {
t.Fatalf("Failed to enable feature gate for BlockVolume: %v", err)
}
obj3 := roundTrip(t, runtime.Object(pvc)).(*v1.PersistentVolumeClaim)
outputMode3 := obj3.Spec.VolumeMode
if outputMode3 == nil {
t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: nil", defaultMode)
} else if *outputMode3 != defaultMode {
t.Errorf("Expected VolumeMode to be defaulted to: %+v, got: %+v", defaultMode, outputMode3)
}
err = utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
if err != nil {
t.Fatalf("Failed to disable feature gate for BlockVolume: %v", err)
}
}
func TestSetDefaulEndpointsProtocol(t *testing.T) {

View File

@ -64,7 +64,7 @@ const isNotIntegerErrorMsg string = `must be an integer`
const isNotPositiveErrorMsg string = `must be greater than zero`
var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255)
var volumeModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive"
var fileModeErrorMsg string = "must be a number between 0 and 0777 (octal), both inclusive"
// BannedOwners is a black list of object that are not allowed to be owners.
var BannedOwners = apimachineryvalidation.BannedOwners
@ -364,10 +364,11 @@ func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fld
return apimachineryvalidation.ValidateNoNewFinalizers(newFinalizers, oldFinalizers, fldPath)
}
func ValidateVolumes(volumes []core.Volume, fldPath *field.Path) (sets.String, field.ErrorList) {
func ValidateVolumes(volumes []core.Volume, fldPath *field.Path) (map[string]core.VolumeSource, field.ErrorList) {
allErrs := field.ErrorList{}
allNames := sets.String{}
vols := make(map[string]core.VolumeSource)
for i, vol := range volumes {
idxPath := fldPath.Index(i)
namePath := idxPath.Child("name")
@ -382,12 +383,69 @@ func ValidateVolumes(volumes []core.Volume, fldPath *field.Path) (sets.String, f
}
if len(el) == 0 {
allNames.Insert(vol.Name)
vols[vol.Name] = vol.VolumeSource
} else {
allErrs = append(allErrs, el...)
}
}
return allNames, allErrs
return vols, allErrs
}
func IsMatchedVolume(name string, volumes map[string]core.VolumeSource) bool {
if _, ok := volumes[name]; ok {
return true
} else {
return false
}
}
func isMatchedDevice(name string, volumes map[string]core.VolumeSource) (bool, bool) {
if source, ok := volumes[name]; ok {
if source.PersistentVolumeClaim != nil {
return true, true
} else {
return true, false
}
} else {
return false, false
}
}
func mountNameAlreadyExists(name string, devices map[string]string) bool {
if _, ok := devices[name]; ok {
return true
} else {
return false
}
}
func mountPathAlreadyExists(mountPath string, devices map[string]string) bool {
for _, devPath := range devices {
if mountPath == devPath {
return true
}
}
return false
}
func deviceNameAlreadyExists(name string, mounts map[string]string) bool {
if _, ok := mounts[name]; ok {
return true
} else {
return false
}
}
func devicePathAlreadyExists(devicePath string, mounts map[string]string) bool {
for _, mountPath := range mounts {
if mountPath == devicePath {
return true
}
}
return false
}
func validateVolumeSource(source *core.VolumeSource, fldPath *field.Path, volName string) field.ErrorList {
@ -745,7 +803,7 @@ func validateSecretVolumeSource(secretSource *core.SecretVolumeSource, fldPath *
secretMode := secretSource.DefaultMode
if secretMode != nil && (*secretMode > 0777 || *secretMode < 0) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *secretMode, volumeModeErrorMsg))
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *secretMode, fileModeErrorMsg))
}
itemsPath := fldPath.Child("items")
@ -764,7 +822,7 @@ func validateConfigMapVolumeSource(configMapSource *core.ConfigMapVolumeSource,
configMapMode := configMapSource.DefaultMode
if configMapMode != nil && (*configMapMode > 0777 || *configMapMode < 0) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *configMapMode, volumeModeErrorMsg))
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *configMapMode, fileModeErrorMsg))
}
itemsPath := fldPath.Child("items")
@ -785,7 +843,7 @@ func validateKeyToPath(kp *core.KeyToPath, fldPath *field.Path) field.ErrorList
}
allErrs = append(allErrs, validateLocalNonReservedPath(kp.Path, fldPath.Child("path"))...)
if kp.Mode != nil && (*kp.Mode > 0777 || *kp.Mode < 0) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *kp.Mode, volumeModeErrorMsg))
allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *kp.Mode, fileModeErrorMsg))
}
return allErrs
@ -882,7 +940,7 @@ func validateDownwardAPIVolumeFile(file *core.DownwardAPIVolumeFile, fldPath *fi
allErrs = append(allErrs, field.Required(fldPath, "one of fieldRef and resourceFieldRef is required"))
}
if file.Mode != nil && (*file.Mode > 0777 || *file.Mode < 0) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, volumeModeErrorMsg))
allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, fileModeErrorMsg))
}
return allErrs
@ -893,7 +951,7 @@ func validateDownwardAPIVolumeSource(downwardAPIVolume *core.DownwardAPIVolumeSo
downwardAPIMode := downwardAPIVolume.DefaultMode
if downwardAPIMode != nil && (*downwardAPIMode > 0777 || *downwardAPIMode < 0) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *downwardAPIMode, volumeModeErrorMsg))
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *downwardAPIMode, fileModeErrorMsg))
}
for _, file := range downwardAPIVolume.Items {
@ -983,7 +1041,7 @@ func validateProjectedVolumeSource(projection *core.ProjectedVolumeSource, fldPa
projectionMode := projection.DefaultMode
if projectionMode != nil && (*projectionMode > 0777 || *projectionMode < 0) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *projectionMode, volumeModeErrorMsg))
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *projectionMode, fileModeErrorMsg))
}
allErrs = append(allErrs, validateProjectionSources(projection, projectionMode, fldPath)...)
@ -1344,6 +1402,8 @@ var supportedAccessModes = sets.NewString(string(core.ReadWriteOnce), string(cor
var supportedReclaimPolicy = sets.NewString(string(core.PersistentVolumeReclaimDelete), string(core.PersistentVolumeReclaimRecycle), string(core.PersistentVolumeReclaimRetain))
var supportedVolumeModes = sets.NewString(string(core.PersistentVolumeBlock), string(core.PersistentVolumeFilesystem))
func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
metaPath := field.NewPath("metadata")
allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath)
@ -1582,7 +1642,11 @@ func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
allErrs = append(allErrs, field.Invalid(specPath.Child("storageClassName"), pv.Spec.StorageClassName, msg))
}
}
if pv.Spec.VolumeMode != nil && !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
allErrs = append(allErrs, field.Forbidden(specPath.Child("volumeMode"), "PersistentVolume volumeMode is disabled by feature-gate"))
} else if pv.Spec.VolumeMode != nil && !supportedVolumeModes.Has(string(*pv.Spec.VolumeMode)) {
allErrs = append(allErrs, field.NotSupported(specPath.Child("volumeMode"), *pv.Spec.VolumeMode, supportedVolumeModes.List()))
}
return allErrs
}
@ -1598,6 +1662,11 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E
}
newPv.Status = oldPv.Status
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
allErrs = append(allErrs, ValidateImmutableField(newPv.Spec.VolumeMode, oldPv.Spec.VolumeMode, field.NewPath("volumeMode"))...)
}
return allErrs
}
@ -1646,6 +1715,11 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg))
}
}
if spec.VolumeMode != nil && !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumeMode"), "PersistentVolumeClaim volumeMode is disabled by feature-gate"))
} else if spec.VolumeMode != nil && !supportedVolumeModes.Has(string(*spec.VolumeMode)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumeMode"), *spec.VolumeMode, supportedVolumeModes.List()))
}
return allErrs
}
@ -1692,6 +1766,10 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
// TODO: remove Beta when no longer needed
allErrs = append(allErrs, ValidateImmutableAnnotation(newPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], oldPvc.ObjectMeta.Annotations[v1.BetaStorageClassAnnotation], v1.BetaStorageClassAnnotation, field.NewPath("metadata"))...)
if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
allErrs = append(allErrs, ValidateImmutableField(newPvc.Spec.VolumeMode, oldPvc.Spec.VolumeMode, field.NewPath("volumeMode"))...)
}
newPvc.Status = oldPvc.Status
return allErrs
}
@ -1975,7 +2053,27 @@ func validateSecretKeySelector(s *core.SecretKeySelector, fldPath *field.Path) f
return allErrs
}
func ValidateVolumeMounts(mounts []core.VolumeMount, volumes sets.String, container *core.Container, fldPath *field.Path) field.ErrorList {
func GetVolumeMountMap(mounts []core.VolumeMount) map[string]string {
volmounts := make(map[string]string)
for _, mnt := range mounts {
volmounts[mnt.Name] = mnt.MountPath
}
return volmounts
}
func GetVolumeDeviceMap(devices []core.VolumeDevice) map[string]string {
voldevices := make(map[string]string)
for _, dev := range devices {
voldevices[dev.Name] = dev.DevicePath
}
return voldevices
}
func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]string, volumes map[string]core.VolumeSource, container *core.Container, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
mountpoints := sets.NewString()
@ -1983,7 +2081,8 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, volumes sets.String, contai
idxPath := fldPath.Index(i)
if len(mnt.Name) == 0 {
allErrs = append(allErrs, field.Required(idxPath.Child("name"), ""))
} else if !volumes.Has(mnt.Name) {
}
if !IsMatchedVolume(mnt.Name, volumes) {
allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), mnt.Name))
}
if len(mnt.MountPath) == 0 {
@ -1993,6 +2092,15 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, volumes sets.String, contai
allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique"))
}
mountpoints.Insert(mnt.MountPath)
// check for overlap with VolumeDevice
if mountNameAlreadyExists(mnt.Name, voldevices) {
allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), mnt.Name, "must not already exist in volumeDevices"))
}
if mountPathAlreadyExists(mnt.MountPath, voldevices) {
allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must not already exist as a path in volumeDevices"))
}
if len(mnt.SubPath) > 0 {
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
}
@ -2004,6 +2112,60 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, volumes sets.String, contai
return allErrs
}
func ValidateVolumeDevices(devices []core.VolumeDevice, volmounts map[string]string, volumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
devicepath := sets.NewString()
devicename := sets.NewString()
if devices != nil && !utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("volumeDevices"), "Container volumeDevices is disabled by feature-gate"))
return allErrs
}
if devices != nil {
for i, dev := range devices {
idxPath := fldPath.Index(i)
devName := dev.Name
devPath := dev.DevicePath
didMatch, isPVC := isMatchedDevice(devName, volumes)
if len(devName) == 0 {
allErrs = append(allErrs, field.Required(idxPath.Child("name"), ""))
}
if devicename.Has(devName) {
allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "must be unique"))
}
// Must be PersistentVolumeClaim volume source
if didMatch && !isPVC {
allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "can only use volume source type of PersistentVolumeClaim for block mode"))
}
if !didMatch {
allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), devName))
}
if len(devPath) == 0 {
allErrs = append(allErrs, field.Required(idxPath.Child("devicePath"), ""))
}
if devicepath.Has(devPath) {
allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "must be unique"))
}
if len(devPath) > 0 && len(validatePathNoBacksteps(devPath, fldPath.Child("devicePath"))) > 0 {
allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "can not contain backsteps ('..')"))
} else {
devicepath.Insert(devPath)
}
// check for overlap with VolumeMount
if deviceNameAlreadyExists(devName, volmounts) {
allErrs = append(allErrs, field.Invalid(idxPath.Child("name"), devName, "must not already exist in volumeMounts"))
}
if devicePathAlreadyExists(devPath, volmounts) {
allErrs = append(allErrs, field.Invalid(idxPath.Child("devicePath"), devPath, "must not already exist as a path in volumeMounts"))
}
if len(devName) > 0 {
devicename.Insert(devName)
}
}
}
return allErrs
}
func validateProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
@ -2187,10 +2349,10 @@ func validatePullPolicy(policy core.PullPolicy, fldPath *field.Path) field.Error
return allErrors
}
func validateInitContainers(containers, otherContainers []core.Container, volumes sets.String, fldPath *field.Path) field.ErrorList {
func validateInitContainers(containers, otherContainers []core.Container, deviceVolumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if len(containers) > 0 {
allErrs = append(allErrs, validateContainers(containers, volumes, fldPath)...)
allErrs = append(allErrs, validateContainers(containers, deviceVolumes, fldPath)...)
}
allNames := sets.String{}
@ -2218,7 +2380,7 @@ func validateInitContainers(containers, otherContainers []core.Container, volume
return allErrs
}
func validateContainers(containers []core.Container, volumes sets.String, fldPath *field.Path) field.ErrorList {
func validateContainers(containers []core.Container, volumes map[string]core.VolumeSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if len(containers) == 0 {
@ -2229,6 +2391,9 @@ func validateContainers(containers []core.Container, volumes sets.String, fldPat
for i, ctr := range containers {
idxPath := fldPath.Index(i)
namePath := idxPath.Child("name")
volMounts := GetVolumeMountMap(ctr.VolumeMounts)
volDevices := GetVolumeDeviceMap(ctr.VolumeDevices)
if len(ctr.Name) == 0 {
allErrs = append(allErrs, field.Required(namePath, ""))
} else {
@ -2266,7 +2431,8 @@ func validateContainers(containers []core.Container, volumes sets.String, fldPat
allErrs = append(allErrs, validateContainerPorts(ctr.Ports, idxPath.Child("ports"))...)
allErrs = append(allErrs, ValidateEnv(ctr.Env, idxPath.Child("env"))...)
allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, idxPath.Child("envFrom"))...)
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volumes, &ctr, idxPath.Child("volumeMounts"))...)
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volDevices, volumes, &ctr, idxPath.Child("volumeMounts"))...)
allErrs = append(allErrs, ValidateVolumeDevices(ctr.VolumeDevices, volMounts, volumes, idxPath.Child("volumeDevices"))...)
allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, idxPath.Child("imagePullPolicy"))...)
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"))...)
allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, idxPath.Child("securityContext"))...)
@ -2546,10 +2712,10 @@ func ValidatePod(pod *core.Pod) field.ErrorList {
func ValidatePodSpec(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
allVolumes, vErrs := ValidateVolumes(spec.Volumes, fldPath.Child("volumes"))
vols, vErrs := ValidateVolumes(spec.Volumes, fldPath.Child("volumes"))
allErrs = append(allErrs, vErrs...)
allErrs = append(allErrs, validateContainers(spec.Containers, allVolumes, fldPath.Child("containers"))...)
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, allVolumes, fldPath.Child("initContainers"))...)
allErrs = append(allErrs, validateContainers(spec.Containers, vols, fldPath.Child("containers"))...)
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"))...)
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)

View File

@ -26,14 +26,12 @@ import (
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme"
_ "k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/core"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/capabilities"
"k8s.io/kubernetes/pkg/security/apparmor"
@ -82,6 +80,7 @@ func testVolumeWithNodeAffinity(t *testing.T, name string, namespace string, aff
}
func TestValidatePersistentVolumes(t *testing.T) {
validMode := core.PersistentVolumeFilesystem
scenarios := map[string]struct {
isExpectedFailure bool
volume *core.PersistentVolume
@ -352,6 +351,25 @@ func TestValidatePersistentVolumes(t *testing.T) {
StorageClassName: "-invalid-",
}),
},
// VolumeMode alpha feature disabled
// TODO: remove when no longer alpha
"alpha disabled valid volume mode": {
isExpectedFailure: true,
volume: testVolume("foo", "", core.PersistentVolumeSpec{
Capacity: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
PersistentVolumeSource: core.PersistentVolumeSource{
HostPath: &core.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(core.HostPathDirectory)),
},
},
StorageClassName: "valid",
VolumeMode: &validMode,
}),
},
// LocalVolume alpha feature disabled
// TODO: remove when no longer alpha
"alpha disabled valid local volume": {
@ -434,38 +452,38 @@ func TestValidatePersistentVolumes(t *testing.T) {
}
func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
validVolume := testVolume("foo", "", api.PersistentVolumeSpec{
Capacity: api.ResourceList{
api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
validVolume := testVolume("foo", "", core.PersistentVolumeSpec{
Capacity: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
},
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
PersistentVolumeSource: api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
PersistentVolumeSource: core.PersistentVolumeSource{
HostPath: &core.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(api.HostPathDirectory)),
Type: newHostPathType(string(core.HostPathDirectory)),
},
},
StorageClassName: "valid",
})
validPvSourceNoUpdate := validVolume.DeepCopy()
invalidPvSourceUpdateType := validVolume.DeepCopy()
invalidPvSourceUpdateType.Spec.PersistentVolumeSource = api.PersistentVolumeSource{
FlexVolume: &api.FlexVolumeSource{
invalidPvSourceUpdateType.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
FlexVolume: &core.FlexVolumeSource{
Driver: "kubernetes.io/blue",
FSType: "ext4",
},
}
invalidPvSourceUpdateDeep := validVolume.DeepCopy()
invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = api.PersistentVolumeSource{
HostPath: &api.HostPathVolumeSource{
invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
HostPath: &core.HostPathVolumeSource{
Path: "/updated",
Type: newHostPathType(string(api.HostPathDirectory)),
Type: newHostPathType(string(core.HostPathDirectory)),
},
}
scenarios := map[string]struct {
isExpectedFailure bool
oldVolume *api.PersistentVolume
newVolume *api.PersistentVolume
oldVolume *core.PersistentVolume
newVolume *core.PersistentVolume
}{
"condition-no-update": {
isExpectedFailure: false,
@ -720,6 +738,7 @@ func testVolumeClaimAnnotation(name string, namespace string, ann string, annval
func TestValidatePersistentVolumeClaim(t *testing.T) {
invalidClassName := "-invalid-"
validClassName := "valid"
validMode := core.PersistentVolumeFilesystem
scenarios := map[string]struct {
isExpectedFailure bool
claim *core.PersistentVolumeClaim
@ -888,7 +907,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(api.ResourceStorage): resource.MustParse("0G"),
core.ResourceName(core.ResourceStorage): resource.MustParse("0G"),
},
},
}),
@ -916,6 +935,32 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
StorageClassName: &invalidClassName,
}),
},
// VolumeMode alpha feature disabled
// TODO: remove when no longer alpha
"disabled alpha valid volume mode": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: "key2",
Operator: "Exists",
},
},
},
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
StorageClassName: &validClassName,
VolumeMode: &validMode,
}),
},
}
for name, scenario := range scenarios {
@ -929,7 +974,101 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}
}
func TestAlphaPVVolumeModeUpdate(t *testing.T) {
block := core.PersistentVolumeBlock
file := core.PersistentVolumeFilesystem
scenarios := map[string]struct {
isExpectedFailure bool
oldPV *core.PersistentVolume
newPV *core.PersistentVolume
enableBlock bool
}{
"valid-update-volume-mode-block-to-block": {
isExpectedFailure: false,
oldPV: createTestVolModePV(&block),
newPV: createTestVolModePV(&block),
enableBlock: true,
},
"valid-update-volume-mode-file-to-file": {
isExpectedFailure: false,
oldPV: createTestVolModePV(&file),
newPV: createTestVolModePV(&file),
enableBlock: true,
},
"invalid-update-volume-mode-to-block": {
isExpectedFailure: true,
oldPV: createTestVolModePV(&file),
newPV: createTestVolModePV(&block),
enableBlock: true,
},
"invalid-update-volume-mode-to-file": {
isExpectedFailure: true,
oldPV: createTestVolModePV(&block),
newPV: createTestVolModePV(&file),
enableBlock: true,
},
"invalid-update-blocksupport-disabled": {
isExpectedFailure: true,
oldPV: createTestVolModePV(&block),
newPV: createTestVolModePV(&block),
enableBlock: false,
},
"invalid-update-volume-mode-nil-to-file": {
isExpectedFailure: true,
oldPV: createTestVolModePV(nil),
newPV: createTestVolModePV(&file),
enableBlock: true,
},
"invalid-update-volume-mode-nil-to-block": {
isExpectedFailure: true,
oldPV: createTestVolModePV(nil),
newPV: createTestVolModePV(&block),
enableBlock: true,
},
"invalid-update-volume-mode-file-to-nil": {
isExpectedFailure: true,
oldPV: createTestVolModePV(&file),
newPV: createTestVolModePV(nil),
enableBlock: true,
},
"invalid-update-volume-mode-block-to-nil": {
isExpectedFailure: true,
oldPV: createTestVolModePV(&block),
newPV: createTestVolModePV(nil),
enableBlock: true,
},
"invalid-update-volume-mode-nil-to-nil": {
isExpectedFailure: false,
oldPV: createTestVolModePV(nil),
newPV: createTestVolModePV(nil),
enableBlock: true,
},
"invalid-update-volume-mode-empty-to-mode": {
isExpectedFailure: true,
oldPV: createTestPV(),
newPV: createTestVolModePV(&block),
enableBlock: true,
},
}
for name, scenario := range scenarios {
// ensure we have a resource version specified for updates
toggleBlockVolumeFeature(scenario.enableBlock, t)
errs := ValidatePersistentVolumeUpdate(scenario.newPV, scenario.oldPV)
if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name)
}
if len(errs) > 0 && !scenario.isExpectedFailure {
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs)
}
}
}
func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
block := core.PersistentVolumeBlock
file := core.PersistentVolumeFilesystem
validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
@ -999,6 +1138,42 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
},
VolumeName: "volume",
})
validClaimVolumeModeFile := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
VolumeMode: &file,
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
VolumeName: "volume",
})
validClaimVolumeModeBlock := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
VolumeMode: &block,
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
VolumeName: "volume",
})
invalidClaimVolumeModeNil := testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
VolumeMode: nil,
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
VolumeName: "volume",
})
invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadOnlyMany,
@ -1080,78 +1255,168 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
oldClaim *core.PersistentVolumeClaim
newClaim *core.PersistentVolumeClaim
enableResize bool
enableBlock bool
}{
"valid-update-volumeName-only": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validUpdateClaim,
enableResize: false,
enableBlock: false,
},
"valid-no-op-update": {
isExpectedFailure: false,
oldClaim: validUpdateClaim,
newClaim: validUpdateClaim,
enableResize: false,
enableBlock: false,
},
"invalid-update-change-resources-on-bound-claim": {
isExpectedFailure: true,
oldClaim: validUpdateClaim,
newClaim: invalidUpdateClaimResources,
enableResize: false,
enableBlock: false,
},
"invalid-update-change-access-modes-on-bound-claim": {
isExpectedFailure: true,
oldClaim: validUpdateClaim,
newClaim: invalidUpdateClaimAccessModes,
enableResize: false,
enableBlock: false,
},
"valid-update-volume-mode-block-to-block": {
isExpectedFailure: false,
oldClaim: validClaimVolumeModeBlock,
newClaim: validClaimVolumeModeBlock,
enableResize: false,
enableBlock: true,
},
"valid-update-volume-mode-file-to-file": {
isExpectedFailure: false,
oldClaim: validClaimVolumeModeFile,
newClaim: validClaimVolumeModeFile,
enableResize: false,
enableBlock: true,
},
"invalid-update-volume-mode-to-block": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeFile,
newClaim: validClaimVolumeModeBlock,
enableResize: false,
enableBlock: true,
},
"invalid-update-volume-mode-to-file": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeBlock,
newClaim: validClaimVolumeModeFile,
enableResize: false,
enableBlock: true,
},
"invalid-update-volume-mode-nil-to-file": {
isExpectedFailure: true,
oldClaim: invalidClaimVolumeModeNil,
newClaim: validClaimVolumeModeFile,
enableResize: false,
enableBlock: true,
},
"invalid-update-volume-mode-nil-to-block": {
isExpectedFailure: true,
oldClaim: invalidClaimVolumeModeNil,
newClaim: validClaimVolumeModeBlock,
enableResize: false,
enableBlock: true,
},
"invalid-update-volume-mode-block-to-nil": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeBlock,
newClaim: invalidClaimVolumeModeNil,
enableResize: false,
enableBlock: true,
},
"invalid-update-volume-mode-file-to-nil": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeFile,
newClaim: invalidClaimVolumeModeNil,
enableResize: false,
enableBlock: true,
},
"invalid-update-volume-mode-empty-to-mode": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: validClaimVolumeModeBlock,
enableResize: false,
enableBlock: true,
},
"invalid-update-volume-mode-mode-to-empty": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeBlock,
newClaim: validClaim,
enableResize: false,
enableBlock: true,
},
"invalid-update-blocksupport-disabled": {
isExpectedFailure: true,
oldClaim: validClaimVolumeModeFile,
newClaim: validClaimVolumeModeFile,
enableResize: false,
enableBlock: false,
},
"invalid-update-change-storage-class-annotation-after-creation": {
isExpectedFailure: true,
oldClaim: validClaimStorageClass,
newClaim: invalidUpdateClaimStorageClass,
enableResize: false,
enableBlock: false,
},
"valid-update-mutable-annotation": {
isExpectedFailure: false,
oldClaim: validClaimAnnotation,
newClaim: validUpdateClaimMutableAnnotation,
enableResize: false,
enableBlock: false,
},
"valid-update-add-annotation": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validAddClaimAnnotation,
enableResize: false,
enableBlock: false,
},
"valid-size-update-resize-disabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: validSizeUpdate,
enableResize: false,
enableBlock: false,
},
"valid-size-update-resize-enabled": {
isExpectedFailure: false,
oldClaim: validClaim,
newClaim: validSizeUpdate,
enableResize: true,
enableBlock: false,
},
"invalid-size-update-resize-enabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: invalidSizeUpdate,
enableResize: true,
enableBlock: false,
},
"unbound-size-update-resize-enabled": {
isExpectedFailure: true,
oldClaim: validClaim,
newClaim: unboundSizeUpdate,
enableResize: true,
enableBlock: false,
},
}
for name, scenario := range scenarios {
// ensure we have a resource version specified for updates
togglePVExpandFeature(scenario.enableResize, t)
toggleBlockVolumeFeature(scenario.enableBlock, t)
scenario.oldClaim.ResourceVersion = "1"
scenario.newClaim.ResourceVersion = "1"
errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim)
@ -1164,6 +1429,23 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
}
}
func toggleBlockVolumeFeature(toggleFlag bool, t *testing.T) {
if toggleFlag {
// Enable alpha feature BlockVolume
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err != nil {
t.Errorf("Failed to enable feature gate for BlockVolume: %v", err)
return
}
} else {
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
if err != nil {
t.Errorf("Failed to disable feature gate for BlockVolume: %v", err)
return
}
}
}
func togglePVExpandFeature(toggleFlag bool, t *testing.T) {
if toggleFlag {
// Enable alpha feature LocalStorageCapacityIsolation
@ -1356,27 +1638,27 @@ func TestValidateGlusterfs(t *testing.T) {
func TestValidateCSIVolumeSource(t *testing.T) {
testCases := []struct {
name string
csi *api.CSIPersistentVolumeSource
csi *core.CSIPersistentVolumeSource
errtype field.ErrorType
errfield string
}{
{
name: "all required fields ok",
csi: &api.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123", ReadOnly: true},
},
{
name: "with default values ok",
csi: &api.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"},
csi: &core.CSIPersistentVolumeSource{Driver: "test-driver", VolumeHandle: "test-123"},
},
{
name: "missing driver name",
csi: &api.CSIPersistentVolumeSource{VolumeHandle: "test-123"},
csi: &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"},
errtype: field.ErrorTypeRequired,
errfield: "driver",
},
{
name: "missing volume handle",
csi: &api.CSIPersistentVolumeSource{Driver: "my-driver"},
csi: &core.CSIPersistentVolumeSource{Driver: "my-driver"},
errtype: field.ErrorTypeRequired,
errfield: "volumeHandle",
},
@ -2988,7 +3270,7 @@ func TestValidateVolumes(t *testing.T) {
t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail)
}
} else {
if len(names) != 1 || !names.Has(tc.vol.Name) {
if len(names) != 1 || !IsMatchedVolume(tc.vol.Name, names) {
t.Errorf("[%d: %q] wrong names result: %v", i, tc.name, names)
}
}
@ -3130,6 +3412,153 @@ func TestAlphaHugePagesIsolation(t *testing.T) {
}
}
func TestAlphaPVCVolumeMode(t *testing.T) {
// Enable alpha feature BlockVolume for PVC
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err != nil {
t.Errorf("Failed to enable feature gate for BlockVolume: %v", err)
return
}
block := core.PersistentVolumeBlock
file := core.PersistentVolumeFilesystem
fake := core.PersistentVolumeMode("fake")
empty := core.PersistentVolumeMode("")
// Success Cases
successCasesPVC := map[string]*core.PersistentVolumeClaim{
"valid block value": createTestVolModePVC(&block),
"valid filesystem value": createTestVolModePVC(&file),
"valid nil value": createTestVolModePVC(nil),
}
for k, v := range successCasesPVC {
if errs := ValidatePersistentVolumeClaim(v); len(errs) != 0 {
t.Errorf("expected success for %s", k)
}
}
// Error Cases
errorCasesPVC := map[string]*core.PersistentVolumeClaim{
"invalid value": createTestVolModePVC(&fake),
"empty value": createTestVolModePVC(&empty),
}
for k, v := range errorCasesPVC {
if errs := ValidatePersistentVolumeClaim(v); len(errs) == 0 {
t.Errorf("expected failure for %s", k)
}
}
}
func TestAlphaPVVolumeMode(t *testing.T) {
// Enable alpha feature BlockVolume for PV
err := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err != nil {
t.Errorf("Failed to enable feature gate for BlockVolume: %v", err)
return
}
block := core.PersistentVolumeBlock
file := core.PersistentVolumeFilesystem
fake := core.PersistentVolumeMode("fake")
empty := core.PersistentVolumeMode("")
// Success Cases
successCasesPV := map[string]*core.PersistentVolume{
"valid block value": createTestVolModePV(&block),
"valid filesystem value": createTestVolModePV(&file),
"valid nil value": createTestVolModePV(nil),
}
for k, v := range successCasesPV {
if errs := ValidatePersistentVolume(v); len(errs) != 0 {
t.Errorf("expected success for %s", k)
}
}
// Error Cases
errorCasesPV := map[string]*core.PersistentVolume{
"invalid value": createTestVolModePV(&fake),
"empty value": createTestVolModePV(&empty),
}
for k, v := range errorCasesPV {
if errs := ValidatePersistentVolume(v); len(errs) == 0 {
t.Errorf("expected failure for %s", k)
}
}
}
func createTestVolModePVC(vmode *core.PersistentVolumeMode) *core.PersistentVolumeClaim {
validName := "valid-storage-class"
pvc := core.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "default",
},
Spec: core.PersistentVolumeClaimSpec{
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
StorageClassName: &validName,
VolumeMode: vmode,
},
}
return &pvc
}
func createTestVolModePV(vmode *core.PersistentVolumeMode) *core.PersistentVolume {
// PersistentVolume with VolumeMode set (valid and invalid)
pv := core.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "",
},
Spec: core.PersistentVolumeSpec{
Capacity: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
PersistentVolumeSource: core.PersistentVolumeSource{
HostPath: &core.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(core.HostPathDirectory)),
},
},
StorageClassName: "test-storage-class",
VolumeMode: vmode,
},
}
return &pv
}
func createTestPV() *core.PersistentVolume {
// PersistentVolume with VolumeMode set (valid and invalid)
pv := core.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
Namespace: "",
},
Spec: core.PersistentVolumeSpec{
Capacity: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
PersistentVolumeSource: core.PersistentVolumeSource{
HostPath: &core.HostPathVolumeSource{
Path: "/foo",
Type: newHostPathType(string(core.HostPathDirectory)),
},
},
StorageClassName: "test-storage-class",
},
}
return &pv
}
func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
testCases := []core.VolumeSource{
@ -3881,7 +4310,16 @@ func TestValidateEnvFrom(t *testing.T) {
}
func TestValidateVolumeMounts(t *testing.T) {
volumes := sets.NewString("abc", "123", "abc-123")
volumes := []core.Volume{
{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err)
return
}
container := core.Container{
SecurityContext: nil,
}
@ -3899,7 +4337,11 @@ func TestValidateVolumeMounts(t *testing.T) {
{Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
{Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
}
if errs := ValidateVolumeMounts(successCase, volumes, &container, field.NewPath("field")); len(errs) != 0 {
goodVolumeDevices := []core.VolumeDevice{
{Name: "xyz", DevicePath: "/foofoo"},
{Name: "uvw", DevicePath: "/foofoo/share/test"},
}
if errs := ValidateVolumeMounts(successCase, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
@ -3913,9 +4355,16 @@ func TestValidateVolumeMounts(t *testing.T) {
"subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
"disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
"name exists in volumeDevice": {{Name: "xyz", MountPath: "/bar"}},
"mountpath exists in volumeDevice": {{Name: "uvw", MountPath: "/mnt/exists"}},
"both exist in volumeDevice": {{Name: "xyz", MountPath: "/mnt/exists"}},
}
badVolumeDevice := []core.VolumeDevice{
{Name: "xyz", DevicePath: "/mnt/exists"},
}
for k, v := range errorCases {
if errs := ValidateVolumeMounts(v, volumes, &container, field.NewPath("field")); len(errs) == 0 {
if errs := ValidateVolumeMounts(v, GetVolumeDeviceMap(badVolumeDevice), vols, &container, field.NewPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %s", k)
}
}
@ -4033,9 +4482,16 @@ func TestValidateMountPropagation(t *testing.T) {
return
}
volumes := []core.Volume{
{Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols2, v2err := ValidateVolumes(volumes, field.NewPath("field"))
if len(v2err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v2err)
return
}
for i, test := range tests {
volumes := sets.NewString("foo")
errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, volumes, test.container, field.NewPath("field"))
errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"))
if test.expectError && len(errs) == 0 {
t.Errorf("test %d expected error, got none", i)
}
@ -4043,7 +4499,81 @@ func TestValidateMountPropagation(t *testing.T) {
t.Errorf("test %d expected success, got error: %v", i, errs)
}
}
}
func TestAlphaValidateVolumeDevices(t *testing.T) {
volumes := []core.Volume{
{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
{Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
}
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
if len(v1err) > 0 {
t.Errorf("Invalid test volumes - expected success %v", v1err)
return
}
disabledAlphaVolDevice := []core.VolumeDevice{
{Name: "abc", DevicePath: "/foo"},
}
successCase := []core.VolumeDevice{
{Name: "abc", DevicePath: "/foo"},
{Name: "abc-123", DevicePath: "/usr/share/test"},
}
goodVolumeMounts := []core.VolumeMount{
{Name: "xyz", MountPath: "/foofoo"},
{Name: "ghi", MountPath: "/foo/usr/share/test"},
}
errorCases := map[string][]core.VolumeDevice{
"empty name": {{Name: "", DevicePath: "/foo"}},
"duplicate name": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc", DevicePath: "/foo/bar"}},
"name not found": {{Name: "not-found", DevicePath: "/usr/share/test"}},
"name found but invalid source": {{Name: "def", DevicePath: "/usr/share/test"}},
"empty devicepath": {{Name: "abc", DevicePath: ""}},
"relative devicepath": {{Name: "abc-123", DevicePath: "baz"}},
"duplicate devicepath": {{Name: "abc", DevicePath: "/foo"}, {Name: "abc-123", DevicePath: "/foo"}},
"no backsteps": {{Name: "def", DevicePath: "/baz/../"}},
"name exists in volumemounts": {{Name: "abc", DevicePath: "/baz/../"}},
"path exists in volumemounts": {{Name: "xyz", DevicePath: "/this/path/exists"}},
"both exist in volumemounts": {{Name: "abc", DevicePath: "/this/path/exists"}},
}
badVolumeMounts := []core.VolumeMount{
{Name: "abc", MountPath: "/foo"},
{Name: "abc-123", MountPath: "/this/path/exists"},
}
// enable Alpha BlockVolume
err1 := utilfeature.DefaultFeatureGate.Set("BlockVolume=true")
if err1 != nil {
t.Errorf("Failed to enable feature gate for BlockVolume: %v", err1)
return
}
// Success Cases:
// Validate normal success cases - only PVC volumeSource
if errs := ValidateVolumeDevices(successCase, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
// Error Cases:
// Validate normal error cases - only PVC volumeSource
for k, v := range errorCases {
if errs := ValidateVolumeDevices(v, GetVolumeMountMap(badVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %s", k)
}
}
// disable Alpha BlockVolume
err2 := utilfeature.DefaultFeatureGate.Set("BlockVolume=false")
if err2 != nil {
t.Errorf("Failed to disable feature gate for BlockVolume: %v", err2)
return
}
if errs := ValidateVolumeDevices(disabledAlphaVolDevice, GetVolumeMountMap(goodVolumeMounts), vols, field.NewPath("field")); len(errs) == 0 {
t.Errorf("expected failure: %v", errs)
}
}
func TestValidateProbe(t *testing.T) {
@ -4158,7 +4688,7 @@ func getResourceLimits(cpu, memory string) core.ResourceList {
}
func TestValidateContainers(t *testing.T) {
volumes := sets.String{}
volumeDevices := make(map[string]core.VolumeSource)
capabilities.SetForTests(capabilities.Capabilities{
AllowPrivileged: true,
})
@ -4328,7 +4858,7 @@ func TestValidateContainers(t *testing.T) {
},
{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)},
}
if errs := validateContainers(successCase, volumes, field.NewPath("field")); len(errs) != 0 {
if errs := validateContainers(successCase, volumeDevices, field.NewPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
@ -4590,7 +5120,7 @@ func TestValidateContainers(t *testing.T) {
},
}
for k, v := range errorCases {
if errs := validateContainers(v, volumes, field.NewPath("field")); len(errs) == 0 {
if errs := validateContainers(v, volumeDevices, field.NewPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %s", k)
}
}

View File

@ -43,11 +43,11 @@ func ValidatePodPresetSpec(spec *settings.PodPresetSpec, fldPath *field.Path) fi
allErrs = append(allErrs, field.Required(fldPath.Child("volumes", "env", "envFrom", "volumeMounts"), "must specify at least one"))
}
volumes, vErrs := apivalidation.ValidateVolumes(spec.Volumes, fldPath.Child("volumes"))
vols, vErrs := apivalidation.ValidateVolumes(spec.Volumes, fldPath.Child("volumes"))
allErrs = append(allErrs, vErrs...)
allErrs = append(allErrs, apivalidation.ValidateEnv(spec.Env, fldPath.Child("env"))...)
allErrs = append(allErrs, apivalidation.ValidateEnvFrom(spec.EnvFrom, fldPath.Child("envFrom"))...)
allErrs = append(allErrs, apivalidation.ValidateVolumeMounts(spec.VolumeMounts, volumes, nil, fldPath.Child("volumeMounts"))...)
allErrs = append(allErrs, apivalidation.ValidateVolumeMounts(spec.VolumeMounts, nil, vols, nil, fldPath.Child("volumeMounts"))...)
return allErrs
}

View File

@ -187,6 +187,12 @@ const (
//
// Enable mount/attachment of Container Storage Interface (CSI) backed PVs
CSIPersistentVolume utilfeature.Feature = "CSIPersistentVolume"
// owner: @screeley44
// alpha: v1.9
//
// Enable Block volume support in containers.
BlockVolume utilfeature.Feature = "BlockVolume"
)
func init() {
@ -222,6 +228,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha},
CSIPersistentVolume: {Default: false, PreRelease: utilfeature.Alpha},
BlockVolume: {Default: false, PreRelease: utilfeature.Alpha},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
pvutil "k8s.io/kubernetes/pkg/api/persistentvolume"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
volumevalidation "k8s.io/kubernetes/pkg/volume/validation"
@ -51,6 +52,8 @@ func (persistentvolumeStrategy) NamespaceScoped() bool {
func (persistentvolumeStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
pv := obj.(*api.PersistentVolume)
pv.Status = api.PersistentVolumeStatus{}
pvutil.DropDisabledAlphaFields(&pv.Spec)
}
func (persistentvolumeStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
@ -72,6 +75,9 @@ func (persistentvolumeStrategy) PrepareForUpdate(ctx genericapirequest.Context,
newPv := obj.(*api.PersistentVolume)
oldPv := old.(*api.PersistentVolume)
newPv.Status = oldPv.Status
pvutil.DropDisabledAlphaFields(&newPv.Spec)
pvutil.DropDisabledAlphaFields(&oldPv.Spec)
}
func (persistentvolumeStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
"k8s.io/kubernetes/pkg/api/legacyscheme"
pvcutil "k8s.io/kubernetes/pkg/api/persistentvolumeclaim"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
)
@ -48,8 +49,10 @@ func (persistentvolumeclaimStrategy) NamespaceScoped() bool {
// PrepareForCreate clears the Status field which is not allowed to be set by end users on creation.
func (persistentvolumeclaimStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
pv := obj.(*api.PersistentVolumeClaim)
pv.Status = api.PersistentVolumeClaimStatus{}
pvc := obj.(*api.PersistentVolumeClaim)
pvc.Status = api.PersistentVolumeClaimStatus{}
pvcutil.DropDisabledAlphaFields(&pvc.Spec)
}
func (persistentvolumeclaimStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
@ -70,6 +73,9 @@ func (persistentvolumeclaimStrategy) PrepareForUpdate(ctx genericapirequest.Cont
newPvc := obj.(*api.PersistentVolumeClaim)
oldPvc := old.(*api.PersistentVolumeClaim)
newPvc.Status = oldPvc.Status
pvcutil.DropDisabledAlphaFields(&newPvc.Spec)
pvcutil.DropDisabledAlphaFields(&oldPvc.Spec)
}
func (persistentvolumeclaimStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {

View File

@ -527,6 +527,11 @@ type PersistentVolumeSpec struct {
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options
// +optional
MountOptions []string `json:"mountOptions,omitempty" protobuf:"bytes,7,opt,name=mountOptions"`
// volumeMode defines if a volume is intended to be used with a formatted filesystem
// or to remain in raw block state. Value of Filesystem is implied when not included in spec.
// This is an alpha feature and may change in the future.
// +optional
VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,8,opt,name=volumeMode,casttype=PersistentVolumeMode"`
}
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes.
@ -544,6 +549,16 @@ const (
PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain"
)
// PersistentVolumeMode describes how a volume is intended to be consumed, either Block or Filesystem.
type PersistentVolumeMode string
const (
// PersistentVolumeBlock means the volume will not be formatted with a filesystem and will remain a raw block device.
PersistentVolumeBlock PersistentVolumeMode = "Block"
// PersistentVolumeFilesystem means the volume will be or is formatted with a filesystem.
PersistentVolumeFilesystem PersistentVolumeMode = "Filesystem"
)
// PersistentVolumeStatus is the current status of a persistent volume.
type PersistentVolumeStatus struct {
// Phase indicates if a volume is available, bound to a claim, or released by a claim.
@ -631,6 +646,11 @@ type PersistentVolumeClaimSpec struct {
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1
// +optional
StorageClassName *string `json:"storageClassName,omitempty" protobuf:"bytes,5,opt,name=storageClassName"`
// volumeMode defines what type of volume is required by the claim.
// Value of Filesystem is implied when not included in claim spec.
// This is an alpha feature and may change in the future.
// +optional
VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,6,opt,name=volumeMode,casttype=PersistentVolumeMode"`
}
// PersistentVolumeClaimConditionType is a valid value of PersistentVolumeClaimCondition.Type
@ -1709,6 +1729,14 @@ const (
MountPropagationBidirectional MountPropagationMode = "Bidirectional"
)
// volumeDevice describes a mapping of a raw block device within a container.
type VolumeDevice struct {
// name must match the name of a persistentVolumeClaim in the pod
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
// devicePath is the path inside of the container that the device will be mapped to.
DevicePath string `json:"devicePath" protobuf:"bytes,2,opt,name=devicePath"`
}
// EnvVar represents an environment variable present in a Container.
type EnvVar struct {
// Name of the environment variable. Must be a C_IDENTIFIER.
@ -2052,6 +2080,12 @@ type Container struct {
// +patchMergeKey=mountPath
// +patchStrategy=merge
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath" protobuf:"bytes,9,rep,name=volumeMounts"`
// volumeDevices is the list of block devices to be used by the container.
// This is an alpha feature and may change in the future.
// +patchMergeKey=devicePath
// +patchStrategy=merge
// +optional
VolumeDevices []VolumeDevice `json:"volumeDevices,omitempty" patchStrategy:"merge" patchMergeKey:"devicePath" protobuf:"bytes,21,rep,name=volumeDevices"`
// Periodic probe of container liveness.
// Container will be restarted if the probe fails.
// Cannot be updated.