sigs.k8s.io/controller-runtime@v0.18.2/pkg/cache/defaulting_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 cache 18 19 import ( 20 "reflect" 21 "testing" 22 "time" 23 24 "github.com/google/go-cmp/cmp" 25 "github.com/google/go-cmp/cmp/cmpopts" 26 fuzz "github.com/google/gofuzz" 27 corev1 "k8s.io/api/core/v1" 28 "k8s.io/apimachinery/pkg/api/meta" 29 "k8s.io/apimachinery/pkg/fields" 30 "k8s.io/apimachinery/pkg/labels" 31 "k8s.io/apimachinery/pkg/runtime/schema" 32 "k8s.io/client-go/rest" 33 "k8s.io/client-go/tools/cache" 34 "k8s.io/utils/ptr" 35 "sigs.k8s.io/controller-runtime/pkg/client" 36 ) 37 38 func TestDefaultOpts(t *testing.T) { 39 t.Parallel() 40 41 pod := &corev1.Pod{} 42 43 compare := func(a, b any) string { 44 return cmp.Diff(a, b, 45 cmpopts.IgnoreUnexported(Options{}), 46 cmpopts.IgnoreFields(Options{}, "HTTPClient", "Scheme", "Mapper", "SyncPeriod"), 47 cmp.Comparer(func(a, b fields.Selector) bool { 48 if (a != nil) != (b != nil) { 49 return false 50 } 51 if a == nil { 52 return true 53 } 54 return a.String() == b.String() 55 }), 56 ) 57 } 58 testCases := []struct { 59 name string 60 in Options 61 62 verification func(Options) string 63 }{ 64 { 65 name: "ByObject.Namespaces gets defaulted from ByObject", 66 in: Options{ 67 ByObject: map[client.Object]ByObject{pod: { 68 Namespaces: map[string]Config{ 69 "default": {}, 70 }, 71 Label: labels.SelectorFromSet(map[string]string{"from": "by-object"}), 72 }}, 73 DefaultNamespaces: map[string]Config{ 74 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-namespaces"})}, 75 }, 76 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"}), 77 }, 78 79 verification: func(o Options) string { 80 expected := map[string]Config{ 81 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "by-object"})}, 82 } 83 return cmp.Diff(expected, o.ByObject[pod].Namespaces) 84 }, 85 }, 86 { 87 name: "ByObject.Namespaces gets defaulted from DefaultNamespaces", 88 in: Options{ 89 ByObject: map[client.Object]ByObject{pod: { 90 Namespaces: map[string]Config{ 91 "default": {}, 92 }, 93 }}, 94 DefaultNamespaces: map[string]Config{ 95 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-namespaces"})}, 96 }, 97 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"}), 98 }, 99 100 verification: func(o Options) string { 101 expected := map[string]Config{ 102 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-namespaces"})}, 103 } 104 return cmp.Diff(expected, o.ByObject[pod].Namespaces) 105 }, 106 }, 107 { 108 name: "ByObject.Namespaces gets defaulted from DefaultLabelSelector", 109 in: Options{ 110 ByObject: map[client.Object]ByObject{pod: { 111 Namespaces: map[string]Config{ 112 "default": {}, 113 }, 114 }}, 115 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"}), 116 }, 117 118 verification: func(o Options) string { 119 expected := map[string]Config{ 120 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"})}, 121 } 122 return cmp.Diff(expected, o.ByObject[pod].Namespaces) 123 }, 124 }, 125 { 126 name: "ByObject.Namespaces gets defaulted from DefaultNamespaces", 127 in: Options{ 128 ByObject: map[client.Object]ByObject{pod: {}}, 129 DefaultNamespaces: map[string]Config{ 130 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-namespaces"})}, 131 }, 132 }, 133 134 verification: func(o Options) string { 135 expected := map[string]Config{ 136 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-namespaces"})}, 137 } 138 return cmp.Diff(expected, o.ByObject[pod].Namespaces) 139 }, 140 }, 141 { 142 name: "ByObject.Namespaces doesn't get defaulted when its empty", 143 in: Options{ 144 ByObject: map[client.Object]ByObject{pod: {Namespaces: map[string]Config{}}}, 145 DefaultNamespaces: map[string]Config{ 146 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-namespaces"})}, 147 }, 148 }, 149 150 verification: func(o Options) string { 151 expected := map[string]Config{} 152 return cmp.Diff(expected, o.ByObject[pod].Namespaces) 153 }, 154 }, 155 { 156 name: "ByObject.Labels gets defaulted from DefautLabelSelector", 157 in: Options{ 158 ByObject: map[client.Object]ByObject{pod: {}}, 159 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"}), 160 }, 161 162 verification: func(o Options) string { 163 expected := labels.SelectorFromSet(map[string]string{"from": "default-label-selector"}) 164 return cmp.Diff(expected, o.ByObject[pod].Label) 165 }, 166 }, 167 { 168 name: "ByObject.Labels doesn't get defaulted when set", 169 in: Options{ 170 ByObject: map[client.Object]ByObject{pod: {Label: labels.SelectorFromSet(map[string]string{"from": "by-object"})}}, 171 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"}), 172 }, 173 174 verification: func(o Options) string { 175 expected := labels.SelectorFromSet(map[string]string{"from": "by-object"}) 176 return cmp.Diff(expected, o.ByObject[pod].Label) 177 }, 178 }, 179 { 180 name: "ByObject.Fields gets defaulted from DefaultFieldSelector", 181 in: Options{ 182 ByObject: map[client.Object]ByObject{pod: {}}, 183 DefaultFieldSelector: fields.SelectorFromSet(map[string]string{"from": "default-field-selector"}), 184 }, 185 186 verification: func(o Options) string { 187 expected := fields.SelectorFromSet(map[string]string{"from": "default-field-selector"}) 188 return cmp.Diff(expected, o.ByObject[pod].Field, cmp.Exporter(func(reflect.Type) bool { return true })) 189 }, 190 }, 191 { 192 name: "ByObject.Fields doesn't get defaulted when set", 193 in: Options{ 194 ByObject: map[client.Object]ByObject{pod: {Field: fields.SelectorFromSet(map[string]string{"from": "by-object"})}}, 195 DefaultFieldSelector: fields.SelectorFromSet(map[string]string{"from": "default-field-selector"}), 196 }, 197 198 verification: func(o Options) string { 199 expected := fields.SelectorFromSet(map[string]string{"from": "by-object"}) 200 return cmp.Diff(expected, o.ByObject[pod].Field, cmp.Exporter(func(reflect.Type) bool { return true })) 201 }, 202 }, 203 { 204 name: "ByObject.UnsafeDisableDeepCopy gets defaulted from DefaultUnsafeDisableDeepCopy", 205 in: Options{ 206 ByObject: map[client.Object]ByObject{pod: {}}, 207 DefaultUnsafeDisableDeepCopy: ptr.To(true), 208 }, 209 210 verification: func(o Options) string { 211 expected := ptr.To(true) 212 return cmp.Diff(expected, o.ByObject[pod].UnsafeDisableDeepCopy) 213 }, 214 }, 215 { 216 name: "ByObject.UnsafeDisableDeepCopy doesn't get defaulted when set", 217 in: Options{ 218 ByObject: map[client.Object]ByObject{pod: {UnsafeDisableDeepCopy: ptr.To(false)}}, 219 DefaultUnsafeDisableDeepCopy: ptr.To(true), 220 }, 221 222 verification: func(o Options) string { 223 expected := ptr.To(false) 224 return cmp.Diff(expected, o.ByObject[pod].UnsafeDisableDeepCopy) 225 }, 226 }, 227 { 228 name: "DefaultNamespace label selector gets defaulted from DefaultLabelSelector", 229 in: Options{ 230 DefaultNamespaces: map[string]Config{"default": {}}, 231 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"}), 232 }, 233 234 verification: func(o Options) string { 235 expected := map[string]Config{ 236 "default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"})}, 237 } 238 return cmp.Diff(expected, o.DefaultNamespaces) 239 }, 240 }, 241 { 242 name: "ByObject.Namespaces get selector from DefaultNamespaces before DefaultSelector", 243 in: Options{ 244 ByObject: map[client.Object]ByObject{ 245 pod: {Namespaces: map[string]Config{"default": {}}}, 246 }, 247 DefaultNamespaces: map[string]Config{"default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "namespace"})}}, 248 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default"}), 249 }, 250 251 verification: func(o Options) string { 252 expected := Options{ 253 ByObject: map[client.Object]ByObject{ 254 pod: {Namespaces: map[string]Config{"default": { 255 LabelSelector: labels.SelectorFromSet(map[string]string{"from": "namespace"}), 256 }}}, 257 }, 258 DefaultNamespaces: map[string]Config{"default": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "namespace"})}}, 259 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default"}), 260 } 261 262 return compare(expected, o) 263 }, 264 }, 265 { 266 name: "Two namespaces in DefaultNamespaces with custom selection logic", 267 in: Options{DefaultNamespaces: map[string]Config{ 268 "kube-public": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-public"})}, 269 "kube-system": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-system"})}, 270 "": {}, 271 }}, 272 273 verification: func(o Options) string { 274 expected := Options{ 275 DefaultNamespaces: map[string]Config{ 276 "kube-public": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-public"})}, 277 "kube-system": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-system"})}, 278 "": {FieldSelector: fields.ParseSelectorOrDie("metadata.namespace!=kube-public,metadata.namespace!=kube-system")}, 279 }, 280 } 281 282 return compare(expected, o) 283 }, 284 }, 285 { 286 name: "Two namespaces in DefaultNamespaces with custom selection logic and namespace default has its own field selector", 287 in: Options{DefaultNamespaces: map[string]Config{ 288 "kube-public": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-public"})}, 289 "kube-system": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-system"})}, 290 "": {FieldSelector: fields.ParseSelectorOrDie("spec.nodeName=foo")}, 291 }}, 292 293 verification: func(o Options) string { 294 expected := Options{ 295 DefaultNamespaces: map[string]Config{ 296 "kube-public": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-public"})}, 297 "kube-system": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-system"})}, 298 "": {FieldSelector: fields.ParseSelectorOrDie( 299 "metadata.namespace!=kube-public,metadata.namespace!=kube-system,spec.nodeName=foo", 300 )}, 301 }, 302 } 303 304 return compare(expected, o) 305 }, 306 }, 307 { 308 name: "Two namespaces in ByObject.Namespaces with custom selection logic", 309 in: Options{ByObject: map[client.Object]ByObject{pod: { 310 Namespaces: map[string]Config{ 311 "kube-public": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-public"})}, 312 "kube-system": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-system"})}, 313 "": {}, 314 }, 315 }}}, 316 317 verification: func(o Options) string { 318 expected := Options{ByObject: map[client.Object]ByObject{pod: { 319 Namespaces: map[string]Config{ 320 "kube-public": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-public"})}, 321 "kube-system": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-system"})}, 322 "": {FieldSelector: fields.ParseSelectorOrDie( 323 "metadata.namespace!=kube-public,metadata.namespace!=kube-system", 324 )}, 325 }, 326 }}} 327 328 return compare(expected, o) 329 }, 330 }, 331 { 332 name: "Two namespaces in ByObject.Namespaces with custom selection logic and namespace default has its own field selector", 333 in: Options{ByObject: map[client.Object]ByObject{pod: { 334 Namespaces: map[string]Config{ 335 "kube-public": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-public"})}, 336 "kube-system": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-system"})}, 337 "": {FieldSelector: fields.ParseSelectorOrDie("spec.nodeName=foo")}, 338 }, 339 }}}, 340 341 verification: func(o Options) string { 342 expected := Options{ByObject: map[client.Object]ByObject{pod: { 343 Namespaces: map[string]Config{ 344 "kube-public": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-public"})}, 345 "kube-system": {LabelSelector: labels.SelectorFromSet(map[string]string{"from": "kube-system"})}, 346 "": {FieldSelector: fields.ParseSelectorOrDie( 347 "metadata.namespace!=kube-public,metadata.namespace!=kube-system,spec.nodeName=foo", 348 )}, 349 }, 350 }}} 351 352 return compare(expected, o) 353 }, 354 }, 355 { 356 name: "DefaultNamespace label selector doesn't get defaulted when set", 357 in: Options{ 358 DefaultNamespaces: map[string]Config{"default": {LabelSelector: labels.Everything()}}, 359 DefaultLabelSelector: labels.SelectorFromSet(map[string]string{"from": "default-label-selector"}), 360 }, 361 362 verification: func(o Options) string { 363 expected := map[string]Config{ 364 "default": {LabelSelector: labels.Everything()}, 365 } 366 return cmp.Diff(expected, o.DefaultNamespaces) 367 }, 368 }, 369 { 370 name: "Defaulted namespaces in ByObject contain ByObject's selector", 371 in: Options{ 372 ByObject: map[client.Object]ByObject{ 373 pod: {Label: labels.SelectorFromSet(map[string]string{"from": "pod"})}, 374 }, 375 DefaultNamespaces: map[string]Config{"default": {}}, 376 }, 377 verification: func(o Options) string { 378 expected := Options{ 379 ByObject: map[client.Object]ByObject{ 380 pod: { 381 Label: labels.SelectorFromSet(map[string]string{"from": "pod"}), 382 Namespaces: map[string]Config{"default": { 383 LabelSelector: labels.SelectorFromSet(map[string]string{"from": "pod"}), 384 }}, 385 }, 386 }, 387 388 DefaultNamespaces: map[string]Config{"default": {}}, 389 } 390 return compare(expected, o) 391 }, 392 }, 393 } 394 395 for _, tc := range testCases { 396 t.Run(tc.name, func(t *testing.T) { 397 tc.in.Mapper = &fakeRESTMapper{} 398 399 defaulted, err := defaultOpts(&rest.Config{}, tc.in) 400 if err != nil { 401 t.Fatal(err) 402 } 403 404 if diff := tc.verification(defaulted); diff != "" { 405 t.Errorf("expected config differs from actual: %s", diff) 406 } 407 }) 408 } 409 } 410 411 type fakeRESTMapper struct { 412 meta.RESTMapper 413 } 414 415 func (f *fakeRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) { 416 return &meta.RESTMapping{Scope: meta.RESTScopeNamespace}, nil 417 } 418 419 func TestDefaultConfigConsidersAllFields(t *testing.T) { 420 t.Parallel() 421 seed := time.Now().UnixNano() 422 t.Logf("Seed is %d", seed) 423 f := fuzz.NewWithSeed(seed).Funcs( 424 func(ls *labels.Selector, _ fuzz.Continue) { 425 *ls = labels.SelectorFromSet(map[string]string{"foo": "bar"}) 426 }, 427 func(fs *fields.Selector, _ fuzz.Continue) { 428 *fs = fields.SelectorFromSet(map[string]string{"foo": "bar"}) 429 }, 430 func(tf *cache.TransformFunc, _ fuzz.Continue) { 431 // never default this, as functions can not be compared so we fail down the line 432 }, 433 ) 434 435 for i := 0; i < 100; i++ { 436 fuzzed := Config{} 437 f.Fuzz(&fuzzed) 438 439 defaulted := defaultConfig(Config{}, fuzzed) 440 441 if diff := cmp.Diff(fuzzed, defaulted, cmp.Exporter(func(reflect.Type) bool { return true })); diff != "" { 442 t.Errorf("Defaulted config doesn't match fuzzed one: %s", diff) 443 } 444 } 445 }