google.golang.org/grpc@v1.72.2/xds/internal/xdsclient/tests/authority_test.go (about) 1 /* 2 * 3 * Copyright 2022 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 package xdsclient_test 20 21 import ( 22 "context" 23 "encoding/json" 24 "fmt" 25 "testing" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 "github.com/google/uuid" 30 "google.golang.org/grpc/internal/testutils" 31 "google.golang.org/grpc/internal/testutils/xds/e2e" 32 "google.golang.org/grpc/internal/xds/bootstrap" 33 xdstestutils "google.golang.org/grpc/xds/internal/testutils" 34 "google.golang.org/grpc/xds/internal/xdsclient" 35 "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" 36 37 v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3" 38 ) 39 40 const ( 41 testAuthority1 = "test-authority1" 42 testAuthority2 = "test-authority2" 43 testAuthority3 = "test-authority3" 44 ) 45 46 var ( 47 // These two resources use `testAuthority1`, which contains an empty server 48 // config in the bootstrap file, and therefore will use the default 49 // management server. 50 authorityTestResourceName11 = xdstestutils.BuildResourceName(xdsresource.ClusterResourceTypeName, testAuthority1, cdsName+"1", nil) 51 authorityTestResourceName12 = xdstestutils.BuildResourceName(xdsresource.ClusterResourceTypeName, testAuthority1, cdsName+"2", nil) 52 // This resource uses `testAuthority2`, which contains an empty server 53 // config in the bootstrap file, and therefore will use the default 54 // management server. 55 authorityTestResourceName2 = xdstestutils.BuildResourceName(xdsresource.ClusterResourceTypeName, testAuthority2, cdsName+"3", nil) 56 // This resource uses `testAuthority3`, which contains a non-empty server 57 // config in the bootstrap file, and therefore will use the non-default 58 // management server. 59 authorityTestResourceName3 = xdstestutils.BuildResourceName(xdsresource.ClusterResourceTypeName, testAuthority3, cdsName+"3", nil) 60 ) 61 62 // setupForAuthorityTests spins up two management servers, one to act as the 63 // default and the other to act as the non-default. It also generates a 64 // bootstrap configuration with three authorities (the first two pointing to the 65 // default and the third one pointing to the non-default). 66 // 67 // Returns two listeners used by the default and non-default management servers 68 // respectively, and the xDS client and its close function. 69 func setupForAuthorityTests(ctx context.Context, t *testing.T) (*testutils.ListenerWrapper, *testutils.ListenerWrapper, xdsclient.XDSClient, func()) { 70 // Create listener wrappers which notify on to a channel whenever a new 71 // connection is accepted. We use this to track the number of transports 72 // used by the xDS client. 73 lisDefault := testutils.NewListenerWrapper(t, nil) 74 lisNonDefault := testutils.NewListenerWrapper(t, nil) 75 76 // Start a management server to act as the default authority. 77 defaultAuthorityServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: lisDefault}) 78 79 // Start a management server to act as the non-default authority. 80 nonDefaultAuthorityServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: lisNonDefault}) 81 82 // Create a bootstrap configuration with two non-default authorities which 83 // have empty server configs, and therefore end up using the default server 84 // config, which points to the above management server. 85 nodeID := uuid.New().String() 86 bootstrapContents, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{ 87 Servers: []byte(fmt.Sprintf(`[{ 88 "server_uri": %q, 89 "channel_creds": [{"type": "insecure"}] 90 }]`, defaultAuthorityServer.Address)), 91 Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)), 92 Authorities: map[string]json.RawMessage{ 93 testAuthority1: []byte(`{}`), 94 testAuthority2: []byte(`{}`), 95 testAuthority3: []byte(fmt.Sprintf(`{ 96 "xds_servers": [{ 97 "server_uri": %q, 98 "channel_creds": [{"type": "insecure"}] 99 }]}`, nonDefaultAuthorityServer.Address)), 100 }, 101 }) 102 if err != nil { 103 t.Fatalf("Failed to create bootstrap configuration: %v", err) 104 } 105 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 106 if err != nil { 107 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 108 } 109 pool := xdsclient.NewPool(config) 110 client, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{ 111 Name: t.Name(), 112 WatchExpiryTimeout: defaultTestWatchExpiryTimeout, 113 }) 114 if err != nil { 115 t.Fatalf("Failed to create an xDS client: %v", err) 116 } 117 118 resources := e2e.UpdateOptions{ 119 NodeID: nodeID, 120 Clusters: []*v3clusterpb.Cluster{ 121 e2e.DefaultCluster(authorityTestResourceName11, edsName, e2e.SecurityLevelNone), 122 e2e.DefaultCluster(authorityTestResourceName12, edsName, e2e.SecurityLevelNone), 123 e2e.DefaultCluster(authorityTestResourceName2, edsName, e2e.SecurityLevelNone), 124 e2e.DefaultCluster(authorityTestResourceName3, edsName, e2e.SecurityLevelNone), 125 }, 126 SkipValidation: true, 127 } 128 if err := defaultAuthorityServer.Update(ctx, resources); err != nil { 129 t.Fatalf("Failed to update management server with resources: %v, err: %v", resources, err) 130 } 131 return lisDefault, lisNonDefault, client, close 132 } 133 134 // Tests the xdsChannel sharing logic among authorities. The test verifies the 135 // following scenarios: 136 // - A watch for a resource name with an authority matching an existing watch 137 // should not result in a new transport being created. 138 // - A watch for a resource name with different authority name but same 139 // authority config as an existing watch should not result in a new transport 140 // being created. 141 func (s) TestAuthority_XDSChannelSharing(t *testing.T) { 142 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 143 defer cancel() 144 lis, _, client, close := setupForAuthorityTests(ctx, t) 145 defer close() 146 147 // Verify that no connection is established to the management server at this 148 // point. A transport is created only when a resource (which belongs to that 149 // authority) is requested. 150 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 151 defer sCancel() 152 if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded { 153 t.Fatal("Unexpected new transport created to management server") 154 } 155 156 // Request the first resource. Verify that a new transport is created. 157 watcher := noopClusterWatcher{} 158 cdsCancel1 := xdsresource.WatchCluster(client, authorityTestResourceName11, watcher) 159 defer cdsCancel1() 160 if _, err := lis.NewConnCh.Receive(ctx); err != nil { 161 t.Fatalf("Timed out when waiting for a new transport to be created to the management server: %v", err) 162 } 163 164 // Request the second resource. Verify that no new transport is created. 165 cdsCancel2 := xdsresource.WatchCluster(client, authorityTestResourceName12, watcher) 166 defer cdsCancel2() 167 sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout) 168 defer sCancel() 169 if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded { 170 t.Fatal("Unexpected new transport created to management server") 171 } 172 173 // Request the third resource. Verify that no new transport is created. 174 cdsCancel3 := xdsresource.WatchCluster(client, authorityTestResourceName2, watcher) 175 defer cdsCancel3() 176 sCtx, sCancel = context.WithTimeout(ctx, defaultTestShortTimeout) 177 defer sCancel() 178 if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded { 179 t.Fatal("Unexpected new transport created to management server") 180 } 181 } 182 183 // Test the xdsChannel close logic. The test verifies that the xDS client 184 // closes an xdsChannel immediately after the last watch is canceled. 185 func (s) TestAuthority_XDSChannelClose(t *testing.T) { 186 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 187 defer cancel() 188 lis, _, client, close := setupForAuthorityTests(ctx, t) 189 defer close() 190 191 // Request the first resource. Verify that a new transport is created. 192 watcher := noopClusterWatcher{} 193 cdsCancel1 := xdsresource.WatchCluster(client, authorityTestResourceName11, watcher) 194 val, err := lis.NewConnCh.Receive(ctx) 195 if err != nil { 196 t.Fatalf("Timed out when waiting for a new transport to be created to the management server: %v", err) 197 } 198 conn := val.(*testutils.ConnWrapper) 199 200 // Request the second resource. Verify that no new transport is created. 201 cdsCancel2 := xdsresource.WatchCluster(client, authorityTestResourceName12, watcher) 202 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 203 defer sCancel() 204 if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded { 205 t.Fatal("Unexpected new transport created to management server") 206 } 207 208 // Cancel both watches, and verify that the connection to the management 209 // server is closed. 210 cdsCancel1() 211 cdsCancel2() 212 if _, err := conn.CloseCh.Receive(ctx); err != nil { 213 t.Fatal("Timeout when waiting for connection to management server to be closed") 214 } 215 } 216 217 // Tests the scenario where the primary management server is unavailable at 218 // startup and the xDS client falls back to the secondary. The test verifies 219 // that the resource watcher is not notifified of the connectivity failure until 220 // all servers have failed. 221 func (s) TestAuthority_Fallback(t *testing.T) { 222 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 223 defer cancel() 224 225 // Create primary and secondary management servers with restartable 226 // listeners. 227 l, err := testutils.LocalTCPListener() 228 if err != nil { 229 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 230 } 231 primaryLis := testutils.NewRestartableListener(l) 232 primaryMgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: primaryLis}) 233 l, err = testutils.LocalTCPListener() 234 if err != nil { 235 t.Fatalf("testutils.LocalTCPListener() failed: %v", err) 236 } 237 secondaryLis := testutils.NewRestartableListener(l) 238 secondaryMgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: secondaryLis}) 239 240 // Create bootstrap configuration with the above primary and fallback 241 // management servers, and an xDS client with that configuration. 242 nodeID := uuid.New().String() 243 bootstrapContents, err := bootstrap.NewContentsForTesting(bootstrap.ConfigOptionsForTesting{ 244 Servers: []byte(fmt.Sprintf(` 245 [ 246 { 247 "server_uri": %q, 248 "channel_creds": [{"type": "insecure"}] 249 }, 250 { 251 "server_uri": %q, 252 "channel_creds": [{"type": "insecure"}] 253 } 254 ]`, primaryMgmtServer.Address, secondaryMgmtServer.Address)), 255 Node: []byte(fmt.Sprintf(`{"id": "%s"}`, nodeID)), 256 }) 257 if err != nil { 258 t.Fatalf("Failed to create bootstrap configuration: %v", err) 259 } 260 config, err := bootstrap.NewConfigFromContents(bootstrapContents) 261 if err != nil { 262 t.Fatalf("Failed to parse bootstrap contents: %s, %v", string(bootstrapContents), err) 263 } 264 pool := xdsclient.NewPool(config) 265 xdsC, close, err := pool.NewClientForTesting(xdsclient.OptionsForTesting{Name: t.Name()}) 266 if err != nil { 267 t.Fatalf("Failed to create an xDS client: %v", err) 268 } 269 defer close() 270 271 const clusterName = "cluster" 272 const edsPrimaryName = "eds-primary" 273 const edsSecondaryName = "eds-secondary" 274 275 // Create a Cluster resource on the primary. 276 resources := e2e.UpdateOptions{ 277 NodeID: nodeID, 278 Clusters: []*v3clusterpb.Cluster{ 279 e2e.DefaultCluster(clusterName, edsPrimaryName, e2e.SecurityLevelNone), 280 }, 281 SkipValidation: true, 282 } 283 if err := primaryMgmtServer.Update(ctx, resources); err != nil { 284 t.Fatalf("Failed to update primary management server with resources: %v, err: %v", resources, err) 285 } 286 287 // Create a Cluster resource on the secondary . 288 resources = e2e.UpdateOptions{ 289 NodeID: nodeID, 290 Clusters: []*v3clusterpb.Cluster{ 291 e2e.DefaultCluster(clusterName, edsSecondaryName, e2e.SecurityLevelNone), 292 }, 293 SkipValidation: true, 294 } 295 if err := secondaryMgmtServer.Update(ctx, resources); err != nil { 296 t.Fatalf("Failed to update primary management server with resources: %v, err: %v", resources, err) 297 } 298 299 // Stop the primary. 300 primaryLis.Close() 301 302 // Register a watch. 303 watcher := newClusterWatcherV2() 304 cdsCancel := xdsresource.WatchCluster(xdsC, clusterName, watcher) 305 defer cdsCancel() 306 307 // Ensure that the connectivity error callback is not called. 308 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 309 defer sCancel() 310 if v, err := watcher.errCh.Receive(sCtx); err != context.DeadlineExceeded { 311 t.Fatalf("Error callback on the watcher with error: %v", v.(error)) 312 } 313 314 // Ensure that the resource update callback is invoked. 315 v, err := watcher.updateCh.Receive(ctx) 316 if err != nil { 317 t.Fatalf("Error when waiting for a resource update callback: %v", err) 318 } 319 gotUpdate := v.(xdsresource.ClusterUpdate) 320 wantUpdate := xdsresource.ClusterUpdate{ 321 ClusterName: clusterName, 322 EDSServiceName: edsSecondaryName, 323 } 324 cmpOpts := []cmp.Option{cmpopts.EquateEmpty(), cmpopts.IgnoreFields(xdsresource.ClusterUpdate{}, "Raw", "LBPolicy", "TelemetryLabels")} 325 if diff := cmp.Diff(wantUpdate, gotUpdate, cmpOpts...); diff != "" { 326 t.Fatalf("Diff in the cluster resource update: (-want, got):\n%s", diff) 327 } 328 329 // Stop the secondary. 330 secondaryLis.Close() 331 332 // Ensure that the connectivity error callback is called. 333 if _, err := watcher.errCh.Receive(ctx); err != nil { 334 t.Fatal("Timeout when waiting for error callback on the watcher") 335 } 336 } 337 338 // TODO: Get rid of the clusterWatcher type in cds_watchers_test.go and use this 339 // one instead. Also, rename this to clusterWatcher as part of that refactor. 340 type clusterWatcherV2 struct { 341 updateCh *testutils.Channel // Messages of type xdsresource.ClusterUpdate 342 errCh *testutils.Channel // Messages of type error 343 resourceNotFoundCh *testutils.Channel // Messages of type error 344 } 345 346 func newClusterWatcherV2() *clusterWatcherV2 { 347 return &clusterWatcherV2{ 348 updateCh: testutils.NewChannel(), 349 errCh: testutils.NewChannel(), 350 resourceNotFoundCh: testutils.NewChannel(), 351 } 352 } 353 354 func (cw *clusterWatcherV2) OnUpdate(update *xdsresource.ClusterResourceData, onDone xdsresource.OnDoneFunc) { 355 cw.updateCh.Send(update.Resource) 356 onDone() 357 } 358 359 func (cw *clusterWatcherV2) OnError(err error, onDone xdsresource.OnDoneFunc) { 360 // When used with a go-control-plane management server that continuously 361 // resends resources which are NACKed by the xDS client, using a `Replace()` 362 // here simplifies tests that want access to the most recently received 363 // error. 364 cw.errCh.Replace(err) 365 onDone() 366 } 367 368 func (cw *clusterWatcherV2) OnResourceDoesNotExist(onDone xdsresource.OnDoneFunc) { 369 // When used with a go-control-plane management server that continuously 370 // resends resources which are NACKed by the xDS client, using a `Replace()` 371 // here simplifies tests that want access to the most recently received 372 // error. 373 cw.resourceNotFoundCh.Replace(xdsresource.NewError(xdsresource.ErrorTypeResourceNotFound, "Cluster not found in received response")) 374 onDone() 375 }