generic ephemeral volume: graduation to GA

The feature gate gets locked to "true", with the goal to remove it in two
releases.

All code now can assume that the feature is enabled. Tests for "feature
disabled" are no longer needed and get removed.

Some code wasn't using the new helper functions yet. That gets changed while
touching those lines.
This commit is contained in:
Patrick Ohly 2021-10-11 15:57:25 +02:00
parent bc263f3ba5
commit a8c930ef46
37 changed files with 128 additions and 526 deletions

View File

@ -9716,7 +9716,7 @@
}, },
"ephemeral": { "ephemeral": {
"$ref": "#/definitions/io.k8s.api.core.v1.EphemeralVolumeSource", "$ref": "#/definitions/io.k8s.api.core.v1.EphemeralVolumeSource",
"description": "Ephemeral represents a volume that is handled by a cluster storage driver. 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.\n\nThis is a beta feature and only available when the GenericEphemeralVolume feature gate is enabled." "description": "Ephemeral represents a volume that is handled by a cluster storage driver. 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",

View File

@ -368,18 +368,15 @@ func startVolumeExpandController(ctx context.Context, controllerContext Controll
} }
func startEphemeralVolumeController(ctx context.Context, controllerContext ControllerContext) (controller.Interface, bool, error) { func startEphemeralVolumeController(ctx context.Context, controllerContext ControllerContext) (controller.Interface, bool, error) {
if utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) { ephemeralController, err := ephemeral.NewController(
ephemeralController, err := ephemeral.NewController( controllerContext.ClientBuilder.ClientOrDie("ephemeral-volume-controller"),
controllerContext.ClientBuilder.ClientOrDie("ephemeral-volume-controller"), controllerContext.InformerFactory.Core().V1().Pods(),
controllerContext.InformerFactory.Core().V1().Pods(), controllerContext.InformerFactory.Core().V1().PersistentVolumeClaims())
controllerContext.InformerFactory.Core().V1().PersistentVolumeClaims()) if err != nil {
if err != nil { return nil, true, fmt.Errorf("failed to start ephemeral volume controller: %v", err)
return nil, true, fmt.Errorf("failed to start ephemeral volume controller: %v", err)
}
go ephemeralController.Run(int(controllerContext.ComponentConfig.EphemeralVolumeController.ConcurrentEphemeralVolumeSyncs), ctx.Done())
return nil, true, nil
} }
return nil, false, nil go ephemeralController.Run(int(controllerContext.ComponentConfig.EphemeralVolumeController.ConcurrentEphemeralVolumeSyncs), ctx.Done())
return nil, true, nil
} }
func startEndpointController(ctx context.Context, controllerCtx ControllerContext) (controller.Interface, bool, error) { func startEndpointController(ctx context.Context, controllerCtx ControllerContext) (controller.Interface, bool, error) {
@ -554,7 +551,6 @@ func startPVCProtectionController(ctx context.Context, controllerContext Control
controllerContext.InformerFactory.Core().V1().Pods(), controllerContext.InformerFactory.Core().V1().Pods(),
controllerContext.ClientBuilder.ClientOrDie("pvc-protection-controller"), controllerContext.ClientBuilder.ClientOrDie("pvc-protection-controller"),
utilfeature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection), utilfeature.DefaultFeatureGate.Enabled(features.StorageObjectInUseProtection),
utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume),
) )
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

