Skip to content

Commit 49bd6c4

Browse files
committed
Add e2e tests for manageJobsWithoutQueueName
1 parent 198d276 commit 49bd6c4

File tree

5 files changed

+275
-0
lines changed

5 files changed

+275
-0
lines changed

Makefile-test.mk

+12
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ test-multikueue-e2e: kustomize ginkgo yq gomod-download dep-crds ginkgo-top run-
112112
.PHONY: test-tas-e2e
113113
test-tas-e2e: kustomize ginkgo yq gomod-download dep-crds kueuectl ginkgo-top run-test-tas-e2e-$(E2E_KIND_VERSION:kindest/node:v%=%)
114114

115+
.PHONY: test-queuename-e2e
116+
test-queuename-e2e: kustomize ginkgo yq gomod-download dep-crds kueuectl ginkgo-top run-test-queuename-e2e-$(E2E_KIND_VERSION:kindest/node:v%=%)
117+
115118
E2E_TARGETS := $(addprefix run-test-e2e-,${E2E_K8S_VERSIONS})
116119
MULTIKUEUE-E2E_TARGETS := $(addprefix run-test-multikueue-e2e-,${E2E_K8S_VERSIONS})
117120
.PHONY: test-e2e-all
@@ -151,6 +154,15 @@ run-test-tas-e2e-%: FORCE
151154
./hack/e2e-test.sh
152155
$(PROJECT_DIR)/bin/ginkgo-top -i $(ARTIFACTS)/$@/e2e.json > $(ARTIFACTS)/$@/e2e-top.yaml
153156

157+
run-test-queuename-e2e-%: K8S_VERSION = $(@:run-test-queuename-e2e-%=%)
158+
run-test-queuename-e2e-%: FORCE
159+
@echo Running e2e for k8s ${K8S_VERSION}
160+
E2E_KIND_VERSION="kindest/node:v$(K8S_VERSION)" KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) CREATE_KIND_CLUSTER=$(CREATE_KIND_CLUSTER) \
161+
ARTIFACTS="$(ARTIFACTS)/$@" IMAGE_TAG=$(IMAGE_TAG) GINKGO_ARGS="$(GINKGO_ARGS)" \
162+
JOBSET_VERSION=$(JOBSET_VERSION) \
163+
KIND_CLUSTER_FILE="kind-cluster.yaml" E2E_TARGET_FOLDER="queuename" \
164+
./hack/e2e-test.sh
165+
$(PROJECT_DIR)/bin/ginkgo-top -i $(ARTIFACTS)/$@/e2e.json > $(ARTIFACTS)/$@/e2e-top.yaml
154166

155167
SCALABILITY_RUNNER := $(PROJECT_DIR)/bin/performance-scheduler-runner
156168
.PHONY: performance-scheduler-runner

