istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/test/mock/config.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package mock 16 17 import ( 18 "fmt" 19 "reflect" 20 "strconv" 21 "testing" 22 "time" 23 24 "go.uber.org/atomic" 25 26 networking "istio.io/api/networking/v1alpha3" 27 authz "istio.io/api/security/v1beta1" 28 api "istio.io/api/type/v1beta1" 29 "istio.io/istio/pilot/pkg/model" 30 config2 "istio.io/istio/pkg/config" 31 "istio.io/istio/pkg/config/schema/collections" 32 "istio.io/istio/pkg/config/schema/resource" 33 "istio.io/istio/pkg/log" 34 "istio.io/istio/pkg/test/config" 35 "istio.io/istio/pkg/test/util/retry" 36 ) 37 38 var ( 39 // ExampleVirtualService is an example V2 route rule 40 ExampleVirtualService = &networking.VirtualService{ 41 Hosts: []string{"prod", "test"}, 42 Http: []*networking.HTTPRoute{ 43 { 44 Route: []*networking.HTTPRouteDestination{ 45 { 46 Destination: &networking.Destination{ 47 Host: "job", 48 }, 49 Weight: 80, 50 }, 51 }, 52 }, 53 }, 54 } 55 56 ExampleServiceEntry = &networking.ServiceEntry{ 57 Hosts: []string{"*.google.com"}, 58 Resolution: networking.ServiceEntry_NONE, 59 Ports: []*networking.ServicePort{ 60 {Number: 80, Name: "http-name", Protocol: "http"}, 61 {Number: 8080, Name: "http2-name", Protocol: "http2"}, 62 }, 63 } 64 65 ExampleGateway = &networking.Gateway{ 66 Servers: []*networking.Server{ 67 { 68 Hosts: []string{"google.com"}, 69 Port: &networking.Port{Name: "http", Protocol: "http", Number: 10080}, 70 }, 71 }, 72 } 73 74 // ExampleDestinationRule is an example destination rule 75 ExampleDestinationRule = &networking.DestinationRule{ 76 Host: "ratings", 77 TrafficPolicy: &networking.TrafficPolicy{ 78 LoadBalancer: &networking.LoadBalancerSettings{ 79 LbPolicy: new(networking.LoadBalancerSettings_Simple), 80 }, 81 }, 82 } 83 84 // ExampleAuthorizationPolicy is an example AuthorizationPolicy 85 ExampleAuthorizationPolicy = &authz.AuthorizationPolicy{ 86 Selector: &api.WorkloadSelector{ 87 MatchLabels: map[string]string{ 88 "app": "httpbin", 89 "version": "v1", 90 }, 91 }, 92 } 93 94 mockGvk = collections.Mock.GroupVersionKind() 95 ) 96 97 // Make creates a mock config indexed by a number 98 func Make(namespace string, i int) config2.Config { 99 name := fmt.Sprintf("%s%d", "mock-config", i) 100 return config2.Config{ 101 Meta: config2.Meta{ 102 GroupVersionKind: mockGvk, 103 Name: name, 104 Namespace: namespace, 105 Labels: map[string]string{ 106 "key": name, 107 }, 108 Annotations: map[string]string{ 109 "annotationkey": name, 110 }, 111 }, 112 Spec: &config.MockConfig{ 113 Key: name, 114 Pairs: []*config.ConfigPair{ 115 {Key: "key", Value: strconv.Itoa(i)}, 116 }, 117 }, 118 } 119 } 120 121 // Compare checks two configs ignoring revisions and creation time 122 func Compare(a, b config2.Config) bool { 123 a.ResourceVersion = "" 124 b.ResourceVersion = "" 125 a.CreationTimestamp = time.Time{} 126 b.CreationTimestamp = time.Time{} 127 return reflect.DeepEqual(a, b) 128 } 129 130 // CheckMapInvariant validates operational invariants of an empty config registry 131 func CheckMapInvariant(r model.ConfigStore, t *testing.T, namespace string, n int) { 132 // check that the config descriptor is the mock config descriptor 133 _, contains := r.Schemas().FindByGroupVersionKind(mockGvk) 134 if !contains { 135 t.Fatal("expected config mock types") 136 } 137 log.Info("Created mock descriptor") 138 139 // create configuration objects 140 elts := make(map[int]config2.Config) 141 for i := 0; i < n; i++ { 142 elts[i] = Make(namespace, i) 143 } 144 log.Info("Make mock objects") 145 146 // post all elements 147 for _, elt := range elts { 148 if _, err := r.Create(elt); err != nil { 149 t.Error(err) 150 } 151 } 152 log.Info("Created mock objects") 153 154 revs := make(map[int]string) 155 156 // check that elements are stored 157 for i, elt := range elts { 158 v1 := r.Get(mockGvk, elt.Name, elt.Namespace) 159 if v1 == nil || !Compare(elt, *v1) { 160 t.Errorf("wanted %v, got %v", elt, v1) 161 } else { 162 revs[i] = v1.ResourceVersion 163 } 164 } 165 166 log.Info("Got stored elements") 167 168 if _, err := r.Create(elts[0]); err == nil { 169 t.Error("expected error posting twice") 170 } 171 172 invalid := config2.Config{ 173 Meta: config2.Meta{ 174 GroupVersionKind: mockGvk, 175 Name: "invalid", 176 ResourceVersion: revs[0], 177 }, 178 Spec: &config.MockConfig{}, 179 } 180 181 missing := config2.Config{ 182 Meta: config2.Meta{ 183 GroupVersionKind: mockGvk, 184 Name: "missing", 185 ResourceVersion: revs[0], 186 }, 187 Spec: &config.MockConfig{Key: "missing"}, 188 } 189 190 if _, err := r.Create(config2.Config{}); err == nil { 191 t.Error("expected error posting empty object") 192 } 193 194 if _, err := r.Create(invalid); err == nil { 195 t.Error("expected error posting invalid object") 196 } 197 198 if _, err := r.Update(config2.Config{}); err == nil { 199 t.Error("expected error updating empty object") 200 } 201 202 if _, err := r.Update(invalid); err == nil { 203 t.Error("expected error putting invalid object") 204 } 205 206 if _, err := r.Update(missing); err == nil { 207 t.Error("expected error putting missing object with a missing key") 208 } 209 210 // check for missing type 211 if l := r.List(config2.GroupVersionKind{}, namespace); len(l) > 0 { 212 t.Errorf("unexpected objects for missing type") 213 } 214 215 // check for missing element 216 if cfg := r.Get(mockGvk, "missing", ""); cfg != nil { 217 t.Error("unexpected configuration object found") 218 } 219 220 // check for missing element 221 if cfg := r.Get(config2.GroupVersionKind{}, "missing", ""); cfg != nil { 222 t.Error("unexpected configuration object found") 223 } 224 225 // delete missing elements 226 if err := r.Delete(config2.GroupVersionKind{}, "missing", "", nil); err == nil { 227 t.Error("expected error on deletion of missing type") 228 } 229 230 // delete missing elements 231 if err := r.Delete(mockGvk, "missing", "", nil); err == nil { 232 t.Error("expected error on deletion of missing element") 233 } 234 if err := r.Delete(mockGvk, "missing", "unknown", nil); err == nil { 235 t.Error("expected error on deletion of missing element in unknown namespace") 236 } 237 238 // list elements 239 l := r.List(mockGvk, namespace) 240 if len(l) != n { 241 t.Errorf("wanted %d element(s), got %d in %v", n, len(l), l) 242 } 243 244 // update all elements 245 for i := 0; i < n; i++ { 246 elt := Make(namespace, i) 247 elt.Spec.(*config.MockConfig).Pairs[0].Value += "(updated)" 248 elt.ResourceVersion = revs[i] 249 elts[i] = elt 250 if _, err := r.Update(elt); err != nil { 251 t.Error(err) 252 } 253 } 254 255 // check that elements are stored 256 for i, elt := range elts { 257 v1 := r.Get(mockGvk, elts[i].Name, elts[i].Namespace) 258 if v1 == nil || !Compare(elt, *v1) { 259 t.Errorf("wanted %v, got %v", elt, v1) 260 } 261 } 262 263 // delete all elements 264 for i := range elts { 265 if err := r.Delete(mockGvk, elts[i].Name, elts[i].Namespace, nil); err != nil { 266 t.Error(err) 267 } 268 } 269 log.Info("Delete elements") 270 271 l = r.List(mockGvk, namespace) 272 if len(l) != 0 { 273 t.Errorf("wanted 0 element(s), got %d in %v", len(l), l) 274 } 275 log.Info("Test done, deleting namespace") 276 } 277 278 // CheckIstioConfigTypes validates that an empty store can ingest Istio config objects 279 func CheckIstioConfigTypes(store model.ConfigStore, namespace string, t *testing.T) { 280 configName := "example" 281 // Global scoped policies like MeshPolicy are not isolated, can't be 282 // run as part of the normal test suites - if needed they should 283 // be run in separate environment. The test suites are setting cluster 284 // scoped policies that may interfere and would require serialization 285 286 cases := []struct { 287 name string 288 configName string 289 schema resource.Schema 290 spec config2.Spec 291 }{ 292 {"VirtualService", configName, collections.VirtualService, ExampleVirtualService}, 293 {"DestinationRule", configName, collections.DestinationRule, ExampleDestinationRule}, 294 {"ServiceEntry", configName, collections.ServiceEntry, ExampleServiceEntry}, 295 {"Gateway", configName, collections.Gateway, ExampleGateway}, 296 {"AuthorizationPolicy", configName, collections.AuthorizationPolicy, ExampleAuthorizationPolicy}, 297 } 298 299 for _, c := range cases { 300 t.Run(c.name, func(t *testing.T) { 301 configMeta := config2.Meta{ 302 GroupVersionKind: c.schema.GroupVersionKind(), 303 Name: c.configName, 304 } 305 if !c.schema.IsClusterScoped() { 306 configMeta.Namespace = namespace 307 } 308 309 if _, err := store.Create(config2.Config{ 310 Meta: configMeta, 311 Spec: c.spec, 312 }); err != nil { 313 t.Errorf("Post(%v) => got %v", c.name, err) 314 } 315 }) 316 } 317 } 318 319 // CheckCacheEvents validates operational invariants of a cache 320 func CheckCacheEvents(store model.ConfigStore, cache model.ConfigStoreController, namespace string, n int, t *testing.T) { 321 n64 := int64(n) 322 stop := make(chan struct{}) 323 defer close(stop) 324 added, deleted := atomic.NewInt64(0), atomic.NewInt64(0) 325 cache.RegisterEventHandler(mockGvk, func(_, _ config2.Config, ev model.Event) { 326 switch ev { 327 case model.EventAdd: 328 if deleted.Load() != 0 { 329 t.Errorf("Events are not serialized (add)") 330 } 331 added.Inc() 332 case model.EventDelete: 333 if added.Load() != n64 { 334 t.Errorf("Events are not serialized (delete)") 335 } 336 deleted.Inc() 337 } 338 log.Infof("Added %d, deleted %d", added.Load(), deleted.Load()) 339 }) 340 go cache.Run(stop) 341 342 // run map invariant sequence 343 CheckMapInvariant(store, t, namespace, n) 344 345 log.Infof("Waiting till all events are received") 346 retry.UntilOrFail(t, func() bool { 347 return added.Load() == n64 && deleted.Load() == n64 348 }, retry.Message("receive events"), retry.Delay(time.Millisecond*500), retry.Timeout(time.Minute)) 349 } 350 351 // CheckCacheFreshness validates operational invariants of a cache 352 func CheckCacheFreshness(cache model.ConfigStoreController, namespace string, t *testing.T) { 353 stop := make(chan struct{}) 354 done := make(chan bool) 355 o := Make(namespace, 0) 356 357 // validate cache consistency 358 cache.RegisterEventHandler(mockGvk, func(_, config config2.Config, ev model.Event) { 359 elts := cache.List(mockGvk, namespace) 360 elt := cache.Get(o.GroupVersionKind, o.Name, o.Namespace) 361 switch ev { 362 case model.EventAdd: 363 if len(elts) != 1 { 364 t.Errorf("Got %#v, expected %d element(s) on Add event", elts, 1) 365 } 366 if elt == nil || !reflect.DeepEqual(elt.Spec, o.Spec) { 367 t.Errorf("Got %#v, expected %#v", elt, o) 368 } 369 370 log.Infof("Calling Update(%s)", config.Key()) 371 revised := Make(namespace, 1) 372 revised.Meta = elt.Meta 373 if _, err := cache.Update(revised); err != nil { 374 t.Error(err) 375 } 376 case model.EventUpdate: 377 if len(elts) != 1 { 378 t.Errorf("Got %#v, expected %d element(s) on Update event", elts, 1) 379 } 380 if elt == nil { 381 t.Errorf("Got %#v, expected nonempty", elt) 382 } 383 384 log.Infof("Calling Delete(%s)", config.Key()) 385 if err := cache.Delete(mockGvk, config.Name, config.Namespace, nil); err != nil { 386 t.Error(err) 387 } 388 case model.EventDelete: 389 if len(elts) != 0 { 390 t.Errorf("Got %#v, expected zero elements on Delete event", elts) 391 } 392 log.Infof("Stopping channel for (%#v)", config.Key()) 393 close(stop) 394 done <- true 395 } 396 }) 397 398 go cache.Run(stop) 399 400 // try warm-up with empty Get 401 if cfg := cache.Get(config2.GroupVersionKind{}, "example", namespace); cfg != nil { 402 t.Error("unexpected result for unknown type") 403 } 404 405 // add and remove 406 log.Infof("Calling Create(%#v)", o) 407 if _, err := cache.Create(o); err != nil { 408 t.Error(err) 409 } 410 411 timeout := time.After(10 * time.Second) 412 select { 413 case <-timeout: 414 t.Fatalf("timeout waiting to be done") 415 case <-done: 416 return 417 } 418 } 419 420 // CheckCacheSync validates operational invariants of a cache against the 421 // non-cached client. 422 func CheckCacheSync(store model.ConfigStore, cache model.ConfigStoreController, namespace string, n int, t *testing.T) { 423 keys := make(map[int]config2.Config) 424 // add elements directly through client 425 for i := 0; i < n; i++ { 426 keys[i] = Make(namespace, i) 427 if _, err := store.Create(keys[i]); err != nil { 428 t.Error(err) 429 } 430 } 431 432 // check in the controller cache 433 stop := make(chan struct{}) 434 defer close(stop) 435 go cache.Run(stop) 436 retry.UntilOrFail(t, cache.HasSynced, retry.Message("HasSynced")) 437 os := cache.List(mockGvk, namespace) 438 if len(os) != n { 439 t.Errorf("cache.List => Got %d, expected %d", len(os), n) 440 } 441 442 // remove elements directly through client 443 for i := 0; i < n; i++ { 444 if err := store.Delete(mockGvk, keys[i].Name, keys[i].Namespace, nil); err != nil { 445 t.Error(err) 446 } 447 } 448 449 // check again in the controller cache 450 retry.UntilOrFail(t, func() bool { 451 os = cache.List(mockGvk, namespace) 452 log.Infof("cache.List => Got %d, expected %d", len(os), 0) 453 return len(os) == 0 454 }, retry.Message("no elements in cache")) 455 456 // now add through the controller 457 for i := 0; i < n; i++ { 458 if _, err := cache.Create(Make(namespace, i)); err != nil { 459 t.Error(err) 460 } 461 } 462 463 // check directly through the client 464 retry.UntilOrFail(t, func() bool { 465 cs := cache.List(mockGvk, namespace) 466 os := store.List(mockGvk, namespace) 467 log.Infof("cache.List => Got %d, expected %d", len(cs), n) 468 log.Infof("store.List => Got %d, expected %d", len(os), n) 469 return len(os) == n && len(cs) == n 470 }, retry.Message("cache and backing store match")) 471 472 // remove elements directly through the client 473 for i := 0; i < n; i++ { 474 if err := store.Delete(mockGvk, keys[i].Name, keys[i].Namespace, nil); err != nil { 475 t.Error(err) 476 } 477 } 478 }