sigs.k8s.io/kueue@v0.6.2/pkg/controller/admissionchecks/provisioning/indexer_test.go (about) 1 /* 2 Copyright 2023 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 provisioning 18 19 import ( 20 "context" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "github.com/google/go-cmp/cmp/cmpopts" 25 corev1 "k8s.io/api/core/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/apimachinery/pkg/runtime" 28 autoscaling "k8s.io/autoscaler/cluster-autoscaler/apis/provisioningrequest/autoscaling.x-k8s.io/v1beta1" 29 clientgoscheme "k8s.io/client-go/kubernetes/scheme" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 "sigs.k8s.io/controller-runtime/pkg/client/fake" 32 33 kueue "sigs.k8s.io/kueue/apis/kueue/v1beta1" 34 "sigs.k8s.io/kueue/pkg/util/slices" 35 utiltesting "sigs.k8s.io/kueue/pkg/util/testing" 36 ) 37 38 const ( 39 TestNamespace = "ns" 40 ) 41 42 func getClientBuilder() (*fake.ClientBuilder, context.Context) { 43 scheme := runtime.NewScheme() 44 if err := clientgoscheme.AddToScheme(scheme); err != nil { 45 panic(err) 46 } 47 if err := kueue.AddToScheme(scheme); err != nil { 48 panic(err) 49 } 50 51 if err := autoscaling.AddToScheme(scheme); err != nil { 52 panic(err) 53 } 54 55 ctx := context.Background() 56 builder := fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.Namespace{ 57 ObjectMeta: metav1.ObjectMeta{ 58 Name: TestNamespace, 59 }, 60 }) 61 _ = SetupIndexer(ctx, utiltesting.AsIndexer(builder)) 62 return builder, ctx 63 } 64 65 func TestIndexProvisioningRequests(t *testing.T) { 66 cases := map[string]struct { 67 requests []*autoscaling.ProvisioningRequest 68 filter client.ListOption 69 wantListError error 70 wantList []string 71 }{ 72 "no owner": { 73 requests: []*autoscaling.ProvisioningRequest{ 74 { 75 ObjectMeta: metav1.ObjectMeta{ 76 Namespace: "default", 77 Name: "name", 78 }, 79 }, 80 }, 81 filter: client.MatchingFields{RequestsOwnedByWorkloadKey: "wl"}, 82 }, 83 "single owner, single match": { 84 requests: []*autoscaling.ProvisioningRequest{ 85 { 86 ObjectMeta: metav1.ObjectMeta{ 87 Namespace: "default", 88 Name: "name", 89 }, 90 }, 91 { 92 ObjectMeta: metav1.ObjectMeta{ 93 Namespace: "default", 94 Name: "name2", 95 OwnerReferences: []metav1.OwnerReference{ 96 { 97 Name: "wl", 98 }, 99 }, 100 }, 101 }, 102 }, 103 filter: client.MatchingFields{RequestsOwnedByWorkloadKey: "wl"}, 104 wantList: []string{"name2"}, 105 }, 106 "multiple owners, multiple matches": { 107 requests: []*autoscaling.ProvisioningRequest{ 108 { 109 ObjectMeta: metav1.ObjectMeta{ 110 Namespace: "default", 111 Name: "name", 112 }, 113 }, 114 { 115 ObjectMeta: metav1.ObjectMeta{ 116 Namespace: "default", 117 Name: "name2", 118 OwnerReferences: []metav1.OwnerReference{ 119 { 120 Name: "wl", 121 }, 122 }, 123 }, 124 }, 125 { 126 ObjectMeta: metav1.ObjectMeta{ 127 Namespace: "default", 128 Name: "name3", 129 OwnerReferences: []metav1.OwnerReference{ 130 { 131 Name: "wl_2", 132 }, 133 { 134 Name: "wl", 135 }, 136 }, 137 }, 138 }, 139 }, 140 filter: client.MatchingFields{RequestsOwnedByWorkloadKey: "wl"}, 141 wantList: []string{"name2", "name3"}, 142 }, 143 } 144 for name, tc := range cases { 145 t.Run(name, func(t *testing.T) { 146 builder, ctx := getClientBuilder() 147 k8sclient := builder.Build() 148 for _, req := range tc.requests { 149 if err := k8sclient.Create(ctx, req); err != nil { 150 t.Errorf("Unable to create %s request: %v", client.ObjectKeyFromObject(req), err) 151 } 152 } 153 154 lst := &autoscaling.ProvisioningRequestList{} 155 156 gotListErr := k8sclient.List(ctx, lst, client.InNamespace("default"), tc.filter) 157 if diff := cmp.Diff(tc.wantListError, gotListErr); diff != "" { 158 t.Errorf("unexpected list error (-want/+got):\n%s", diff) 159 } 160 161 gotList := slices.Map(lst.Items, func(pr *autoscaling.ProvisioningRequest) string { return pr.Name }) 162 if diff := cmp.Diff(tc.wantList, gotList, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" { 163 t.Errorf("unexpected list (-want/+got):\n%s", diff) 164 } 165 }) 166 } 167 } 168 169 func TestIndexWorkload(t *testing.T) { 170 cases := map[string]struct { 171 workloads []*kueue.Workload 172 filter client.ListOption 173 wantListError error 174 wantList []string 175 }{ 176 "no checks": { 177 workloads: []*kueue.Workload{ 178 { 179 ObjectMeta: metav1.ObjectMeta{ 180 Namespace: "default", 181 Name: "name", 182 }, 183 }, 184 }, 185 filter: client.MatchingFields{WorkloadsWithAdmissionCheckKey: "check"}, 186 }, 187 "single check, single match": { 188 workloads: []*kueue.Workload{ 189 { 190 ObjectMeta: metav1.ObjectMeta{ 191 Namespace: "default", 192 Name: "name", 193 }, 194 }, 195 { 196 ObjectMeta: metav1.ObjectMeta{ 197 Namespace: "default", 198 Name: "name2", 199 }, 200 Status: kueue.WorkloadStatus{ 201 AdmissionChecks: []kueue.AdmissionCheckState{ 202 { 203 Name: "check", 204 }, 205 }, 206 }, 207 }, 208 }, 209 filter: client.MatchingFields{WorkloadsWithAdmissionCheckKey: "check"}, 210 wantList: []string{"name2"}, 211 }, 212 "multiple checks, multiple matches": { 213 workloads: []*kueue.Workload{ 214 { 215 ObjectMeta: metav1.ObjectMeta{ 216 Namespace: "default", 217 Name: "name", 218 }, 219 }, 220 { 221 ObjectMeta: metav1.ObjectMeta{ 222 Namespace: "default", 223 Name: "name2", 224 }, 225 Status: kueue.WorkloadStatus{ 226 AdmissionChecks: []kueue.AdmissionCheckState{ 227 { 228 Name: "check", 229 }, 230 }, 231 }, 232 }, 233 { 234 ObjectMeta: metav1.ObjectMeta{ 235 Namespace: "default", 236 Name: "name3", 237 }, 238 Status: kueue.WorkloadStatus{ 239 AdmissionChecks: []kueue.AdmissionCheckState{ 240 { 241 Name: "check2", 242 }, 243 { 244 Name: "check", 245 }, 246 }, 247 }, 248 }, 249 }, 250 filter: client.MatchingFields{WorkloadsWithAdmissionCheckKey: "check"}, 251 wantList: []string{"name2", "name3"}, 252 }, 253 } 254 for name, tc := range cases { 255 t.Run(name, func(t *testing.T) { 256 builder, ctx := getClientBuilder() 257 k8sclient := builder.Build() 258 for _, wl := range tc.workloads { 259 if err := k8sclient.Create(ctx, wl); err != nil { 260 t.Errorf("Unable to create %s workload: %v", client.ObjectKeyFromObject(wl), err) 261 } 262 } 263 264 lst := &kueue.WorkloadList{} 265 266 gotListErr := k8sclient.List(ctx, lst, client.InNamespace("default"), tc.filter) 267 if diff := cmp.Diff(tc.wantListError, gotListErr); diff != "" { 268 t.Errorf("unexpected list error (-want/+got):\n%s", diff) 269 } 270 271 gotList := slices.Map(lst.Items, func(wl *kueue.Workload) string { return wl.Name }) 272 if diff := cmp.Diff(tc.wantList, gotList, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" { 273 t.Errorf("unexpected list (-want/+got):\n%s", diff) 274 } 275 }) 276 } 277 } 278 279 func TestIndexAdmissionChecks(t *testing.T) { 280 cases := map[string]struct { 281 checks []*kueue.AdmissionCheck 282 filter client.ListOption 283 wantList []string 284 }{ 285 "different controller": { 286 checks: []*kueue.AdmissionCheck{ 287 utiltesting.MakeAdmissionCheck("check1"). 288 ControllerName("other"). 289 Parameters(kueue.GroupVersion.Group, ConfigKind, "config1"). 290 Obj(), 291 }, 292 filter: client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"}, 293 }, 294 "bad ref group": { 295 checks: []*kueue.AdmissionCheck{ 296 utiltesting.MakeAdmissionCheck("check1"). 297 ControllerName(ControllerName). 298 Parameters("core", ConfigKind, "config1"). 299 Obj(), 300 }, 301 filter: client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"}, 302 }, 303 "bad ref kind": { 304 checks: []*kueue.AdmissionCheck{ 305 utiltesting.MakeAdmissionCheck("check1"). 306 ControllerName(ControllerName). 307 Parameters(kueue.GroupVersion.Group, "kind", "config1"). 308 Obj(), 309 }, 310 filter: client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"}, 311 }, 312 "empty name": { 313 checks: []*kueue.AdmissionCheck{ 314 utiltesting.MakeAdmissionCheck("check1"). 315 ControllerName(ControllerName). 316 Parameters(kueue.GroupVersion.Group, ConfigKind, ""). 317 Obj(), 318 }, 319 filter: client.MatchingFields{AdmissionCheckUsingConfigKey: ""}, 320 }, 321 "match": { 322 checks: []*kueue.AdmissionCheck{ 323 utiltesting.MakeAdmissionCheck("check1"). 324 ControllerName(ControllerName). 325 Parameters(kueue.GroupVersion.Group, ConfigKind, "config1"). 326 Obj(), 327 }, 328 filter: client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"}, 329 wantList: []string{"check1"}, 330 }, 331 "multiple checks, partial match": { 332 checks: []*kueue.AdmissionCheck{ 333 utiltesting.MakeAdmissionCheck("check1"). 334 ControllerName(ControllerName). 335 Parameters(kueue.GroupVersion.Group, ConfigKind, "config1"). 336 Obj(), 337 utiltesting.MakeAdmissionCheck("check2"). 338 ControllerName(ControllerName). 339 Parameters(kueue.GroupVersion.Group, ConfigKind, "config1"). 340 Obj(), 341 utiltesting.MakeAdmissionCheck("check3"). 342 ControllerName(ControllerName). 343 Parameters(kueue.GroupVersion.Group, ConfigKind, "config2"). 344 Obj(), 345 }, 346 filter: client.MatchingFields{AdmissionCheckUsingConfigKey: "config1"}, 347 wantList: []string{"check1", "check2"}, 348 }, 349 } 350 for name, tc := range cases { 351 t.Run(name, func(t *testing.T) { 352 builder, ctx := getClientBuilder() 353 k8sclient := builder.Build() 354 for _, req := range tc.checks { 355 if err := k8sclient.Create(ctx, req); err != nil { 356 t.Errorf("Unable to create %s request: %v", client.ObjectKeyFromObject(req), err) 357 } 358 } 359 360 lst := &kueue.AdmissionCheckList{} 361 362 gotListErr := k8sclient.List(ctx, lst, tc.filter) 363 if gotListErr != nil { 364 t.Errorf("unexpected list error:%s", gotListErr) 365 } 366 367 gotList := slices.Map(lst.Items, func(ac *kueue.AdmissionCheck) string { return ac.Name }) 368 if diff := cmp.Diff(tc.wantList, gotList, cmpopts.EquateEmpty(), cmpopts.SortSlices(func(a, b string) bool { return a < b })); diff != "" { 369 t.Errorf("unexpected list (-want/+got):\n%s", diff) 370 } 371 }) 372 } 373 }