Skip to content

Commit e7152d4

Browse files
committed
[KEP] FlavorFungability: replace FlavorFungibilityImplicitPreferenceDefault feature gate with API
* preference API field
1 parent 04ec7bf commit e7152d4

File tree

22 files changed

+355
-102
lines changed

22 files changed

+355
-102
lines changed

apis/kueue/v1beta1/clusterqueue_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,13 @@ const (
389389
TryNextFlavor FlavorFungibilityPolicy = "TryNextFlavor"
390390
)
391391

392+
type FlavorAssignmentPreference string
393+
394+
const (
395+
PreferenceBorrowing FlavorAssignmentPreference = "Borrowing"
396+
PreferencePreempting FlavorAssignmentPreference = "Preempting"
397+
)
398+
392399
// FlavorFungibility determines whether a workload should try the next flavor
393400
// before borrowing or preempting in current flavor.
394401
type FlavorFungibility struct {
@@ -415,6 +422,13 @@ type FlavorFungibility struct {
415422
// +kubebuilder:validation:Enum={MayStopSearch,TryNextFlavor,Preempt}
416423
// +kubebuilder:default="TryNextFlavor"
417424
WhenCanPreempt FlavorFungibilityPolicy `json:"whenCanPreempt,omitempty"`
425+
// preference selects the order between borrowing-first and preemption-first
426+
// when both WhenCanBorrow and WhenCanPreempt are set to `TryNextFlavor`.
427+
// If unset, the default preference is borrowing-first.
428+
// Preference must be unset when either WhenCanBorrow or WhenCanPreempt is not `TryNextFlavor`.
429+
//
430+
// +kubebuilder:validation:Enum={Borrowing,Preempting}
431+
Preference *FlavorAssignmentPreference `json:"preference,omitempty"`
418432
}
419433

420434
// ClusterQueuePreemption contains policies to preempt Workloads from this

apis/kueue/v1beta1/zz_generated.deepcopy.go

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

apis/kueue/v1beta2/clusterqueue_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,13 @@ const (
370370
TryNextFlavor FlavorFungibilityPolicy = "TryNextFlavor"
371371
)
372372

373+
type FlavorAssignmentPreference string
374+
375+
const (
376+
PreferenceBorrowing FlavorAssignmentPreference = "Borrowing"
377+
PreferencePreempting FlavorAssignmentPreference = "Preempting"
378+
)
379+
373380
// FlavorFungibility determines whether a workload should try the next flavor
374381
// before borrowing or preempting in current flavor.
375382
type FlavorFungibility struct {
@@ -396,6 +403,13 @@ type FlavorFungibility struct {
396403
// +kubebuilder:validation:Enum={MayStopSearch,TryNextFlavor,Preempt}
397404
// +kubebuilder:default="TryNextFlavor"
398405
WhenCanPreempt FlavorFungibilityPolicy `json:"whenCanPreempt,omitempty"`
406+
// preference selects the order between borrowing-first and preemption-first
407+
// when both WhenCanBorrow and WhenCanPreempt are set to `TryNextFlavor`.
408+
// If unset, the default preference is borrowing-first. Preference must be
409+
// unset when either WhenCanBorrow or WhenCanPreempt is not `TryNextFlavor`.
410+
//
411+
// +kubebuilder:validation:Enum={Borrowing,Preempting}
412+
Preference *FlavorAssignmentPreference `json:"preference,omitempty"`
399413
}
400414

401415
// ClusterQueuePreemption contains policies to preempt Workloads from this

apis/kueue/v1beta2/zz_generated.deepcopy.go

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

charts/kueue/templates/crd/kueue.x-k8s.io_clusterqueues.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,16 @@ spec:
192192
flavorFungibility defines whether a workload should try the next flavor
193193
before borrowing or preempting in the flavor being evaluated.
194194
properties:
195+
preference:
196+
description: |-
197+
preference selects the order between borrowing-first and preemption-first
198+
when both WhenCanBorrow and WhenCanPreempt are set to `TryNextFlavor`.
199+
If unset, the default preference is borrowing-first.
200+
Preference must be unset when either WhenCanBorrow or WhenCanPreempt is not `TryNextFlavor`.
201+
enum:
202+
- Borrowing
203+
- Preempting
204+
type: string
195205
whenCanBorrow:
196206
default: MayStopSearch
197207
description: |-
@@ -948,6 +958,16 @@ spec:
948958
flavorFungibility defines whether a workload should try the next flavor
949959
before borrowing or preempting in the flavor being evaluated.
950960
properties:
961+
preference:
962+
description: |-
963+
preference selects the order between borrowing-first and preemption-first
964+
when both WhenCanBorrow and WhenCanPreempt are set to `TryNextFlavor`.
965+
If unset, the default preference is borrowing-first. Preference must be
966+
unset when either WhenCanBorrow or WhenCanPreempt is not `TryNextFlavor`.
967+
enum:
968+
- Borrowing
969+
- Preempting
970+
type: string
951971
whenCanBorrow:
952972
default: MayStopSearch
953973
description: |-

client-go/applyconfiguration/kueue/v1beta1/flavorfungibility.go

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client-go/applyconfiguration/kueue/v1beta2/flavorfungibility.go

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/components/crd/bases/kueue.x-k8s.io_clusterqueues.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,16 @@ spec:
163163
flavorFungibility defines whether a workload should try the next flavor
164164
before borrowing or preempting in the flavor being evaluated.
165165
properties:
166+
preference:
167+
description: |-
168+
preference selects the order between borrowing-first and preemption-first
169+
when both WhenCanBorrow and WhenCanPreempt are set to `TryNextFlavor`.
170+
If unset, the default preference is borrowing-first.
171+
Preference must be unset when either WhenCanBorrow or WhenCanPreempt is not `TryNextFlavor`.
172+
enum:
173+
- Borrowing
174+
- Preempting
175+
type: string
166176
whenCanBorrow:
167177
default: MayStopSearch
168178
description: |-
@@ -936,6 +946,16 @@ spec:
936946
flavorFungibility defines whether a workload should try the next flavor
937947
before borrowing or preempting in the flavor being evaluated.
938948
properties:
949+
preference:
950+
description: |-
951+
preference selects the order between borrowing-first and preemption-first
952+
when both WhenCanBorrow and WhenCanPreempt are set to `TryNextFlavor`.
953+
If unset, the default preference is borrowing-first. Preference must be
954+
unset when either WhenCanBorrow or WhenCanPreempt is not `TryNextFlavor`.
955+
enum:
956+
- Borrowing
957+
- Preempting
958+
type: string
939959
whenCanBorrow:
940960
default: MayStopSearch
941961
description: |-

pkg/features/kube_features.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,6 @@ const (
157157
// matches managedJobsNamespaceSelector.
158158
ManagedJobsNamespaceSelectorAlwaysRespected featuregate.Feature = "ManagedJobsNamespaceSelectorAlwaysRespected"
159159

160-
// owner: @pajakd
161-
// kep: https://github.com/kubernetes-sigs/kueue/tree/main/keps/582-preempt-based-on-flavor-order
162-
//
163-
// In flavor fungibility, the preference whether to preempt or borrow is inferred from flavor fungibility policy
164-
// This feature gate is going to be replaced by an API before graduation or deprecation.
165-
FlavorFungibilityImplicitPreferenceDefault featuregate.Feature = "FlavorFungibilityImplicitPreferenceDefault"
166-
167160
// owner: @alaypatel07
168161
// kep: https://github.com/kubernetes-sigs/kueue/tree/main/keps/2941-DRA
169162
//
@@ -288,9 +281,6 @@ var defaultVersionedFeatureGates = map[featuregate.Feature]featuregate.Versioned
288281
ManagedJobsNamespaceSelectorAlwaysRespected: {
289282
{Version: version.MustParse("0.13"), Default: false, PreRelease: featuregate.Alpha},
290283
},
291-
FlavorFungibilityImplicitPreferenceDefault: {
292-
{Version: version.MustParse("0.13"), Default: false, PreRelease: featuregate.Alpha},
293-
},
294284
DynamicResourceAllocation: {
295285
{Version: version.MustParse("0.14"), Default: false, PreRelease: featuregate.Alpha},
296286
},

pkg/scheduler/flavorassigner/flavorassigner.go

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,60 @@ type granularMode struct {
350350
borrowingLevel borrowingLevel
351351
}
352352

353+
func effectiveAssignmentPreference(cfg kueue.FlavorFungibility) kueue.FlavorAssignmentPreference {
354+
if cfg.WhenCanBorrow == kueue.TryNextFlavor && cfg.WhenCanPreempt == kueue.TryNextFlavor {
355+
if cfg.Preference != nil {
356+
return *cfg.Preference
357+
}
358+
return kueue.PreferenceBorrowing
359+
}
360+
if cfg.WhenCanBorrow == kueue.TryNextFlavor && cfg.WhenCanPreempt != kueue.TryNextFlavor {
361+
return kueue.PreferencePreempting
362+
}
363+
return kueue.PreferenceBorrowing
364+
}
365+
366+
// preferenceCategory maps a granularMode into one of four categories
367+
// Interpreting those buckets:
368+
// Category 0 – Fits without preemption and without borrowing.
369+
// This is the best possible outcome in every preference mode.
370+
// Category 1
371+
// With PreferenceBorrowing: fits without needing to preempt, but does require borrowing.
372+
// With PreferencePreempting: requires preemption but can do so without borrowing (i.e., reclaiming local quota).
373+
// Category 2
374+
// With PreferenceBorrowing: requires preemption, but no borrowing is necessary.
375+
// With PreferencePreempting: fits without preemption but does need borrowing.
376+
// Category 3 – Requires both preemption and borrowing; it is the least desirable outcome in either mode.
377+
func preferenceCategory(mode granularMode, pref kueue.FlavorAssignmentPreference) int {
378+
borrowOptimal := mode.borrowingLevel.optimal()
379+
requiresPreemption := mode.preemptionMode != fit
380+
switch pref {
381+
case kueue.PreferencePreempting:
382+
switch {
383+
case !requiresPreemption && borrowOptimal:
384+
return 0
385+
case requiresPreemption && borrowOptimal:
386+
return 1
387+
case !requiresPreemption:
388+
return 2
389+
default:
390+
return 3
391+
}
392+
// kueue.PreferenceBorrowing
393+
default:
394+
switch {
395+
case !requiresPreemption && borrowOptimal:
396+
return 0
397+
case !requiresPreemption:
398+
return 1
399+
case requiresPreemption && borrowOptimal:
400+
return 2
401+
default:
402+
return 3
403+
}
404+
}
405+
}
406+
353407
func worstGranularMode() granularMode {
354408
return granularMode{preemptionMode: noFit, borrowingLevel: math.MaxInt}
355409
}
@@ -371,6 +425,9 @@ const (
371425
)
372426

373427
// isPreferred returns true if mode a is better than b according to the selected policy
428+
// Preference is driven solely by ClusterQueue's flavorFungibility configuration.
429+
// If WhenCanBorrow is TryNextFlavor, we prioritize assignments that avoid borrowing,
430+
// otherwise we prioritize assignments that avoid preemption.
374431
func isPreferred(a, b granularMode, fungibilityConfig kueue.FlavorFungibility) bool {
375432
if a.preemptionMode == noFit {
376433
return false
@@ -379,25 +436,19 @@ func isPreferred(a, b granularMode, fungibilityConfig kueue.FlavorFungibility) b
379436
return true
380437
}
381438

382-
if !features.Enabled(features.FlavorFungibilityImplicitPreferenceDefault) {
383-
if a.preemptionMode != b.preemptionMode {
384-
return a.preemptionMode > b.preemptionMode
385-
} else {
386-
return a.borrowingLevel.betterThan(b.borrowingLevel)
387-
}
439+
pref := effectiveAssignmentPreference(fungibilityConfig)
440+
catA := preferenceCategory(a, pref)
441+
catB := preferenceCategory(b, pref)
442+
if catA != catB {
443+
return catA < catB
388444
}
389-
390-
if fungibilityConfig.WhenCanBorrow == kueue.TryNextFlavor {
391-
if a.borrowingLevel != b.borrowingLevel {
392-
return a.borrowingLevel.betterThan(b.borrowingLevel)
393-
}
445+
if a.preemptionMode != b.preemptionMode {
394446
return a.preemptionMode > b.preemptionMode
395-
} else {
396-
if a.preemptionMode != b.preemptionMode {
397-
return a.preemptionMode > b.preemptionMode
398-
}
447+
}
448+
if a.borrowingLevel != b.borrowingLevel {
399449
return a.borrowingLevel.betterThan(b.borrowingLevel)
400450
}
451+
return false
401452
}
402453

403454
func fromPreemptionPossibility(preemptionPossibility preemptioncommon.PreemptionPossibility) preemptionMode {

0 commit comments

Comments
 (0)