test/e2e/config/default/manager_e2e_patch.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
- op: add
55
path: /spec/template/spec/containers/0/args/-
66
value: --feature-gates=MultiKueueBatchJobWithManagedBy=true,TopologyAwareScheduling=true,LocalQueueMetrics=true
7+
- op: add
8+
path: /spec/strategy
9+
value:
10+
type: Recreate
+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
Copyright 2025 The Kubernetes 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 queuename
18+
19+
import (
20+
"time"
21+
22+
"github.com/onsi/ginkgo/v2"
23+
"github.com/onsi/gomega"
24+
batchv1 "k8s.io/api/batch/v1"
25+
corev1 "k8s.io/api/core/v1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27+
"k8s.io/apimachinery/pkg/types"
28+
29+
"k8s.io/utils/ptr"
30+
kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
31+
workloadjob "sigs.k8s.io/kueue/pkg/controller/jobs/job"
32+
"sigs.k8s.io/kueue/pkg/util/testing"
33+
testingjob "sigs.k8s.io/kueue/pkg/util/testingjobs/job"
34+
"sigs.k8s.io/kueue/test/util"
35+
)
36+
37+
var _ = ginkgo.Describe("ManageJobsWithoutQueueName", ginkgo.Ordered, func() {
38+
var ns *corev1.Namespace
39+
40+
ginkgo.BeforeAll(func() {
41+
configurationUpdate := time.Now()
42+
config := util.GetKueueConfiguration(ctx, k8sClient)
43+
config.ManageJobsWithoutQueueName = true
44+
util.ApplyKueueConfiguration(ctx, k8sClient, config)
45+
util.RestartKueueController(ctx, k8sClient)
46+
ginkgo.GinkgoLogr.Info("Kueue configuration updated", "took", time.Since(configurationUpdate))
47+
})
48+
49+
ginkgo.AfterAll(func() {
50+
config := util.GetKueueConfiguration(ctx, k8sClient)
51+
config.ManageJobsWithoutQueueName = false
52+
util.ApplyKueueConfiguration(ctx, k8sClient, config)
53+
util.RestartKueueController(ctx, k8sClient)
54+
})
55+
56+
ginkgo.BeforeEach(func() {
57+
ns = &corev1.Namespace{
58+
ObjectMeta: metav1.ObjectMeta{
59+
GenerateName: "e2e-",
60+
},
61+
}
62+
gomega.Expect(k8sClient.Create(ctx, ns)).To(gomega.Succeed())
63+
})
64+
ginkgo.AfterEach(func() {
65+
gomega.Expect(util.DeleteNamespace(ctx, k8sClient, ns)).To(gomega.Succeed())
66+
})
67+
ginkgo.When("creating a Job when manageJobsWithoutQueueName=true", func() {
68+
var (
69+
defaultRf *kueue.ResourceFlavor
70+
localQueue *kueue.LocalQueue
71+
clusterQueue *kueue.ClusterQueue
72+
)
73+
ginkgo.BeforeEach(func() {
74+
defaultRf = testing.MakeResourceFlavor("default").Obj()
75+
gomega.Expect(k8sClient.Create(ctx, defaultRf)).Should(gomega.Succeed())
76+
clusterQueue = testing.MakeClusterQueue("cluster-queue").
77+
ResourceGroup(
78+
*testing.MakeFlavorQuotas(defaultRf.Name).
79+
Resource(corev1.ResourceCPU, "2").
80+
Resource(corev1.ResourceMemory, "2G").Obj()).Obj()
81+
gomega.Expect(k8sClient.Create(ctx, clusterQueue)).Should(gomega.Succeed())
82+
localQueue = testing.MakeLocalQueue("main", ns.Name).ClusterQueue("cluster-queue").Obj()
83+
gomega.Expect(k8sClient.Create(ctx, localQueue)).Should(gomega.Succeed())
84+
})
85+
ginkgo.AfterEach(func() {
86+
gomega.Expect(util.DeleteAllJobsInNamespace(ctx, k8sClient, ns)).Should(gomega.Succeed())
87+
// Force remove workloads to be sure that cluster queue can be removed.
88+
gomega.Expect(util.DeleteWorkloadsInNamespace(ctx, k8sClient, ns)).Should(gomega.Succeed())
89+
gomega.Expect(util.DeleteObject(ctx, k8sClient, localQueue)).Should(gomega.Succeed())
90+
util.ExpectObjectToBeDeleted(ctx, k8sClient, clusterQueue, true)
91+
util.ExpectObjectToBeDeleted(ctx, k8sClient, defaultRf, true)
92+
})
93+
94+
ginkgo.It("should suspend it", func() {
95+
96+
var testJob *batchv1.Job
97+
ginkgo.By("creating a job without queue name", func() {
98+
testJob = testingjob.MakeJob("test-job", ns.Name).Suspend(false).Obj()
99+
gomega.Expect(k8sClient.Create(ctx, testJob)).Should(gomega.Succeed())
100+
})
101+
102+
ginkgo.By("verifying that the job is suspended", func() {
103+
jobLookupKey := types.NamespacedName{Name: testJob.Name, Namespace: ns.Name}
104+
createdJob := &batchv1.Job{}
105+
gomega.Eventually(func(g gomega.Gomega) {
106+
g.Expect(k8sClient.Get(ctx, jobLookupKey, createdJob)).Should(gomega.Succeed())
107+
g.Expect(ptr.Deref(createdJob.Spec.Suspend, false)).To(gomega.BeTrue())
108+
}, util.LongTimeout, util.Interval).Should(gomega.Succeed())
109+
})
110+
})
111+
112+
ginkgo.It("should unsuspend it", func() {
113+
114+
var testJob *batchv1.Job
115+
var jobLookupKey types.NamespacedName
116+
createdJob := &batchv1.Job{}
117+
ginkgo.By("creating a job without queue name", func() {
118+
testJob = testingjob.MakeJob("test-job", ns.Name).Suspend(false).Obj()
119+
gomega.Expect(k8sClient.Create(ctx, testJob)).Should(gomega.Succeed())
120+
})
121+
ginkgo.By("setting the queue-name label", func() {
122+
jobLookupKey = types.NamespacedName{Name: testJob.Name, Namespace: ns.Name}
123+
gomega.Eventually(func() error {
124+
if err := k8sClient.Get(ctx, jobLookupKey, createdJob); err != nil {
125+
return err
126+
}
127+
createdJob.Labels["kueue.x-k8s.io/queue-name"] = "main"
128+
return k8sClient.Update(ctx, createdJob)
129+
}, util.LongTimeout, util.Interval).Should(gomega.Succeed())
130+
})
131+
ginkgo.By("verifying that the job is unsuspended", func() {
132+
gomega.Eventually(func(g gomega.Gomega) {
133+
g.Expect(k8sClient.Get(ctx, jobLookupKey, createdJob)).Should(gomega.Succeed())
134+
g.Expect(ptr.Deref(createdJob.Spec.Suspend, false)).To(gomega.BeFalse())
135+
}, util.LongTimeout, util.Interval).Should(gomega.Succeed())
136+
})
137+
ginkgo.By("verifying that the job has been admitted", func() {
138+
wlLookupKey := types.NamespacedName{Name: workloadjob.GetWorkloadNameForJob(testJob.Name, testJob.UID), Namespace: ns.Name}
139+
createdWorkload := &kueue.Workload{}
140+
gomega.Eventually(func(g gomega.Gomega) {
141+
g.Expect(k8sClient.Get(ctx, wlLookupKey, createdWorkload)).Should(gomega.Succeed())
142+
g.Expect(createdWorkload.Status.Admission).ShouldNot(gomega.BeNil())
143+
}, util.LongTimeout, util.Interval).Should(gomega.Succeed())
144+
})
145+
})
146+
})
147+
})

