@@ -201,8 +201,17 @@ class XdsClientTest : public ::testing::Test {
201
201
server_ = std::move(server);
202
202
}
203
203
204
+ bool FallbackOnReachabilityOnly() const override {
205
+ return fallback_on_reachability_only_;
206
+ }
207
+
208
+ void SetFallbackOnReachabilityOnly() {
209
+ fallback_on_reachability_only_ = true;
210
+ }
211
+
204
212
private:
205
213
std::optional<FakeXdsServer> server_;
214
+ bool fallback_on_reachability_only_ = false;
206
215
};
207
216
208
217
class Builder {
@@ -6263,6 +6272,254 @@ TEST_F(XdsClientTest, FallbackOnStartup) {
6263
6272
/*resource_names=*/{"foo1"});
6264
6273
}
6265
6274
6275
+ TEST_F(XdsClientTest, FallbackOnReachabilityOnly) {
6276
+ testing::ScopedExperimentalEnvVar env_var(
6277
+ "GRPC_EXPERIMENTAL_XDS_ENDPOINT_FALLBACK");
6278
+ constexpr char kAuthority[] = "xds.example.com";
6279
+ const std::string kXdstpResourceName = absl::StrCat(
6280
+ "xdstp://", kAuthority, "/", XdsFooResource::TypeUrl(), "/foo1");
6281
+ FakeXdsBootstrap::FakeAuthority authority;
6282
+ authority.SetFallbackOnReachabilityOnly();
6283
+ FakeXdsBootstrap::FakeXdsServer primary_server(kDefaultXdsServerUrl);
6284
+ FakeXdsBootstrap::FakeXdsServer fallback_server("fallback_xds_server");
6285
+ InitXdsClient(FakeXdsBootstrap::Builder()
6286
+ .AddAuthority(kAuthority, authority)
6287
+ .SetServers({primary_server, fallback_server}));
6288
+ // Start a watch.
6289
+ auto watcher = StartFooWatch(kXdstpResourceName);
6290
+ EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair(
6291
+ kDefaultXdsServerUrl, true)));
6292
+ EXPECT_THAT(
6293
+ GetResourceCounts(),
6294
+ ::testing::ElementsAre(::testing::Pair(
6295
+ ResourceCountLabelsEq(
6296
+ kAuthority, XdsFooResourceType::Get()->type_url(), "requested"),
6297
+ 1)));
6298
+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
6299
+ ::testing::IsEmpty(), ::testing::_, ::testing::ElementsAre()));
6300
+ // CSDS should show that the resource has been requested.
6301
+ ClientConfig csds = DumpCsds();
6302
+ EXPECT_THAT(csds.generic_xds_configs(),
6303
+ ::testing::ElementsAre(CsdsResourceRequested(
6304
+ XdsFooResourceType::Get()->type_url(), kXdstpResourceName)));
6305
+ // XdsClient should have created an ADS stream to the primary server.
6306
+ auto stream = WaitForAdsStream();
6307
+ ASSERT_TRUE(stream != nullptr);
6308
+ // XdsClient should have sent a subscription request on the ADS stream.
6309
+ auto request = WaitForRequest(stream.get());
6310
+ ASSERT_TRUE(request.has_value());
6311
+ CheckRequest(*request, XdsFooResourceType::Get()->type_url(),
6312
+ /*version_info=*/"", /*response_nonce=*/"",
6313
+ /*error_detail=*/absl::OkStatus(),
6314
+ /*resource_names=*/{kXdstpResourceName});
6315
+ // Primary server sends initial response.
6316
+ stream->SendMessageToClient(
6317
+ ResponseBuilder(XdsFooResourceType::Get()->type_url())
6318
+ .set_version_info("20")
6319
+ .set_nonce("O")
6320
+ .AddFooResource(XdsFooResource(kXdstpResourceName, 6))
6321
+ .Serialize());
6322
+ // Resource should be delivered to watcher.
6323
+ auto resource = watcher->WaitForNextResource();
6324
+ ASSERT_NE(resource, nullptr);
6325
+ EXPECT_EQ(resource->name, kXdstpResourceName);
6326
+ EXPECT_EQ(resource->value, 6);
6327
+ // Metrics should show 1 resource update and 1 cached resource.
6328
+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
6329
+ ::testing::ElementsAre(::testing::Pair(
6330
+ ::testing::Pair(kDefaultXdsServerUrl,
6331
+ XdsFooResourceType::Get()->type_url()),
6332
+ 1)),
6333
+ ::testing::_, ::testing::_));
6334
+ EXPECT_THAT(
6335
+ GetResourceCounts(),
6336
+ ::testing::ElementsAre(::testing::Pair(
6337
+ ResourceCountLabelsEq(kAuthority,
6338
+ XdsFooResourceType::Get()->type_url(), "acked"),
6339
+ 1)));
6340
+ // Check CSDS data.
6341
+ csds = DumpCsds();
6342
+ EXPECT_THAT(csds.generic_xds_configs(),
6343
+ ::testing::UnorderedElementsAre(CsdsResourceAcked(
6344
+ XdsFooResourceType::Get()->type_url(), kXdstpResourceName,
6345
+ resource->AsJsonString(), "20", TimestampProtoEq(kTime0))));
6346
+ // Client should send ACK to server.
6347
+ request = WaitForRequest(stream.get());
6348
+ ASSERT_TRUE(request.has_value());
6349
+ CheckRequest(*request, XdsFooResourceType::Get()->type_url(),
6350
+ /*version_info=*/"20", /*response_nonce=*/"O",
6351
+ /*error_detail=*/absl::OkStatus(),
6352
+ /*resource_names=*/{kXdstpResourceName});
6353
+ // Trigger connection failure to primary.
6354
+ TriggerConnectionFailure(primary_server,
6355
+ absl::UnavailableError("Server down"));
6356
+ // This should trigger fallback.
6357
+ auto fallback_stream = WaitForAdsStream(fallback_server);
6358
+ ASSERT_NE(fallback_stream, nullptr);
6359
+ request = WaitForRequest(fallback_stream.get());
6360
+ ASSERT_TRUE(request.has_value());
6361
+ CheckRequest(*request, XdsFooResourceType::Get()->type_url(),
6362
+ /*version_info=*/"", /*response_nonce=*/"",
6363
+ /*error_detail=*/absl::OkStatus(),
6364
+ /*resource_names=*/{kXdstpResourceName});
6365
+ // Metrics should show primary channel failing and fallback channel working.
6366
+ EXPECT_THAT(
6367
+ GetServerConnections(),
6368
+ ::testing::ElementsAre(
6369
+ ::testing::Pair(kDefaultXdsServerUrl, false),
6370
+ ::testing::Pair(fallback_server.target()->server_uri(), true)));
6371
+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
6372
+ ::testing::_, ::testing::_,
6373
+ ::testing::ElementsAre(::testing::Pair(kDefaultXdsServerUrl, 1))));
6374
+ // Fallback server sends a response.
6375
+ fallback_stream->SendMessageToClient(
6376
+ ResponseBuilder(XdsFooResourceType::Get()->type_url())
6377
+ .set_version_info("5")
6378
+ .set_nonce("A")
6379
+ .AddFooResource(XdsFooResource(kXdstpResourceName, 30))
6380
+ .Serialize());
6381
+ // Resource is delivered to watcher.
6382
+ resource = watcher->WaitForNextResource();
6383
+ ASSERT_NE(resource, nullptr);
6384
+ EXPECT_EQ(resource->name, kXdstpResourceName);
6385
+ EXPECT_EQ(resource->value, 30);
6386
+ // Metrics show update.
6387
+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
6388
+ ::testing::ElementsAre(
6389
+ ::testing::Pair(
6390
+ ::testing::Pair(kDefaultXdsServerUrl,
6391
+ XdsFooResourceType::Get()->type_url()),
6392
+ 1),
6393
+ ::testing::Pair(
6394
+ ::testing::Pair(fallback_server.target()->server_uri(),
6395
+ XdsFooResourceType::Get()->type_url()),
6396
+ 1)),
6397
+ ::testing::_, ::testing::_));
6398
+ EXPECT_THAT(
6399
+ GetResourceCounts(),
6400
+ ::testing::ElementsAre(::testing::Pair(
6401
+ ResourceCountLabelsEq(kAuthority,
6402
+ XdsFooResourceType::Get()->type_url(), "acked"),
6403
+ 1)));
6404
+ // Check CSDS data.
6405
+ csds = DumpCsds();
6406
+ EXPECT_THAT(csds.generic_xds_configs(),
6407
+ ::testing::UnorderedElementsAre(CsdsResourceAcked(
6408
+ XdsFooResourceType::Get()->type_url(), kXdstpResourceName,
6409
+ resource->AsJsonString(), "5", TimestampProtoEq(kTime0))));
6410
+ // Client should send ACK to fallback server.
6411
+ request = WaitForRequest(fallback_stream.get());
6412
+ ASSERT_TRUE(request.has_value());
6413
+ CheckRequest(*request, XdsFooResourceType::Get()->type_url(),
6414
+ /*version_info=*/"5", /*response_nonce=*/"A",
6415
+ /*error_detail=*/absl::OkStatus(),
6416
+ /*resource_names=*/{kXdstpResourceName});
6417
+ // Clean up.
6418
+ CancelFooWatch(watcher.get(), kXdstpResourceName);
6419
+ EXPECT_TRUE(stream->IsOrphaned());
6420
+ EXPECT_TRUE(fallback_stream->IsOrphaned());
6421
+ }
6422
+
6423
+ TEST_F(XdsClientTest, FallbackOnReachabilityOnlyNotEnabled) {
6424
+ constexpr char kAuthority[] = "xds.example.com";
6425
+ const std::string kXdstpResourceName = absl::StrCat(
6426
+ "xdstp://", kAuthority, "/", XdsFooResource::TypeUrl(), "/foo1");
6427
+ FakeXdsBootstrap::FakeAuthority authority;
6428
+ authority.SetFallbackOnReachabilityOnly();
6429
+ FakeXdsBootstrap::FakeXdsServer primary_server(kDefaultXdsServerUrl);
6430
+ FakeXdsBootstrap::FakeXdsServer fallback_server("fallback_xds_server");
6431
+ InitXdsClient(FakeXdsBootstrap::Builder()
6432
+ .AddAuthority(kAuthority, authority)
6433
+ .SetServers({primary_server, fallback_server}));
6434
+ // Start a watch.
6435
+ auto watcher = StartFooWatch(kXdstpResourceName);
6436
+ EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair(
6437
+ kDefaultXdsServerUrl, true)));
6438
+ EXPECT_THAT(
6439
+ GetResourceCounts(),
6440
+ ::testing::ElementsAre(::testing::Pair(
6441
+ ResourceCountLabelsEq(
6442
+ kAuthority, XdsFooResourceType::Get()->type_url(), "requested"),
6443
+ 1)));
6444
+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
6445
+ ::testing::IsEmpty(), ::testing::_, ::testing::ElementsAre()));
6446
+ // CSDS should show that the resource has been requested.
6447
+ ClientConfig csds = DumpCsds();
6448
+ EXPECT_THAT(csds.generic_xds_configs(),
6449
+ ::testing::ElementsAre(CsdsResourceRequested(
6450
+ XdsFooResourceType::Get()->type_url(), kXdstpResourceName)));
6451
+ // XdsClient should have created an ADS stream.
6452
+ auto stream = WaitForAdsStream();
6453
+ ASSERT_TRUE(stream != nullptr);
6454
+ // XdsClient should have sent a subscription request on the ADS stream.
6455
+ auto request = WaitForRequest(stream.get());
6456
+ ASSERT_TRUE(request.has_value());
6457
+ CheckRequest(*request, XdsFooResourceType::Get()->type_url(),
6458
+ /*version_info=*/"", /*response_nonce=*/"",
6459
+ /*error_detail=*/absl::OkStatus(),
6460
+ /*resource_names=*/{kXdstpResourceName});
6461
+ // Server sends resource.
6462
+ stream->SendMessageToClient(
6463
+ ResponseBuilder(XdsFooResourceType::Get()->type_url())
6464
+ .set_version_info("20")
6465
+ .set_nonce("O")
6466
+ .AddFooResource(XdsFooResource(kXdstpResourceName, 6))
6467
+ .Serialize());
6468
+ // Resource should be delivered to watcher.
6469
+ auto resource = watcher->WaitForNextResource();
6470
+ ASSERT_NE(resource, nullptr);
6471
+ EXPECT_EQ(resource->name, kXdstpResourceName);
6472
+ EXPECT_EQ(resource->value, 6);
6473
+ // Metrics should show 1 resource update and 1 cached resource.
6474
+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
6475
+ ::testing::ElementsAre(::testing::Pair(
6476
+ ::testing::Pair(kDefaultXdsServerUrl,
6477
+ XdsFooResourceType::Get()->type_url()),
6478
+ 1)),
6479
+ ::testing::_, ::testing::_));
6480
+ EXPECT_THAT(
6481
+ GetResourceCounts(),
6482
+ ::testing::ElementsAre(::testing::Pair(
6483
+ ResourceCountLabelsEq(kAuthority,
6484
+ XdsFooResourceType::Get()->type_url(), "acked"),
6485
+ 1)));
6486
+ // Check CSDS data.
6487
+ csds = DumpCsds();
6488
+ EXPECT_THAT(csds.generic_xds_configs(),
6489
+ ::testing::UnorderedElementsAre(CsdsResourceAcked(
6490
+ XdsFooResourceType::Get()->type_url(), kXdstpResourceName,
6491
+ resource->AsJsonString(), "20", TimestampProtoEq(kTime0))));
6492
+ // Client should send ACK to server.
6493
+ request = WaitForRequest(stream.get());
6494
+ ASSERT_TRUE(request.has_value());
6495
+ CheckRequest(*request, XdsFooResourceType::Get()->type_url(),
6496
+ /*version_info=*/"20", /*response_nonce=*/"O",
6497
+ /*error_detail=*/absl::OkStatus(),
6498
+ /*resource_names=*/{kXdstpResourceName});
6499
+ // Trigger connection failure to primary.
6500
+ TriggerConnectionFailure(primary_server,
6501
+ absl::UnavailableError("Server down"));
6502
+ // Error should be reported to the watcher.
6503
+ auto error = watcher->WaitForNextAmbientError();
6504
+ ASSERT_TRUE(error.has_value());
6505
+ EXPECT_EQ(error->code(), absl::StatusCode::kUnavailable);
6506
+ EXPECT_EQ(error->message(),
6507
+ "xDS channel for server default_xds_server: Server down (node "
6508
+ "ID:xds_client_test)");
6509
+ // This should NOT trigger fallback.
6510
+ auto fallback_stream = WaitForAdsStream(fallback_server);
6511
+ ASSERT_EQ(fallback_stream, nullptr);
6512
+ // Metrics should show primary channel failing, but no fallback channel.
6513
+ EXPECT_THAT(GetServerConnections(), ::testing::ElementsAre(::testing::Pair(
6514
+ kDefaultXdsServerUrl, false)));
6515
+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
6516
+ ::testing::_, ::testing::_,
6517
+ ::testing::ElementsAre(::testing::Pair(kDefaultXdsServerUrl, 1))));
6518
+ // Clean up.
6519
+ CancelFooWatch(watcher.get(), kXdstpResourceName);
6520
+ EXPECT_TRUE(stream->IsOrphaned());
6521
+ }
6522
+
6266
6523
} // namespace
6267
6524
} // namespace testing
6268
6525
} // namespace grpc_core
0 commit comments