github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/xdsclient/client_test.go (about) 1 /* 2 * 3 * Copyright 2019 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 20 21 import ( 22 "context" 23 "fmt" 24 "testing" 25 "time" 26 27 "github.com/google/go-cmp/cmp" 28 "github.com/google/go-cmp/cmp/cmpopts" 29 "github.com/hxx258456/ccgo/grpc/internal/grpclog" 30 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/load" 31 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/pubsub" 32 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource" 33 "google.golang.org/protobuf/types/known/anypb" 34 35 grpc "github.com/hxx258456/ccgo/grpc" 36 "github.com/hxx258456/ccgo/grpc/credentials/insecure" 37 "github.com/hxx258456/ccgo/grpc/internal/grpcsync" 38 "github.com/hxx258456/ccgo/grpc/internal/grpctest" 39 "github.com/hxx258456/ccgo/grpc/internal/testutils" 40 xdstestutils "github.com/hxx258456/ccgo/grpc/xds/internal/testutils" 41 "github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/bootstrap" 42 "google.golang.org/protobuf/testing/protocmp" 43 ) 44 45 type s struct { 46 grpctest.Tester 47 } 48 49 func Test(t *testing.T) { 50 grpctest.RunSubTests(t, s{}) 51 } 52 53 const ( 54 testXDSServer = "xds-server" 55 testXDSServerAuthority = "xds-server-authority" 56 57 testAuthority = "test-authority" 58 testAuthority2 = "test-authority-2" 59 testLDSName = "test-lds" 60 testRDSName = "test-rds" 61 testCDSName = "test-cds" 62 testEDSName = "test-eds" 63 64 defaultTestWatchExpiryTimeout = 500 * time.Millisecond 65 defaultTestTimeout = 5 * time.Second 66 defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen. 67 ) 68 69 func newStringP(s string) *string { 70 return &s 71 } 72 73 func clientOpts() *bootstrap.Config { 74 return &bootstrap.Config{ 75 XDSServer: &bootstrap.ServerConfig{ 76 ServerURI: testXDSServer, 77 Creds: grpc.WithTransportCredentials(insecure.NewCredentials()), 78 NodeProto: xdstestutils.EmptyNodeProtoV2, 79 }, 80 Authorities: map[string]*bootstrap.Authority{ 81 testAuthority: { 82 XDSServer: &bootstrap.ServerConfig{ 83 ServerURI: testXDSServerAuthority, 84 Creds: grpc.WithTransportCredentials(insecure.NewCredentials()), 85 NodeProto: xdstestutils.EmptyNodeProtoV2, 86 }, 87 }, 88 }, 89 } 90 } 91 92 type testController struct { 93 // config is the config this controller is created with. 94 config *bootstrap.ServerConfig 95 96 done *grpcsync.Event 97 addWatches map[xdsresource.ResourceType]*testutils.Channel 98 removeWatches map[xdsresource.ResourceType]*testutils.Channel 99 } 100 101 func overrideNewController(t *testing.T) *testutils.Channel { 102 origNewController := newController 103 ch := testutils.NewChannel() 104 newController = func(config *bootstrap.ServerConfig, pubsub *pubsub.Pubsub, validator xdsresource.UpdateValidatorFunc, logger *grpclog.PrefixLogger) (controllerInterface, error) { 105 ret := newTestController(config) 106 ch.Send(ret) 107 return ret, nil 108 } 109 t.Cleanup(func() { newController = origNewController }) 110 return ch 111 } 112 113 func newTestController(config *bootstrap.ServerConfig) *testController { 114 addWatches := map[xdsresource.ResourceType]*testutils.Channel{ 115 xdsresource.ListenerResource: testutils.NewChannel(), 116 xdsresource.RouteConfigResource: testutils.NewChannel(), 117 xdsresource.ClusterResource: testutils.NewChannel(), 118 xdsresource.EndpointsResource: testutils.NewChannel(), 119 } 120 removeWatches := map[xdsresource.ResourceType]*testutils.Channel{ 121 xdsresource.ListenerResource: testutils.NewChannel(), 122 xdsresource.RouteConfigResource: testutils.NewChannel(), 123 xdsresource.ClusterResource: testutils.NewChannel(), 124 xdsresource.EndpointsResource: testutils.NewChannel(), 125 } 126 return &testController{ 127 config: config, 128 done: grpcsync.NewEvent(), 129 addWatches: addWatches, 130 removeWatches: removeWatches, 131 } 132 } 133 134 func (c *testController) AddWatch(resourceType xdsresource.ResourceType, resourceName string) { 135 c.addWatches[resourceType].Send(resourceName) 136 } 137 138 func (c *testController) RemoveWatch(resourceType xdsresource.ResourceType, resourceName string) { 139 c.removeWatches[resourceType].Send(resourceName) 140 } 141 142 func (c *testController) ReportLoad(server string) (*load.Store, func()) { 143 panic("ReportLoad is not implemented") 144 } 145 146 func (c *testController) Close() { 147 c.done.Fire() 148 } 149 150 // TestWatchCallAnotherWatch covers the case where watch() is called inline by a 151 // callback. It makes sure it doesn't cause a deadlock. 152 func (s) TestWatchCallAnotherWatch(t *testing.T) { 153 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 154 defer cancel() 155 // Start a watch for some resource, so that the controller and update 156 // handlers are built for this authority. The test needs these to make an 157 // inline watch in a callback. 158 client, ctrlCh := testClientSetup(t, false) 159 newWatch(t, client, xdsresource.ClusterResource, "doesnot-matter") 160 controller, updateHandler := getControllerAndPubsub(ctx, t, client, ctrlCh, xdsresource.ClusterResource, "doesnot-matter") 161 162 clusterUpdateCh := testutils.NewChannel() 163 firstTime := true 164 client.WatchCluster(testCDSName, func(update xdsresource.ClusterUpdate, err error) { 165 clusterUpdateCh.Send(xdsresource.ClusterUpdateErrTuple{Update: update, Err: err}) 166 // Calls another watch inline, to ensure there's deadlock. 167 client.WatchCluster("another-random-name", func(xdsresource.ClusterUpdate, error) {}) 168 169 if _, err := controller.addWatches[xdsresource.ClusterResource].Receive(ctx); firstTime && err != nil { 170 t.Fatalf("want new watch to start, got error %v", err) 171 } 172 firstTime = false 173 }) 174 if _, err := controller.addWatches[xdsresource.ClusterResource].Receive(ctx); err != nil { 175 t.Fatalf("want new watch to start, got error %v", err) 176 } 177 178 wantUpdate := xdsresource.ClusterUpdate{ClusterName: testEDSName} 179 updateHandler.NewClusters(map[string]xdsresource.ClusterUpdateErrTuple{testCDSName: {Update: wantUpdate}}, xdsresource.UpdateMetadata{}) 180 if err := verifyClusterUpdate(ctx, clusterUpdateCh, wantUpdate, nil); err != nil { 181 t.Fatal(err) 182 } 183 184 // The second update needs to be different in the underlying resource proto 185 // for the watch callback to be invoked. 186 wantUpdate2 := xdsresource.ClusterUpdate{ClusterName: testEDSName + "2", Raw: &anypb.Any{}} 187 updateHandler.NewClusters(map[string]xdsresource.ClusterUpdateErrTuple{testCDSName: {Update: wantUpdate2}}, xdsresource.UpdateMetadata{}) 188 if err := verifyClusterUpdate(ctx, clusterUpdateCh, wantUpdate2, nil); err != nil { 189 t.Fatal(err) 190 } 191 } 192 193 func verifyListenerUpdate(ctx context.Context, updateCh *testutils.Channel, wantUpdate xdsresource.ListenerUpdate, wantErr error) error { 194 u, err := updateCh.Receive(ctx) 195 if err != nil { 196 return fmt.Errorf("timeout when waiting for listener update: %v", err) 197 } 198 gotUpdate := u.(xdsresource.ListenerUpdateErrTuple) 199 if wantErr != nil { 200 if gotUpdate.Err != wantErr { 201 return fmt.Errorf("unexpected error: %v, want %v", gotUpdate.Err, wantErr) 202 } 203 return nil 204 } 205 if gotUpdate.Err != nil || !cmp.Equal(gotUpdate.Update, wantUpdate, protocmp.Transform()) { 206 return fmt.Errorf("unexpected endpointsUpdate: (%v, %v), want: (%v, nil)", gotUpdate.Update, gotUpdate.Err, wantUpdate) 207 } 208 return nil 209 } 210 211 func verifyRouteConfigUpdate(ctx context.Context, updateCh *testutils.Channel, wantUpdate xdsresource.RouteConfigUpdate, wantErr error) error { 212 u, err := updateCh.Receive(ctx) 213 if err != nil { 214 return fmt.Errorf("timeout when waiting for route configuration update: %v", err) 215 } 216 gotUpdate := u.(xdsresource.RouteConfigUpdateErrTuple) 217 if wantErr != nil { 218 if gotUpdate.Err != wantErr { 219 return fmt.Errorf("unexpected error: %v, want %v", gotUpdate.Err, wantErr) 220 } 221 return nil 222 } 223 if gotUpdate.Err != nil || !cmp.Equal(gotUpdate.Update, wantUpdate, protocmp.Transform()) { 224 return fmt.Errorf("unexpected route config update: (%v, %v), want: (%v, nil)", gotUpdate.Update, gotUpdate.Err, wantUpdate) 225 } 226 return nil 227 } 228 229 func verifyClusterUpdate(ctx context.Context, updateCh *testutils.Channel, wantUpdate xdsresource.ClusterUpdate, wantErr error) error { 230 u, err := updateCh.Receive(ctx) 231 if err != nil { 232 return fmt.Errorf("timeout when waiting for cluster update: %v", err) 233 } 234 gotUpdate := u.(xdsresource.ClusterUpdateErrTuple) 235 if wantErr != nil { 236 if gotUpdate.Err != wantErr { 237 return fmt.Errorf("unexpected error: %v, want %v", gotUpdate.Err, wantErr) 238 } 239 return nil 240 } 241 if !cmp.Equal(gotUpdate.Update, wantUpdate, protocmp.Transform()) { 242 return fmt.Errorf("unexpected clusterUpdate: (%v, %v), want: (%v, nil)", gotUpdate.Update, gotUpdate.Err, wantUpdate) 243 } 244 return nil 245 } 246 247 func verifyEndpointsUpdate(ctx context.Context, updateCh *testutils.Channel, wantUpdate xdsresource.EndpointsUpdate, wantErr error) error { 248 u, err := updateCh.Receive(ctx) 249 if err != nil { 250 return fmt.Errorf("timeout when waiting for endpoints update: %v", err) 251 } 252 gotUpdate := u.(xdsresource.EndpointsUpdateErrTuple) 253 if wantErr != nil { 254 if gotUpdate.Err != wantErr { 255 return fmt.Errorf("unexpected error: %v, want %v", gotUpdate.Err, wantErr) 256 } 257 return nil 258 } 259 if gotUpdate.Err != nil || !cmp.Equal(gotUpdate.Update, wantUpdate, cmpopts.EquateEmpty(), protocmp.Transform()) { 260 return fmt.Errorf("unexpected endpointsUpdate: (%v, %v), want: (%v, nil)", gotUpdate.Update, gotUpdate.Err, wantUpdate) 261 } 262 return nil 263 } 264 265 // Test that multiple New() returns the same Client. And only when the last 266 // client is closed, the underlying client is closed. 267 func (s) TestClientNewSingleton(t *testing.T) { 268 oldBootstrapNewConfig := bootstrapNewConfig 269 bootstrapNewConfig = func() (*bootstrap.Config, error) { 270 return &bootstrap.Config{ 271 XDSServer: &bootstrap.ServerConfig{ 272 ServerURI: testXDSServer, 273 Creds: grpc.WithInsecure(), 274 NodeProto: xdstestutils.EmptyNodeProtoV2, 275 }, 276 }, nil 277 } 278 defer func() { bootstrapNewConfig = oldBootstrapNewConfig }() 279 280 ctrlCh := overrideNewController(t) 281 282 // The first New(). Should create a Client and a new APIClient. 283 client, err := newRefCounted() 284 if err != nil { 285 t.Fatalf("failed to create client: %v", err) 286 } 287 288 // Call a watch to create the controller. 289 client.WatchCluster("doesnot-matter", func(update xdsresource.ClusterUpdate, err error) {}) 290 291 clientImpl := client.clientImpl 292 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 293 defer cancel() 294 c, err := ctrlCh.Receive(ctx) 295 if err != nil { 296 t.Fatalf("timeout when waiting for API client to be created: %v", err) 297 } 298 apiClient := c.(*testController) 299 300 // Call New() again. They should all return the same client implementation, 301 // and should not create new API client. 302 const count = 9 303 for i := 0; i < count; i++ { 304 tc, terr := newRefCounted() 305 if terr != nil { 306 client.Close() 307 t.Fatalf("%d-th call to New() failed with error: %v", i, terr) 308 } 309 if tc.clientImpl != clientImpl { 310 client.Close() 311 tc.Close() 312 t.Fatalf("%d-th call to New() got a different client %p, want %p", i, tc.clientImpl, clientImpl) 313 } 314 315 sctx, scancel := context.WithTimeout(context.Background(), defaultTestShortTimeout) 316 defer scancel() 317 _, err := ctrlCh.Receive(sctx) 318 if err == nil { 319 client.Close() 320 t.Fatalf("%d-th call to New() created a new API client", i) 321 } 322 } 323 324 // Call Close(). Nothing should be actually closed until the last ref calls 325 // Close(). 326 for i := 0; i < count; i++ { 327 client.Close() 328 if clientImpl.done.HasFired() { 329 t.Fatalf("%d-th call to Close(), unexpected done in the client implemenation", i) 330 } 331 if apiClient.done.HasFired() { 332 t.Fatalf("%d-th call to Close(), unexpected done in the API client", i) 333 } 334 } 335 336 // Call the last Close(). The underlying implementation and API Client 337 // should all be closed. 338 client.Close() 339 if !clientImpl.done.HasFired() { 340 t.Fatalf("want client implementation to be closed, got not done") 341 } 342 if !apiClient.done.HasFired() { 343 t.Fatalf("want API client to be closed, got not done") 344 } 345 346 // Call New() again after the previous Client is actually closed. Should 347 // create a Client and a new APIClient. 348 client2, err2 := newRefCounted() 349 if err2 != nil { 350 t.Fatalf("failed to create client: %v", err) 351 } 352 defer client2.Close() 353 354 // Call a watch to create the controller. 355 client2.WatchCluster("abc", func(update xdsresource.ClusterUpdate, err error) {}) 356 357 c2, err := ctrlCh.Receive(ctx) 358 if err != nil { 359 t.Fatalf("timeout when waiting for API client to be created: %v", err) 360 } 361 apiClient2 := c2.(*testController) 362 363 // The client wrapper with ref count should be the same. 364 if client2 != client { 365 t.Fatalf("New() after Close() should return the same client wrapper, got different %p, %p", client2, client) 366 } 367 if client2.clientImpl == clientImpl { 368 t.Fatalf("New() after Close() should return different client implementation, got the same %p", client2.clientImpl) 369 } 370 if apiClient2 == apiClient { 371 t.Fatalf("New() after Close() should return different API client, got the same %p", apiClient2) 372 } 373 }