test/e2e/queuename/suite_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
Copyright 2025 The Kubernetes 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 queuename
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"os"
23+
"testing"
24+
"time"
25+
26+
"github.com/onsi/ginkgo/v2"
27+
"github.com/onsi/gomega"
28+
ctrl "sigs.k8s.io/controller-runtime"
29+
"sigs.k8s.io/controller-runtime/pkg/client"
30+
31+
visibilityv1beta1 "sigs.k8s.io/kueue/client-go/clientset/versioned/typed/visibility/v1beta1"
32+
"sigs.k8s.io/kueue/test/util"
33+
)
34+
35+
var (
36+
k8sClient client.WithWatch
37+
ctx context.Context
38+
visibilityClient visibilityv1beta1.VisibilityV1beta1Interface
39+
impersonatedVisibilityClient visibilityv1beta1.VisibilityV1beta1Interface
40+
)
41+
42+
func TestAPIs(t *testing.T) {
43+
suiteName := "End To End Queue-name handling Suite"
44+
if ver, found := os.LookupEnv("E2E_KIND_VERSION"); found {
45+
suiteName = fmt.Sprintf("%s: %s", suiteName, ver)
46+
}
47+
gomega.RegisterFailHandler(ginkgo.Fail)
48+
ginkgo.RunSpecs(t,
49+
suiteName,
50+
)
51+
}
52+
53+
var _ = ginkgo.BeforeSuite(func() {
54+
ctrl.SetLogger(util.NewTestingLogger(ginkgo.GinkgoWriter, -3))
55+
56+
k8sClient, _ = util.CreateClientUsingCluster("")
57+
visibilityClient = util.CreateVisibilityClient("")
58+
impersonatedVisibilityClient = util.CreateVisibilityClient("system:serviceaccount:kueue-system:default")
59+
ctx = context.Background()
60+
61+
waitForAvailableStart := time.Now()
62+
util.WaitForKueueAvailability(ctx, k8sClient)
63+
util.WaitForJobSetAvailability(ctx, k8sClient)
64+
ginkgo.GinkgoLogr.Info("Kueue and JobSet oprators are available in the cluster", "waitingTime", time.Since(waitForAvailableStart))
65+
})

test/util/e2e.go

+47
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"time"
78

