istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/ads_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  package xds_test
    15  
    16  import (
    17  	"fmt"
    18  	"reflect"
    19  	"testing"
    20  	"time"
    21  
    22  	core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    23  	discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
    24  
    25  	networking "istio.io/api/networking/v1alpha3"
    26  	"istio.io/istio/pilot/pkg/features"
    27  	"istio.io/istio/pilot/pkg/model"
    28  	"istio.io/istio/pilot/pkg/status/distribution"
    29  	"istio.io/istio/pilot/pkg/xds"
    30  	v3 "istio.io/istio/pilot/pkg/xds/v3"
    31  	xdsfake "istio.io/istio/pilot/test/xds"
    32  	"istio.io/istio/pilot/test/xdstest"
    33  	"istio.io/istio/pkg/adsc"
    34  	"istio.io/istio/pkg/config"
    35  	"istio.io/istio/pkg/config/host"
    36  	"istio.io/istio/pkg/config/protocol"
    37  	"istio.io/istio/pkg/config/schema/gvk"
    38  	"istio.io/istio/pkg/config/schema/kind"
    39  	"istio.io/istio/pkg/ledger"
    40  	"istio.io/istio/pkg/slices"
    41  	"istio.io/istio/pkg/test"
    42  	"istio.io/istio/pkg/test/util/assert"
    43  	"istio.io/istio/pkg/test/util/retry"
    44  	"istio.io/istio/pkg/util/sets"
    45  	"istio.io/istio/tests/util/leak"
    46  )
    47  
    48  const (
    49  	testConfigNamespace = "default"
    50  
    51  	routeA = "http.80"
    52  	routeB = "https.443.https.my-gateway.testns"
    53  )
    54  
    55  func TestAdsReconnectAfterRestart(t *testing.T) {
    56  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
    57  
    58  	ads := s.ConnectADS().WithType(v3.EndpointType)
    59  	res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{"fake-cluster"}})
    60  	// Close the connection and reconnect
    61  	ads.Cleanup()
    62  
    63  	ads = s.ConnectADS().WithType(v3.EndpointType)
    64  
    65  	// Reconnect with the same resources
    66  	ads.RequestResponseAck(t, &discovery.DiscoveryRequest{
    67  		ResourceNames: []string{"fake-cluster"},
    68  		ResponseNonce: res.Nonce,
    69  		VersionInfo:   res.VersionInfo,
    70  	})
    71  }
    72  
    73  // TestAdsReconnectRequests provides a regression test for a case where Envoy sends an EDS request as the first
    74  // request on a connection.
    75  func TestAdsReconnectRequests(t *testing.T) {
    76  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
    77  
    78  	ads := s.ConnectADS()
    79  	// Send normal CDS and EDS requests
    80  	_ = ads.RequestResponseAck(t, &discovery.DiscoveryRequest{TypeUrl: v3.ClusterType})
    81  	eres := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{TypeUrl: v3.EndpointType, ResourceNames: []string{"my-resource"}})
    82  
    83  	// A push should get a response for both
    84  	s.Discovery.ConfigUpdate(&model.PushRequest{Full: true})
    85  	ads.ExpectResponse(t)
    86  	ads.ExpectResponse(t)
    87  	// Close the connection and reconnect
    88  	ads.Cleanup()
    89  	ads = s.ConnectADS()
    90  
    91  	// Send a request for EDS version 1 - we do not explicitly ACK this.
    92  	ads.Request(t, &discovery.DiscoveryRequest{
    93  		TypeUrl:       v3.EndpointType,
    94  		ResourceNames: []string{"my-resource"},
    95  		ResponseNonce: eres.Nonce,
    96  	})
    97  	// We should get a response
    98  	eres3 := ads.ExpectResponse(t)
    99  	// Now send our CDS request
   100  	ads.RequestResponseAck(t, &discovery.DiscoveryRequest{
   101  		TypeUrl:       v3.ClusterType,
   102  		ResponseNonce: eres.Nonce,
   103  	})
   104  	// Send another request. This is essentially an ACK of eres3. However, envoy expects a response
   105  	ads.RequestResponseAck(t, &discovery.DiscoveryRequest{
   106  		TypeUrl:       v3.EndpointType,
   107  		ResourceNames: []string{"my-resource"},
   108  		ResponseNonce: eres3.Nonce,
   109  	})
   110  }
   111  
   112  func TestAdsUnsubscribe(t *testing.T) {
   113  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   114  
   115  	ads := s.ConnectADS().WithType(v3.EndpointType)
   116  	res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{"fake-cluster"}})
   117  
   118  	ads.Request(t, &discovery.DiscoveryRequest{
   119  		ResourceNames: nil,
   120  		ResponseNonce: res.Nonce,
   121  		VersionInfo:   res.VersionInfo,
   122  	})
   123  	ads.ExpectNoResponse(t)
   124  }
   125  
   126  // Regression for envoy restart and overlapping connections
   127  func TestAdsReconnect(t *testing.T) {
   128  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   129  	ads := s.ConnectADS().WithType(v3.ClusterType)
   130  	ads.RequestResponseAck(t, nil)
   131  
   132  	// envoy restarts and reconnects
   133  	ads2 := s.ConnectADS().WithType(v3.ClusterType)
   134  	ads2.RequestResponseAck(t, nil)
   135  
   136  	// closes old process
   137  	ads.Cleanup()
   138  
   139  	// event happens, expect push to the remaining connection
   140  	xds.AdsPushAll(s.Discovery)
   141  	ads2.ExpectResponse(t)
   142  }
   143  
   144  // Regression for connection with a bad ID
   145  func TestAdsBadId(t *testing.T) {
   146  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   147  	ads := s.ConnectADS().WithID("").WithType(v3.ClusterType)
   148  	xds.AdsPushAll(s.Discovery)
   149  	ads.ExpectNoResponse(t)
   150  }
   151  
   152  func TestVersionNonce(t *testing.T) {
   153  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   154  	ads := s.ConnectADS().WithType(v3.ClusterType)
   155  	resp1 := ads.RequestResponseAck(t, nil)
   156  	fullPush(s)
   157  	resp2 := ads.ExpectResponse(t)
   158  	if !(resp1.VersionInfo < resp2.VersionInfo) {
   159  		t.Fatalf("version should be incrementing: %v -> %v", resp1.VersionInfo, resp2.VersionInfo)
   160  	}
   161  	if resp1.Nonce == resp2.Nonce {
   162  		t.Fatalf("nonce should change %v -> %v", resp1.Nonce, resp2.Nonce)
   163  	}
   164  }
   165  
   166  func TestAdsClusterUpdate(t *testing.T) {
   167  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   168  	ads := s.ConnectADS().WithType(v3.EndpointType)
   169  
   170  	version := ""
   171  	nonce := ""
   172  	sendEDSReqAndVerify := func(clusterName string) {
   173  		res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{
   174  			ResourceNames: []string{clusterName},
   175  			VersionInfo:   version,
   176  			ResponseNonce: nonce,
   177  		})
   178  		version = res.VersionInfo
   179  		nonce = res.Nonce
   180  		got := xdstest.MapKeys(xdstest.ExtractLoadAssignments(xdstest.UnmarshalClusterLoadAssignment(t, res.Resources)))
   181  		if len(got) != 1 {
   182  			t.Fatalf("expected 1 response, got %v", len(got))
   183  		}
   184  		if got[0] != clusterName {
   185  			t.Fatalf("expected cluster %v got %v", clusterName, got[0])
   186  		}
   187  	}
   188  
   189  	cluster1 := "outbound|80||local.default.svc.cluster.local"
   190  	sendEDSReqAndVerify(cluster1)
   191  	cluster2 := "outbound|80||hello.default.svc.cluster.local"
   192  	sendEDSReqAndVerify(cluster2)
   193  }
   194  
   195  // nolint: lll
   196  func TestAdsPushScoping(t *testing.T) {
   197  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   198  
   199  	const (
   200  		svcSuffix = ".testPushScoping.com"
   201  		ns1       = "ns1"
   202  	)
   203  
   204  	removeServiceByNames := func(ns string, names ...string) {
   205  		configsUpdated := sets.New[model.ConfigKey]()
   206  
   207  		for _, name := range names {
   208  			hostname := host.Name(name)
   209  			s.MemRegistry.RemoveService(hostname)
   210  			configsUpdated.Insert(model.ConfigKey{
   211  				Kind:      kind.ServiceEntry,
   212  				Name:      string(hostname),
   213  				Namespace: ns,
   214  			})
   215  		}
   216  
   217  		s.Discovery.ConfigUpdate(&model.PushRequest{Full: true, ConfigsUpdated: configsUpdated})
   218  	}
   219  	removeService := func(ns string, indexes ...int) {
   220  		var names []string
   221  
   222  		for _, i := range indexes {
   223  			names = append(names, fmt.Sprintf("svc%d%s", i, svcSuffix))
   224  		}
   225  
   226  		removeServiceByNames(ns, names...)
   227  	}
   228  	addServiceByNames := func(ns string, names ...string) {
   229  		configsUpdated := sets.New[model.ConfigKey]()
   230  
   231  		for _, name := range names {
   232  			hostname := host.Name(name)
   233  			configsUpdated.Insert(model.ConfigKey{
   234  				Kind:      kind.ServiceEntry,
   235  				Name:      string(hostname),
   236  				Namespace: ns,
   237  			})
   238  
   239  			s.MemRegistry.AddService(&model.Service{
   240  				Hostname:       hostname,
   241  				DefaultAddress: "10.11.0.1",
   242  				Ports: []*model.Port{
   243  					{
   244  						Name:     "http-main",
   245  						Port:     2080,
   246  						Protocol: protocol.HTTP,
   247  					},
   248  				},
   249  				Attributes: model.ServiceAttributes{
   250  					Namespace: ns,
   251  				},
   252  			})
   253  		}
   254  
   255  		s.Discovery.ConfigUpdate(&model.PushRequest{Full: true, ConfigsUpdated: configsUpdated})
   256  	}
   257  	addService := func(ns string, indexes ...int) {
   258  		var hostnames []string
   259  		for _, i := range indexes {
   260  			hostnames = append(hostnames, fmt.Sprintf("svc%d%s", i, svcSuffix))
   261  		}
   262  		addServiceByNames(ns, hostnames...)
   263  	}
   264  
   265  	addServiceInstance := func(hostname host.Name, indexes ...int) {
   266  		for _, i := range indexes {
   267  			s.MemRegistry.AddEndpoint(hostname, "http-main", 2080, "192.168.1.10", i)
   268  		}
   269  
   270  		s.Discovery.ConfigUpdate(&model.PushRequest{Full: false, ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.ServiceEntry, Name: string(hostname), Namespace: testConfigNamespace})})
   271  	}
   272  
   273  	addVirtualService := func(i int, hosts []string, dest string) {
   274  		if _, err := s.Store().Create(config.Config{
   275  			Meta: config.Meta{
   276  				GroupVersionKind: gvk.VirtualService,
   277  				Name:             fmt.Sprintf("vs%d", i), Namespace: testConfigNamespace,
   278  			},
   279  			Spec: &networking.VirtualService{
   280  				Hosts: hosts,
   281  				Http: []*networking.HTTPRoute{{
   282  					Name: "dest-foo",
   283  					Route: []*networking.HTTPRouteDestination{{
   284  						Destination: &networking.Destination{
   285  							Host: dest,
   286  						},
   287  					}},
   288  				}},
   289  				ExportTo: nil,
   290  			},
   291  		}); err != nil {
   292  			t.Fatal(err)
   293  		}
   294  	}
   295  	removeVirtualService := func(i int) {
   296  		s.Store().Delete(gvk.VirtualService, fmt.Sprintf("vs%d", i), testConfigNamespace, nil)
   297  	}
   298  
   299  	addDelegateVirtualService := func(i int, hosts []string, dest string) {
   300  		if _, err := s.Store().Create(config.Config{
   301  			Meta: config.Meta{
   302  				GroupVersionKind: gvk.VirtualService,
   303  				Name:             fmt.Sprintf("rootvs%d", i), Namespace: testConfigNamespace,
   304  			},
   305  			Spec: &networking.VirtualService{
   306  				Hosts: hosts,
   307  
   308  				Http: []*networking.HTTPRoute{{
   309  					Name: "dest-foo",
   310  					Delegate: &networking.Delegate{
   311  						Name:      fmt.Sprintf("delegatevs%d", i),
   312  						Namespace: testConfigNamespace,
   313  					},
   314  				}},
   315  				ExportTo: nil,
   316  			},
   317  		}); err != nil {
   318  			t.Fatal(err)
   319  		}
   320  
   321  		if _, err := s.Store().Create(config.Config{
   322  			Meta: config.Meta{
   323  				GroupVersionKind: gvk.VirtualService,
   324  				Name:             fmt.Sprintf("delegatevs%d", i), Namespace: testConfigNamespace,
   325  			},
   326  			Spec: &networking.VirtualService{
   327  				Http: []*networking.HTTPRoute{{
   328  					Name: "dest-foo",
   329  					Route: []*networking.HTTPRouteDestination{{
   330  						Destination: &networking.Destination{
   331  							Host: dest,
   332  						},
   333  					}},
   334  				}},
   335  				ExportTo: nil,
   336  			},
   337  		}); err != nil {
   338  			t.Fatal(err)
   339  		}
   340  	}
   341  
   342  	updateDelegateVirtualService := func(i int, dest string) {
   343  		if _, err := s.Store().Update(config.Config{
   344  			Meta: config.Meta{
   345  				GroupVersionKind: gvk.VirtualService,
   346  				Name:             fmt.Sprintf("delegatevs%d", i), Namespace: testConfigNamespace,
   347  			},
   348  			Spec: &networking.VirtualService{
   349  				Http: []*networking.HTTPRoute{{
   350  					Name: "dest-foo",
   351  					Headers: &networking.Headers{
   352  						Request: &networking.Headers_HeaderOperations{
   353  							Remove: []string{"any-string"},
   354  						},
   355  					},
   356  					Route: []*networking.HTTPRouteDestination{
   357  						{
   358  							Destination: &networking.Destination{
   359  								Host: dest,
   360  							},
   361  						},
   362  					},
   363  				}},
   364  				ExportTo: nil,
   365  			},
   366  		}); err != nil {
   367  			t.Fatal(err)
   368  		}
   369  	}
   370  
   371  	removeDelegateVirtualService := func(i int) {
   372  		s.Store().Delete(gvk.VirtualService, fmt.Sprintf("rootvs%d", i), testConfigNamespace, nil)
   373  		s.Store().Delete(gvk.VirtualService, fmt.Sprintf("delegatevs%d", i), testConfigNamespace, nil)
   374  	}
   375  
   376  	addDestinationRule := func(i int, host string) {
   377  		if _, err := s.Store().Create(config.Config{
   378  			Meta: config.Meta{
   379  				GroupVersionKind: gvk.DestinationRule,
   380  				Name:             fmt.Sprintf("dr%d", i), Namespace: testConfigNamespace,
   381  			},
   382  			Spec: &networking.DestinationRule{
   383  				Host:     host,
   384  				ExportTo: nil,
   385  			},
   386  		}); err != nil {
   387  			t.Fatal(err)
   388  		}
   389  	}
   390  	removeDestinationRule := func(i int) {
   391  		s.Store().Delete(gvk.DestinationRule, fmt.Sprintf("dr%d", i), testConfigNamespace, nil)
   392  	}
   393  
   394  	sc := &networking.Sidecar{
   395  		Egress: []*networking.IstioEgressListener{
   396  			{
   397  				Hosts: []string{testConfigNamespace + "/*" + svcSuffix},
   398  			},
   399  		},
   400  	}
   401  	scc := config.Config{
   402  		Meta: config.Meta{
   403  			GroupVersionKind: gvk.Sidecar,
   404  			Name:             "sc", Namespace: testConfigNamespace,
   405  		},
   406  		Spec: sc,
   407  	}
   408  	notMatchedScc := config.Config{
   409  		Meta: config.Meta{
   410  			GroupVersionKind: gvk.Sidecar,
   411  			Name:             "notMatchedSc", Namespace: testConfigNamespace,
   412  		},
   413  		Spec: &networking.Sidecar{
   414  			WorkloadSelector: &networking.WorkloadSelector{
   415  				Labels: map[string]string{"notMatched": "notMatched"},
   416  			},
   417  		},
   418  	}
   419  	if _, err := s.Store().Create(scc); err != nil {
   420  		t.Fatal(err)
   421  	}
   422  	addService(testConfigNamespace, 1, 2, 3)
   423  
   424  	adscConn := s.Connect(nil, nil, nil)
   425  	defer adscConn.Close()
   426  	type svcCase struct {
   427  		desc string
   428  
   429  		ev          model.Event
   430  		svcIndexes  []int
   431  		svcNames    []string
   432  		ns          string
   433  		instIndexes []struct {
   434  			name    string
   435  			indexes []int
   436  		}
   437  		vsIndexes []struct {
   438  			index int
   439  			hosts []string
   440  			dest  string
   441  		}
   442  		delegatevsIndexes []struct {
   443  			index int
   444  			hosts []string
   445  			dest  string
   446  		}
   447  		drIndexes []struct {
   448  			index int
   449  			host  string
   450  		}
   451  		cfgs []config.Config
   452  
   453  		expectedUpdates   []string
   454  		unexpectedUpdates []string
   455  	}
   456  	svcCases := []svcCase{
   457  		{
   458  			desc:            "Add a scoped service",
   459  			ev:              model.EventAdd,
   460  			svcIndexes:      []int{4},
   461  			ns:              testConfigNamespace,
   462  			expectedUpdates: []string{v3.ListenerType},
   463  		}, // then: default 1,2,3,4
   464  		{
   465  			desc: "Add instances to a scoped service",
   466  			ev:   model.EventAdd,
   467  			instIndexes: []struct {
   468  				name    string
   469  				indexes []int
   470  			}{{fmt.Sprintf("svc%d%s", 4, svcSuffix), []int{1, 2}}},
   471  			ns:              testConfigNamespace,
   472  			expectedUpdates: []string{v3.EndpointType},
   473  		}, // then: default 1,2,3,4
   474  		{
   475  			desc: "Add virtual service to a scoped service",
   476  			ev:   model.EventAdd,
   477  			vsIndexes: []struct {
   478  				index int
   479  				hosts []string
   480  				dest  string
   481  			}{{index: 4, hosts: []string{fmt.Sprintf("svc%d%s", 4, svcSuffix)}, dest: "unknown-svc"}},
   482  			expectedUpdates: []string{v3.ListenerType},
   483  		},
   484  		{
   485  			desc: "Delete virtual service of a scoped service",
   486  			ev:   model.EventDelete,
   487  			vsIndexes: []struct {
   488  				index int
   489  				hosts []string
   490  				dest  string
   491  			}{{index: 4}},
   492  			expectedUpdates: []string{v3.ListenerType},
   493  		},
   494  		{
   495  			desc: "Add destination rule to a scoped service",
   496  			ev:   model.EventAdd,
   497  			drIndexes: []struct {
   498  				index int
   499  				host  string
   500  			}{{4, fmt.Sprintf("svc%d%s", 4, svcSuffix)}},
   501  			expectedUpdates: []string{v3.ClusterType},
   502  		},
   503  		{
   504  			desc: "Delete destination rule of a scoped service",
   505  			ev:   model.EventDelete,
   506  			drIndexes: []struct {
   507  				index int
   508  				host  string
   509  			}{{index: 4}},
   510  			expectedUpdates: []string{v3.ClusterType},
   511  		},
   512  		{
   513  			desc:              "Add a unscoped(name not match) service",
   514  			ev:                model.EventAdd,
   515  			svcNames:          []string{"foo.com"},
   516  			ns:                testConfigNamespace,
   517  			unexpectedUpdates: []string{v3.ClusterType},
   518  		}, // then: default 1,2,3,4, foo.com; ns1: 11
   519  		{
   520  			desc: "Add instances to an unscoped service",
   521  			ev:   model.EventAdd,
   522  			instIndexes: []struct {
   523  				name    string
   524  				indexes []int
   525  			}{{"foo.com", []int{1, 2}}},
   526  			ns:                testConfigNamespace,
   527  			unexpectedUpdates: []string{v3.EndpointType},
   528  		}, // then: default 1,2,3,4
   529  		{
   530  			desc:              "Add a unscoped(ns not match) service",
   531  			ev:                model.EventAdd,
   532  			svcIndexes:        []int{11},
   533  			ns:                ns1,
   534  			unexpectedUpdates: []string{v3.ClusterType},
   535  		}, // then: default 1,2,3,4, foo.com; ns1: 11
   536  		{
   537  			desc: "Add virtual service to an unscoped service",
   538  			ev:   model.EventAdd,
   539  			vsIndexes: []struct {
   540  				index int
   541  				hosts []string
   542  				dest  string
   543  			}{{index: 0, hosts: []string{"foo.com"}, dest: "unknown-service"}},
   544  			unexpectedUpdates: []string{v3.ClusterType},
   545  		},
   546  		{
   547  			desc: "Delete virtual service of a unscoped service",
   548  			ev:   model.EventDelete,
   549  			vsIndexes: []struct {
   550  				index int
   551  				hosts []string
   552  				dest  string
   553  			}{{index: 0}},
   554  			unexpectedUpdates: []string{v3.ClusterType},
   555  		},
   556  		{
   557  			desc: "Add destination rule to an unscoped service",
   558  			ev:   model.EventAdd,
   559  			drIndexes: []struct {
   560  				index int
   561  				host  string
   562  			}{{0, "foo.com"}},
   563  			unexpectedUpdates: []string{v3.ClusterType},
   564  		},
   565  		{
   566  			desc: "Delete destination rule of a unscoped service",
   567  			ev:   model.EventDelete,
   568  			drIndexes: []struct {
   569  				index int
   570  				host  string
   571  			}{{index: 0}},
   572  			unexpectedUpdates: []string{v3.ClusterType},
   573  		},
   574  		{
   575  			desc: "Add virtual service for scoped service with transitively scoped dest svc",
   576  			ev:   model.EventAdd,
   577  			vsIndexes: []struct {
   578  				index int
   579  				hosts []string
   580  				dest  string
   581  			}{{index: 4, hosts: []string{fmt.Sprintf("svc%d%s", 4, svcSuffix)}, dest: "foo.com"}},
   582  			expectedUpdates: []string{v3.ClusterType, v3.EndpointType},
   583  		},
   584  		{
   585  			desc: "Add instances for transitively scoped svc",
   586  			ev:   model.EventAdd,
   587  			instIndexes: []struct {
   588  				name    string
   589  				indexes []int
   590  			}{{"foo.com", []int{1, 2}}},
   591  			ns:              testConfigNamespace,
   592  			expectedUpdates: []string{v3.EndpointType},
   593  		},
   594  		{
   595  			desc: "Delete virtual service for scoped service with transitively scoped dest svc",
   596  			ev:   model.EventDelete,
   597  			vsIndexes: []struct {
   598  				index int
   599  				hosts []string
   600  				dest  string
   601  			}{{index: 4}},
   602  			expectedUpdates: []string{v3.ClusterType},
   603  		},
   604  		{
   605  			desc: "Add delegation virtual service for scoped service with transitively scoped dest svc",
   606  			ev:   model.EventAdd,
   607  			delegatevsIndexes: []struct {
   608  				index int
   609  				hosts []string
   610  				dest  string
   611  			}{{index: 4, hosts: []string{fmt.Sprintf("svc%d%s", 4, svcSuffix)}, dest: "foo.com"}},
   612  			expectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType, v3.EndpointType},
   613  		},
   614  		{
   615  			desc: "Update delegate virtual service should trigger full push",
   616  			ev:   model.EventUpdate,
   617  			delegatevsIndexes: []struct {
   618  				index int
   619  				hosts []string
   620  				dest  string
   621  			}{{index: 4, hosts: []string{fmt.Sprintf("svc%d%s", 4, svcSuffix)}, dest: "foo.com"}},
   622  			expectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType},
   623  		},
   624  		{
   625  			desc: "Delete delegate virtual service for scoped service with transitively scoped dest svc",
   626  			ev:   model.EventDelete,
   627  			delegatevsIndexes: []struct {
   628  				index int
   629  				hosts []string
   630  				dest  string
   631  			}{{index: 4}},
   632  			expectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType},
   633  		},
   634  		{
   635  			desc:            "Remove a scoped service",
   636  			ev:              model.EventDelete,
   637  			svcIndexes:      []int{4},
   638  			ns:              testConfigNamespace,
   639  			expectedUpdates: []string{v3.ListenerType},
   640  		}, // then: default 1,2,3, foo.com; ns: 11
   641  		{
   642  			desc:              "Remove a unscoped(name not match) service",
   643  			ev:                model.EventDelete,
   644  			svcNames:          []string{"foo.com"},
   645  			ns:                testConfigNamespace,
   646  			unexpectedUpdates: []string{v3.ClusterType},
   647  		}, // then: default 1,2,3; ns1: 11
   648  		{
   649  			desc:              "Remove a unscoped(ns not match) service",
   650  			ev:                model.EventDelete,
   651  			svcIndexes:        []int{11},
   652  			ns:                ns1,
   653  			unexpectedUpdates: []string{v3.ClusterType},
   654  		}, // then: default 1,2,3
   655  		{
   656  			desc:              "Add an unmatched Sidecar config",
   657  			ev:                model.EventAdd,
   658  			cfgs:              []config.Config{notMatchedScc},
   659  			ns:                testConfigNamespace,
   660  			unexpectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType, v3.EndpointType},
   661  		},
   662  		{
   663  			desc:            "Update the Sidecar config",
   664  			ev:              model.EventUpdate,
   665  			cfgs:            []config.Config{scc},
   666  			ns:              testConfigNamespace,
   667  			expectedUpdates: []string{v3.ListenerType, v3.RouteType, v3.ClusterType, v3.EndpointType},
   668  		},
   669  	}
   670  
   671  	for _, c := range svcCases {
   672  		t.Run(c.desc, func(t *testing.T) {
   673  			// Let events from previous tests complete
   674  			time.Sleep(time.Millisecond * 50)
   675  			adscConn.WaitClear()
   676  			var wantUpdates []string
   677  			wantUpdates = append(wantUpdates, c.expectedUpdates...)
   678  			wantUpdates = append(wantUpdates, c.unexpectedUpdates...)
   679  
   680  			switch c.ev {
   681  			case model.EventAdd:
   682  				if len(c.svcIndexes) > 0 {
   683  					addService(c.ns, c.svcIndexes...)
   684  				}
   685  				if len(c.svcNames) > 0 {
   686  					addServiceByNames(c.ns, c.svcNames...)
   687  				}
   688  				if len(c.instIndexes) > 0 {
   689  					for _, instIndex := range c.instIndexes {
   690  						addServiceInstance(host.Name(instIndex.name), instIndex.indexes...)
   691  					}
   692  				}
   693  				if len(c.vsIndexes) > 0 {
   694  					for _, vsIndex := range c.vsIndexes {
   695  						addVirtualService(vsIndex.index, vsIndex.hosts, vsIndex.dest)
   696  					}
   697  				}
   698  				if len(c.delegatevsIndexes) > 0 {
   699  					for _, vsIndex := range c.delegatevsIndexes {
   700  						addDelegateVirtualService(vsIndex.index, vsIndex.hosts, vsIndex.dest)
   701  					}
   702  				}
   703  				if len(c.drIndexes) > 0 {
   704  					for _, drIndex := range c.drIndexes {
   705  						addDestinationRule(drIndex.index, drIndex.host)
   706  					}
   707  				}
   708  				if len(c.cfgs) > 0 {
   709  					for _, cfg := range c.cfgs {
   710  						if _, err := s.Store().Create(cfg); err != nil {
   711  							t.Fatal(err)
   712  						}
   713  					}
   714  				}
   715  			case model.EventUpdate:
   716  				if len(c.delegatevsIndexes) > 0 {
   717  					for _, vsIndex := range c.delegatevsIndexes {
   718  						updateDelegateVirtualService(vsIndex.index, vsIndex.dest)
   719  					}
   720  				}
   721  				if len(c.cfgs) > 0 {
   722  					for _, cfg := range c.cfgs {
   723  						if _, err := s.Store().Update(cfg); err != nil {
   724  							t.Fatal(err)
   725  						}
   726  					}
   727  				}
   728  			case model.EventDelete:
   729  				if len(c.svcIndexes) > 0 {
   730  					removeService(c.ns, c.svcIndexes...)
   731  				}
   732  				if len(c.svcNames) > 0 {
   733  					removeServiceByNames(c.ns, c.svcNames...)
   734  				}
   735  				if len(c.vsIndexes) > 0 {
   736  					for _, vsIndex := range c.vsIndexes {
   737  						removeVirtualService(vsIndex.index)
   738  					}
   739  				}
   740  				if len(c.delegatevsIndexes) > 0 {
   741  					for _, vsIndex := range c.delegatevsIndexes {
   742  						removeDelegateVirtualService(vsIndex.index)
   743  					}
   744  				}
   745  				if len(c.drIndexes) > 0 {
   746  					for _, drIndex := range c.drIndexes {
   747  						removeDestinationRule(drIndex.index)
   748  					}
   749  				}
   750  			default:
   751  				t.Fatalf("wrong event for case %v", c)
   752  			}
   753  
   754  			timeout := time.Millisecond * 200
   755  			upd, _ := adscConn.Wait(timeout, wantUpdates...)
   756  			for _, expect := range c.expectedUpdates {
   757  				if !slices.Contains(upd, expect) {
   758  					t.Fatalf("expected update %s not in updates %v", expect, upd)
   759  				}
   760  			}
   761  			for _, unexpect := range c.unexpectedUpdates {
   762  				if slices.Contains(upd, unexpect) {
   763  					t.Fatalf("expected to not get update %s, but it is in updates %v", unexpect, upd)
   764  				}
   765  			}
   766  		})
   767  	}
   768  }
   769  
   770  func TestAdsUpdate(t *testing.T) {
   771  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   772  	ads := s.ConnectADS()
   773  
   774  	s.MemRegistry.AddService(&model.Service{
   775  		Hostname:       "adsupdate.default.svc.cluster.local",
   776  		DefaultAddress: "10.11.0.1",
   777  		Ports: []*model.Port{
   778  			{
   779  				Name:     "http-main",
   780  				Port:     2080,
   781  				Protocol: protocol.HTTP,
   782  			},
   783  		},
   784  		Attributes: model.ServiceAttributes{
   785  			Name:      "adsupdate",
   786  			Namespace: "default",
   787  		},
   788  	})
   789  	s.Discovery.ConfigUpdate(&model.PushRequest{Full: true})
   790  	time.Sleep(time.Millisecond * 200)
   791  	s.MemRegistry.SetEndpoints("adsupdate.default.svc.cluster.local", "default",
   792  		newEndpointWithAccount("10.2.0.1", "hello-sa", "v1"))
   793  
   794  	cluster := "outbound|2080||adsupdate.default.svc.cluster.local"
   795  	res := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{
   796  		ResourceNames: []string{cluster},
   797  		TypeUrl:       v3.EndpointType,
   798  	})
   799  	eps, f := xdstest.ExtractLoadAssignments(xdstest.UnmarshalClusterLoadAssignment(t, res.GetResources()))[cluster]
   800  	if !f {
   801  		t.Fatalf("did not find cluster %v", cluster)
   802  	}
   803  	if !reflect.DeepEqual(eps, []string{"10.2.0.1:80"}) {
   804  		t.Fatalf("expected endpoints [10.2.0.1:80] got %v", eps)
   805  	}
   806  
   807  	_ = s.MemRegistry.AddEndpoint("adsupdate.default.svc.cluster.local",
   808  		"http-main", 2080, "10.1.7.1", 1080)
   809  
   810  	// will trigger recompute and push for all clients - including some that may be closing
   811  	// This reproduced the 'push on closed connection' bug.
   812  	xds.AdsPushAll(s.Discovery)
   813  	res1 := ads.ExpectResponse(t)
   814  	xdstest.UnmarshalClusterLoadAssignment(t, res1.GetResources())
   815  }
   816  
   817  func TestEnvoyRDSProtocolError(t *testing.T) {
   818  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   819  	ads := s.ConnectADS().WithType(v3.RouteType)
   820  	ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeA}})
   821  
   822  	xds.AdsPushAll(s.Discovery)
   823  	res := ads.ExpectResponse(t)
   824  
   825  	// send empty response and validate no response is returned.
   826  	ads.Request(t, &discovery.DiscoveryRequest{
   827  		ResourceNames: nil,
   828  		VersionInfo:   res.VersionInfo,
   829  		ResponseNonce: res.Nonce,
   830  	})
   831  	ads.ExpectNoResponse(t)
   832  
   833  	// Refresh routes
   834  	ads.Request(t, &discovery.DiscoveryRequest{
   835  		ResourceNames: []string{routeA, routeB},
   836  		VersionInfo:   res.VersionInfo,
   837  		ResponseNonce: res.Nonce,
   838  	})
   839  }
   840  
   841  func TestEnvoyRDSUpdatedRouteRequest(t *testing.T) {
   842  	expectRoutes := func(resp *discovery.DiscoveryResponse, expected ...string) {
   843  		t.Helper()
   844  		got := xdstest.MapKeys(xdstest.ExtractRouteConfigurations(xdstest.UnmarshalRouteConfiguration(t, resp.Resources)))
   845  		if !reflect.DeepEqual(expected, got) {
   846  			t.Fatalf("expected routes %v got %v", expected, got)
   847  		}
   848  	}
   849  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   850  	ads := s.ConnectADS().WithType(v3.RouteType)
   851  	resp := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeA}})
   852  	expectRoutes(resp, routeA)
   853  
   854  	xds.AdsPushAll(s.Discovery)
   855  	resp = ads.ExpectResponse(t)
   856  	expectRoutes(resp, routeA)
   857  
   858  	// Test update from A -> B
   859  	resp = ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeB}})
   860  	expectRoutes(resp, routeB)
   861  
   862  	// Test update from B -> A, B
   863  	resp = ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeA, routeB}})
   864  	expectRoutes(resp, routeA, routeB)
   865  
   866  	// Test update from B, B -> A
   867  	resp = ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{routeA}})
   868  	expectRoutes(resp, routeA)
   869  }
   870  
   871  func TestEdsCache(t *testing.T) {
   872  	makeEndpoint := func(addr []*networking.WorkloadEntry) config.Config {
   873  		return config.Config{
   874  			Meta: config.Meta{
   875  				Name:             "service",
   876  				Namespace:        "default",
   877  				GroupVersionKind: gvk.ServiceEntry,
   878  			},
   879  			Spec: &networking.ServiceEntry{
   880  				Hosts: []string{"foo.com"},
   881  				Ports: []*networking.ServicePort{{
   882  					Number:   80,
   883  					Protocol: "HTTP",
   884  					Name:     "http",
   885  				}},
   886  				Resolution: networking.ServiceEntry_STATIC,
   887  				Endpoints:  addr,
   888  			},
   889  		}
   890  	}
   891  	assertEndpoints := func(a *adsc.ADSC, addr ...string) {
   892  		t.Helper()
   893  		retry.UntilSuccessOrFail(t, func() error {
   894  			got := sets.New(xdstest.ExtractEndpoints(a.GetEndpoints()["outbound|80||foo.com"])...)
   895  			want := sets.New(addr...)
   896  
   897  			if !got.Equals(want) {
   898  				return fmt.Errorf("invalid endpoints, got %v want %v", got, addr)
   899  			}
   900  			return nil
   901  		}, retry.Timeout(time.Second*5))
   902  	}
   903  
   904  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{
   905  		Configs: []config.Config{
   906  			makeEndpoint([]*networking.WorkloadEntry{
   907  				{Address: "1.2.3.4", Locality: "region/zone"},
   908  				{Address: "1.2.3.5", Locality: "notmatch"},
   909  			}),
   910  		},
   911  	})
   912  	ads := s.Connect(&model.Proxy{Locality: &core.Locality{Region: "region"}}, nil, watchAll)
   913  
   914  	assertEndpoints(ads, "1.2.3.4:80", "1.2.3.5:80")
   915  	t.Logf("endpoints: %+v", xdstest.ExtractEndpoints(ads.GetEndpoints()["outbound|80||foo.com"]))
   916  
   917  	if _, err := s.Store().Update(makeEndpoint([]*networking.WorkloadEntry{
   918  		{Address: "1.2.3.6", Locality: "region/zone"},
   919  		{Address: "1.2.3.5", Locality: "notmatch"},
   920  	})); err != nil {
   921  		t.Fatal(err)
   922  	}
   923  	if _, err := ads.Wait(time.Second*5, v3.EndpointType); err != nil {
   924  		t.Fatal(err)
   925  	}
   926  	assertEndpoints(ads, "1.2.3.6:80", "1.2.3.5:80")
   927  	t.Logf("endpoints: %+v", xdstest.ExtractEndpoints(ads.GetEndpoints()["outbound|80||foo.com"]))
   928  
   929  	ads.WaitClear()
   930  	if _, err := s.Store().Create(config.Config{
   931  		Meta: config.Meta{
   932  			Name:             "service",
   933  			Namespace:        "default",
   934  			GroupVersionKind: gvk.DestinationRule,
   935  		},
   936  		Spec: &networking.DestinationRule{
   937  			Host: "foo.com",
   938  			TrafficPolicy: &networking.TrafficPolicy{
   939  				OutlierDetection: &networking.OutlierDetection{},
   940  			},
   941  		},
   942  	}); err != nil {
   943  		t.Fatal(err)
   944  	}
   945  	if _, err := ads.Wait(time.Second*5, v3.EndpointType); err != nil {
   946  		t.Fatal(err)
   947  	}
   948  	assertEndpoints(ads, "1.2.3.6:80", "1.2.3.5:80")
   949  	retry.UntilSuccessOrFail(t, func() error {
   950  		found := false
   951  		for _, ep := range ads.GetEndpoints()["outbound|80||foo.com"].Endpoints {
   952  			if ep.Priority == 1 {
   953  				found = true
   954  			}
   955  		}
   956  		if !found {
   957  			return fmt.Errorf("locality did not update")
   958  		}
   959  		return nil
   960  	}, retry.Timeout(time.Second*5))
   961  
   962  	ads.WaitClear()
   963  
   964  	ep := makeEndpoint([]*networking.WorkloadEntry{{Address: "1.2.3.6", Locality: "region/zone"}, {Address: "1.2.3.5", Locality: "notmatch"}})
   965  	ep.Spec.(*networking.ServiceEntry).Resolution = networking.ServiceEntry_DNS
   966  	if _, err := s.Store().Update(ep); err != nil {
   967  		t.Fatal(err)
   968  	}
   969  	if _, err := ads.Wait(time.Second*5, v3.EndpointType); err != nil {
   970  		t.Fatal(err)
   971  	}
   972  	assertEndpoints(ads)
   973  	t.Logf("endpoints: %+v", ads.GetEndpoints())
   974  }
   975  
   976  // TestPushQueueLeak is a regression test for https://github.com/grpc/grpc-go/issues/4758
   977  func TestPushQueueLeak(t *testing.T) {
   978  	ds := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   979  	p := ds.ConnectADS()
   980  	p.RequestResponseAck(t, nil)
   981  	for _, c := range ds.Discovery.AllClients() {
   982  		leak.MustGarbageCollect(t, c)
   983  	}
   984  	ds.Discovery.AdsPushAll(&model.PushRequest{Push: ds.PushContext()})
   985  	p.Cleanup()
   986  }
   987  
   988  func TestDistribution(t *testing.T) {
   989  	xds.ResetConnectionNumberForTest()
   990  	s := xdsfake.NewFakeDiscoveryServer(t, xdsfake.FakeOptions{})
   991  	expectNonce := func(nonce, ty string) {
   992  		t.Helper()
   993  		assert.EventuallyEqual(t, func() string {
   994  			return s.Discovery.StatusReporter.QueryLastNonce("test.default-1", ty)
   995  		}, nonce[:xds.VersionLen])
   996  	}
   997  
   998  	ledger := ledger.Make(time.Minute)
   999  	ledger.Put("key", "value") // If there is no config, ledger would be empty
  1000  	s.Env().SetLedger(ledger)
  1001  	reporter := &distribution.Reporter{
  1002  		UpdateInterval: features.StatusUpdateInterval,
  1003  	}
  1004  	reporter.Init(s.Env().GetLedger(), test.NewStop(t))
  1005  	s.Discovery.StatusReporter = reporter
  1006  
  1007  	ads := s.ConnectADS().WithType(v3.ClusterType)
  1008  	// Subscribe to clusters
  1009  	res1 := ads.RequestResponseAck(t, &discovery.DiscoveryRequest{ResourceNames: []string{"fake-cluster"}})
  1010  	expectNonce(res1.Nonce, v3.ClusterType)
  1011  
  1012  	// Send a push
  1013  	s.Discovery.Push(&model.PushRequest{Full: true, Push: s.Env().PushContext()})
  1014  	res := ads.ExpectResponse(t)
  1015  	// Not yet ACKed, should return last one
  1016  	expectNonce(res1.Nonce, v3.ClusterType)
  1017  	expectNonce(res.Nonce, v3.RouteType)
  1018  
  1019  	ads.Request(t, &discovery.DiscoveryRequest{
  1020  		VersionInfo:   res.VersionInfo,
  1021  		ResourceNames: []string{"fake-cluster"},
  1022  		TypeUrl:       v3.ClusterType,
  1023  		ResponseNonce: res.Nonce,
  1024  	})
  1025  	// After ACK, should be updated
  1026  	expectNonce(res.Nonce, v3.ClusterType)
  1027  	// Types we are not subscribed to are also updated
  1028  	expectNonce(res.Nonce, v3.RouteType)
  1029  	// Ledger has no explicit close, only through GC, so we need to make sure it can be GCed
  1030  	s.Env().SetLedger(nil)
  1031  	s.Discovery.StatusReporter = nil
  1032  }