This commit is contained in:
Ania Borowiec 2026-05-22 09:44:13 +00:00 committed by GitHub
commit 83e41da8b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 868 additions and 123 deletions

View file

@ -19976,6 +19976,10 @@
"$ref": "#/definitions/io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference",
"description": "PodGroupTemplateRef references an optional PodGroup template within other object (e.g. Workload) that was used to create the PodGroup. This field is immutable."
},
"preemptionPolicy": {
"description": "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"type": "string"
},
"priority": {
"description": "Priority is the value of priority of this pod group. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"format": "int32",
@ -20055,6 +20059,10 @@
"description": "Name is a unique identifier for the PodGroupTemplate within the Workload. It must be a DNS label. This field is immutable.",
"type": "string"
},
"preemptionPolicy": {
"description": "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"type": "string"
},
"priority": {
"description": "Priority is the value of priority of pod groups created from this template. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"format": "int32",

View file

@ -211,6 +211,11 @@
],
"description": "PodGroupTemplateRef references an optional PodGroup template within other object (e.g. Workload) that was used to create the PodGroup. This field is immutable."
},
"preemptionPolicy": {
"default": "PreemptLowerPriority",
"description": "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"type": "string"
},
"priority": {
"description": "Priority is the value of priority of this pod group. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"format": "int32",
@ -300,6 +305,10 @@
"description": "Name is a unique identifier for the PodGroupTemplate within the Workload. It must be a DNS label. This field is immutable.",
"type": "string"
},
"preemptionPolicy": {
"description": "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"type": "string"
},
"priority": {
"description": "Priority is the value of priority of pod groups created from this template. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"format": "int32",

View file

@ -22698,20 +22698,20 @@ func TestValidateSecret(t *testing.T) {
secret core.Secret
valid bool
}{
"valid": {validSecret(), true},
"empty name": {emptyName, false},
"invalid name": {invalidName, false},
"empty namespace": {emptyNs, false},
"invalid namespace": {invalidNs, false},
"over max size": {overMaxSize, false},
"invalid key": {invalidKey, false},
"valid service-account-token secret": {validServiceAccountTokenSecret(), true},
"empty service-account-token annotation": {emptyTokenAnnotation, false},
"valid": {validSecret(), true},
"empty name": {emptyName, false},
"invalid name": {invalidName, false},
"empty namespace": {emptyNs, false},
"invalid namespace": {invalidNs, false},
"over max size": {overMaxSize, false},
"invalid key": {invalidKey, false},
"valid service-account-token secret": {validServiceAccountTokenSecret(), true},
"empty service-account-token annotation": {emptyTokenAnnotation, false},
"missing service-account-token annotation": {missingTokenAnnotation, false},
"missing service-account-token annotations": {missingTokenAnnotations, false},
"leading dot key": {leadingDotKey, true},
"dot key": {dotKey, false},
"double dot key": {doubleDotKey, false},
"leading dot key": {leadingDotKey, true},
"dot key": {dotKey, false},
"double dot key": {doubleDotKey, false},
}
for name, tc := range tests {

View file

@ -38,6 +38,10 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} {
if s.Spec.DisruptionMode == nil {
s.Spec.DisruptionMode = new(scheduling.DisruptionModePod)
}
if s.Spec.PreemptionPolicy == nil {
preemptLowerPriority := core.PreemptLowerPriority
s.Spec.PreemptionPolicy = &preemptLowerPriority
}
},
}
}

View file

@ -244,6 +244,16 @@ type PodGroupTemplate struct {
// +featureGate=WorkloadAwarePreemption
// +optional
Priority *int32
// PreemptionPolicy is the Policy for preempting pods with lower priority.
// One of Never, PreemptLowerPriority.
// Defaults to PreemptLowerPriority if unset.
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
//
// +featureGate=WorkloadAwarePreemption
// +optional
PreemptionPolicy *core.PreemptionPolicy
}
// PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup.
@ -456,6 +466,17 @@ type PodGroupSpec struct {
// +featureGate=WorkloadAwarePreemption
// +optional
Priority *int32
// PreemptionPolicy is the Policy for preempting pods with lower priority.
// One of Never, PreemptLowerPriority.
// Defaults to PreemptLowerPriority if unset.
// This field is immutable.
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
//
// +featureGate=WorkloadAwarePreemption
// +optional
PreemptionPolicy *core.PreemptionPolicy
}
// PodGroupStatus represents information about the status of a pod group.

View file

@ -24,10 +24,12 @@ package v1alpha2
import (
unsafe "unsafe"
v1 "k8s.io/api/core/v1"
schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
core "k8s.io/kubernetes/pkg/apis/core"
scheduling "k8s.io/kubernetes/pkg/apis/scheduling"
)
@ -411,6 +413,7 @@ func autoConvert_v1alpha2_PodGroupSpec_To_scheduling_PodGroupSpec(in *scheduling
out.DisruptionMode = (*scheduling.DisruptionMode)(unsafe.Pointer(in.DisruptionMode))
out.PriorityClassName = in.PriorityClassName
out.Priority = (*int32)(unsafe.Pointer(in.Priority))
out.PreemptionPolicy = (*core.PreemptionPolicy)(unsafe.Pointer(in.PreemptionPolicy))
return nil
}
@ -429,6 +432,7 @@ func autoConvert_scheduling_PodGroupSpec_To_v1alpha2_PodGroupSpec(in *scheduling
out.DisruptionMode = (*schedulingv1alpha2.DisruptionMode)(unsafe.Pointer(in.DisruptionMode))
out.PriorityClassName = in.PriorityClassName
out.Priority = (*int32)(unsafe.Pointer(in.Priority))
out.PreemptionPolicy = (*v1.PreemptionPolicy)(unsafe.Pointer(in.PreemptionPolicy))
return nil
}
@ -438,7 +442,7 @@ func Convert_scheduling_PodGroupSpec_To_v1alpha2_PodGroupSpec(in *scheduling.Pod
}
func autoConvert_v1alpha2_PodGroupStatus_To_scheduling_PodGroupStatus(in *schedulingv1alpha2.PodGroupStatus, out *scheduling.PodGroupStatus, s conversion.Scope) error {
out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions))
out.Conditions = *(*[]metav1.Condition)(unsafe.Pointer(&in.Conditions))
out.ResourceClaimStatuses = *(*[]scheduling.PodGroupResourceClaimStatus)(unsafe.Pointer(&in.ResourceClaimStatuses))
return nil
}
@ -449,7 +453,7 @@ func Convert_v1alpha2_PodGroupStatus_To_scheduling_PodGroupStatus(in *scheduling
}
func autoConvert_scheduling_PodGroupStatus_To_v1alpha2_PodGroupStatus(in *scheduling.PodGroupStatus, out *schedulingv1alpha2.PodGroupStatus, s conversion.Scope) error {
out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions))
out.Conditions = *(*[]metav1.Condition)(unsafe.Pointer(&in.Conditions))
out.ResourceClaimStatuses = *(*[]schedulingv1alpha2.PodGroupResourceClaimStatus)(unsafe.Pointer(&in.ResourceClaimStatuses))
return nil
}
@ -469,6 +473,7 @@ func autoConvert_v1alpha2_PodGroupTemplate_To_scheduling_PodGroupTemplate(in *sc
out.DisruptionMode = (*scheduling.DisruptionMode)(unsafe.Pointer(in.DisruptionMode))
out.PriorityClassName = in.PriorityClassName
out.Priority = (*int32)(unsafe.Pointer(in.Priority))
out.PreemptionPolicy = (*core.PreemptionPolicy)(unsafe.Pointer(in.PreemptionPolicy))
return nil
}
@ -487,6 +492,7 @@ func autoConvert_scheduling_PodGroupTemplate_To_v1alpha2_PodGroupTemplate(in *sc
out.DisruptionMode = (*schedulingv1alpha2.DisruptionMode)(unsafe.Pointer(in.DisruptionMode))
out.PriorityClassName = in.PriorityClassName
out.Priority = (*int32)(unsafe.Pointer(in.Priority))
out.PreemptionPolicy = (*v1.PreemptionPolicy)(unsafe.Pointer(in.PreemptionPolicy))
return nil
}

View file