89
"github.com/google/go-cmp/cmp/cmpopts"
910
kfmpi "github.com/kubeflow/mpi-operator/pkg/apis/kubeflow/v2beta1"
@@ -21,6 +22,9 @@ import (
2122
"sigs.k8s.io/controller-runtime/pkg/client/config"
2223
jobset "sigs.k8s.io/jobset/api/jobset/v1alpha2"
2324
leaderworkersetv1 "sigs.k8s.io/lws/api/leaderworkerset/v1"
25+
"sigs.k8s.io/yaml"
26+
27+
configapi "sigs.k8s.io/kueue/apis/config/v1beta1"
2428

2529
kueuealpha "sigs.k8s.io/kueue/apis/kueue/v1alpha1"
2630
kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1"
@@ -112,6 +116,21 @@ func CreateVisibilityClient(user string) visibilityv1beta1.VisibilityV1beta1Inte
112116
return visibilityClient
113117
}
114118

119+
func rolloutOperatorDeployment(ctx context.Context, k8sClient client.Client, key types.NamespacedName) {
120+
deployment := &appsv1.Deployment{}
121+
gomega.EventuallyWithOffset(2, func(g gomega.Gomega) {
122+
g.Expect(k8sClient.Get(ctx, key, deployment)).To(gomega.Succeed())
123+
deployment.Spec.Template.ObjectMeta.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
124+
g.Expect(k8sClient.Update(ctx, deployment)).To(gomega.Succeed())
125+
126+
// Ensure that the rollout has started by waiting for the deployment to be unavailable
127+
g.Expect(deployment.Status.Conditions).To(gomega.ContainElement(gomega.BeComparableTo(
128+
appsv1.DeploymentCondition{Type: appsv1.DeploymentAvailable, Status: corev1.ConditionFalse},
129+
cmpopts.IgnoreFields(appsv1.DeploymentCondition{}, "Reason", "Message", "LastUpdateTime", "LastTransitionTime")),
130+
))
131+
}, StartUpTimeout, Interval).Should(gomega.Succeed())
132+
}
133+
115134
func waitForOperatorAvailability(ctx context.Context, k8sClient client.Client, key types.NamespacedName) {
116135
deployment := &appsv1.Deployment{}
117136
pods := &corev1.PodList{}
@@ -171,3 +190,31 @@ func WaitForKubeRayOperatorAvailability(ctx context.Context, k8sClient client.Cl
171190
kroKey := types.NamespacedName{Namespace: "ray-system", Name: "kuberay-operator"}
172191
waitForOperatorAvailability(ctx, k8sClient, kroKey)
173192
}
193+
194+
func GetKueueConfiguration(ctx context.Context, k8sClient client.Client) *configapi.Configuration {
195+
var kueueCfg configapi.Configuration
196+
kcmKey := types.NamespacedName{Namespace: "kueue-system", Name: "kueue-manager-config"}
197+
configMap := &corev1.ConfigMap{}
198+
199+
gomega.Expect(k8sClient.Get(ctx, kcmKey, configMap)).To(gomega.Succeed())
200+
gomega.Expect(yaml.Unmarshal([]byte(configMap.Data["controller_manager_config.yaml"]), &kueueCfg)).To(gomega.Succeed())
201+
return &kueueCfg
202+
}
203+
204+
func ApplyKueueConfiguration(ctx context.Context, k8sClient client.Client, kueueCfg *configapi.Configuration) {
205+
configMap := &corev1.ConfigMap{}
206+
kcmKey := types.NamespacedName{Namespace: "kueue-system", Name: "kueue-manager-config"}
207+
config, _ := yaml.Marshal(kueueCfg)
208+
209+
gomega.Eventually(func(g gomega.Gomega) {
210+
gomega.Expect(k8sClient.Get(ctx, kcmKey, configMap)).To(gomega.Succeed())
211+
configMap.Data["controller_manager_config.yaml"] = string(config)
212+
g.Expect(k8sClient.Update(ctx, configMap)).To(gomega.Succeed())
213+
}, Timeout, Interval).Should(gomega.Succeed())
214+
}
215+
216+
func RestartKueueController(ctx context.Context, k8sClient client.Client) {
217+
kcmKey := types.NamespacedName{Namespace: "kueue-system", Name: "kueue-controller-manager"}
218+
rolloutOperatorDeployment(ctx, k8sClient, kcmKey)
219+
waitForOperatorAvailability(ctx, k8sClient, kcmKey)
220+
}

0 commit comments

Comments
 (0)