@ -556,7 +556,6 @@ 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) {
@ -605,16 +604,6 @@ 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) && !ephemeralInUse(oldPodSpec) {
for i := range podSpec.Volumes {
podSpec.Volumes[i].Ephemeral = nil
}
}
}
func dropPodAffinityTermNamespaceSelector(terms []api.PodAffinityTerm) { func dropPodAffinityTermNamespaceSelector(terms []api.PodAffinityTerm) {
for i := range terms { for i := range terms {
terms[i].NamespaceSelector = nil terms[i].NamespaceSelector = nil
@ -795,19 +784,6 @@ func csiInUse(podSpec *api.PodSpec) bool {
return false return false
} }
// ephemeralInUse returns true if any pod's spec include inline CSI volumes.
func ephemeralInUse(podSpec *api.PodSpec) bool {
if podSpec == nil {
return false
}
for i := range podSpec.Volumes {
if podSpec.Volumes[i].Ephemeral != nil {
return true
}
}
return false
}
// SeccompAnnotationForField takes a pod seccomp profile field and returns the // SeccompAnnotationForField takes a pod seccomp profile field and returns the
// converted annotation value // converted annotation value
func SeccompAnnotationForField(field *api.SeccompProfile) string { func SeccompAnnotationForField(field *api.SeccompProfile) string {

View File

@ -182,9 +182,6 @@ type VolumeSource struct {
// A pod can use both types of ephemeral volumes and // A pod can use both types of ephemeral volumes and
// persistent volumes at the same time. // persistent volumes at the same time.
// //
// This is a beta feature and only available when the GenericEphemeralVolume
// feature gate is enabled.
//
// +optional // +optional
Ephemeral *EphemeralVolumeSource Ephemeral *EphemeralVolumeSource
} }

View File

@ -22,7 +22,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
policyapiv1beta1 "k8s.io/api/policy/v1beta1" policyapiv1beta1 "k8s.io/api/policy/v1beta1"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
@ -94,9 +94,6 @@ var ValidatePodSecurityPolicyName = apimachineryvalidation.NameIsDNSSubdomain
// PodSecurityPolicyValidationOptions contains additional parameters for ValidatePodSecurityPolicy. // PodSecurityPolicyValidationOptions contains additional parameters for ValidatePodSecurityPolicy.
type PodSecurityPolicyValidationOptions struct { type PodSecurityPolicyValidationOptions struct {
// AllowEphemeralVolumeType determines whether Ephemeral is a valid entry
// in PodSecurityPolicySpec.Volumes.
AllowEphemeralVolumeType bool
} }
// ValidatePodSecurityPolicy validates a PodSecurityPolicy and returns an ErrorList // ValidatePodSecurityPolicy validates a PodSecurityPolicy and returns an ErrorList
@ -332,10 +329,6 @@ func validatePodSecurityPolicyVolumes(opts PodSecurityPolicyValidationOptions, f
allowed := psputil.GetAllFSTypesAsSet() allowed := psputil.GetAllFSTypesAsSet()
// add in the * value since that is a pseudo type that is not included by default // add in the * value since that is a pseudo type that is not included by default
allowed.Insert(string(policy.All)) allowed.Insert(string(policy.All))
// Ephemeral may or may not be allowed.
if !opts.AllowEphemeralVolumeType {
allowed.Delete(string(policy.Ephemeral))
}
for _, v := range volumes { for _, v := range volumes {
if !allowed.Has(string(v)) { if !allowed.Has(string(v)) {
allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumes"), v, allowed.List())) allErrs = append(allErrs, field.NotSupported(fldPath.Child("volumes"), v, allowed.List()))

View File

@ -22,7 +22,7 @@ import (
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
policyv1beta1 "k8s.io/api/policy/v1beta1" policyv1beta1 "k8s.io/api/policy/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -786,7 +786,7 @@ func TestValidatePSPVolumes(t *testing.T) {
for _, strVolume := range volumes.List() { for _, strVolume := range volumes.List() {
psp := validPSP() psp := validPSP()
psp.Spec.Volumes = []policy.FSType{policy.FSType(strVolume)} psp.Spec.Volumes = []policy.FSType{policy.FSType(strVolume)}
errs := ValidatePodSecurityPolicy(psp, PodSecurityPolicyValidationOptions{AllowEphemeralVolumeType: true}) errs := ValidatePodSecurityPolicy(psp, PodSecurityPolicyValidationOptions{})
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("%s validation expected no errors but received %v", strVolume, errs) t.Errorf("%s validation expected no errors but received %v", strVolume, errs)
} }
@ -1118,34 +1118,26 @@ func TestAllowEphemeralVolumeType(t *testing.T) {
}, },
} }
for _, allowed := range []bool{true, false} { for _, oldPSPInfo := range pspInfo {
for _, oldPSPInfo := range pspInfo { for _, newPSPInfo := range pspInfo {
for _, newPSPInfo := range pspInfo { oldPSP := oldPSPInfo.psp()
oldPSP := oldPSPInfo.psp() newPSP := newPSPInfo.psp()
newPSP := newPSPInfo.psp() if newPSP == nil {
if newPSP == nil { continue
continue
}
t.Run(fmt.Sprintf("feature enabled=%v, old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", allowed, oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) {
opts := PodSecurityPolicyValidationOptions{
AllowEphemeralVolumeType: allowed,
}
var errs field.ErrorList
expectErrors := newPSPInfo.hasGenericVolume && !allowed
if oldPSP == nil {
errs = ValidatePodSecurityPolicy(newPSP, opts)
} else {
errs = ValidatePodSecurityPolicyUpdate(oldPSP, newPSP, opts)
}
if expectErrors && len(errs) == 0 {
t.Error("expected errors, got none")
}
if !expectErrors && len(errs) > 0 {
t.Errorf("expected no errors, got: %v", errs)
}
})
} }
t.Run(fmt.Sprintf("old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) {
opts := PodSecurityPolicyValidationOptions{}
var errs field.ErrorList
if oldPSP == nil {
errs = ValidatePodSecurityPolicy(newPSP, opts)
} else {
errs = ValidatePodSecurityPolicyUpdate(oldPSP, newPSP, opts)
}
if len(errs) > 0 {
t.Errorf("expected no errors, got: %v", errs)
}
})
} }
} }
} }

View File

@ -47,10 +47,9 @@ func CreateVolumeSpec(podVolume v1.Volume, pod *v1.Pod, nodeName types.NodeName,
claimName = pvcSource.ClaimName claimName = pvcSource.ClaimName
readOnly = pvcSource.ReadOnly readOnly = pvcSource.ReadOnly
} }
isEphemeral := false isEphemeral := podVolume.VolumeSource.Ephemeral != nil
if ephemeralSource := podVolume.VolumeSource.Ephemeral; ephemeralSource != nil && utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) { if isEphemeral {
claimName = ephemeral.VolumeClaimName(pod, &podVolume) claimName = ephemeral.VolumeClaimName(pod, &podVolume)
isEphemeral = true
} }
if claimName != "" { if claimName != "" {
klog.V(10).Infof( klog.V(10).Infof(

View File

@ -20,9 +20,8 @@ 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" "k8s.io/component-helpers/storage/ephemeral"
) )
const ( const (
@ -31,9 +30,9 @@ const (
) )
// PodPVCIndexFunc creates an index function that returns PVC keys (= // PodPVCIndexFunc creates an index function that returns PVC keys (=
// namespace/name) for given pod. If enabled, this includes the PVCs // namespace/name) for given pod. This includes the PVCs
// that might be created for generic ephemeral volumes. // that might be created for generic ephemeral volumes.
func PodPVCIndexFunc(genericEphemeralVolumeFeatureEnabled bool) func(obj interface{}) ([]string, error) { func PodPVCIndexFunc() func(obj interface{}) ([]string, error) {
return func(obj interface{}) ([]string, error) { return func(obj interface{}) ([]string, error) {
pod, ok := obj.(*v1.Pod) pod, ok := obj.(*v1.Pod)
if !ok { if !ok {
@ -44,9 +43,8 @@ func PodPVCIndexFunc(genericEphemeralVolumeFeatureEnabled bool) func(obj interfa
claimName := "" claimName := ""
if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil { if pvcSource := podVolume.VolumeSource.PersistentVolumeClaim; pvcSource != nil {
claimName = pvcSource.ClaimName claimName = pvcSource.ClaimName
} } else if podVolume.VolumeSource.Ephemeral != nil {
if ephemeralSource := podVolume.VolumeSource.Ephemeral; genericEphemeralVolumeFeatureEnabled && ephemeralSource != nil { claimName = ephemeral.VolumeClaimName(pod, &podVolume)
claimName = pod.Name + "-" + podVolume.Name
} }
if claimName != "" { if claimName != "" {
keys = append(keys, fmt.Sprintf("%s/%s", pod.Namespace, claimName)) keys = append(keys, fmt.Sprintf("%s/%s", pod.Namespace, claimName))
@ -56,10 +54,9 @@ func PodPVCIndexFunc(genericEphemeralVolumeFeatureEnabled bool) func(obj interfa
} }
} }
// AddPodPVCIndexerIfNotPresent adds the PodPVCIndexFunc with the current global setting for GenericEphemeralVolume. // AddPodPVCIndexerIfNotPresent adds the PodPVCIndexFunc.
func AddPodPVCIndexerIfNotPresent(indexer cache.Indexer) error { func AddPodPVCIndexerIfNotPresent(indexer cache.Indexer) error {
return AddIndexerIfNotPresent(indexer, PodPVCIndex, return AddIndexerIfNotPresent(indexer, PodPVCIndex, PodPVCIndexFunc())
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

@ -56,18 +56,14 @@ 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, genericEphemeralVolumeFeatureEnabled bool) (*Controller, error) { func NewPVCProtectionController(pvcInformer coreinformers.PersistentVolumeClaimInformer, podInformer coreinformers.PodInformer, cl clientset.Interface, storageObjectInUseProtectionFeatureEnabled 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())
@ -85,7 +81,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(genericEphemeralVolumeFeatureEnabled)); err != nil { if err := common.AddIndexerIfNotPresent(e.podIndexer, common.PodPVCIndex, common.PodPVCIndexFunc()); err != nil {
return nil, fmt.Errorf("could not initialize pvc protection controller: %w", err) return nil, fmt.Errorf("could not initialize pvc protection controller: %w", err)
} }
podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
@ -252,23 +248,12 @@ func (c *Controller) askInformer(pvc *v1.PersistentVolumeClaim) (bool, error) {
continue continue
} }
if c.genericEphemeralVolumeFeatureEnabled { // We still need to look at each volume: that's redundant for volume.PersistentVolumeClaim,
// 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
// but for volume.Ephemeral we need to be sure that this particular PVC is the one // created for the ephemeral volume.
// created for the ephemeral volume. if c.podUsesPVC(pod, pvc) {
if c.podUsesPVC(pod, pvc) { return true, nil
return true, nil
}
continue
} }
// This is the traditional behavior without GenericEphemeralVolume enabled.
if pod.Spec.NodeName == "" {
continue
}
// found a pod using this PVC
return true, nil
} }
klog.V(4).InfoS("No Pod using PVC was found in the Informer's cache", "PVC", klog.KObj(pvc)) klog.V(4).InfoS("No Pod using PVC was found in the Informer's cache", "PVC", klog.KObj(pvc))
@ -300,7 +285,7 @@ func (c *Controller) podUsesPVC(pod *v1.Pod, pvc *v1.PersistentVolumeClaim) bool
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.Name || if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc.Name ||
c.genericEphemeralVolumeFeatureEnabled && !podIsShutDown(pod) && volume.Ephemeral != nil && ephemeral.VolumeClaimName(pod, &volume) == pvc.Name && ephemeral.VolumeIsForPod(pod, pvc) == nil { !podIsShutDown(pod) && volume.Ephemeral != nil && ephemeral.VolumeClaimName(pod, &volume) == pvc.Name && ephemeral.VolumeIsForPod(pod, pvc) == nil {
klog.V(2).InfoS("Pod uses PVC", "pod", klog.KObj(pod), "PVC", klog.KObj(pvc)) klog.V(2).InfoS("Pod uses PVC", "pod", klog.KObj(pod), "PVC", klog.KObj(pvc))
return true return true
} }
@ -407,7 +392,7 @@ func (c *Controller) enqueuePVCs(pod *v1.Pod, deleted bool) {
switch { switch {
case volume.PersistentVolumeClaim != nil: 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: case volume.Ephemeral != nil:
c.queue.Add(pod.Namespace + "/" + ephemeral.VolumeClaimName(pod, &volume)) c.queue.Add(pod.Namespace + "/" + ephemeral.VolumeClaimName(pod, &volume))
} }
} }

View File

@ -146,7 +146,7 @@ func generateUpdateErrorFunc(t *testing.T, failures int) clienttesting.ReactionF
} }
} }
func testPVCProtectionController(t *testing.T, genericEphemeralVolumeFeatureEnabled bool) { func TestPVCProtectionController(t *testing.T) {
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, genericEphemeralVolumeFeatureEnab
podInformer := informers.Core().V1().Pods() podInformer := informers.Core().V1().Pods()
// Create the controller // Create the controller
ctrl, err := NewPVCProtectionController(pvcInformer, podInformer, client, test.storageObjectInUseProtectionEnabled, genericEphemeralVolumeFeatureEnabled) ctrl, err := NewPVCProtectionController(pvcInformer, podInformer, client, test.storageObjectInUseProtectionEnabled)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
@ -518,8 +518,3 @@ func testPVCProtectionController(t *testing.T, genericEphemeralVolumeFeatureEnab
} }
} }
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

@ -162,6 +162,7 @@ const (
// owner: @pohly // owner: @pohly
// alpha: v1.19 // alpha: v1.19
// beta: v1.21 // beta: v1.21
// GA: v1.23
// //
// Enables generic ephemeral inline volume support for pods // Enables generic ephemeral inline volume support for pods
GenericEphemeralVolume featuregate.Feature = "GenericEphemeralVolume" GenericEphemeralVolume featuregate.Feature = "GenericEphemeralVolume"
@ -802,7 +803,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
CSIInlineVolume: {Default: true, PreRelease: featuregate.Beta}, CSIInlineVolume: {Default: true, PreRelease: featuregate.Beta},
CSIStorageCapacity: {Default: true, PreRelease: featuregate.Beta}, CSIStorageCapacity: {Default: true, PreRelease: featuregate.Beta},
CSIServiceAccountToken: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23 CSIServiceAccountToken: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
GenericEphemeralVolume: {Default: true, PreRelease: featuregate.Beta}, GenericEphemeralVolume: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
CSIVolumeFSGroupPolicy: {Default: true, PreRelease: featuregate.Beta}, CSIVolumeFSGroupPolicy: {Default: true, PreRelease: featuregate.Beta},
RuntimeClass: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23 RuntimeClass: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23
NetworkPolicyEndPort: {Default: true, PreRelease: featuregate.Beta}, NetworkPolicyEndPort: {Default: true, PreRelease: featuregate.Beta},

View File

@ -2038,9 +2038,6 @@ func (kl *Kubelet) hasHostMountPVC(pod *v1.Pod) bool {
case volume.PersistentVolumeClaim != nil: case volume.PersistentVolumeClaim != nil:
pvcName = volume.PersistentVolumeClaim.ClaimName pvcName = volume.PersistentVolumeClaim.ClaimName
case volume.Ephemeral != nil: case volume.Ephemeral != nil:
if !utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
continue
}
pvcName = ephemeral.VolumeClaimName(pod, &volume) pvcName = ephemeral.VolumeClaimName(pod, &volume)
default: default:
continue continue

View File

@ -38,10 +38,8 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
utilfeature "k8s.io/apiserver/pkg/util/feature"
core "k8s.io/client-go/testing" core "k8s.io/client-go/testing"
"k8s.io/client-go/tools/record" "k8s.io/client-go/tools/record"
featuregatetesting "k8s.io/component-base/featuregate/testing"
netutils "k8s.io/utils/net" netutils "k8s.io/utils/net"
// TODO: remove this import if // TODO: remove this import if
@ -49,7 +47,6 @@ import (
// to "v1"? // to "v1"?
_ "k8s.io/kubernetes/pkg/apis/core/install" _ "k8s.io/kubernetes/pkg/apis/core/install"
"k8s.io/kubernetes/pkg/features"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing" containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
"k8s.io/kubernetes/pkg/kubelet/cri/streaming/portforward" "k8s.io/kubernetes/pkg/kubelet/cri/streaming/portforward"
@ -2979,13 +2976,12 @@ func TestGetPortForward(t *testing.T) {
func TestHasHostMountPVC(t *testing.T) { func TestHasHostMountPVC(t *testing.T) {
type testcase struct { type testcase struct {
pvError error pvError error
pvcError error pvcError error
expected bool expected bool
podHasPVC bool podHasPVC bool
pvcIsHostPath bool pvcIsHostPath bool
podHasEphemeral bool podHasEphemeral bool
ephemeralEnabled bool
} }
tests := map[string]testcase{ tests := map[string]testcase{
"no pvc": {podHasPVC: false, expected: false}, "no pvc": {podHasPVC: false, expected: false},
@ -3005,16 +3001,9 @@ func TestHasHostMountPVC(t *testing.T) {
expected: true, expected: true,
}, },
"enabled ephemeral host path": { "enabled ephemeral host path": {
podHasEphemeral: true, podHasEphemeral: true,
pvcIsHostPath: true, pvcIsHostPath: true,
ephemeralEnabled: true, expected: true,
expected: true,
},
"disabled ephemeral host path": {
podHasEphemeral: true,
pvcIsHostPath: true,
ephemeralEnabled: false,
expected: false,
}, },
"non host path pvc": { "non host path pvc": {
podHasPVC: true, podHasPVC: true,
@ -3024,7 +3013,6 @@ func TestHasHostMountPVC(t *testing.T) {
} }
run := func(t *testing.T, v testcase) { run := func(t *testing.T, v testcase) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, v.ephemeralEnabled)()
testKubelet := newTestKubelet(t, false) testKubelet := newTestKubelet(t, false)
defer testKubelet.Cleanup() defer testKubelet.Cleanup()
pod := &v1.Pod{ pod := &v1.Pod{

View File

@ -149,8 +149,7 @@ func (s *volumeStatCalculator) calcAndStoreStats() {
Name: pvcSource.ClaimName, Name: pvcSource.ClaimName,
Namespace: s.pod.GetNamespace(), Namespace: s.pod.GetNamespace(),
} }
} } else if volSpec.Ephemeral != nil {
if volSpec.Ephemeral != nil && utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
pvcRef = &stats.PVCReference{ pvcRef = &stats.PVCReference{
Name: ephemeral.VolumeClaimName(s.pod, &volSpec), Name: ephemeral.VolumeClaimName(s.pod, &volSpec),
Namespace: s.pod.GetNamespace(), Namespace: s.pod.GetNamespace(),

View File

@ -105,7 +105,6 @@ var (
) )
func TestPVCRef(t *testing.T) { func TestPVCRef(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
mockCtrl := gomock.NewController(t) mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish() defer mockCtrl.Finish()

View File

@ -36,6 +36,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
utilfeature "k8s.io/apiserver/pkg/util/feature" utilfeature "k8s.io/apiserver/pkg/util/feature"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/component-helpers/storage/ephemeral"
"k8s.io/kubernetes/pkg/features" "k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/config" "k8s.io/kubernetes/pkg/kubelet/config"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
@ -462,28 +463,15 @@ func (dswp *desiredStateOfWorldPopulator) deleteProcessedPod(
func (dswp *desiredStateOfWorldPopulator) createVolumeSpec( func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
podVolume v1.Volume, pod *v1.Pod, 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) {
pvcSource := podVolume.VolumeSource.PersistentVolumeClaim pvcSource := podVolume.VolumeSource.PersistentVolumeClaim
ephemeral := false isEphemeral := pvcSource == nil && podVolume.VolumeSource.Ephemeral != nil
if pvcSource == nil && if isEphemeral {
podVolume.VolumeSource.Ephemeral != nil {
if !utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
// Provide an unambiguous error message that
// explains why the volume cannot be
// processed. If we just ignore the volume
// source, the error is just a vague "unknown
// volume source".
return nil, nil, "", fmt.Errorf(
"volume %s is a generic ephemeral volume, but that feature is disabled in kubelet",
podVolume.Name,
)
}
// Generic ephemeral inline volumes are handled the // Generic ephemeral inline volumes are handled the
// same way as a PVC reference. The only additional // same way as a PVC reference. The only additional
// constraint (checked below) is that the PVC must be // constraint (checked below) is that the PVC must be
// owned by the pod. // owned by the pod.
pvcSource = &v1.PersistentVolumeClaimVolumeSource{ pvcSource = &v1.PersistentVolumeClaimVolumeSource{
ClaimName: pod.Name + "-" + podVolume.Name, ClaimName: ephemeral.VolumeClaimName(pod, &podVolume),
} }
ephemeral = true
} }
if pvcSource != nil { if pvcSource != nil {
klog.V(5).InfoS("Found PVC", "PVC", klog.KRef(pod.Namespace, pvcSource.ClaimName)) klog.V(5).InfoS("Found PVC", "PVC", klog.KRef(pod.Namespace, pvcSource.ClaimName))
@ -497,12 +485,10 @@ func (dswp *desiredStateOfWorldPopulator) createVolumeSpec(
pvcSource.ClaimName, pvcSource.ClaimName,
err) err)
} }
if ephemeral && !metav1.IsControlledBy(pvc, pod) { if isEphemeral {
return nil, nil, "", fmt.Errorf( if err := ephemeral.VolumeIsForPod(pod, pvc); err != nil {
"error processing PVC %s/%s: not the ephemeral PVC for the pod", return nil, nil, "", err
pod.Namespace, }
pvcSource.ClaimName,
)
} }
pvName, pvcUID := pvc.Spec.VolumeName, pvc.UID pvName, pvcUID := pvc.Spec.VolumeName, pvc.UID
klog.V(5).InfoS("Found bound PV for PVC", "PVC", klog.KRef(pod.Namespace, pvcSource.ClaimName), "PVCUID", pvcUID, "PVName", pvName) klog.V(5).InfoS("Found bound PV for PVC", "PVC", klog.KRef(pod.Namespace, pvcSource.ClaimName), "PVCUID", pvcUID, "PVName", pvName)

View File

@ -546,8 +546,6 @@ func TestFindAndRemoveNonattachableVolumes(t *testing.T) {
} }
func TestEphemeralVolumeOwnerCheck(t *testing.T) { func TestEphemeralVolumeOwnerCheck(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
// create dswp // create dswp
pod, pv, pvc := createEphemeralVolumeObjects("dswp-test-pod", "dswp-test-volume-name", false /* not owned */) pod, pv, pvc := createEphemeralVolumeObjects("dswp-test-pod", "dswp-test-volume-name", false /* not owned */)
dswp, fakePodManager, _, _, _ := createDswpWithVolume(t, pv, pvc) dswp, fakePodManager, _, _, _ := createDswpWithVolume(t, pv, pvc)
@ -560,109 +558,14 @@ func TestEphemeralVolumeOwnerCheck(t *testing.T) {
t.Fatalf("%s should not have been processed by the populator", podName) t.Fatalf("%s should not have been processed by the populator", podName)
} }
require.Equal(t, require.Equal(t,
[]string{fmt.Sprintf("error processing PVC %s/%s: not the ephemeral PVC for the pod", pvc.Namespace, pvc.Name)}, []string{fmt.Sprintf("PVC %s/%s was not created for pod %s/%s (pod is not owner)",
pvc.Namespace, pvc.Name,
pod.Namespace, pod.Name,
)},
dswp.desiredStateOfWorld.PopPodErrors(podName), dswp.desiredStateOfWorld.PopPodErrors(podName),
) )
} }
func TestEphemeralVolumeEnablement(t *testing.T) {
// create dswp
pod, pv, pvc := createEphemeralVolumeObjects("dswp-test-pod", "dswp-test-volume-name", true /* owned */)
dswp, fakePodManager, fakesDSW, _, fakePodState := createDswpWithVolume(t, pv, pvc)
fakePodManager.AddPod(pod)
podName := util.GetUniquePodName(pod)
volumeName := pod.Spec.Volumes[0].Name
generatedVolumeName := "fake-plugin/" + volumeName
// Feature disabled -> refuse to process pod.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, false)()
dswp.findAndAddNewPods()
if dswp.pods.processedPods[podName] {
t.Fatalf("%s should not have been processed by the populator", podName)
}
require.Equal(t,
[]string{fmt.Sprintf("volume %s is a generic ephemeral volume, but that feature is disabled in kubelet", volumeName)},
dswp.desiredStateOfWorld.PopPodErrors(podName),
)
// Enabled -> process pod.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
dswp.findAndAddNewPods()
if !dswp.pods.processedPods[podName] {
t.Fatalf("Failed to record that the volumes for the specified pod: %s have been processed by the populator", podName)
}
expectedVolumeName := v1.UniqueVolumeName(generatedVolumeName)
volumeExists := fakesDSW.VolumeExists(expectedVolumeName)
if !volumeExists {
t.Fatalf(
"VolumeExists(%q) failed. Expected: <true> Actual: <%v>",
expectedVolumeName,
volumeExists)
}
if podExistsInVolume := fakesDSW.PodExistsInVolume(
podName, expectedVolumeName); !podExistsInVolume {
t.Fatalf(
"DSW PodExistsInVolume returned incorrect value. Expected: <true> Actual: <%v>",
podExistsInVolume)
}
verifyVolumeExistsInVolumesToMount(
t, v1.UniqueVolumeName(generatedVolumeName), false /* expectReportedInUse */, fakesDSW)
//let the pod be terminated
podGet, exist := fakePodManager.GetPodByName(pod.Namespace, pod.Name)
if !exist {
t.Fatalf("Failed to get pod by pod name: %s and namespace: %s", pod.Name, pod.Namespace)
}
fakePodState.removed = map[kubetypes.UID]struct{}{podGet.UID: {}}
// Pretend again that the feature is disabled.
// Removal of the pod and volumes is expected to work.
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, false)()
dswp.findAndRemoveDeletedPods()
// Although Pod status is terminated, pod still exists in pod manager and actual state does not has this pod and volume information
// desired state populator will fail to delete this pod and volume first
volumeExists = fakesDSW.VolumeExists(expectedVolumeName)
if !volumeExists {
t.Fatalf(
"VolumeExists(%q) failed. Expected: <true> Actual: <%v>",
expectedVolumeName,
volumeExists)
}
if podExistsInVolume := fakesDSW.PodExistsInVolume(
podName, expectedVolumeName); !podExistsInVolume {
t.Fatalf(
"DSW PodExistsInVolume returned incorrect value. Expected: <true> Actual: <%v>",
podExistsInVolume)
}
// reconcile with actual state so that volume is added into the actual state
// desired state populator now can successfully delete the pod and volume
fakeASW := dswp.actualStateOfWorld
reconcileASW(fakeASW, fakesDSW, t)
dswp.findAndRemoveDeletedPods()
volumeExists = fakesDSW.VolumeExists(expectedVolumeName)
if volumeExists {
t.Fatalf(
"VolumeExists(%q) failed. Expected: <false> Actual: <%v>",
expectedVolumeName,
volumeExists)
}
if podExistsInVolume := fakesDSW.PodExistsInVolume(
podName, expectedVolumeName); podExistsInVolume {
t.Fatalf(
"DSW PodExistsInVolume returned incorrect value. Expected: <false> Actual: <%v>",
podExistsInVolume)
}
}
func TestFindAndAddNewPods_FindAndRemoveDeletedPods_Valid_Block_VolumeDevices(t *testing.T) { func TestFindAndAddNewPods_FindAndRemoveDeletedPods_Valid_Block_VolumeDevices(t *testing.T) {
// create dswp // create dswp
mode := v1.PersistentVolumeBlock mode := v1.PersistentVolumeBlock

View File

@ -1133,100 +1133,6 @@ func TestApplySeccompVersionSkew(t *testing.T) {
} }
} }
// TestEphemeralVolumeEnablement checks the behavior of the API server
// when the GenericEphemeralVolume feature is turned on and then off:
// the Ephemeral struct must be preserved even during updates.
func TestEphemeralVolumeEnablement(t *testing.T) {
// Enable the Feature Gate during the first pod creation
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
pod := createPodWithGenericEphemeralVolume()
expectedPod := pod.DeepCopy()
Strategy.PrepareForCreate(context.Background(), pod)
require.Equal(t, expectedPod.Spec, pod.Spec, "pod spec")
errs := Strategy.Validate(context.Background(), pod)
require.Empty(t, errs, "errors from validation")
// Now let's disable the Feature Gate, update some other field from the Pod and expect the volume to remain present
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, false)()
updatePod := testUpdatePod(t, pod, "aaa")
// And let's enable the FG again, add another from and check if the volume is still present
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, true)()
testUpdatePod(t, updatePod, "bbb")
}
// TestEphemeralVolumeDisabled checks the behavior of the API server
// when the GenericEphemeralVolume is off: the Ephemeral struct gets dropped,
// validation fails.
func TestEphemeralVolumeDisabled(t *testing.T) {
// Disable the Feature Gate during the first pod creation
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, false)()
pod := createPodWithGenericEphemeralVolume()
expectedPod := pod.DeepCopy()
expectedPod.Spec.Volumes[0].VolumeSource.Ephemeral = nil
Strategy.PrepareForCreate(context.Background(), pod)
require.Equal(t, expectedPod.Spec, pod.Spec, "pod spec")
errs := Strategy.Validate(context.Background(), pod)
require.NotEmpty(t, errs, "no errors from validation")
}
func testUpdatePod(t *testing.T, oldPod *api.Pod, labelValue string) *api.Pod {
updatedPod := oldPod.DeepCopy()
updatedPod.Labels = map[string]string{"XYZ": labelValue}
expectedPod := updatedPod.DeepCopy()
Strategy.PrepareForUpdate(context.Background(), updatedPod, oldPod)
require.Equal(t, expectedPod.Spec, updatedPod.Spec, "updated pod spec")
errs := Strategy.Validate(context.Background(), updatedPod)
require.Empty(t, errs, "errors from validation")
return updatedPod
}
func createPodWithGenericEphemeralVolume() *api.Pod {
return &api.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: "ns",
Name: "pod",
},
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{{
Name: "foo",
Image: "example",
TerminationMessagePolicy: api.TerminationMessageReadFile,
ImagePullPolicy: api.PullAlways,
}},
Volumes: []api.Volume{
{
Name: "ephemeral",
VolumeSource: api.VolumeSource{
Ephemeral: &api.EphemeralVolumeSource{
VolumeClaimTemplate: &api.PersistentVolumeClaimTemplate{
Spec: api.PersistentVolumeClaimSpec{
AccessModes: []api.PersistentVolumeAccessMode{
api.ReadWriteOnce,
},
Resources: api.ResourceRequirements{
Requests: api.ResourceList{
api.ResourceStorage: resource.MustParse("1Gi"),
},
},
},
},
},
},
},
},
},
}
}
func newPodtWithHugePageValue(reousreceName api.ResourceName, value resource.Quantity) *api.Pod { func newPodtWithHugePageValue(reousreceName api.ResourceName, value resource.Quantity) *api.Pod {
return &api.Pod{ return &api.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{

View File

@ -23,12 +23,10 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/storage/names" "k8s.io/apiserver/pkg/storage/names"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/api/legacyscheme" "k8s.io/kubernetes/pkg/api/legacyscheme"
psputil "k8s.io/kubernetes/pkg/api/podsecuritypolicy" psputil "k8s.io/kubernetes/pkg/api/podsecuritypolicy"
"k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/apis/policy/validation" "k8s.io/kubernetes/pkg/apis/policy/validation"
"k8s.io/kubernetes/pkg/features"
) )
// strategy implements behavior for PodSecurityPolicy objects // strategy implements behavior for PodSecurityPolicy objects
@ -74,10 +72,7 @@ func (strategy) Canonicalize(obj runtime.Object) {
} }
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
opts := validation.PodSecurityPolicyValidationOptions{ opts := validation.PodSecurityPolicyValidationOptions{}
// Only allowed if the feature is enabled.
AllowEphemeralVolumeType: utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume),
}
return validation.ValidatePodSecurityPolicy(obj.(*policy.PodSecurityPolicy), opts) return validation.ValidatePodSecurityPolicy(obj.(*policy.PodSecurityPolicy), opts)
} }
@ -85,12 +80,7 @@ func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorLis
func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil } func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil }
func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
opts := validation.PodSecurityPolicyValidationOptions{ opts := validation.PodSecurityPolicyValidationOptions{}
// Allowed if the feature is enabled or the old policy already had it.
// A policy that had the type set when that was valid must remain valid.
AllowEphemeralVolumeType: utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) ||
volumeInUse(old.(*policy.PodSecurityPolicy), policy.Ephemeral),
}
return validation.ValidatePodSecurityPolicyUpdate(old.(*policy.PodSecurityPolicy), obj.(*policy.PodSecurityPolicy), opts) return validation.ValidatePodSecurityPolicyUpdate(old.(*policy.PodSecurityPolicy), obj.(*policy.PodSecurityPolicy), opts)
} }
@ -98,15 +88,3 @@ func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) fie
func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
return nil return nil
} }
func volumeInUse(oldPSP *policy.PodSecurityPolicy, volume policy.FSType) bool {
if oldPSP == nil {
return false
}
for _, v := range oldPSP.Spec.Volumes {
if v == volume {
return true
}
}
return false
}