@ -22,6 +22,7 @@ limitations under the License.
package v1alpha2
import (
v1 "k8s.io/api/core/v1"
schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@ -40,6 +41,10 @@ func SetObjectDefaults_PodGroup(in *schedulingv1alpha2.PodGroup) {
var ptrVar1 schedulingv1alpha2.DisruptionMode = "Pod"
in.Spec.DisruptionMode = &ptrVar1
}
if in.Spec.PreemptionPolicy == nil {
var ptrVar1 v1.PreemptionPolicy = "PreemptLowerPriority"
in.Spec.PreemptionPolicy = &ptrVar1
}
}
func SetObjectDefaults_PodGroupList(in *schedulingv1alpha2.PodGroupList) {

View file

@ -25,6 +25,7 @@ import (
context "context"
fmt "fmt"
v1 "k8s.io/api/core/v1"
schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2"
equality "k8s.io/apimachinery/pkg/api/equality"
operation "k8s.io/apimachinery/pkg/api/operation"
@ -770,6 +771,47 @@ func Validate_PodGroupSpec(
errs = append(errs, fn(fldPath.Child("priority"), obj.Priority, oldVal, oldObj != nil)...)
}
{ // field schedulingv1alpha2.PodGroupSpec.PreemptionPolicy
fn := func(
fldPath *field.Path,
obj, oldObj *v1.PreemptionPolicy,
oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update {
if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) {
return nil
}
}
// call field-attached validations
earlyReturn := false
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.ForbiddenPointer).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, // optional fields with default values are effectively required
validate.RequiredPointer).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.Immutable).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}
oldVal := safe.Field(oldObj,
func(oldObj *schedulingv1alpha2.PodGroupSpec) *v1.PreemptionPolicy {
return oldObj.PreemptionPolicy
})
errs = append(errs, fn(fldPath.Child("preemptionPolicy"), obj.PreemptionPolicy, oldVal, oldObj != nil)...)
}
return errs
}
@ -1089,6 +1131,41 @@ func Validate_PodGroupTemplate(
errs = append(errs, fn(fldPath.Child("priority"), obj.Priority, oldVal, oldObj != nil)...)
}
{ // field schedulingv1alpha2.PodGroupTemplate.PreemptionPolicy
fn := func(
fldPath *field.Path,
obj, oldObj *v1.PreemptionPolicy,
oldValueCorrelated bool) (errs field.ErrorList) {
// don't revalidate unchanged data
if oldValueCorrelated && op.Type == operation.Update {
if obj == oldObj || (obj != nil && oldObj != nil && *obj == *oldObj) {
return nil
}
}
// call field-attached validations
earlyReturn := false
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.ForbiddenPointer).MarkShortCircuit(); len(e) != 0 {
errs = append(errs, e...)
earlyReturn = true
}
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", false, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
if e := validate.IfOption(ctx, op, fldPath, obj, oldObj, "WorkloadAwarePreemption", true, validate.OptionalPointer).MarkShortCircuit(); len(e) != 0 {
earlyReturn = true
}
if earlyReturn {
return // do not proceed
}
return
}
oldVal := safe.Field(oldObj,
func(oldObj *schedulingv1alpha2.PodGroupTemplate) *v1.PreemptionPolicy {
return oldObj.PreemptionPolicy
})
errs = append(errs, fn(fldPath.Child("preemptionPolicy"), obj.PreemptionPolicy, oldVal, oldObj != nil)...)
}
return errs
}

View file

@ -70,7 +70,11 @@ func ValidatePriorityClassUpdate(pc, oldPc *scheduling.PriorityClass) field.Erro
// ValidatePodGroup tests if all fields in a PodGroup are set correctly.
func ValidatePodGroup(podGroup *scheduling.PodGroup) field.ErrorList {
return apivalidation.ValidateObjectMeta(&podGroup.ObjectMeta, true, apivalidation.ValidatePodGroupName, field.NewPath("metadata"))
allErrs := apivalidation.ValidateObjectMeta(&podGroup.ObjectMeta, true, apivalidation.ValidatePodGroupName, field.NewPath("metadata"))
if podGroup.Spec.PreemptionPolicy != nil {
allErrs = append(allErrs, apivalidation.ValidatePreemptionPolicy(podGroup.Spec.PreemptionPolicy, field.NewPath("spec", "preemptionPolicy"))...)
}
return allErrs
}
// ValidatePodGroupUpdate tests if an update to PodGroup is valid.
@ -80,7 +84,15 @@ func ValidatePodGroupUpdate(podGroup, oldPodGroup *scheduling.PodGroup) field.Er
// ValidateWorkload tests if all fields in a Workload are set correctly.
func ValidateWorkload(workload *scheduling.Workload) field.ErrorList {
return apivalidation.ValidateObjectMeta(&workload.ObjectMeta, true, validateWorkloadName, field.NewPath("metadata"))
allErrs := apivalidation.ValidateObjectMeta(&workload.ObjectMeta, true, validateWorkloadName, field.NewPath("metadata"))
fldPath := field.NewPath("spec", "podGroupTemplates")
for i, template := range workload.Spec.PodGroupTemplates {
idxPath := fldPath.Index(i)
if template.PreemptionPolicy != nil {
allErrs = append(allErrs, apivalidation.ValidatePreemptionPolicy(template.PreemptionPolicy, idxPath.Child("preemptionPolicy"))...)
}
}
return allErrs
}
// ValidateWorkloadUpdate tests if an update to Workload is valid.

View file

@ -191,6 +191,15 @@ func TestValidateWorkload(t *testing.T) {
"no scheduling constraints": mkWorkload(func(w *scheduling.Workload) {
w.Spec.PodGroupTemplates[1].SchedulingConstraints = nil
}),
"default preemption policy (nil)": mkWorkload(), // preemption policy is unset (nil) by default
"valid preemption policy (Never)": mkWorkload(func(w *scheduling.Workload) {
preemptNever := core.PreemptNever
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptNever
}),
"valid preemption policy (PreemptLowerPriority)": mkWorkload(func(w *scheduling.Workload) {
preemptLowerPriority := core.PreemptLowerPriority
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptLowerPriority
}),
}
for name, workload := range successCases {
errs := ValidateWorkload(workload)
@ -251,6 +260,15 @@ func TestValidateWorkload(t *testing.T) {
field.Invalid(field.NewPath("metadata", "namespace"), strings.Repeat("n", 64), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
},
},
"invalid preemption policy": {
workload: mkWorkload(func(w *scheduling.Workload) {
invalidPolicy := core.PreemptionPolicy("Invalid")
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &invalidPolicy
}),
expectedErrs: field.ErrorList{
field.NotSupported(field.NewPath("spec", "podGroupTemplates").Index(0).Child("preemptionPolicy"), "Invalid", []string{"PreemptLowerPriority", "Never"}),
},
},
}
for name, tc := range failureCases {
@ -363,6 +381,15 @@ func TestValidatePodGroup(t *testing.T) {
"no scheduling constraints": mkPodGroup(func(pg *scheduling.PodGroup) {
pg.Spec.SchedulingConstraints = nil
}),
"default preemption policy (nil)": mkPodGroup(), // preemption policy is unset (nil) by default
"valid preemption policy (Never)": mkPodGroup(func(pg *scheduling.PodGroup) {
preemptNever := core.PreemptNever
pg.Spec.PreemptionPolicy = &preemptNever
}),
"valid preemption policy (PreemptLowerPriority)": mkPodGroup(func(pg *scheduling.PodGroup) {
preemptLowerPriority := core.PreemptLowerPriority
pg.Spec.PreemptionPolicy = &preemptLowerPriority
}),
}
for name, podGroup := range successCases {
errs := ValidatePodGroup(podGroup)
@ -423,6 +450,15 @@ func TestValidatePodGroup(t *testing.T) {
field.Invalid(field.NewPath("metadata", "namespace"), strings.Repeat("n", 64), "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
},
},
"invalid preemption policy": {
podGroup: mkPodGroup(func(pg *scheduling.PodGroup) {
invalidPolicy := core.PreemptionPolicy("Invalid")
pg.Spec.PreemptionPolicy = &invalidPolicy
}),
expectedErrs: field.ErrorList{
field.NotSupported(field.NewPath("spec", "preemptionPolicy"), "Invalid", []string{"PreemptLowerPriority", "Never"}),
},
},
}
for name, tc := range failureCases {

View file

@ -245,6 +245,11 @@ func (in *PodGroupSpec) DeepCopyInto(out *PodGroupSpec) {
*out = new(int32)
**out = **in
}
if in.PreemptionPolicy != nil {
in, out := &in.PreemptionPolicy, &out.PreemptionPolicy
*out = new(core.PreemptionPolicy)
**out = **in
}
return
}
@ -314,6 +319,11 @@ func (in *PodGroupTemplate) DeepCopyInto(out *PodGroupTemplate) {
*out = new(int32)
**out = **in
}
if in.PreemptionPolicy != nil {
in, out := &in.PreemptionPolicy, &out.PreemptionPolicy
*out = new(core.PreemptionPolicy)
**out = **in
}
return
}

View file

@ -54257,6 +54257,15 @@ func schema_k8sio_api_scheduling_v1alpha2_PodGroupSpec(ref common.ReferenceCallb
Format: "int32",
},
},
"preemptionPolicy": {
SchemaProps: spec.SchemaProps{
Description: "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.\n\n\nPossible enum values:\n - `\"Never\"` means that pod never preempts other pods with lower priority.\n - `\"PreemptLowerPriority\"` means that pod can preempt other pods with lower priority.",
Default: "PreemptLowerPriority",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"Never", "PreemptLowerPriority"},
},
},
},
Required: []string{"schedulingPolicy"},
},
@ -54400,6 +54409,14 @@ func schema_k8sio_api_scheduling_v1alpha2_PodGroupTemplate(ref common.ReferenceC
Format: "int32",
},
},
"preemptionPolicy": {
SchemaProps: spec.SchemaProps{
Description: "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is available only when the WorkloadAwarePreemption feature gate is enabled.\n\n\nPossible enum values:\n - `\"Never\"` means that pod never preempts other pods with lower priority.\n - `\"PreemptLowerPriority\"` means that pod can preempt other pods with lower priority.",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"Never", "PreemptLowerPriority"},
},
},
},
Required: []string{"name", "schedulingPolicy"},
},

