Merge pull request #92784 from pohly/generic-ephemeral-inline-volumes

generic ephemeral inline volumes
This commit is contained in:
Kubernetes Prow Robot 2020-07-10 15:41:46 -07:00 committed by GitHub
commit 0cb7e320a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
128 changed files with 30161 additions and 24886 deletions

View File

@ -6278,6 +6278,20 @@
], ],
"type": "object" "type": "object"
}, },
"io.k8s.api.core.v1.EphemeralVolumeSource": {
"description": "Represents an ephemeral volume that is handled by a normal storage driver.",
"properties": {
"readOnly": {
"description": "Specifies a read-only configuration for the volume. Defaults to false (read/write).",
"type": "boolean"
},
"volumeClaimTemplate": {
"$ref": "#/definitions/io.k8s.api.core.v1.PersistentVolumeClaimTemplate",
"description": "Will be used to create a stand-alone PVC to provision the volume. The pod in which this EphemeralVolumeSource is embedded will be the owner of the PVC, i.e. the PVC will be deleted together with the pod. The name of the PVC will be `<pod name>-<volume name>` where `<volume name>` is the name from the `PodSpec.Volumes` array entry. Pod validation will reject the pod if the concatenated name is not valid for a PVC (for example, too long).\n\nAn existing PVC with that name that is not owned by the pod will *not* be used for the pod to avoid using an unrelated volume by mistake. Starting the pod is then blocked until the unrelated PVC is removed. If such a pre-created PVC is meant to be used by the pod, the PVC has to updated with an owner reference to the pod once the pod exists. Normally this should not be necessary, but it may be useful when manually reconstructing a broken cluster.\n\nThis field is read-only and no changes will be made by Kubernetes to the PVC after it has been created.\n\nRequired, must not be nil."
}
},
"type": "object"
},
"io.k8s.api.core.v1.Event": { "io.k8s.api.core.v1.Event": {
"description": "Event is a report of an event somewhere in the cluster.", "description": "Event is a report of an event somewhere in the cluster.",
"properties": { "properties": {
@ -7903,6 +7917,23 @@
}, },
"type": "object" "type": "object"
}, },
"io.k8s.api.core.v1.PersistentVolumeClaimTemplate": {
"description": "PersistentVolumeClaimTemplate is used to produce PersistentVolumeClaim objects as part of an EphemeralVolumeSource.",
"properties": {
"metadata": {
"$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta",
"description": "May contain labels and annotations that will be copied into the PVC when creating it. No other fields are allowed and will be rejected during validation."
},
"spec": {
"$ref": "#/definitions/io.k8s.api.core.v1.PersistentVolumeClaimSpec",
"description": "The specification for the PersistentVolumeClaim. The entire content is copied unchanged into the PVC that gets created from this template. The same fields as in a PersistentVolumeClaim are also valid here."
}
},
"required": [
"spec"
],
"type": "object"
},
"io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource": { "io.k8s.api.core.v1.PersistentVolumeClaimVolumeSource": {
"description": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).", "description": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).",
"properties": { "properties": {
@ -10313,6 +10344,10 @@
"$ref": "#/definitions/io.k8s.api.core.v1.EmptyDirVolumeSource", "$ref": "#/definitions/io.k8s.api.core.v1.EmptyDirVolumeSource",
"description": "EmptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir" "description": "EmptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir"
}, },
"ephemeral": {
"$ref": "#/definitions/io.k8s.api.core.v1.EphemeralVolumeSource",
"description": "Ephemeral represents a volume that is handled by a cluster storage driver (Alpha feature). The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed.\n\nUse this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information.\n\nA pod can use both types of ephemeral volumes and persistent volumes at the same time."
},
"fc": { "fc": {
"$ref": "#/definitions/io.k8s.api.core.v1.FCVolumeSource", "$ref": "#/definitions/io.k8s.api.core.v1.FCVolumeSource",
"description": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod." "description": "FC represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod."

View File

@ -75,6 +75,7 @@ go_library(
"//pkg/controller/ttl:go_default_library", "//pkg/controller/ttl:go_default_library",
"//pkg/controller/ttlafterfinished:go_default_library", "//pkg/controller/ttlafterfinished:go_default_library",
"//pkg/controller/volume/attachdetach:go_default_library", "//pkg/controller/volume/attachdetach:go_default_library",
"//pkg/controller/volume/ephemeral:go_default_library",
"//pkg/controller/volume/expand:go_default_library", "//pkg/controller/volume/expand:go_default_library",
"//pkg/controller/volume/persistentvolume:go_default_library", "//pkg/controller/volume/persistentvolume:go_default_library",
"//pkg/controller/volume/persistentvolume/config:go_default_library", "//pkg/controller/volume/persistentvolume/config:go_default_library",

View File

@ -424,6 +424,7 @@ func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc
controllers["pv-protection"] = startPVProtectionController controllers["pv-protection"] = startPVProtectionController
controllers["ttl-after-finished"] = startTTLAfterFinishedController controllers["ttl-after-finished"] = startTTLAfterFinishedController
controllers["root-ca-cert-publisher"] = startRootCACertPublisher controllers["root-ca-cert-publisher"] = startRootCACertPublisher
controllers["ephemeral-volume"] = startEphemeralVolumeController
return controllers return controllers
} }

View File

@ -57,6 +57,7 @@ import (
ttlcontroller "k8s.io/kubernetes/pkg/controller/ttl" ttlcontroller "k8s.io/kubernetes/pkg/controller/ttl"
"k8s.io/kubernetes/pkg/controller/ttlafterfinished" "k8s.io/kubernetes/pkg/controller/ttlafterfinished"
"k8s.io/kubernetes/pkg/controller/volume/attachdetach" "k8s.io/kubernetes/pkg/controller/volume/attachdetach"
"k8s.io/kubernetes/pkg/controller/volume/ephemeral"
"k8s.io/kubernetes/pkg/controller/volume/expand" "k8s.io/kubernetes/pkg/controller/volume/expand"
persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume" persistentvolumecontroller "k8s.io/kubernetes/pkg/controller/volume/persistentvolume"
"k8s.io/kubernetes/pkg/controller/volume/pvcprotection" "k8s.io/kubernetes/pkg/controller/volume/pvcprotection"
@ -373,6 +374,22 @@ func startVolumeExpandController(ctx ControllerContext) (http.Handler, bool, err
return nil, false, nil return nil, false, nil
} }
func startEphemeralVolumeController(ctx ControllerContext) (http.Handler, bool, error) {
if utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
ephemeralController, err := ephemeral.NewController(
ctx.ClientBuilder.ClientOrDie("ephemeral-volume-controller"),
ctx.InformerFactory.Core().V1().Pods(),
ctx.InformerFactory.Core().V1().PersistentVolumeClaims())
if err != nil {
return nil, true, fmt.Errorf("failed to start ephemeral volume controller: %v", err)
}
// TODO (before beta at the latest): make this configurable similar to the EndpointController
go ephemeralController.Run(1 /* int(ctx.ComponentConfig.EphemeralController.ConcurrentEphemeralVolumeSyncs) */, ctx.Stop)
return nil, true, nil
}
return nil, false, nil
}
func startEndpointController(ctx ControllerContext) (http.Handler, bool, error) { func startEndpointController(ctx ControllerContext) (http.Handler, bool, error) {
go endpointcontroller.NewEndpointController( go endpointcontroller.NewEndpointController(
ctx.InformerFactory.Core().V1().Pods(), ctx.InformerFactory.Core().V1().Pods(),
@ -539,6 +556,7 @@ func startPVCProtectionController(ctx ControllerContext) (http.Handler, bool, er
ctx.InformerFactory.Core().V1().Pods(), ctx.InformerFactory.Core().V1().Pods(),
ctx.ClientBuilder.ClientOrDie("pvc-protection-controller"), ctx.ClientBuilder.ClientOrDie("pvc-protection-controller"),
utilfeature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection), utilfeature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection),
utilfeature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection),
) )
if err != nil { if err != nil {
return nil, true, fmt.Errorf("failed to start the pvc protection controller: %v", err) return nil, true, fmt.Errorf("failed to start the pvc protection controller: %v", err)

View File

@ -431,6 +431,7 @@ func dropDisabledFields(
dropDisabledProcMountField(podSpec, oldPodSpec) dropDisabledProcMountField(podSpec, oldPodSpec)
dropDisabledCSIVolumeSourceAlphaFields(podSpec, oldPodSpec) dropDisabledCSIVolumeSourceAlphaFields(podSpec, oldPodSpec)
dropDisabledEphemeralVolumeSourceAlphaFields(podSpec, oldPodSpec)
if !utilfeature.DefaultFeatureGate.Enabled(features.NonPreemptingPriority) && if !utilfeature.DefaultFeatureGate.Enabled(features.NonPreemptingPriority) &&
!podPriorityInUse(oldPodSpec) { !podPriorityInUse(oldPodSpec) {
@ -499,6 +500,16 @@ func dropDisabledCSIVolumeSourceAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
} }
} }
// dropDisabledEphemeralVolumeSourceAlphaFields removes disabled alpha fields from []EphemeralVolumeSource.
// This should be called from PrepareForCreate/PrepareForUpdate for all pod specs resources containing a EphemeralVolumeSource
func dropDisabledEphemeralVolumeSourceAlphaFields(podSpec, oldPodSpec *api.PodSpec) {
if !utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) && !csiInUse(oldPodSpec) {
for i := range podSpec.Volumes {
podSpec.Volumes[i].Ephemeral = nil
}
}
}
func ephemeralContainersInUse(podSpec *api.PodSpec) bool { func ephemeralContainersInUse(podSpec *api.PodSpec) bool {
if podSpec == nil { if podSpec == nil {
return false return false

View File

@ -159,7 +159,7 @@ func TestCompatibility_v1_PodSecurityContext(t *testing.T) {
} }
validator := func(obj runtime.Object) field.ErrorList { validator := func(obj runtime.Object) field.ErrorList {
return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), field.NewPath("spec")) return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), &(obj.(*api.Pod).ObjectMeta), field.NewPath("spec"))
} }
for _, tc := range cases { for _, tc := range cases {

View File

@ -94,6 +94,13 @@ func SetObjectDefaults_DaemonSet(in *v1.DaemonSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
corev1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -301,6 +308,13 @@ func SetObjectDefaults_Deployment(in *v1.Deployment) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
corev1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -508,6 +522,13 @@ func SetObjectDefaults_ReplicaSet(in *v1.ReplicaSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
corev1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -715,6 +736,13 @@ func SetObjectDefaults_StatefulSet(in *v1.StatefulSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
corev1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -863,6 +891,7 @@ func SetObjectDefaults_StatefulSet(in *v1.StatefulSet) {
for i := range in.Spec.VolumeClaimTemplates { for i := range in.Spec.VolumeClaimTemplates {
a := &in.Spec.VolumeClaimTemplates[i] a := &in.Spec.VolumeClaimTemplates[i]
corev1.SetDefaults_PersistentVolumeClaim(a) corev1.SetDefaults_PersistentVolumeClaim(a)
corev1.SetDefaults_PersistentVolumeClaimSpec(&a.Spec)
corev1.SetDefaults_ResourceList(&a.Spec.Resources.Limits) corev1.SetDefaults_ResourceList(&a.Spec.Resources.Limits)
corev1.SetDefaults_ResourceList(&a.Spec.Resources.Requests) corev1.SetDefaults_ResourceList(&a.Spec.Resources.Requests)
corev1.SetDefaults_ResourceList(&a.Status.Capacity) corev1.SetDefaults_ResourceList(&a.Status.Capacity)

View File

@ -90,6 +90,13 @@ func SetObjectDefaults_Deployment(in *v1beta1.Deployment) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -297,6 +304,13 @@ func SetObjectDefaults_StatefulSet(in *v1beta1.StatefulSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -445,6 +459,7 @@ func SetObjectDefaults_StatefulSet(in *v1beta1.StatefulSet) {
for i := range in.Spec.VolumeClaimTemplates { for i := range in.Spec.VolumeClaimTemplates {
a := &in.Spec.VolumeClaimTemplates[i] a := &in.Spec.VolumeClaimTemplates[i]
v1.SetDefaults_PersistentVolumeClaim(a) v1.SetDefaults_PersistentVolumeClaim(a)
v1.SetDefaults_PersistentVolumeClaimSpec(&a.Spec)
v1.SetDefaults_ResourceList(&a.Spec.Resources.Limits) v1.SetDefaults_ResourceList(&a.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.Spec.Resources.Requests) v1.SetDefaults_ResourceList(&a.Spec.Resources.Requests)
v1.SetDefaults_ResourceList(&a.Status.Capacity) v1.SetDefaults_ResourceList(&a.Status.Capacity)

View File

@ -94,6 +94,13 @@ func SetObjectDefaults_DaemonSet(in *v1beta2.DaemonSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -301,6 +308,13 @@ func SetObjectDefaults_Deployment(in *v1beta2.Deployment) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -508,6 +522,13 @@ func SetObjectDefaults_ReplicaSet(in *v1beta2.ReplicaSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -715,6 +736,13 @@ func SetObjectDefaults_StatefulSet(in *v1beta2.StatefulSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -863,6 +891,7 @@ func SetObjectDefaults_StatefulSet(in *v1beta2.StatefulSet) {
for i := range in.Spec.VolumeClaimTemplates { for i := range in.Spec.VolumeClaimTemplates {
a := &in.Spec.VolumeClaimTemplates[i] a := &in.Spec.VolumeClaimTemplates[i]
v1.SetDefaults_PersistentVolumeClaim(a) v1.SetDefaults_PersistentVolumeClaim(a)
v1.SetDefaults_PersistentVolumeClaimSpec(&a.Spec)
v1.SetDefaults_ResourceList(&a.Spec.Resources.Limits) v1.SetDefaults_ResourceList(&a.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.Spec.Resources.Requests) v1.SetDefaults_ResourceList(&a.Spec.Resources.Requests)
v1.SetDefaults_ResourceList(&a.Status.Capacity) v1.SetDefaults_ResourceList(&a.Status.Capacity)

View File

@ -88,6 +88,13 @@ func SetObjectDefaults_Job(in *v1.Job) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) corev1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
corev1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
corev1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]

View File

@ -89,6 +89,13 @@ func SetObjectDefaults_CronJob(in *v1beta1.CronJob) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.JobTemplate.Spec.Template.Spec.InitContainers { for i := range in.Spec.JobTemplate.Spec.Template.Spec.InitContainers {
a := &in.Spec.JobTemplate.Spec.Template.Spec.InitContainers[i] a := &in.Spec.JobTemplate.Spec.Template.Spec.InitContainers[i]
@ -295,6 +302,13 @@ func SetObjectDefaults_JobTemplate(in *v1beta1.JobTemplate) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Template.Spec.Template.Spec.InitContainers { for i := range in.Template.Spec.Template.Spec.InitContainers {
a := &in.Template.Spec.Template.Spec.InitContainers[i] a := &in.Template.Spec.Template.Spec.InitContainers[i]

View File

@ -89,6 +89,13 @@ func SetObjectDefaults_CronJob(in *v2alpha1.CronJob) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.JobTemplate.Spec.Template.Spec.InitContainers { for i := range in.Spec.JobTemplate.Spec.Template.Spec.InitContainers {
a := &in.Spec.JobTemplate.Spec.Template.Spec.InitContainers[i] a := &in.Spec.JobTemplate.Spec.Template.Spec.InitContainers[i]
@ -295,6 +302,13 @@ func SetObjectDefaults_JobTemplate(in *v2alpha1.JobTemplate) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Template.Spec.Template.Spec.InitContainers { for i := range in.Template.Spec.Template.Spec.InitContainers {
a := &in.Template.Spec.Template.Spec.InitContainers[i] a := &in.Template.Spec.Template.Spec.InitContainers[i]

View File

@ -263,6 +263,14 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
i.ISCSIInterface = "default" i.ISCSIInterface = "default"
} }
}, },
func(i *core.PersistentVolumeClaimSpec, c fuzz.Continue) {
// Match defaulting in pkg/apis/core/v1/defaults.go.
volumeMode := core.PersistentVolumeMode(c.RandString())
if volumeMode == "" {
volumeMode = core.PersistentVolumeFilesystem
}
i.VolumeMode = &volumeMode
},
func(d *core.DNSPolicy, c fuzz.Continue) { func(d *core.DNSPolicy, c fuzz.Continue) {
policies := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault} policies := []core.DNSPolicy{core.DNSClusterFirst, core.DNSDefault}
*d = policies[c.Rand.Intn(len(policies))] *d = policies[c.Rand.Intn(len(policies))]

View File

@ -157,6 +157,33 @@ type VolumeSource struct {
// CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). // CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).
// +optional // +optional
CSI *CSIVolumeSource CSI *CSIVolumeSource
// Ephemeral represents a volume that is handled by a cluster storage driver (Alpha feature).
// The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts,
// and deleted when the pod is removed.
//
// Use this if:
// a) the volume is only needed while the pod runs,
// b) features of normal volumes like restoring from snapshot or capacity
// tracking are needed,
// c) the storage driver is specified through a storage class, and
// d) the storage driver supports dynamic volume provisioning through
// a PersistentVolumeClaim (see EphemeralVolumeSource for more
// information on the connection between this volume type
// and PersistentVolumeClaim).
//
// Use PersistentVolumeClaim or one of the vendor-specific
// APIs for volumes that persist for longer than the lifecycle
// of an individual pod.
//
// Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to
// be used that way - see the documentation of the driver for
// more information.
//
// A pod can use both types of ephemeral volumes and
// persistent volumes at the same time.
//
// +optional
Ephemeral *EphemeralVolumeSource
} }
// PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs. // PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs.
@ -1670,6 +1697,53 @@ type CSIVolumeSource struct {
NodePublishSecretRef *LocalObjectReference NodePublishSecretRef *LocalObjectReference
} }
// EphemeralVolumeSource represents an ephemeral volume that is handled by a normal storage driver.
type EphemeralVolumeSource struct {
// VolumeClaimTemplate will be used to create a stand-alone PVC to provision the volume.
// The pod in which this EphemeralVolumeSource is embedded will be the
// owner of the PVC, i.e. the PVC will be deleted together with the
// pod. The name of the PVC will be `<pod name>-<volume name>` where
// `<volume name>` is the name from the `PodSpec.Volumes` array
// entry. Pod validation will reject the pod if the concatenated name
// is not valid for a PVC (for example, too long).
//
// An existing PVC with that name that is not owned by the pod
// will *not* be used for the pod to avoid using an unrelated
// volume by mistake. Starting the pod is then blocked until
// the unrelated PVC is removed. If such a pre-created PVC is
// meant to be used by the pod, the PVC has to updated with an
// owner reference to the pod once the pod exists. Normally
// this should not be necessary, but it may be useful when
// manually reconstructing a broken cluster.
//
// This field is read-only and no changes will be made by Kubernetes
// to the PVC after it has been created.
//
// Required, must not be nil.
VolumeClaimTemplate *PersistentVolumeClaimTemplate
// ReadOnly specifies a read-only configuration for the volume.
// Defaults to false (read/write).
// +optional
ReadOnly bool
}
// PersistentVolumeClaimTemplate is used to produce
// PersistentVolumeClaim objects as part of an EphemeralVolumeSource.
type PersistentVolumeClaimTemplate struct {
// ObjectMeta may contain labels and annotations that will be copied into the PVC
// when creating it. No other fields are allowed and will be rejected during
// validation.
// +optional
metav1.ObjectMeta
// Spec for the PersistentVolumeClaim. The entire content is
// copied unchanged into the PVC that gets created from this
// template. The same fields as in a PersistentVolumeClaim
// are also valid here.
Spec PersistentVolumeClaimSpec
}
// ContainerPort represents a network port in a single container // ContainerPort represents a network port in a single container
type ContainerPort struct { type ContainerPort struct {
// Optional: If specified, this must be an IANA_SVC_NAME Each named port // Optional: If specified, this must be an IANA_SVC_NAME Each named port

View File

@ -285,9 +285,11 @@ 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 { }
obj.Spec.VolumeMode = new(v1.PersistentVolumeMode) func SetDefaults_PersistentVolumeClaimSpec(obj *v1.PersistentVolumeClaimSpec) {
*obj.Spec.VolumeMode = v1.PersistentVolumeFilesystem if obj.VolumeMode == nil {
obj.VolumeMode = new(v1.PersistentVolumeMode)
*obj.VolumeMode = v1.PersistentVolumeFilesystem
} }
} }
func SetDefaults_ISCSIVolumeSource(obj *v1.ISCSIVolumeSource) { func SetDefaults_ISCSIVolumeSource(obj *v1.ISCSIVolumeSource) {

View File

@ -142,6 +142,7 @@ func TestWorkloadDefaults(t *testing.T) {
".Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode": `420`, ".Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode": `420`,
".Spec.Volumes[0].VolumeSource.DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`, ".Spec.Volumes[0].VolumeSource.DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
".Spec.Volumes[0].VolumeSource.EmptyDir": `{}`, ".Spec.Volumes[0].VolumeSource.EmptyDir": `{}`,
".Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode": `"Filesystem"`,
".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`, ".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`,
".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`, ".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`,
".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`, ".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`,
@ -265,6 +266,7 @@ func TestPodDefaults(t *testing.T) {
".Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode": `420`, ".Spec.Volumes[0].VolumeSource.DownwardAPI.DefaultMode": `420`,
".Spec.Volumes[0].VolumeSource.DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`, ".Spec.Volumes[0].VolumeSource.DownwardAPI.Items[0].FieldRef.APIVersion": `"v1"`,
".Spec.Volumes[0].VolumeSource.EmptyDir": `{}`, ".Spec.Volumes[0].VolumeSource.EmptyDir": `{}`,
".Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode": `"Filesystem"`,
".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`, ".Spec.Volumes[0].VolumeSource.HostPath.Type": `""`,
".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`, ".Spec.Volumes[0].VolumeSource.ISCSI.ISCSIInterface": `"default"`,
".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`, ".Spec.Volumes[0].VolumeSource.Projected.DefaultMode": `420`,
@ -1375,6 +1377,58 @@ func TestSetDefaultPersistentVolumeClaim(t *testing.T) {
} }
} }
func TestSetDefaultEphemeral(t *testing.T) {
fsMode := v1.PersistentVolumeFilesystem
blockMode := v1.PersistentVolumeBlock
tests := []struct {
name string
volumeMode *v1.PersistentVolumeMode
expectedVolumeMode v1.PersistentVolumeMode
}{
{
name: "volume mode nil",
volumeMode: nil,
expectedVolumeMode: v1.PersistentVolumeFilesystem,
},
{
name: "volume mode filesystem",
volumeMode: &fsMode,
expectedVolumeMode: v1.PersistentVolumeFilesystem,
},
{
name: "volume mode block",
volumeMode: &blockMode,
expectedVolumeMode: v1.PersistentVolumeBlock,
},
}
for _, test := range tests {
pod := &v1.Pod{
Spec: v1.PodSpec{
Volumes: []v1.Volume{
{
VolumeSource: v1.VolumeSource{
Ephemeral: &v1.EphemeralVolumeSource{
VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{
Spec: v1.PersistentVolumeClaimSpec{
VolumeMode: test.volumeMode,
},
},
},
},
},
},
},
}
obj1 := roundTrip(t, runtime.Object(pod))
pod1 := obj1.(*v1.Pod)
if *pod1.Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode != test.expectedVolumeMode {
t.Errorf("Test %s failed, Expected VolumeMode: %v, but got %v", test.name, test.volumeMode, *pod1.Spec.Volumes[0].VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.VolumeMode)
}
}
}
func TestSetDefaultEndpointsProtocol(t *testing.T) { func TestSetDefaultEndpointsProtocol(t *testing.T) {
in := &v1.Endpoints{Subsets: []v1.EndpointSubset{ in := &v1.Endpoints{Subsets: []v1.EndpointSubset{
{Ports: []v1.EndpointPort{{}, {Protocol: "UDP"}, {}}}, {Ports: []v1.EndpointPort{{}, {Protocol: "UDP"}, {}}},

View File

@ -541,6 +541,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := s.AddGeneratedConversionFunc((*v1.EphemeralVolumeSource)(nil), (*core.EphemeralVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_EphemeralVolumeSource_To_core_EphemeralVolumeSource(a.(*v1.EphemeralVolumeSource), b.(*core.EphemeralVolumeSource), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*core.EphemeralVolumeSource)(nil), (*v1.EphemeralVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_core_EphemeralVolumeSource_To_v1_EphemeralVolumeSource(a.(*core.EphemeralVolumeSource), b.(*v1.EphemeralVolumeSource), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.Event)(nil), (*core.Event)(nil), func(a, b interface{}, scope conversion.Scope) error { if err := s.AddGeneratedConversionFunc((*v1.Event)(nil), (*core.Event)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_Event_To_core_Event(a.(*v1.Event), b.(*core.Event), scope) return Convert_v1_Event_To_core_Event(a.(*v1.Event), b.(*core.Event), scope)
}); err != nil { }); err != nil {
@ -1131,6 +1141,16 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil { }); err != nil {
return err return err
} }
if err := s.AddGeneratedConversionFunc((*v1.PersistentVolumeClaimTemplate)(nil), (*core.PersistentVolumeClaimTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_PersistentVolumeClaimTemplate_To_core_PersistentVolumeClaimTemplate(a.(*v1.PersistentVolumeClaimTemplate), b.(*core.PersistentVolumeClaimTemplate), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*core.PersistentVolumeClaimTemplate)(nil), (*v1.PersistentVolumeClaimTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_core_PersistentVolumeClaimTemplate_To_v1_PersistentVolumeClaimTemplate(a.(*core.PersistentVolumeClaimTemplate), b.(*v1.PersistentVolumeClaimTemplate), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.PersistentVolumeClaimVolumeSource)(nil), (*core.PersistentVolumeClaimVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error { if err := s.AddGeneratedConversionFunc((*v1.PersistentVolumeClaimVolumeSource)(nil), (*core.PersistentVolumeClaimVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource(a.(*v1.PersistentVolumeClaimVolumeSource), b.(*core.PersistentVolumeClaimVolumeSource), scope) return Convert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource(a.(*v1.PersistentVolumeClaimVolumeSource), b.(*core.PersistentVolumeClaimVolumeSource), scope)
}); err != nil { }); err != nil {
@ -3514,6 +3534,28 @@ func Convert_core_EphemeralContainers_To_v1_EphemeralContainers(in *core.Ephemer
return autoConvert_core_EphemeralContainers_To_v1_EphemeralContainers(in, out, s) return autoConvert_core_EphemeralContainers_To_v1_EphemeralContainers(in, out, s)
} }
func autoConvert_v1_EphemeralVolumeSource_To_core_EphemeralVolumeSource(in *v1.EphemeralVolumeSource, out *core.EphemeralVolumeSource, s conversion.Scope) error {
out.VolumeClaimTemplate = (*core.PersistentVolumeClaimTemplate)(unsafe.Pointer(in.VolumeClaimTemplate))
out.ReadOnly = in.ReadOnly
return nil
}
// Convert_v1_EphemeralVolumeSource_To_core_EphemeralVolumeSource is an autogenerated conversion function.
func Convert_v1_EphemeralVolumeSource_To_core_EphemeralVolumeSource(in *v1.EphemeralVolumeSource, out *core.EphemeralVolumeSource, s conversion.Scope) error {
return autoConvert_v1_EphemeralVolumeSource_To_core_EphemeralVolumeSource(in, out, s)
}
func autoConvert_core_EphemeralVolumeSource_To_v1_EphemeralVolumeSource(in *core.EphemeralVolumeSource, out *v1.EphemeralVolumeSource, s conversion.Scope) error {
out.VolumeClaimTemplate = (*v1.PersistentVolumeClaimTemplate)(unsafe.Pointer(in.VolumeClaimTemplate))
out.ReadOnly = in.ReadOnly
return nil
}
// Convert_core_EphemeralVolumeSource_To_v1_EphemeralVolumeSource is an autogenerated conversion function.
func Convert_core_EphemeralVolumeSource_To_v1_EphemeralVolumeSource(in *core.EphemeralVolumeSource, out *v1.EphemeralVolumeSource, s conversion.Scope) error {
return autoConvert_core_EphemeralVolumeSource_To_v1_EphemeralVolumeSource(in, out, s)
}
func autoConvert_v1_Event_To_core_Event(in *v1.Event, out *core.Event, s conversion.Scope) error { func autoConvert_v1_Event_To_core_Event(in *v1.Event, out *core.Event, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta out.ObjectMeta = in.ObjectMeta
if err := Convert_v1_ObjectReference_To_core_ObjectReference(&in.InvolvedObject, &out.InvolvedObject, s); err != nil { if err := Convert_v1_ObjectReference_To_core_ObjectReference(&in.InvolvedObject, &out.InvolvedObject, s); err != nil {
@ -5149,6 +5191,32 @@ func Convert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(
return autoConvert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in, out, s) return autoConvert_core_PersistentVolumeClaimStatus_To_v1_PersistentVolumeClaimStatus(in, out, s)
} }
func autoConvert_v1_PersistentVolumeClaimTemplate_To_core_PersistentVolumeClaimTemplate(in *v1.PersistentVolumeClaimTemplate, out *core.PersistentVolumeClaimTemplate, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if err := Convert_v1_PersistentVolumeClaimSpec_To_core_PersistentVolumeClaimSpec(&in.Spec, &out.Spec, s); err != nil {
return err
}
return nil
}
// Convert_v1_PersistentVolumeClaimTemplate_To_core_PersistentVolumeClaimTemplate is an autogenerated conversion function.
func Convert_v1_PersistentVolumeClaimTemplate_To_core_PersistentVolumeClaimTemplate(in *v1.PersistentVolumeClaimTemplate, out *core.PersistentVolumeClaimTemplate, s conversion.Scope) error {
return autoConvert_v1_PersistentVolumeClaimTemplate_To_core_PersistentVolumeClaimTemplate(in, out, s)
}
func autoConvert_core_PersistentVolumeClaimTemplate_To_v1_PersistentVolumeClaimTemplate(in *core.PersistentVolumeClaimTemplate, out *v1.PersistentVolumeClaimTemplate, s conversion.Scope) error {
out.ObjectMeta = in.ObjectMeta
if err := Convert_core_PersistentVolumeClaimSpec_To_v1_PersistentVolumeClaimSpec(&in.Spec, &out.Spec, s); err != nil {
return err
}
return nil
}
// Convert_core_PersistentVolumeClaimTemplate_To_v1_PersistentVolumeClaimTemplate is an autogenerated conversion function.
func Convert_core_PersistentVolumeClaimTemplate_To_v1_PersistentVolumeClaimTemplate(in *core.PersistentVolumeClaimTemplate, out *v1.PersistentVolumeClaimTemplate, s conversion.Scope) error {
return autoConvert_core_PersistentVolumeClaimTemplate_To_v1_PersistentVolumeClaimTemplate(in, out, s)
}
func autoConvert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource(in *v1.PersistentVolumeClaimVolumeSource, out *core.PersistentVolumeClaimVolumeSource, s conversion.Scope) error { func autoConvert_v1_PersistentVolumeClaimVolumeSource_To_core_PersistentVolumeClaimVolumeSource(in *v1.PersistentVolumeClaimVolumeSource, out *core.PersistentVolumeClaimVolumeSource, s conversion.Scope) error {
out.ClaimName = in.ClaimName out.ClaimName = in.ClaimName
out.ReadOnly = in.ReadOnly out.ReadOnly = in.ReadOnly
@ -8015,6 +8083,7 @@ func autoConvert_v1_VolumeSource_To_core_VolumeSource(in *v1.VolumeSource, out *
out.ScaleIO = (*core.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) out.ScaleIO = (*core.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.StorageOS = (*core.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS)) out.StorageOS = (*core.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS))
out.CSI = (*core.CSIVolumeSource)(unsafe.Pointer(in.CSI)) out.CSI = (*core.CSIVolumeSource)(unsafe.Pointer(in.CSI))
out.Ephemeral = (*core.EphemeralVolumeSource)(unsafe.Pointer(in.Ephemeral))
return nil return nil
} }
@ -8060,6 +8129,7 @@ func autoConvert_core_VolumeSource_To_v1_VolumeSource(in *core.VolumeSource, out
out.ScaleIO = (*v1.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO)) out.ScaleIO = (*v1.ScaleIOVolumeSource)(unsafe.Pointer(in.ScaleIO))
out.StorageOS = (*v1.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS)) out.StorageOS = (*v1.StorageOSVolumeSource)(unsafe.Pointer(in.StorageOS))
out.CSI = (*v1.CSIVolumeSource)(unsafe.Pointer(in.CSI)) out.CSI = (*v1.CSIVolumeSource)(unsafe.Pointer(in.CSI))
out.Ephemeral = (*v1.EphemeralVolumeSource)(unsafe.Pointer(in.Ephemeral))
return nil return nil
} }

View File

@ -200,6 +200,7 @@ func SetObjectDefaults_PersistentVolume(in *v1.PersistentVolume) {
func SetObjectDefaults_PersistentVolumeClaim(in *v1.PersistentVolumeClaim) { func SetObjectDefaults_PersistentVolumeClaim(in *v1.PersistentVolumeClaim) {
SetDefaults_PersistentVolumeClaim(in) SetDefaults_PersistentVolumeClaim(in)
SetDefaults_PersistentVolumeClaimSpec(&in.Spec)
SetDefaults_ResourceList(&in.Spec.Resources.Limits) SetDefaults_ResourceList(&in.Spec.Resources.Limits)
SetDefaults_ResourceList(&in.Spec.Resources.Requests) SetDefaults_ResourceList(&in.Spec.Resources.Requests)
SetDefaults_ResourceList(&in.Status.Capacity) SetDefaults_ResourceList(&in.Status.Capacity)
@ -272,6 +273,13 @@ func SetObjectDefaults_Pod(in *v1.Pod) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.InitContainers { for i := range in.Spec.InitContainers {
a := &in.Spec.InitContainers[i] a := &in.Spec.InitContainers[i]
@ -478,6 +486,13 @@ func SetObjectDefaults_PodTemplate(in *v1.PodTemplate) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Template.Spec.InitContainers { for i := range in.Template.Spec.InitContainers {
a := &in.Template.Spec.InitContainers[i] a := &in.Template.Spec.InitContainers[i]
@ -686,6 +701,13 @@ func SetObjectDefaults_ReplicationController(in *v1.ReplicationController) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]

View File

@ -37,6 +37,7 @@ import (
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
v1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
"k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/intstr"
@ -349,15 +350,26 @@ func ValidateObjectMetaUpdate(newMeta, oldMeta *metav1.ObjectMeta, fldPath *fiel
return allErrs return allErrs
} }
func ValidateVolumes(volumes []core.Volume, fldPath *field.Path) (map[string]core.VolumeSource, field.ErrorList) { func ValidateVolumes(volumes []core.Volume, podMeta *metav1.ObjectMeta, fldPath *field.Path) (map[string]core.VolumeSource, field.ErrorList) {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allNames := sets.String{} allNames := sets.String{}
allCreatedPVCs := sets.String{}
// Determine which PVCs will be created for this pod. We need
// the exact name of the pod for this. Without it, this sanity
// check has to be skipped.
if podMeta != nil && podMeta.Name != "" {
for _, vol := range volumes {
if vol.VolumeSource.Ephemeral != nil {
allCreatedPVCs.Insert(podMeta.Name + "-" + vol.Name)
}
}
}
vols := make(map[string]core.VolumeSource) 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")
el := validateVolumeSource(&vol.VolumeSource, idxPath, vol.Name) el := validateVolumeSource(&vol.VolumeSource, idxPath, vol.Name, podMeta)
if len(vol.Name) == 0 { if len(vol.Name) == 0 {
el = append(el, field.Required(namePath, "")) el = append(el, field.Required(namePath, ""))
} else { } else {
@ -372,8 +384,14 @@ func ValidateVolumes(volumes []core.Volume, fldPath *field.Path) (map[string]cor
} else { } else {
allErrs = append(allErrs, el...) allErrs = append(allErrs, el...)
} }
// A PersistentVolumeClaimSource should not reference a created PVC. That doesn't
// make sense.
if vol.PersistentVolumeClaim != nil && allCreatedPVCs.Has(vol.PersistentVolumeClaim.ClaimName) {
allErrs = append(allErrs, field.Invalid(idxPath.Child("persistentVolumeClaim").Child("claimName"), vol.PersistentVolumeClaim.ClaimName,
"must not reference a PVC that gets created for an ephemeral volume"))
}
} }
return vols, allErrs return vols, allErrs
} }
@ -428,7 +446,7 @@ func devicePathAlreadyExists(devicePath string, mounts map[string]string) bool {
return false return false
} }
func validateVolumeSource(source *core.VolumeSource, fldPath *field.Path, volName string) field.ErrorList { func validateVolumeSource(source *core.VolumeSource, fldPath *field.Path, volName string, podMeta *metav1.ObjectMeta) field.ErrorList {
numVolumes := 0 numVolumes := 0
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
if source.EmptyDir != nil { if source.EmptyDir != nil {
@ -659,6 +677,23 @@ func validateVolumeSource(source *core.VolumeSource, fldPath *field.Path, volNam
allErrs = append(allErrs, validateCSIVolumeSource(source.CSI, fldPath.Child("csi"))...) allErrs = append(allErrs, validateCSIVolumeSource(source.CSI, fldPath.Child("csi"))...)
} }
} }
if source.Ephemeral != nil {
if numVolumes > 0 {
allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeral"), "may not specify more than 1 volume type"))
} else {
numVolumes++
allErrs = append(allErrs, validateEphemeralVolumeSource(source.Ephemeral, fldPath.Child("ephemeral"))...)
// Check the expected name for the PVC. This gets skipped if information is missing,
// because that already gets flagged as a problem elsewhere. For example,
// ValidateObjectMeta as called by validatePodMetadataAndSpec checks that the name is set.
if podMeta != nil && podMeta.Name != "" && volName != "" {
pvcName := podMeta.Name + "-" + volName
for _, msg := range ValidatePersistentVolumeName(pvcName, false) {
allErrs = append(allErrs, field.Invalid(fldPath.Child("name"), volName, fmt.Sprintf("PVC name %q: %v", pvcName, msg)))
}
}
}
}
if numVolumes == 0 { if numVolumes == 0 {
allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type")) allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type"))
@ -1552,6 +1587,41 @@ func validateCSIVolumeSource(csi *core.CSIVolumeSource, fldPath *field.Path) fie
return allErrs return allErrs
} }
func validateEphemeralVolumeSource(ephemeral *core.EphemeralVolumeSource, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{}
if ephemeral.VolumeClaimTemplate == nil {
allErrs = append(allErrs, field.Required(fldPath.Child("volumeClaimTemplate"), ""))
} else {
allErrs = append(allErrs, ValidatePersistentVolumeClaimTemplate(ephemeral.VolumeClaimTemplate, fldPath.Child("volumeClaimTemplate"))...)
}
return allErrs
}
// ValidatePersistentVolumeClaimTemplate verifies that the embedded object meta and spec are valid.
// Checking of the object data is very minimal because only labels and annotations are used.
func ValidatePersistentVolumeClaimTemplate(claimTemplate *core.PersistentVolumeClaimTemplate, fldPath *field.Path) field.ErrorList {
allErrs := validatePersistentVolumeClaimTemplateObjectMeta(&claimTemplate.ObjectMeta, fldPath.Child("metadata"))
allErrs = append(allErrs, ValidatePersistentVolumeClaimSpec(&claimTemplate.Spec, fldPath.Child("spec"))...)
return allErrs
}
func validatePersistentVolumeClaimTemplateObjectMeta(objMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
allErrs := apimachineryvalidation.ValidateAnnotations(objMeta.Annotations, fldPath.Child("annotations"))
allErrs = append(allErrs, v1validation.ValidateLabels(objMeta.Labels, fldPath.Child("labels"))...)
// All other fields are not supported and thus must not be set
// to avoid confusion. We could reject individual fields,
// but then adding a new one to ObjectMeta wouldn't be checked
// unless this code gets updated. Instead, we ensure that
// only allowed fields are set via reflection.
allErrs = append(allErrs, validateFieldAllowList(*objMeta, allowedPVCTemplateObjectMetaFields, "cannot be set for an ephemeral volume", fldPath)...)
return allErrs
}
var allowedPVCTemplateObjectMetaFields = map[string]bool{
"Annotations": true,
"Labels": true,
}
// ValidatePersistentVolumeName checks that a name is appropriate for a // ValidatePersistentVolumeName checks that a name is appropriate for a
// PersistentVolumeName object. // PersistentVolumeName object.
var ValidatePersistentVolumeName = apimachineryvalidation.NameIsDNSSubdomain var ValidatePersistentVolumeName = apimachineryvalidation.NameIsDNSSubdomain
@ -2647,21 +2717,31 @@ func validateEphemeralContainers(ephemeralContainers []core.EphemeralContainer,
} }
// Ephemeral Containers should not be relied upon for fundamental pod services, so fields such as // Ephemeral Containers should not be relied upon for fundamental pod services, so fields such as
// Lifecycle, probes, resources and ports should be disallowed. This is implemented as a whitelist // Lifecycle, probes, resources and ports should be disallowed. This is implemented as a list
// so that new fields will be given consideration prior to inclusion in Ephemeral Containers. // of allowed fields so that new fields will be given consideration prior to inclusion in Ephemeral Containers.
specType, specValue := reflect.TypeOf(ec.EphemeralContainerCommon), reflect.ValueOf(ec.EphemeralContainerCommon) allErrs = append(allErrs, validateFieldAllowList(ec.EphemeralContainerCommon, allowedEphemeralContainerFields, "cannot be set for an Ephemeral Container", idxPath)...)
for i := 0; i < specType.NumField(); i++ { }
f := specType.Field(i)
if allowedEphemeralContainerFields[f.Name] {
continue
}
// Compare the value of this field to its zero value to determine if it has been set return allErrs
if !reflect.DeepEqual(specValue.Field(i).Interface(), reflect.Zero(f.Type).Interface()) { }
r, n := utf8.DecodeRuneInString(f.Name)
lcName := string(unicode.ToLower(r)) + f.Name[n:] // validateFieldAcceptList checks that only allowed fields are set.
allErrs = append(allErrs, field.Forbidden(idxPath.Child(lcName), "cannot be set for an Ephemeral Container")) // The value must be a struct (not a pointer to a struct!).
} func validateFieldAllowList(value interface{}, allowedFields map[string]bool, errorText string, fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
reflectType, reflectValue := reflect.TypeOf(value), reflect.ValueOf(value)
for i := 0; i < reflectType.NumField(); i++ {
f := reflectType.Field(i)
if allowedFields[f.Name] {
continue
}
// Compare the value of this field to its zero value to determine if it has been set
if !reflect.DeepEqual(reflectValue.Field(i).Interface(), reflect.Zero(f.Type).Interface()) {
r, n := utf8.DecodeRuneInString(f.Name)
lcName := string(unicode.ToLower(r)) + f.Name[n:]
allErrs = append(allErrs, field.Forbidden(fldPath.Child(lcName), errorText))
} }
} }
@ -3119,7 +3199,7 @@ func validatePodMetadataAndSpec(pod *core.Pod, opts PodValidationOptions) field.
fldPath := field.NewPath("metadata") fldPath := field.NewPath("metadata")
allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath) allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath)
allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"))...)
allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, field.NewPath("spec"))...) allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, &pod.ObjectMeta, field.NewPath("spec"))...)
// we do additional validation only pertinent for pods and not pod templates // we do additional validation only pertinent for pods and not pod templates
// this was done to preserve backwards compatibility // this was done to preserve backwards compatibility
@ -3198,10 +3278,12 @@ func validatePodIPs(pod *core.Pod) field.ErrorList {
// This includes checking formatting and uniqueness. It also canonicalizes the // This includes checking formatting and uniqueness. It also canonicalizes the
// structure by setting default values and implementing any backwards-compatibility // structure by setting default values and implementing any backwards-compatibility
// tricks. // tricks.
func ValidatePodSpec(spec *core.PodSpec, fldPath *field.Path) field.ErrorList { // The pod metadata is needed to validate generic ephemeral volumes. It is optional
// and should be left empty unless the spec is from a real pod object.
func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
vols, vErrs := ValidateVolumes(spec.Volumes, fldPath.Child("volumes")) vols, vErrs := ValidateVolumes(spec.Volumes, podMeta, fldPath.Child("volumes"))
allErrs = append(allErrs, vErrs...) allErrs = append(allErrs, vErrs...)
allErrs = append(allErrs, validateContainers(spec.Containers, false, vols, fldPath.Child("containers"))...) allErrs = append(allErrs, validateContainers(spec.Containers, false, vols, fldPath.Child("containers"))...)
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"))...) allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, vols, fldPath.Child("initContainers"))...)
@ -4493,7 +4575,7 @@ func ValidatePodTemplateSpec(spec *core.PodTemplateSpec, fldPath *field.Path) fi
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.Labels, fldPath.Child("labels"))...) allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.Labels, fldPath.Child("labels"))...)
allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidateAnnotations(spec.Annotations, fldPath.Child("annotations"))...)
allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidatePodSpecificAnnotations(spec.Annotations, &spec.Spec, fldPath.Child("annotations"))...)
allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, fldPath.Child("spec"))...) allErrs = append(allErrs, ValidatePodSpec(&spec.Spec, nil, fldPath.Child("spec"))...)
allErrs = append(allErrs, validateSeccompAnnotationsAndFields(spec.ObjectMeta, &spec.Spec, fldPath.Child("spec"))...) allErrs = append(allErrs, validateSeccompAnnotationsAndFields(spec.ObjectMeta, &spec.Spec, fldPath.Child("spec"))...)
if len(spec.Spec.EphemeralContainers) > 0 { if len(spec.Spec.EphemeralContainers) > 0 {

View File

@ -932,42 +932,195 @@ func testVolumeClaimStorageClassInAnnotationAndSpec(name, namespace, scNameInAnn
} }
} }
func TestValidatePersistentVolumeClaim(t *testing.T) { func testValidatePVC(t *testing.T, ephemeral bool) {
invalidClassName := "-invalid-" invalidClassName := "-invalid-"
validClassName := "valid" validClassName := "valid"
invalidMode := core.PersistentVolumeMode("fakeVolumeMode") invalidMode := core.PersistentVolumeMode("fakeVolumeMode")
validMode := core.PersistentVolumeFilesystem validMode := core.PersistentVolumeFilesystem
goodName := "foo"
goodNS := "ns"
if ephemeral {
// Must be empty for ephemeral inline volumes.
goodName = ""
goodNS = ""
}
goodClaimSpec := 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,
}
now := metav1.Now()
ten := int64(10)
scenarios := map[string]struct { scenarios := map[string]struct {
isExpectedFailure bool isExpectedFailure bool
claim *core.PersistentVolumeClaim claim *core.PersistentVolumeClaim
}{ }{
"good-claim": { "good-claim": {
isExpectedFailure: false, isExpectedFailure: false,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, goodClaimSpec),
Selector: &metav1.LabelSelector{ },
MatchExpressions: []metav1.LabelSelectorRequirement{ "missing-name": {
{ isExpectedFailure: !ephemeral,
Key: "key2", claim: testVolumeClaim("", goodNS, goodClaimSpec),
Operator: "Exists", },
}, "missing-namespace": {
isExpectedFailure: !ephemeral,
claim: testVolumeClaim(goodName, "", goodClaimSpec),
},
"with-generate-name": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.GenerateName = "pvc-"
return claim
}(),
},
"with-uid": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.UID = "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d"
return claim
}(),
},
"with-resource-version": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.ResourceVersion = "1"
return claim
}(),
},
"with-generation": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Generation = 100
return claim
}(),
},
"with-creation-timestamp": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.CreationTimestamp = now
return claim
}(),
},
"with-deletion-grace-period-seconds": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.DeletionGracePeriodSeconds = &ten
return claim
}(),
},
"with-owner-references": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.OwnerReferences = []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "pod",
Name: "foo",
UID: "ac051fac-2ead-46d9-b8b4-4e0fbeb7455d",
}, },
}, }
AccessModes: []core.PersistentVolumeAccessMode{ return claim
core.ReadWriteOnce, }(),
core.ReadOnlyMany, },
}, "with-finalizers": {
Resources: core.ResourceRequirements{ isExpectedFailure: ephemeral,
Requests: core.ResourceList{ claim: func() *core.PersistentVolumeClaim {
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Finalizers = []string{
"example.com/foo",
}
return claim
}(),
},
"with-cluster-name": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.ClusterName = "foo"
return claim
}(),
},
"with-managed-fields": {
isExpectedFailure: ephemeral,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.ManagedFields = []metav1.ManagedFieldsEntry{
{
FieldsType: "FieldsV1",
Operation: "Apply",
APIVersion: "apps/v1",
Manager: "foo",
}, },
}, }
StorageClassName: &validClassName, return claim
VolumeMode: &validMode, }(),
}), },
"with-good-labels": {
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Labels = map[string]string{
"apps.kubernetes.io/name": "test",
}
return claim
}(),
},
"with-bad-labels": {
isExpectedFailure: true,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Labels = map[string]string{
"hello-world": "hyphen not allowed",
}
return claim
}(),
},
"with-good-annotations": {
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Labels = map[string]string{
"foo": "bar",
}
return claim
}(),
},
"with-bad-annotations": {
isExpectedFailure: true,
claim: func() *core.PersistentVolumeClaim {
claim := testVolumeClaim(goodName, goodNS, goodClaimSpec)
claim.Labels = map[string]string{
"hello-world": "hyphen not allowed",
}
return claim
}(),
}, },
"invalid-claim-zero-capacity": { "invalid-claim-zero-capacity": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{ MatchExpressions: []metav1.LabelSelectorRequirement{
{ {
@ -990,7 +1143,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
"invalid-label-selector": { "invalid-label-selector": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{ MatchExpressions: []metav1.LabelSelectorRequirement{
{ {
@ -1013,7 +1166,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
"invalid-accessmode": { "invalid-accessmode": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{"fakemode"}, AccessModes: []core.PersistentVolumeAccessMode{"fakemode"},
Resources: core.ResourceRequirements{ Resources: core.ResourceRequirements{
Requests: core.ResourceList{ Requests: core.ResourceList{
@ -1022,23 +1175,9 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
}), }),
}, },
"missing-namespace": {
isExpectedFailure: true,
claim: testVolumeClaim("foo", "", core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
core.ReadOnlyMany,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
}),
},
"no-access-modes": { "no-access-modes": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Resources: core.ResourceRequirements{ Resources: core.ResourceRequirements{
Requests: core.ResourceList{ Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"), core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
@ -1048,7 +1187,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
"no-resource-requests": { "no-resource-requests": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{ AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce, core.ReadWriteOnce,
}, },
@ -1056,7 +1195,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
"invalid-resource-requests": { "invalid-resource-requests": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{ AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce, core.ReadWriteOnce,
}, },
@ -1069,7 +1208,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
"negative-storage-request": { "negative-storage-request": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{ MatchExpressions: []metav1.LabelSelectorRequirement{
{ {
@ -1091,7 +1230,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
"zero-storage-request": { "zero-storage-request": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{ MatchExpressions: []metav1.LabelSelectorRequirement{
{ {
@ -1113,7 +1252,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
"invalid-storage-class-name": { "invalid-storage-class-name": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
Selector: &metav1.LabelSelector{ Selector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{ MatchExpressions: []metav1.LabelSelectorRequirement{
{ {
@ -1136,7 +1275,7 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
}, },
"invalid-volume-mode": { "invalid-volume-mode": {
isExpectedFailure: true, isExpectedFailure: true,
claim: testVolumeClaim("foo", "ns", core.PersistentVolumeClaimSpec{ claim: testVolumeClaim(goodName, goodNS, core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{ AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce, core.ReadWriteOnce,
core.ReadOnlyMany, core.ReadOnlyMany,
@ -1153,17 +1292,43 @@ func TestValidatePersistentVolumeClaim(t *testing.T) {
for name, scenario := range scenarios { for name, scenario := range scenarios {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
errs := ValidatePersistentVolumeClaim(scenario.claim) var errs field.ErrorList
if ephemeral {
volumes := []core.Volume{
{
Name: "foo",
VolumeSource: core.VolumeSource{
Ephemeral: &core.EphemeralVolumeSource{
VolumeClaimTemplate: &core.PersistentVolumeClaimTemplate{
ObjectMeta: scenario.claim.ObjectMeta,
Spec: scenario.claim.Spec,
},
},
},
},
}
_, errs = ValidateVolumes(volumes, nil, field.NewPath(""))
} else {
errs = ValidatePersistentVolumeClaim(scenario.claim)
}
if len(errs) == 0 && scenario.isExpectedFailure { if len(errs) == 0 && scenario.isExpectedFailure {
t.Errorf("Unexpected success for scenario: %s", name) t.Error("Unexpected success for scenario")
} }
if len(errs) > 0 && !scenario.isExpectedFailure { if len(errs) > 0 && !scenario.isExpectedFailure {
t.Errorf("Unexpected failure for scenario: %s - %+v", name, errs) t.Errorf("Unexpected failure: %+v", errs)
} }
}) })
} }
} }
func TestValidatePersistentVolumeClaim(t *testing.T) {
testValidatePVC(t, false)
}
func TestValidateEphemeralVolume(t *testing.T) {
testValidatePVC(t, true)
}
func TestAlphaPVVolumeModeUpdate(t *testing.T) { func TestAlphaPVVolumeModeUpdate(t *testing.T) {
block := core.PersistentVolumeBlock block := core.PersistentVolumeBlock
file := core.PersistentVolumeFilesystem file := core.PersistentVolumeFilesystem
@ -3825,7 +3990,7 @@ func TestValidateVolumes(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
names, errs := ValidateVolumes([]core.Volume{tc.vol}, field.NewPath("field")) names, errs := ValidateVolumes([]core.Volume{tc.vol}, nil, field.NewPath("field"))
if len(errs) != len(tc.errs) { if len(errs) != len(tc.errs) {
t.Fatalf("unexpected error(s): got %d, want %d: %v", len(tc.errs), len(errs), errs) t.Fatalf("unexpected error(s): got %d, want %d: %v", len(tc.errs), len(errs), errs)
} }
@ -3851,7 +4016,7 @@ func TestValidateVolumes(t *testing.T) {
{Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
{Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, {Name: "abc", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
} }
_, errs := ValidateVolumes(dupsCase, field.NewPath("field")) _, errs := ValidateVolumes(dupsCase, nil, field.NewPath("field"))
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected error") t.Errorf("expected error")
} else if len(errs) != 1 { } else if len(errs) != 1 {
@ -3864,7 +4029,7 @@ func TestValidateVolumes(t *testing.T) {
hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}} hugePagesCase := core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{Medium: core.StorageMediumHugePages}}
// Enable HugePages // Enable HugePages
if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working"); len(errs) != 0 { if errs := validateVolumeSource(&hugePagesCase, field.NewPath("field").Index(0), "working", nil); len(errs) != 0 {
t.Errorf("Unexpected error when HugePages feature is enabled.") t.Errorf("Unexpected error when HugePages feature is enabled.")
} }
@ -4194,7 +4359,7 @@ func TestAlphaLocalStorageCapacityIsolation(t *testing.T) {
} }
for _, tc := range testCases { for _, tc := range testCases {
if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol"); len(errs) != 0 { if errs := validateVolumeSource(&tc, field.NewPath("spec"), "tmpvol", nil); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
@ -4937,7 +5102,7 @@ func TestValidateVolumeMounts(t *testing.T) {
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, {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))}}}, {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
} }
vols, v1err := ValidateVolumes(volumes, field.NewPath("field")) vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 { if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err) t.Errorf("Invalid test volume - expected success %v", v1err)
return return
@ -5000,7 +5165,7 @@ func TestValidateDisabledSubpath(t *testing.T) {
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, {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))}}}, {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
} }
vols, v1err := ValidateVolumes(volumes, field.NewPath("field")) vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 { if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err) t.Errorf("Invalid test volume - expected success %v", v1err)
return return
@ -5062,7 +5227,7 @@ func TestValidateSubpathMutuallyExclusive(t *testing.T) {
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, {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))}}}, {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
} }
vols, v1err := ValidateVolumes(volumes, field.NewPath("field")) vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 { if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err) t.Errorf("Invalid test volume - expected success %v", v1err)
return return
@ -5143,7 +5308,7 @@ func TestValidateDisabledSubpathExpr(t *testing.T) {
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}}, {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))}}}, {Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
} }
vols, v1err := ValidateVolumes(volumes, field.NewPath("field")) vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 { if len(v1err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v1err) t.Errorf("Invalid test volume - expected success %v", v1err)
return return
@ -5337,7 +5502,7 @@ func TestValidateMountPropagation(t *testing.T) {
volumes := []core.Volume{ volumes := []core.Volume{
{Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, {Name: "foo", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
} }
vols2, v2err := ValidateVolumes(volumes, field.NewPath("field")) vols2, v2err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v2err) > 0 { if len(v2err) > 0 {
t.Errorf("Invalid test volume - expected success %v", v2err) t.Errorf("Invalid test volume - expected success %v", v2err)
return return
@ -5360,7 +5525,7 @@ func TestAlphaValidateVolumeDevices(t *testing.T) {
{Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}}, {Name: "def", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
} }
vols, v1err := ValidateVolumes(volumes, field.NewPath("field")) vols, v1err := ValidateVolumes(volumes, nil, field.NewPath("field"))
if len(v1err) > 0 { if len(v1err) > 0 {
t.Errorf("Invalid test volumes - expected success %v", v1err) t.Errorf("Invalid test volumes - expected success %v", v1err)
return return
@ -6560,14 +6725,14 @@ func TestValidatePodSpec(t *testing.T) {
badfsGroupChangePolicy1 := core.PodFSGroupChangePolicy("invalid") badfsGroupChangePolicy1 := core.PodFSGroupChangePolicy("invalid")
badfsGroupChangePolicy2 := core.PodFSGroupChangePolicy("") badfsGroupChangePolicy2 := core.PodFSGroupChangePolicy("")
successCases := []core.PodSpec{ successCases := map[string]core.PodSpec{
{ // Populate basic fields, leave defaults for most. "populate basic fields, leave defaults for most": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate all fields. "populate all fields": {
Volumes: []core.Volume{ Volumes: []core.Volume{
{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
}, },
@ -6582,7 +6747,7 @@ func TestValidatePodSpec(t *testing.T) {
ActiveDeadlineSeconds: &activeDeadlineSeconds, ActiveDeadlineSeconds: &activeDeadlineSeconds,
ServiceAccountName: "acct", ServiceAccountName: "acct",
}, },
{ // Populate all fields with larger active deadline. "populate all fields with larger active deadline": {
Volumes: []core.Volume{ Volumes: []core.Volume{
{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}, {Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}},
}, },
@ -6597,7 +6762,7 @@ func TestValidatePodSpec(t *testing.T) {
ActiveDeadlineSeconds: &activeDeadlineSecondsMax, ActiveDeadlineSeconds: &activeDeadlineSecondsMax,
ServiceAccountName: "acct", ServiceAccountName: "acct",
}, },
{ // Populate HostNetwork. "populate HostNetwork": {
Containers: []core.Container{ Containers: []core.Container{
{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File", {Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
Ports: []core.ContainerPort{ Ports: []core.ContainerPort{
@ -6610,7 +6775,7 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate RunAsUser SupplementalGroups FSGroup with minID 0 "populate RunAsUser SupplementalGroups FSGroup with minID 0": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
SupplementalGroups: []int64{minGroupID}, SupplementalGroups: []int64{minGroupID},
@ -6620,7 +6785,7 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647 "populate RunAsUser SupplementalGroups FSGroup with maxID 2147483647": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
SupplementalGroups: []int64{maxGroupID}, SupplementalGroups: []int64{maxGroupID},
@ -6630,7 +6795,7 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate HostIPC. "populate HostIPC": {
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
HostIPC: true, HostIPC: true,
}, },
@ -6639,7 +6804,7 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate HostPID. "populate HostPID": {
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
HostPID: true, HostPID: true,
}, },
@ -6648,27 +6813,27 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate Affinity. "populate Affinity": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate HostAliases. "populate HostAliases": {
HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}}, HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1", "host2"}}},
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate HostAliases with `foo.bar` hostnames. "populate HostAliases with `foo.bar` hostnames": {
HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}}, HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate HostAliases with HostNetwork. "populate HostAliases with HostNetwork": {
HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}}, HostAliases: []core.HostAlias{{IP: "12.34.56.78", Hostnames: []string{"host1.foo", "host2.bar"}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
@ -6677,14 +6842,14 @@ func TestValidatePodSpec(t *testing.T) {
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
{ // Populate PriorityClassName. "populate PriorityClassName": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
PriorityClassName: "valid-name", PriorityClassName: "valid-name",
}, },
{ // Populate ShareProcessNamespace "populate ShareProcessNamespace": {
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
@ -6693,20 +6858,20 @@ func TestValidatePodSpec(t *testing.T) {
ShareProcessNamespace: &[]bool{true}[0], ShareProcessNamespace: &[]bool{true}[0],
}, },
}, },
{ // Populate RuntimeClassName "populate RuntimeClassName": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
RuntimeClassName: utilpointer.StringPtr("valid-sandbox"), RuntimeClassName: utilpointer.StringPtr("valid-sandbox"),
}, },
{ // Populate Overhead "populate Overhead": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways, RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
RuntimeClassName: utilpointer.StringPtr("valid-sandbox"), RuntimeClassName: utilpointer.StringPtr("valid-sandbox"),
Overhead: core.ResourceList{}, Overhead: core.ResourceList{},
}, },
{ "populate DNSPolicy": {
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
SecurityContext: &core.PodSecurityContext{ SecurityContext: &core.PodSecurityContext{
FSGroupChangePolicy: &goodfsGroupChangePolicy, FSGroupChangePolicy: &goodfsGroupChangePolicy,
@ -6715,10 +6880,12 @@ func TestValidatePodSpec(t *testing.T) {
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
} }
for i := range successCases { for k, v := range successCases {
if errs := ValidatePodSpec(&successCases[i], field.NewPath("field")); len(errs) != 0 { t.Run(k, func(t *testing.T) {
t.Errorf("expected success: %v", errs) if errs := ValidatePodSpec(&v, nil, field.NewPath("field")); len(errs) != 0 {
} t.Errorf("expected success: %v", errs)
}
})
} }
activeDeadlineSeconds = int64(0) activeDeadlineSeconds = int64(0)
@ -6919,7 +7086,7 @@ func TestValidatePodSpec(t *testing.T) {
}, },
} }
for k, v := range failureCases { for k, v := range failureCases {
if errs := ValidatePodSpec(&v, field.NewPath("field")); len(errs) == 0 { if errs := ValidatePodSpec(&v, nil, field.NewPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %q", k) t.Errorf("expected failure for %q", k)
} }
} }
@ -6946,9 +7113,24 @@ func TestValidatePod(t *testing.T) {
} }
return spec return spec
} }
validPVCSpec := core.PersistentVolumeClaimSpec{
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
Resources: core.ResourceRequirements{
Requests: core.ResourceList{
core.ResourceName(core.ResourceStorage): resource.MustParse("10G"),
},
},
}
validPVCTemplate := core.PersistentVolumeClaimTemplate{
Spec: validPVCSpec,
}
longPodName := strings.Repeat("a", 200)
longVolName := strings.Repeat("b", 60)
successCases := []core.Pod{ successCases := map[string]core.Pod{
{ // Basic fields. "basic fields": {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"}, ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{ Spec: core.PodSpec{
Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}}, Volumes: []core.Volume{{Name: "vol", VolumeSource: core.VolumeSource{EmptyDir: &core.EmptyDirVolumeSource{}}}},
@ -6957,7 +7139,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
}, },
{ // Just about everything. "just about everything": {
ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"}, ObjectMeta: metav1.ObjectMeta{Name: "abc.123.do-re-mi", Namespace: "ns"},
Spec: core.PodSpec{ Spec: core.PodSpec{
Volumes: []core.Volume{ Volumes: []core.Volume{
@ -6972,7 +7154,7 @@ func TestValidatePod(t *testing.T) {
NodeName: "foobar", NodeName: "foobar",
}, },
}, },
{ // Serialized node affinity requirements. "serialized node affinity requirements": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7032,7 +7214,7 @@ func TestValidatePod(t *testing.T) {
}, },
), ),
}, },
{ // Serialized node affinity requirements. "serialized node affinity requirements, II": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7073,7 +7255,7 @@ func TestValidatePod(t *testing.T) {
}, },
), ),
}, },
{ // Serialized pod affinity in affinity requirements in annotations. "serialized pod affinity in affinity requirements in annotations": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7129,7 +7311,7 @@ func TestValidatePod(t *testing.T) {
}, },
}), }),
}, },
{ // Serialized pod anti affinity with different Label Operators in affinity requirements in annotations. "serialized pod anti affinity with different Label Operators in affinity requirements in annotations": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7183,63 +7365,63 @@ func TestValidatePod(t *testing.T) {
}, },
}), }),
}, },
{ // populate forgiveness tolerations with exists operator in annotations. "populate forgiveness tolerations with exists operator in annotations.": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
}, },
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}), Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Exists", Value: "", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
}, },
{ // populate forgiveness tolerations with equal operator in annotations. "populate forgiveness tolerations with equal operator in annotations.": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
}, },
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}), Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoExecute", TolerationSeconds: &[]int64{60}[0]}}),
}, },
{ // populate tolerations equal operator in annotations. "populate tolerations equal operator in annotations.": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
}, },
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}), Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar", Effect: "NoSchedule"}}),
}, },
{ // populate tolerations exists operator in annotations. "populate tolerations exists operator in annotations.": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
}, },
Spec: validPodSpec(nil), Spec: validPodSpec(nil),
}, },
{ // empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys. "empty key with Exists operator is OK for toleration, empty toleration key means match all taint keys.": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
}, },
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}), Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Operator: "Exists", Effect: "NoSchedule"}}),
}, },
{ // empty operator is OK for toleration, defaults to Equal. "empty operator is OK for toleration, defaults to Equal.": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
}, },
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}), Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Value: "bar", Effect: "NoSchedule"}}),
}, },
{ // empty effect is OK for toleration, empty toleration effect means match all taint effects. "empty effect is OK for toleration, empty toleration effect means match all taint effects.": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
}, },
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}), Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "foo", Operator: "Equal", Value: "bar"}}),
}, },
{ // negative tolerationSeconds is OK for toleration. "negative tolerationSeconds is OK for toleration.": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "pod-forgiveness-invalid", Name: "pod-forgiveness-invalid",
Namespace: "ns", Namespace: "ns",
}, },
Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}), Spec: extendPodSpecwithTolerations(validPodSpec(nil), []core.Toleration{{Key: "node.kubernetes.io/not-ready", Operator: "Exists", Effect: "NoExecute", TolerationSeconds: &[]int64{-2}[0]}}),
}, },
{ // runtime default seccomp profile "runtime default seccomp profile": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7249,7 +7431,7 @@ func TestValidatePod(t *testing.T) {
}, },
Spec: validPodSpec(nil), Spec: validPodSpec(nil),
}, },
{ // docker default seccomp profile "docker default seccomp profile": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7259,7 +7441,7 @@ func TestValidatePod(t *testing.T) {
}, },
Spec: validPodSpec(nil), Spec: validPodSpec(nil),
}, },
{ // unconfined seccomp profile "unconfined seccomp profile": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7269,7 +7451,7 @@ func TestValidatePod(t *testing.T) {
}, },
Spec: validPodSpec(nil), Spec: validPodSpec(nil),
}, },
{ // localhost seccomp profile "localhost seccomp profile": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7279,7 +7461,7 @@ func TestValidatePod(t *testing.T) {
}, },
Spec: validPodSpec(nil), Spec: validPodSpec(nil),
}, },
{ // localhost seccomp profile for a container "localhost seccomp profile for a container": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7289,7 +7471,7 @@ func TestValidatePod(t *testing.T) {
}, },
Spec: validPodSpec(nil), Spec: validPodSpec(nil),
}, },
{ // runtime default seccomp profile for a pod "runtime default seccomp profile for a pod": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7305,7 +7487,7 @@ func TestValidatePod(t *testing.T) {
}, },
}, },
}, },
{ // runtime default seccomp profile for a container "runtime default seccomp profile for a container": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7322,7 +7504,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSDefault, DNSPolicy: core.DNSDefault,
}, },
}, },
{ // unconfined seccomp profile for a pod "unconfined seccomp profile for a pod": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7338,7 +7520,7 @@ func TestValidatePod(t *testing.T) {
}, },
}, },
}, },
{ // unconfined seccomp profile for a container "unconfined seccomp profile for a container": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7355,7 +7537,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSDefault, DNSPolicy: core.DNSDefault,
}, },
}, },
{ // localhost seccomp profile for a pod "localhost seccomp profile for a pod": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7372,7 +7554,7 @@ func TestValidatePod(t *testing.T) {
}, },
}, },
}, },
{ // localhost seccomp profile for a container "localhost seccomp profile for a container, II": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7390,7 +7572,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSDefault, DNSPolicy: core.DNSDefault,
}, },
}, },
{ // default AppArmor profile for a container "default AppArmor profile for a container": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7400,7 +7582,7 @@ func TestValidatePod(t *testing.T) {
}, },
Spec: validPodSpec(nil), Spec: validPodSpec(nil),
}, },
{ // default AppArmor profile for an init container "default AppArmor profile for an init container": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7415,7 +7597,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
}, },
{ // localhost AppArmor profile for a container "localhost AppArmor profile for a container": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7425,7 +7607,7 @@ func TestValidatePod(t *testing.T) {
}, },
Spec: validPodSpec(nil), Spec: validPodSpec(nil),
}, },
{ // syntactically valid sysctls "syntactically valid sysctls": {
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "123", Name: "123",
Namespace: "ns", Namespace: "ns",
@ -7452,7 +7634,7 @@ func TestValidatePod(t *testing.T) {
}, },
}, },
}, },
{ // valid extended resources for init container "valid extended resources for init container": {
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
Spec: core.PodSpec{ Spec: core.PodSpec{
InitContainers: []core.Container{ InitContainers: []core.Container{
@ -7476,7 +7658,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
}, },
{ // valid extended resources for regular container "valid extended resources for regular container": {
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
Spec: core.PodSpec{ Spec: core.PodSpec{
InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, InitContainers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
@ -7500,7 +7682,7 @@ func TestValidatePod(t *testing.T) {
DNSPolicy: core.DNSClusterFirst, DNSPolicy: core.DNSClusterFirst,
}, },
}, },
{ // valid serviceaccount token projected volume with serviceaccount name specified "valid serviceaccount token projected volume with serviceaccount name specified": {
ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"}, ObjectMeta: metav1.ObjectMeta{Name: "valid-extended", Namespace: "ns"},
Spec: core.PodSpec{ Spec: core.PodSpec{
ServiceAccountName: "some-service-account", ServiceAccountName: "some-service-account",
@ -7527,11 +7709,25 @@ func TestValidatePod(t *testing.T) {
}, },
}, },
}, },
"ephemeral volume + PVC, no conflict between them": {
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Volumes: []core.Volume{
{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
{Name: "ephemeral", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
},
} }
for _, pod := range successCases { for k, v := range successCases {
if errs := ValidatePodCreate(&pod, PodValidationOptions{}); len(errs) != 0 { t.Run(k, func(t *testing.T) {
t.Errorf("expected success: %v", errs) if errs := ValidatePodCreate(&v, PodValidationOptions{}); len(errs) != 0 {
} t.Errorf("expected success: %v", errs)
}
})
} }
errorCases := map[string]struct { errorCases := map[string]struct {
@ -8421,15 +8617,47 @@ func TestValidatePod(t *testing.T) {
}, },
}, },
}, },
"final PVC name for ephemeral volume must be valid": {
expectedError: "spec.volumes[1].name: Invalid value: \"" + longVolName + "\": PVC name \"" + longPodName + "-" + longVolName + "\": must be no more than 253 characters",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{Name: longPodName, Namespace: "ns"},
Spec: core.PodSpec{
Volumes: []core.Volume{
{Name: "pvc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "my-pvc"}}},
{Name: longVolName, VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
},
},
"PersistentVolumeClaimVolumeSource must not reference a generated PVC": {
expectedError: "spec.volumes[0].persistentVolumeClaim.claimName: Invalid value: \"123-ephemeral-volume\": must not reference a PVC that gets created for an ephemeral volume",
spec: core.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "123", Namespace: "ns"},
Spec: core.PodSpec{
Volumes: []core.Volume{
{Name: "pvc-volume", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "123-ephemeral-volume"}}},
{Name: "ephemeral-volume", VolumeSource: core.VolumeSource{Ephemeral: &core.EphemeralVolumeSource{VolumeClaimTemplate: &validPVCTemplate}}},
},
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
RestartPolicy: core.RestartPolicyAlways,
DNSPolicy: core.DNSClusterFirst,
},
},
},
} }
for k, v := range errorCases { for k, v := range errorCases {
if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 { t.Run(k, func(t *testing.T) {
t.Errorf("expected failure for %q", k) if errs := ValidatePodCreate(&v.spec, PodValidationOptions{}); len(errs) == 0 {
} else if v.expectedError == "" { t.Errorf("expected failure")
t.Errorf("missing expectedError for %q, got %q", k, errs.ToAggregate().Error()) } else if v.expectedError == "" {
} else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) { t.Errorf("missing expectedError, got %q", errs.ToAggregate().Error())
t.Errorf("expected error for %q to contain %q, got %q", k, v.expectedError, actualError) } else if actualError := errs.ToAggregate().Error(); !strings.Contains(actualError, v.expectedError) {
} t.Errorf("expected error to contain %q, got %q", v.expectedError, actualError)
}
})
} }
} }

