sigs.k8s.io/kueue@v0.6.2/pkg/controller/admissionchecks/multikueue/jobset_adapter_test.go (about) 1 /* 2 Copyright 2024 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 multikueue 18 19 import ( 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/google/go-cmp/cmp/cmpopts" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/apimachinery/pkg/types" 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 "sigs.k8s.io/controller-runtime/pkg/reconcile" 28 jobset "sigs.k8s.io/jobset/api/jobset/v1alpha2" 29 30 kueuealpha "sigs.k8s.io/kueue/apis/kueue/v1alpha1" 31 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 32 "sigs.k8s.io/kueue/pkg/controller/constants" 33 "sigs.k8s.io/kueue/pkg/util/slices" 34 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 35 testingjobset "sigs.k8s.io/kueue/pkg/util/testingjobs/jobset" 36 ) 37 38 func TestWlReconcileJobset(t *testing.T) { 39 objCheckOpts := []cmp.Option{ 40 cmpopts.IgnoreFields(metav1.ObjectMeta{}, "ResourceVersion"), 41 cmpopts.EquateEmpty(), 42 cmpopts.IgnoreFields(metav1.Condition{}, "LastTransitionTime"), 43 cmpopts.IgnoreFields(kueue.AdmissionCheckState{}, "LastTransitionTime"), 44 cmpopts.SortSlices(func(a, b metav1.Condition) bool { return a.Type < b.Type }), 45 } 46 47 baseWorkloadBuilder := utiltesting.MakeWorkload("wl1", TestNamespace) 48 baseJobSetBuilder := testingjobset.MakeJobSet("jobset1", TestNamespace) 49 50 cases := map[string]struct { 51 managersWorkloads []kueue.Workload 52 managersJobSets []jobset.JobSet 53 worker1Workloads []kueue.Workload 54 worker1JobSets []jobset.JobSet 55 56 wantError error 57 wantManagersWorkloads []kueue.Workload 58 wantManagersJobsSets []jobset.JobSet 59 wantWorker1Workloads []kueue.Workload 60 wantWorker1JobSets []jobset.JobSet 61 }{ 62 "remote wl with reservation, multikueue AC is marked Ready": { 63 managersWorkloads: []kueue.Workload{ 64 *baseWorkloadBuilder.Clone(). 65 AdmissionCheck(kueue.AdmissionCheckState{Name: "ac1", State: kueue.CheckStatePending}). 66 ControllerReference(jobset.SchemeGroupVersion.WithKind("JobSet"), "jobset1", "uid1"). 67 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 68 Obj(), 69 }, 70 71 managersJobSets: []jobset.JobSet{ 72 *baseJobSetBuilder.DeepCopy().Obj(), 73 }, 74 75 worker1Workloads: []kueue.Workload{ 76 *baseWorkloadBuilder.Clone(). 77 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 78 Obj(), 79 }, 80 wantManagersWorkloads: []kueue.Workload{ 81 *baseWorkloadBuilder.Clone(). 82 AdmissionCheck(kueue.AdmissionCheckState{ 83 Name: "ac1", 84 State: kueue.CheckStateReady, 85 Message: `The workload got reservation on "worker1"`, 86 }). 87 ControllerReference(jobset.SchemeGroupVersion.WithKind("JobSet"), "jobset1", "uid1"). 88 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 89 Obj(), 90 }, 91 wantManagersJobsSets: []jobset.JobSet{ 92 *baseJobSetBuilder.DeepCopy().Obj(), 93 }, 94 95 wantWorker1Workloads: []kueue.Workload{ 96 *baseWorkloadBuilder.Clone(). 97 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 98 Obj(), 99 }, 100 wantWorker1JobSets: []jobset.JobSet{ 101 *baseJobSetBuilder.DeepCopy(). 102 Label(constants.PrebuiltWorkloadLabel, "wl1"). 103 Label(kueuealpha.MultiKueueOriginLabel, defaultOrigin). 104 Obj(), 105 }, 106 }, 107 "remote jobset status is changed, the status is copied in the local Jobset ": { 108 managersWorkloads: []kueue.Workload{ 109 *baseWorkloadBuilder.Clone(). 110 AdmissionCheck(kueue.AdmissionCheckState{ 111 Name: "ac1", 112 State: kueue.CheckStateReady, 113 Message: `The workload got reservation on "worker1"`, 114 }). 115 ControllerReference(jobset.SchemeGroupVersion.WithKind("JobSet"), "jobset1", "uid1"). 116 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 117 Obj(), 118 }, 119 120 managersJobSets: []jobset.JobSet{ 121 *baseJobSetBuilder.DeepCopy().Obj(), 122 }, 123 124 worker1JobSets: []jobset.JobSet{ 125 *baseJobSetBuilder.DeepCopy(). 126 Label(constants.PrebuiltWorkloadLabel, "wl1"). 127 JobsStatus( 128 jobset.ReplicatedJobStatus{ 129 Name: "replicated-job-1", 130 Ready: 1, 131 Succeeded: 1, 132 }, 133 jobset.ReplicatedJobStatus{ 134 Name: "replicated-job-2", 135 Ready: 3, 136 Succeeded: 0, 137 }, 138 ). 139 Obj(), 140 }, 141 142 worker1Workloads: []kueue.Workload{ 143 *baseWorkloadBuilder.Clone(). 144 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 145 Obj(), 146 }, 147 wantManagersWorkloads: []kueue.Workload{ 148 *baseWorkloadBuilder.Clone(). 149 AdmissionCheck(kueue.AdmissionCheckState{ 150 Name: "ac1", 151 State: kueue.CheckStateReady, 152 Message: `The workload got reservation on "worker1"`, 153 }). 154 ControllerReference(jobset.SchemeGroupVersion.WithKind("JobSet"), "jobset1", "uid1"). 155 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 156 Obj(), 157 }, 158 wantManagersJobsSets: []jobset.JobSet{ 159 *baseJobSetBuilder.DeepCopy(). 160 JobsStatus( 161 jobset.ReplicatedJobStatus{ 162 Name: "replicated-job-1", 163 Ready: 1, 164 Succeeded: 1, 165 }, 166 jobset.ReplicatedJobStatus{ 167 Name: "replicated-job-2", 168 Ready: 3, 169 Succeeded: 0, 170 }, 171 ). 172 Obj(), 173 }, 174 175 wantWorker1Workloads: []kueue.Workload{ 176 *baseWorkloadBuilder.Clone(). 177 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 178 Obj(), 179 }, 180 wantWorker1JobSets: []jobset.JobSet{ 181 *baseJobSetBuilder.DeepCopy(). 182 Label(constants.PrebuiltWorkloadLabel, "wl1"). 183 JobsStatus( 184 jobset.ReplicatedJobStatus{ 185 Name: "replicated-job-1", 186 Ready: 1, 187 Succeeded: 1, 188 }, 189 jobset.ReplicatedJobStatus{ 190 Name: "replicated-job-2", 191 Ready: 3, 192 Succeeded: 0, 193 }, 194 ). 195 Obj(), 196 }, 197 }, 198 "remote wl is finished, the local workload and JobSet are marked completed ": { 199 managersWorkloads: []kueue.Workload{ 200 *baseWorkloadBuilder.Clone(). 201 AdmissionCheck(kueue.AdmissionCheckState{ 202 Name: "ac1", 203 State: kueue.CheckStateReady, 204 Message: `The workload got reservation on "worker1"`, 205 }). 206 ControllerReference(jobset.SchemeGroupVersion.WithKind("JobSet"), "jobset1", "uid1"). 207 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 208 Obj(), 209 }, 210 211 managersJobSets: []jobset.JobSet{ 212 *baseJobSetBuilder.DeepCopy().Obj(), 213 }, 214 215 worker1JobSets: []jobset.JobSet{ 216 *baseJobSetBuilder.DeepCopy(). 217 Label(constants.PrebuiltWorkloadLabel, "wl1"). 218 Condition(metav1.Condition{Type: string(jobset.JobSetCompleted), Status: metav1.ConditionTrue}). 219 Obj(), 220 }, 221 222 worker1Workloads: []kueue.Workload{ 223 *baseWorkloadBuilder.Clone(). 224 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 225 Condition(metav1.Condition{Type: kueue.WorkloadFinished, Status: metav1.ConditionTrue, Reason: "ByTest", Message: "by test"}). 226 Obj(), 227 }, 228 wantManagersWorkloads: []kueue.Workload{ 229 *baseWorkloadBuilder.Clone(). 230 AdmissionCheck(kueue.AdmissionCheckState{ 231 Name: "ac1", 232 State: kueue.CheckStateReady, 233 Message: `The workload got reservation on "worker1"`, 234 }). 235 ControllerReference(jobset.SchemeGroupVersion.WithKind("JobSet"), "jobset1", "uid1"). 236 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 237 Condition(metav1.Condition{Type: kueue.WorkloadFinished, Status: metav1.ConditionTrue, Reason: "ByTest", Message: `by test`}). 238 Obj(), 239 }, 240 wantManagersJobsSets: []jobset.JobSet{ 241 *baseJobSetBuilder.DeepCopy(). 242 Condition(metav1.Condition{Type: string(jobset.JobSetCompleted), Status: metav1.ConditionTrue}). 243 Obj(), 244 }, 245 246 wantWorker1Workloads: []kueue.Workload{ 247 *baseWorkloadBuilder.Clone(). 248 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 249 Condition(metav1.Condition{Type: kueue.WorkloadFinished, Status: metav1.ConditionTrue, Reason: "ByTest", Message: "by test"}). 250 Obj(), 251 }, 252 wantWorker1JobSets: []jobset.JobSet{ 253 *baseJobSetBuilder.DeepCopy(). 254 Label(constants.PrebuiltWorkloadLabel, "wl1"). 255 Condition(metav1.Condition{Type: string(jobset.JobSetCompleted), Status: metav1.ConditionTrue}). 256 Obj(), 257 }, 258 }, 259 "the local JobSet is marked finished, the remote objects are removed": { 260 managersWorkloads: []kueue.Workload{ 261 *baseWorkloadBuilder.Clone(). 262 AdmissionCheck(kueue.AdmissionCheckState{ 263 Name: "ac1", 264 State: kueue.CheckStateReady, 265 Message: `The workload got reservation on "worker1"`, 266 }). 267 ControllerReference(jobset.SchemeGroupVersion.WithKind("JobSet"), "jobset1", "uid1"). 268 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 269 Condition(metav1.Condition{Type: kueue.WorkloadFinished, Status: metav1.ConditionTrue, Reason: "ByTest", Message: `by test`}). 270 Obj(), 271 }, 272 273 managersJobSets: []jobset.JobSet{ 274 *baseJobSetBuilder.DeepCopy(). 275 Condition(metav1.Condition{Type: string(jobset.JobSetCompleted), Status: metav1.ConditionTrue}). 276 Obj(), 277 }, 278 279 worker1JobSets: []jobset.JobSet{ 280 *baseJobSetBuilder.DeepCopy(). 281 Label(constants.PrebuiltWorkloadLabel, "wl1"). 282 Condition(metav1.Condition{Type: string(jobset.JobSetCompleted), Status: metav1.ConditionTrue}). 283 Obj(), 284 }, 285 286 worker1Workloads: []kueue.Workload{ 287 *baseWorkloadBuilder.Clone(). 288 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 289 Condition(metav1.Condition{Type: kueue.WorkloadFinished, Status: metav1.ConditionTrue, Reason: "ByTest", Message: "by test"}). 290 Obj(), 291 }, 292 wantManagersWorkloads: []kueue.Workload{ 293 *baseWorkloadBuilder.Clone(). 294 AdmissionCheck(kueue.AdmissionCheckState{ 295 Name: "ac1", 296 State: kueue.CheckStateReady, 297 Message: `The workload got reservation on "worker1"`, 298 }). 299 ControllerReference(jobset.SchemeGroupVersion.WithKind("JobSet"), "jobset1", "uid1"). 300 ReserveQuota(utiltesting.MakeAdmission("q1").Obj()). 301 Condition(metav1.Condition{Type: kueue.WorkloadFinished, Status: metav1.ConditionTrue, Reason: "ByTest", Message: `by test`}). 302 Obj(), 303 }, 304 wantManagersJobsSets: []jobset.JobSet{ 305 *baseJobSetBuilder.DeepCopy(). 306 Condition(metav1.Condition{Type: string(jobset.JobSetCompleted), Status: metav1.ConditionTrue}). 307 Obj(), 308 }, 309 }, 310 } 311 312 for name, tc := range cases { 313 t.Run(name, func(t *testing.T) { 314 manageBuilder, ctx := getClientBuilder() 315 316 manageBuilder = manageBuilder.WithLists(&kueue.WorkloadList{Items: tc.managersWorkloads}, &jobset.JobSetList{Items: tc.managersJobSets}) 317 manageBuilder = manageBuilder.WithStatusSubresource(slices.Map(tc.managersWorkloads, func(w *kueue.Workload) client.Object { return w })...) 318 manageBuilder = manageBuilder.WithStatusSubresource(slices.Map(tc.managersJobSets, func(w *jobset.JobSet) client.Object { return w })...) 319 manageBuilder = manageBuilder.WithObjects( 320 utiltesting.MakeMultiKueueConfig("config1").Clusters("worker1").Obj(), 321 utiltesting.MakeAdmissionCheck("ac1").ControllerName(ControllerName). 322 Parameters(kueuealpha.GroupVersion.Group, "MultiKueueConfig", "config1"). 323 Obj(), 324 ) 325 326 managerClient := manageBuilder.Build() 327 328 cRec := newClustersReconciler(managerClient, TestNamespace, 0, defaultOrigin) 329 330 worker1Builder, _ := getClientBuilder() 331 worker1Builder = worker1Builder.WithLists(&kueue.WorkloadList{Items: tc.worker1Workloads}, &jobset.JobSetList{Items: tc.worker1JobSets}) 332 worker1Client := worker1Builder.Build() 333 334 w1remoteClient := newRemoteClient(managerClient, nil, nil, defaultOrigin, "") 335 w1remoteClient.client = worker1Client 336 337 cRec.remoteClients["worker1"] = w1remoteClient 338 339 helper, _ := newMultiKueueStoreHelper(managerClient) 340 reconciler := newWlReconciler(managerClient, helper, cRec, defaultOrigin) 341 342 _, gotErr := reconciler.Reconcile(ctx, reconcile.Request{NamespacedName: types.NamespacedName{Name: "wl1", Namespace: TestNamespace}}) 343 if gotErr != nil { 344 t.Errorf("unexpected error: %s", gotErr) 345 } 346 347 gotManagersWokloads := &kueue.WorkloadList{} 348 err := managerClient.List(ctx, gotManagersWokloads) 349 if err != nil { 350 t.Error("unexpected list managers workloads error") 351 } 352 353 if diff := cmp.Diff(tc.wantManagersWorkloads, gotManagersWokloads.Items, objCheckOpts...); diff != "" { 354 t.Errorf("unexpected manangers workloads (-want/+got):\n%s", diff) 355 } 356 357 gotWorker1Wokloads := &kueue.WorkloadList{} 358 err = worker1Client.List(ctx, gotWorker1Wokloads) 359 if err != nil { 360 t.Error("unexpected list managers workloads error") 361 } 362 363 if diff := cmp.Diff(tc.wantWorker1Workloads, gotWorker1Wokloads.Items, objCheckOpts...); diff != "" { 364 t.Errorf("unexpected manangers workloads (-want/+got):\n%s", diff) 365 } 366 gotManagersJobs := &jobset.JobSetList{} 367 err = managerClient.List(ctx, gotManagersJobs) 368 if err != nil { 369 t.Error("unexpected list managers jobs error") 370 } 371 372 if diff := cmp.Diff(tc.wantManagersJobsSets, gotManagersJobs.Items, objCheckOpts...); diff != "" { 373 t.Errorf("unexpected manangers jobs (-want/+got):\n%s", diff) 374 } 375 376 gotWorker1Job := &jobset.JobSetList{} 377 err = worker1Client.List(ctx, gotWorker1Job) 378 if err != nil { 379 t.Error("unexpected list managers jobs error") 380 } 381 382 if diff := cmp.Diff(tc.wantWorker1JobSets, gotWorker1Job.Items, objCheckOpts...); diff != "" { 383 t.Errorf("unexpected worker1 jobs (-want/+got):\n%s", diff) 384 } 385 }) 386 } 387 }