google.golang.org/grpc@v1.74.2/xds/internal/clients/grpctransport/grpc_transport_ext_test.go (about) 1 /* 2 * 3 * Copyright 2025 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 grpctransport_test 20 21 import ( 22 "context" 23 "sync" 24 "testing" 25 "time" 26 27 "google.golang.org/grpc/credentials" 28 "google.golang.org/grpc/credentials/insecure" 29 "google.golang.org/grpc/credentials/local" 30 "google.golang.org/grpc/internal/grpctest" 31 "google.golang.org/grpc/xds/internal/clients" 32 "google.golang.org/grpc/xds/internal/clients/grpctransport" 33 "google.golang.org/grpc/xds/internal/clients/internal/testutils" 34 "google.golang.org/grpc/xds/internal/clients/internal/testutils/e2e" 35 ) 36 37 const ( 38 defaultTestTimeout = 10 * time.Second 39 defaultTestShortTimeout = 10 * time.Millisecond // For events expected to *not* happen. 40 ) 41 42 type s struct { 43 grpctest.Tester 44 } 45 46 func Test(t *testing.T) { 47 grpctest.RunSubTests(t, s{}) 48 } 49 50 type testCredentials struct { 51 credentials.Bundle 52 transportCredentials credentials.TransportCredentials 53 } 54 55 func (tc *testCredentials) TransportCredentials() credentials.TransportCredentials { 56 return tc.transportCredentials 57 } 58 func (tc *testCredentials) PerRPCCredentials() credentials.PerRPCCredentials { 59 return nil 60 } 61 62 // TestBuild_Single tests that multiple calls to Build() with the same 63 // clients.ServerIdentifier returns the same transport. Also verifies that 64 // only when all references to the newly created transport are released, 65 // the underlying transport is closed. 66 func (s) TestBuild_Single(t *testing.T) { 67 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 68 defer cancel() 69 70 lis := testutils.NewListenerWrapper(t, nil) 71 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: lis}) 72 73 serverID := clients.ServerIdentifier{ 74 ServerURI: mgmtServer.Address, 75 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "local"}, 76 } 77 configs := map[string]grpctransport.Config{ 78 "local": {Credentials: &testCredentials{transportCredentials: local.NewCredentials()}}, 79 } 80 81 // Calling Build() first time should create new gRPC transport. 82 builder := grpctransport.NewBuilder(configs) 83 tr, err := builder.Build(serverID) 84 if err != nil { 85 t.Fatalf("Failed to build transport: %v", err) 86 } 87 // Create a new stream to the server and verify that a new transport is 88 // created. 89 if _, err = tr.NewStream(ctx, "/envoy.service.discovery.v3.AggregatedDiscoveryService/StreamAggregatedResources"); err != nil { 90 t.Fatalf("Failed to create stream: %v", err) 91 } 92 val, err := lis.NewConnCh.Receive(ctx) 93 if err != nil { 94 t.Fatalf("Timed out when waiting for a new transport to be created to the management server: %v", err) 95 } 96 conn := val.(*testutils.ConnWrapper) 97 98 // Calling Build() again should not create new gRPC transport. 99 const count = 9 100 transports := make([]clients.Transport, count) 101 for i := 0; i < count; i++ { 102 func() { 103 transports[i], err = builder.Build(serverID) 104 if err != nil { 105 t.Fatalf("Failed to build transport: %v", err) 106 } 107 // Create a new stream to the server and verify that no connection 108 // is established to the management server at this point. A new 109 // transport is created only when an existing connection for 110 // serverID does not exist. 111 if _, err = tr.NewStream(ctx, "/envoy.service.discovery.v3.AggregatedDiscoveryService/StreamAggregatedResources"); err != nil { 112 t.Fatalf("Failed to create stream: %v", err) 113 } 114 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 115 defer sCancel() 116 if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded { 117 t.Fatal("Unexpected new transport created to management server") 118 } 119 }() 120 } 121 122 // Call Close() multiple times on each of the transport received in the 123 // above for loop. Close() calls are idempotent. The underlying gRPC 124 // transport is removed after the Close() call but calling close second 125 // time should not panic and underlying gRPC transport should not be 126 // closed. 127 for i := 0; i < count; i++ { 128 func() { 129 transports[i].Close() 130 transports[i].Close() 131 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 132 defer sCancel() 133 if _, err := conn.CloseCh.Receive(sCtx); err != context.DeadlineExceeded { 134 t.Fatal("Unexpected transport closure to management server") 135 } 136 }() 137 } 138 139 // Call the last Close(). The underlying gRPC transport should be closed 140 // because calls in the above for loop have released all references. 141 tr.Close() 142 if _, err := conn.CloseCh.Receive(ctx); err != nil { 143 t.Fatal("Timeout when waiting for connection to management server to be closed") 144 } 145 146 // Calling Build() again, after the previous transport was actually closed, 147 // should create a new one. 148 tr2, err := builder.Build(serverID) 149 if err != nil { 150 t.Fatalf("Failed to create xDS client: %v", err) 151 } 152 defer tr2.Close() 153 // Create a new stream to the server and verify that a new transport is 154 // created. 155 if _, err = tr2.NewStream(ctx, "/envoy.service.discovery.v3.AggregatedDiscoveryService/StreamAggregatedResources"); err != nil { 156 t.Fatalf("Failed to create stream: %v", err) 157 } 158 if _, err := lis.NewConnCh.Receive(ctx); err != nil { 159 t.Fatalf("Timed out when waiting for a new transport to be created to the management server: %v", err) 160 } 161 } 162 163 // TestBuild_Multiple tests the scenario where there are multiple calls to 164 // Build() with different clients.ServerIdentifier. Verifies that reference 165 // counts are tracked correctly for each transport and that only when all 166 // references are released for a transport, it is closed. 167 func (s) TestBuild_Multiple(t *testing.T) { 168 ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout) 169 defer cancel() 170 171 lis := testutils.NewListenerWrapper(t, nil) 172 mgmtServer := e2e.StartManagementServer(t, e2e.ManagementServerOptions{Listener: lis}) 173 174 serverID1 := clients.ServerIdentifier{ 175 ServerURI: mgmtServer.Address, 176 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "local"}, 177 } 178 serverID2 := clients.ServerIdentifier{ 179 ServerURI: mgmtServer.Address, 180 Extensions: grpctransport.ServerIdentifierExtension{ConfigName: "insecure"}, 181 } 182 configs := map[string]grpctransport.Config{ 183 "local": {Credentials: &testCredentials{transportCredentials: local.NewCredentials()}}, 184 "insecure": {Credentials: insecure.NewBundle()}, 185 } 186 187 // Create two gRPC transports. 188 builder := grpctransport.NewBuilder(configs) 189 190 tr1, err := builder.Build(serverID1) 191 if err != nil { 192 t.Fatalf("Failed to build transport: %v", err) 193 } 194 // Create a new stream to the server and verify that a new transport is 195 // created. 196 if _, err = tr1.NewStream(ctx, "/envoy.service.discovery.v3.AggregatedDiscoveryService/StreamAggregatedResources"); err != nil { 197 t.Fatalf("Failed to create stream: %v", err) 198 } 199 val, err := lis.NewConnCh.Receive(ctx) 200 if err != nil { 201 t.Fatalf("Timed out when waiting for a new transport to be created to the management server: %v", err) 202 } 203 conn1 := val.(*testutils.ConnWrapper) 204 205 tr2, err := builder.Build(serverID2) 206 if err != nil { 207 t.Fatalf("Failed to build transport: %v", err) 208 } 209 // Create a new stream to the server and verify that a new transport is 210 // created because credentials are different. 211 if _, err = tr2.NewStream(ctx, "/envoy.service.discovery.v3.AggregatedDiscoveryService/StreamAggregatedResources"); err != nil { 212 t.Fatalf("Failed to create stream: %v", err) 213 } 214 val, err = lis.NewConnCh.Receive(ctx) 215 if err != nil { 216 t.Fatalf("Timed out when waiting for a new transport to be created to the management server: %v", err) 217 } 218 conn2 := val.(*testutils.ConnWrapper) 219 220 // Create N more references to each of the two transports. 221 const count = 9 222 transports1 := make([]clients.Transport, count) 223 transports2 := make([]clients.Transport, count) 224 var wg sync.WaitGroup 225 wg.Add(2) 226 go func() { 227 defer wg.Done() 228 for i := 0; i < count; i++ { 229 var err error 230 transports1[i], err = builder.Build(serverID1) 231 if err != nil { 232 t.Errorf("Failed to build transport: %v", err) 233 } 234 // Create a new stream to the server and verify that no connection 235 // is established to the management server at this point. A new 236 // transport is created only when an existing connection for 237 // serverID does not exist. 238 if _, err = transports1[i].NewStream(ctx, "/envoy.service.discovery.v3.AggregatedDiscoveryService/StreamAggregatedResources"); err != nil { 239 t.Errorf("Failed to create stream: %v", err) 240 } 241 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 242 defer sCancel() 243 if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded { 244 t.Error("Unexpected new transport created to management server") 245 } 246 } 247 }() 248 go func() { 249 defer wg.Done() 250 for i := 0; i < count; i++ { 251 var err error 252 transports2[i], err = builder.Build(serverID2) 253 if err != nil { 254 t.Errorf("%d-th call to Build() failed with error: %v", i, err) 255 } 256 // Create a new stream to the server and verify that no connection 257 // is established to the management server at this point. A new 258 // transport is created only when an existing connection for 259 // serverID does not exist. 260 if _, err = transports2[i].NewStream(ctx, "/envoy.service.discovery.v3.AggregatedDiscoveryService/StreamAggregatedResources"); err != nil { 261 t.Errorf("Failed to create stream: %v", err) 262 } 263 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 264 defer sCancel() 265 if _, err := lis.NewConnCh.Receive(sCtx); err != context.DeadlineExceeded { 266 t.Error("Unexpected new transport created to management server") 267 } 268 } 269 }() 270 wg.Wait() 271 if t.Failed() { 272 t.FailNow() 273 } 274 275 // Call Close() multiple times on each of the transport received in the 276 // above for loop. Close() calls are idempotent. The underlying gRPC 277 // transport is removed after the Close() call but calling close second 278 // time should not panic and underlying gRPC transport should not be 279 // closed. 280 for i := 0; i < count; i++ { 281 transports1[i].Close() 282 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 283 defer sCancel() 284 if _, err := conn1.CloseCh.Receive(sCtx); err != context.DeadlineExceeded { 285 t.Fatal("Unexpected transport closure to management server") 286 } 287 transports1[i].Close() 288 } 289 // Call the last Close(). The underlying gRPC transport should be closed 290 // because calls in the above for loop have released all references. 291 tr1.Close() 292 if _, err := conn1.CloseCh.Receive(ctx); err != nil { 293 t.Fatal("Timeout when waiting for connection to management server to be closed") 294 } 295 296 // Call Close() multiple times on each of the transport received in the 297 // above for loop. Close() calls are idempotent. The underlying gRPC 298 // transport is removed after the Close() call but calling close second 299 // time should not panic and underlying gRPC transport should not be 300 // closed. 301 for i := 0; i < count; i++ { 302 transports2[i].Close() 303 sCtx, sCancel := context.WithTimeout(ctx, defaultTestShortTimeout) 304 defer sCancel() 305 if _, err := conn2.CloseCh.Receive(sCtx); err != context.DeadlineExceeded { 306 t.Fatal("Unexpected transport closure to management server") 307 } 308 transports2[i].Close() 309 } 310 // Call the last Close(). The underlying gRPC transport should be closed 311 // because calls in the above for loop have released all references. 312 tr2.Close() 313 if _, err := conn2.CloseCh.Receive(ctx); err != nil { 314 t.Fatal("Timeout when waiting for connection to management server to be closed") 315 } 316 }