Merge pull request #105609 from pohly/generic-ephemeral-volume-ga

generic ephemeral volume GA
This commit is contained in:
Kubernetes Prow Robot 2021-10-28 17:36:50 -07:00 committed by GitHub
commit c592bd40f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 175 additions and 570 deletions

View File

@ -9728,7 +9728,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,7 +368,6 @@ 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(),
@ -378,8 +377,6 @@ func startEphemeralVolumeController(ctx context.Context, controllerContext Contr
} }
go ephemeralController.Run(int(controllerContext.ComponentConfig.EphemeralVolumeController.ConcurrentEphemeralVolumeSyncs), ctx.Done()) go ephemeralController.Run(int(controllerContext.ComponentConfig.EphemeralVolumeController.ConcurrentEphemeralVolumeSyncs), ctx.Done())
return nil, true, nil return nil, true, nil
}
return nil, false, 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"
@ -92,26 +92,19 @@ func ValidatePodDisruptionBudgetStatusUpdate(status, oldStatus policy.PodDisrupt
// trailing dashes are allowed. // trailing dashes are allowed.
var ValidatePodSecurityPolicyName = apimachineryvalidation.NameIsDNSSubdomain var ValidatePodSecurityPolicyName = apimachineryvalidation.NameIsDNSSubdomain
// PodSecurityPolicyValidationOptions contains additional parameters for ValidatePodSecurityPolicy.
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
// with any errors. // with any errors.
func ValidatePodSecurityPolicy(psp *policy.PodSecurityPolicy, opts PodSecurityPolicyValidationOptions) field.ErrorList { func ValidatePodSecurityPolicy(psp *policy.PodSecurityPolicy) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&psp.ObjectMeta, false, ValidatePodSecurityPolicyName, field.NewPath("metadata"))...) allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&psp.ObjectMeta, false, ValidatePodSecurityPolicyName, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(psp.Annotations, field.NewPath("metadata").Child("annotations"))...) allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(psp.Annotations, field.NewPath("metadata").Child("annotations"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&psp.Spec, opts, field.NewPath("spec"))...) allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&psp.Spec, field.NewPath("spec"))...)
return allErrs return allErrs
} }
// ValidatePodSecurityPolicySpec validates a PodSecurityPolicySpec and returns an ErrorList // ValidatePodSecurityPolicySpec validates a PodSecurityPolicySpec and returns an ErrorList
// with any errors. // with any errors.
func ValidatePodSecurityPolicySpec(spec *policy.PodSecurityPolicySpec, opts PodSecurityPolicyValidationOptions, fldPath *field.Path) field.ErrorList { func ValidatePodSecurityPolicySpec(spec *policy.PodSecurityPolicySpec, fldPath *field.Path) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, validatePSPRunAsUser(fldPath.Child("runAsUser"), &spec.RunAsUser)...) allErrs = append(allErrs, validatePSPRunAsUser(fldPath.Child("runAsUser"), &spec.RunAsUser)...)
@ -119,7 +112,7 @@ func ValidatePodSecurityPolicySpec(spec *policy.PodSecurityPolicySpec, opts PodS
allErrs = append(allErrs, validatePSPSELinux(fldPath.Child("seLinux"), &spec.SELinux)...) allErrs = append(allErrs, validatePSPSELinux(fldPath.Child("seLinux"), &spec.SELinux)...)
allErrs = append(allErrs, validatePSPSupplementalGroup(fldPath.Child("supplementalGroups"), &spec.SupplementalGroups)...) allErrs = append(allErrs, validatePSPSupplementalGroup(fldPath.Child("supplementalGroups"), &spec.SupplementalGroups)...)
allErrs = append(allErrs, validatePSPFSGroup(fldPath.Child("fsGroup"), &spec.FSGroup)...) allErrs = append(allErrs, validatePSPFSGroup(fldPath.Child("fsGroup"), &spec.FSGroup)...)
allErrs = append(allErrs, validatePodSecurityPolicyVolumes(opts, fldPath, spec.Volumes)...) allErrs = append(allErrs, validatePodSecurityPolicyVolumes(fldPath, spec.Volumes)...)
if len(spec.RequiredDropCapabilities) > 0 && hasCap(policy.AllowAllCapabilities, spec.AllowedCapabilities) { if len(spec.RequiredDropCapabilities) > 0 && hasCap(policy.AllowAllCapabilities, spec.AllowedCapabilities) {
allErrs = append(allErrs, field.Invalid(field.NewPath("requiredDropCapabilities"), spec.RequiredDropCapabilities, allErrs = append(allErrs, field.Invalid(field.NewPath("requiredDropCapabilities"), spec.RequiredDropCapabilities,
"must be empty when all capabilities are allowed by a wildcard")) "must be empty when all capabilities are allowed by a wildcard"))
@ -327,15 +320,11 @@ func validatePSPSupplementalGroup(fldPath *field.Path, groupOptions *policy.Supp
} }
// validatePodSecurityPolicyVolumes validates the volume fields of PodSecurityPolicy. // validatePodSecurityPolicyVolumes validates the volume fields of PodSecurityPolicy.
func validatePodSecurityPolicyVolumes(opts PodSecurityPolicyValidationOptions, fldPath *field.Path, volumes []policy.FSType) field.ErrorList { func validatePodSecurityPolicyVolumes(fldPath *field.Path, volumes []policy.FSType) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
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()))
@ -530,11 +519,11 @@ func validateRuntimeClassStrategy(fldPath *field.Path, rc *policy.RuntimeClassSt
} }
// ValidatePodSecurityPolicyUpdate validates a PSP for updates. // ValidatePodSecurityPolicyUpdate validates a PSP for updates.
func ValidatePodSecurityPolicyUpdate(old *policy.PodSecurityPolicy, new *policy.PodSecurityPolicy, opts PodSecurityPolicyValidationOptions) field.ErrorList { func ValidatePodSecurityPolicyUpdate(old *policy.PodSecurityPolicy, new *policy.PodSecurityPolicy) field.ErrorList {
allErrs := field.ErrorList{} allErrs := field.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&new.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&new.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(new.Annotations, field.NewPath("metadata").Child("annotations"))...) allErrs = append(allErrs, ValidatePodSecurityPolicySpecificAnnotations(new.Annotations, field.NewPath("metadata").Child("annotations"))...)
allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&new.Spec, opts, field.NewPath("spec"))...) allErrs = append(allErrs, ValidatePodSecurityPolicySpec(&new.Spec, field.NewPath("spec"))...)
return allErrs return allErrs
} }

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"
@ -590,7 +590,7 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
} }
for k, v := range errorCases { for k, v := range errorCases {
errs := ValidatePodSecurityPolicy(v.psp, PodSecurityPolicyValidationOptions{}) errs := ValidatePodSecurityPolicy(v.psp)
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("%s expected errors but got none", k) t.Errorf("%s expected errors but got none", k)
continue continue
@ -613,7 +613,7 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
// Should not be able to update to an invalid policy. // Should not be able to update to an invalid policy.
for k, v := range errorCases { for k, v := range errorCases {
v.psp.ResourceVersion = "444" // Required for updates. v.psp.ResourceVersion = "444" // Required for updates.
errs := ValidatePodSecurityPolicyUpdate(validPSP(), v.psp, PodSecurityPolicyValidationOptions{}) errs := ValidatePodSecurityPolicyUpdate(validPSP(), v.psp)
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("[%s] expected update errors but got none", k) t.Errorf("[%s] expected update errors but got none", k)
continue continue
@ -743,13 +743,13 @@ func TestValidatePodSecurityPolicy(t *testing.T) {
} }
for k, v := range successCases { for k, v := range successCases {
if errs := ValidatePodSecurityPolicy(v.psp, PodSecurityPolicyValidationOptions{}); len(errs) != 0 { if errs := ValidatePodSecurityPolicy(v.psp); len(errs) != 0 {
t.Errorf("Expected success for %s, got %v", k, errs) t.Errorf("Expected success for %s, got %v", k, errs)
} }
// Should be able to update to a valid PSP. // Should be able to update to a valid PSP.
v.psp.ResourceVersion = "444" // Required for updates. v.psp.ResourceVersion = "444" // Required for updates.
if errs := ValidatePodSecurityPolicyUpdate(validPSP(), v.psp, PodSecurityPolicyValidationOptions{}); len(errs) != 0 { if errs := ValidatePodSecurityPolicyUpdate(validPSP(), v.psp); len(errs) != 0 {
t.Errorf("Expected success for %s update, got %v", k, errs) t.Errorf("Expected success for %s update, got %v", k, errs)
} }
} }
@ -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)
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,7 +1118,6 @@ 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()
@ -1127,25 +1126,17 @@ func TestAllowEphemeralVolumeType(t *testing.T) {
continue continue
} }
t.Run(fmt.Sprintf("feature enabled=%v, old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", allowed, oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) { t.Run(fmt.Sprintf("old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) {
opts := PodSecurityPolicyValidationOptions{
AllowEphemeralVolumeType: allowed,
}
var errs field.ErrorList var errs field.ErrorList
expectErrors := newPSPInfo.hasGenericVolume && !allowed
if oldPSP == nil { if oldPSP == nil {
errs = ValidatePodSecurityPolicy(newPSP, opts) errs = ValidatePodSecurityPolicy(newPSP)
} else { } else {
errs = ValidatePodSecurityPolicyUpdate(oldPSP, newPSP, opts) errs = ValidatePodSecurityPolicyUpdate(oldPSP, newPSP)
} }
if expectErrors && len(errs) == 0 { if len(errs) > 0 {
t.Error("expected errors, got none")
}
if !expectErrors && len(errs) > 0 {
t.Errorf("expected no errors, got: %v", errs) 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

@ -171,6 +171,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"
@ -826,7 +827,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},
VolumeSubpath: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25 VolumeSubpath: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.25
RuntimeClass: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23 RuntimeClass: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // remove in 1.23

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"
@ -2985,7 +2982,6 @@ func TestHasHostMountPVC(t *testing.T) {
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},
@ -3007,15 +3003,8 @@ func TestHasHostMountPVC(t *testing.T) {
"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,
pvcIsHostPath: false, pvcIsHostPath: false,
@ -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,39 +72,17 @@ 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{ return validation.ValidatePodSecurityPolicy(obj.(*policy.PodSecurityPolicy))
// Only allowed if the feature is enabled.
AllowEphemeralVolumeType: utilfeature.DefaultFeatureGate.Enabled(features.GenericEphemeralVolume),
}
return validation.ValidatePodSecurityPolicy(obj.(*policy.PodSecurityPolicy), opts)
} }
// WarningsOnCreate returns warnings for the creation of the given object. // WarningsOnCreate returns warnings for the creation of the given object.
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{ return validation.ValidatePodSecurityPolicyUpdate(old.(*policy.PodSecurityPolicy), obj.(*policy.PodSecurityPolicy))
// 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)
} }
// WarningsOnUpdate returns warnings for the given update. // WarningsOnUpdate returns warnings for the given update.
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,7 +80,6 @@ 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()
@ -92,26 +88,17 @@ func TestAllowEphemeralVolumeType(t *testing.T) {
continue continue
} }
t.Run(fmt.Sprintf("feature enabled=%v, old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", enabled, oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) { t.Run(fmt.Sprintf("old PodSecurityPolicySpec %v, new PodSecurityPolicySpec %v", oldPSPInfo.description, newPSPInfo.description), func(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.GenericEphemeralVolume, enabled)()
var errs field.ErrorList var errs field.ErrorList
var expectErrors bool
if oldPSP == nil { if oldPSP == nil {
errs = Strategy.Validate(context.Background(), newPSP) errs = Strategy.Validate(context.Background(), newPSP)
expectErrors = newPSPInfo.hasGenericVolume && !enabled
} else { } else {
errs = Strategy.ValidateUpdate(context.Background(), newPSP, oldPSP) errs = Strategy.ValidateUpdate(context.Background(), newPSP, oldPSP)
expectErrors = !oldPSPInfo.hasGenericVolume && newPSPInfo.hasGenericVolume && !enabled
} }
if expectErrors && len(errs) == 0 { if len(errs) > 0 {
t.Error("expected errors, got none")
}
if !expectErrors && len(errs) > 0 {
t.Errorf("expected no errors, got: %v", errs) 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

@ -22,11 +22,11 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
storagev1 "k8s.io/api/storage/v1" storagev1 "k8s.io/api/storage/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/rand"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
storagelisters "k8s.io/client-go/listers/storage/v1" storagelisters "k8s.io/client-go/listers/storage/v1"
ephemeral "k8s.io/component-helpers/storage/ephemeral"
storagehelpers "k8s.io/component-helpers/storage/volume" storagehelpers "k8s.io/component-helpers/storage/volume"
csitrans "k8s.io/csi-translation-lib" csitrans "k8s.io/csi-translation-lib"
"k8s.io/klog/v2" "k8s.io/klog/v2"
@ -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{}
@ -152,23 +150,17 @@ func (pl *CSILimits) filterAttachableVolumes(
for _, vol := range pod.Spec.Volumes { for _, vol := range pod.Spec.Volumes {
// CSI volumes can only be used through a PVC. // CSI volumes can only be used through a PVC.
pvcName := "" pvcName := ""
ephemeral := false isEphemeral := false
switch { switch {
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
// retrieved. // retrieved.
pvcName = pod.Name + "-" + vol.Name pvcName = ephemeral.VolumeClaimName(pod, &vol)
ephemeral = true isEphemeral = true
default: default:
continue continue
} }
@ -193,8 +185,10 @@ func (pl *CSILimits) filterAttachableVolumes(
} }
// The PVC for an ephemeral volume must be owned by the pod. // The PVC for an ephemeral volume must be owned by the pod.
if ephemeral && !metav1.IsControlledBy(pvc, pod) { if isEphemeral {
return fmt.Errorf("PVC %s/%s is not owned by pod", pod.Namespace, pvcName) if err := ephemeral.VolumeIsForPod(pod, pvc); err != nil {
return err
}
} }
driverName, volumeHandle := pl.getCSIDriverInfo(csiNode, pvc) driverName, volumeHandle := pl.getCSIDriverInfo(csiNode, pvc)
@ -322,7 +316,6 @@ func NewCSI(_ runtime.Object, handle framework.Handle, fts feature.Features) (fr
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",
@ -568,7 +561,7 @@ func TestCSILimits(t *testing.T) {
extraClaims: []v1.PersistentVolumeClaim{*conflictingClaim}, extraClaims: []v1.PersistentVolumeClaim{*conflictingClaim},
driverNames: []string{ebsCSIDriverName}, driverNames: []string{ebsCSIDriverName},
test: "ephemeral volume not owned", test: "ephemeral volume not owned",
wantStatus: framework.NewStatus(framework.Error, "PVC test/abc-xyz is not owned by pod"), wantStatus: framework.NewStatus(framework.Error, "PVC test/abc-xyz was not created for pod test/abc (pod is not owner)"),
}, },
{ {
newPod: ephemeralVolumePod, newPod: ephemeralVolumePod,
@ -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

@ -26,13 +26,13 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
storage "k8s.io/api/storage/v1" storage "k8s.io/api/storage/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/rand"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
storagelisters "k8s.io/client-go/listers/storage/v1" storagelisters "k8s.io/client-go/listers/storage/v1"
"k8s.io/component-helpers/storage/ephemeral"
csilibplugins "k8s.io/csi-translation-lib/plugins" csilibplugins "k8s.io/csi-translation-lib/plugins"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/scheduler/framework" "k8s.io/kubernetes/pkg/scheduler/framework"
@ -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
@ -288,23 +284,17 @@ func (pl *nonCSILimits) filterVolumes(pod *v1.Pod, newPod bool, filteredVolumes
} }
pvcName := "" pvcName := ""
ephemeral := false isEphemeral := false
switch { switch {
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
// retrieved. // retrieved.
pvcName = pod.Name + "-" + vol.Name pvcName = ephemeral.VolumeClaimName(pod, vol)
ephemeral = true isEphemeral = true
default: default:
continue continue
} }
@ -332,8 +322,10 @@ func (pl *nonCSILimits) filterVolumes(pod *v1.Pod, newPod bool, filteredVolumes
} }
// The PVC for an ephemeral volume must be owned by the pod. // The PVC for an ephemeral volume must be owned by the pod.
if ephemeral && !metav1.IsControlledBy(pvc, pod) { if isEphemeral {
return fmt.Errorf("PVC %s/%s is not owned by pod", pod.Namespace, pvcName) if err := ephemeral.VolumeIsForPod(pod, pvc); err != nil {
return err
}
} }
pvName := pvc.Spec.VolumeName pvName := pvc.Spec.VolumeName

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,
@ -102,7 +97,7 @@ func TestEphemeralLimits(t *testing.T) {
ephemeralEnabled: true, ephemeralEnabled: true,
extraClaims: []v1.PersistentVolumeClaim{*conflictingClaim}, extraClaims: []v1.PersistentVolumeClaim{*conflictingClaim},
test: "volume not owned", test: "volume not owned",
wantStatus: framework.NewStatus(framework.Error, "PVC test/abc-xyz is not owned by pod"), wantStatus: framework.NewStatus(framework.Error, "PVC test/abc-xyz was not created for pod test/abc (pod is not owner)"),
}, },
{ {
newPod: ephemeralVolumePod, newPod: ephemeralVolumePod,
@ -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

@ -51,7 +51,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

@ -40,6 +40,7 @@ import (
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
storagelisters "k8s.io/client-go/listers/storage/v1" storagelisters "k8s.io/client-go/listers/storage/v1"
storagelistersv1beta1 "k8s.io/client-go/listers/storage/v1beta1" storagelistersv1beta1 "k8s.io/client-go/listers/storage/v1beta1"
"k8s.io/component-helpers/storage/ephemeral"
storagehelpers "k8s.io/component-helpers/storage/volume" storagehelpers "k8s.io/component-helpers/storage/volume"
csitrans "k8s.io/csi-translation-lib" csitrans "k8s.io/csi-translation-lib"
csiplugins "k8s.io/csi-translation-lib/plugins" csiplugins "k8s.io/csi-translation-lib/plugins"
@ -686,29 +687,25 @@ func (b *volumeBinder) checkBindings(pod *v1.Pod, bindings []*BindingInfo, claim
func (b *volumeBinder) isVolumeBound(pod *v1.Pod, vol *v1.Volume) (bound bool, pvc *v1.PersistentVolumeClaim, err error) { func (b *volumeBinder) isVolumeBound(pod *v1.Pod, vol *v1.Volume) (bound bool, pvc *v1.PersistentVolumeClaim, err error) {
pvcName := "" pvcName := ""
ephemeral := false isEphemeral := false
switch { switch {
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 = pod.Name + "-" + vol.Name pvcName = ephemeral.VolumeClaimName(pod, vol)
ephemeral = true isEphemeral = true
default: default:
return true, nil, nil return true, nil, nil
} }
bound, pvc, err = b.isPVCBound(pod.Namespace, pvcName) bound, pvc, err = b.isPVCBound(pod.Namespace, pvcName)
// ... the PVC must be owned by the pod. // ... the PVC must be owned by the pod.
if ephemeral && err == nil && pvc != nil && !metav1.IsControlledBy(pvc, pod) { if isEphemeral && err == nil && pvc != nil {
return false, nil, fmt.Errorf("PVC %s/%s is not owned by pod", pod.Namespace, pvcName) if err := ephemeral.VolumeIsForPod(pod, pvc); err != nil {
return false, nil, err
}
} }
return return
} }

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

@ -25,9 +25,9 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
"k8s.io/component-helpers/storage/ephemeral"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/scheduler/apis/config" "k8s.io/kubernetes/pkg/scheduler/apis/config"
"k8s.io/kubernetes/pkg/scheduler/apis/config/validation" "k8s.io/kubernetes/pkg/scheduler/apis/config/validation"
@ -71,7 +71,6 @@ func (d *stateData) Clone() framework.StateData {
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
} }
@ -127,13 +126,13 @@ func (pl *VolumeBinding) podHasPVCs(pod *v1.Pod) (bool, error) {
hasPVC := false hasPVC := false
for _, vol := range pod.Spec.Volumes { for _, vol := range pod.Spec.Volumes {
var pvcName string var pvcName string
ephemeral := false isEphemeral := false
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 = pod.Name + "-" + vol.Name pvcName = ephemeral.VolumeClaimName(pod, &vol)
ephemeral = true isEphemeral = true
default: default:
// Volume is not using a PVC, ignore // Volume is not using a PVC, ignore
continue continue
@ -144,7 +143,7 @@ func (pl *VolumeBinding) podHasPVCs(pod *v1.Pod) (bool, error) {
// The error usually has already enough context ("persistentvolumeclaim "myclaim" not found"), // The error usually has already enough context ("persistentvolumeclaim "myclaim" not found"),
// but we can do better for generic ephemeral inline volumes where that situation // but we can do better for generic ephemeral inline volumes where that situation
// is normal directly after creating a pod. // is normal directly after creating a pod.
if ephemeral && apierrors.IsNotFound(err) { if isEphemeral && apierrors.IsNotFound(err) {
err = fmt.Errorf("waiting for ephemeral volume controller to create the persistentvolumeclaim %q", pvcName) err = fmt.Errorf("waiting for ephemeral volume controller to create the persistentvolumeclaim %q", pvcName)
} }
return hasPVC, err return hasPVC, err
@ -158,8 +157,10 @@ func (pl *VolumeBinding) podHasPVCs(pod *v1.Pod) (bool, error) {
return hasPVC, fmt.Errorf("persistentvolumeclaim %q is being deleted", pvc.Name) return hasPVC, fmt.Errorf("persistentvolumeclaim %q is being deleted", pvc.Name)
} }
if ephemeral && !metav1.IsControlledBy(pvc, pod) { if isEphemeral {
return hasPVC, fmt.Errorf("persistentvolumeclaim %q was not created for the pod", pvc.Name) if err := ephemeral.VolumeIsForPod(pod, pvc); err != nil {
return hasPVC, err
}
} }
} }
return hasPVC, nil return hasPVC, nil
@ -402,7 +403,6 @@ func New(plArgs runtime.Object, fh framework.Handle, fts feature.Features) (fram
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,10 +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"
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"
) )
@ -385,8 +384,8 @@ 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 = pod.Name + "-" + v.Name claimName = ephemeral.VolumeClaimName(pod, &v)
} }
if claimName != "" { if claimName != "" {
pvcVertex := g.getOrCreateVertex_locked(pvcVertexType, pod.Namespace, claimName) pvcVertex := g.getOrCreateVertex_locked(pvcVertexType, pod.Namespace, claimName)

View File

@ -193,7 +193,6 @@ 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{
@ -203,7 +202,6 @@ func buildControllerRoles() ([]rbacv1.ClusterRole, []rbacv1.ClusterRoleBinding)
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

@ -5580,9 +5580,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

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