istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/xds/delta_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 xds_test 16 17 import ( 18 "fmt" 19 "reflect" 20 "testing" 21 22 discovery "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3" 23 24 "istio.io/istio/pilot/pkg/features" 25 "istio.io/istio/pilot/pkg/model" 26 v3 "istio.io/istio/pilot/pkg/xds/v3" 27 "istio.io/istio/pilot/test/xds" 28 "istio.io/istio/pilot/test/xdstest" 29 "istio.io/istio/pkg/config/protocol" 30 "istio.io/istio/pkg/config/schema/kind" 31 "istio.io/istio/pkg/slices" 32 "istio.io/istio/pkg/test" 33 "istio.io/istio/pkg/test/util/assert" 34 "istio.io/istio/pkg/util/sets" 35 "istio.io/istio/pkg/workloadapi" 36 xdsserver "istio.io/istio/pkg/xds" 37 ) 38 39 func TestDeltaAds(t *testing.T) { 40 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{}) 41 ads := s.ConnectDeltaADS().WithType(v3.ClusterType) 42 ads.RequestResponseAck(nil) 43 } 44 45 func TestDeltaAdsClusterUpdate(t *testing.T) { 46 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{}) 47 ads := s.ConnectDeltaADS().WithType(v3.EndpointType) 48 nonce := "" 49 sendEDSReqAndVerify := func(add, remove, expect []string) { 50 t.Helper() 51 res := ads.RequestResponseAck(&discovery.DeltaDiscoveryRequest{ 52 ResponseNonce: nonce, 53 ResourceNamesSubscribe: add, 54 ResourceNamesUnsubscribe: remove, 55 }) 56 nonce = res.Nonce 57 got := xdstest.MapKeys(xdstest.ExtractLoadAssignments(xdstest.UnmarshalClusterLoadAssignment(t, xdsserver.ResourcesToAny(res.Resources)))) 58 if !reflect.DeepEqual(expect, got) { 59 t.Fatalf("expected clusters %v got %v", expect, got) 60 } 61 } 62 63 sendEDSReqAndVerify([]string{"outbound|80||local.default.svc.cluster.local"}, nil, []string{"outbound|80||local.default.svc.cluster.local"}) 64 // Only send the one that is requested 65 sendEDSReqAndVerify([]string{"outbound|81||local.default.svc.cluster.local"}, nil, []string{"outbound|81||local.default.svc.cluster.local"}) 66 ads.Request(&discovery.DeltaDiscoveryRequest{ 67 ResponseNonce: nonce, 68 ResourceNamesUnsubscribe: []string{"outbound|81||local.default.svc.cluster.local"}, 69 }) 70 ads.ExpectNoResponse() 71 } 72 73 func TestDeltaCDS(t *testing.T) { 74 base := sets.New("BlackHoleCluster", "PassthroughCluster", "InboundPassthroughClusterIpv4") 75 assertResources := func(resp *discovery.DeltaDiscoveryResponse, names ...string) { 76 t.Helper() 77 got := slices.Map(resp.Resources, (*discovery.Resource).GetName) 78 79 assert.Equal(t, sets.New(got...), sets.New(names...).Merge(base)) 80 } 81 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{}) 82 addTestClientEndpoints(s.MemRegistry) 83 s.MemRegistry.AddHTTPService(edsIncSvc, edsIncVip, 8080) 84 s.MemRegistry.SetEndpoints(edsIncSvc, "", 85 newEndpointWithAccount("127.0.0.1", "hello-sa", "v1")) 86 // Wait until the above debounce, to ensure we can precisely check XDS responses without spurious pushes 87 s.EnsureSynced(t) 88 89 ads := s.ConnectDeltaADS().WithID("sidecar~127.0.0.1~test.default~default.svc.cluster.local") 90 91 // Initially we get everything 92 ads.Request(&discovery.DeltaDiscoveryRequest{ 93 ResourceNamesSubscribe: []string{}, 94 }) 95 resp := ads.ExpectResponse() 96 assertResources(resp, "outbound|80||test-1.default", "outbound|8080||eds.test.svc.cluster.local", "inbound|80||") 97 assert.Equal(t, resp.RemovedResources, nil) 98 99 // On remove, just get the removal 100 s.MemRegistry.RemoveService("test-1.default") 101 resp = ads.ExpectResponse() 102 assertResources(resp, "inbound|80||") // currently we always send the inbound stuff. Not ideal, but acceptable 103 assert.Equal(t, resp.RemovedResources, []string{"outbound|80||test-1.default"}) 104 105 // Another removal should behave the same 106 s.MemRegistry.RemoveService("eds.test.svc.cluster.local") 107 resp = ads.ExpectResponse() 108 assertResources(resp) 109 assert.Equal(t, resp.RemovedResources, []string{"inbound|80||", "outbound|8080||eds.test.svc.cluster.local"}) 110 } 111 112 func TestDeltaCDSReconnect(t *testing.T) { 113 base := sets.New("BlackHoleCluster", "PassthroughCluster", "InboundPassthroughClusterIpv4") 114 assertResources := func(resp *discovery.DeltaDiscoveryResponse, names ...string) { 115 t.Helper() 116 got := slices.Map(resp.Resources, (*discovery.Resource).GetName) 117 118 assert.Equal(t, sets.New(got...), sets.New(names...).Merge(base)) 119 } 120 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{}) 121 addTestClientEndpoints(s.MemRegistry) 122 s.MemRegistry.AddHTTPService(edsIncSvc, edsIncVip, 8080) 123 s.MemRegistry.SetEndpoints(edsIncSvc, "", 124 newEndpointWithAccount("127.0.0.1", "hello-sa", "v1")) 125 // Wait until the above debounce, to ensure we can precisely check XDS responses without spurious pushes 126 s.EnsureSynced(t) 127 128 ads := s.ConnectDeltaADS() 129 130 // Initially we get everything 131 resp := ads.RequestResponseAck(&discovery.DeltaDiscoveryRequest{}) 132 assertResources(resp, "outbound|80||test-1.default", "outbound|8080||eds.test.svc.cluster.local") 133 assert.Equal(t, resp.RemovedResources, nil) 134 135 // Disconnect and remove a service 136 ads.Cleanup() 137 s.MemRegistry.RemoveService("test-1.default") 138 s.MemRegistry.AddHTTPService("eds2.test.svc.cluster.local", "10.10.1.3", 8080) 139 s.EnsureSynced(t) 140 ads = s.ConnectDeltaADS() 141 resp = ads.RequestResponseAck(&discovery.DeltaDiscoveryRequest{ 142 InitialResourceVersions: map[string]string{ 143 "outbound|80||test-1.default": "", 144 "outbound|8080||eds.test.svc.cluster.local": "", 145 }, 146 }) 147 assertResources(resp, "outbound|8080||eds.test.svc.cluster.local", "outbound|8080||eds2.test.svc.cluster.local") 148 assert.Equal(t, resp.RemovedResources, []string{"outbound|80||test-1.default"}) 149 150 // Another removal should behave the same 151 s.MemRegistry.RemoveService("eds.test.svc.cluster.local") 152 resp = ads.ExpectResponse() 153 // ACK 154 ads.Request(&discovery.DeltaDiscoveryRequest{ 155 TypeUrl: resp.TypeUrl, 156 ResponseNonce: resp.Nonce, 157 }) 158 assertResources(resp) 159 assert.Equal(t, resp.RemovedResources, []string{"outbound|8080||eds.test.svc.cluster.local"}) 160 161 // Another removal should behave the same 162 s.MemRegistry.RemoveService("eds2.test.svc.cluster.local") 163 resp = ads.ExpectResponse() 164 assertResources(resp) 165 assert.Equal(t, resp.RemovedResources, []string{"outbound|8080||eds2.test.svc.cluster.local"}) 166 } 167 168 func TestDeltaEDS(t *testing.T) { 169 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{ 170 ConfigString: mustReadFile(t, "tests/testdata/config/destination-rule-locality.yaml"), 171 }) 172 addTestClientEndpoints(s.MemRegistry) 173 s.MemRegistry.AddHTTPService(edsIncSvc, edsIncVip, 8080) 174 s.MemRegistry.SetEndpoints(edsIncSvc, "", 175 newEndpointWithAccount("127.0.0.1", "hello-sa", "v1")) 176 177 // Wait until the above debounce, to ensure we can precisely check XDS responses without spurious pushes 178 s.EnsureSynced(t) 179 180 ads := s.ConnectDeltaADS().WithType(v3.EndpointType) 181 ads.Request(&discovery.DeltaDiscoveryRequest{ 182 ResourceNamesSubscribe: []string{"outbound|80||test-1.default"}, 183 }) 184 resp := ads.ExpectResponse() 185 if len(resp.Resources) != 1 || resp.Resources[0].Name != "outbound|80||test-1.default" { 186 t.Fatalf("received unexpected eds resource %v", resp.Resources) 187 } 188 if len(resp.RemovedResources) != 0 { 189 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 190 } 191 192 ads.Request(&discovery.DeltaDiscoveryRequest{ 193 ResourceNamesSubscribe: []string{"outbound|8080||" + edsIncSvc}, 194 }) 195 resp = ads.ExpectResponse() 196 if len(resp.Resources) != 1 || resp.Resources[0].Name != "outbound|8080||"+edsIncSvc { 197 t.Fatalf("received unexpected eds resource %v", resp.Resources) 198 } 199 if len(resp.RemovedResources) != 0 { 200 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 201 } 202 203 // update endpoint 204 s.MemRegistry.SetEndpoints(edsIncSvc, "", 205 newEndpointWithAccount("127.0.0.2", "hello-sa", "v1")) 206 resp = ads.ExpectResponse() 207 if len(resp.Resources) != 1 || resp.Resources[0].Name != "outbound|8080||"+edsIncSvc { 208 t.Fatalf("received unexpected eds resource %v", resp.Resources) 209 } 210 if len(resp.RemovedResources) != 0 { 211 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 212 } 213 214 t.Logf("update svc") 215 // update svc, only send the eds for this service 216 s.MemRegistry.AddHTTPService(edsIncSvc, "10.10.1.3", 8080) 217 218 resp = ads.ExpectResponse() 219 if len(resp.Resources) != 1 || resp.Resources[0].Name != "outbound|8080||"+edsIncSvc { 220 t.Fatalf("received unexpected eds resource %v", resp.Resources) 221 } 222 if len(resp.RemovedResources) != 0 { 223 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 224 } 225 226 // delete svc, only send eds for this service 227 s.MemRegistry.RemoveService(edsIncSvc) 228 229 resp = ads.ExpectResponse() 230 if len(resp.RemovedResources) != 1 || resp.RemovedResources[0] != "outbound|8080||"+edsIncSvc { 231 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 232 } 233 if len(resp.Resources) != 0 { 234 t.Fatalf("received unexpected eds resource %v", resp.Resources) 235 } 236 } 237 238 func TestDeltaReconnectRequests(t *testing.T) { 239 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{ 240 Services: []*model.Service{ 241 { 242 Hostname: "adsupdate.example.com", 243 DefaultAddress: "10.11.0.1", 244 Ports: []*model.Port{ 245 { 246 Name: "http-main", 247 Port: 2080, 248 Protocol: protocol.HTTP, 249 }, 250 }, 251 Attributes: model.ServiceAttributes{ 252 Name: "adsupdate", 253 Namespace: "default", 254 }, 255 }, 256 { 257 Hostname: "adsstatic.example.com", 258 DefaultAddress: "10.11.0.2", 259 Ports: []*model.Port{ 260 { 261 Name: "http-main", 262 Port: 2080, 263 Protocol: protocol.HTTP, 264 }, 265 }, 266 Attributes: model.ServiceAttributes{ 267 Name: "adsstatic", 268 Namespace: "default", 269 }, 270 }, 271 }, 272 }) 273 274 const updateCluster = "outbound|2080||adsupdate.example.com" 275 const staticCluster = "outbound|2080||adsstatic.example.com" 276 ads := s.ConnectDeltaADS() 277 // Send initial request 278 res := ads.RequestResponseAck(&discovery.DeltaDiscoveryRequest{TypeUrl: v3.ClusterType}) 279 // we must get the cluster back 280 if resn := xdstest.ExtractResource(res.Resources); !resn.Contains(updateCluster) || !resn.Contains(staticCluster) { 281 t.Fatalf("unexpected resources: %v", resn) 282 } 283 284 // A push should get a response 285 s.Discovery.ConfigUpdate(&model.PushRequest{Full: true}) 286 ads.ExpectResponse() 287 288 // Close the connection 289 ads.Cleanup() 290 291 // Service is removed while connection is closed 292 s.MemRegistry.RemoveService("adsupdate.example.com") 293 s.Discovery.ConfigUpdate(&model.PushRequest{ 294 Full: true, 295 ConfigsUpdated: sets.New(model.ConfigKey{Kind: kind.ServiceEntry, Name: "adsupdate.example.com", Namespace: "default"}), 296 }) 297 s.EnsureSynced(t) 298 299 ads = s.ConnectDeltaADS() 300 // Sometimes we get an EDS request first before CDS 301 ads.RequestResponseAck(&discovery.DeltaDiscoveryRequest{ 302 TypeUrl: v3.EndpointType, 303 ResponseNonce: "", 304 ResourceNamesSubscribe: []string{"outbound|80||local.default.svc.cluster.local"}, 305 }) 306 307 // Now send initial CDS request 308 res = ads.RequestResponseAck(&discovery.DeltaDiscoveryRequest{ 309 TypeUrl: v3.ClusterType, 310 InitialResourceVersions: map[string]string{ 311 // This time we include the version map, since it is a reconnect 312 staticCluster: "", 313 updateCluster: "", 314 }, 315 }) 316 317 // Expect that we send an EDS response even though there was no request 318 resp := ads.ExpectResponse() 319 if resp.TypeUrl != v3.EndpointType { 320 t.Fatalf("unexpected response type %v. Expected dependent EDS response", resp.TypeUrl) 321 } 322 // we must NOT get the cluster back 323 if resn := xdstest.ExtractResource(res.Resources); resn.Contains(updateCluster) || !resn.Contains(staticCluster) { 324 t.Fatalf("unexpected resources: %v", resn) 325 } 326 // It should be removed 327 if resn := sets.New(res.RemovedResources...); !resn.Contains(updateCluster) { 328 t.Fatalf("unexpected remove resources: %v", resn) 329 } 330 } 331 332 func TestDeltaWDS(t *testing.T) { 333 test.SetForTest(t, &features.EnableAmbient, true) 334 s := xds.NewFakeDiscoveryServer(t, xds.FakeOptions{}) 335 wlA := &model.WorkloadInfo{ 336 Workload: &workloadapi.Workload{ 337 Uid: fmt.Sprintf("Kubernetes//Pod/%s/%s", "test", "a"), 338 Namespace: "test", 339 Name: "a", 340 }, 341 } 342 wlB := &model.WorkloadInfo{ 343 Workload: &workloadapi.Workload{ 344 Uid: fmt.Sprintf("Kubernetes//Pod/%s/%s", "test", "b"), 345 Namespace: "test", 346 Name: "n", 347 }, 348 } 349 wlC := &model.WorkloadInfo{ 350 Workload: &workloadapi.Workload{ 351 Uid: fmt.Sprintf("Kubernetes//Pod/%s/%s", "test", "c"), 352 Namespace: "test", 353 Name: "c", 354 }, 355 } 356 svcA := &model.ServiceInfo{ 357 Service: &workloadapi.Service{ 358 Name: "a", 359 Namespace: "default", 360 Hostname: "a.default.svc.cluster.local", 361 }, 362 } 363 svcB := &model.ServiceInfo{ 364 Service: &workloadapi.Service{ 365 Name: "b", 366 Namespace: "default", 367 Hostname: "b.default.svc.cluster.local", 368 }, 369 } 370 svcC := &model.ServiceInfo{ 371 Service: &workloadapi.Service{ 372 Name: "c", 373 Namespace: "default", 374 Hostname: "c.default.svc.cluster.local", 375 }, 376 } 377 s.MemRegistry.AddWorkloadInfo(wlA, wlB, wlC) 378 s.MemRegistry.AddServiceInfo(svcA, svcB, svcC) 379 380 // Wait until the above debounce, to ensure we can precisely check XDS responses without spurious pushes 381 s.EnsureSynced(t) 382 383 ads := s.ConnectDeltaADS().WithType(v3.AddressType).WithID("ztunnel~1.1.1.1~test.default~default.svc.cluster.local") 384 ads.Request(&discovery.DeltaDiscoveryRequest{ 385 ResourceNamesSubscribe: []string{"*"}, 386 }) 387 resp := ads.ExpectResponse() 388 if len(resp.Resources) != 6 { 389 t.Fatalf("received unexpected eds resource %v", resp.Resources) 390 } 391 if len(resp.RemovedResources) != 0 { 392 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 393 } 394 395 // simulate a svc update 396 s.XdsUpdater.ConfigUpdate(&model.PushRequest{ 397 ConfigsUpdated: sets.New(model.ConfigKey{ 398 Kind: kind.Address, Name: svcA.ResourceName(), Namespace: svcA.Namespace, 399 }), 400 }) 401 402 resp = ads.ExpectResponse() 403 if len(resp.Resources) != 1 || resp.Resources[0].Name != svcA.ResourceName() { 404 t.Fatalf("received unexpected address resource %v", resp.Resources) 405 } 406 if len(resp.RemovedResources) != 0 { 407 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 408 } 409 410 // simulate a svc delete 411 s.MemRegistry.RemoveServiceInfo(svcA) 412 s.XdsUpdater.ConfigUpdate(&model.PushRequest{ 413 ConfigsUpdated: sets.New(model.ConfigKey{ 414 Kind: kind.Address, Name: svcA.ResourceName(), Namespace: svcA.Namespace, 415 }), 416 }) 417 418 resp = ads.ExpectResponse() 419 if len(resp.Resources) != 0 { 420 t.Fatalf("received unexpected address resource %v", resp.Resources) 421 } 422 if len(resp.RemovedResources) != 1 || resp.RemovedResources[0] != svcA.ResourceName() { 423 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 424 } 425 426 // delete workload 427 s.MemRegistry.RemoveWorkloadInfo(wlA) 428 // a full push and a pod delete event 429 // This is a merged push request 430 s.XdsUpdater.ConfigUpdate(&model.PushRequest{ 431 Full: true, 432 }) 433 434 resp = ads.ExpectResponse() 435 if len(resp.RemovedResources) != 1 || resp.RemovedResources[0] != wlA.ResourceName() { 436 t.Fatalf("received unexpected removed eds resource %v", resp.RemovedResources) 437 } 438 if len(resp.Resources) != 4 { 439 t.Fatalf("received unexpected eds resource %v", resp.Resources) 440 } 441 }