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  }