sigs.k8s.io/kueue@v0.6.2/pkg/controller/jobframework/setup_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 jobframework 18 19 import ( 20 "context" 21 "net/http" 22 "testing" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 kubeflow "github.com/kubeflow/mpi-operator/pkg/apis/kubeflow/v2beta1" 27 kftraining "github.com/kubeflow/training-operator/pkg/apis/kubeflow.org/v1" 28 rayv1 "github.com/ray-project/kuberay/ray-operator/apis/ray/v1" 29 rayjobapi "github.com/ray-project/kuberay/ray-operator/apis/ray/v1alpha1" 30 batchv1 "k8s.io/api/batch/v1" 31 corev1 "k8s.io/api/core/v1" 32 apimeta "k8s.io/apimachinery/pkg/api/meta" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/runtime/schema" 35 "k8s.io/client-go/rest" 36 "k8s.io/klog/v2" 37 "sigs.k8s.io/controller-runtime/pkg/client" 38 ctrlmgr "sigs.k8s.io/controller-runtime/pkg/manager" 39 jobset "sigs.k8s.io/jobset/api/jobset/v1alpha2" 40 41 configapi "sigs.k8s.io/kueue/apis/config/v1beta1" 42 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 43 "sigs.k8s.io/kueue/pkg/util/slices" 44 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 45 ) 46 47 func TestSetupControllers(t *testing.T) { 48 cases := map[string]struct { 49 opts []Option 50 mapperGVKs []schema.GroupVersionKind 51 wantError error 52 }{ 53 "setup controllers succeed": { 54 opts: []Option{ 55 WithEnabledFrameworks(&configapi.Integrations{ 56 Frameworks: []string{"batch/job", "kubeflow.org/mpijob"}, 57 }), 58 }, 59 mapperGVKs: []schema.GroupVersionKind{ 60 batchv1.SchemeGroupVersion.WithKind("Job"), 61 kubeflow.SchemeGroupVersionKind, 62 }, 63 }, 64 "mapper doesn't have kubeflow.org/mpijob, but no error occur": { 65 opts: []Option{ 66 WithEnabledFrameworks(&configapi.Integrations{ 67 Frameworks: []string{"batch/job", "kubeflow.org/mpijob"}, 68 }), 69 }, 70 mapperGVKs: []schema.GroupVersionKind{ 71 batchv1.SchemeGroupVersion.WithKind("Job"), 72 }, 73 }, 74 } 75 for name, tc := range cases { 76 t.Run(name, func(t *testing.T) { 77 _, logger := utiltesting.ContextWithLog(t) 78 k8sClient := utiltesting.NewClientBuilder(jobset.AddToScheme, kubeflow.AddToScheme, rayjobapi.AddToScheme, kftraining.AddToScheme, rayv1.AddToScheme).Build() 79 80 mgrOpts := ctrlmgr.Options{ 81 Scheme: k8sClient.Scheme(), 82 NewClient: func(*rest.Config, client.Options) (client.Client, error) { 83 return k8sClient, nil 84 }, 85 MapperProvider: func(*rest.Config, *http.Client) (apimeta.RESTMapper, error) { 86 gvs := make([]schema.GroupVersion, len(tc.mapperGVKs)) 87 for _, gvk := range tc.mapperGVKs { 88 gvs = append(gvs, gvk.GroupVersion()) 89 } 90 mapper := apimeta.NewDefaultRESTMapper(gvs) 91 for _, gvk := range tc.mapperGVKs { 92 mapper.Add(gvk, apimeta.RESTScopeNamespace) 93 } 94 return mapper, nil 95 }, 96 } 97 mgr, err := ctrlmgr.New(&rest.Config{}, mgrOpts) 98 if err != nil { 99 t.Fatalf("Failed to setup manager: %v", err) 100 } 101 102 gotError := SetupControllers(mgr, logger, tc.opts...) 103 if diff := cmp.Diff(tc.wantError, gotError, cmpopts.EquateErrors()); len(diff) != 0 { 104 t.Errorf("Unexpected error from SetupControllers (-want,+got):\n%s", diff) 105 } 106 }) 107 } 108 } 109 110 func TestSetupIndexes(t *testing.T) { 111 testNamespace := "test" 112 113 cases := map[string]struct { 114 opts []Option 115 workloads []kueue.Workload 116 filter client.ListOption 117 wantError error 118 wantFieldMatcherError bool 119 wantWorkloads []string 120 }{ 121 "proper indexes are set": { 122 workloads: []kueue.Workload{ 123 *utiltesting.MakeWorkload("alpha-wl", testNamespace). 124 ControllerReference(batchv1.SchemeGroupVersion.WithKind("Job"), "alpha", "job"). 125 Obj(), 126 *utiltesting.MakeWorkload("beta-wl", testNamespace). 127 ControllerReference(batchv1.SchemeGroupVersion.WithKind("Job"), "beta", "job"). 128 Obj(), 129 }, 130 opts: []Option{ 131 WithEnabledFrameworks(&configapi.Integrations{ 132 Frameworks: []string{"batch/job"}, 133 }), 134 }, 135 filter: client.MatchingFields{GetOwnerKey(batchv1.SchemeGroupVersion.WithKind("Job")): "alpha"}, 136 wantWorkloads: []string{"alpha-wl"}, 137 }, 138 "kubeflow.org/mpijob is disabled in the configAPI": { 139 workloads: []kueue.Workload{ 140 *utiltesting.MakeWorkload("alpha-wl", testNamespace). 141 ControllerReference(kubeflow.SchemeGroupVersionKind, "alpha", "mpijob"). 142 Obj(), 143 *utiltesting.MakeWorkload("beta-wl", testNamespace). 144 ControllerReference(kubeflow.SchemeGroupVersionKind, "beta", "mpijob"). 145 Obj(), 146 }, 147 opts: []Option{ 148 WithEnabledFrameworks(&configapi.Integrations{ 149 Frameworks: []string{"batch/job"}, 150 }), 151 }, 152 filter: client.MatchingFields{GetOwnerKey(kubeflow.SchemeGroupVersionKind): "alpha"}, 153 wantFieldMatcherError: true, 154 }, 155 } 156 for name, tc := range cases { 157 t.Run(name, func(t *testing.T) { 158 ctx, cancel := context.WithCancel(context.Background()) 159 defer cancel() 160 161 builder := utiltesting.NewClientBuilder().WithObjects(&corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testNamespace}}) 162 gotIndexerErr := SetupIndexes(ctx, utiltesting.AsIndexer(builder), tc.opts...) 163 if diff := cmp.Diff(tc.wantError, gotIndexerErr, cmpopts.EquateErrors()); len(diff) != 0 { 164 t.Fatalf("Unexpected setupIndexer error (-want,+got):\n%s", diff) 165 } 166 k8sClient := builder.Build() 167 for _, wl := range tc.workloads { 168 if err := k8sClient.Create(ctx, &wl); err != nil { 169 t.Fatalf("Unable to create workload, %q: %v", klog.KObj(&wl), err) 170 } 171 } 172 173 // In any case, a list operation without fieldMatcher should succeed. 174 gotWls := &kueue.WorkloadList{} 175 if gotListErr := k8sClient.List(ctx, gotWls, client.InNamespace(testNamespace)); gotListErr != nil { 176 t.Fatalf("Failed to list workloads without a fieldMatcher: %v", gotListErr) 177 } 178 deployedWlNames := slices.Map(tc.workloads, func(j *kueue.Workload) string { return j.Name }) 179 gotWlNames := slices.Map(gotWls.Items, func(j *kueue.Workload) string { return j.Name }) 180 if diff := cmp.Diff(deployedWlNames, gotWlNames, cmpopts.EquateEmpty(), 181 cmpopts.SortSlices(func(a, b string) bool { return a < b })); len(diff) != 0 { 182 t.Errorf("Unexpected list workloads (-want,+got):\n%s", diff) 183 } 184 185 // List workloads with fieldMatcher. 186 gotListErr := k8sClient.List(ctx, gotWls, client.InNamespace(testNamespace), tc.filter) 187 if (gotListErr != nil) != tc.wantFieldMatcherError { 188 t.Errorf("Unexpected list error\nwant: %v\ngot: %v", tc.wantFieldMatcherError, gotListErr) 189 } 190 191 if !tc.wantFieldMatcherError { 192 gotWlNames = slices.Map(gotWls.Items, func(j *kueue.Workload) string { return j.Name }) 193 if diff := cmp.Diff(tc.wantWorkloads, gotWlNames, cmpopts.EquateEmpty(), 194 cmpopts.SortSlices(func(a, b string) bool { return a < b })); len(diff) != 0 { 195 t.Errorf("Unexpected list workloads (-want,+got):\n%s", diff) 196 } 197 } 198 }) 199 } 200 }