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  }