View file

@ -296,71 +296,71 @@ func TestSetDefaultsKubeletConfiguration(t *testing.T) {
CacheUnauthorizedTTL: metav1.Duration{Duration: 30 * time.Second},
},
},
RegistryPullQPS: ptr.To[int32](0),
RegistryBurst: 10,
EventRecordQPS: ptr.To[int32](0),
EventBurst: 100,
EnableDebuggingHandlers: ptr.To(false),
HealthzPort: ptr.To[int32](0),
HealthzBindAddress: "127.0.0.1",
OOMScoreAdj: ptr.To[int32](0),
ClusterDNS: []string{},
StreamingConnectionIdleTimeout: metav1.Duration{Duration: 4 * time.Hour},
NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Second},
NodeStatusReportFrequency: metav1.Duration{Duration: 5 * time.Minute},
NodeLeaseDurationSeconds: 40,
ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock",
ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute},
ImageGCHighThresholdPercent: ptr.To[int32](0),
ImageGCLowThresholdPercent: ptr.To[int32](0),
ImagePullCredentialsVerificationPolicy: "NeverVerifyPreloadedImages",
VolumeStatsAggPeriod: metav1.Duration{Duration: time.Minute},
CgroupsPerQOS: ptr.To(false),
CgroupDriver: "cgroupfs",
CPUManagerPolicy: "none",
CPUManagerPolicyOptions: map[string]string{},
CPUManagerReconcilePeriod: metav1.Duration{Duration: 10 * time.Second},
MemoryManagerPolicy: v1beta1.NoneMemoryManagerPolicy,
TopologyManagerPolicy: v1beta1.NoneTopologyManagerPolicy,
TopologyManagerScope: v1beta1.ContainerTopologyManagerScope,
QOSReserved: map[string]string{},
RuntimeRequestTimeout: metav1.Duration{Duration: 2 * time.Minute},
HairpinMode: v1beta1.PromiscuousBridge,
MaxPods: 110,
PodPidsLimit: ptr.To[int64](0),
ResolverConfig: ptr.To(""),
CPUCFSQuota: ptr.To(false),
CPUCFSQuotaPeriod: &zeroDuration,
NodeStatusMaxImages: ptr.To[int32](0),
MaxOpenFiles: 1000000,
ContentType: "application/vnd.kubernetes.protobuf",
KubeAPIQPS: ptr.To[int32](0),
KubeAPIBurst: 100,
SerializeImagePulls: ptr.To(false),
MaxParallelImagePulls: nil,
EvictionHard: map[string]string{},
EvictionSoft: map[string]string{},
EvictionSoftGracePeriod: map[string]string{},
EvictionPressureTransitionPeriod: metav1.Duration{Duration: 5 * time.Minute},
EvictionMinimumReclaim: map[string]string{},
MergeDefaultEvictionSettings: ptr.To(false),
EnableControllerAttachDetach: ptr.To(false),
MakeIPTablesUtilChains: ptr.To(false),
IPTablesMasqueradeBit: ptr.To[int32](0),
IPTablesDropBit: ptr.To[int32](0),
FeatureGates: map[string]bool{},
FailSwapOn: ptr.To(false),
MemorySwap: v1beta1.MemorySwapConfiguration{SwapBehavior: ""},
ContainerLogMaxSize: "10Mi",
ContainerLogMaxFiles: ptr.To[int32](0),
ContainerLogMaxWorkers: ptr.To[int32](1),
ContainerLogMonitorInterval: &metav1.Duration{Duration: 10 * time.Second},
RegistryPullQPS: ptr.To[int32](0),
RegistryBurst: 10,
EventRecordQPS: ptr.To[int32](0),
EventBurst: 100,
EnableDebuggingHandlers: ptr.To(false),
HealthzPort: ptr.To[int32](0),
HealthzBindAddress: "127.0.0.1",
OOMScoreAdj: ptr.To[int32](0),
ClusterDNS: []string{},
StreamingConnectionIdleTimeout: metav1.Duration{Duration: 4 * time.Hour},
NodeStatusUpdateFrequency: metav1.Duration{Duration: 10 * time.Second},
NodeStatusReportFrequency: metav1.Duration{Duration: 5 * time.Minute},
NodeLeaseDurationSeconds: 40,
ContainerRuntimeEndpoint: "unix:///run/containerd/containerd.sock",
ImageMinimumGCAge: metav1.Duration{Duration: 2 * time.Minute},
ImageGCHighThresholdPercent: ptr.To[int32](0),
ImageGCLowThresholdPercent: ptr.To[int32](0),
ImagePullCredentialsVerificationPolicy: "NeverVerifyPreloadedImages",
VolumeStatsAggPeriod: metav1.Duration{Duration: time.Minute},
CgroupsPerQOS: ptr.To(false),
CgroupDriver: "cgroupfs",
CPUManagerPolicy: "none",
CPUManagerPolicyOptions: map[string]string{},
CPUManagerReconcilePeriod: metav1.Duration{Duration: 10 * time.Second},
MemoryManagerPolicy: v1beta1.NoneMemoryManagerPolicy,
TopologyManagerPolicy: v1beta1.NoneTopologyManagerPolicy,
TopologyManagerScope: v1beta1.ContainerTopologyManagerScope,
QOSReserved: map[string]string{},
RuntimeRequestTimeout: metav1.Duration{Duration: 2 * time.Minute},
HairpinMode: v1beta1.PromiscuousBridge,
MaxPods: 110,
PodPidsLimit: ptr.To[int64](0),
ResolverConfig: ptr.To(""),
CPUCFSQuota: ptr.To(false),
CPUCFSQuotaPeriod: &zeroDuration,
NodeStatusMaxImages: ptr.To[int32](0),
MaxOpenFiles: 1000000,
ContentType: "application/vnd.kubernetes.protobuf",
KubeAPIQPS: ptr.To[int32](0),
KubeAPIBurst: 100,
SerializeImagePulls: ptr.To(false),
MaxParallelImagePulls: nil,
EvictionHard: map[string]string{},
EvictionSoft: map[string]string{},
EvictionSoftGracePeriod: map[string]string{},
EvictionPressureTransitionPeriod: metav1.Duration{Duration: 5 * time.Minute},
EvictionMinimumReclaim: map[string]string{},
MergeDefaultEvictionSettings: ptr.To(false),
EnableControllerAttachDetach: ptr.To(false),
MakeIPTablesUtilChains: ptr.To(false),
IPTablesMasqueradeBit: ptr.To[int32](0),
IPTablesDropBit: ptr.To[int32](0),
FeatureGates: map[string]bool{},
FailSwapOn: ptr.To(false),
MemorySwap: v1beta1.MemorySwapConfiguration{SwapBehavior: ""},
ContainerLogMaxSize: "10Mi",
ContainerLogMaxFiles: ptr.To[int32](0),
ContainerLogMaxWorkers: ptr.To[int32](1),
ContainerLogMonitorInterval: &metav1.Duration{Duration: 10 * time.Second},
ConfigMapAndSecretChangeDetectionStrategy: v1beta1.WatchChangeDetectionStrategy,
SystemReserved: map[string]string{},
KubeReserved: map[string]string{},
EnforceNodeAllocatable: []string{},
AllowedUnsafeSysctls: []string{},
VolumePluginDir: DefaultVolumePluginDir,
SystemReserved: map[string]string{},
KubeReserved: map[string]string{},
EnforceNodeAllocatable: []string{},
AllowedUnsafeSysctls: []string{},
VolumePluginDir: DefaultVolumePluginDir,
Logging: logsapi.LoggingConfiguration{
Format: "text",
FlushFrequency: logsapi.TimeOrMetaDuration{Duration: metav1.Duration{Duration: 5 * time.Second}, SerializeAsString: true},

View file

@ -179,6 +179,7 @@ func dropDisabledPodGroupSpecFields(podGroupSpec, oldPodGroupSpec *scheduling.Po
dropDisabledDisruptionModeField(podGroupSpec, oldPodGroupSpec)
dropDisabledPriorityClassNameField(podGroupSpec, oldPodGroupSpec)
dropDisabledPriorityField(podGroupSpec, oldPodGroupSpec)
dropDisabledPreemptionPolicyField(podGroupSpec, oldPodGroupSpec)
}
func dropDisabledPodGroupStatusFields(newPodGroup, oldPodGroup *scheduling.PodGroup) {
@ -244,6 +245,16 @@ func dropDisabledPriorityField(podGroupSpec, oldPodGroupSpec *scheduling.PodGrou
podGroupSpec.Priority = nil
}
// dropDisabledPreemptionPolicyField removes the PreemptionPolicy field unless it is
// already used in the old PodGroup spec.
func dropDisabledPreemptionPolicyField(podGroupSpec, oldPodGroupSpec *scheduling.PodGroupSpec) {
if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) || preemptionPolicyInUse(oldPodGroupSpec) {
// No need to drop anything.
return
}
podGroupSpec.PreemptionPolicy = nil
}
func schedulingConstraintsInUse(podGroupSpec *scheduling.PodGroupSpec) bool {
return podGroupSpec != nil && podGroupSpec.SchedulingConstraints != nil
}
@ -263,3 +274,7 @@ func priorityClassNameInUse(podGroupSpec *scheduling.PodGroupSpec) bool {
func priorityInUse(podGroupSpec *scheduling.PodGroupSpec) bool {
return podGroupSpec != nil && podGroupSpec.Priority != nil
}
func preemptionPolicyInUse(podGroupSpec *scheduling.PodGroupSpec) bool {
return podGroupSpec != nil && podGroupSpec.PreemptionPolicy != nil
}

View file

@ -27,6 +27,7 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/scheduling"
// Side-effect import: registers PodGroup with legacyscheme.Scheme so
// the ConvertToVersion calls below resolve the type.
@ -66,12 +67,27 @@ func podGroupWithSchedulingConstraints(keys ...string) *scheduling.PodGroup {
return pg
}
func podGroupWithDisruptionMode(mode scheduling.DisruptionMode) *scheduling.PodGroup {
func basePodGroupWithWAP() *scheduling.PodGroup {
pg := podGroup.DeepCopy()
disruptionModePod := scheduling.DisruptionModePod
preemptLowerPriority := core.PreemptLowerPriority
pg.Spec.DisruptionMode = &disruptionModePod
pg.Spec.PreemptionPolicy = &preemptLowerPriority
return pg
}
func podGroupWithDisruptionMode(mode scheduling.DisruptionMode) *scheduling.PodGroup {
pg := basePodGroupWithWAP()
pg.Spec.DisruptionMode = &mode
return pg
}
func podGroupWithPreemptionPolicy(policy core.PreemptionPolicy) *scheduling.PodGroup {
pg := basePodGroupWithWAP()
pg.Spec.PreemptionPolicy = &policy
return pg
}
var (
fieldImmutableError = "field is immutable"
minCountError = "must be greater than or equal to 1"
@ -82,6 +98,7 @@ var (
subdomainNameError = "lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters"
forbiddenError = "Forbidden"
supportedModesError = `supported values: "Pod", "PodGroup"`
supportedPoliciesError = `supported values: "PreemptLowerPriority", "Never"`
)
func TestStrategy(t *testing.T) {
@ -258,6 +275,25 @@ func TestStrategyCreate(t *testing.T) {
enableWorkloadAwarePreemption: true,
expectValidationError: maximumError,
},
"workload aware preemption disabled - drop preemptionPolicy": {
obj: podGroupWithPreemptionPolicy(core.PreemptNever),
expectObj: podGroup,
},
"workload aware preemption enabled - preserve preemptionPolicy (Never)": {
obj: podGroupWithPreemptionPolicy(core.PreemptNever),
expectObj: podGroupWithPreemptionPolicy(core.PreemptNever),
enableWorkloadAwarePreemption: true,
},
"workload aware preemption enabled - preserve preemptionPolicy (PreemptLowerPriority)": {
obj: podGroupWithPreemptionPolicy(core.PreemptLowerPriority),
expectObj: podGroupWithPreemptionPolicy(core.PreemptLowerPriority),
enableWorkloadAwarePreemption: true,
},
"workload aware preemption enabled - invalid preemptionPolicy": {
obj: podGroupWithPreemptionPolicy(core.PreemptionPolicy("Invalid")),
enableWorkloadAwarePreemption: true,
expectValidationError: supportedPoliciesError,
},
}
for name, tc := range testCases {
@ -448,6 +484,17 @@ func TestStrategyUpdate(t *testing.T) {
enableWorkloadAwarePreemption: true,
expectValidationError: fieldImmutableError,
},
"preemptionPolicy update, workload aware preemption disabled": {
oldObj: podGroupWithPreemptionPolicy(core.PreemptNever),
newObj: podGroupWithPreemptionPolicy(core.PreemptLowerPriority),
expectValidationError: forbiddenError,
},
"preemptionPolicy update, workload aware preemption enabled": {
oldObj: podGroupWithPreemptionPolicy(core.PreemptNever),
newObj: podGroupWithPreemptionPolicy(core.PreemptLowerPriority),
enableWorkloadAwarePreemption: true,
expectValidationError: fieldImmutableError,
},
}
for name, tc := range testCases {

View file

@ -130,6 +130,7 @@ func dropDisabledPodGroupTemplatesFields(templates, oldTemplates []scheduling.Po
dropDisabledDisruptionModeField(template, oldTemplate)
dropDisabledPriorityClassNameField(template, oldTemplate)
dropDisabledPriorityField(template, oldTemplate)
dropDisabledPreemptionPolicyField(template, oldTemplate)
}
}
@ -181,6 +182,16 @@ func dropDisabledPriorityField(template, oldTemplate *scheduling.PodGroupTemplat
template.Priority = nil
}
// dropDisabledPreemptionPolicyField removes the PreemptionPolicy field from a template unless it is
// already used in the old template.
func dropDisabledPreemptionPolicyField(template, oldTemplate *scheduling.PodGroupTemplate) {
if utilfeature.DefaultFeatureGate.Enabled(features.WorkloadAwarePreemption) || preemptionPolicyInUse(oldTemplate) {
// No need to drop anything.
return
}
template.PreemptionPolicy = nil
}
func schedulingConstraintsInUse(pgt *scheduling.PodGroupTemplate) bool {
return pgt != nil && pgt.SchedulingConstraints != nil
}
@ -200,3 +211,7 @@ func priorityClassNameInUse(pgt *scheduling.PodGroupTemplate) bool {
func priorityInUse(pgt *scheduling.PodGroupTemplate) bool {
return pgt != nil && pgt.Priority != nil
}
func preemptionPolicyInUse(pgt *scheduling.PodGroupTemplate) bool {
return pgt != nil && pgt.PreemptionPolicy != nil
}

View file

@ -27,6 +27,7 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/scheduling"
// Side-effect import: registers Workload with legacyscheme.Scheme so
// the ConvertToVersion calls below resolve the type.
@ -57,13 +58,16 @@ var (
podDisruptionMode = scheduling.DisruptionModePod
podGroupDisruptionMode = scheduling.DisruptionModePodGroup
invalidDisruptionMode = scheduling.DisruptionMode("Invalid")
preemptNever = core.PreemptNever
preemptLowerPriority = core.PreemptLowerPriority
fieldImmutableError = "field is immutable"
minCountError = "must be greater than or equal to 1"
tooManyItemsError = "must have at most 1 item"
requiredError = "Required value"
subdomainNameError = "lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters"
supportedModesError = `supported values: "Pod", "PodGroup"`
fieldImmutableError = "field is immutable"
minCountError = "must be greater than or equal to 1"
tooManyItemsError = "must have at most 1 item"
requiredError = "Required value"
subdomainNameError = "lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters"
supportedModesError = `supported values: "Pod", "PodGroup"`
supportedPoliciesError = `supported values: "PreemptLowerPriority", "Never"`
)
func TestWorkloadStrategy(t *testing.T) {
@ -233,6 +237,50 @@ func TestStrategyCreate(t *testing.T) {
enableWorkloadAwarePreemption: true,
expectValidationError: subdomainNameError,
},
"workload aware preemption disabled - drop preemptionPolicy": {
obj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptNever
return w
}(),
expectObj: workload,
},
"workload aware preemption enabled - preserve preemptionPolicy (Never)": {
obj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptNever
return w
}(),
enableWorkloadAwarePreemption: true,
expectObj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptNever
return w
}(),
},
"workload aware preemption enabled - preserve preemptionPolicy (PreemptLowerPriority)": {
obj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptLowerPriority
return w
}(),
enableWorkloadAwarePreemption: true,
expectObj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptLowerPriority
return w
}(),
},
"workload aware preemption enabled - invalid preemptionPolicy": {
obj: func() *scheduling.Workload {
w := workload.DeepCopy()
invalidPolicy := core.PreemptionPolicy("Invalid")
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &invalidPolicy
return w
}(),
enableWorkloadAwarePreemption: true,
expectValidationError: supportedPoliciesError,
},
}
for name, tc := range testCases {
@ -530,6 +578,33 @@ func TestStrategyUpdate(t *testing.T) {
enableWorkloadAwarePreemption: true,
expectValidationError: fieldImmutableError,
},
"preemptionPolicy update, workload aware preemption disabled": {
oldObj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptNever
return w
}(),
newObj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptLowerPriority
return w
}(),
expectValidationError: fieldImmutableError,
},
"preemptionPolicy update, workload aware preemption enabled": {
oldObj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptNever
return w
}(),
newObj: func() *scheduling.Workload {
w := workload.DeepCopy()
w.Spec.PodGroupTemplates[0].PreemptionPolicy = &preemptLowerPriority
return w
}(),
enableWorkloadAwarePreemption: true,
expectValidationError: fieldImmutableError,
},
}
for name, tc := range testCases {

View file

@ -232,12 +232,12 @@ func NewFit(_ context.Context, plArgs runtime.Object, h fwk.Handle, fts feature.
}
pl := &Fit{
ignoredResources: sets.New(args.IgnoredResources...),
ignoredResourceGroups: sets.New(args.IgnoredResourceGroups...),
enableInPlacePodVerticalScaling: fts.EnableInPlacePodVerticalScaling,
handle: h,
enablePodLevelResources: fts.EnablePodLevelResources,
enableDRAExtendedResource: fts.EnableDRAExtendedResource,
ignoredResources: sets.New(args.IgnoredResources...),
ignoredResourceGroups: sets.New(args.IgnoredResourceGroups...),
enableInPlacePodVerticalScaling: fts.EnableInPlacePodVerticalScaling,
handle: h,
enablePodLevelResources: fts.EnablePodLevelResources,
enableDRAExtendedResource: fts.EnableDRAExtendedResource,
enableInPlacePodLevelResourcesVerticalScaling: fts.EnableInPlacePodLevelResourcesVerticalScaling,
resourceAllocationScorer: scorer,
}

View file

@ -205,7 +205,7 @@ func TestPodGroupEvaluator_SelectVictimsOnDomain(t *testing.T) {
expectedStatus: fwk.NewStatus(fwk.Success),
},
{
name: "PodGroup with PreemptNever does not perform preemption",
name: "PodGroup with PreemptNever in a single pod is ignored and performs preemption",
nodeNames: []string{"node1", "node2", "node3"},
initPods: []*v1.Pod{
st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(),
@ -216,6 +216,28 @@ func TestPodGroupEvaluator_SelectVictimsOnDomain(t *testing.T) {
st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(),
[]*v1.Pod{
st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).PreemptionPolicy(v1.PreemptNever).Obj(),
},
),
blockingRules: []blockingRule{
{nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")},
{nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")},
{nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")},
},
expectedPods: []string{"p1"},
expectedStatus: fwk.NewStatus(fwk.Success),
},
{
name: "PodGroup with PreemptNever does not perform preemption",
nodeNames: []string{"node1", "node2", "node3"},
initPods: []*v1.Pod{
st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(),
st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(),
st.MakePod().Name("p3").UID("v3").Node("node3").Priority(lowPriority).PodGroupName("pg2").Obj(),
},
preemptor: newPodGroupPreemptor(
st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).PreemptionPolicy(v1.PreemptNever).Obj(),
[]*v1.Pod{
st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).Obj(),
st.MakePod().Name("p-2").UID("p-2").Priority(highPriority).Obj(),
},
),
@ -225,7 +247,47 @@ func TestPodGroupEvaluator_SelectVictimsOnDomain(t *testing.T) {
{nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")},
},
expectedPods: []string{},
expectedStatus: fwk.NewStatus(fwk.Unschedulable),
expectedStatus: fwk.NewStatus(fwk.Unschedulable, "not eligible due to preemptionPolicy=Never."),
},
{
name: "PodGroup with default preemption policy performs preemption",
nodeNames: []string{"node1", "node2", "node3"},
initPods: []*v1.Pod{
st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(),
st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(),
st.MakePod().Name("p3").UID("v3").Node("node3").Priority(lowPriority).PodGroupName("pg2").Obj(),
},
preemptor: newPodGroupPreemptor(
st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(), // PreemptionPolicy is nil (default)
[]*v1.Pod{st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).Obj()},
),
blockingRules: []blockingRule{
{nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")},
{nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")},
{nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")},
},
expectedPods: []string{"p1"},
expectedStatus: fwk.NewStatus(fwk.Success),
},
{
name: "PodGroup with PreemptLowerPriority performs preemption",
nodeNames: []string{"node1", "node2", "node3"},
initPods: []*v1.Pod{
st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(),
st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(),
st.MakePod().Name("p3").UID("v3").Node("node3").Priority(lowPriority).PodGroupName("pg2").Obj(),
},
preemptor: newPodGroupPreemptor(
st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).PreemptionPolicy(v1.PreemptLowerPriority).Obj(),
[]*v1.Pod{st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).Obj()},
),
blockingRules: []blockingRule{
{nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")},
{nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")},
{nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")},
},
expectedPods: []string{"p1"},
expectedStatus: fwk.NewStatus(fwk.Success),
},
{
name: "Preemptor group is not eligible if any member has nominated node with terminating pods",
@ -432,7 +494,7 @@ func TestPodGroupEvaluator_SelectVictimsOnDomain(t *testing.T) {
expectedStatus: fwk.NewStatus(fwk.Success),
},
{
name: "PodGroup with PreemptNever does not perform preemption",
name: "PodGroup with PreemptNever in a single pod is ignored and performs preemption with PodGroup victims",
nodeNames: []string{"node1", "node2", "node3"},
initPods: []*v1.Pod{
st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(),
@ -447,6 +509,32 @@ func TestPodGroupEvaluator_SelectVictimsOnDomain(t *testing.T) {
st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).Obj(),
[]*v1.Pod{
st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).PreemptionPolicy(v1.PreemptNever).Obj(),
},
),
blockingRules: []blockingRule{
{nodeName: "node1", capacity: 1, blockingVictims: sets.New("p1")},
{nodeName: "node2", capacity: 1, blockingVictims: sets.New("p2")},
{nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")},
},
expectedPods: []string{"p1"},
expectedStatus: fwk.NewStatus(fwk.Success),
},
{
name: "PodGroup with PreemptNever does not perform preemption",
nodeNames: []string{"node1", "node2", "node3"},
initPods: []*v1.Pod{
st.MakePod().Name("p1").UID("v1").Node("node1").Priority(lowPriority).Obj(),
st.MakePod().Name("p2").UID("v2").Node("node2").Priority(lowPriority).PodGroupName("pg1").Obj(),
st.MakePod().Name("p3").UID("v3").Node("node3").Priority(lowPriority).PodGroupName("pg2").Obj(),
},
initPodGroups: []*schedulingapi.PodGroup{
st.MakePodGroup().Name("pg1").UID("pg1").DisruptionMode(schedulingapi.DisruptionModePod).Priority(lowPriority).Obj(),
st.MakePodGroup().Name("pg2").UID("pg2").DisruptionMode(schedulingapi.DisruptionModePod).Priority(lowPriority).Obj(),
},
preemptor: newPodGroupPreemptor(
st.MakePodGroup().Name("preemptor-pg").Priority(highPriority).PreemptionPolicy(v1.PreemptNever).Obj(),
[]*v1.Pod{
st.MakePod().Name("p-1").UID("p-1").Priority(highPriority).Obj(),
st.MakePod().Name("p-2").UID("p-2").Priority(highPriority).Obj(),
},
),
@ -456,7 +544,7 @@ func TestPodGroupEvaluator_SelectVictimsOnDomain(t *testing.T) {
{nodeName: "node3", capacity: 1, blockingVictims: sets.New("p3")},
},
expectedPods: []string{},
expectedStatus: fwk.NewStatus(fwk.Unschedulable),
expectedStatus: fwk.NewStatus(fwk.Unschedulable, "not eligible due to preemptionPolicy=Never."),
},
{
name: "PDB: Prefer lower priority pod for preemption, when preemption without pdb violation is not possible",

View file

@ -42,10 +42,8 @@ type podGroupPreemptor struct {
func newPodGroupPreemptor(pg *schedulingapi.PodGroup, pods []*v1.Pod) *podGroupPreemptor {
preemptionPolicy := v1.PreemptLowerPriority
for _, pod := range pods {
if p := pod.Spec.PreemptionPolicy; p != nil && *p == v1.PreemptNever {
preemptionPolicy = *p
}
if pg != nil && pg.Spec.PreemptionPolicy != nil {
preemptionPolicy = *pg.Spec.PreemptionPolicy
}
return &podGroupPreemptor{
priority: util.PodGroupPriority(pg),

View file

@ -1666,6 +1666,12 @@ func (wrapper *PodGroupWrapper) Priority(priority int32) *PodGroupWrapper {
return wrapper
}
// PreemptionPolicy sets the preemption policy of the inner PodGroup.
func (wrapper *PodGroupWrapper) PreemptionPolicy(policy v1.PreemptionPolicy) *PodGroupWrapper {
wrapper.PodGroup.Spec.PreemptionPolicy = &policy
return wrapper
}
// WorkloadWrapper wraps a Workload inside.
type WorkloadWrapper struct{ schedulingapi.Workload }
@ -1730,3 +1736,9 @@ func (wrapper *PodGroupTemplateWrapper) BasicPolicy() *PodGroupTemplateWrapper {
wrapper.SchedulingPolicy.Basic = &schedulingapi.BasicSchedulingPolicy{}
return wrapper
}
// PreemptionPolicy sets the preemption policy of the inner PodGroupTemplate.
func (wrapper *PodGroupTemplateWrapper) PreemptionPolicy(policy v1.PreemptionPolicy) *PodGroupTemplateWrapper {
wrapper.PodGroupTemplate.PreemptionPolicy = &policy
return wrapper
}

View file

@ -24,6 +24,7 @@ import (
io "io"
k8s_io_api_core_v1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
math_bits "math/bits"
@ -397,6 +398,13 @@ func (m *PodGroupSpec) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.PreemptionPolicy != nil {
i -= len(*m.PreemptionPolicy)
copy(dAtA[i:], *m.PreemptionPolicy)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PreemptionPolicy)))
i--
dAtA[i] = 0x42
}
if m.Priority != nil {
i = encodeVarintGenerated(dAtA, i, uint64(*m.Priority))
i--
@ -536,6 +544,13 @@ func (m *PodGroupTemplate) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if m.PreemptionPolicy != nil {
i -= len(*m.PreemptionPolicy)
copy(dAtA[i:], *m.PreemptionPolicy)
i = encodeVarintGenerated(dAtA, i, uint64(len(*m.PreemptionPolicy)))
i--
dAtA[i] = 0x42
}
if m.Priority != nil {
i = encodeVarintGenerated(dAtA, i, uint64(*m.Priority))
i--
@ -1029,6 +1044,10 @@ func (m *PodGroupSpec) Size() (n int) {
if m.Priority != nil {
n += 1 + sovGenerated(uint64(*m.Priority))
}
if m.PreemptionPolicy != nil {
l = len(*m.PreemptionPolicy)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@ -1082,6 +1101,10 @@ func (m *PodGroupTemplate) Size() (n int) {
if m.Priority != nil {
n += 1 + sovGenerated(uint64(*m.Priority))
}
if m.PreemptionPolicy != nil {
l = len(*m.PreemptionPolicy)
n += 1 + l + sovGenerated(uint64(l))
}
return n
}
@ -1305,6 +1328,7 @@ func (this *PodGroupSpec) String() string {
`DisruptionMode:` + valueToStringGenerated(this.DisruptionMode) + `,`,
`PriorityClassName:` + fmt.Sprintf("%v", this.PriorityClassName) + `,`,
`Priority:` + valueToStringGenerated(this.Priority) + `,`,
`PreemptionPolicy:` + valueToStringGenerated(this.PreemptionPolicy) + `,`,
`}`,
}, "")
return s
@ -1347,6 +1371,7 @@ func (this *PodGroupTemplate) String() string {
`DisruptionMode:` + valueToStringGenerated(this.DisruptionMode) + `,`,
`PriorityClassName:` + fmt.Sprintf("%v", this.PriorityClassName) + `,`,
`Priority:` + valueToStringGenerated(this.Priority) + `,`,
`PreemptionPolicy:` + valueToStringGenerated(this.PreemptionPolicy) + `,`,
`}`,
}, "")
return s
@ -2552,6 +2577,39 @@ func (m *PodGroupSpec) Unmarshal(dAtA []byte) error {
}
}
m.Priority = &v
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PreemptionPolicy", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := k8s_io_api_core_v1.PreemptionPolicy(dAtA[iNdEx:postIndex])
m.PreemptionPolicy = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])
@ -2940,6 +2998,39 @@ func (m *PodGroupTemplate) Unmarshal(dAtA []byte) error {
}
}
m.Priority = &v
case 8:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field PreemptionPolicy", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthGenerated
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
s := k8s_io_api_core_v1.PreemptionPolicy(dAtA[iNdEx:postIndex])
m.PreemptionPolicy = &s
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(dAtA[iNdEx:])

View file

@ -21,6 +21,7 @@ syntax = "proto2";
package k8s.io.api.scheduling.v1alpha2;
import "k8s.io/api/core/v1/generated.proto";
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
@ -289,6 +290,21 @@ message PodGroupSpec {
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:maximum=1000000000 # HighestUserDefinablePriority
optional int32 priority = 7;
// PreemptionPolicy is the Policy for preempting pods with lower priority.
// One of Never, PreemptLowerPriority.
// Defaults to PreemptLowerPriority if unset.
// This field is immutable.
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
//
// +featureGate=WorkloadAwarePreemption
// +optional
// +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable
// +default="PreemptLowerPriority"
optional string preemptionPolicy = 8;
}
// PodGroupStatus represents information about the status of a pod group.
@ -417,6 +433,18 @@ message PodGroupTemplate {
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:maximum=1000000000 # HighestUserDefinablePriority
optional int32 priority = 7;
// PreemptionPolicy is the Policy for preempting pods with lower priority.
// One of Never, PreemptLowerPriority.
// Defaults to PreemptLowerPriority if unset.
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
//
// +featureGate=WorkloadAwarePreemption
// +optional
// +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional
optional string preemptionPolicy = 8;
}
// PodGroupTemplateReference references a PodGroup template defined in some object (e.g. Workload).

View file

@ -17,6 +17,7 @@ limitations under the License.
package v1alpha2
import (
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -201,6 +202,18 @@ type PodGroupTemplate struct {
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:maximum=1000000000 # HighestUserDefinablePriority
Priority *int32 `json:"priority,omitempty" protobuf:"varint,7,opt,name=priority"`
// PreemptionPolicy is the Policy for preempting pods with lower priority.
// One of Never, PreemptLowerPriority.
// Defaults to PreemptLowerPriority if unset.
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
//
// +featureGate=WorkloadAwarePreemption
// +optional
// +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional
PreemptionPolicy *apiv1.PreemptionPolicy `json:"preemptionPolicy,omitempty" protobuf:"bytes,8,opt,name=preemptionPolicy"`
}
// PodGroupSchedulingPolicy defines the scheduling configuration for a PodGroup.
@ -455,6 +468,21 @@ type PodGroupSpec struct {
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:maximum=1000000000 # HighestUserDefinablePriority
Priority *int32 `json:"priority,omitempty" protobuf:"varint,7,opt,name=priority"`
// PreemptionPolicy is the Policy for preempting pods with lower priority.
// One of Never, PreemptLowerPriority.
// Defaults to PreemptLowerPriority if unset.
// This field is immutable.
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
//
// +featureGate=WorkloadAwarePreemption
// +optional
// +k8s:ifDisabled("WorkloadAwarePreemption")=+k8s:forbidden
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:optional
// +k8s:ifEnabled("WorkloadAwarePreemption")=+k8s:immutable
// +default="PreemptLowerPriority"
PreemptionPolicy *apiv1.PreemptionPolicy `json:"preemptionPolicy,omitempty" protobuf:"bytes,8,opt,name=preemptionPolicy"`
}
// PodGroupStatus represents information about the status of a pod group.

View file

@ -114,6 +114,7 @@ var map_PodGroupSpec = map[string]string{
"disruptionMode": "DisruptionMode defines the mode in which a given PodGroup can be disrupted. Controllers are expected to fill this field by copying it from a PodGroupTemplate. One of Pod, PodGroup. Defaults to Pod if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"priorityClassName": "PriorityClassName defines the priority that should be considered when scheduling this pod group. Controllers are expected to fill this field by copying it from a PodGroupTemplate. Otherwise, it is validated and resolved similarly to the PriorityClassName on PodGroupTemplate (i.e. if no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, the pod group's priority will be zero). This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"priority": "Priority is the value of priority of this pod group. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"preemptionPolicy": "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is immutable. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
}
func (PodGroupSpec) SwaggerDoc() map[string]string {
@ -139,6 +140,7 @@ var map_PodGroupTemplate = map[string]string{
"disruptionMode": "DisruptionMode defines the mode in which a given PodGroup can be disrupted. One of Pod, PodGroup. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"priorityClassName": "PriorityClassName indicates the priority that should be considered when scheduling a pod group created from this template. If no priority class is specified, admission control can set this to the global default priority class if it exists. Otherwise, pod groups created from this template will have the priority set to zero. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"priority": "Priority is the value of priority of pod groups created from this template. Various system components use this field to find the priority of the pod group. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
"preemptionPolicy": "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset. This field is available only when the WorkloadAwarePreemption feature gate is enabled.",
}
func (PodGroupTemplate) SwaggerDoc() map[string]string {

View file

@ -22,7 +22,8 @@ limitations under the License.
package v1alpha2
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@ -244,6 +245,11 @@ func (in *PodGroupSpec) DeepCopyInto(out *PodGroupSpec) {
*out = new(int32)
**out = **in
}
if in.PreemptionPolicy != nil {
in, out := &in.PreemptionPolicy, &out.PreemptionPolicy
*out = new(v1.PreemptionPolicy)
**out = **in
}
return
}
@ -262,7 +268,7 @@ func (in *PodGroupStatus) DeepCopyInto(out *PodGroupStatus) {
*out = *in
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in))
*out = make([]metav1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@ -313,6 +319,11 @@ func (in *PodGroupTemplate) DeepCopyInto(out *PodGroupTemplate) {
*out = new(int32)
**out = **in
}
if in.PreemptionPolicy != nil {
in, out := &in.PreemptionPolicy, &out.PreemptionPolicy
*out = new(v1.PreemptionPolicy)
**out = **in
}
return
}

View file

@ -72,7 +72,8 @@
],
"disruptionMode": "disruptionModeValue",
"priorityClassName": "priorityClassNameValue",
"priority": 7
"priority": 7,
"preemptionPolicy": "preemptionPolicyValue"
},
"status": {
"conditions": [

View file

@ -38,6 +38,7 @@ spec:
workload:
podGroupTemplateName: podGroupTemplateNameValue
workloadName: workloadNameValue
preemptionPolicy: preemptionPolicyValue
priority: 7
priorityClassName: priorityClassNameValue
resourceClaims:

View file

@ -74,7 +74,8 @@
],
"disruptionMode": "disruptionModeValue",
"priorityClassName": "priorityClassNameValue",
"priority": 7
"priority": 7,
"preemptionPolicy": "preemptionPolicyValue"
}
]
}

View file

@ -40,6 +40,7 @@ spec:
podGroupTemplates:
- disruptionMode: disruptionModeValue
name: nameValue
preemptionPolicy: preemptionPolicyValue
priority: 7
priorityClassName: priorityClassNameValue
resourceClaims:

View file

@ -14808,6 +14808,10 @@ var schemaYAML = typed.YAMLObject(`types:
- name: podGroupTemplateRef
type:
namedType: io.k8s.api.scheduling.v1alpha2.PodGroupTemplateReference
- name: preemptionPolicy
type:
scalar: string
default: PreemptLowerPriority
- name: priority
type:
scalar: numeric
@ -14858,6 +14862,9 @@ var schemaYAML = typed.YAMLObject(`types:
type:
scalar: string
default: ""
- name: preemptionPolicy
type:
scalar: string
- name: priority
type:
scalar: numeric

View file

@ -19,6 +19,7 @@ limitations under the License.
package v1alpha2
import (
v1 "k8s.io/api/core/v1"
schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2"
)
@ -75,6 +76,13 @@ type PodGroupSpecApplyConfiguration struct {
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
Priority *int32 `json:"priority,omitempty"`
// PreemptionPolicy is the Policy for preempting pods with lower priority.
// One of Never, PreemptLowerPriority.
// Defaults to PreemptLowerPriority if unset.
// This field is immutable.
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
PreemptionPolicy *v1.PreemptionPolicy `json:"preemptionPolicy,omitempty"`
}
// PodGroupSpecApplyConfiguration constructs a declarative configuration of the PodGroupSpec type for use with
@ -143,3 +151,11 @@ func (b *PodGroupSpecApplyConfiguration) WithPriority(value int32) *PodGroupSpec
b.Priority = &value
return b
}
// WithPreemptionPolicy sets the PreemptionPolicy field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the PreemptionPolicy field is set to the value of the last call.
func (b *PodGroupSpecApplyConfiguration) WithPreemptionPolicy(value v1.PreemptionPolicy) *PodGroupSpecApplyConfiguration {
b.PreemptionPolicy = &value
return b
}

View file

@ -19,6 +19,7 @@ limitations under the License.
package v1alpha2
import (
v1 "k8s.io/api/core/v1"
schedulingv1alpha2 "k8s.io/api/scheduling/v1alpha2"
)
@ -66,6 +67,12 @@ type PodGroupTemplateApplyConfiguration struct {
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
Priority *int32 `json:"priority,omitempty"`
// PreemptionPolicy is the Policy for preempting pods with lower priority.
// One of Never, PreemptLowerPriority.
// Defaults to PreemptLowerPriority if unset.
// This field is available only when the WorkloadAwarePreemption feature gate
// is enabled.
PreemptionPolicy *v1.PreemptionPolicy `json:"preemptionPolicy,omitempty"`
}
// PodGroupTemplateApplyConfiguration constructs a declarative configuration of the PodGroupTemplate type for use with
@ -134,3 +141,11 @@ func (b *PodGroupTemplateApplyConfiguration) WithPriority(value int32) *PodGroup
b.Priority = &value
return b
}
// WithPreemptionPolicy sets the PreemptionPolicy field in the declarative configuration to the given value
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
// If called multiple times, the PreemptionPolicy field is set to the value of the last call.
func (b *PodGroupTemplateApplyConfiguration) WithPreemptionPolicy(value v1.PreemptionPolicy) *PodGroupTemplateApplyConfiguration {
b.PreemptionPolicy = &value
return b
}

View file

@ -28,6 +28,7 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/scheduling"
"k8s.io/kubernetes/pkg/features"
registry "k8s.io/kubernetes/pkg/registry/scheduling/podgroup"
@ -172,7 +173,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")},
},
"pod disruption mode, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod)),
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPreemptionPolicy(core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
},
"pod disruption mode, workload aware preemption disabled": {
@ -180,7 +181,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "disruptionMode"), "")},
},
"pod group disruption mode, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePodGroup)),
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePodGroup), setPreemptionPolicy(core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
},
"pod group disruption mode, workload aware preemption disabled": {
@ -188,7 +189,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "disruptionMode"), "")},
},
"invalid disruption mode, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode("Invalid")),
input: mkValidPodGroup(setDisruptionMode("Invalid"), setPreemptionPolicy(core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{field.NotSupported(field.NewPath("spec", "disruptionMode"), "Invalid", sets.List(allowedDisruptionModes))},
},
@ -197,12 +198,12 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "disruptionMode"), "")},
},
"valid pod group without disruption mode, workload aware preemption enabled": {
input: mkValidPodGroup(),
input: mkValidPodGroup(setPreemptionPolicy(core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "disruptionMode"), "")},
},
"valid priority class name, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriorityClassName("high-priority")),
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPreemptionPolicy(core.PreemptLowerPriority), setPriorityClassName("high-priority")),
enableWorkloadAwarePreemption: true,
},
"valid priority class name, workload aware preemption disabled": {
@ -210,7 +211,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priorityClassName"), "")},
},
"invalid priority class name, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriorityClassName("high/priority")),
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPreemptionPolicy(core.PreemptLowerPriority), setPriorityClassName("high/priority")),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("spec", "priorityClassName"), nil, "").WithOrigin("format=k8s-long-name"),
@ -221,7 +222,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priorityClassName"), "")},
},
"valid priority, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriority(1000)),
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPreemptionPolicy(core.PreemptLowerPriority), setPriority(1000)),
enableWorkloadAwarePreemption: true,
},
"valid priority, workload aware preemption disabled": {
@ -229,7 +230,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priority"), "")},
},
"valid negative priority, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriority(-2147483648)),
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPreemptionPolicy(core.PreemptLowerPriority), setPriority(-2147483648)),
enableWorkloadAwarePreemption: true,
},
"valid negative priority, workload aware preemption disabled": {
@ -237,7 +238,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priority"), "")},
},
"too high priority, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPriority(scheduling.HighestUserDefinablePriority+1)),
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPreemptionPolicy(core.PreemptLowerPriority), setPriority(scheduling.HighestUserDefinablePriority+1)),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("spec", "priority"), nil, "").WithOrigin("maximum"),
@ -247,6 +248,19 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
input: mkValidPodGroup(setPriority(scheduling.HighestUserDefinablePriority + 1)),
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priority"), "")},
},
"preemption policy, workload aware preemption disabled": {
input: mkValidPodGroup(setPreemptionPolicy(core.PreemptNever)),
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "preemptionPolicy"), "")},
},
"valid preemption policy (Never), workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod), setPreemptionPolicy(core.PreemptNever)),
enableWorkloadAwarePreemption: true,
},
"valid pod group without preemption policy, workload aware preemption enabled": {
input: mkValidPodGroup(setDisruptionMode(scheduling.DisruptionModePod)),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{field.Required(field.NewPath("spec", "preemptionPolicy"), "")},
},
"ok resourceClaimName reference": {
input: mkValidPodGroup(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "claim", ResourceClaimName: new("resource-claim")})),
},
@ -559,6 +573,17 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
updateObj: mkValidPodGroup(setResourceVersion("1"), setPriority(2000)),
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "priority"), "")},
},
"invalid update of preemption policy, workload aware preemption enabled": {
oldObj: mkValidPodGroup(setResourceVersion("1"), setPreemptionPolicy(core.PreemptNever)),
updateObj: mkValidPodGroup(setResourceVersion("1"), setPreemptionPolicy(core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "preemptionPolicy"), nil, "").WithOrigin("immutable")},
},
"invalid update of preemption policy, workload aware preemption disabled": {
oldObj: mkValidPodGroup(setResourceVersion("1"), setPreemptionPolicy(core.PreemptNever)),
updateObj: mkValidPodGroup(setResourceVersion("1"), setPreemptionPolicy(core.PreemptLowerPriority)),
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "preemptionPolicy"), "")},
},
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
@ -869,3 +894,9 @@ func setPriority(priority int32) func(obj *scheduling.PodGroup) {
obj.Spec.Priority = new(priority)
}
}
func setPreemptionPolicy(policy core.PreemptionPolicy) func(obj *scheduling.PodGroup) {
return func(obj *scheduling.PodGroup) {
obj.Spec.PreemptionPolicy = &policy
}
}

