istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/aggregate/controller_test.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 aggregate
    16  
    17  import (
    18  	"fmt"
    19  	"reflect"
    20  	"testing"
    21  	"time"
    22  
    23  	"go.uber.org/atomic"
    24  
    25  	meshconfig "istio.io/api/mesh/v1alpha1"
    26  	"istio.io/istio/pilot/pkg/model"
    27  	"istio.io/istio/pilot/pkg/serviceregistry"
    28  	"istio.io/istio/pilot/pkg/serviceregistry/memory"
    29  	"istio.io/istio/pilot/pkg/serviceregistry/mock"
    30  	"istio.io/istio/pilot/pkg/serviceregistry/provider"
    31  	"istio.io/istio/pkg/cluster"
    32  	"istio.io/istio/pkg/config/host"
    33  	"istio.io/istio/pkg/test/util/retry"
    34  )
    35  
    36  type mockMeshConfigHolder struct {
    37  	trustDomainAliases []string
    38  }
    39  
    40  func (mh mockMeshConfigHolder) Mesh() *meshconfig.MeshConfig {
    41  	return &meshconfig.MeshConfig{
    42  		TrustDomainAliases: mh.trustDomainAliases,
    43  	}
    44  }
    45  
    46  func buildMockController() *Controller {
    47  	discovery1 := memory.NewServiceDiscovery(mock.ReplicatedFooServiceV1.DeepCopy(),
    48  		mock.HelloService.DeepCopy(),
    49  		mock.ExtHTTPService.DeepCopy(),
    50  	)
    51  	for _, port := range mock.HelloService.Ports {
    52  		discovery1.AddInstance(mock.MakeServiceInstance(mock.HelloService, port, 0, model.Locality{}))
    53  		discovery1.AddInstance(mock.MakeServiceInstance(mock.HelloService, port, 1, model.Locality{}))
    54  	}
    55  
    56  	discovery2 := memory.NewServiceDiscovery(mock.ReplicatedFooServiceV2.DeepCopy(),
    57  		mock.WorldService.DeepCopy(),
    58  		mock.ExtHTTPSService.DeepCopy(),
    59  	)
    60  	for _, port := range mock.WorldService.Ports {
    61  		discovery2.AddInstance(mock.MakeServiceInstance(mock.WorldService, port, 0, model.Locality{}))
    62  		discovery2.AddInstance(mock.MakeServiceInstance(mock.WorldService, port, 1, model.Locality{}))
    63  	}
    64  	registry1 := serviceregistry.Simple{
    65  		ProviderID:          provider.ID("mockAdapter1"),
    66  		DiscoveryController: discovery1,
    67  	}
    68  
    69  	registry2 := serviceregistry.Simple{
    70  		ProviderID:          provider.ID("mockAdapter2"),
    71  		DiscoveryController: discovery2,
    72  	}
    73  
    74  	ctls := NewController(Options{&mockMeshConfigHolder{}})
    75  	ctls.AddRegistry(registry1)
    76  	ctls.AddRegistry(registry2)
    77  
    78  	return ctls
    79  }
    80  
    81  // return aggregator and cluster1 and cluster2 service discovery
    82  func buildMockControllerForMultiCluster() (*Controller, *memory.ServiceDiscovery, *memory.ServiceDiscovery) {
    83  	discovery1 := memory.NewServiceDiscovery(mock.HelloService)
    84  
    85  	discovery2 := memory.NewServiceDiscovery(mock.MakeService(mock.ServiceArgs{
    86  		Hostname:        mock.HelloService.Hostname,
    87  		Address:         "10.1.2.0",
    88  		ServiceAccounts: []string{},
    89  		ClusterID:       "cluster-2",
    90  	}), mock.WorldService)
    91  
    92  	registry1 := serviceregistry.Simple{
    93  		ProviderID:          provider.Kubernetes,
    94  		ClusterID:           "cluster-1",
    95  		DiscoveryController: discovery1,
    96  	}
    97  
    98  	registry2 := serviceregistry.Simple{
    99  		ProviderID:          provider.Kubernetes,
   100  		ClusterID:           "cluster-2",
   101  		DiscoveryController: discovery2,
   102  	}
   103  
   104  	ctls := NewController(Options{})
   105  	ctls.AddRegistry(registry1)
   106  	ctls.AddRegistry(registry2)
   107  
   108  	return ctls, discovery1, discovery2
   109  }
   110  
   111  func TestServicesForMultiCluster(t *testing.T) {
   112  	originalHelloService := mock.HelloService.DeepCopy()
   113  	aggregateCtl, _, registry2 := buildMockControllerForMultiCluster()
   114  	// List Services from aggregate controller
   115  	services := aggregateCtl.Services()
   116  
   117  	// Set up ground truth hostname values
   118  	hosts := map[host.Name]bool{
   119  		mock.HelloService.Hostname: false,
   120  		mock.WorldService.Hostname: false,
   121  	}
   122  
   123  	count := 0
   124  	// Compare return value to ground truth
   125  	for _, svc := range services {
   126  		if counted, existed := hosts[svc.Hostname]; existed && !counted {
   127  			count++
   128  			hosts[svc.Hostname] = true
   129  		}
   130  	}
   131  
   132  	if count != len(hosts) {
   133  		t.Fatalf("Cluster local service map expected size %d, actual %v vs %v", count, hosts, services)
   134  	}
   135  
   136  	// Now verify ClusterVIPs for each service
   137  	ClusterVIPs := map[host.Name]map[cluster.ID][]string{
   138  		mock.HelloService.Hostname: {
   139  			"cluster-1": []string{"10.1.0.0"},
   140  			"cluster-2": []string{"10.1.2.0"},
   141  		},
   142  		mock.WorldService.Hostname: {
   143  			"cluster-2": []string{"10.2.0.0"},
   144  		},
   145  	}
   146  	for _, svc := range services {
   147  		if !reflect.DeepEqual(svc.ClusterVIPs.Addresses, ClusterVIPs[svc.Hostname]) {
   148  			t.Fatalf("Service %s ClusterVIPs actual %v, expected %v", svc.Hostname,
   149  				svc.ClusterVIPs.Addresses, ClusterVIPs[svc.Hostname])
   150  		}
   151  	}
   152  
   153  	registry2.RemoveService(mock.HelloService.Hostname)
   154  	// List Services from aggregate controller
   155  	services = aggregateCtl.Services()
   156  	// Now verify ClusterVIPs for each service
   157  	ClusterVIPs = map[host.Name]map[cluster.ID][]string{
   158  		mock.HelloService.Hostname: {
   159  			"cluster-1": []string{"10.1.0.0"},
   160  		},
   161  		mock.WorldService.Hostname: {
   162  			"cluster-2": []string{"10.2.0.0"},
   163  		},
   164  	}
   165  	for _, svc := range services {
   166  		if !reflect.DeepEqual(svc.ClusterVIPs.Addresses, ClusterVIPs[svc.Hostname]) {
   167  			t.Fatalf("Service %s ClusterVIPs actual %v, expected %v", svc.Hostname,
   168  				svc.ClusterVIPs.Addresses, ClusterVIPs[svc.Hostname])
   169  		}
   170  	}
   171  
   172  	// check HelloService is not mutated
   173  	if !reflect.DeepEqual(originalHelloService, mock.HelloService) {
   174  		t.Errorf("Original hello service is mutated")
   175  	}
   176  }
   177  
   178  func TestServices(t *testing.T) {
   179  	aggregateCtl := buildMockController()
   180  	// List Services from aggregate controller
   181  	services := aggregateCtl.Services()
   182  
   183  	// Set up ground truth hostname values
   184  	serviceMap := map[host.Name]bool{
   185  		mock.HelloService.Hostname:    false,
   186  		mock.ExtHTTPService.Hostname:  false,
   187  		mock.WorldService.Hostname:    false,
   188  		mock.ExtHTTPSService.Hostname: false,
   189  	}
   190  
   191  	svcCount := 0
   192  	// Compare return value to ground truth
   193  	for _, svc := range services {
   194  		if counted, existed := serviceMap[svc.Hostname]; existed && !counted {
   195  			svcCount++
   196  			serviceMap[svc.Hostname] = true
   197  		}
   198  	}
   199  
   200  	if svcCount != len(serviceMap) {
   201  		t.Fatal("Return services does not match ground truth")
   202  	}
   203  }
   204  
   205  func TestGetService(t *testing.T) {
   206  	aggregateCtl := buildMockController()
   207  
   208  	// Get service from mockAdapter1
   209  	svc := aggregateCtl.GetService(mock.HelloService.Hostname)
   210  	if svc == nil {
   211  		t.Fatal("Fail to get service")
   212  	}
   213  	if svc.Hostname != mock.HelloService.Hostname {
   214  		t.Fatal("Returned service is incorrect")
   215  	}
   216  
   217  	// Get service from mockAdapter2
   218  	svc = aggregateCtl.GetService(mock.WorldService.Hostname)
   219  	if svc == nil {
   220  		t.Fatal("Fail to get service")
   221  	}
   222  	if svc.Hostname != mock.WorldService.Hostname {
   223  		t.Fatal("Returned service is incorrect")
   224  	}
   225  }
   226  
   227  func TestGetProxyServiceTargets(t *testing.T) {
   228  	aggregateCtl := buildMockController()
   229  
   230  	// Get Instances from mockAdapter1
   231  	instances := aggregateCtl.GetProxyServiceTargets(&model.Proxy{IPAddresses: []string{mock.HelloInstanceV0}})
   232  	if len(instances) != 6 {
   233  		t.Fatalf("Returned GetProxyServiceTargets' amount %d is not correct", len(instances))
   234  	}
   235  	for _, inst := range instances {
   236  		if inst.Service.Hostname != mock.HelloService.Hostname {
   237  			t.Fatal("Returned Instance is incorrect")
   238  		}
   239  	}
   240  
   241  	// Get Instances from mockAdapter2
   242  	instances = aggregateCtl.GetProxyServiceTargets(&model.Proxy{IPAddresses: []string{mock.MakeIP(mock.WorldService, 1)}})
   243  	if len(instances) != 6 {
   244  		t.Fatalf("Returned GetProxyServiceTargets' amount %d is not correct", len(instances))
   245  	}
   246  	for _, inst := range instances {
   247  		if inst.Service.Hostname != mock.WorldService.Hostname {
   248  			t.Fatal("Returned Instance is incorrect")
   249  		}
   250  	}
   251  }
   252  
   253  func TestGetProxyWorkloadLabels(t *testing.T) {
   254  	// If no registries return workload labels, we must return nil, rather than an empty list.
   255  	// This ensures callers can distinguish between no labels, and labels not found.
   256  	aggregateCtl := buildMockController()
   257  
   258  	instances := aggregateCtl.GetProxyWorkloadLabels(&model.Proxy{IPAddresses: []string{mock.HelloInstanceV0}})
   259  	if instances != nil {
   260  		t.Fatalf("expected nil workload labels, got: %v", instances)
   261  	}
   262  }
   263  
   264  func TestAddRegistry(t *testing.T) {
   265  	registries := []serviceregistry.Simple{
   266  		{
   267  			ProviderID:          "registry1",
   268  			ClusterID:           "cluster1",
   269  			DiscoveryController: memory.NewServiceDiscovery(),
   270  		},
   271  		{
   272  			ProviderID:          "registry2",
   273  			ClusterID:           "cluster2",
   274  			DiscoveryController: memory.NewServiceDiscovery(),
   275  		},
   276  		{
   277  			ProviderID:          provider.Kubernetes,
   278  			ClusterID:           "cluster3",
   279  			DiscoveryController: memory.NewServiceDiscovery(),
   280  		},
   281  	}
   282  	ctrl := NewController(Options{})
   283  
   284  	registry1Counter := atomic.NewInt32(0)
   285  	registry2Counter := atomic.NewInt32(0)
   286  
   287  	for _, r := range registries {
   288  		counter := atomic.NewInt32(0)
   289  		clusterID := r.Cluster()
   290  		if clusterID == "cluster1" {
   291  			counter = registry1Counter
   292  		}
   293  		if clusterID == "cluster2" {
   294  			counter = registry2Counter
   295  		}
   296  		ctrl.AppendServiceHandlerForCluster(clusterID, func(_, curr *model.Service, event model.Event) {
   297  			counter.Add(1)
   298  		})
   299  		ctrl.AddRegistry(r)
   300  	}
   301  	if l := len(ctrl.registries); l != 3 {
   302  		t.Fatalf("Expected length of the registries slice should be 3, got %d", l)
   303  	}
   304  
   305  	if ctrl.registries[0].Instance.Provider() != provider.Kubernetes {
   306  		t.Errorf("expected first registry should be %s, but got %s", provider.Kubernetes,
   307  			ctrl.registries[0].Instance.Provider())
   308  	}
   309  
   310  	registries[0].DiscoveryController.(*memory.ServiceDiscovery).AddService(mock.HelloService)
   311  	registries[1].DiscoveryController.(*memory.ServiceDiscovery).AddService(mock.HelloService)
   312  
   313  	ctrl.DeleteRegistry(registries[1].Cluster(), registries[1].Provider())
   314  	ctrl.UnRegisterHandlersForCluster(registries[1].Cluster())
   315  	registries[0].DiscoveryController.(*memory.ServiceDiscovery).AddService(mock.HelloService)
   316  
   317  	if registry1Counter.Load() != 3 {
   318  		t.Errorf("cluster1 expected 3 event, but got %d", registry1Counter.Load())
   319  	}
   320  	if registry2Counter.Load() != 2 {
   321  		t.Errorf("cluster2 expected 2 event, but got %d", registry2Counter.Load())
   322  	}
   323  }
   324  
   325  func TestGetDeleteRegistry(t *testing.T) {
   326  	registries := []serviceregistry.Simple{
   327  		{
   328  			ProviderID:          "registry1",
   329  			ClusterID:           "cluster1",
   330  			DiscoveryController: memory.NewServiceDiscovery(),
   331  		},
   332  		{
   333  			ProviderID:          "registry2",
   334  			ClusterID:           "cluster2",
   335  			DiscoveryController: memory.NewServiceDiscovery(),
   336  		},
   337  		{
   338  			ProviderID:          "registry3",
   339  			ClusterID:           "cluster3",
   340  			DiscoveryController: memory.NewServiceDiscovery(),
   341  		},
   342  	}
   343  	wrapRegistry := func(r serviceregistry.Instance) serviceregistry.Instance {
   344  		return &registryEntry{Instance: r}
   345  	}
   346  
   347  	ctrl := NewController(Options{})
   348  	for _, r := range registries {
   349  		ctrl.AddRegistry(r)
   350  	}
   351  
   352  	// Test Get
   353  	result := ctrl.GetRegistries()
   354  	if l := len(result); l != 3 {
   355  		t.Fatalf("Expected length of the registries slice should be 3, got %d", l)
   356  	}
   357  
   358  	// Test Delete cluster2
   359  	ctrl.DeleteRegistry(registries[1].ClusterID, registries[1].ProviderID)
   360  	result = ctrl.GetRegistries()
   361  	if l := len(result); l != 2 {
   362  		t.Fatalf("Expected length of the registries slice should be 2, got %d", l)
   363  	}
   364  	// check left registries are orders as before
   365  	if !reflect.DeepEqual(result[0], wrapRegistry(registries[0])) || !reflect.DeepEqual(result[1], wrapRegistry(registries[2])) {
   366  		t.Fatalf("Expected registries order has been changed")
   367  	}
   368  }
   369  
   370  func TestSkipSearchingRegistryForProxy(t *testing.T) {
   371  	cluster1 := serviceregistry.Simple{
   372  		ClusterID:           "cluster-1",
   373  		ProviderID:          provider.Kubernetes,
   374  		DiscoveryController: memory.NewServiceDiscovery(),
   375  	}
   376  	cluster2 := serviceregistry.Simple{
   377  		ClusterID:           "cluster-2",
   378  		ProviderID:          provider.Kubernetes,
   379  		DiscoveryController: memory.NewServiceDiscovery(),
   380  	}
   381  	// external registries may eventually be associated with a cluster
   382  	external := serviceregistry.Simple{
   383  		ClusterID:           "cluster-1",
   384  		ProviderID:          provider.External,
   385  		DiscoveryController: memory.NewServiceDiscovery(),
   386  	}
   387  
   388  	cases := []struct {
   389  		nodeClusterID cluster.ID
   390  		registry      serviceregistry.Instance
   391  		want          bool
   392  	}{
   393  		// matching kube registry
   394  		{"cluster-1", cluster1, false},
   395  		// unmatching kube registry
   396  		{"cluster-1", cluster2, true},
   397  		// always search external
   398  		{"cluster-1", external, false},
   399  		{"cluster-2", external, false},
   400  		{"", external, false},
   401  		// always search for empty node cluster id
   402  		{"", cluster1, false},
   403  		{"", cluster2, false},
   404  		{"", external, false},
   405  	}
   406  
   407  	for i, c := range cases {
   408  		got := skipSearchingRegistryForProxy(c.nodeClusterID, c.registry)
   409  		if got != c.want {
   410  			t.Errorf("%s: got %v want %v",
   411  				fmt.Sprintf("[%v] registry=%v node=%v", i, c.registry, c.nodeClusterID),
   412  				got, c.want)
   413  		}
   414  	}
   415  }
   416  
   417  func runnableRegistry(name string) *RunnableRegistry {
   418  	return &RunnableRegistry{
   419  		Instance: serviceregistry.Simple{
   420  			ClusterID: cluster.ID(name), ProviderID: "test",
   421  			DiscoveryController: memory.NewServiceDiscovery(),
   422  		},
   423  		running: atomic.NewBool(false),
   424  	}
   425  }
   426  
   427  type RunnableRegistry struct {
   428  	serviceregistry.Instance
   429  	running *atomic.Bool
   430  }
   431  
   432  func (rr *RunnableRegistry) Run(stop <-chan struct{}) {
   433  	if rr.running.Load() {
   434  		panic("--- registry has been run twice ---")
   435  	}
   436  	rr.running.Store(true)
   437  	<-stop
   438  }
   439  
   440  func expectRunningOrFail(t *testing.T, ctrl *Controller, want bool) {
   441  	// running gets flipped in a goroutine, retry to avoid race
   442  	retry.UntilSuccessOrFail(t, func() error {
   443  		for _, registry := range ctrl.registries {
   444  			if running := registry.Instance.(*RunnableRegistry).running.Load(); running != want {
   445  				return fmt.Errorf("%s running is %v but wanted %v", registry.Cluster(), running, want)
   446  			}
   447  		}
   448  		return nil
   449  	}, retry.Timeout(50*time.Millisecond), retry.Delay(0))
   450  }
   451  
   452  func TestDeferredRun(t *testing.T) {
   453  	stop := make(chan struct{})
   454  	defer close(stop)
   455  	ctrl := NewController(Options{})
   456  
   457  	t.Run("AddRegistry before aggregate Run does not run", func(t *testing.T) {
   458  		ctrl.AddRegistry(runnableRegistry("earlyAdd"))
   459  		ctrl.AddRegistryAndRun(runnableRegistry("earlyAddAndRun"), stop)
   460  		expectRunningOrFail(t, ctrl, false)
   461  	})
   462  	t.Run("aggregate Run starts all registries", func(t *testing.T) {
   463  		go ctrl.Run(stop)
   464  		expectRunningOrFail(t, ctrl, true)
   465  		ctrl.DeleteRegistry("earlyAdd", "test")
   466  		ctrl.DeleteRegistry("earlyAddAndRun", "test")
   467  	})
   468  	t.Run("AddRegistry after aggregate Run does not start registry", func(t *testing.T) {
   469  		ctrl.AddRegistry(runnableRegistry("missed"))
   470  		expectRunningOrFail(t, ctrl, false)
   471  		ctrl.DeleteRegistry("missed", "test")
   472  		expectRunningOrFail(t, ctrl, true)
   473  	})
   474  	t.Run("AddRegistryAndRun after aggregate Run starts registry", func(t *testing.T) {
   475  		ctrl.AddRegistryAndRun(runnableRegistry("late"), stop)
   476  		expectRunningOrFail(t, ctrl, true)
   477  	})
   478  }