mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +00:00
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:
parent
bc263f3ba5
commit
a8c930ef46
2
api/openapi-spec/swagger.json
generated
2
api/openapi-spec/swagger.json
generated
@ -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",
|
||||||
|
@ -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)
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()))
|
||||||
|
@ -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,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,18 @@ 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{
|
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, opts)
|
||||||
} else {
|
} else {
|
||||||
errs = ValidatePodSecurityPolicyUpdate(oldPSP, newPSP, opts)
|
errs = ValidatePodSecurityPolicyUpdate(oldPSP, newPSP, opts)
|
||||||
}
|
}
|
||||||
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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) })
|
|
||||||
}
|
|
||||||
|
@ -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},
|
||||||
|
@ -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
|
||||||
|
@ -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{
|
||||||
|
@ -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(),
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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{
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,5 +26,4 @@ type Features struct {
|
|||||||
EnableReadWriteOncePod bool
|
EnableReadWriteOncePod bool
|
||||||
EnableVolumeCapacityPriority bool
|
EnableVolumeCapacityPriority bool
|
||||||
EnableCSIStorageCapacity bool
|
EnableCSIStorageCapacity bool
|
||||||
EnableGenericEphemeralVolume bool
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
@ -326,7 +318,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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{
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
@ -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:
|
||||||
@ -404,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
|
||||||
|
@ -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{})
|
||||||
|
|
||||||
|
@ -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{})
|
||||||
|
|
||||||
|
@ -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 != "" {
|
||||||
|
@ -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"},
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user