diff --git a/README.md b/README.md index 72bdc5e02..c0ef8b6a8 100644 --- a/README.md +++ b/README.md @@ -182,10 +182,11 @@ The Default Evictor Plugin is used by default for filtering pods before processi > Setting a value in `extraEnabled` **enables an additional protection rule**. This means the specified type of Pods will be **protected** from eviction. -| Value | Meaning | -|--------------------|------------------------------------------------------------------------| -| `"PodsWithPVC"` | Prevents eviction of Pods using Persistent Volume Claims (PVCs). | -| `"PodsWithoutPDB"` | Prevents eviction of Pods without a PodDisruptionBudget (PDB). | +| Value | Meaning | +|----------------------------|------------------------------------------------------------------| +| `"PodsWithPVC"` | Prevents eviction of Pods using Persistent Volume Claims (PVCs). | +| `"PodsWithoutPDB"` | Prevents eviction of Pods without a PodDisruptionBudget (PDB). | +| `"PodsWithResourceClaims"` | Prevents eviction of Pods using ResourceClaims. | ### Example policy diff --git a/pkg/descheduler/policyconfig_test.go b/pkg/descheduler/policyconfig_test.go index d9ba441c6..9bcf065bc 100644 --- a/pkg/descheduler/policyconfig_test.go +++ b/pkg/descheduler/policyconfig_test.go @@ -750,7 +750,7 @@ profiles: - "DefaultEvictor" `), result: nil, - err: fmt.Errorf("in profile ProfileName: invalid pod protection policy in ExtraEnabled: \"InvalidProtection\". Valid options are: [PodsWithPVC PodsWithoutPDB]"), + err: fmt.Errorf("in profile ProfileName: invalid pod protection policy in ExtraEnabled: \"InvalidProtection\". Valid options are: [PodsWithPVC PodsWithoutPDB PodsWithResourceClaims]"), }, { description: "test error when Disables an extra protection", @@ -802,7 +802,7 @@ profiles: - "DefaultEvictor" `), result: nil, - err: fmt.Errorf("in profile ProfileName: invalid pod protection policy in ExtraEnabled: \"DaemonSetPods\". Valid options are: [PodsWithPVC PodsWithoutPDB]"), + err: fmt.Errorf("in profile ProfileName: invalid pod protection policy in ExtraEnabled: \"DaemonSetPods\". Valid options are: [PodsWithPVC PodsWithoutPDB PodsWithResourceClaims]"), }, } diff --git a/pkg/framework/plugins/defaultevictor/constraints.go b/pkg/framework/plugins/defaultevictor/constraints.go index 9772ea185..ffa57be89 100644 --- a/pkg/framework/plugins/defaultevictor/constraints.go +++ b/pkg/framework/plugins/defaultevictor/constraints.go @@ -211,3 +211,17 @@ func evictionConstraintsForIgnorePodsWithoutPDB(ignorePodsWithoutPDB bool, handl } return nil } + +func evictionConstraintsForResourceClaimsPods(protectionEnabled bool) []constraint { + if protectionEnabled { + return []constraint{ + func(pod *v1.Pod) error { + if utils.IsPodWithResourceClaims(pod) { + return fmt.Errorf("pod has ResourceClaims and descheduler is configured to protect ResourceClaims pods") + } + return nil + }, + } + } + return nil +} diff --git a/pkg/framework/plugins/defaultevictor/defaultevictor.go b/pkg/framework/plugins/defaultevictor/defaultevictor.go index 9eee814b4..a200269ac 100644 --- a/pkg/framework/plugins/defaultevictor/defaultevictor.go +++ b/pkg/framework/plugins/defaultevictor/defaultevictor.go @@ -124,6 +124,7 @@ func applyEffectivePodProtections(d *DefaultEvictor, podProtections []PodProtect applyDaemonSetPodsProtection(d, protectionMap) applyPvcPodsProtection(d, protectionMap) applyPodsWithoutPDBProtection(d, protectionMap, handle) + applyPodsWithResourceClaimsProtection(d, protectionMap) return nil } @@ -168,6 +169,11 @@ func applyPodsWithoutPDBProtection(d *DefaultEvictor, protectionMap map[PodProte d.constraints = append(d.constraints, evictionConstraintsForIgnorePodsWithoutPDB(isProtectionEnabled, handle)...) } +func applyPodsWithResourceClaimsProtection(d *DefaultEvictor, protectionMap map[PodProtection]bool) { + isProtectionEnabled := protectionMap[PodsWithResourceClaims] + d.constraints = append(d.constraints, evictionConstraintsForResourceClaimsPods(isProtectionEnabled)...) +} + // getEffectivePodProtections determines which policies are currently active. // It supports both new-style (PodProtections) and legacy-style flags. func getEffectivePodProtections(args *DefaultEvictorArgs) []PodProtection { diff --git a/pkg/framework/plugins/defaultevictor/defaultevictor_test.go b/pkg/framework/plugins/defaultevictor/defaultevictor_test.go index 7bf6ccac0..56d89a223 100644 --- a/pkg/framework/plugins/defaultevictor/defaultevictor_test.go +++ b/pkg/framework/plugins/defaultevictor/defaultevictor_test.go @@ -28,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/util/uuid" "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes/fake" + "k8s.io/utils/ptr" "sigs.k8s.io/descheduler/pkg/api" evictionutils "sigs.k8s.io/descheduler/pkg/descheduler/evictions/utils" podutil "sigs.k8s.io/descheduler/pkg/descheduler/pod" @@ -860,6 +861,24 @@ func TestDefaultEvictorFilter(t *testing.T) { }, result: false, }, + { + description: "Pod with ResourceClaims is not evicted because 'PodsWithResourceClaims' is in extraPodProtections", + pods: []*v1.Pod{ + test.BuildTestPod("p20", 400, 0, n1.Name, func(pod *v1.Pod) { + pod.ObjectMeta.OwnerReferences = test.GetNormalPodOwnerRefList() + pod.Spec.ResourceClaims = []v1.PodResourceClaim{ + { + Name: "test-claim", + ResourceClaimName: ptr.To("test-resource-claim"), + }, + } + }), + }, + podProtections: PodProtections{ + ExtraEnabled: []PodProtection{PodsWithResourceClaims}, + }, + result: false, + }, } for _, test := range testCases { @@ -1049,6 +1068,26 @@ func TestGetEffectivePodProtections_TableDriven(t *testing.T) { }, wantResult: []PodProtection{PodsWithLocalStorage, SystemCriticalPods, PodsWithPVC}, }, + { + name: "NewConfig_EnableOneExtra(PodsWithResourceClaims)_ReturnsDefaultPlusOne", + args: &DefaultEvictorArgs{ + PodProtections: PodProtections{ + DefaultDisabled: []PodProtection{}, + ExtraEnabled: []PodProtection{PodsWithResourceClaims}, + }, + }, + wantResult: append(defaultSet, PodsWithResourceClaims), + }, + { + name: "NewConfig_DisableAndEnable_ReturnsModifiedSet", + args: &DefaultEvictorArgs{ + PodProtections: PodProtections{ + DefaultDisabled: []PodProtection{FailedBarePods, DaemonSetPods}, + ExtraEnabled: []PodProtection{PodsWithPVC, PodsWithResourceClaims}, + }, + }, + wantResult: []PodProtection{PodsWithLocalStorage, SystemCriticalPods, PodsWithPVC, PodsWithResourceClaims}, + }, } for _, tt := range tests { diff --git a/pkg/framework/plugins/defaultevictor/types.go b/pkg/framework/plugins/defaultevictor/types.go index 44c1212b4..7c6600d47 100644 --- a/pkg/framework/plugins/defaultevictor/types.go +++ b/pkg/framework/plugins/defaultevictor/types.go @@ -55,12 +55,13 @@ type DefaultEvictorArgs struct { type PodProtection string const ( - PodsWithLocalStorage PodProtection = "PodsWithLocalStorage" - DaemonSetPods PodProtection = "DaemonSetPods" - SystemCriticalPods PodProtection = "SystemCriticalPods" - FailedBarePods PodProtection = "FailedBarePods" - PodsWithPVC PodProtection = "PodsWithPVC" - PodsWithoutPDB PodProtection = "PodsWithoutPDB" + PodsWithLocalStorage PodProtection = "PodsWithLocalStorage" + DaemonSetPods PodProtection = "DaemonSetPods" + SystemCriticalPods PodProtection = "SystemCriticalPods" + FailedBarePods PodProtection = "FailedBarePods" + PodsWithPVC PodProtection = "PodsWithPVC" + PodsWithoutPDB PodProtection = "PodsWithoutPDB" + PodsWithResourceClaims PodProtection = "PodsWithResourceClaims" ) // PodProtections holds the list of enabled and disabled protection policies. @@ -98,9 +99,11 @@ var defaultPodProtections = []PodProtection{ // Currently supported extra policies: // - PodsWithPVC: Protects pods using PersistentVolumeClaims. // - PodsWithoutPDB: Protects pods lacking a PodDisruptionBudget. +// - PodsWithResourceClaims: Protects pods using ResourceClaims. var extraPodProtections = []PodProtection{ PodsWithPVC, PodsWithoutPDB, + PodsWithResourceClaims, } // NoEvictionPolicy dictates whether a no-eviction policy is preferred or mandatory. diff --git a/pkg/framework/plugins/defaultevictor/validation_test.go b/pkg/framework/plugins/defaultevictor/validation_test.go index c50a1ec22..7e946edfe 100644 --- a/pkg/framework/plugins/defaultevictor/validation_test.go +++ b/pkg/framework/plugins/defaultevictor/validation_test.go @@ -122,7 +122,7 @@ func TestValidateDefaultEvictorArgs(t *testing.T) { ExtraEnabled: []PodProtection{"InvalidPolicy"}, }, }, - errInfo: fmt.Errorf(`invalid pod protection policy in ExtraEnabled: "InvalidPolicy". Valid options are: [PodsWithPVC PodsWithoutPDB]`), + errInfo: fmt.Errorf(`invalid pod protection policy in ExtraEnabled: "InvalidPolicy". Valid options are: [PodsWithPVC PodsWithoutPDB PodsWithResourceClaims]`), }, { name: "Invalid ExtraEnabled: Misspelled policy", @@ -131,7 +131,7 @@ func TestValidateDefaultEvictorArgs(t *testing.T) { ExtraEnabled: []PodProtection{"PodsWithPVCC"}, }, }, - errInfo: fmt.Errorf(`invalid pod protection policy in ExtraEnabled: "PodsWithPVCC". Valid options are: [PodsWithPVC PodsWithoutPDB]`), + errInfo: fmt.Errorf(`invalid pod protection policy in ExtraEnabled: "PodsWithPVCC". Valid options are: [PodsWithPVC PodsWithoutPDB PodsWithResourceClaims]`), }, { name: "Invalid ExtraEnabled: Policy from DefaultDisabled list", @@ -140,7 +140,7 @@ func TestValidateDefaultEvictorArgs(t *testing.T) { ExtraEnabled: []PodProtection{DaemonSetPods}, }, }, - errInfo: fmt.Errorf(`invalid pod protection policy in ExtraEnabled: "DaemonSetPods". Valid options are: [PodsWithPVC PodsWithoutPDB]`), + errInfo: fmt.Errorf(`invalid pod protection policy in ExtraEnabled: "DaemonSetPods". Valid options are: [PodsWithPVC PodsWithoutPDB PodsWithResourceClaims]`), }, { name: "Invalid DefaultDisabled: Unknown policy", diff --git a/pkg/utils/pod.go b/pkg/utils/pod.go index 3b015317e..e3148e65c 100644 --- a/pkg/utils/pod.go +++ b/pkg/utils/pod.go @@ -120,6 +120,11 @@ func IsPodWithPVC(pod *v1.Pod) bool { return false } +// IsPodWithResourceClaims returns true if the pod has resource claims. +func IsPodWithResourceClaims(pod *v1.Pod) bool { + return len(pod.Spec.ResourceClaims) != 0 +} + // IsPodCoveredByPDB returns true if the pod is covered by at least one PodDisruptionBudget. func IsPodCoveredByPDB(pod *v1.Pod, lister policyv1.PodDisruptionBudgetLister) (bool, error) { // We can't use the GetPodPodDisruptionBudgets expansion method here because it treats no pdb as an error,