View File

@ -23,10 +23,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apimachinery/pkg/util/validation/field"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/policy" "k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/features"
) )
func TestAllowEphemeralVolumeType(t *testing.T) { func TestAllowEphemeralVolumeType(t *testing.T) {
@ -83,35 +80,25 @@ func TestAllowEphemeralVolumeType(t *testing.T) {
}, },
} }
for _, enabled := range []bool{true, false} { for _, oldPSPInfo := range pspInfo {
for _, oldPSPInfo := range pspInfo { for _, newPSPInfo := range pspInfo {
for _, newPSPInfo := range pspInfo { oldPSP := oldPSPInfo.psp()
oldPSP := oldPSPInfo.psp() newPSP := newPSPInfo.psp()
newPSP := newPSPInfo.psp() if newPSP == nil {
if newPSP == nil { continue
continue
}
t.Run(fmt.Sprintf("feature enabled=%v, old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", enabled, oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, enabled)()
var errs field.ErrorList
var expectErrors bool
if oldPSP == nil {
errs = Strategy.Validate(context.Background(), newPSP)
expectErrors = newPSPInfo.hasGenericVolume && !enabled
} else {
errs = Strategy.ValidateUpdate(context.Background(), newPSP, oldPSP)
expectErrors = !oldPSPInfo.hasGenericVolume && newPSPInfo.hasGenericVolume && !enabled
}
if expectErrors && len(errs) == 0 {
t.Error("expected errors, got none")
}
if !expectErrors && len(errs) > 0 {
t.Errorf("expected no errors, got: %v", errs)
}
})
} }
t.Run(fmt.Sprintf("old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) {
var errs field.ErrorList
if oldPSP == nil {
errs = Strategy.Validate(context.Background(), newPSP)
} else {
errs = Strategy.ValidateUpdate(context.Background(), newPSP, oldPSP)
}
if len(errs) > 0 {
t.Errorf("expected no errors, got: %v", errs)
}
})
} }
} }
} }

