Skip to content

Commit e22aa86

Browse files
committed
change api to defaultProfileName and add test in config and webhook validation
Signed-off-by: kangclzjc <kangz@nvidia.com>
1 parent 590baa2 commit e22aa86

File tree

21 files changed

+578
-110
lines changed

21 files changed

+578
-110
lines changed

docs/api-reference/operator-api.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,8 @@ _Appears in:_
882882

883883
| Field | Description | Default | Validation |
884884
| --- | --- | --- | --- |
885-
| `profiles` _[SchedulerProfile](#schedulerprofile) array_ | Profiles is the list of scheduler profiles. Each profile has a backend name, optional config, and whether it is the default.<br />The kube-scheduler backend is always enabled; use profile name "kube-scheduler" to configure or set it as default.<br />Valid profile names: "kube-scheduler", "kai-scheduler". Exactly one profile should have default: true; if none, kube-scheduler is the default. | | |
885+
| `profiles` _[SchedulerProfile](#schedulerprofile) array_ | Profiles is the list of scheduler profiles. Each profile has a backend name and optional config.<br />The kube-scheduler backend is always enabled; use profile name "kube-scheduler" to configure or set it as default.<br />Valid profile names: "kube-scheduler", "kai-scheduler". Use defaultProfileName to designate the default backend. If not set, defaulting sets it to "kube-scheduler". | | |
886+
| `defaultProfileName` _string_ | DefaultProfileName is the name of the default scheduler profile. If unset, defaulting sets it to "kube-scheduler". | | |
886887

887888

888889
#### SchedulerName
@@ -906,7 +907,7 @@ _Appears in:_
906907

907908

908909

909-
SchedulerProfile defines a scheduler backend profile with optional backend-specific config and default flag.
910+
SchedulerProfile defines a scheduler backend profile with optional backend-specific config.
910911

911912

912913

@@ -917,7 +918,6 @@ _Appears in:_
917918
| --- | --- | --- | --- |
918919
| `name` _[SchedulerName](#schedulername)_ | Name is the scheduler profile name. Valid values: "kube-scheduler", "kai-scheduler".<br />For the Kubernetes default scheduler use "kube-scheduler"; Pod.Spec.SchedulerName will be set to "default-scheduler". | | Enum: [kai-scheduler kube-scheduler] <br />Required: \{\} <br /> |
919920
| `config` _[RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.33/#rawextension-runtime-pkg)_ | Config holds backend-specific options. The operator unmarshals it into the config type for this backend (see backend config types). | | |
920-
| `default` _boolean_ | Default indicates this profile is the default backend when a workload does not specify one. Exactly one profile should have default: true. | | |
921921

922922

923923
#### Server

docs/proposals/375-scheduler-backend-framework/README.md

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ func Initialize(client client.Client, scheme *runtime.Scheme, eventRecorder reco
215215
// Get returns the backend for the given name. kube-scheduler is always available; other backends return nil if not enabled via a profile.
216216
func Get(name string) SchedulerBackend
217217

218-
// GetDefault returns the backend designated as default in OperatorConfiguration (the profile with default: true; if none, kube-scheduler). The manager does not define the default; it exposes the one from config.
218+
// GetDefault returns the backend designated as default in OperatorConfiguration (scheduler.defaultProfileName).
219219
func GetDefault() SchedulerBackend
220220

221221
```
@@ -241,12 +241,15 @@ type OperatorConfiguration struct {
241241

242242
// SchedulerConfiguration configures scheduler profiles and which is the default.
243243
type SchedulerConfiguration struct {
244-
// Profiles is the list of scheduler profiles. Each profile has a backend name, optional config, and whether it is the default.
244+
// Profiles is the list of scheduler profiles. Each profile has a backend name and optional config.
245245
// The kube-scheduler backend is always enabled and active even if not listed here. Listing "kube-scheduler" in profiles
246-
// only adds a profile (e.g. with config like GangScheduling: false) and allows marking it as default.
247-
// Valid backend names: "kube-scheduler", "kai-scheduler". Exactly one profile should have default: true; if none, kube-scheduler is the default.
246+
// only adds a profile (e.g. with config like GangScheduling: false). Use defaultProfileName to designate the default backend.
247+
// Valid backend names: "kube-scheduler", "kai-scheduler". If defaultProfileName is unset, defaulting sets it to "kube-scheduler".
248248
// +optional
249249
Profiles []SchedulerProfile `json:"profiles,omitempty"`
250+
// DefaultProfileName is the name of the default scheduler profile.
251+
// +optional
252+
DefaultProfileName string `json:"defaultProfileName,omitempty"`
250253
}
251254

252255
// SchedulerName is the name for a supported scheduler backend.
@@ -270,7 +273,7 @@ var SupportedSchedulerNames = []SchedulerName {
270273
// <add any other supported backend scheduler constant here>
271274
}
272275

273-
// SchedulerProfile defines a scheduler backend profile with optional backend-specific config and default flag.
276+
// SchedulerProfile defines a scheduler backend profile with optional backend-specific config.
274277
type SchedulerProfile struct {
275278
// Name is the scheduler backend name. Valid values: "kube-scheduler", "kai-scheduler".
276279
// +kubebuilder:validation:Enum=kai-scheduler;kube-scheduler
@@ -279,18 +282,15 @@ type SchedulerProfile struct {
279282
// Config holds backend-specific options. The operator unmarshals it into the config type for this backend (see backend config types below).
280283
// +optional
281284
Config *runtime.RawExtension `json:"config,omitempty"`
282-
283-
// Default indicates this profile is the default backend when a workload does not specify one. Exactly one profile should have default: true.
284-
// +optional
285-
Default bool `json:"default,omitempty"`
286285
}
287286
```
288287

289288
The `OperatorConfiguration` provides a way to enable and configure one or more scheduler backends. `SchedulerProfile` allows you to configure the following:
290289

291290
- **Name:** This is the name of the scheduler backend. This must be one of the supported schedulers.
292291
- **Config:** Optional scheduler-specific configuration as `runtime.RawExtension`. It is the responsibility of the scheduler backend implementation to interpret and possibly deserialize it to type.
293-
- **Default:** Indicates if this scheduler should be the default. In case no scheduler name is set in any `PodSpec` across all `PodCliqueTemplateSpec` then the default scheduler as indicated via this field will be set.
292+
293+
`SchedulerConfiguration.defaultProfileName` designates which profile is the default. When no scheduler name is set in any `PodSpec` across all `PodCliqueTemplateSpec`, the default scheduler indicated by `defaultProfileName` will be used.
294294

295295
**Backend Enabling Behavior:**
296296

@@ -300,22 +300,20 @@ The kube-scheduler backend has special behavior compared to other scheduler back
300300

301301
2. **Explicit Configuration Optional**: You only need to add kube-scheduler to `profiles` if you want to:
302302
- Configure it with specific options (e.g., `gangScheduling: true`)
303-
- Explicitly mark it as the default (though it's already the default if no other profile sets `default: true`)
303+
- Set it as the default via `defaultProfileName` (defaulting sets kube-scheduler as default when `defaultProfileName` is unset)
304304

305305
3. **Other Schedulers Require Explicit Enablement**: All non-kube-scheduler backends (kai-scheduler, third-party schedulers) must be explicitly listed in `profiles` to be enabled. If a workload references a scheduler that is not in the profiles list, the validating webhook will reject the PodCliqueSet.
306306

307307
4. **Default Selection Logic**:
308-
- If `profiles` is empty → kube-scheduler is the default
309-
- If exactly one profile has `default: true` → that backend is the default
310-
- If multiple profiles have `default: true` → operator startup fails with validation error
311-
- If no profile has `default: true` → kube-scheduler is the default (even if not in the list)
308+
- If `profiles` is empty → defaulting adds kube-scheduler and sets `defaultProfileName: "kube-scheduler"`
309+
- `defaultProfileName` must be one of the configured profile names; validation rejects invalid or missing default profile name
312310

313311
If no `SchedulerProfile` has been set, then Grove operator behaves as if you specified:
314312
```yaml
315313
scheduler:
314+
defaultProfileName: kube-scheduler
316315
profiles:
317316
- name: "kube-scheduler"
318-
default: true
319317
```
320318
321319
> NOTE: If you as a workload operator wish to use a specific scheduler, please ensure that it has been enabled and properly configured as part of `OperatorConfiguration`. If PodCliqueSet uses a scheduler which has not been enabled, then the validating webhook will reject any creation request for this PodCliqueSet.
@@ -336,46 +334,46 @@ type KubeSchedulerConfig struct {
336334

337335
```yaml
338336
# --- Omit scheduler profiles completely ---
339-
# Same as profiles: [{ name: "kube-scheduler", default: true }]
337+
# Same as defaultProfileName: kube-scheduler, profiles: [{ name: "kube-scheduler" }]
340338
```
341339

342340
```yaml
343341
# --- Single scheduler profile, no specific configuration ---
344342
scheduler:
343+
defaultProfileName: kube-scheduler
345344
profiles:
346345
- name: "kube-scheduler"
347-
default: true
348346
# In this configuration Gang Scheduling will not be enabled
349347
```
350348

351349
```yaml
352350
# --- Single scheduler profile with configuration ---
353351
scheduler:
352+
defaultProfileName: kube-scheduler
354353
profiles:
355354
- name: "kube-scheduler"
356355
config:
357356
gangScheduling: true
358-
default: true
359357
```
360358
361359
```yaml
362360
# --- Multiple scheduler profiles; default is kube-scheduler ---
363361
scheduler:
362+
defaultProfileName: kube-scheduler
364363
profiles:
365364
- name: "kube-scheduler"
366365
config:
367366
gangScheduling: true
368-
default: true
369367
- name: "kai-scheduler" # no scheduler-specific configuration is defined
370368
```
371369
372370
```yaml
373371
# --- Only kai-scheduler profile; kube-scheduler is still implicitly available but kai-scheduler is the default ---
374372
scheduler:
373+
defaultProfileName: kai-scheduler
375374
profiles:
376375
- name: "kai-scheduler"
377376
config: {}
378-
default: true
379377
```
380378
381379

operator/api/config/v1alpha1/defaults.go

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -72,16 +72,17 @@ func SetDefaults_OperatorConfiguration(operatorConfig *OperatorConfiguration) {
7272
// SetDefaults_SchedulerConfiguration sets defaults for scheduler configuration.
7373
// Principle: respect all user-explicit values first.
7474
//
75-
// 1. If user did not include kube in profiles, add kube. Whether kube is default: only if no other profile is default, set kube as default.
76-
// 2. If user did not set any default (no profile has default: true), set kube as default. Validation will reject invalid cases.
75+
// 1. If user did not include kube in profiles, add kube.
76+
// 2. If defaultProfileName is unset, set it to "kube-scheduler". Validation will reject invalid cases.
7777
func SetDefaults_SchedulerConfiguration(cfg *SchedulerConfiguration) {
7878
if len(cfg.Profiles) == 0 {
7979
cfg.Profiles = []SchedulerProfile{
80-
{Name: SchedulerNameKube, Default: true},
80+
{Name: SchedulerNameKube},
8181
}
82+
cfg.DefaultProfileName = string(SchedulerNameKube)
8283
return
8384
}
84-
// 1. If user didn't add kube, add it (default: false for now).
85+
// 1. If user didn't add kube, add it.
8586
hasKube := false
8687
for i := range cfg.Profiles {
8788
if cfg.Profiles[i].Name == SchedulerNameKube {
@@ -90,27 +91,12 @@ func SetDefaults_SchedulerConfiguration(cfg *SchedulerConfiguration) {
9091
}
9192
}
9293
if !hasKube {
93-
cfg.Profiles = append(cfg.Profiles, SchedulerProfile{Name: SchedulerNameKube, Default: false})
94+
cfg.Profiles = append(cfg.Profiles, SchedulerProfile{Name: SchedulerNameKube})
9495
}
9596

96-
var (
97-
defaultCount int
98-
kubeIdx = -1
99-
)
100-
for i := range cfg.Profiles {
101-
if cfg.Profiles[i].Name == SchedulerNameKube {
102-
kubeIdx = i
103-
}
104-
if cfg.Profiles[i].Default {
105-
defaultCount++
106-
}
107-
}
108-
if kubeIdx < 0 {
109-
return
110-
}
111-
// 2. No default → set kube as default.
112-
if defaultCount == 0 {
113-
cfg.Profiles[kubeIdx].Default = true
97+
// 2. No default profile name → set kube as default.
98+
if cfg.DefaultProfileName == "" {
99+
cfg.DefaultProfileName = string(SchedulerNameKube)
114100
}
115101
}
116102

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// /*
2+
// Copyright 2026 The Grove Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
// */
16+
17+
package v1alpha1
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestSetDefaults_SchedulerConfiguration(t *testing.T) {
26+
tests := []struct {
27+
name string
28+
cfg *SchedulerConfiguration
29+
wantProfiles []SchedulerProfile
30+
wantDefaultProfile string
31+
}{
32+
{
33+
name: "empty profiles: add kube and set defaultProfileName",
34+
cfg: &SchedulerConfiguration{},
35+
wantProfiles: []SchedulerProfile{{Name: SchedulerNameKube}},
36+
wantDefaultProfile: string(SchedulerNameKube),
37+
},
38+
{
39+
name: "nil profiles (len 0): add kube and set defaultProfileName",
40+
cfg: &SchedulerConfiguration{
41+
Profiles: nil,
42+
DefaultProfileName: "",
43+
},
44+
wantProfiles: []SchedulerProfile{{Name: SchedulerNameKube}},
45+
wantDefaultProfile: string(SchedulerNameKube),
46+
},
47+
{
48+
name: "only kai in profiles: append kube and set defaultProfileName",
49+
cfg: &SchedulerConfiguration{
50+
Profiles: []SchedulerProfile{{Name: SchedulerNameKai}},
51+
DefaultProfileName: "",
52+
},
53+
wantProfiles: []SchedulerProfile{{Name: SchedulerNameKai}, {Name: SchedulerNameKube}},
54+
wantDefaultProfile: string(SchedulerNameKube),
55+
},
56+
{
57+
name: "only kube in profiles, defaultProfileName unset: set defaultProfileName",
58+
cfg: &SchedulerConfiguration{
59+
Profiles: []SchedulerProfile{{Name: SchedulerNameKube}},
60+
DefaultProfileName: "",
61+
},
62+
wantProfiles: []SchedulerProfile{{Name: SchedulerNameKube}},
63+
wantDefaultProfile: string(SchedulerNameKube),
64+
},
65+
{
66+
name: "kube and kai in profiles, defaultProfileName unset: set defaultProfileName to kube",
67+
cfg: &SchedulerConfiguration{
68+
Profiles: []SchedulerProfile{
69+
{Name: SchedulerNameKube},
70+
{Name: SchedulerNameKai},
71+
},
72+
DefaultProfileName: "",
73+
},
74+
wantProfiles: []SchedulerProfile{
75+
{Name: SchedulerNameKube},
76+
{Name: SchedulerNameKai},
77+
},
78+
wantDefaultProfile: string(SchedulerNameKube),
79+
},
80+
{
81+
name: "kube and kai in profiles, defaultProfileName already set to kube: no change",
82+
cfg: &SchedulerConfiguration{
83+
Profiles: []SchedulerProfile{
84+
{Name: SchedulerNameKube},
85+
{Name: SchedulerNameKai},
86+
},
87+
DefaultProfileName: string(SchedulerNameKube),
88+
},
89+
wantProfiles: []SchedulerProfile{
90+
{Name: SchedulerNameKube},
91+
{Name: SchedulerNameKai},
92+
},
93+
wantDefaultProfile: string(SchedulerNameKube),
94+
},
95+
{
96+
name: "kube and kai in profiles, defaultProfileName already set to kai: no change",
97+
cfg: &SchedulerConfiguration{
98+
Profiles: []SchedulerProfile{
99+
{Name: SchedulerNameKube},
100+
{Name: SchedulerNameKai},
101+
},
102+
DefaultProfileName: string(SchedulerNameKai),
103+
},
104+
wantProfiles: []SchedulerProfile{
105+
{Name: SchedulerNameKube},
106+
{Name: SchedulerNameKai},
107+
},
108+
wantDefaultProfile: string(SchedulerNameKai),
109+
},
110+
{
111+
name: "only kai in profiles, defaultProfileName already kai: append kube only",
112+
cfg: &SchedulerConfiguration{
113+
Profiles: []SchedulerProfile{{Name: SchedulerNameKai}},
114+
DefaultProfileName: string(SchedulerNameKai),
115+
},
116+
wantProfiles: []SchedulerProfile{{Name: SchedulerNameKai}, {Name: SchedulerNameKube}},
117+
wantDefaultProfile: string(SchedulerNameKai),
118+
},
119+
}
120+
121+
for _, tt := range tests {
122+
t.Run(tt.name, func(t *testing.T) {
123+
SetDefaults_SchedulerConfiguration(tt.cfg)
124+
assert.Equal(t, tt.wantProfiles, tt.cfg.Profiles, "Profiles after defaulting")
125+
assert.Equal(t, tt.wantDefaultProfile, tt.cfg.DefaultProfileName, "DefaultProfileName after defaulting")
126+
})
127+
}
128+
}

operator/api/config/v1alpha1/types.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,17 @@ var (
6969

7070
// SchedulerConfiguration configures scheduler profiles and which is the default.
7171
type SchedulerConfiguration struct {
72-
// Profiles is the list of scheduler profiles. Each profile has a backend name, optional config, and whether it is the default.
72+
// Profiles is the list of scheduler profiles. Each profile has a backend name and optional config.
7373
// The kube-scheduler backend is always enabled; use profile name "kube-scheduler" to configure or set it as default.
74-
// Valid profile names: "kube-scheduler", "kai-scheduler". Exactly one profile should have default: true; if none, kube-scheduler is the default.
74+
// Valid profile names: "kube-scheduler", "kai-scheduler". Use defaultProfileName to designate the default backend. If not set, defaulting sets it to "kube-scheduler".
7575
// +optional
7676
Profiles []SchedulerProfile `json:"profiles,omitempty"`
77+
// DefaultProfileName is the name of the default scheduler profile. If unset, defaulting sets it to "kube-scheduler".
78+
// +optional
79+
DefaultProfileName string `json:"defaultProfileName,omitempty"`
7780
}
7881

79-
// SchedulerProfile defines a scheduler backend profile with optional backend-specific config and default flag.
82+
// SchedulerProfile defines a scheduler backend profile with optional backend-specific config.
8083
type SchedulerProfile struct {
8184
// Name is the scheduler profile name. Valid values: "kube-scheduler", "kai-scheduler".
8285
// For the Kubernetes default scheduler use "kube-scheduler"; Pod.Spec.SchedulerName will be set to "default-scheduler".
@@ -87,10 +90,6 @@ type SchedulerProfile struct {
8790
// Config holds backend-specific options. The operator unmarshals it into the config type for this backend (see backend config types).
8891
// +optional
8992
Config *runtime.RawExtension `json:"config,omitempty"`
90-
91-
// Default indicates this profile is the default backend when a workload does not specify one. Exactly one profile should have default: true.
92-
// +optional
93-
Default bool `json:"default,omitempty"`
9493
}
9594

9695
// KaiSchedulerConfiguration defines the configuration for the kai-scheduler backend.

0 commit comments

Comments
 (0)