View File

@ -1433,6 +1433,27 @@ func (in *EphemeralContainers) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EphemeralVolumeSource) DeepCopyInto(out *EphemeralVolumeSource) {
*out = *in
if in.VolumeClaimTemplate != nil {
in, out := &in.VolumeClaimTemplate, &out.VolumeClaimTemplate
*out = new(PersistentVolumeClaimTemplate)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EphemeralVolumeSource.
func (in *EphemeralVolumeSource) DeepCopy() *EphemeralVolumeSource {
if in == nil {
return nil
}
out := new(EphemeralVolumeSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Event) DeepCopyInto(out *Event) { func (in *Event) DeepCopyInto(out *Event) {
*out = *in *out = *in
@ -2987,6 +3008,24 @@ func (in *PersistentVolumeClaimStatus) DeepCopy() *PersistentVolumeClaimStatus {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PersistentVolumeClaimTemplate) DeepCopyInto(out *PersistentVolumeClaimTemplate) {
*out = *in
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimTemplate.
func (in *PersistentVolumeClaimTemplate) DeepCopy() *PersistentVolumeClaimTemplate {
if in == nil {
return nil
}
out := new(PersistentVolumeClaimTemplate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PersistentVolumeClaimVolumeSource) DeepCopyInto(out *PersistentVolumeClaimVolumeSource) { func (in *PersistentVolumeClaimVolumeSource) DeepCopyInto(out *PersistentVolumeClaimVolumeSource) {
*out = *in *out = *in
@ -5748,6 +5787,11 @@ func (in *VolumeSource) DeepCopyInto(out *VolumeSource) {
*out = new(CSIVolumeSource) *out = new(CSIVolumeSource)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.Ephemeral != nil {
in, out := &in.Ephemeral, &out.Ephemeral
*out = new(EphemeralVolumeSource)
(*in).DeepCopyInto(*out)
}
return return
} }

View File

@ -98,6 +98,13 @@ func SetObjectDefaults_DaemonSet(in *v1beta1.DaemonSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -305,6 +312,13 @@ func SetObjectDefaults_Deployment(in *v1beta1.Deployment) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]
@ -553,6 +567,13 @@ func SetObjectDefaults_ReplicaSet(in *v1beta1.ReplicaSet) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
for i := range in.Spec.Template.Spec.InitContainers { for i := range in.Spec.Template.Spec.InitContainers {
a := &in.Spec.Template.Spec.InitContainers[i] a := &in.Spec.Template.Spec.InitContainers[i]

View File

@ -94,6 +94,13 @@ func SetObjectDefaults_PodPreset(in *v1alpha1.PodPreset) {
if a.VolumeSource.ScaleIO != nil { if a.VolumeSource.ScaleIO != nil {
v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO) v1.SetDefaults_ScaleIOVolumeSource(a.VolumeSource.ScaleIO)
} }
if a.VolumeSource.Ephemeral != nil {
if a.VolumeSource.Ephemeral.VolumeClaimTemplate != nil {
v1.SetDefaults_PersistentVolumeClaimSpec(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Limits)
v1.SetDefaults_ResourceList(&a.VolumeSource.Ephemeral.VolumeClaimTemplate.Spec.Resources.Requests)
}
}
} }
} }

View File

@ -44,7 +44,7 @@ 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"))
} }
vols, vErrs := apivalidation.ValidateVolumes(spec.Volumes, fldPath.Child("volumes")) vols, vErrs := apivalidation.ValidateVolumes(spec.Volumes, nil, 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"))...)

View File

@ -137,6 +137,7 @@ filegroup(
"//pkg/controller/util/node:all-srcs", "//pkg/controller/util/node:all-srcs",
"//pkg/controller/volume/attachdetach:all-srcs", "//pkg/controller/volume/attachdetach:all-srcs",
"//pkg/controller/volume/common:all-srcs", "//pkg/controller/volume/common:all-srcs",
"//pkg/controller/volume/ephemeral:all-srcs",
"//pkg/controller/volume/events:all-srcs", "//pkg/controller/volume/events:all-srcs",
"//pkg/controller/volume/expand:all-srcs", "//pkg/controller/volume/expand:all-srcs",
"//pkg/controller/volume/persistentvolume:all-srcs", "//pkg/controller/volume/persistentvolume:all-srcs",

View File

@ -203,7 +203,7 @@ func NewAttachDetachController(
// This custom indexer will index pods by its PVC keys. Then we don't need // This custom indexer will index pods by its PVC keys. Then we don't need
// to iterate all pods every time to find pods which reference given PVC. // to iterate all pods every time to find pods which reference given PVC.
if err := common.AddIndexerIfNotPresent(adc.podIndexer, common.PodPVCIndex, common.PodPVCIndexFunc); err != nil { if err := common.AddPodPVCIndexerIfNotPresent(adc.podIndexer); err != nil {
return nil, fmt.Errorf("Could not initialize attach detach controller: %v", err) return nil, fmt.Errorf("Could not initialize attach detach controller: %v", err)
} }
@ -425,7 +425,7 @@ func (adc *attachDetachController) populateDesiredStateOfWorld() error {
// The volume specs present in the ActualStateOfWorld are nil, let's replace those // The volume specs present in the ActualStateOfWorld are nil, let's replace those
// with the correct ones found on pods. The present in the ASW with no corresponding // with the correct ones found on pods. The present in the ASW with no corresponding
// pod will be detached and the spec is irrelevant. // pod will be detached and the spec is irrelevant.
volumeSpec, err := util.CreateVolumeSpec(podVolume, podToAdd.Namespace, nodeName, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator) volumeSpec, err := util.CreateVolumeSpec(podVolume, podToAdd, nodeName, &adc.volumePluginMgr, adc.pvcLister, adc.pvLister, adc.csiMigratedPluginManager, adc.intreeToCSITranslator)
if err != nil { if err != nil {
klog.Errorf( klog.Errorf(
"Error creating spec for volume %q, pod %q/%q: %v", "Error creating spec for volume %q, pod %q/%q: %v",

View File

@ -168,7 +168,7 @@ func (collector *attachDetachStateCollector) getVolumeInUseCount() volumeCount {
continue continue
} }
for _, podVolume := range pod.Spec.Volumes { for _, podVolume := range pod.Spec.Volumes {
volumeSpec, err := util.CreateVolumeSpec(podVolume, pod.Namespace, types.NodeName(pod.Spec.NodeName), collector.volumePluginMgr, collector.pvcLister, collector.pvLister, collector.csiMigratedPluginManager, collector.intreeToCSITranslator) volumeSpec, err := util.CreateVolumeSpec(podVolume, pod, types.NodeName(pod.Spec.NodeName), collector.volumePluginMgr, collector.pvcLister, collector.pvLister, collector.csiMigratedPluginManager, collector.intreeToCSITranslator)
if err != nil { if err != nil {
continue continue
} }

View File

@ -39,39 +39,49 @@ import (
// A volume.Spec that refers to an in-tree plugin spec is translated to refer // A volume.Spec that refers to an in-tree plugin spec is translated to refer
// to a migrated CSI plugin spec if all conditions for CSI migration on a node // to a migrated CSI plugin spec if all conditions for CSI migration on a node
// for the in-tree plugin is satisfied. // for the in-tree plugin is satisfied.
func CreateVolumeSpec(podVolume v1.Volume, podNamespace string, nodeName types.NodeName, vpm *volume.VolumePluginMgr, pvcLister corelisters.PersistentVolumeClaimLister, pvLister corelisters.PersistentVolumeLister, csiMigratedPluginManager csimigration.PluginManager, csiTranslator csimigration.InTreeToCSITranslator) (*volume.Spec, error) { func CreateVolumeSpec(podVolume v1.Volume, pod *v1.Pod, nodeName types.NodeName, vpm *volume.VolumePluginMgr, pvcLister corelisters.PersistentVolumeClaimLister, pvLister corelisters.PersistentVolumeLister, csiMigratedPluginManager csimigration.PluginManager, csiTranslator csimigration.InTreeToCSITranslator) (*volume.Spec, error) {
claimName := ""
readOnly := false
if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil { if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
claimName = pvcSource.ClaimName
readOnly = pvcSource.ReadOnly
}
if ephemeralSource := podVolume.VolumeSource.Ephemeral; ephemeralSource != nil && utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
claimName = pod.Name + "-" + podVolume.Name
readOnly = ephemeralSource.ReadOnly
}
if claimName != "" {
klog.V(10).Infof( klog.V(10).Infof(
"Found PVC, ClaimName: %q/%q", "Found PVC, ClaimName: %q/%q",
podNamespace, pod.Namespace,
pvcSource.ClaimName) claimName)
// If podVolume is a PVC, fetch the real PV behind the claim // If podVolume is a PVC, fetch the real PV behind the claim
pvName, pvcUID, err := getPVCFromCacheExtractPV( pvName, pvcUID, err := getPVCFromCacheExtractPV(
podNamespace, pvcSource.ClaimName, pvcLister) pod.Namespace, claimName, pvcLister)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"error processing PVC %q/%q: %v", "error processing PVC %q/%q: %v",
podNamespace, pod.Namespace,
pvcSource.ClaimName, claimName,
err) err)
} }
klog.V(10).Infof( klog.V(10).Infof(
"Found bound PV for PVC (ClaimName %q/%q pvcUID %v): pvName=%q", "Found bound PV for PVC (ClaimName %q/%q pvcUID %v): pvName=%q",
podNamespace, pod.Namespace,
pvcSource.ClaimName, claimName,
pvcUID, pvcUID,
pvName) pvName)
// Fetch actual PV object // Fetch actual PV object
volumeSpec, err := getPVSpecFromCache( volumeSpec, err := getPVSpecFromCache(
pvName, pvcSource.ReadOnly, pvcUID, pvLister) pvName, readOnly, pvcUID, pvLister)
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"error processing PVC %q/%q: %v", "error processing PVC %q/%q: %v",
podNamespace, pod.Namespace,
pvcSource.ClaimName, claimName,
err) err)
} }
@ -79,8 +89,8 @@ func CreateVolumeSpec(podVolume v1.Volume, podNamespace string, nodeName types.N
if err != nil { if err != nil {
return nil, fmt.Errorf( return nil, fmt.Errorf(
"error performing CSI migration checks and translation for PVC %q/%q: %v", "error performing CSI migration checks and translation for PVC %q/%q: %v",
podNamespace, pod.Namespace,
pvcSource.ClaimName, claimName,
err) err)
} }
@ -88,8 +98,8 @@ func CreateVolumeSpec(podVolume v1.Volume, podNamespace string, nodeName types.N
"Extracted volumeSpec (%v) from bound PV (pvName %q) and PVC (ClaimName %q/%q pvcUID %v)", "Extracted volumeSpec (%v) from bound PV (pvName %q) and PVC (ClaimName %q/%q pvcUID %v)",
volumeSpec.Name(), volumeSpec.Name(),
pvName, pvName,
podNamespace, pod.Namespace,
pvcSource.ClaimName, claimName,
pvcUID) pvcUID)
return volumeSpec, nil return volumeSpec, nil
@ -219,7 +229,7 @@ func ProcessPodVolumes(pod *v1.Pod, addVolumes bool, desiredStateOfWorld cache.D
// Process volume spec for each volume defined in pod // Process volume spec for each volume defined in pod
for _, podVolume := range pod.Spec.Volumes { for _, podVolume := range pod.Spec.Volumes {
volumeSpec, err := CreateVolumeSpec(podVolume, pod.Namespace, nodeName, volumePluginMgr, pvcLister, pvLister, csiMigratedPluginManager, csiTranslator) volumeSpec, err := CreateVolumeSpec(podVolume, pod, nodeName, volumePluginMgr, pvcLister, pvLister, csiMigratedPluginManager, csiTranslator)
if err != nil { if err != nil {
klog.V(10).Infof( klog.V(10).Infof(
"Error processing volume %q for pod %q/%q: %v", "Error processing volume %q for pod %q/%q: %v",

View File

@ -6,7 +6,9 @@ go_library(
importpath = "k8s.io/kubernetes/pkg/controller/volume/common", importpath = "k8s.io/kubernetes/pkg/controller/volume/common",
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//pkg/features:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library", "//staging/src/k8s.io/client-go/tools/cache:go_default_library",
], ],
) )

View File

@ -20,7 +20,9 @@ import (
"fmt" "fmt"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/cache"
"k8s.io/kubernetes/pkg/features"
) )
const ( const (
@ -28,19 +30,36 @@ const (
PodPVCIndex = "pod-pvc-index" PodPVCIndex = "pod-pvc-index"
) )
// PodPVCIndexFunc returns PVC keys for given pod // PodPVCIndexFunc creates an index function that returns PVC keys (=
func PodPVCIndexFunc(obj interface{}) ([]string, error) { // namespace/name) for given pod. If enabled, this includes the PVCs
pod, ok := obj.(*v1.Pod) // that might be created for generic ephemeral volumes.
if !ok { func PodPVCIndexFunc(genericEphemeralVolumeFeatureEnabled bool) func(obj interface{}) ([]string, error) {
return []string{}, nil return func(obj interface{}) ([]string, error) {
} pod, ok := obj.(*v1.Pod)
keys := []string{} if !ok {
for _, podVolume := range pod.Spec.Volumes { return []string{}, nil
if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
keys = append(keys, fmt.Sprintf("%s/%s", pod.Namespace, pvcSource.ClaimName))
} }
keys := []string{}
for _, podVolume := range pod.Spec.Volumes {
claimName := ""
if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
claimName = pvcSource.ClaimName
}
if ephemeralSource := podVolume.VolumeSource.Ephemeral; genericEphemeralVolumeFeatureEnabled && ephemeralSource != nil {
claimName = pod.Name + "-" + podVolume.Name
}
if claimName != "" {
keys = append(keys, fmt.Sprintf("%s/%s", pod.Namespace, claimName))
}
}
return keys, nil
} }
return keys, nil }
// AddPodPVCIndexerIfNotPresent adds the PodPVCIndexFunc with the current global setting for GenericEphemeralVolume.
func AddPodPVCIndexerIfNotPresent(indexer cache.Indexer) error {
return AddIndexerIfNotPresent(indexer, PodPVCIndex,
PodPVCIndexFunc(utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume)))
} }
// AddIndexerIfNotPresent adds the index function with the name into the cache indexer if not present // AddIndexerIfNotPresent adds the index function with the name into the cache indexer if not present

View File

@ -0,0 +1,62 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"controller.go",
"doc.go",
],
importpath = "k8s.io/kubernetes/pkg/controller/volume/ephemeral",
visibility = ["//visibility:public"],
deps = [
"//pkg/controller/volume/common:go_default_library",
"//pkg/controller/volume/events:go_default_library",
"//pkg/volume/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//staging/src/k8s.io/client-go/informers/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/typed/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//staging/src/k8s.io/client-go/tools/record:go_default_library",
"//staging/src/k8s.io/client-go/util/workqueue:go_default_library",
"//vendor/k8s.io/klog/v2:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["controller_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/controller:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/client-go/informers:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
"//staging/src/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/klog/v2:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,6 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- saad-ali
- jsafrane
- pohly

View File

@ -0,0 +1,286 @@
/*
Copyright 2020 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 ephemeral
import (
"context"
"fmt"
"time"
"k8s.io/klog/v2"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
coreinformers "k8s.io/client-go/informers/core/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
v1core "k8s.io/client-go/kubernetes/typed/core/v1"
corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
kcache "k8s.io/client-go/tools/cache"
"k8s.io/client-go/tools/record"
"k8s.io/client-go/util/workqueue"
"k8s.io/kubernetes/pkg/controller/volume/common"
"k8s.io/kubernetes/pkg/controller/volume/events"
"k8s.io/kubernetes/pkg/volume/util"
)
// Controller creates PVCs for ephemeral inline volumes in a pod spec.
type Controller interface {
Run(workers int, stopCh <-chan struct{})
}
type ephemeralController struct {
// kubeClient is the kube API client used by volumehost to communicate with
// the API server.
kubeClient clientset.Interface
// pvcLister is the shared PVC lister used to fetch and store PVC
// objects from the API server. It is shared with other controllers and
// therefore the PVC objects in its store should be treated as immutable.
pvcLister corelisters.PersistentVolumeClaimLister
pvcsSynced kcache.InformerSynced
// podLister is the shared Pod lister used to fetch Pod
// objects from the API server. It is shared with other controllers and
// therefore the Pod objects in its store should be treated as immutable.
podLister corelisters.PodLister
podSynced kcache.InformerSynced
// podIndexer has the common PodPVC indexer indexer installed To
// limit iteration over pods to those of interest.
podIndexer cache.Indexer
// recorder is used to record events in the API server
recorder record.EventRecorder
queue workqueue.RateLimitingInterface
}
// NewController creates an ephemeral volume controller.
func NewController(
kubeClient clientset.Interface,
podInformer coreinformers.PodInformer,
pvcInformer coreinformers.PersistentVolumeClaimInformer) (Controller, error) {
ec := &ephemeralController{
kubeClient: kubeClient,
podLister: podInformer.Lister(),
podIndexer: podInformer.Informer().GetIndexer(),
podSynced: podInformer.Informer().HasSynced,
pvcLister: pvcInformer.Lister(),
pvcsSynced: pvcInformer.Informer().HasSynced,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ephemeral_volume"),
}
eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartLogging(klog.Infof)
eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")})
ec.recorder = eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "ephemeral_volume"})
podInformer.Informer().AddEventHandler(kcache.ResourceEventHandlerFuncs{
AddFunc: ec.enqueuePod,
// The pod spec is immutable. Therefore the controller can ignore pod updates
// because there cannot be any changes that have to be copied into the generated
// PVC.
// Deletion of the PVC is handled through the owner reference and garbage collection.
// Therefore pod deletions also can be ignored.
})
pvcInformer.Informer().AddEventHandler(kcache.ResourceEventHandlerFuncs{
DeleteFunc: ec.onPVCDelete,
})
if err := common.AddPodPVCIndexerIfNotPresent(ec.podIndexer); err != nil {
return nil, fmt.Errorf("Could not initialize pvc protection controller: %v", err)
}
return ec, nil
}
func (ec *ephemeralController) enqueuePod(obj interface{}) {
pod, ok := obj.(*v1.Pod)
if !ok {
return
}
// Ignore pods which are already getting deleted.
if pod.DeletionTimestamp != nil {
return
}
for _, vol := range pod.Spec.Volumes {
if vol.Ephemeral != nil {
// It has at least one ephemeral inline volume, work on it.
key, err := kcache.DeletionHandlingMetaNamespaceKeyFunc(pod)
if err != nil {
runtime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", pod, err))
return
}
ec.queue.Add(key)
break
}
}
}
func (ec *ephemeralController) onPVCDelete(obj interface{}) {
pvc, ok := obj.(*v1.PersistentVolumeClaim)
if !ok {
return
}
// Someone deleted a PVC, either intentionally or
// accidentally. If there is a pod referencing it because of
// an ephemeral volume, then we should re-create the PVC.
// The common indexer does some prefiltering for us by
// limiting the list to those pods which reference
// the PVC.
objs, err := ec.podIndexer.ByIndex(common.PodPVCIndex, fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name))
if err != nil {
runtime.HandleError(fmt.Errorf("listing pods from cache: %v", err))
return
}
for _, obj := range objs {
ec.enqueuePod(obj)
}
}
func (ec *ephemeralController) Run(workers int, stopCh <-chan struct{}) {
defer runtime.HandleCrash()
defer ec.queue.ShutDown()
klog.Infof("Starting ephemeral volume controller")
defer klog.Infof("Shutting down ephemeral volume controller")
if !cache.WaitForNamedCacheSync("ephemeral", stopCh, ec.podSynced, ec.pvcsSynced) {
return
}
for i := 0; i < workers; i++ {
go wait.Until(ec.runWorker, time.Second, stopCh)
}
<-stopCh
}
func (ec *ephemeralController) runWorker() {
for ec.processNextWorkItem() {
}
}
func (ec *ephemeralController) processNextWorkItem() bool {
key, shutdown := ec.queue.Get()
if shutdown {
return false
}
defer ec.queue.Done(key)
err := ec.syncHandler(key.(string))
if err == nil {
ec.queue.Forget(key)
return true
}
runtime.HandleError(fmt.Errorf("%v failed with: %v", key, err))
ec.queue.AddRateLimited(key)
return true
}
// syncHandler is invoked for each pod which might need to be processed.
// If an error is returned from this function, the pod will be requeued.
func (ec *ephemeralController) syncHandler(key string) error {
namespace, name, err := kcache.SplitMetaNamespaceKey(key)
if err != nil {
return err
}
pod, err := ec.podLister.Pods(namespace).Get(name)
if err != nil {
if errors.IsNotFound(err) {
klog.V(5).Infof("ephemeral: nothing to do for pod %s, it is gone", key)
return nil
}
klog.V(5).Infof("Error getting pod %s/%s (uid: %q) from informer : %v", pod.Namespace, pod.Name, pod.UID, err)
return err
}
// Ignore pods which are already getting deleted.
if pod.DeletionTimestamp != nil {
klog.V(5).Infof("ephemeral: nothing to do for pod %s, it is marked for deletion", key)
return nil
}
for _, vol := range pod.Spec.Volumes {
if err := ec.handleVolume(pod, vol); err != nil {
ec.recorder.Event(pod, v1.EventTypeWarning, events.FailedBinding, fmt.Sprintf("ephemeral volume %s: %v", vol.Name, err))
return fmt.Errorf("pod %s, ephemeral volume %s: %v", key, vol.Name, err)
}
}
return nil
}
// handleEphemeralVolume is invoked for each volume of a pod.
func (ec *ephemeralController) handleVolume(pod *v1.Pod, vol v1.Volume) error {
klog.V(5).Infof("ephemeral: checking volume %s", vol.Name)
ephemeral := vol.Ephemeral
if ephemeral == nil {
return nil
}
pvcName := pod.Name + "-" + vol.Name
pvc, err := ec.pvcLister.PersistentVolumeClaims(pod.Namespace).Get(pvcName)
if err != nil && !errors.IsNotFound(err) {
return err
}
if pvc != nil {
if metav1.IsControlledBy(pvc, pod) {
// Already created, nothing more to do.
klog.V(5).Infof("ephemeral: volume %s: PVC %s already created", vol.Name, pvcName)
return nil
}
return fmt.Errorf("PVC %q (uid: %q) was not created for the pod",
util.GetPersistentVolumeClaimQualifiedName(pvc), pvc.UID)
}
// Create the PVC with pod as owner.
isTrue := true
pvc = &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{
Name: pvcName,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "v1",
Kind: "Pod",
Name: pod.Name,
UID: pod.UID,
Controller: &isTrue,
BlockOwnerDeletion: &isTrue,
},
},
Annotations: ephemeral.VolumeClaimTemplate.Annotations,
Labels: ephemeral.VolumeClaimTemplate.Labels,
},
Spec: ephemeral.VolumeClaimTemplate.Spec,
}
_, err = ec.kubeClient.CoreV1().PersistentVolumeClaims(pod.Namespace).Create(context.TODO(), pvc, metav1.CreateOptions{})
if err != nil {
return fmt.Errorf("create PVC %s: %v", pvcName, err)
}
return nil
}

View File

@ -0,0 +1,221 @@
/*
Copyright 2020 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 ephemeral
import (
"context"
"sort"
"testing"
"k8s.io/api/core/v1"
// storagev1 "k8s.io/api/storage/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"
kcache "k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/controller"
"github.com/stretchr/testify/assert"
)
var (
testPodName = "test-pod"
testNamespace = "my-namespace"
testPodUID = types.UID("uidpod1")
otherNamespace = "not-my-namespace"
ephemeralVolumeName = "ephemeral-volume"
testPod = makePod(testPodName, testNamespace, testPodUID)
testPodWithEphemeral = makePod(testPodName, testNamespace, testPodUID, *makeEphemeralVolume(ephemeralVolumeName))
testPodEphemeralClaim = makePVC(testPodName+"-"+ephemeralVolumeName, testNamespace, makeOwnerReference(testPodWithEphemeral, true))
conflictingClaim = makePVC(testPodName+"-"+ephemeralVolumeName, testNamespace, nil)
otherNamespaceClaim = makePVC(testPodName+"-"+ephemeralVolumeName, otherNamespace, nil)
)
func init() {
klog.InitFlags(nil)
}
func TestSyncHandler(t *testing.T) {
tests := []struct {
name string
podKey string
pvcs []*v1.PersistentVolumeClaim
pods []*v1.Pod
expectedPVCs []v1.PersistentVolumeClaim
expectedError bool
}{
{
name: "create",
pods: []*v1.Pod{testPodWithEphemeral},
podKey: podKey(testPodWithEphemeral),
expectedPVCs: []v1.PersistentVolumeClaim{*testPodEphemeralClaim},
},
{
name: "no-such-pod",
podKey: podKey(testPodWithEphemeral),
},
{
name: "pod-deleted",
pods: func() []*v1.Pod {
deleted := metav1.Now()
pods := []*v1.Pod{testPodWithEphemeral.DeepCopy()}
pods[0].DeletionTimestamp = &deleted
return pods
}(),
podKey: podKey(testPodWithEphemeral),
},
{
name: "no-volumes",
pods: []*v1.Pod{testPod},
podKey: podKey(testPod),
},
{
name: "create-with-other-PVC",
pods: []*v1.Pod{testPodWithEphemeral},
podKey: podKey(testPodWithEphemeral),
pvcs: []*v1.PersistentVolumeClaim{otherNamespaceClaim},
expectedPVCs: []v1.PersistentVolumeClaim{*otherNamespaceClaim, *testPodEphemeralClaim},
},
{
name: "wrong-PVC-owner",
pods: []*v1.Pod{testPodWithEphemeral},
podKey: podKey(testPodWithEphemeral),
pvcs: []*v1.PersistentVolumeClaim{conflictingClaim},
expectedPVCs: []v1.PersistentVolumeClaim{*conflictingClaim},
expectedError: true,
},
}
for _, tc := range tests {
// Run sequentially because of global logging.
t.Run(tc.name, func(t *testing.T) {
// There is no good way to shut down the informers. They spawn
// various goroutines and some of them (in particular shared informer)
// become very unhappy ("close on closed channel") when using a context
// that gets cancelled. Therefore we just keep everything running.
ctx := context.Background()
var objects []runtime.Object
for _, pod := range tc.pods {
objects = append(objects, pod)
}
for _, pvc := range tc.pvcs {
objects = append(objects, pvc)
}
fakeKubeClient := createTestClient(objects...)
informerFactory := informers.NewSharedInformerFactory(fakeKubeClient, controller.NoResyncPeriodFunc())
podInformer := informerFactory.Core().V1().Pods()
pvcInformer := informerFactory.Core().V1().PersistentVolumeClaims()
c, err := NewController(fakeKubeClient, podInformer, pvcInformer)
if err != nil {
t.Fatalf("error creating ephemeral controller : %v", err)
}
ec, _ := c.(*ephemeralController)
// Ensure informers are up-to-date.
go informerFactory.Start(ctx.Done())
informerFactory.WaitForCacheSync(ctx.Done())
cache.WaitForCacheSync(ctx.Done(), podInformer.Informer().HasSynced, pvcInformer.Informer().HasSynced)
err = ec.syncHandler(tc.podKey)
if err != nil && !tc.expectedError {
t.Fatalf("unexpected error while running handler: %v", err)
}
if err == nil && tc.expectedError {
t.Fatalf("unexpected success")
}
pvcs, err := fakeKubeClient.CoreV1().PersistentVolumeClaims("").List(ctx, metav1.ListOptions{})
if err != nil {
t.Fatalf("unexpected error while listing PVCs: %v", err)
}
assert.Equal(t, sortPVCs(tc.expectedPVCs), sortPVCs(pvcs.Items))
})
}
}
func makePVC(name, namespace string, owner *metav1.OwnerReference) *v1.PersistentVolumeClaim {
pvc := &v1.PersistentVolumeClaim{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
Spec: v1.PersistentVolumeClaimSpec{},
}
if owner != nil {
pvc.OwnerReferences = []metav1.OwnerReference{*owner}
}
return pvc
}
func makeEphemeralVolume(name string) *v1.Volume {
return &v1.Volume{
Name: name,
VolumeSource: v1.VolumeSource{
Ephemeral: &v1.EphemeralVolumeSource{
VolumeClaimTemplate: &v1.PersistentVolumeClaimTemplate{},
},
},
}
}
func makePod(name, namespace string, uid types.UID, volumes ...v1.Volume) *v1.Pod {
pvc := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace, UID: uid},
Spec: v1.PodSpec{
Volumes: volumes,
},
}
return pvc
}
func podKey(pod *v1.Pod) string {
key, _ := kcache.DeletionHandlingMetaNamespaceKeyFunc(testPodWithEphemeral)
return key
}
func makeOwnerReference(pod *v1.Pod, isController bool) *metav1.OwnerReference {
isTrue := true
return &metav1.OwnerReference{
APIVersion: "v1",
Kind: "Pod",
Name: pod.Name,
UID: pod.UID,
Controller: &isController,
BlockOwnerDeletion: &isTrue,
}
}
func sortPVCs(pvcs []v1.PersistentVolumeClaim) []v1.PersistentVolumeClaim {
sort.Slice(pvcs, func(i, j int) bool {
return pvcs[i].Namespace < pvcs[j].Namespace ||
pvcs[i].Name < pvcs[j].Name
})
return pvcs
}
func createTestClient(objects ...runtime.Object) *fake.Clientset {
fakeClient := fake.NewSimpleClientset(objects...)
return fakeClient
}

View File

@ -0,0 +1,21 @@
/*
Copyright 2020 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 ephemeral implements the controller part of
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-storage/1698-generic-ephemeral-volumes
//
// It was derived from the expand controller.
package ephemeral

View File

@ -134,7 +134,7 @@ func NewController(p ControllerParameters) (*PersistentVolumeController, error)
// This custom indexer will index pods by its PVC keys. Then we don't need // This custom indexer will index pods by its PVC keys. Then we don't need
// to iterate all pods every time to find pods which reference given PVC. // to iterate all pods every time to find pods which reference given PVC.
if err := common.AddIndexerIfNotPresent(controller.podIndexer, common.PodPVCIndex, common.PodPVCIndexFunc); err != nil { if err := common.AddPodPVCIndexerIfNotPresent(controller.podIndexer); err != nil {
return nil, fmt.Errorf("Could not initialize attach detach controller: %v", err) return nil, fmt.Errorf("Could not initialize attach detach controller: %v", err)
} }

View File

@ -55,14 +55,18 @@ type Controller struct {
// allows overriding of StorageObjectInUseProtection feature Enabled/Disabled for testing // allows overriding of StorageObjectInUseProtection feature Enabled/Disabled for testing
storageObjectInUseProtectionEnabled bool storageObjectInUseProtectionEnabled bool
// allows overriding of GenericEphemeralVolume feature Enabled/Disabled for testing
genericEphemeralVolumeFeatureEnabled bool
} }
// NewPVCProtectionController returns a new instance of PVCProtectionController. // NewPVCProtectionController returns a new instance of PVCProtectionController.
func NewPVCProtectionController(pvcInformer coreinformers.PersistentVolumeClaimInformer, podInformer coreinformers.PodInformer, cl clientset.Interface, storageObjectInUseProtectionFeatureEnabled bool) (*Controller, error) { func NewPVCProtectionController(pvcInformer coreinformers.PersistentVolumeClaimInformer, podInformer coreinformers.PodInformer, cl clientset.Interface, storageObjectInUseProtectionFeatureEnabled, genericEphemeralVolumeFeatureEnabled bool) (*Controller, error) {
e := &Controller{ e := &Controller{
client: cl, client: cl,
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pvcprotection"), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pvcprotection"),
storageObjectInUseProtectionEnabled: storageObjectInUseProtectionFeatureEnabled, storageObjectInUseProtectionEnabled: storageObjectInUseProtectionFeatureEnabled,
genericEphemeralVolumeFeatureEnabled: genericEphemeralVolumeFeatureEnabled,
} }
if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil { if cl != nil && cl.CoreV1().RESTClient().GetRateLimiter() != nil {
ratelimiter.RegisterMetricAndTrackRateLimiterUsage("persistentvolumeclaim_protection_controller", cl.CoreV1().RESTClient().GetRateLimiter()) ratelimiter.RegisterMetricAndTrackRateLimiterUsage("persistentvolumeclaim_protection_controller", cl.CoreV1().RESTClient().GetRateLimiter())
@ -80,7 +84,7 @@ func NewPVCProtectionController(pvcInformer coreinformers.PersistentVolumeClaimI
e.podLister = podInformer.Lister() e.podLister = podInformer.Lister()
e.podListerSynced = podInformer.Informer().HasSynced e.podListerSynced = podInformer.Informer().HasSynced
e.podIndexer = podInformer.Informer().GetIndexer() e.podIndexer = podInformer.Informer().GetIndexer()
if err := common.AddIndexerIfNotPresent(e.podIndexer, common.PodPVCIndex, common.PodPVCIndexFunc); err != nil { if err := common.AddIndexerIfNotPresent(e.podIndexer, common.PodPVCIndex, common.PodPVCIndexFunc(genericEphemeralVolumeFeatureEnabled)); err != nil {
return nil, fmt.Errorf("Could not initialize pvc protection controller: %v", err) return nil, fmt.Errorf("Could not initialize pvc protection controller: %v", err)
} }
podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
@ -236,6 +240,7 @@ func (c *Controller) isBeingUsed(pvc *v1.PersistentVolumeClaim) (bool, error) {
func (c *Controller) askInformer(pvc *v1.PersistentVolumeClaim) (bool, error) { func (c *Controller) askInformer(pvc *v1.PersistentVolumeClaim) (bool, error) {
klog.V(4).Infof("Looking for Pods using PVC %s/%s in the Informer's cache", pvc.Namespace, pvc.Name) klog.V(4).Infof("Looking for Pods using PVC %s/%s in the Informer's cache", pvc.Namespace, pvc.Name)
// The indexer is used to find pods which might use the PVC.
objs, err := c.podIndexer.ByIndex(common.PodPVCIndex, fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)) objs, err := c.podIndexer.ByIndex(common.PodPVCIndex, fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name))
if err != nil { if err != nil {
return false, fmt.Errorf("cache-based list of pods failed while processing %s/%s: %s", pvc.Namespace, pvc.Name, err.Error()) return false, fmt.Errorf("cache-based list of pods failed while processing %s/%s: %s", pvc.Namespace, pvc.Name, err.Error())
@ -245,6 +250,19 @@ func (c *Controller) askInformer(pvc *v1.PersistentVolumeClaim) (bool, error) {
if !ok { if !ok {
continue continue
} }
if c.genericEphemeralVolumeFeatureEnabled {
// We still need to look at each volume: that's redundant for volume.PersistentVolumeClaim,
// but for volume.Ephemeral we need to be sure that this particular PVC is the one
// created for the ephemeral volume.
if c.podUsesPVC(pod, pvc) {
return true, nil
}
continue
}
// This is the traditional behavior without GenericEphemeralVolume enabled.
if pod.Spec.NodeName == "" { if pod.Spec.NodeName == "" {
continue continue
} }
@ -265,7 +283,7 @@ func (c *Controller) askAPIServer(pvc *v1.PersistentVolumeClaim) (bool, error) {
} }
for _, pod := range podsList.Items { for _, pod := range podsList.Items {
if podUsesPVC(&pod, pvc.Name) { if c.podUsesPVC(&pod, pvc) {
return true, nil return true, nil
} }
} }
@ -274,13 +292,14 @@ func (c *Controller) askAPIServer(pvc *v1.PersistentVolumeClaim) (bool, error) {
return false, nil return false, nil
} }
func podUsesPVC(pod *v1.Pod, pvc string) bool { func (c *Controller) podUsesPVC(pod *v1.Pod, pvc *v1.PersistentVolumeClaim) bool {
// Check whether pvc is used by pod only if pod is scheduled, because // Check whether pvc is used by pod only if pod is scheduled, because
// kubelet sees pods after they have been scheduled and it won't allow // kubelet sees pods after they have been scheduled and it won't allow
// starting a pod referencing a PVC with a non-nil deletionTimestamp. // starting a pod referencing a PVC with a non-nil deletionTimestamp.
if pod.Spec.NodeName != "" { if pod.Spec.NodeName != "" {
for _, volume := range pod.Spec.Volumes { for _, volume := range pod.Spec.Volumes {
if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc { if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc.Name ||
c.genericEphemeralVolumeFeatureEnabled && !podIsShutDown(pod) && volume.Ephemeral != nil && pod.Name+"-"+volume.Name == pvc.Name && metav1.IsControlledBy(pvc, pod) {
klog.V(2).Infof("Pod %s/%s uses PVC %s", pod.Namespace, pod.Name, pvc) klog.V(2).Infof("Pod %s/%s uses PVC %s", pod.Namespace, pod.Name, pvc)
return true return true
} }
@ -289,6 +308,43 @@ func podUsesPVC(pod *v1.Pod, pvc string) bool {
return false return false
} }
// podIsShutDown returns true if kubelet is done with the pod or
// it was force-deleted.
func podIsShutDown(pod *v1.Pod) bool {
// The following text is based on how pod shutdown was
// initially described to me. During PR review, it was pointed out
// that this is not correct: "deleteGracePeriodSeconds tells
// kubelet when it can start force terminating the
// containers. Volume teardown only starts after containers
// are termianted. So there is an additional time period after
// the grace period where volume teardown is happening."
//
// TODO (https://github.com/kubernetes/enhancements/issues/1698#issuecomment-655344680):
// investigate what kubelet really does and if necessary,
// add some other signal for "kubelet is done". For now the check
// is used only for ephemeral volumes, because it
// is needed to avoid the deadlock.
//
// A pod that has a deletionTimestamp and a zero
// deletionGracePeriodSeconds
// a) has been processed by kubelet and is ready for deletion or
// b) was force-deleted.
//
// It's now just waiting for garbage collection. We could wait
// for it to actually get removed, but that may be blocked by
// finalizers for the pod and thus get delayed.
//
// Worse, it is possible that there is a cyclic dependency
// (pod finalizer waits for PVC to get removed, PVC protection
// controller waits for pod to get removed). By considering
// the PVC unused in this case, we allow the PVC to get
// removed and break such a cycle.
//
// Therefore it is better to proceed with PVC removal,
// which is safe (case a) and/or desirable (case b).
return pod.DeletionTimestamp != nil && pod.DeletionGracePeriodSeconds != nil && *pod.DeletionGracePeriodSeconds == 0
}
// pvcAddedUpdated reacts to pvc added/updated events // pvcAddedUpdated reacts to pvc added/updated events
func (c *Controller) pvcAddedUpdated(obj interface{}) { func (c *Controller) pvcAddedUpdated(obj interface{}) {
pvc, ok := obj.(*v1.PersistentVolumeClaim) pvc, ok := obj.(*v1.PersistentVolumeClaim)
@ -354,8 +410,11 @@ func (c *Controller) enqueuePVCs(pod *v1.Pod, deleted bool) {
// Enqueue all PVCs that the pod uses // Enqueue all PVCs that the pod uses
for _, volume := range pod.Spec.Volumes { for _, volume := range pod.Spec.Volumes {
if volume.PersistentVolumeClaim != nil { switch {
case volume.PersistentVolumeClaim != nil:
c.queue.Add(pod.Namespace + "/" + volume.PersistentVolumeClaim.ClaimName) c.queue.Add(pod.Namespace + "/" + volume.PersistentVolumeClaim.ClaimName)
case c.genericEphemeralVolumeFeatureEnabled && volume.Ephemeral != nil:
c.queue.Add(pod.Namespace + "/" + pod.Name + "-" + volume.Name)
} }
} }
} }

View File

@ -146,7 +146,7 @@ func generateUpdateErrorFunc(t *testing.T, failures int) clienttesting.ReactionF
} }
} }
func TestPVCProtectionController(t *testing.T) { func testPVCProtectionController(t *testing.T, genericEphemeralVolumeFeatureEnabled bool) {
pvcGVR := schema.GroupVersionResource{ pvcGVR := schema.GroupVersionResource{
Group: v1.GroupName, Group: v1.GroupName,
Version: "v1", Version: "v1",
@ -430,7 +430,7 @@ func TestPVCProtectionController(t *testing.T) {
podInformer := informers.Core().V1().Pods() podInformer := informers.Core().V1().Pods()
// Create the controller // Create the controller
ctrl, err := NewPVCProtectionController(pvcInformer, podInformer, client, test.storageObjectInUseProtectionEnabled) ctrl, err := NewPVCProtectionController(pvcInformer, podInformer, client, test.storageObjectInUseProtectionEnabled, genericEphemeralVolumeFeatureEnabled)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -518,3 +518,8 @@ func TestPVCProtectionController(t *testing.T) {
} }
} }
func TestPVCProtectionController(t *testing.T) {
t.Run("with-GenericEphemeralVolume", func(t *testing.T) { testPVCProtectionController(t, true) })
t.Run("without-GenericEphemeralVolume", func(t *testing.T) { testPVCProtectionController(t, false) })
}

View File

@ -661,13 +661,28 @@ func (b *volumeBinder) checkBindings(pod *v1.Pod, bindings []*BindingInfo, claim
return true, nil return true, nil
} }
func (b *volumeBinder) isVolumeBound(namespace string, vol *v1.Volume) (bool, *v1.PersistentVolumeClaim, error) { func (b *volumeBinder) isVolumeBound(pod *v1.Pod, vol *v1.Volume) (bound bool, pvc *v1.PersistentVolumeClaim, err error) {
if vol.PersistentVolumeClaim == nil { pvcName := ""
ephemeral := false
switch {
case vol.PersistentVolumeClaim != nil:
pvcName = vol.PersistentVolumeClaim.ClaimName
case vol.Ephemeral != nil &&
utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume):
// Generic ephemeral inline volumes also use a PVC,
// just with a computed name, and...
pvcName = pod.Name + "-" + vol.Name
ephemeral = true
default:
return true, nil, nil return true, nil, nil
} }
pvcName := vol.PersistentVolumeClaim.ClaimName bound, pvc, err = b.isPVCBound(pod.Namespace, pvcName)
return b.isPVCBound(namespace, pvcName) // ... the PVC must be owned by the pod.
if ephemeral && err == nil && pvc != nil && !metav1.IsControlledBy(pvc, pod) {
return false, nil, fmt.Errorf("PVC %s/%s is not owned by pod", pod.Namespace, pvcName)
}
return
} }
func (b *volumeBinder) isPVCBound(namespace, pvcName string) (bool, *v1.PersistentVolumeClaim, error) { func (b *volumeBinder) isPVCBound(namespace, pvcName string) (bool, *v1.PersistentVolumeClaim, error) {
@ -703,7 +718,7 @@ func (b *volumeBinder) isPVCFullyBound(pvc *v1.PersistentVolumeClaim) bool {
// arePodVolumesBound returns true if all volumes are fully bound // arePodVolumesBound returns true if all volumes are fully bound
func (b *volumeBinder) arePodVolumesBound(pod *v1.Pod) bool { func (b *volumeBinder) arePodVolumesBound(pod *v1.Pod) bool {
for _, vol := range pod.Spec.Volumes { for _, vol := range pod.Spec.Volumes {
if isBound, _, _ := b.isVolumeBound(pod.Namespace, &vol); !isBound { if isBound, _, _ := b.isVolumeBound(pod, &vol); !isBound {
// Pod has at least one PVC that needs binding // Pod has at least one PVC that needs binding
return false return false
} }
@ -719,7 +734,7 @@ func (b *volumeBinder) GetPodVolumes(pod *v1.Pod) (boundClaims []*v1.PersistentV
unboundClaimsDelayBinding = []*v1.PersistentVolumeClaim{} unboundClaimsDelayBinding = []*v1.PersistentVolumeClaim{}
for _, vol := range pod.Spec.Volumes { for _, vol := range pod.Spec.Volumes {
volumeBound, pvc, err := b.isVolumeBound(pod.Namespace, &vol) volumeBound, pvc, err := b.isVolumeBound(pod, &vol)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }

View File

@ -306,6 +306,12 @@ const (
// spreading and disables legacy SelectorSpread plugin. // spreading and disables legacy SelectorSpread plugin.
DefaultPodTopologySpread featuregate.Feature = "DefaultPodTopologySpread" DefaultPodTopologySpread featuregate.Feature = "DefaultPodTopologySpread"
// owner: @pohly
// alpha: v1.19
//
// Enables generic ephemeral inline volume support for pods
GenericEphemeralVolume featuregate.Feature = "GenericEphemeralVolume"
// owner: @tallclair // owner: @tallclair
// alpha: v1.12 // alpha: v1.12
// beta: v1.14 // beta: v1.14
@ -678,6 +684,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
CSIBlockVolume: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20 CSIBlockVolume: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.20
CSIInlineVolume: {Default: true, PreRelease: featuregate.Beta}, CSIInlineVolume: {Default: true, PreRelease: featuregate.Beta},
CSIStorageCapacity: {Default: false, PreRelease: featuregate.Alpha}, CSIStorageCapacity: {Default: false, PreRelease: featuregate.Alpha},
GenericEphemeralVolume: {Default: false, PreRelease: featuregate.Alpha},
RuntimeClass: {Default: true, PreRelease: featuregate.Beta}, RuntimeClass: {Default: true, PreRelease: featuregate.Beta},
NodeLease: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, NodeLease: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
SCTPSupport: {Default: true, PreRelease: featuregate.Beta}, SCTPSupport: {Default: true, PreRelease: featuregate.Beta},

View File

@ -316,7 +316,7 @@ func (dswp *desiredStateOfWorldPopulator) processPodVolumes(
} }
pvc, volumeSpec, volumeGidValue, err := pvc, volumeSpec, volumeGidValue, err :=
dswp.createVolumeSpec(podVolume, pod.Name, pod.Namespace, mounts, devices) dswp.createVolumeSpec(podVolume, pod, mounts, devices)
if err != nil { if err != nil {
klog.Errorf( klog.Errorf(
"Error processing volume %q for pod %q: %v", "Error processing volume %q for pod %q: %v",
@ -491,29 +491,50 @@ func (dswp *desiredStateOfWorldPopulator) deleteProcessedPod(
// specified volume. It dereference any PVC to get PV objects, if needed. // specified volume. It dereference any PVC to get PV objects, if needed.
// Returns an error if unable to obtain the volume at this time. // Returns an error if unable to obtain the volume at this time.
func (dswp *desiredStateOfWorldPopulator) createVolumeSpec( func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
podVolume v1.Volume, podName string, podNamespace string, mounts, devices sets.String) (*v1.PersistentVolumeClaim, *volume.Spec, string, error) { podVolume v1.Volume, pod *v1.Pod, mounts, devices sets.String) (*v1.PersistentVolumeClaim, *volume.Spec, string, error) {
if pvcSource := pvcSource := podVolume.VolumeSource.PersistentVolumeClaim
podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil { ephemeral := false
if pvcSource == nil &&
podVolume.VolumeSource.Ephemeral != nil &&
utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
// Generic ephemeral inline volumes are handled the
// same way as a PVC reference. The only additional
// constraint (checked below) is that the PVC must be
// owned by the pod.
pvcSource = &v1.PersistentVolumeClaimVolumeSource{
ClaimName: pod.Name + "-" + podVolume.Name,
ReadOnly: podVolume.VolumeSource.Ephemeral.ReadOnly,
}
ephemeral = true
}
if pvcSource != nil {
klog.V(5).Infof( klog.V(5).Infof(
"Found PVC, ClaimName: %q/%q", "Found PVC, ClaimName: %q/%q",
podNamespace, pod.Namespace,
pvcSource.ClaimName) pvcSource.ClaimName)
// If podVolume is a PVC, fetch the real PV behind the claim // If podVolume is a PVC, fetch the real PV behind the claim
pvc, err := dswp.getPVCExtractPV( pvc, err := dswp.getPVCExtractPV(
podNamespace, pvcSource.ClaimName) pod.Namespace, pvcSource.ClaimName)
if err != nil { if err != nil {
return nil, nil, "", fmt.Errorf( return nil, nil, "", fmt.Errorf(
"error processing PVC %s/%s: %v", "error processing PVC %s/%s: %v",
podNamespace, pod.Namespace,
pvcSource.ClaimName, pvcSource.ClaimName,
err) err)
} }
if ephemeral && !metav1.IsControlledBy(pvc, pod) {
return nil, nil, "", fmt.Errorf(
"error processing PVC %s/%s: not the ephemeral PVC for the pod",
pod.Namespace,
pvcSource.ClaimName,
)
}
pvName, pvcUID := pvc.Spec.VolumeName, pvc.UID pvName, pvcUID := pvc.Spec.VolumeName, pvc.UID
klog.V(5).Infof( klog.V(5).Infof(
"Found bound PV for PVC (ClaimName %q/%q pvcUID %v): pvName=%q", "Found bound PV for PVC (ClaimName %q/%q pvcUID %v): pvName=%q",
podNamespace, pod.Namespace,
pvcSource.ClaimName, pvcSource.ClaimName,
pvcUID, pvcUID,
pvName) pvName)
@ -524,7 +545,7 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
if err != nil { if err != nil {
return nil, nil, "", fmt.Errorf( return nil, nil, "", fmt.Errorf(
"error processing PVC %s/%s: %v", "error processing PVC %s/%s: %v",
podNamespace, pod.Namespace,
pvcSource.ClaimName, pvcSource.ClaimName,
err) err)
} }
@ -533,7 +554,7 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
"Extracted volumeSpec (%v) from bound PV (pvName %q) and PVC (ClaimName %q/%q pvcUID %v)", "Extracted volumeSpec (%v) from bound PV (pvName %q) and PVC (ClaimName %q/%q pvcUID %v)",
volumeSpec.Name(), volumeSpec.Name(),
pvName, pvName,
podNamespace, pod.Namespace,
pvcSource.ClaimName, pvcSource.ClaimName,
pvcUID) pvcUID)

View File

@ -518,7 +518,7 @@ func TestCreateVolumeSpec_Valid_File_VolumeMounts(t *testing.T) {
fakePodManager.AddPod(pod) fakePodManager.AddPod(pod)
mountsMap, devicesMap := util.GetPodVolumeNames(pod) mountsMap, devicesMap := util.GetPodVolumeNames(pod)
_, volumeSpec, _, err := _, volumeSpec, _, err :=
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) dswp.createVolumeSpec(pod.Spec.Volumes[0], pod, mountsMap, devicesMap)
// Assert // Assert
if volumeSpec == nil || err != nil { if volumeSpec == nil || err != nil {
@ -564,7 +564,7 @@ func TestCreateVolumeSpec_Valid_Nil_VolumeMounts(t *testing.T) {
fakePodManager.AddPod(pod) fakePodManager.AddPod(pod)
mountsMap, devicesMap := util.GetPodVolumeNames(pod) mountsMap, devicesMap := util.GetPodVolumeNames(pod)
_, volumeSpec, _, err := _, volumeSpec, _, err :=
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) dswp.createVolumeSpec(pod.Spec.Volumes[0], pod, mountsMap, devicesMap)
// Assert // Assert
if volumeSpec == nil || err != nil { if volumeSpec == nil || err != nil {
@ -610,7 +610,7 @@ func TestCreateVolumeSpec_Valid_Block_VolumeDevices(t *testing.T) {
fakePodManager.AddPod(pod) fakePodManager.AddPod(pod)
mountsMap, devicesMap := util.GetPodVolumeNames(pod) mountsMap, devicesMap := util.GetPodVolumeNames(pod)
_, volumeSpec, _, err := _, volumeSpec, _, err :=
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) dswp.createVolumeSpec(pod.Spec.Volumes[0], pod, mountsMap, devicesMap)
// Assert // Assert
if volumeSpec == nil || err != nil { if volumeSpec == nil || err != nil {
@ -656,7 +656,7 @@ func TestCreateVolumeSpec_Invalid_File_VolumeDevices(t *testing.T) {
fakePodManager.AddPod(pod) fakePodManager.AddPod(pod)
mountsMap, devicesMap := util.GetPodVolumeNames(pod) mountsMap, devicesMap := util.GetPodVolumeNames(pod)
_, volumeSpec, _, err := _, volumeSpec, _, err :=
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) dswp.createVolumeSpec(pod.Spec.Volumes[0], pod, mountsMap, devicesMap)
// Assert // Assert
if volumeSpec != nil || err == nil { if volumeSpec != nil || err == nil {
@ -702,7 +702,7 @@ func TestCreateVolumeSpec_Invalid_Block_VolumeMounts(t *testing.T) {
fakePodManager.AddPod(pod) fakePodManager.AddPod(pod)
mountsMap, devicesMap := util.GetPodVolumeNames(pod) mountsMap, devicesMap := util.GetPodVolumeNames(pod)
_, volumeSpec, _, err := _, volumeSpec, _, err :=
dswp.createVolumeSpec(pod.Spec.Volumes[0], pod.Name, pod.Namespace, mountsMap, devicesMap) dswp.createVolumeSpec(pod.Spec.Volumes[0], pod, mountsMap, devicesMap)
// Assert // Assert
if volumeSpec != nil || err == nil { if volumeSpec != nil || err == nil {

View File

@ -10,6 +10,7 @@ go_library(
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
deps = [ deps = [
"//pkg/api/v1/pod:go_default_library", "//pkg/api/v1/pod:go_default_library",
"//pkg/features:go_default_library",
"//pkg/scheduler/apis/config:go_default_library", "//pkg/scheduler/apis/config:go_default_library",
"//pkg/scheduler/framework/runtime:go_default_library", "//pkg/scheduler/framework/runtime:go_default_library",
"//pkg/scheduler/framework/v1alpha1:go_default_library", "//pkg/scheduler/framework/v1alpha1:go_default_library",
@ -19,8 +20,10 @@ go_library(
"//pkg/scheduler/profile:go_default_library", "//pkg/scheduler/profile:go_default_library",
"//pkg/scheduler/util:go_default_library", "//pkg/scheduler/util:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library", "//staging/src/k8s.io/client-go/rest:go_default_library",
"//staging/src/k8s.io/kube-scheduler/extender/v1:go_default_library", "//staging/src/k8s.io/kube-scheduler/extender/v1:go_default_library",

View File

@ -29,9 +29,12 @@ import (
"k8s.io/klog/v2" "k8s.io/klog/v2"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
extenderv1 "k8s.io/kube-scheduler/extender/v1" extenderv1 "k8s.io/kube-scheduler/extender/v1"
podutil "k8s.io/kubernetes/pkg/api/v1/pod" podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/scheduler/framework/runtime" "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1" framework "k8s.io/kubernetes/pkg/scheduler/framework/v1alpha1"
internalcache "k8s.io/kubernetes/pkg/scheduler/internal/cache" internalcache "k8s.io/kubernetes/pkg/scheduler/internal/cache"
@ -575,11 +578,19 @@ func podPassesBasicChecks(pod *v1.Pod, pvcLister corelisters.PersistentVolumeCla
manifest := &(pod.Spec) manifest := &(pod.Spec)
for i := range manifest.Volumes { for i := range manifest.Volumes {
volume := &manifest.Volumes[i] volume := &manifest.Volumes[i]
if volume.PersistentVolumeClaim == nil { var pvcName string
// Volume is not a PVC, ignore ephemeral := false
switch {
case volume.PersistentVolumeClaim != nil:
pvcName = volume.PersistentVolumeClaim.ClaimName
case volume.Ephemeral != nil &&
utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume):
pvcName = pod.Name + "-" + volume.Name
ephemeral = true
default:
// Volume is not using a PVC, ignore
continue continue
} }
pvcName := volume.PersistentVolumeClaim.ClaimName
pvc, err := pvcLister.PersistentVolumeClaims(namespace).Get(pvcName) pvc, err := pvcLister.PersistentVolumeClaims(namespace).Get(pvcName)
if err != nil { if err != nil {
// The error has already enough context ("persistentvolumeclaim "myclaim" not found") // The error has already enough context ("persistentvolumeclaim "myclaim" not found")
@ -589,6 +600,11 @@ func podPassesBasicChecks(pod *v1.Pod, pvcLister corelisters.PersistentVolumeCla
if pvc.DeletionTimestamp != nil { if pvc.DeletionTimestamp != nil {
return fmt.Errorf("persistentvolumeclaim %q is being deleted", pvc.Name) return fmt.Errorf("persistentvolumeclaim %q is being deleted", pvc.Name)
} }
if ephemeral &&
!metav1.IsControlledBy(pvc, pod) {
return fmt.Errorf("persistentvolumeclaim %q was not created for the pod", pvc.Name)
}
} }
return nil return nil

View File

@ -61,7 +61,8 @@ func (d *stateData) Clone() framework.StateData {
// In the Filter phase, pod binding cache is created for the pod and used in // In the Filter phase, pod binding cache is created for the pod and used in
// Reserve and PreBind phases. // Reserve and PreBind phases.
type VolumeBinding struct { type VolumeBinding struct {
Binder scheduling.SchedulerVolumeBinder Binder scheduling.SchedulerVolumeBinder
GenericEphemeralVolumeFeatureEnabled bool
} }
var _ framework.PreFilterPlugin = &VolumeBinding{} var _ framework.PreFilterPlugin = &VolumeBinding{}
@ -77,9 +78,10 @@ func (pl *VolumeBinding) Name() string {
return Name return Name
} }
func podHasPVCs(pod *v1.Pod) bool { func (pl *VolumeBinding) podHasPVCs(pod *v1.Pod) bool {
for _, vol := range pod.Spec.Volumes { for _, vol := range pod.Spec.Volumes {
if vol.PersistentVolumeClaim != nil { if vol.PersistentVolumeClaim != nil ||
pl.GenericEphemeralVolumeFeatureEnabled && vol.Ephemeral != nil {
return true return true
} }
} }
@ -91,7 +93,7 @@ func podHasPVCs(pod *v1.Pod) bool {
// UnschedulableAndUnresolvable is returned. // UnschedulableAndUnresolvable is returned.
func (pl *VolumeBinding) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) *framework.Status { func (pl *VolumeBinding) PreFilter(ctx context.Context, state *framework.CycleState, pod *v1.Pod) *framework.Status {
// If pod does not reference any PVC, we don't need to do anything. // If pod does not reference any PVC, we don't need to do anything.
if !podHasPVCs(pod) { if !pl.podHasPVCs(pod) {
state.Write(stateKey, &stateData{skip: true}) state.Write(stateKey, &stateData{skip: true})
return nil return nil
} }
@ -268,7 +270,8 @@ func New(plArgs runtime.Object, fh framework.FrameworkHandle) (framework.Plugin,
} }
binder := scheduling.NewVolumeBinder(fh.ClientSet(), podInformer, nodeInformer, csiNodeInformer, pvcInformer, pvInformer, storageClassInformer, capacityCheck, time.Duration(args.BindTimeoutSeconds)*time.Second) binder := scheduling.NewVolumeBinder(fh.ClientSet(), podInformer, nodeInformer, csiNodeInformer, pvcInformer, pvInformer, storageClassInformer, capacityCheck, time.Duration(args.BindTimeoutSeconds)*time.Second)
return &VolumeBinding{ return &VolumeBinding{
Binder: binder, Binder: binder,
GenericEphemeralVolumeFeatureEnabled: utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume),
}, nil }, nil
} }

View File

@ -346,6 +346,18 @@ func TestValidatePodFailures(t *testing.T) {
}, },
} }
failGenericEphemeralPod := defaultPod()
failGenericEphemeralPod.Spec.Volumes = []api.Volume{
{
Name: "generic ephemeral volume",
VolumeSource: api.VolumeSource{
Ephemeral: &api.EphemeralVolumeSource{
VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{},
},
},
},
}
errorCases := map[string]struct { errorCases := map[string]struct {
pod *api.Pod pod *api.Pod
psp *policy.PodSecurityPolicy psp *policy.PodSecurityPolicy
@ -485,6 +497,11 @@ func TestValidatePodFailures(t *testing.T) {
psp: defaultPSP(), psp: defaultPSP(),
expectedError: "csi volumes are not allowed to be used", expectedError: "csi volumes are not allowed to be used",
}, },
"generic ephemeral volumes without proper policy set": {
pod: failGenericEphemeralPod,
psp: defaultPSP(),
expectedError: "ephemeral volumes are not allowed to be used",
},
} }
for name, test := range errorCases { for name, test := range errorCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
@ -888,6 +905,18 @@ func TestValidatePodSuccess(t *testing.T) {
}, },
} }
genericEphemeralPod := defaultPod()
genericEphemeralPod.Spec.Volumes = []api.Volume{
{
Name: "generic ephemeral volume",
VolumeSource: api.VolumeSource{
Ephemeral: &api.EphemeralVolumeSource{
VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{},
},
},
},
}
successCases := map[string]struct { successCases := map[string]struct {
pod *api.Pod pod *api.Pod
psp *policy.PodSecurityPolicy psp *policy.PodSecurityPolicy
@ -995,6 +1024,22 @@ func TestValidatePodSuccess(t *testing.T) {
return psp return psp
}(), }(),
}, },
"generic ephemeral volume policy with generic ephemeral volume used": {
pod: genericEphemeralPod,
psp: func() *policy.PodSecurityPolicy {
psp := defaultPSP()
psp.Spec.Volumes = []policy.FSType{policy.Ephemeral}
return psp
}(),
},
"policy.All with generic ephemeral volume used": {
pod: genericEphemeralPod,
psp: func() *policy.PodSecurityPolicy {
psp := defaultPSP()
psp.Spec.Volumes = []policy.FSType{policy.All}
return psp
}(),
},
} }
for name, test := range successCases { for name, test := range successCases {
@ -1328,6 +1373,7 @@ func defaultV1Pod() *v1.Pod {
// the FSTypeAll wildcard. // the FSTypeAll wildcard.
func TestValidateAllowedVolumes(t *testing.T) { func TestValidateAllowedVolumes(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
val := reflect.ValueOf(api.VolumeSource{}) val := reflect.ValueOf(api.VolumeSource{})

View File

@ -68,6 +68,7 @@ func GetAllFSTypesAsSet() sets.String {
string(policy.PortworxVolume), string(policy.PortworxVolume),
string(policy.ScaleIO), string(policy.ScaleIO),
string(policy.CSI), string(policy.CSI),
string(policy.Ephemeral),
) )
return fstypes return fstypes
} }
@ -131,6 +132,8 @@ func GetVolumeFSType(v api.Volume) (policy.FSType, error) {
return policy.ScaleIO, nil return policy.ScaleIO, nil
case v.CSI != nil: case v.CSI != nil:
return policy.CSI, nil return policy.CSI, nil
case v.Ephemeral != nil:
return policy.Ephemeral, nil
} }
return "", fmt.Errorf("unknown volume type for volume: %#v", v) return "", fmt.Errorf("unknown volume type for volume: %#v", v)

View File

@ -630,6 +630,7 @@ func TestAdmitCaps(t *testing.T) {
func TestAdmitVolumes(t *testing.T) { func TestAdmitVolumes(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
val := reflect.ValueOf(kapi.VolumeSource{}) val := reflect.ValueOf(kapi.VolumeSource{})

View File

@ -20,8 +20,10 @@ import (
"sync" "sync"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
pvutil "k8s.io/kubernetes/pkg/api/v1/persistentvolume" pvutil "k8s.io/kubernetes/pkg/api/v1/persistentvolume"
podutil "k8s.io/kubernetes/pkg/api/v1/pod" podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/third_party/forked/gonum/graph" "k8s.io/kubernetes/third_party/forked/gonum/graph"
"k8s.io/kubernetes/third_party/forked/gonum/graph/simple" "k8s.io/kubernetes/third_party/forked/gonum/graph/simple"
) )
@ -375,8 +377,14 @@ func (g *Graph) AddPod(pod *corev1.Pod) {
}) })
for _, v := range pod.Spec.Volumes { for _, v := range pod.Spec.Volumes {
claimName := ""
if v.PersistentVolumeClaim != nil { if v.PersistentVolumeClaim != nil {
pvcVertex := g.getOrCreateVertex_locked(pvcVertexType, pod.Namespace, v.PersistentVolumeClaim.ClaimName) claimName = v.PersistentVolumeClaim.ClaimName
} else if v.Ephemeral != nil && utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
claimName = pod.Name + "-" + v.Name
}
if claimName != "" {
pvcVertex := g.getOrCreateVertex_locked(pvcVertexType, pod.Namespace, claimName)
e := newDestinationEdge(pvcVertex, podVertex, nodeVertex) e := newDestinationEdge(pvcVertex, podVertex, nodeVertex)
g.graph.SetEdge(e) g.graph.SetEdge(e)
g.addEdgeToDestinationIndex_locked(e) g.addEdgeToDestinationIndex_locked(e)

View File

@ -190,6 +190,17 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
}) })
} }
if utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "ephemeral-volume-controller"},
Rules: []rbacv1.PolicyRule{
rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbacv1helpers.NewRule("get", "list", "watch", "create").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
eventsRule(),
},
})
}
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "generic-garbage-collector"}, ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "generic-garbage-collector"},
Rules: []rbacv1.PolicyRule{ Rules: []rbacv1.PolicyRule{

File diff suppressed because it is too large Load Diff

View File

@ -1371,6 +1371,37 @@ message EphemeralContainers {
repeated EphemeralContainer ephemeralContainers = 2; repeated EphemeralContainer ephemeralContainers = 2;
} }
// Represents an ephemeral volume that is handled by a normal storage driver.
message EphemeralVolumeSource {
// Will be used to create a stand-alone PVC to provision the volume.
// The pod in which this EphemeralVolumeSource is embedded will be the
// owner of the PVC, i.e. the PVC will be deleted together with the
// pod. The name of the PVC will be `<pod name>-<volume name>` where
// `<volume name>` is the name from the `PodSpec.Volumes` array
// entry. Pod validation will reject the pod if the concatenated name
// is not valid for a PVC (for example, too long).
//
// An existing PVC with that name that is not owned by the pod
// will *not* be used for the pod to avoid using an unrelated
// volume by mistake. Starting the pod is then blocked until
// the unrelated PVC is removed. If such a pre-created PVC is
// meant to be used by the pod, the PVC has to updated with an
// owner reference to the pod once the pod exists. Normally
// this should not be necessary, but it may be useful when
// manually reconstructing a broken cluster.
//
// This field is read-only and no changes will be made by Kubernetes
// to the PVC after it has been created.
//
// Required, must not be nil.
optional PersistentVolumeClaimTemplate volumeClaimTemplate = 1;
// Specifies a read-only configuration for the volume.
// Defaults to false (read/write).
// +optional
optional bool readOnly = 2;
}
// Event is a report of an event somewhere in the cluster. // Event is a report of an event somewhere in the cluster.
message Event { message Event {
// Standard object's metadata. // Standard object's metadata.
@ -2682,6 +2713,23 @@ message PersistentVolumeClaimStatus {
repeated PersistentVolumeClaimCondition conditions = 4; repeated PersistentVolumeClaimCondition conditions = 4;
} }
// PersistentVolumeClaimTemplate is used to produce
// PersistentVolumeClaim objects as part of an EphemeralVolumeSource.
message PersistentVolumeClaimTemplate {
// May contain labels and annotations that will be copied into the PVC
// when creating it. No other fields are allowed and will be rejected during
// validation.
//
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
// The specification for the PersistentVolumeClaim. The entire content is
// copied unchanged into the PVC that gets created from this
// template. The same fields as in a PersistentVolumeClaim
// are also valid here.
optional PersistentVolumeClaimSpec spec = 2;
}
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. // PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
// This volume finds the bound PV and mounts that volume for the pod. A // This volume finds the bound PV and mounts that volume for the pod. A
// PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another // PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another
@ -5341,6 +5389,34 @@ message VolumeSource {
// CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). // CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).
// +optional // +optional
optional CSIVolumeSource csi = 28; optional CSIVolumeSource csi = 28;
// Ephemeral represents a volume that is handled by a cluster storage driver (Alpha feature).
// The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts,
// and deleted when the pod is removed.
//
// Use this if:
// a) the volume is only needed while the pod runs,
// b) features of normal volumes like restoring from snapshot or capacity
// tracking are needed,
// c) the storage driver is specified through a storage class, and
// d) the storage driver supports dynamic volume provisioning through
// a PersistentVolumeClaim (see EphemeralVolumeSource for more
// information on the connection between this volume type
// and PersistentVolumeClaim).
//
// Use PersistentVolumeClaim or one of the vendor-specific
// APIs for volumes that persist for longer than the lifecycle
// of an individual pod.
//
// Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to
// be used that way - see the documentation of the driver for
// more information.
//
// A pod can use both types of ephemeral volumes and
// persistent volumes at the same time.
//
// +optional
optional EphemeralVolumeSource ephemeral = 29;
} }
// Represents a vSphere volume resource. // Represents a vSphere volume resource.

View File

@ -156,6 +156,33 @@ type VolumeSource struct {
// CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature). // CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).
// +optional // +optional
CSI *CSIVolumeSource `json:"csi,omitempty" protobuf:"bytes,28,opt,name=csi"` CSI *CSIVolumeSource `json:"csi,omitempty" protobuf:"bytes,28,opt,name=csi"`
// Ephemeral represents a volume that is handled by a cluster storage driver (Alpha feature).
// The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts,
// and deleted when the pod is removed.
//
// Use this if:
// a) the volume is only needed while the pod runs,
// b) features of normal volumes like restoring from snapshot or capacity
// tracking are needed,
// c) the storage driver is specified through a storage class, and
// d) the storage driver supports dynamic volume provisioning through
// a PersistentVolumeClaim (see EphemeralVolumeSource for more
// information on the connection between this volume type
// and PersistentVolumeClaim).
//
// Use PersistentVolumeClaim or one of the vendor-specific
// APIs for volumes that persist for longer than the lifecycle
// of an individual pod.
//
// Use CSI for light-weight local ephemeral volumes if the CSI driver is meant to
// be used that way - see the documentation of the driver for
// more information.
//
// A pod can use both types of ephemeral volumes and
// persistent volumes at the same time.
//
// +optional
Ephemeral *EphemeralVolumeSource `json:"ephemeral,omitempty" protobuf:"bytes,29,opt,name=ephemeral"`
} }
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. // PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
@ -1746,6 +1773,54 @@ type CSIVolumeSource struct {
NodePublishSecretRef *LocalObjectReference `json:"nodePublishSecretRef,omitempty" protobuf:"bytes,5,opt,name=nodePublishSecretRef"` NodePublishSecretRef *LocalObjectReference `json:"nodePublishSecretRef,omitempty" protobuf:"bytes,5,opt,name=nodePublishSecretRef"`
} }
// Represents an ephemeral volume that is handled by a normal storage driver.
type EphemeralVolumeSource struct {
// Will be used to create a stand-alone PVC to provision the volume.
// The pod in which this EphemeralVolumeSource is embedded will be the
// owner of the PVC, i.e. the PVC will be deleted together with the
// pod. The name of the PVC will be `<pod name>-<volume name>` where
// `<volume name>` is the name from the `PodSpec.Volumes` array
// entry. Pod validation will reject the pod if the concatenated name
// is not valid for a PVC (for example, too long).
//
// An existing PVC with that name that is not owned by the pod
// will *not* be used for the pod to avoid using an unrelated
// volume by mistake. Starting the pod is then blocked until
// the unrelated PVC is removed. If such a pre-created PVC is
// meant to be used by the pod, the PVC has to updated with an
// owner reference to the pod once the pod exists. Normally
// this should not be necessary, but it may be useful when
// manually reconstructing a broken cluster.
//
// This field is read-only and no changes will be made by Kubernetes
// to the PVC after it has been created.
//
// Required, must not be nil.
VolumeClaimTemplate *PersistentVolumeClaimTemplate `json:"volumeClaimTemplate,omitempty" protobuf:"bytes,1,opt,name=volumeClaimTemplate"`
// Specifies a read-only configuration for the volume.
// Defaults to false (read/write).
// +optional
ReadOnly bool `json:"readOnly,omitempty" protobuf:"varint,2,opt,name=readOnly"`
}
// PersistentVolumeClaimTemplate is used to produce
// PersistentVolumeClaim objects as part of an EphemeralVolumeSource.
type PersistentVolumeClaimTemplate struct {
// May contain labels and annotations that will be copied into the PVC
// when creating it. No other fields are allowed and will be rejected during
// validation.
//
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// The specification for the PersistentVolumeClaim. The entire content is
// copied unchanged into the PVC that gets created from this
// template. The same fields as in a PersistentVolumeClaim
// are also valid here.
Spec PersistentVolumeClaimSpec `json:"spec" protobuf:"bytes,2,name=spec"`
}
// ContainerPort represents a network port in a single container. // ContainerPort represents a network port in a single container.
type ContainerPort struct { type ContainerPort struct {
// If specified, this must be an IANA_SVC_NAME and unique within the pod. Each // If specified, this must be an IANA_SVC_NAME and unique within the pod. Each

View File

@ -626,6 +626,16 @@ func (EphemeralContainers) SwaggerDoc() map[string]string {
return map_EphemeralContainers return map_EphemeralContainers
} }
var map_EphemeralVolumeSource = map[string]string{
"": "Represents an ephemeral volume that is handled by a normal storage driver.",
"volumeClaimTemplate": "Will be used to create a stand-alone PVC to provision the volume. The pod in which this EphemeralVolumeSource is embedded will be the owner of the PVC, i.e. the PVC will be deleted together with the pod. The name of the PVC will be `<pod name>-<volume name>` where `<volume name>` is the name from the `PodSpec.Volumes` array entry. Pod validation will reject the pod if the concatenated name is not valid for a PVC (for example, too long).\n\nAn existing PVC with that name that is not owned by the pod will *not* be used for the pod to avoid using an unrelated volume by mistake. Starting the pod is then blocked until the unrelated PVC is removed. If such a pre-created PVC is meant to be used by the pod, the PVC has to updated with an owner reference to the pod once the pod exists. Normally this should not be necessary, but it may be useful when manually reconstructing a broken cluster.\n\nThis field is read-only and no changes will be made by Kubernetes to the PVC after it has been created.\n\nRequired, must not be nil.",
"readOnly": "Specifies a read-only configuration for the volume. Defaults to false (read/write).",
}
func (EphemeralVolumeSource) SwaggerDoc() map[string]string {
return map_EphemeralVolumeSource
}
var map_Event = map[string]string{ var map_Event = map[string]string{
"": "Event is a report of an event somewhere in the cluster.", "": "Event is a report of an event somewhere in the cluster.",
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", "metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
@ -1319,6 +1329,16 @@ func (PersistentVolumeClaimStatus) SwaggerDoc() map[string]string {
return map_PersistentVolumeClaimStatus return map_PersistentVolumeClaimStatus
} }
var map_PersistentVolumeClaimTemplate = map[string]string{
"": "PersistentVolumeClaimTemplate is used to produce PersistentVolumeClaim objects as part of an EphemeralVolumeSource.",
"metadata": "May contain labels and annotations that will be copied into the PVC when creating it. No other fields are allowed and will be rejected during validation.",
"spec": "The specification for the PersistentVolumeClaim. The entire content is copied unchanged into the PVC that gets created from this template. The same fields as in a PersistentVolumeClaim are also valid here.",
}
func (PersistentVolumeClaimTemplate) SwaggerDoc() map[string]string {
return map_PersistentVolumeClaimTemplate
}
var map_PersistentVolumeClaimVolumeSource = map[string]string{ var map_PersistentVolumeClaimVolumeSource = map[string]string{
"": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).", "": "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).",
"claimName": "ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", "claimName": "ClaimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims",
@ -2443,6 +2463,7 @@ var map_VolumeSource = map[string]string{
"scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.", "scaleIO": "ScaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes.",
"storageos": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.", "storageos": "StorageOS represents a StorageOS volume attached and mounted on Kubernetes nodes.",
"csi": "CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).", "csi": "CSI (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers (Beta feature).",
"ephemeral": "Ephemeral represents a volume that is handled by a cluster storage driver (Alpha feature). The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed.\n\nUse this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information.\n\nA pod can use both types of ephemeral volumes and persistent volumes at the same time.",
} }
func (VolumeSource) SwaggerDoc() map[string]string { func (VolumeSource) SwaggerDoc() map[string]string {

View File

@ -1433,6 +1433,27 @@ func (in *EphemeralContainers) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EphemeralVolumeSource) DeepCopyInto(out *EphemeralVolumeSource) {
*out = *in
if in.VolumeClaimTemplate != nil {
in, out := &in.VolumeClaimTemplate, &out.VolumeClaimTemplate
*out = new(PersistentVolumeClaimTemplate)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EphemeralVolumeSource.
func (in *EphemeralVolumeSource) DeepCopy() *EphemeralVolumeSource {
if in == nil {
return nil
}
out := new(EphemeralVolumeSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Event) DeepCopyInto(out *Event) { func (in *Event) DeepCopyInto(out *Event) {
*out = *in *out = *in
@ -2985,6 +3006,24 @@ func (in *PersistentVolumeClaimStatus) DeepCopy() *PersistentVolumeClaimStatus {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PersistentVolumeClaimTemplate) DeepCopyInto(out *PersistentVolumeClaimTemplate) {
*out = *in
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PersistentVolumeClaimTemplate.
func (in *PersistentVolumeClaimTemplate) DeepCopy() *PersistentVolumeClaimTemplate {
if in == nil {
return nil
}
out := new(PersistentVolumeClaimTemplate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PersistentVolumeClaimVolumeSource) DeepCopyInto(out *PersistentVolumeClaimVolumeSource) { func (in *PersistentVolumeClaimVolumeSource) DeepCopyInto(out *PersistentVolumeClaimVolumeSource) {
*out = *in *out = *in
@ -5763,6 +5802,11 @@ func (in *VolumeSource) DeepCopyInto(out *VolumeSource) {
*out = new(CSIVolumeSource) *out = new(CSIVolumeSource)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.Ephemeral != nil {
in, out := &in.Ephemeral, &out.Ephemeral
*out = new(EphemeralVolumeSource)
(*in).DeepCopyInto(*out)
}
return return
} }

View File

@ -313,6 +313,7 @@ const (
PortworxVolume FSType = "portworxVolume" PortworxVolume FSType = "portworxVolume"
ScaleIO FSType = "scaleIO" ScaleIO FSType = "scaleIO"
CSI FSType = "csi" CSI FSType = "csi"
Ephemeral FSType = "ephemeral"
All FSType = "*" All FSType = "*"
) )

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More