sigs.k8s.io/kueue@v0.6.2/pkg/controller/jobframework/integrationmanager_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 jobframework 18 19 import ( 20 "context" 21 "errors" 22 "reflect" 23 "testing" 24 25 "github.com/google/go-cmp/cmp" 26 "github.com/google/go-cmp/cmp/cmpopts" 27 corev1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/runtime" 30 "k8s.io/client-go/tools/record" 31 ctrl "sigs.k8s.io/controller-runtime" 32 "sigs.k8s.io/controller-runtime/pkg/client" 33 ) 34 35 func testNewReconciler(client.Client, record.EventRecorder, ...Option) JobReconcilerInterface { 36 return nil 37 } 38 39 func testSetupWebhook(ctrl.Manager, ...Option) error { 40 return nil 41 } 42 43 func testSetupIndexes(context.Context, client.FieldIndexer) error { 44 return nil 45 } 46 47 func testAddToScheme(*runtime.Scheme) error { 48 return nil 49 } 50 51 func testCanSupportIntegration(...Option) (bool, error) { 52 return true, nil 53 } 54 55 var ( 56 testIntegrationCallbacks = IntegrationCallbacks{ 57 NewReconciler: testNewReconciler, 58 SetupWebhook: testSetupWebhook, 59 JobType: &corev1.Pod{}, 60 SetupIndexes: testSetupIndexes, 61 AddToScheme: testAddToScheme, 62 CanSupportIntegration: testCanSupportIntegration, 63 } 64 ) 65 66 func TestRegister(t *testing.T) { 67 cases := map[string]struct { 68 manager *integrationManager 69 integrationName string 70 integrationCallbacks IntegrationCallbacks 71 wantError error 72 wantList []string 73 wantCallbacks IntegrationCallbacks 74 }{ 75 "successful": { 76 manager: &integrationManager{ 77 names: []string{"oldFramework"}, 78 integrations: map[string]IntegrationCallbacks{ 79 "oldFramework": testIntegrationCallbacks, 80 }, 81 }, 82 integrationName: "newFramework", 83 integrationCallbacks: testIntegrationCallbacks, 84 wantError: nil, 85 wantList: []string{"newFramework", "oldFramework"}, 86 wantCallbacks: testIntegrationCallbacks, 87 }, 88 "duplicate name": { 89 manager: &integrationManager{ 90 names: []string{"newFramework"}, 91 integrations: map[string]IntegrationCallbacks{ 92 "newFramework": testIntegrationCallbacks, 93 }, 94 }, 95 integrationName: "newFramework", 96 integrationCallbacks: IntegrationCallbacks{}, 97 wantError: errDuplicateFrameworkName, 98 wantList: []string{"newFramework"}, 99 wantCallbacks: testIntegrationCallbacks, 100 }, 101 "missing NewReconciler": { 102 manager: &integrationManager{}, 103 integrationName: "newFramework", 104 integrationCallbacks: IntegrationCallbacks{ 105 SetupWebhook: testSetupWebhook, 106 JobType: &corev1.Pod{}, 107 SetupIndexes: testSetupIndexes, 108 AddToScheme: testAddToScheme, 109 CanSupportIntegration: testCanSupportIntegration, 110 }, 111 wantError: errMissingMandatoryField, 112 wantList: []string{}, 113 }, 114 "missing SetupWebhook": { 115 manager: &integrationManager{}, 116 integrationName: "newFramework", 117 integrationCallbacks: IntegrationCallbacks{ 118 NewReconciler: testNewReconciler, 119 JobType: &corev1.Pod{}, 120 SetupIndexes: testSetupIndexes, 121 AddToScheme: testAddToScheme, 122 CanSupportIntegration: testCanSupportIntegration, 123 }, 124 wantError: errMissingMandatoryField, 125 wantList: []string{}, 126 }, 127 "missing JobType": { 128 manager: &integrationManager{}, 129 integrationName: "newFramework", 130 integrationCallbacks: IntegrationCallbacks{ 131 NewReconciler: testNewReconciler, 132 SetupWebhook: testSetupWebhook, 133 SetupIndexes: testSetupIndexes, 134 AddToScheme: testAddToScheme, 135 CanSupportIntegration: testCanSupportIntegration, 136 }, 137 wantError: errMissingMandatoryField, 138 wantList: []string{}, 139 }, 140 "missing SetupIndexes": { 141 manager: &integrationManager{}, 142 integrationName: "newFramework", 143 integrationCallbacks: IntegrationCallbacks{ 144 NewReconciler: testNewReconciler, 145 SetupWebhook: testSetupWebhook, 146 JobType: &corev1.Pod{}, 147 AddToScheme: testAddToScheme, 148 CanSupportIntegration: testCanSupportIntegration, 149 }, 150 wantError: nil, 151 wantList: []string{"newFramework"}, 152 wantCallbacks: IntegrationCallbacks{ 153 NewReconciler: testNewReconciler, 154 SetupWebhook: testSetupWebhook, 155 JobType: &corev1.Pod{}, 156 AddToScheme: testAddToScheme, 157 }, 158 }, 159 "missing AddToScheme": { 160 manager: &integrationManager{}, 161 integrationName: "newFramework", 162 integrationCallbacks: IntegrationCallbacks{ 163 NewReconciler: testNewReconciler, 164 SetupWebhook: testSetupWebhook, 165 JobType: &corev1.Pod{}, 166 SetupIndexes: testSetupIndexes, 167 CanSupportIntegration: testCanSupportIntegration, 168 }, 169 wantError: nil, 170 wantList: []string{"newFramework"}, 171 wantCallbacks: IntegrationCallbacks{ 172 NewReconciler: testNewReconciler, 173 SetupWebhook: testSetupWebhook, 174 JobType: &corev1.Pod{}, 175 SetupIndexes: testSetupIndexes, 176 }, 177 }, 178 "missing CanSupportIntegration": { 179 manager: &integrationManager{}, 180 integrationName: "newFramework", 181 integrationCallbacks: IntegrationCallbacks{ 182 NewReconciler: testNewReconciler, 183 AddToScheme: testAddToScheme, 184 SetupWebhook: testSetupWebhook, 185 JobType: &corev1.Pod{}, 186 SetupIndexes: testSetupIndexes, 187 }, 188 wantError: nil, 189 wantList: []string{"newFramework"}, 190 wantCallbacks: IntegrationCallbacks{ 191 NewReconciler: testNewReconciler, 192 AddToScheme: testAddToScheme, 193 SetupWebhook: testSetupWebhook, 194 JobType: &corev1.Pod{}, 195 SetupIndexes: testSetupIndexes, 196 }, 197 }, 198 } 199 200 for tcName, tc := range cases { 201 t.Run(tcName, func(t *testing.T) { 202 gotError := tc.manager.register(tc.integrationName, tc.integrationCallbacks) 203 if diff := cmp.Diff(tc.wantError, gotError, cmpopts.EquateErrors()); diff != "" { 204 t.Errorf("Unexpected error (-want +got):\n%s", diff) 205 } 206 gotList := tc.manager.getList() 207 if diff := cmp.Diff(tc.wantList, gotList); diff != "" { 208 t.Errorf("Unexpected frameworks list (-want +got):\n%s", diff) 209 } 210 211 if gotCallbacks, found := tc.manager.get(tc.integrationName); found { 212 if diff := cmp.Diff(tc.wantCallbacks, gotCallbacks, cmp.FilterValues(func(_, _ interface{}) bool { return true }, cmp.Comparer(compareCallbacks))); diff != "" { 213 t.Errorf("Unexpected callbacks (-want +got):\n%s", diff) 214 } 215 } 216 }) 217 } 218 } 219 220 func compareCallbacks(x, y interface{}) bool { 221 xcb := x.(IntegrationCallbacks) 222 ycb := y.(IntegrationCallbacks) 223 224 if reflect.ValueOf(xcb.NewReconciler).Pointer() != reflect.ValueOf(ycb.NewReconciler).Pointer() { 225 return false 226 } 227 if reflect.ValueOf(xcb.SetupWebhook).Pointer() != reflect.ValueOf(ycb.SetupWebhook).Pointer() { 228 return false 229 } 230 if reflect.TypeOf(xcb.JobType) != reflect.TypeOf(ycb.JobType) { 231 return false 232 } 233 if reflect.ValueOf(xcb.SetupIndexes).Pointer() != reflect.ValueOf(ycb.SetupIndexes).Pointer() { 234 return false 235 } 236 return reflect.ValueOf(xcb.AddToScheme).Pointer() == reflect.ValueOf(ycb.AddToScheme).Pointer() 237 } 238 239 func TestForEach(t *testing.T) { 240 foeEachError := errors.New("test error") 241 cases := map[string]struct { 242 registered []string 243 errorOn string 244 wantCalls []string 245 wantError error 246 }{ 247 "all": { 248 registered: []string{"a", "b", "c", "d", "e"}, 249 errorOn: "", 250 wantCalls: []string{"a", "b", "c", "d", "e"}, 251 wantError: nil, 252 }, 253 "partial": { 254 registered: []string{"a", "b", "c", "d", "e"}, 255 errorOn: "c", 256 wantCalls: []string{"a", "b", "c"}, 257 wantError: foeEachError, 258 }, 259 } 260 261 for tcName, tc := range cases { 262 t.Run(tcName, func(t *testing.T) { 263 manager := integrationManager{} 264 for _, name := range tc.registered { 265 if err := manager.register(name, testIntegrationCallbacks); err != nil { 266 t.Fatalf("unable to register %s, %s", name, err.Error()) 267 } 268 } 269 270 gotCalls := []string{} 271 gotError := manager.forEach(func(name string, cb IntegrationCallbacks) error { 272 gotCalls = append(gotCalls, name) 273 if name == tc.errorOn { 274 return foeEachError 275 } 276 return nil 277 }) 278 279 if diff := cmp.Diff(tc.wantError, gotError, cmpopts.EquateErrors()); diff != "" { 280 t.Errorf("Unexpected error (-want +got):\n%s", diff) 281 } 282 if diff := cmp.Diff(tc.wantCalls, gotCalls); diff != "" { 283 t.Errorf("Unexpected calls list (-want +got):\n%s", diff) 284 } 285 }) 286 } 287 } 288 289 func TestGetCallbacksForOwner(t *testing.T) { 290 dontManage := IntegrationCallbacks{ 291 NewReconciler: func(client.Client, record.EventRecorder, ...Option) JobReconcilerInterface { 292 panic("not implemented") 293 }, 294 SetupWebhook: func(ctrl.Manager, ...Option) error { panic("not implemented") }, 295 JobType: nil, 296 } 297 manageK1 := func() IntegrationCallbacks { 298 ret := dontManage 299 ret.IsManagingObjectsOwner = func(owner *metav1.OwnerReference) bool { return owner.Kind == "K1" } 300 return ret 301 }() 302 manageK2 := func() IntegrationCallbacks { 303 ret := dontManage 304 ret.IsManagingObjectsOwner = func(owner *metav1.OwnerReference) bool { return owner.Kind == "K2" } 305 return ret 306 }() 307 308 mgr := integrationManager{ 309 names: []string{"manageK1", "dontManage", "manageK2"}, 310 integrations: map[string]IntegrationCallbacks{ 311 "dontManage": dontManage, 312 "manageK1": manageK1, 313 "manageK2": manageK2, 314 }, 315 } 316 317 cases := map[string]struct { 318 owner *metav1.OwnerReference 319 wantCallbacks *IntegrationCallbacks 320 }{ 321 "K1": { 322 owner: &metav1.OwnerReference{Kind: "K1"}, 323 wantCallbacks: &manageK1, 324 }, 325 "K2": { 326 owner: &metav1.OwnerReference{Kind: "K2"}, 327 wantCallbacks: &manageK2, 328 }, 329 "K3": { 330 owner: &metav1.OwnerReference{Kind: "K3"}, 331 wantCallbacks: nil, 332 }, 333 } 334 335 for tcName, tc := range cases { 336 t.Run(tcName, func(t *testing.T) { 337 gotCallbacks := mgr.getCallbacksForOwner(tc.owner) 338 if tc.wantCallbacks == nil { 339 if gotCallbacks != nil { 340 t.Errorf("This owner should be unmanaged") 341 } 342 } else { 343 if gotCallbacks == nil { 344 t.Errorf("This owner should be managed") 345 } else { 346 if diff := cmp.Diff(*tc.wantCallbacks, *gotCallbacks, cmp.FilterValues(func(_, _ interface{}) bool { return true }, cmp.Comparer(compareCallbacks))); diff != "" { 347 t.Errorf("Unexpected callbacks (-want +got):\n%s", diff) 348 } 349 } 350 } 351 }) 352 } 353 }