Skip to content

Commit ca9d1c1

Browse files
committed
fix(api): allow PodGroup minMember of 0 (#1989)
Backport of 62c594b to v0.9, adapted for non-pointer MinMember and the SubGroups-based scheduler API. Signed-off-by: lin121291 <4jp33f9e@gmail.com> Signed-off-by: gshaibi <gshaibi@nvidia.com>
1 parent 49fa93a commit ca9d1c1

7 files changed

Lines changed: 76 additions & 5 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
kind: Fixed
2+
body: |-
3+
Allow PodGroup minMember of 0 for workloads with no gang requirement

deployments/kai-scheduler/crds/scheduling.run.ai_podgroups.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ spec:
6161
MinMember defines the minimal number of members/tasks to run the pod group;
6262
if there's not enough resources to start all tasks, the scheduler
6363
will not start anyone.
64+
A value of 0 means no gang requirement: all pods are scheduled elastically (e.g. scale-to-zero workloads).
6465
format: int32
6566
type: integer
6667
parallelism:

pkg/apis/scheduling/v2alpha2/podgroup_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type PodGroupSpec struct {
3434
// MinMember defines the minimal number of members/tasks to run the pod group;
3535
// if there's not enough resources to start all tasks, the scheduler
3636
// will not start anyone.
37+
// A value of 0 means no gang requirement: all pods are scheduled elastically (e.g. scale-to-zero workloads).
3738
MinMember int32 `json:"minMember,omitempty" protobuf:"bytes,1,opt,name=minMember"`
3839

3940
// Queue defines the queue to allocate resource for PodGroup; if queue does not exist,

pkg/podgrouper/podgrouper/plugins/knative/knative_test.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
package knative
55

66
import (
7-
"strconv"
87
"testing"
98

109
"github.com/stretchr/testify/assert"
@@ -113,6 +112,17 @@ func TestGetPodGroupMetadata(t *testing.T) {
113112
}
114113

115114
func TestGetPodGroupMetadata_MinScale(t *testing.T) {
115+
for minScale, expectedMinAvailable := range map[string]int32{
116+
"3": 3,
117+
"0": 0, // scale-to-zero: no gang requirement
118+
} {
119+
t.Run("min-scale="+minScale, func(t *testing.T) {
120+
testGetPodGroupMetadataMinScale(t, minScale, expectedMinAvailable)
121+
})
122+
}
123+
}
124+
125+
func testGetPodGroupMetadataMinScale(t *testing.T, minScale string, expectedMinAvailable int32) {
116126
service := &unstructured.Unstructured{
117127
Object: map[string]interface{}{
118128
"kind": "Service",
@@ -156,7 +166,7 @@ func TestGetPodGroupMetadata_MinScale(t *testing.T) {
156166
Namespace: "test_namespace",
157167
UID: "2",
158168
Annotations: map[string]string{
159-
"autoscaling.knative.dev/min-scale": "3",
169+
"autoscaling.knative.dev/min-scale": minScale,
160170
},
161171
},
162172
Spec: knative.RevisionSpec{},
@@ -197,7 +207,7 @@ func TestGetPodGroupMetadata_MinScale(t *testing.T) {
197207
assert.Equal(t, "pg-revision-2", metadata.Name)
198208
assert.Equal(t, constants.InferencePriorityClass, metadata.PriorityClassName)
199209
assert.Equal(t, "test_queue", metadata.Queue)
200-
assert.Equal(t, "3", strconv.Itoa(int(metadata.MinAvailable)))
210+
assert.Equal(t, expectedMinAvailable, metadata.MinAvailable)
201211
}
202212

203213
func TestGetPodGroupMetadataBackwardsCompatibility(t *testing.T) {

pkg/scheduler/api/podgroup_info/job_info.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,9 @@ func (pgi *PodGroupInfo) setSubGroups(podGroup *enginev2alpha2.PodGroup) {
204204
}
205205
defaultSubGroup, found := pgi.SubGroups[DefaultSubGroup]
206206
if !found {
207-
pgi.SubGroups[DefaultSubGroup] = NewSubGroupInfo(DefaultSubGroup, max(podGroup.Spec.MinMember, 1))
207+
pgi.SubGroups[DefaultSubGroup] = NewSubGroupInfo(DefaultSubGroup, max(podGroup.Spec.MinMember, 0))
208208
} else {
209-
defaultSubGroup.SetMinAvailable(max(podGroup.Spec.MinMember, 1))
209+
defaultSubGroup.SetMinAvailable(max(podGroup.Spec.MinMember, 0))
210210
}
211211
}
212212

pkg/scheduler/api/podgroup_info/job_info_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,36 @@ func TestAddTaskInfo(t *testing.T) {
115115
}
116116
}
117117

118+
func TestSetPodGroupMinMember(t *testing.T) {
119+
tests := []struct {
120+
name string
121+
minMember int32
122+
expectedMinAvail int32
123+
}{
124+
{"zero minMember is honored", 0, 0},
125+
{"positive minMember is honored", 3, 3},
126+
}
127+
for _, test := range tests {
128+
t.Run(test.name, func(t *testing.T) {
129+
info := NewPodGroupInfo("group-1")
130+
info.SetPodGroup(&v2alpha2.PodGroup{
131+
ObjectMeta: metav1.ObjectMeta{
132+
Name: "group-1",
133+
Namespace: "ns-1",
134+
},
135+
Spec: v2alpha2.PodGroupSpec{
136+
Queue: "queue-1",
137+
MinMember: test.minMember,
138+
},
139+
})
140+
141+
if got := info.SubGroups[DefaultSubGroup].GetMinAvailable(); got != test.expectedMinAvail {
142+
t.Errorf("expected minAvailable %d, got %d", test.expectedMinAvail, got)
143+
}
144+
})
145+
}
146+
}
147+
118148
func TestDeleteTaskInfo(t *testing.T) {
119149
// case1
120150
case01_uid := common_info.PodGroupID("owner1")

test/e2e/suites/integrations/third_party/knative/knative_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,32 @@ var _ = Describe("Knative integration", Ordered, func() {
118118
"Expected minmember of podgroup to be 1")
119119
})
120120

121+
It("knative service with scale-to-zero - minscale 0", func(ctx context.Context) {
122+
namespace := queue.GetConnectedNamespaceToQueue(testCtx.Queues[0])
123+
serviceName := "knative-service-" + utils.GenerateRandomK8sName(10)
124+
125+
knativeService := getKnativeServiceObject(serviceName, namespace, testCtx.Queues[0].Name)
126+
127+
knativeService.Spec.ConfigurationSpec.Template.Annotations[minScaleAnnotation] = "0"
128+
129+
Expect(testCtx.ControllerClient.Create(ctx, knativeService)).To(Succeed())
130+
defer func() {
131+
Expect(testCtx.ControllerClient.Delete(ctx, knativeService)).To(Succeed())
132+
}()
133+
134+
// Knative still creates the initial pod on deployment even with min-scale 0
135+
pods := getServicePods(ctx, testCtx, serviceName, namespace, 1)
136+
137+
wait.ForPodsScheduled(ctx, testCtx.ControllerClient, namespace, pods)
138+
139+
var podGroups v2alpha2.PodGroupList
140+
Expect(testCtx.ControllerClient.List(ctx, &podGroups, client.InNamespace(namespace))).To(Succeed())
141+
Expect(len(podGroups.Items)).To(Equal(1),
142+
"Expected one podgroup for the revision")
143+
Expect(podGroups.Items[0].Spec.MinMember).To(Equal(int32(0)),
144+
"Expected minmember of podgroup to be 0")
145+
})
146+
121147
Context("Non-Gang scheduling", func() {
122148
BeforeAll(func(ctx context.Context) {
123149
setKnativeGangAndWait(ctx, testCtx, pointer.Bool(false))

0 commit comments

Comments
 (0)