View File

@ -26,5 +26,4 @@ type Features struct {
EnableReadWriteOncePod bool EnableReadWriteOncePod bool
EnableVolumeCapacityPriority bool EnableVolumeCapacityPriority bool
EnableCSIStorageCapacity bool EnableCSIStorageCapacity bool
EnableGenericEphemeralVolume bool
} }

View File

@ -56,8 +56,6 @@ type CSILimits struct {
randomVolumeIDPrefix string randomVolumeIDPrefix string
translator InTreeToCSITranslator translator InTreeToCSITranslator
enableGenericEphemeralVolume bool
} }
var _ framework.FilterPlugin = &CSILimits{} var _ framework.FilterPlugin = &CSILimits{}
@ -157,12 +155,6 @@ func (pl *CSILimits) filterAttachableVolumes(
case vol.PersistentVolumeClaim != nil: case vol.PersistentVolumeClaim != nil:
pvcName = vol.PersistentVolumeClaim.ClaimName pvcName = vol.PersistentVolumeClaim.ClaimName
case vol.Ephemeral != nil: case vol.Ephemeral != nil:
if newPod && !pl.enableGenericEphemeralVolume {
return fmt.Errorf(
"volume %s is a generic ephemeral volume, but that feature is disabled in kube-scheduler",
vol.Name,
)
}
// Generic ephemeral inline volumes also use a PVC, // Generic ephemeral inline volumes also use a PVC,
// just with a computed name and certain ownership. // just with a computed name and certain ownership.
// That is checked below once the pvc object is // That is checked below once the pvc object is
@ -320,13 +312,12 @@ func NewCSI(_ runtime.Object, handle framework.Handle, fts feature.Features) (fr
scLister := informerFactory.Storage().V1().StorageClasses().Lister() scLister := informerFactory.Storage().V1().StorageClasses().Lister()
return &CSILimits{ return &CSILimits{
csiNodeLister: csiNodesLister, csiNodeLister: csiNodesLister,
pvLister: pvLister, pvLister: pvLister,
pvcLister: pvcLister, pvcLister: pvcLister,
scLister: scLister, scLister: scLister,
randomVolumeIDPrefix: rand.String(32), randomVolumeIDPrefix: rand.String(32),
translator: csitrans.New(), translator: csitrans.New(),
enableGenericEphemeralVolume: fts.EnableGenericEphemeralVolume,
}, nil }, nil
} }

