mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
Add VolumeType api to PV and PVC
This commit is contained in:
parent
842518d336
commit
36f30bc689
@ -1,7 +1,9 @@
|
|||||||
- baseImportPath: "./pkg/apis/core/"
|
- baseImportPath: "./pkg/apis/core/"
|
||||||
allowedImports:
|
allowedImports:
|
||||||
- k8s.io/apimachinery
|
- k8s.io/apimachinery
|
||||||
|
- k8s.io/apiserver/pkg/util/feature
|
||||||
- k8s.io/kubernetes/pkg/apis/core
|
- k8s.io/kubernetes/pkg/apis/core
|
||||||
|
- k8s.io/kubernetes/pkg/features
|
||||||
- k8s.io/kubernetes/pkg/util
|
- k8s.io/kubernetes/pkg/util
|
||||||
- k8s.io/api/core/v1
|
- k8s.io/api/core/v1
|
||||||
|
|
||||||
|
@ -17,7 +17,9 @@ limitations under the License.
|
|||||||
package persistentvolume
|
package persistentvolume
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getClaimRefNamespace(pv *api.PersistentVolume) string {
|
func getClaimRefNamespace(pv *api.PersistentVolume) string {
|
||||||
@ -96,3 +98,11 @@ func VisitPVSecretNames(pv *api.PersistentVolume, visitor Visitor) bool {
|
|||||||
}
|
}
|
||||||
return true
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,7 +24,9 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPVSecrets(t *testing.T) {
|
func TestPVSecrets(t *testing.T) {
|
||||||
@ -204,3 +206,62 @@ func collectSecretPaths(t *testing.T, path *field.Path, name string, tp reflect.
|
|||||||
|
|
||||||
return secretPaths
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
37
pkg/api/persistentvolumeclaim/BUILD
Normal file
37
pkg/api/persistentvolumeclaim/BUILD
Normal 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",
|
||||||
|
],
|
||||||
|
)
|
4
pkg/api/persistentvolumeclaim/OWNERS
Executable file
4
pkg/api/persistentvolumeclaim/OWNERS
Executable file
@ -0,0 +1,4 @@
|
|||||||
|
reviewers:
|
||||||
|
- smarterclayton
|
||||||
|
- jsafrane
|
||||||
|
- david-mcmahon
|
31
pkg/api/persistentvolumeclaim/util.go
Normal file
31
pkg/api/persistentvolumeclaim/util.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
71
pkg/api/persistentvolumeclaim/util_test.go
Normal file
71
pkg/api/persistentvolumeclaim/util_test.go
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -243,12 +243,15 @@ func DropDisabledAlphaFields(podSpec *api.PodSpec) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range podSpec.Containers {
|
for i := range podSpec.Containers {
|
||||||
DropDisabledVolumeMountsAlphaFields(podSpec.Containers[i].VolumeMounts)
|
DropDisabledVolumeMountsAlphaFields(podSpec.Containers[i].VolumeMounts)
|
||||||
}
|
}
|
||||||
for i := range podSpec.InitContainers {
|
for i := range podSpec.InitContainers {
|
||||||
DropDisabledVolumeMountsAlphaFields(podSpec.InitContainers[i].VolumeMounts)
|
DropDisabledVolumeMountsAlphaFields(podSpec.InitContainers[i].VolumeMounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DropDisabledVolumeDevicesAlphaFields(podSpec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DropDisabledVolumeMountsAlphaFields removes disabled fields from []VolumeMount.
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -24,7 +24,9 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPodSecrets(t *testing.T) {
|
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")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -462,6 +462,11 @@ type PersistentVolumeSpec struct {
|
|||||||
// simply fail if one is invalid.
|
// simply fail if one is invalid.
|
||||||
// +optional
|
// +optional
|
||||||
MountOptions []string
|
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
|
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes
|
||||||
@ -479,6 +484,16 @@ const (
|
|||||||
PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain"
|
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 {
|
type PersistentVolumeStatus struct {
|
||||||
// Phase indicates if a volume is available, bound to a claim, or released by a claim
|
// Phase indicates if a volume is available, bound to a claim, or released by a claim
|
||||||
// +optional
|
// +optional
|
||||||
@ -548,6 +563,11 @@ type PersistentVolumeClaimSpec struct {
|
|||||||
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#class-1
|
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#class-1
|
||||||
// +optional
|
// +optional
|
||||||
StorageClassName *string
|
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
|
type PersistentVolumeClaimConditionType string
|
||||||
@ -1586,6 +1606,14 @@ const (
|
|||||||
MountPropagationBidirectional MountPropagationMode = "Bidirectional"
|
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.
|
// EnvVar represents an environment variable present in a Container.
|
||||||
type EnvVar struct {
|
type EnvVar struct {
|
||||||
// Required: This must be a C_IDENTIFIER.
|
// Required: This must be a C_IDENTIFIER.
|
||||||
@ -1879,6 +1907,10 @@ type Container struct {
|
|||||||
Resources ResourceRequirements
|
Resources ResourceRequirements
|
||||||
// +optional
|
// +optional
|
||||||
VolumeMounts []VolumeMount
|
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
|
// +optional
|
||||||
LivenessProbe *Probe
|
LivenessProbe *Probe
|
||||||
// +optional
|
// +optional
|
||||||
|
@ -20,6 +20,8 @@ import (
|
|||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
"k8s.io/kubernetes/pkg/util/parsers"
|
"k8s.io/kubernetes/pkg/util/parsers"
|
||||||
utilpointer "k8s.io/kubernetes/pkg/util/pointer"
|
utilpointer "k8s.io/kubernetes/pkg/util/pointer"
|
||||||
)
|
)
|
||||||
@ -228,11 +230,19 @@ func SetDefaults_PersistentVolume(obj *v1.PersistentVolume) {
|
|||||||
if obj.Spec.PersistentVolumeReclaimPolicy == "" {
|
if obj.Spec.PersistentVolumeReclaimPolicy == "" {
|
||||||
obj.Spec.PersistentVolumeReclaimPolicy = v1.PersistentVolumeReclaimRetain
|
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) {
|
func SetDefaults_PersistentVolumeClaim(obj *v1.PersistentVolumeClaim) {
|
||||||
if obj.Status.Phase == "" {
|
if obj.Status.Phase == "" {
|
||||||
obj.Status.Phase = v1.ClaimPending
|
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) {
|
func SetDefaults_ISCSIVolumeSource(obj *v1.ISCSIVolumeSource) {
|
||||||
if obj.ISCSIInterface == "" {
|
if obj.ISCSIInterface == "" {
|
||||||
|
@ -26,6 +26,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
corev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||||
|
|
||||||
@ -817,6 +818,33 @@ func TestSetDefaultPersistentVolume(t *testing.T) {
|
|||||||
if pv2.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimRetain {
|
if pv2.Spec.PersistentVolumeReclaimPolicy != v1.PersistentVolumeReclaimRetain {
|
||||||
t.Errorf("Expected pv reclaim policy %v, got %v", v1.PersistentVolumeReclaimRetain, pv2.Spec.PersistentVolumeReclaimPolicy)
|
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) {
|
func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
|
||||||
@ -827,6 +855,32 @@ func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
|
|||||||
if pvc2.Status.Phase != v1.ClaimPending {
|
if pvc2.Status.Phase != v1.ClaimPending {
|
||||||
t.Errorf("Expected claim phase %v, got %v", v1.ClaimPending, pvc2.Status.Phase)
|
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) {
|
func TestSetDefaulEndpointsProtocol(t *testing.T) {
|
||||||
|
@ -64,7 +64,7 @@ const isNotIntegerErrorMsg string = `must be an integer`
|
|||||||
const isNotPositiveErrorMsg string = `must be greater than zero`
|
const isNotPositiveErrorMsg string = `must be greater than zero`
|
||||||
|
|
||||||
var pdPartitionErrorMsg string = validation.InclusiveRangeError(1, 255)
|
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.
|
// BannedOwners is a black list of object that are not allowed to be owners.
|
||||||
var BannedOwners = apimachineryvalidation.BannedOwners
|
var BannedOwners = apimachineryvalidation.BannedOwners
|
||||||
@ -364,10 +364,11 @@ func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fld
|
|||||||
return apimachineryvalidation.ValidateNoNewFinalizers(newFinalizers, oldFinalizers, fldPath)
|
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{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
allNames := sets.String{}
|
allNames := sets.String{}
|
||||||
|
vols := make(map[string]core.VolumeSource)
|
||||||
for i, vol := range volumes {
|
for i, vol := range volumes {
|
||||||
idxPath := fldPath.Index(i)
|
idxPath := fldPath.Index(i)
|
||||||
namePath := idxPath.Child("name")
|
namePath := idxPath.Child("name")
|
||||||
@ -382,12 +383,69 @@ func ValidateVolumes(volumes []core.Volume, fldPath *field.Path) (sets.String, f
|
|||||||
}
|
}
|
||||||
if len(el) == 0 {
|
if len(el) == 0 {
|
||||||
allNames.Insert(vol.Name)
|
allNames.Insert(vol.Name)
|
||||||
|
vols[vol.Name] = vol.VolumeSource
|
||||||
} else {
|
} else {
|
||||||
allErrs = append(allErrs, el...)
|
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 {
|
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
|
secretMode := secretSource.DefaultMode
|
||||||
if secretMode != nil && (*secretMode > 0777 || *secretMode < 0) {
|
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")
|
itemsPath := fldPath.Child("items")
|
||||||
@ -764,7 +822,7 @@ func validateConfigMapVolumeSource(configMapSource *core.ConfigMapVolumeSource,
|
|||||||
|
|
||||||
configMapMode := configMapSource.DefaultMode
|
configMapMode := configMapSource.DefaultMode
|
||||||
if configMapMode != nil && (*configMapMode > 0777 || *configMapMode < 0) {
|
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")
|
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"))...)
|
allErrs = append(allErrs, validateLocalNonReservedPath(kp.Path, fldPath.Child("path"))...)
|
||||||
if kp.Mode != nil && (*kp.Mode > 0777 || *kp.Mode < 0) {
|
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
|
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"))
|
allErrs = append(allErrs, field.Required(fldPath, "one of fieldRef and resourceFieldRef is required"))
|
||||||
}
|
}
|
||||||
if file.Mode != nil && (*file.Mode > 0777 || *file.Mode < 0) {
|
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
|
return allErrs
|
||||||
@ -893,7 +951,7 @@ func validateDownwardAPIVolumeSource(downwardAPIVolume *core.DownwardAPIVolumeSo
|
|||||||
|
|
||||||
downwardAPIMode := downwardAPIVolume.DefaultMode
|
downwardAPIMode := downwardAPIVolume.DefaultMode
|
||||||
if downwardAPIMode != nil && (*downwardAPIMode > 0777 || *downwardAPIMode < 0) {
|
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 {
|
for _, file := range downwardAPIVolume.Items {
|
||||||
@ -983,7 +1041,7 @@ func validateProjectedVolumeSource(projection *core.ProjectedVolumeSource, fldPa
|
|||||||
|
|
||||||
projectionMode := projection.DefaultMode
|
projectionMode := projection.DefaultMode
|
||||||
if projectionMode != nil && (*projectionMode > 0777 || *projectionMode < 0) {
|
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)...)
|
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 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 {
|
func ValidatePersistentVolume(pv *core.PersistentVolume) field.ErrorList {
|
||||||
metaPath := field.NewPath("metadata")
|
metaPath := field.NewPath("metadata")
|
||||||
allErrs := ValidateObjectMeta(&pv.ObjectMeta, false, ValidatePersistentVolumeName, metaPath)
|
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))
|
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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1598,6 +1662,11 @@ func ValidatePersistentVolumeUpdate(newPv, oldPv *core.PersistentVolume) field.E
|
|||||||
}
|
}
|
||||||
|
|
||||||
newPv.Status = oldPv.Status
|
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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1646,6 +1715,11 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
|
|||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("storageClassName"), *spec.StorageClassName, msg))
|
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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1692,6 +1766,10 @@ func ValidatePersistentVolumeClaimUpdate(newPvc, oldPvc *core.PersistentVolumeCl
|
|||||||
// TODO: remove Beta when no longer needed
|
// 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"))...)
|
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
|
newPvc.Status = oldPvc.Status
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
@ -1975,7 +2053,27 @@ func validateSecretKeySelector(s *core.SecretKeySelector, fldPath *field.Path) f
|
|||||||
return allErrs
|
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{}
|
allErrs := field.ErrorList{}
|
||||||
mountpoints := sets.NewString()
|
mountpoints := sets.NewString()
|
||||||
|
|
||||||
@ -1983,7 +2081,8 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, volumes sets.String, contai
|
|||||||
idxPath := fldPath.Index(i)
|
idxPath := fldPath.Index(i)
|
||||||
if len(mnt.Name) == 0 {
|
if len(mnt.Name) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(idxPath.Child("name"), ""))
|
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))
|
allErrs = append(allErrs, field.NotFound(idxPath.Child("name"), mnt.Name))
|
||||||
}
|
}
|
||||||
if len(mnt.MountPath) == 0 {
|
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"))
|
allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique"))
|
||||||
}
|
}
|
||||||
mountpoints.Insert(mnt.MountPath)
|
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 {
|
if len(mnt.SubPath) > 0 {
|
||||||
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
|
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
|
||||||
}
|
}
|
||||||
@ -2004,6 +2112,60 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, volumes sets.String, contai
|
|||||||
return allErrs
|
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 {
|
func validateProbe(probe *core.Probe, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
@ -2187,10 +2349,10 @@ func validatePullPolicy(policy core.PullPolicy, fldPath *field.Path) field.Error
|
|||||||
return allErrors
|
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
|
var allErrs field.ErrorList
|
||||||
if len(containers) > 0 {
|
if len(containers) > 0 {
|
||||||
allErrs = append(allErrs, validateContainers(containers, volumes, fldPath)...)
|
allErrs = append(allErrs, validateContainers(containers, deviceVolumes, fldPath)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
allNames := sets.String{}
|
allNames := sets.String{}
|
||||||
@ -2218,7 +2380,7 @@ func validateInitContainers(containers, otherContainers []core.Container, volume
|
|||||||
return allErrs
|
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{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if len(containers) == 0 {
|
if len(containers) == 0 {
|
||||||
@ -2229,6 +2391,9 @@ func validateContainers(containers []core.Container, volumes sets.String, fldPat
|
|||||||
for i, ctr := range containers {
|
for i, ctr := range containers {
|
||||||
idxPath := fldPath.Index(i)
|
idxPath := fldPath.Index(i)
|
||||||
namePath := idxPath.Child("name")
|
namePath := idxPath.Child("name")
|
||||||
|
volMounts := GetVolumeMountMap(ctr.VolumeMounts)
|
||||||
|
volDevices := GetVolumeDeviceMap(ctr.VolumeDevices)
|
||||||
|
|
||||||
if len(ctr.Name) == 0 {
|
if len(ctr.Name) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(namePath, ""))
|
allErrs = append(allErrs, field.Required(namePath, ""))
|
||||||
} else {
|
} 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, validateContainerPorts(ctr.Ports, idxPath.Child("ports"))...)
|
||||||
allErrs = append(allErrs, ValidateEnv(ctr.Env, idxPath.Child("env"))...)
|
allErrs = append(allErrs, ValidateEnv(ctr.Env, idxPath.Child("env"))...)
|
||||||
allErrs = append(allErrs, ValidateEnvFrom(ctr.EnvFrom, idxPath.Child("envFrom"))...)
|
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, validatePullPolicy(ctr.ImagePullPolicy, idxPath.Child("imagePullPolicy"))...)
|
||||||
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"))...)
|
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"))...)
|
||||||
allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, idxPath.Child("securityContext"))...)
|
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 {
|
func ValidatePodSpec(spec *core.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := 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, vErrs...)
|
||||||
allErrs = append(allErrs, validateContainers(spec.Containers, allVolumes, fldPath.Child("containers"))...)
|
allErrs = append(allErrs, validateContainers(spec.Containers, vols, fldPath.Child("containers"))...)
|
||||||
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, allVolumes, fldPath.Child("initContainers"))...)
|
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"))...)
|
||||||
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
|
allErrs = append(allErrs, validateRestartPolicy(&spec.RestartPolicy, fldPath.Child("restartPolicy"))...)
|
||||||
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
|
allErrs = append(allErrs, validateDNSPolicy(&spec.DNSPolicy, fldPath.Child("dnsPolicy"))...)
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
||||||
|
@ -26,14 +26,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/api/resource"
|
"k8s.io/apimachinery/pkg/api/resource"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
_ "k8s.io/kubernetes/pkg/api/testapi"
|
_ "k8s.io/kubernetes/pkg/api/testapi"
|
||||||
"k8s.io/kubernetes/pkg/apis/core"
|
"k8s.io/kubernetes/pkg/apis/core"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
|
||||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||||
"k8s.io/kubernetes/pkg/capabilities"
|
"k8s.io/kubernetes/pkg/capabilities"
|
||||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
"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) {
|
func TestValidatePersistentVolumes(t *testing.T) {
|
||||||
|
validMode := core.PersistentVolumeFilesystem
|
||||||
scenarios := map[string]struct {
|
scenarios := map[string]struct {
|
||||||
isExpectedFailure bool
|
isExpectedFailure bool
|
||||||
volume *core.PersistentVolume
|
volume *core.PersistentVolume
|
||||||
@ -352,6 +351,25 @@ func TestValidatePersistentVolumes(t *testing.T) {
|
|||||||
StorageClassName: "-invalid-",
|
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
|
// LocalVolume alpha feature disabled
|
||||||
// TODO: remove when no longer alpha
|
// TODO: remove when no longer alpha
|
||||||
"alpha disabled valid local volume": {
|
"alpha disabled valid local volume": {
|
||||||
@ -434,38 +452,38 @@ func TestValidatePersistentVolumes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
func TestValidatePersistentVolumeSourceUpdate(t *testing.T) {
|
||||||
validVolume := testVolume("foo", "", api.PersistentVolumeSpec{
|
validVolume := testVolume("foo", "", core.PersistentVolumeSpec{
|
||||||
Capacity: api.ResourceList{
|
Capacity: core.ResourceList{
|
||||||
api.ResourceName(api.ResourceStorage): resource.MustParse("1G"),
|
core.ResourceName(core.ResourceStorage): resource.MustParse("1G"),
|
||||||
},
|
},
|
||||||
AccessModes: []api.PersistentVolumeAccessMode{api.ReadWriteOnce},
|
AccessModes: []core.PersistentVolumeAccessMode{core.ReadWriteOnce},
|
||||||
PersistentVolumeSource: api.PersistentVolumeSource{
|
PersistentVolumeSource: core.PersistentVolumeSource{
|
||||||
HostPath: &api.HostPathVolumeSource{
|
HostPath: &core.HostPathVolumeSource{
|
||||||
Path: "/foo",
|
Path: "/foo",
|
||||||
Type: newHostPathType(string(api.HostPathDirectory)),
|
Type: newHostPathType(string(core.HostPathDirectory)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
StorageClassName: "valid",
|
StorageClassName: "valid",
|
||||||
})
|
})
|
||||||
validPvSourceNoUpdate := validVolume.DeepCopy()
|
validPvSourceNoUpdate := validVolume.DeepCopy()
|
||||||
invalidPvSourceUpdateType := validVolume.DeepCopy()
|
invalidPvSourceUpdateType := validVolume.DeepCopy()
|
||||||
invalidPvSourceUpdateType.Spec.PersistentVolumeSource = api.PersistentVolumeSource{
|
invalidPvSourceUpdateType.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
|
||||||
FlexVolume: &api.FlexVolumeSource{
|
FlexVolume: &core.FlexVolumeSource{
|
||||||
Driver: "kubernetes.io/blue",
|
Driver: "kubernetes.io/blue",
|
||||||
FSType: "ext4",
|
FSType: "ext4",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
invalidPvSourceUpdateDeep := validVolume.DeepCopy()
|
invalidPvSourceUpdateDeep := validVolume.DeepCopy()
|
||||||
invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = api.PersistentVolumeSource{
|
invalidPvSourceUpdateDeep.Spec.PersistentVolumeSource = core.PersistentVolumeSource{
|
||||||
HostPath: &api.HostPathVolumeSource{
|
HostPath: &core.HostPathVolumeSource{
|
||||||
Path: "/updated",
|
Path: "/updated",
|
||||||
Type: newHostPathType(string(api.HostPathDirectory)),
|
Type: newHostPathType(string(core.HostPathDirectory)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
scenarios := map[string]struct {
|
scenarios := map[string]struct {
|
||||||
isExpectedFailure bool
|
isExpectedFailure bool
|
||||||
oldVolume *api.PersistentVolume
|
oldVolume *core.PersistentVolume
|
||||||
newVolume *api.PersistentVolume
|
newVolume *core.PersistentVolume
|
||||||
}{
|
}{
|
||||||
"condition-no-update": {
|
"condition-no-update": {
|
||||||
isExpectedFailure: false,
|
isExpectedFailure: false,
|
||||||
@ -720,6 +738,7 @@ func testVolumeClaimAnnotation(name string, namespace string, ann string, annval
|
|||||||
func TestValidatePersistentVolumeClaim(t *testing.T) {
|
func TestValidatePersistentVolumeClaim(t *testing.T) {
|
||||||
invalidClassName := "-invalid-"
|
invalidClassName := "-invalid-"
|
||||||
validClassName := "valid"
|
validClassName := "valid"
|
||||||
|
validMode := core.PersistentVolumeFilesystem
|
||||||
scenarios := map[string]struct {
|
scenarios := map[string]struct {
|
||||||
isExpectedFailure bool
|
isExpectedFailure bool
|
||||||
claim *core.PersistentVolumeClaim
|
claim *core.PersistentVolumeClaim
|
||||||
@ -888,7 +907,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
|
|||||||
},
|
},
|
||||||
Resources: core.ResourceRequirements{
|
Resources: core.ResourceRequirements{
|
||||||
Requests: core.ResourceList{
|
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,
|
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 {
|
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) {
|
func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
|
||||||
|
block := core.PersistentVolumeBlock
|
||||||
|
file := core.PersistentVolumeFilesystem
|
||||||
|
|
||||||
validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
|
validClaim := testVolumeClaimWithStatus("foo", "ns", core.PersistentVolumeClaimSpec{
|
||||||
AccessModes: []core.PersistentVolumeAccessMode{
|
AccessModes: []core.PersistentVolumeAccessMode{
|
||||||
core.ReadWriteOnce,
|
core.ReadWriteOnce,
|
||||||
@ -999,6 +1138,42 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
VolumeName: "volume",
|
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{
|
invalidUpdateClaimStorageClass := testVolumeClaimStorageClass("foo", "ns", "fast2", core.PersistentVolumeClaimSpec{
|
||||||
AccessModes: []core.PersistentVolumeAccessMode{
|
AccessModes: []core.PersistentVolumeAccessMode{
|
||||||
core.ReadOnlyMany,
|
core.ReadOnlyMany,
|
||||||
@ -1080,78 +1255,168 @@ func TestValidatePersistentVolumeClaimUpdate(t *testing.T) {
|
|||||||
oldClaim *core.PersistentVolumeClaim
|
oldClaim *core.PersistentVolumeClaim
|
||||||
newClaim *core.PersistentVolumeClaim
|
newClaim *core.PersistentVolumeClaim
|
||||||
enableResize bool
|
enableResize bool
|
||||||
|
enableBlock bool
|
||||||
}{
|
}{
|
||||||
"valid-update-volumeName-only": {
|
"valid-update-volumeName-only": {
|
||||||
isExpectedFailure: false,
|
isExpectedFailure: false,
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
newClaim: validUpdateClaim,
|
newClaim: validUpdateClaim,
|
||||||
enableResize: false,
|
enableResize: false,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"valid-no-op-update": {
|
"valid-no-op-update": {
|
||||||
isExpectedFailure: false,
|
isExpectedFailure: false,
|
||||||
oldClaim: validUpdateClaim,
|
oldClaim: validUpdateClaim,
|
||||||
newClaim: validUpdateClaim,
|
newClaim: validUpdateClaim,
|
||||||
enableResize: false,
|
enableResize: false,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"invalid-update-change-resources-on-bound-claim": {
|
"invalid-update-change-resources-on-bound-claim": {
|
||||||
isExpectedFailure: true,
|
isExpectedFailure: true,
|
||||||
oldClaim: validUpdateClaim,
|
oldClaim: validUpdateClaim,
|
||||||
newClaim: invalidUpdateClaimResources,
|
newClaim: invalidUpdateClaimResources,
|
||||||
enableResize: false,
|
enableResize: false,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"invalid-update-change-access-modes-on-bound-claim": {
|
"invalid-update-change-access-modes-on-bound-claim": {
|
||||||
isExpectedFailure: true,
|
isExpectedFailure: true,
|
||||||
oldClaim: validUpdateClaim,
|
oldClaim: validUpdateClaim,
|
||||||
newClaim: invalidUpdateClaimAccessModes,
|
newClaim: invalidUpdateClaimAccessModes,
|
||||||
enableResize: false,
|
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": {
|
"invalid-update-change-storage-class-annotation-after-creation": {
|
||||||
isExpectedFailure: true,
|
isExpectedFailure: true,
|
||||||
oldClaim: validClaimStorageClass,
|
oldClaim: validClaimStorageClass,
|
||||||
newClaim: invalidUpdateClaimStorageClass,
|
newClaim: invalidUpdateClaimStorageClass,
|
||||||
enableResize: false,
|
enableResize: false,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"valid-update-mutable-annotation": {
|
"valid-update-mutable-annotation": {
|
||||||
isExpectedFailure: false,
|
isExpectedFailure: false,
|
||||||
oldClaim: validClaimAnnotation,
|
oldClaim: validClaimAnnotation,
|
||||||
newClaim: validUpdateClaimMutableAnnotation,
|
newClaim: validUpdateClaimMutableAnnotation,
|
||||||
enableResize: false,
|
enableResize: false,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"valid-update-add-annotation": {
|
"valid-update-add-annotation": {
|
||||||
isExpectedFailure: false,
|
isExpectedFailure: false,
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
newClaim: validAddClaimAnnotation,
|
newClaim: validAddClaimAnnotation,
|
||||||
enableResize: false,
|
enableResize: false,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"valid-size-update-resize-disabled": {
|
"valid-size-update-resize-disabled": {
|
||||||
isExpectedFailure: true,
|
isExpectedFailure: true,
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
newClaim: validSizeUpdate,
|
newClaim: validSizeUpdate,
|
||||||
enableResize: false,
|
enableResize: false,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"valid-size-update-resize-enabled": {
|
"valid-size-update-resize-enabled": {
|
||||||
isExpectedFailure: false,
|
isExpectedFailure: false,
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
newClaim: validSizeUpdate,
|
newClaim: validSizeUpdate,
|
||||||
enableResize: true,
|
enableResize: true,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"invalid-size-update-resize-enabled": {
|
"invalid-size-update-resize-enabled": {
|
||||||
isExpectedFailure: true,
|
isExpectedFailure: true,
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
newClaim: invalidSizeUpdate,
|
newClaim: invalidSizeUpdate,
|
||||||
enableResize: true,
|
enableResize: true,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
"unbound-size-update-resize-enabled": {
|
"unbound-size-update-resize-enabled": {
|
||||||
isExpectedFailure: true,
|
isExpectedFailure: true,
|
||||||
oldClaim: validClaim,
|
oldClaim: validClaim,
|
||||||
newClaim: unboundSizeUpdate,
|
newClaim: unboundSizeUpdate,
|
||||||
enableResize: true,
|
enableResize: true,
|
||||||
|
enableBlock: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
// ensure we have a resource version specified for updates
|
// ensure we have a resource version specified for updates
|
||||||
togglePVExpandFeature(scenario.enableResize, t)
|
togglePVExpandFeature(scenario.enableResize, t)
|
||||||
|
toggleBlockVolumeFeature(scenario.enableBlock, t)
|
||||||
scenario.oldClaim.ResourceVersion = "1"
|
scenario.oldClaim.ResourceVersion = "1"
|
||||||
scenario.newClaim.ResourceVersion = "1"
|
scenario.newClaim.ResourceVersion = "1"
|
||||||
errs := ValidatePersistentVolumeClaimUpdate(scenario.newClaim, scenario.oldClaim)
|
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) {
|
func togglePVExpandFeature(toggleFlag bool, t *testing.T) {
|
||||||
if toggleFlag {
|
if toggleFlag {
|
||||||
// Enable alpha feature LocalStorageCapacityIsolation
|
// Enable alpha feature LocalStorageCapacityIsolation
|
||||||
@ -1356,27 +1638,27 @@ func TestValidateGlusterfs(t *testing.T) {
|
|||||||
func TestValidateCSIVolumeSource(t *testing.T) {
|
func TestValidateCSIVolumeSource(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
csi *api.CSIPersistentVolumeSource
|
csi *core.CSIPersistentVolumeSource
|
||||||
errtype field.ErrorType
|
errtype field.ErrorType
|
||||||
errfield string
|
errfield string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "all required fields ok",
|
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",
|
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",
|
name: "missing driver name",
|
||||||
csi: &api.CSIPersistentVolumeSource{VolumeHandle: "test-123"},
|
csi: &core.CSIPersistentVolumeSource{VolumeHandle: "test-123"},
|
||||||
errtype: field.ErrorTypeRequired,
|
errtype: field.ErrorTypeRequired,
|
||||||
errfield: "driver",
|
errfield: "driver",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing volume handle",
|
name: "missing volume handle",
|
||||||
csi: &api.CSIPersistentVolumeSource{Driver: "my-driver"},
|
csi: &core.CSIPersistentVolumeSource{Driver: "my-driver"},
|
||||||
errtype: field.ErrorTypeRequired,
|
errtype: field.ErrorTypeRequired,
|
||||||
errfield: "volumeHandle",
|
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)
|
t.Errorf("[%d: %q] expected error detail %q, got %q", i, tc.name, tc.errdetail, errs[0].Detail)
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
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) {
|
func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
|
||||||
|
|
||||||
testCases := []core.VolumeSource{
|
testCases := []core.VolumeSource{
|
||||||
@ -3881,7 +4310,16 @@ func TestValidateEnvFrom(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateVolumeMounts(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{
|
container := core.Container{
|
||||||
SecurityContext: nil,
|
SecurityContext: nil,
|
||||||
}
|
}
|
||||||
@ -3899,7 +4337,11 @@ func TestValidateVolumeMounts(t *testing.T) {
|
|||||||
{Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
|
{Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
|
||||||
{Name: "abc-123", MountPath: "/bad", 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)
|
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 contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
|
||||||
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
|
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
|
||||||
"disabled MountPropagation feature gate": {{Name: "abc", MountPath: "/bar", MountPropagation: &propagation}},
|
"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 {
|
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)
|
t.Errorf("expected failure for %s", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4033,9 +4482,16 @@ func TestValidateMountPropagation(t *testing.T) {
|
|||||||
return
|
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 {
|
for i, test := range tests {
|
||||||
volumes := sets.NewString("foo")
|
errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, nil, vols2, test.container, field.NewPath("field"))
|
||||||
errs := ValidateVolumeMounts([]core.VolumeMount{test.mount}, volumes, test.container, field.NewPath("field"))
|
|
||||||
if test.expectError && len(errs) == 0 {
|
if test.expectError && len(errs) == 0 {
|
||||||
t.Errorf("test %d expected error, got none", i)
|
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)
|
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) {
|
func TestValidateProbe(t *testing.T) {
|
||||||
@ -4158,7 +4688,7 @@ func getResourceLimits(cpu, memory string) core.ResourceList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestValidateContainers(t *testing.T) {
|
func TestValidateContainers(t *testing.T) {
|
||||||
volumes := sets.String{}
|
volumeDevices := make(map[string]core.VolumeSource)
|
||||||
capabilities.SetForTests(capabilities.Capabilities{
|
capabilities.SetForTests(capabilities.Capabilities{
|
||||||
AllowPrivileged: true,
|
AllowPrivileged: true,
|
||||||
})
|
})
|
||||||
@ -4328,7 +4858,7 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", SecurityContext: fakeValidSecurityContext(true)},
|
{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)
|
t.Errorf("expected success: %v", errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4590,7 +5120,7 @@ func TestValidateContainers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
for k, v := range errorCases {
|
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)
|
t.Errorf("expected failure for %s", k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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"))
|
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, vErrs...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateEnv(spec.Env, fldPath.Child("env"))...)
|
allErrs = append(allErrs, apivalidation.ValidateEnv(spec.Env, fldPath.Child("env"))...)
|
||||||
allErrs = append(allErrs, apivalidation.ValidateEnvFrom(spec.EnvFrom, fldPath.Child("envFrom"))...)
|
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
|
return allErrs
|
||||||
}
|
}
|
||||||
|
@ -187,6 +187,12 @@ const (
|
|||||||
//
|
//
|
||||||
// Enable mount/attachment of Container Storage Interface (CSI) backed PVs
|
// Enable mount/attachment of Container Storage Interface (CSI) backed PVs
|
||||||
CSIPersistentVolume utilfeature.Feature = "CSIPersistentVolume"
|
CSIPersistentVolume utilfeature.Feature = "CSIPersistentVolume"
|
||||||
|
|
||||||
|
// owner: @screeley44
|
||||||
|
// alpha: v1.9
|
||||||
|
//
|
||||||
|
// Enable Block volume support in containers.
|
||||||
|
BlockVolume utilfeature.Feature = "BlockVolume"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -222,6 +228,7 @@ var defaultKubernetesFeatureGates = map[utilfeature.Feature]utilfeature.FeatureS
|
|||||||
MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
|
MountContainers: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha},
|
VolumeScheduling: {Default: false, PreRelease: utilfeature.Alpha},
|
||||||
CSIPersistentVolume: {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
|
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||||
// unintentionally on either side:
|
// unintentionally on either side:
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
pvutil "k8s.io/kubernetes/pkg/api/persistentvolume"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
volumevalidation "k8s.io/kubernetes/pkg/volume/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) {
|
func (persistentvolumeStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
|
||||||
pv := obj.(*api.PersistentVolume)
|
pv := obj.(*api.PersistentVolume)
|
||||||
pv.Status = api.PersistentVolumeStatus{}
|
pv.Status = api.PersistentVolumeStatus{}
|
||||||
|
|
||||||
|
pvutil.DropDisabledAlphaFields(&pv.Spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (persistentvolumeStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
|
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)
|
newPv := obj.(*api.PersistentVolume)
|
||||||
oldPv := old.(*api.PersistentVolume)
|
oldPv := old.(*api.PersistentVolume)
|
||||||
newPv.Status = oldPv.Status
|
newPv.Status = oldPv.Status
|
||||||
|
|
||||||
|
pvutil.DropDisabledAlphaFields(&newPv.Spec)
|
||||||
|
pvutil.DropDisabledAlphaFields(&oldPv.Spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (persistentvolumeStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
|
func (persistentvolumeStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
|
@ -28,6 +28,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/storage"
|
"k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
|
pvcutil "k8s.io/kubernetes/pkg/api/persistentvolumeclaim"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"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.
|
// 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) {
|
func (persistentvolumeclaimStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
|
||||||
pv := obj.(*api.PersistentVolumeClaim)
|
pvc := obj.(*api.PersistentVolumeClaim)
|
||||||
pv.Status = api.PersistentVolumeClaimStatus{}
|
pvc.Status = api.PersistentVolumeClaimStatus{}
|
||||||
|
|
||||||
|
pvcutil.DropDisabledAlphaFields(&pvc.Spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (persistentvolumeclaimStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
|
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)
|
newPvc := obj.(*api.PersistentVolumeClaim)
|
||||||
oldPvc := old.(*api.PersistentVolumeClaim)
|
oldPvc := old.(*api.PersistentVolumeClaim)
|
||||||
newPvc.Status = oldPvc.Status
|
newPvc.Status = oldPvc.Status
|
||||||
|
|
||||||
|
pvcutil.DropDisabledAlphaFields(&newPvc.Spec)
|
||||||
|
pvcutil.DropDisabledAlphaFields(&oldPvc.Spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (persistentvolumeclaimStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
|
func (persistentvolumeclaimStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
|
@ -527,6 +527,11 @@ type PersistentVolumeSpec struct {
|
|||||||
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options
|
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options
|
||||||
// +optional
|
// +optional
|
||||||
MountOptions []string `json:"mountOptions,omitempty" protobuf:"bytes,7,opt,name=mountOptions"`
|
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.
|
// PersistentVolumeReclaimPolicy describes a policy for end-of-life maintenance of persistent volumes.
|
||||||
@ -544,6 +549,16 @@ const (
|
|||||||
PersistentVolumeReclaimRetain PersistentVolumeReclaimPolicy = "Retain"
|
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.
|
// PersistentVolumeStatus is the current status of a persistent volume.
|
||||||
type PersistentVolumeStatus struct {
|
type PersistentVolumeStatus struct {
|
||||||
// Phase indicates if a volume is available, bound to a claim, or released by a claim.
|
// 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
|
// More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1
|
||||||
// +optional
|
// +optional
|
||||||
StorageClassName *string `json:"storageClassName,omitempty" protobuf:"bytes,5,opt,name=storageClassName"`
|
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
|
// PersistentVolumeClaimConditionType is a valid value of PersistentVolumeClaimCondition.Type
|
||||||
@ -1709,6 +1729,14 @@ const (
|
|||||||
MountPropagationBidirectional MountPropagationMode = "Bidirectional"
|
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.
|
// EnvVar represents an environment variable present in a Container.
|
||||||
type EnvVar struct {
|
type EnvVar struct {
|
||||||
// Name of the environment variable. Must be a C_IDENTIFIER.
|
// Name of the environment variable. Must be a C_IDENTIFIER.
|
||||||
@ -2052,6 +2080,12 @@ type Container struct {
|
|||||||
// +patchMergeKey=mountPath
|
// +patchMergeKey=mountPath
|
||||||
// +patchStrategy=merge
|
// +patchStrategy=merge
|
||||||
VolumeMounts []VolumeMount `json:"volumeMounts,omitempty" patchStrategy:"merge" patchMergeKey:"mountPath" protobuf:"bytes,9,rep,name=volumeMounts"`
|
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.
|
// Periodic probe of container liveness.
|
||||||
// Container will be restarted if the probe fails.
|
// Container will be restarted if the probe fails.
|
||||||
// Cannot be updated.
|
// Cannot be updated.
|
||||||
|
Loading…
Reference in New Issue
Block a user