View file

@ -48,6 +48,11 @@ func init() {
{ErrorType: "FieldValueInvalid", Origin: "format=k8s-long-name"},
{ErrorType: "FieldValueRequired"},
},
"spec.preemptionPolicy": {
{ErrorType: "FieldValueForbidden"},
{ErrorType: "FieldValueInvalid", Origin: "immutable"},
{ErrorType: "FieldValueRequired"},
},
"spec.priority": {
{ErrorType: "FieldValueForbidden"},
{ErrorType: "FieldValueInvalid", Origin: "immutable"},

View file

@ -29,6 +29,7 @@ import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
apitesting "k8s.io/kubernetes/pkg/api/testing"
"k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/scheduling"
"k8s.io/kubernetes/pkg/features"
registry "k8s.io/kubernetes/pkg/registry/scheduling/workload"
@ -203,7 +204,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("schedulingConstraints", "topology").Index(0).Child("key"), nil, "").WithOrigin("format=k8s-label-key")},
},
"pod disruption mode, workload aware preemption enabled": {
input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode)),
input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode), setPreemptionPolicy(0, core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
},
"pod disruption mode, workload aware preemption disabled": {
@ -211,7 +212,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("disruptionMode"), "")},
},
"pod group disruption mode, workload aware preemption enabled": {
input: mkValidWorkload(setDisruptionMode(0, podGroupDisruptionMode)),
input: mkValidWorkload(setDisruptionMode(0, podGroupDisruptionMode), setPreemptionPolicy(0, core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
},
"pod group disruption mode, workload aware preemption disabled": {
@ -219,7 +220,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("disruptionMode"), "")},
},
"invalid disruption mode, workload aware preemption enabled": {
input: mkValidWorkload(setDisruptionMode(0, invalidDisruptionMode)),
input: mkValidWorkload(setDisruptionMode(0, invalidDisruptionMode), setPreemptionPolicy(0, core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{field.NotSupported(field.NewPath("spec", "podGroupTemplates").Index(0).Child("disruptionMode"), invalidDisruptionMode, sets.List(allowedDisruptionModes))},
},
@ -228,7 +229,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("disruptionMode"), "")},
},
"valid priorityClassName, workload aware preemption enabled": {
input: mkValidWorkload(setPriorityClassName(0, "high-priority")),
input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode), setPreemptionPolicy(0, core.PreemptLowerPriority), setPriorityClassName(0, "high-priority")),
enableWorkloadAwarePreemption: true,
},
"valid priorityClassName, workload aware preemption disabled": {
@ -236,7 +237,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priorityClassName"), "")},
},
"invalid priorityClassName, workload aware preemption enabled": {
input: mkValidWorkload(setPriorityClassName(0, "high/priority")),
input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode), setPreemptionPolicy(0, core.PreemptLowerPriority), setPriorityClassName(0, "high/priority")),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priorityClassName"), nil, "").WithOrigin("format=k8s-long-name"),
@ -248,7 +249,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
},
"valid priority, workload aware preemption enabled": {
input: mkValidWorkload(setPriority(0, 1000)),
input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode), setPreemptionPolicy(0, core.PreemptLowerPriority), setPriority(0, 1000)),
enableWorkloadAwarePreemption: true,
},
"valid priority, workload aware preemption disabled": {
@ -256,7 +257,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priority"), "")},
},
"valid negative priority, workload aware preemption enabled": {
input: mkValidWorkload(setPriority(0, -2147483648)),
input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode), setPreemptionPolicy(0, core.PreemptLowerPriority), setPriority(0, -2147483648)),
enableWorkloadAwarePreemption: true,
},
"valid negative priority, workload aware preemption disabled": {
@ -264,7 +265,7 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priority"), "")},
},
"too high priority, workload aware preemption enabled": {
input: mkValidWorkload(setPriority(0, scheduling.HighestUserDefinablePriority+1)),
input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode), setPreemptionPolicy(0, core.PreemptLowerPriority), setPriority(0, scheduling.HighestUserDefinablePriority+1)),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{
field.Invalid(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priority"), nil, "").WithOrigin("maximum"),
@ -274,6 +275,14 @@ func testDeclarativeValidate(t *testing.T, apiVersion string) {
input: mkValidWorkload(setPriority(0, scheduling.HighestUserDefinablePriority+1)),
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("priority"), "")},
},
"preemption policy, workload aware preemption disabled": {
input: mkValidWorkload(setPreemptionPolicy(0, core.PreemptNever)),
expectedErrs: field.ErrorList{field.Forbidden(field.NewPath("spec", "podGroupTemplates").Index(0).Child("preemptionPolicy"), "")},
},
"valid preemption policy (Never), workload aware preemption enabled": {
input: mkValidWorkload(setDisruptionMode(0, podDisruptionMode), setPreemptionPolicy(0, core.PreemptNever)),
enableWorkloadAwarePreemption: true,
},
"ok resourceClaimName reference": {
input: mkValidWorkload(addResourceClaims(scheduling.PodGroupResourceClaim{Name: "claim", ResourceClaimName: new("resource-claim")})),
},
@ -602,6 +611,17 @@ func testDeclarativeValidateUpdate(t *testing.T, apiVersion string) {
updateObj: mkValidWorkload(setResourceVersion("1"), setPriority(0, 2000)),
expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")},
},
"invalid update of preemption policy, workload aware preemption enabled": {
oldObj: mkValidWorkload(setResourceVersion("1"), setPreemptionPolicy(0, core.PreemptNever)),
updateObj: mkValidWorkload(setResourceVersion("1"), setPreemptionPolicy(0, core.PreemptLowerPriority)),
enableWorkloadAwarePreemption: true,
expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")},
},
"invalid update of preemption policy, workload aware preemption disabled": {
oldObj: mkValidWorkload(setResourceVersion("1"), setPreemptionPolicy(0, core.PreemptNever)),
updateObj: mkValidWorkload(setResourceVersion("1"), setPreemptionPolicy(0, core.PreemptLowerPriority)),
expectedErrs: field.ErrorList{field.Invalid(field.NewPath("spec", "podGroupTemplates"), nil, "").WithOrigin("immutable")},
},
}
for k, tc := range testCases {
t.Run(k, func(t *testing.T) {
@ -782,3 +802,9 @@ func setPriority(pgIdx int, priority int32) func(obj *scheduling.Workload) {
obj.Spec.PodGroupTemplates[pgIdx].Priority = new(priority)
}
}
func setPreemptionPolicy(pgIdx int, policy core.PreemptionPolicy) func(obj *scheduling.Workload) {
return func(obj *scheduling.Workload) {
obj.Spec.PodGroupTemplates[pgIdx].PreemptionPolicy = &policy
}
}

View file

@ -60,6 +60,9 @@ func init() {
{ErrorType: "FieldValueInvalid", Origin: "format=k8s-short-name"},
{ErrorType: "FieldValueRequired"},
},
"spec.podGroupTemplates[*].preemptionPolicy": {
{ErrorType: "FieldValueForbidden"},
},
"spec.podGroupTemplates[*].priority": {
{ErrorType: "FieldValueForbidden"},
{ErrorType: "FieldValueInvalid", Origin: "maximum"},

View file

@ -698,6 +698,29 @@ func TestPodGroupPreemption(t *testing.T) {
expectedPreempted: []string{"vb"},
expectedPodsPreemptedByWAP: 1,
},
{
name: "PodGroup with PreemptNever preemption policy does not perform preemption",
nodes: []*v1.Node{
st.MakeNode().Name("node1").Capacity(map[v1.ResourceName]string{v1.ResourceCPU: "3", v1.ResourceMemory: "4Gi", v1.ResourcePods: "32"}).Obj(),
},
podGroups: []*schedulingapi.PodGroup{
st.MakePodGroup().Name("pg1").Namespace("default").Priority(100).MinCount(3).PreemptionPolicy(v1.PreemptNever).Obj(),
},
initialPods: []*v1.Pod{
st.MakePod().Name("low-1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Container("image").ZeroTerminationGracePeriod().Priority(10).Obj(),
st.MakePod().Name("low-2").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Container("image").ZeroTerminationGracePeriod().Priority(10).Obj(),
st.MakePod().Name("low-3").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Container("image").ZeroTerminationGracePeriod().Priority(10).Obj(),
},
preemptorPods: []*v1.Pod{
st.MakePod().Name("high-1").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Container("image").PodGroupName("pg1").ZeroTerminationGracePeriod().Priority(100).Obj(),
st.MakePod().Name("high-2").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Container("image").PodGroupName("pg1").ZeroTerminationGracePeriod().Priority(100).Obj(),
st.MakePod().Name("high-3").Req(map[v1.ResourceName]string{v1.ResourceCPU: "1"}).Container("image").PodGroupName("pg1").ZeroTerminationGracePeriod().Priority(100).Obj(),
},
expectedScheduled: []string{"low-1", "low-2", "low-3"},
expectedPreempted: []string{},
expectedUnschedulable: []string{"high-1", "high-2", "high-3"},
expectedPodsPreemptedByWAP: 0,
},
}
for _, tt := range tests {