View File

@ -546,13 +546,6 @@ func TestCSILimits(t *testing.T) {
test: "should not count in-tree and count csi volumes if migration is disabled (when scheduling in-tree volumes)", test: "should not count in-tree and count csi volumes if migration is disabled (when scheduling in-tree volumes)",
}, },
// ephemeral volumes // ephemeral volumes
{
newPod: ephemeralVolumePod,
filterName: "csi",
driverNames: []string{ebsCSIDriverName},
test: "ephemeral volume feature disabled",
wantStatus: framework.NewStatus(framework.Error, "volume xyz is a generic ephemeral volume, but that feature is disabled in kube-scheduler"),
},
{ {
newPod: ephemeralVolumePod, newPod: ephemeralVolumePod,
filterName: "csi", filterName: "csi",
@ -657,8 +650,6 @@ func TestCSILimits(t *testing.T) {
scLister: getFakeCSIStorageClassLister(scName, test.driverNames[0]), scLister: getFakeCSIStorageClassLister(scName, test.driverNames[0]),
randomVolumeIDPrefix: rand.String(32), randomVolumeIDPrefix: rand.String(32),
translator: csitrans.New(), translator: csitrans.New(),
enableGenericEphemeralVolume: test.ephemeralEnabled,
} }
gotStatus := p.Filter(context.Background(), nil, test.newPod, node) gotStatus := p.Filter(context.Background(), nil, test.newPod, node)
if !reflect.DeepEqual(gotStatus, test.wantStatus) { if !reflect.DeepEqual(gotStatus, test.wantStatus) {

View File

@ -117,8 +117,6 @@ type nonCSILimits struct {
// It is used to prefix volumeID generated inside the predicate() method to // It is used to prefix volumeID generated inside the predicate() method to
// avoid conflicts with any real volume. // avoid conflicts with any real volume.
randomVolumeIDPrefix string randomVolumeIDPrefix string
enableGenericEphemeralVolume bool
} }
var _ framework.FilterPlugin = &nonCSILimits{} var _ framework.FilterPlugin = &nonCSILimits{}
@ -191,8 +189,6 @@ func newNonCSILimits(
pvcLister: pvcLister, pvcLister: pvcLister,
scLister: scLister, scLister: scLister,
randomVolumeIDPrefix: rand.String(32), randomVolumeIDPrefix: rand.String(32),
enableGenericEphemeralVolume: fts.EnableGenericEphemeralVolume,
} }
return pl return pl
@ -293,12 +289,6 @@ func (pl *nonCSILimits) filterVolumes(pod *v1.Pod, newPod bool, filteredVolumes
case vol.PersistentVolumeClaim != nil: case vol.PersistentVolumeClaim != nil:
pvcName = vol.PersistentVolumeClaim.ClaimName pvcName = vol.PersistentVolumeClaim.ClaimName
case vol.Ephemeral != nil: case vol.Ephemeral != nil:
if !pl.enableGenericEphemeralVolume {
return fmt.Errorf(
"volume %s is a generic ephemeral volume, but that feature is disabled in kube-scheduler",
vol.Name,
)
}
// Generic ephemeral inline volumes also use a PVC, // Generic ephemeral inline volumes also use a PVC,
// just with a computed name and certain ownership. // just with a computed name and certain ownership.
// That is checked below once the pvc object is // That is checked below once the pvc object is

View File

@ -86,11 +86,6 @@ func TestEphemeralLimits(t *testing.T) {
test string test string
wantStatus *framework.Status wantStatus *framework.Status
}{ }{
{
newPod: ephemeralVolumePod,
test: "volume feature disabled",
wantStatus: framework.NewStatus(framework.Error, "volume xyz is a generic ephemeral volume, but that feature is disabled in kube-scheduler"),
},
{ {
newPod: ephemeralVolumePod, newPod: ephemeralVolumePod,
ephemeralEnabled: true, ephemeralEnabled: true,
@ -123,9 +118,7 @@ func TestEphemeralLimits(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.test, func(t *testing.T) { t.Run(test.test, func(t *testing.T) {
fts := feature.Features{ fts := feature.Features{}
EnableGenericEphemeralVolume: test.ephemeralEnabled,
}
node, csiNode := getNodeWithPodAndVolumeLimits("node", test.existingPods, int64(test.maxVols), filterName) node, csiNode := getNodeWithPodAndVolumeLimits("node", test.existingPods, int64(test.maxVols), filterName)
p := newNonCSILimits(filterName, getFakeCSINodeLister(csiNode), getFakeCSIStorageClassLister(filterName, driverName), getFakePVLister(filterName), append(getFakePVCLister(filterName), test.extraClaims...), fts).(framework.FilterPlugin) p := newNonCSILimits(filterName, getFakeCSINodeLister(csiNode), getFakeCSIStorageClassLister(filterName, driverName), getFakePVLister(filterName), append(getFakePVCLister(filterName), test.extraClaims...), fts).(framework.FilterPlugin)
gotStatus := p.Filter(context.Background(), nil, test.newPod, node) gotStatus := p.Filter(context.Background(), nil, test.newPod, node)

View File

@ -54,7 +54,6 @@ func NewInTreeRegistry() runtime.Registry {
EnableReadWriteOncePod: feature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod), EnableReadWriteOncePod: feature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority), EnableVolumeCapacityPriority: feature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
EnableCSIStorageCapacity: feature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity), EnableCSIStorageCapacity: feature.DefaultFeatureGate.Enabled(features.CSIStorageCapacity),
EnableGenericEphemeralVolume: feature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume),
} }
return runtime.Registry{ return runtime.Registry{

View File

@ -692,12 +692,6 @@ func (b *volumeBinder) isVolumeBound(pod *v1.Pod, vol *v1.Volume) (bound bool, p
case vol.PersistentVolumeClaim != nil: case vol.PersistentVolumeClaim != nil:
pvcName = vol.PersistentVolumeClaim.ClaimName pvcName = vol.PersistentVolumeClaim.ClaimName
case vol.Ephemeral != nil: case vol.Ephemeral != nil:
if !utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) {
return false, nil, fmt.Errorf(
"volume %s is a generic ephemeral volume, but that feature is disabled in kube-scheduler",
vol.Name,
)
}
// Generic ephemeral inline volumes also use a PVC, // Generic ephemeral inline volumes also use a PVC,
// just with a computed name, and... // just with a computed name, and...
pvcName = ephemeral.VolumeClaimName(pod, vol) pvcName = ephemeral.VolumeClaimName(pod, vol)

View File

@ -833,9 +833,6 @@ func TestFindPodVolumesWithoutProvisioning(t *testing.T) {
// If nil, makePod with podPVCs // If nil, makePod with podPVCs
pod *v1.Pod pod *v1.Pod
// GenericEphemeralVolume feature enabled?
ephemeral bool
// Expected podBindingCache fields // Expected podBindingCache fields
expectedBindings []*BindingInfo expectedBindings []*BindingInfo
@ -942,7 +939,6 @@ func TestFindPodVolumesWithoutProvisioning(t *testing.T) {
withNamespace("testns"). withNamespace("testns").
withNodeName("node1"). withNodeName("node1").
withGenericEphemeralVolume("no-such-pvc").Pod, withGenericEphemeralVolume("no-such-pvc").Pod,
ephemeral: true,
shouldFail: true, shouldFail: true,
}, },
"generic-ephemeral,with-pvc": { "generic-ephemeral,with-pvc": {
@ -952,7 +948,6 @@ func TestFindPodVolumesWithoutProvisioning(t *testing.T) {
withGenericEphemeralVolume("test-volume").Pod, withGenericEphemeralVolume("test-volume").Pod,
cachePVCs: []*v1.PersistentVolumeClaim{correctGenericPVC}, cachePVCs: []*v1.PersistentVolumeClaim{correctGenericPVC},
pvs: []*v1.PersistentVolume{pvBoundGeneric}, pvs: []*v1.PersistentVolume{pvBoundGeneric},
ephemeral: true,
}, },
"generic-ephemeral,wrong-pvc": { "generic-ephemeral,wrong-pvc": {
pod: makePod("test-pod"). pod: makePod("test-pod").
@ -961,17 +956,6 @@ func TestFindPodVolumesWithoutProvisioning(t *testing.T) {
withGenericEphemeralVolume("test-volume").Pod, withGenericEphemeralVolume("test-volume").Pod,
cachePVCs: []*v1.PersistentVolumeClaim{conflictingGenericPVC}, cachePVCs: []*v1.PersistentVolumeClaim{conflictingGenericPVC},
pvs: []*v1.PersistentVolume{pvBoundGeneric}, pvs: []*v1.PersistentVolume{pvBoundGeneric},
ephemeral: true,
shouldFail: true,
},
"generic-ephemeral,disabled": {
pod: makePod("test-pod").
withNamespace("testns").
withNodeName("node1").
withGenericEphemeralVolume("test-volume").Pod,
cachePVCs: []*v1.PersistentVolumeClaim{correctGenericPVC},
pvs: []*v1.PersistentVolume{pvBoundGeneric},
ephemeral: false,
shouldFail: true, shouldFail: true,
}, },
} }
@ -986,8 +970,6 @@ func TestFindPodVolumesWithoutProvisioning(t *testing.T) {
} }
run := func(t *testing.T, scenario scenarioType, csiStorageCapacity bool, csiDriver *storagev1.CSIDriver) { run := func(t *testing.T, scenario scenarioType, csiStorageCapacity bool, csiDriver *storagev1.CSIDriver) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, scenario.ephemeral)()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()

View File

@ -69,11 +69,10 @@ 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 SchedulerVolumeBinder Binder SchedulerVolumeBinder
PVCLister corelisters.PersistentVolumeClaimLister PVCLister corelisters.PersistentVolumeClaimLister
GenericEphemeralVolumeFeatureEnabled bool scorer volumeCapacityScorer
scorer volumeCapacityScorer fts feature.Features
fts feature.Features
} }
var _ framework.PreFilterPlugin = &VolumeBinding{} var _ framework.PreFilterPlugin = &VolumeBinding{}
@ -131,7 +130,7 @@ func (pl *VolumeBinding) podHasPVCs(pod *v1.Pod) (bool, error) {
switch { switch {
case vol.PersistentVolumeClaim != nil: case vol.PersistentVolumeClaim != nil:
pvcName = vol.PersistentVolumeClaim.ClaimName pvcName = vol.PersistentVolumeClaim.ClaimName
case vol.Ephemeral != nil && pl.GenericEphemeralVolumeFeatureEnabled: case vol.Ephemeral != nil:
pvcName = ephemeral.VolumeClaimName(pod, &vol) pvcName = ephemeral.VolumeClaimName(pod, &vol)
isEphemeral = true isEphemeral = true
default: default:
@ -402,10 +401,9 @@ func New(plArgs runtime.Object, fh framework.Handle, fts feature.Features) (fram
scorer = buildScorerFunction(shape) scorer = buildScorerFunction(shape)
} }
return &VolumeBinding{ return &VolumeBinding{
Binder: binder, Binder: binder,
PVCLister: pvcInformer.Lister(), PVCLister: pvcInformer.Lister(),
GenericEphemeralVolumeFeatureEnabled: fts.EnableGenericEphemeralVolume, scorer: scorer,
scorer: scorer, fts: fts,
fts: fts,
}, nil }, nil
} }

View File

@ -1417,7 +1417,6 @@ func moveContainersToEphemeral(in *api.Pod) *api.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

@ -630,7 +630,6 @@ 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

@ -21,11 +21,9 @@ import (
"time" "time"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/component-helpers/storage/ephemeral" "k8s.io/component-helpers/storage/ephemeral"
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"
) )
@ -386,7 +384,7 @@ func (g *Graph) AddPod(pod *corev1.Pod) {
claimName := "" claimName := ""
if v.PersistentVolumeClaim != nil { if v.PersistentVolumeClaim != nil {
claimName = v.PersistentVolumeClaim.ClaimName claimName = v.PersistentVolumeClaim.ClaimName
} else if v.Ephemeral != nil && utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) { } else if v.Ephemeral != nil {
claimName = ephemeral.VolumeClaimName(pod, &v) claimName = ephemeral.VolumeClaimName(pod, &v)
} }
if claimName != "" { if claimName != "" {

View File

@ -193,17 +193,15 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
}) })
} }
if utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume) { addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{
addControllerRole(&controllerRoles, &controllerRoleBindings, rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "ephemeral-volume-controller"},
ObjectMeta: metav1.ObjectMeta{Name: saRolePrefix + "ephemeral-volume-controller"}, Rules: []rbacv1.PolicyRule{
Rules: []rbacv1.PolicyRule{ rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(),
rbacv1helpers.NewRule("get", "list", "watch").Groups(legacyGroup).Resources("pods").RuleOrDie(), rbacv1helpers.NewRule("update").Groups(legacyGroup).Resources("pods/finalizers").RuleOrDie(),
rbacv1helpers.NewRule("update").Groups(legacyGroup).Resources("pods/finalizers").RuleOrDie(), rbacv1helpers.NewRule("get", "list", "watch", "create").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(),
rbacv1helpers.NewRule("get", "list", "watch", "create").Groups(legacyGroup).Resources("persistentvolumeclaims").RuleOrDie(), eventsRule(),
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"},

View File

@ -5573,9 +5573,6 @@ message VolumeSource {
// A pod can use both types of ephemeral volumes and // A pod can use both types of ephemeral volumes and
// persistent volumes at the same time. // persistent volumes at the same time.
// //
// This is a beta feature and only available when the GenericEphemeralVolume
// feature gate is enabled.
//
// +optional // +optional
optional EphemeralVolumeSource ephemeral = 29; optional EphemeralVolumeSource ephemeral = 29;
} }

View File

@ -179,9 +179,6 @@ type VolumeSource struct {
// A pod can use both types of ephemeral volumes and // A pod can use both types of ephemeral volumes and
// persistent volumes at the same time. // persistent volumes at the same time.
// //
// This is a beta feature and only available when the GenericEphemeralVolume
// feature gate is enabled.
//
// +optional // +optional
Ephemeral *EphemeralVolumeSource `json:"ephemeral,omitempty" protobuf:"bytes,29,opt,name=ephemeral"` Ephemeral *EphemeralVolumeSource `json:"ephemeral,omitempty" protobuf:"bytes,29,opt,name=ephemeral"`
} }

View File

@ -2472,7 +2472,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. 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.\n\nThis is a beta feature and only available when the GenericEphemeralVolume feature gate is enabled.", "ephemeral": "Ephemeral represents a volume that is handled by a cluster storage driver. 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

@ -119,6 +119,9 @@ func (p *ephemeralTestSuite) DefineTests(driver storageframework.TestDriver, pat
eDriver, _ = driver.(storageframework.EphemeralTestDriver) eDriver, _ = driver.(storageframework.EphemeralTestDriver)
} }
if pattern.VolType == storageframework.GenericEphemeralVolume { if pattern.VolType == storageframework.GenericEphemeralVolume {
// The GenericEphemeralVolume feature is GA, but
// perhaps this test is run against an older Kubernetes
// where the feature might be disabled.
enabled, err := GenericEphemeralVolumesEnabled(f.ClientSet, f.Timeouts, f.Namespace.Name) enabled, err := GenericEphemeralVolumesEnabled(f.ClientSet, f.Timeouts, f.Namespace.Name)
framework.ExpectNoError(err, "check GenericEphemeralVolume feature") framework.ExpectNoError(err, "check GenericEphemeralVolume feature")
if !enabled { if !enabled {