@@ -2966,7 +2966,104 @@ TEST_F(XdsClientTest, ResourceDoesNotExistUponTimeout) {
29662966 EXPECT_TRUE(stream->IsOrphaned());
29672967}
29682968
2969+ TEST_F(XdsClientTest, ResourceTimerIsTransientErrorIgnoredUnlessEnabled) {
2970+ event_engine_->SetRunAfterDurationCallback(
2971+ [&](grpc_event_engine::experimental::EventEngine::Duration duration) {
2972+ grpc_event_engine::experimental::EventEngine::Duration expected =
2973+ std::chrono::seconds(15);
2974+ EXPECT_EQ(duration, expected)
2975+ << "Expected: " << expected.count()
2976+ << "\nActual: " << duration.count();
2977+ });
2978+ InitXdsClient(FakeXdsBootstrap::Builder().SetServers(
2979+ {FakeXdsBootstrap::FakeXdsServer(kDefaultXdsServerUrl, false, true)}));
2980+ // Start a watch for "foo1".
2981+ auto watcher = StartFooWatch("foo1");
2982+ // Watcher should initially not see any resource reported.
2983+ EXPECT_FALSE(watcher->HasEvent());
2984+ // Check metric data.
2985+ EXPECT_THAT(metrics_reporter_->resource_updates_valid(),
2986+ ::testing::ElementsAre());
2987+ EXPECT_THAT(metrics_reporter_->resource_updates_invalid(),
2988+ ::testing::ElementsAre());
2989+ EXPECT_THAT(GetResourceCounts(),
2990+ ::testing::ElementsAre(::testing::Pair(
2991+ ResourceCountLabelsEq(XdsClient::kOldStyleAuthority,
2992+ XdsFooResourceType::Get()->type_url(),
2993+ "requested"),
2994+ 1)));
2995+ // XdsClient should have created an ADS stream.
2996+ auto stream = WaitForAdsStream();
2997+ ASSERT_TRUE(stream != nullptr);
2998+ // XdsClient should have sent a subscription request on the ADS stream.
2999+ auto request = WaitForRequest(stream.get());
3000+ ASSERT_TRUE(request.has_value());
3001+ CheckRequest(*request, XdsFooResourceType::Get()->type_url(),
3002+ /*version_info=*/"", /*response_nonce=*/"",
3003+ /*error_detail=*/absl::OkStatus(),
3004+ /*resource_names=*/{"foo1"});
3005+ CheckRequestNode(*request); // Should be present on the first request.
3006+ // Do not send a response, but wait for the resource to be reported as
3007+ // not existing.
3008+ EXPECT_TRUE(watcher->WaitForDoesNotExist());
3009+ // Check metric data.
3010+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
3011+ ::testing::ElementsAre(), ::testing::ElementsAre(), ::testing::_));
3012+ EXPECT_THAT(GetResourceCounts(),
3013+ ::testing::ElementsAre(::testing::Pair(
3014+ ResourceCountLabelsEq(XdsClient::kOldStyleAuthority,
3015+ XdsFooResourceType::Get()->type_url(),
3016+ "does_not_exist"),
3017+ 1)));
3018+ // Start a new watcher for the same resource. It should immediately
3019+ // receive the same does-not-exist notification.
3020+ auto watcher2 = StartFooWatch("foo1");
3021+ EXPECT_TRUE(watcher2->WaitForDoesNotExist());
3022+ // Now server sends a response.
3023+ stream->SendMessageToClient(
3024+ ResponseBuilder(XdsFooResourceType::Get()->type_url())
3025+ .set_version_info("1")
3026+ .set_nonce("A")
3027+ .AddFooResource(XdsFooResource("foo1", 6))
3028+ .Serialize());
3029+ // XdsClient should have delivered the response to the watchers.
3030+ auto resource = watcher->WaitForNextResource();
3031+ ASSERT_NE(resource, nullptr);
3032+ EXPECT_EQ(resource->name, "foo1");
3033+ EXPECT_EQ(resource->value, 6);
3034+ resource = watcher2->WaitForNextResource();
3035+ ASSERT_NE(resource, nullptr);
3036+ EXPECT_EQ(resource->name, "foo1");
3037+ EXPECT_EQ(resource->value, 6);
3038+ // Check metric data.
3039+ EXPECT_TRUE(metrics_reporter_->WaitForMetricsReporterData(
3040+ ::testing::ElementsAre(::testing::Pair(
3041+ ::testing::Pair(kDefaultXdsServerUrl,
3042+ XdsFooResourceType::Get()->type_url()),
3043+ 1)),
3044+ ::testing::ElementsAre(), ::testing::_));
3045+ EXPECT_THAT(
3046+ GetResourceCounts(),
3047+ ::testing::ElementsAre(::testing::Pair(
3048+ ResourceCountLabelsEq(XdsClient::kOldStyleAuthority,
3049+ XdsFooResourceType::Get()->type_url(), "acked"),
3050+ 1)));
3051+ // XdsClient should have sent an ACK message to the xDS server.
3052+ request = WaitForRequest(stream.get());
3053+ ASSERT_TRUE(request.has_value());
3054+ CheckRequest(*request, XdsFooResourceType::Get()->type_url(),
3055+ /*version_info=*/"1", /*response_nonce=*/"A",
3056+ /*error_detail=*/absl::OkStatus(),
3057+ /*resource_names=*/{"foo1"});
3058+ // Cancel watch.
3059+ CancelFooWatch(watcher.get(), "foo1");
3060+ CancelFooWatch(watcher2.get(), "foo1");
3061+ EXPECT_TRUE(stream->IsOrphaned());
3062+ }
3063+
29693064TEST_F(XdsClientTest, ResourceTimerIsTransientFailure) {
3065+ testing::ScopedEnvVar env_var("GRPC_EXPERIMENTAL_XDS_DATA_ERROR_HANDLING",
3066+ "true");
29703067 event_engine_->SetRunAfterDurationCallback(
29713068 [&](grpc_event_engine::experimental::EventEngine::Duration duration) {
29723069 grpc_event_engine::experimental::EventEngine::Duration expected =
0 commit comments