// Copyright 2017 gRPC authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include "src/core/ext/filters/client_channel/backup_poller.h" #include "src/proto/grpc/testing/xds/v3/fault.grpc.pb.h" #include "src/proto/grpc/testing/xds/v3/router.grpc.pb.h" #include "test/cpp/end2end/xds/no_op_http_filter.h" #include "test/cpp/end2end/xds/xds_end2end_test_lib.h" namespace grpc { namespace testing { namespace { using ::envoy::extensions::filters::http::fault::v3::HTTPFault; using std::chrono::system_clock; using LdsTest = XdsEnd2endTest; INSTANTIATE_TEST_SUITE_P(XdsTest, LdsTest, ::testing::Values(XdsTestType()), &XdsTestType::Name); // Tests that LDS client should send a NACK if there is no API listener in the // Listener in the LDS response. TEST_P(LdsTest, NoApiListener) { auto listener = default_listener_; listener.clear_api_listener(); balancer_->ads_service()->SetLdsResource(listener); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr("Listener has neither address nor ApiListener")); } // Tests that LDS client should send a NACK if the route_specifier in the // http_connection_manager is neither inlined route_config nor RDS. TEST_P(LdsTest, WrongRouteSpecifier) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); http_connection_manager.mutable_scoped_routes(); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); balancer_->ads_service()->SetLdsResource(listener); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr( "HttpConnectionManager neither has inlined route_config nor RDS.")); } // Tests that LDS client should send a NACK if the rds message in the // http_connection_manager is missing the config_source field. TEST_P(LdsTest, RdsMissingConfigSource) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); http_connection_manager.mutable_rds()->set_route_config_name( kDefaultRouteConfigurationName); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); balancer_->ads_service()->SetLdsResource(listener); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "HttpConnectionManager missing config_source for RDS.")); } // Tests that LDS client should send a NACK if the rds message in the // http_connection_manager has a config_source field that does not specify // ADS or SELF. TEST_P(LdsTest, RdsConfigSourceDoesNotSpecifyAdsOrSelf) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); auto* rds = http_connection_manager.mutable_rds(); rds->set_route_config_name(kDefaultRouteConfigurationName); rds->mutable_config_source()->set_path("/foo/bar"); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); balancer_->ads_service()->SetLdsResource(listener); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("HttpConnectionManager ConfigSource for " "RDS does not specify ADS or SELF.")); } // Tests that LDS client accepts the rds message in the // http_connection_manager with a config_source field that specifies ADS. TEST_P(LdsTest, AcceptsRdsConfigSourceOfTypeAds) { CreateAndStartBackends(1); auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); auto* rds = http_connection_manager.mutable_rds(); rds->set_route_config_name(kDefaultRouteConfigurationName); rds->mutable_config_source()->mutable_ads(); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = balancer_->ads_service()->lds_response_state(); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Tests that we NACK non-terminal filters at the end of the list. TEST_P(LdsTest, NacksNonTerminalHttpFilterAtEndOfList) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); auto* filter = http_connection_manager.mutable_http_filters(0); filter->set_name("unknown"); filter->mutable_typed_config()->set_type_url( "custom/grpc.testing.client_only_http_filter"); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "non-terminal filter for config type grpc.testing" ".client_only_http_filter is the last filter in the chain")); } // Test that we NACK terminal filters that are not at the end of the list. TEST_P(LdsTest, NacksTerminalFilterBeforeEndOfList) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); // The default_listener_ has a terminal router filter by default. Add an // additional filter. auto* filter = http_connection_manager.add_http_filters(); filter->set_name("grpc.testing.terminal_http_filter"); filter->mutable_typed_config()->set_type_url( "custom/grpc.testing.terminal_http_filter"); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr( "terminal filter for config type envoy.extensions.filters.http" ".router.v3.Router must be the last filter in the chain")); } // Test that we NACK empty filter names. TEST_P(LdsTest, RejectsEmptyHttpFilterName) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); auto* filter = http_connection_manager.mutable_http_filters(0); filter->Clear(); filter->mutable_typed_config()->PackFrom(Listener()); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("empty filter name at index 0")); } // Test that we NACK duplicate HTTP filter names. TEST_P(LdsTest, RejectsDuplicateHttpFilterName) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); http_connection_manager.mutable_http_filters(0) ->mutable_typed_config() ->PackFrom(HTTPFault()); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("duplicate HTTP filter name: router")); } // Test that we NACK unknown filter types. TEST_P(LdsTest, RejectsUnknownHttpFilterType) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); auto* filter = http_connection_manager.mutable_http_filters(0); filter->set_name("unknown"); filter->mutable_typed_config()->PackFrom(Listener()); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("no filter registered for config type " "envoy.config.listener.v3.Listener")); } // Test that we ignore optional unknown filter types. TEST_P(LdsTest, IgnoresOptionalUnknownHttpFilterType) { CreateAndStartBackends(1); auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); auto* filter = http_connection_manager.mutable_http_filters(0); filter->set_name("unknown"); filter->mutable_typed_config()->PackFrom(Listener()); filter->set_is_optional(true); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = balancer_->ads_service()->lds_response_state(); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK filters without configs. TEST_P(LdsTest, RejectsHttpFilterWithoutConfig) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); auto* filter = http_connection_manager.mutable_http_filters(0); filter->Clear(); filter->set_name("unknown"); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "no filter config specified for filter name unknown")); } // Test that we ignore optional filters without configs. TEST_P(LdsTest, IgnoresOptionalHttpFilterWithoutConfig) { CreateAndStartBackends(1); auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); auto* filter = http_connection_manager.mutable_http_filters(0); filter->Clear(); filter->set_name("unknown"); filter->set_is_optional(true); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = balancer_->ads_service()->lds_response_state(); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK unparseable filter configs. TEST_P(LdsTest, RejectsUnparseableHttpFilterType) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); auto* filter = http_connection_manager.mutable_http_filters(0); filter->set_name("unknown"); filter->mutable_typed_config()->PackFrom(listener); filter->mutable_typed_config()->set_type_url( "type.googleapis.com/envoy.extensions.filters.http.fault.v3.HTTPFault"); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr( "filter config for type " "envoy.extensions.filters.http.fault.v3.HTTPFault failed to parse")); } // Test that we NACK HTTP filters unsupported on client-side. TEST_P(LdsTest, RejectsHttpFiltersNotSupportedOnClients) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); auto* filter = http_connection_manager.mutable_http_filters(0); filter->set_name("grpc.testing.server_only_http_filter"); filter->mutable_typed_config()->set_type_url( "custom/grpc.testing.server_only_http_filter"); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr("Filter grpc.testing.server_only_http_filter is not " "supported on clients")); } // Test that we ignore optional HTTP filters unsupported on client-side. TEST_P(LdsTest, IgnoresOptionalHttpFiltersNotSupportedOnClients) { CreateAndStartBackends(1); auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); *http_connection_manager.add_http_filters() = http_connection_manager.http_filters(0); auto* filter = http_connection_manager.mutable_http_filters(0); filter->set_name("grpc.testing.server_only_http_filter"); filter->mutable_typed_config()->set_type_url( "custom/grpc.testing.server_only_http_filter"); filter->set_is_optional(true); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = balancer_->ads_service()->lds_response_state(); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK non-zero xff_num_trusted_hops TEST_P(LdsTest, RejectsNonZeroXffNumTrusterHops) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); http_connection_manager.set_xff_num_trusted_hops(1); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("'xff_num_trusted_hops' must be zero")); } // Test that we NACK non-empty original_ip_detection_extensions TEST_P(LdsTest, RejectsNonEmptyOriginalIpDetectionExtensions) { auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); http_connection_manager.add_original_ip_detection_extensions(); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); const auto response_state = WaitForLdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr("'original_ip_detection_extensions' must be empty")); } using LdsV2Test = XdsEnd2endTest; INSTANTIATE_TEST_SUITE_P(XdsTest, LdsV2Test, ::testing::Values(XdsTestType().set_use_v2()), &XdsTestType::Name); // Tests that we ignore the HTTP filter list in v2. // TODO(roth): The test framework is not set up to allow us to test // the server sending v2 resources when the client requests v3, so this // just tests a pure v2 setup. When we have time, fix this. TEST_P(LdsV2Test, IgnoresHttpFilters) { CreateAndStartBackends(1); auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); auto* filter = http_connection_manager.add_http_filters(); filter->set_name("unknown"); filter->mutable_typed_config()->PackFrom(Listener()); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); SetListenerAndRouteConfiguration(balancer_.get(), listener, default_route_config_); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); CheckRpcSendOk(DEBUG_LOCATION); } class LdsDeletionTest : public XdsEnd2endTest { protected: void SetUp() override {} // Individual tests call InitClient(). }; INSTANTIATE_TEST_SUITE_P(XdsTest, LdsDeletionTest, ::testing::Values(XdsTestType()), &XdsTestType::Name); // Tests that we go into TRANSIENT_FAILURE if the Listener is deleted. TEST_P(LdsDeletionTest, ListenerDeleted) { InitClient(); CreateAndStartBackends(1); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // We need to wait for all backends to come online. WaitForAllBackends(DEBUG_LOCATION); // Unset LDS resource. balancer_->ads_service()->UnsetResource(kLdsTypeUrl, kServerName); // Wait for RPCs to start failing. SendRpcsUntil(DEBUG_LOCATION, [](const RpcResult& result) { if (result.status.ok()) return true; // Keep going. EXPECT_EQ(result.status.error_code(), StatusCode::UNAVAILABLE); EXPECT_EQ(result.status.error_message(), absl::StrCat("empty address list: ", kServerName, ": xDS listener resource does not exist")); return false; }); // Make sure we ACK'ed the update. auto response_state = balancer_->ads_service()->lds_response_state(); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Tests that we ignore Listener deletions if configured to do so. TEST_P(LdsDeletionTest, ListenerDeletionIgnored) { InitClient(BootstrapBuilder().SetIgnoreResourceDeletion()); CreateAndStartBackends(2); // Bring up client pointing to backend 0 and wait for it to connect. EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION, 0, 1); // Make sure we ACKed the LDS update. auto response_state = balancer_->ads_service()->lds_response_state(); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); // Unset LDS resource and wait for client to ACK the update. balancer_->ads_service()->UnsetResource(kLdsTypeUrl, kServerName); const auto deadline = absl::Now() + absl::Seconds(30); while (true) { ASSERT_LT(absl::Now(), deadline) << "timed out waiting for LDS ACK"; response_state = balancer_->ads_service()->lds_response_state(); if (response_state.has_value()) break; } EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); // Make sure we can still send RPCs. CheckRpcSendOk(DEBUG_LOCATION); // Now recreate the LDS resource pointing to a different CDS and EDS // resource, pointing to backend 1, and make sure the client uses it. const char* kNewClusterName = "new_cluster_name"; const char* kNewEdsResourceName = "new_eds_resource_name"; auto cluster = default_cluster_; cluster.set_name(kNewClusterName); cluster.mutable_eds_cluster_config()->set_service_name(kNewEdsResourceName); balancer_->ads_service()->SetCdsResource(cluster); args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args, kNewEdsResourceName)); RouteConfiguration new_route_config = default_route_config_; new_route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_route() ->set_cluster(kNewClusterName); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, new_route_config); // Wait for client to start using backend 1. WaitForAllBackends(DEBUG_LOCATION, 1, 2); } using LdsRdsTest = XdsEnd2endTest; // Test with and without RDS. // Also test with v2 and RDS to ensure that we handle those cases. INSTANTIATE_TEST_SUITE_P( XdsTest, LdsRdsTest, ::testing::Values(XdsTestType(), XdsTestType().set_enable_rds_testing(), XdsTestType().set_enable_rds_testing().set_use_v2()), &XdsTestType::Name); MATCHER_P2(AdjustedClockInRange, t1, t2, "equals time") { gpr_cycle_counter cycle_now = gpr_get_cycle_counter(); grpc_core::Timestamp cycle_time = grpc_core::Timestamp::FromCycleCounterRoundDown(cycle_now); grpc_core::Timestamp time_spec = grpc_core::Timestamp::FromTimespecRoundDown(gpr_now(GPR_CLOCK_MONOTONIC)); grpc_core::Timestamp now = arg + (time_spec - cycle_time); bool ok = true; ok &= ::testing::ExplainMatchResult(::testing::Ge(t1), now, result_listener); ok &= ::testing::ExplainMatchResult(::testing::Lt(t2), now, result_listener); return ok; } // Tests that XdsClient sends an ACK for the RouteConfiguration, whether or // not it was inlined into the LDS response. TEST_P(LdsRdsTest, Vanilla) { (void)SendRpc(); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); // Make sure we actually used the RPC service for the right version of xDS. EXPECT_EQ(balancer_->ads_service()->seen_v2_client(), GetParam().use_v2()); EXPECT_NE(balancer_->ads_service()->seen_v3_client(), GetParam().use_v2()); } TEST_P(LdsRdsTest, DefaultRouteSpecifiesSlashPrefix) { CreateAndStartBackends(1); RouteConfiguration route_config = default_route_config_; route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_match() ->set_prefix("/"); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // We need to wait for all backends to come online. WaitForAllBackends(DEBUG_LOCATION); } // Tests that LDS client ACKs but fails if matching domain can't be found in // the LDS response. TEST_P(LdsRdsTest, NoMatchedDomain) { RouteConfiguration route_config = default_route_config_; route_config.mutable_virtual_hosts(0)->clear_domains(); route_config.mutable_virtual_hosts(0)->add_domains("unmatched_domain"); SetRouteConfiguration(balancer_.get(), route_config); CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::UNAVAILABLE, absl::StrCat( (GetParam().enable_rds_testing() ? kDefaultRouteConfigurationName : kServerName), ": UNAVAILABLE: could not find VirtualHost for ", kServerName, " in RouteConfiguration")); // Do a bit of polling, to allow the ACK to get to the ADS server. channel_->WaitForConnected(grpc_timeout_milliseconds_to_deadline(100)); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Tests that LDS client should choose the virtual host with matching domain // if multiple virtual hosts exist in the LDS response. TEST_P(LdsRdsTest, ChooseMatchedDomain) { RouteConfiguration route_config = default_route_config_; *(route_config.add_virtual_hosts()) = route_config.virtual_hosts(0); route_config.mutable_virtual_hosts(0)->clear_domains(); route_config.mutable_virtual_hosts(0)->add_domains("unmatched_domain"); SetRouteConfiguration(balancer_.get(), route_config); (void)SendRpc(); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Tests that LDS client should choose the last route in the virtual host if // multiple routes exist in the LDS response. TEST_P(LdsRdsTest, ChooseLastRoute) { RouteConfiguration route_config = default_route_config_; *(route_config.mutable_virtual_hosts(0)->add_routes()) = route_config.virtual_hosts(0).routes(0); route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_route() ->mutable_cluster_header(); SetRouteConfiguration(balancer_.get(), route_config); (void)SendRpc(); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Tests that LDS client should ignore route which has query_parameters. TEST_P(LdsRdsTest, RouteMatchHasQueryParameters) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); route1->mutable_match()->add_query_parameters(); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should send a ACK if route match has a prefix // that is either empty or a single slash TEST_P(LdsRdsTest, RouteMatchHasValidPrefixEmptyOrSingleSlash) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix(""); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix("/"); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); (void)SendRpc(); const auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Tests that LDS client should ignore route which has a path // prefix string does not start with "/". TEST_P(LdsRdsTest, RouteMatchHasInvalidPrefixNoLeadingSlash) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("grpc.testing.EchoTest1Service/"); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should ignore route which has a prefix // string with more than 2 slashes. TEST_P(LdsRdsTest, RouteMatchHasInvalidPrefixExtraContent) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/Echo1/"); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should ignore route which has a prefix // string "//". TEST_P(LdsRdsTest, RouteMatchHasInvalidPrefixDoubleSlash) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("//"); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should ignore route which has path // but it's empty. TEST_P(LdsRdsTest, RouteMatchHasInvalidPathEmptyPath) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path(""); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should ignore route which has path // string does not start with "/". TEST_P(LdsRdsTest, RouteMatchHasInvalidPathNoLeadingSlash) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("grpc.testing.EchoTest1Service/Echo1"); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should ignore route which has path // string that has too many slashes; for example, ends with "/". TEST_P(LdsRdsTest, RouteMatchHasInvalidPathTooManySlashes) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service/Echo1/"); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should ignore route which has path // string that has only 1 slash: missing "/" between service and method. TEST_P(LdsRdsTest, RouteMatchHasInvalidPathOnlyOneSlash) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service.Echo1"); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should ignore route which has path // string that is missing service. TEST_P(LdsRdsTest, RouteMatchHasInvalidPathMissingService) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("//Echo1"); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Tests that LDS client should ignore route which has path // string that is missing method. TEST_P(LdsRdsTest, RouteMatchHasInvalidPathMissingMethod) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service/"); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("No valid routes specified.")); } // Test that LDS client should reject route which has invalid path regex. TEST_P(LdsRdsTest, RouteMatchHasInvalidPathRegex) { const char* kNewCluster1Name = "new_cluster_1"; RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->mutable_safe_regex()->set_regex("a[z-a]"); route1->mutable_route()->set_cluster(kNewCluster1Name); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "path matcher: Invalid regex string specified in matcher.")); } // Tests that LDS client should fail RPCs with UNAVAILABLE status code if the // matching route has an action other than RouteAction. TEST_P(LdsRdsTest, MatchingRouteHasNoRouteAction) { RouteConfiguration route_config = default_route_config_; // Set a route with an inappropriate route action auto* vhost = route_config.mutable_virtual_hosts(0); vhost->mutable_routes(0)->mutable_redirect(); // Add another route to make sure that the resolver code actually tries to // match to a route instead of using a shorthand logic to error out. auto* route = vhost->add_routes(); route->mutable_match()->set_prefix(""); route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::UNAVAILABLE, "Matching route has inappropriate action"); } TEST_P(LdsRdsTest, RouteActionClusterHasEmptyClusterName) { RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); route1->mutable_route()->set_cluster(""); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr("RouteAction cluster contains empty cluster name.")); } TEST_P(LdsRdsTest, RouteActionWeightedTargetHasIncorrectTotalWeightSet) { const size_t kWeight75 = 75; const char* kNewCluster1Name = "new_cluster_1"; RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* weighted_cluster1 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster1->set_name(kNewCluster1Name); weighted_cluster1->mutable_weight()->set_value(kWeight75); route1->mutable_route() ->mutable_weighted_clusters() ->mutable_total_weight() ->set_value(kWeight75 + 1); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "RouteAction weighted_cluster has incorrect total weight")); } TEST_P(LdsRdsTest, RouteActionWeightedClusterHasZeroTotalWeight) { const char* kNewCluster1Name = "new_cluster_1"; RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* weighted_cluster1 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster1->set_name(kNewCluster1Name); weighted_cluster1->mutable_weight()->set_value(0); route1->mutable_route() ->mutable_weighted_clusters() ->mutable_total_weight() ->set_value(0); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr( "RouteAction weighted_cluster has no valid clusters specified.")); } TEST_P(LdsRdsTest, RouteActionWeightedTargetClusterHasEmptyClusterName) { const size_t kWeight75 = 75; RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* weighted_cluster1 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster1->set_name(""); weighted_cluster1->mutable_weight()->set_value(kWeight75); route1->mutable_route() ->mutable_weighted_clusters() ->mutable_total_weight() ->set_value(kWeight75); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("RouteAction weighted_cluster cluster " "contains empty cluster name.")); } TEST_P(LdsRdsTest, RouteActionWeightedTargetClusterHasNoWeight) { const size_t kWeight75 = 75; const char* kNewCluster1Name = "new_cluster_1"; RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* weighted_cluster1 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster1->set_name(kNewCluster1Name); route1->mutable_route() ->mutable_weighted_clusters() ->mutable_total_weight() ->set_value(kWeight75); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "RouteAction weighted_cluster cluster missing weight")); } TEST_P(LdsRdsTest, RouteHeaderMatchInvalidRegex) { const char* kNewCluster1Name = "new_cluster_1"; RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* header_matcher1 = route1->mutable_match()->add_headers(); header_matcher1->set_name("header1"); header_matcher1->mutable_safe_regex_match()->set_regex("a[z-a]"); route1->mutable_route()->set_cluster(kNewCluster1Name); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr( "header matcher: Invalid regex string specified in matcher.")); } TEST_P(LdsRdsTest, RouteHeaderMatchInvalidRange) { const char* kNewCluster1Name = "new_cluster_1"; RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* header_matcher1 = route1->mutable_match()->add_headers(); header_matcher1->set_name("header1"); header_matcher1->mutable_range_match()->set_start(1001); header_matcher1->mutable_range_match()->set_end(1000); route1->mutable_route()->set_cluster(kNewCluster1Name); SetRouteConfiguration(balancer_.get(), route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr( "header matcher: Invalid range specifier specified: end cannot be " "smaller than start.")); } // Tests that LDS client should choose the default route (with no matching // specified) after unable to find a match with previous routes. TEST_P(LdsRdsTest, XdsRoutingPathMatching) { CreateAndStartBackends(4); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const size_t kNumEcho1Rpcs = 10; const size_t kNumEcho2Rpcs = 20; const size_t kNumEchoRpcs = 30; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 2)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(3, 4)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); // Populating Route Configurations for LDS. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service/Echo1"); route1->mutable_route()->set_cluster(kNewCluster1Name); auto* route2 = new_route_config.mutable_virtual_hosts(0)->add_routes(); route2->mutable_match()->set_path("/grpc.testing.EchoTest2Service/Echo2"); route2->mutable_route()->set_cluster(kNewCluster2Name); auto* route3 = new_route_config.mutable_virtual_hosts(0)->add_routes(); route3->mutable_match()->set_path("/grpc.testing.EchoTest3Service/Echo3"); route3->mutable_route()->set_cluster(kDefaultClusterName); auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); WaitForAllBackends(DEBUG_LOCATION, 0, 2, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_timeout_ms(5000)); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_wait_for_ready(true)); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs, RpcOptions() .set_rpc_service(SERVICE_ECHO1) .set_rpc_method(METHOD_ECHO1) .set_wait_for_ready(true)); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho2Rpcs, RpcOptions() .set_rpc_service(SERVICE_ECHO2) .set_rpc_method(METHOD_ECHO2) .set_wait_for_ready(true)); // Make sure RPCs all go to the correct backend. for (size_t i = 0; i < 2; ++i) { EXPECT_EQ(kNumEchoRpcs / 2, backends_[i]->backend_service()->request_count()); EXPECT_EQ(0, backends_[i]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[i]->backend_service2()->request_count()); } EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); EXPECT_EQ(kNumEcho1Rpcs, backends_[2]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[2]->backend_service2()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service1()->request_count()); EXPECT_EQ(kNumEcho2Rpcs, backends_[3]->backend_service2()->request_count()); } TEST_P(LdsRdsTest, XdsRoutingPathMatchingCaseInsensitive) { CreateAndStartBackends(4); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const size_t kNumEcho1Rpcs = 10; const size_t kNumEchoRpcs = 30; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); // Populating Route Configurations for LDS. RouteConfiguration new_route_config = default_route_config_; // First route will not match, since it's case-sensitive. // Second route will match with same path. auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("/GrPc.TeStInG.EcHoTeSt1SErViCe/EcHo1"); route1->mutable_route()->set_cluster(kNewCluster1Name); auto* route2 = new_route_config.mutable_virtual_hosts(0)->add_routes(); route2->mutable_match()->set_path("/GrPc.TeStInG.EcHoTeSt1SErViCe/EcHo1"); route2->mutable_match()->mutable_case_sensitive()->set_value(false); route2->mutable_route()->set_cluster(kNewCluster2Name); auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_wait_for_ready(true)); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs, RpcOptions() .set_rpc_service(SERVICE_ECHO1) .set_rpc_method(METHOD_ECHO1) .set_wait_for_ready(true)); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); EXPECT_EQ(kNumEcho1Rpcs, backends_[2]->backend_service1()->request_count()); } TEST_P(LdsRdsTest, XdsRoutingPrefixMatching) { CreateAndStartBackends(4); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const size_t kNumEcho1Rpcs = 10; const size_t kNumEcho2Rpcs = 20; const size_t kNumEchoRpcs = 30; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 2)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(3, 4)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); // Populating Route Configurations for LDS. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); route1->mutable_route()->set_cluster(kNewCluster1Name); auto* route2 = new_route_config.mutable_virtual_hosts(0)->add_routes(); route2->mutable_match()->set_prefix("/grpc.testing.EchoTest2Service/"); route2->mutable_route()->set_cluster(kNewCluster2Name); auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); WaitForAllBackends(DEBUG_LOCATION, 0, 2); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_wait_for_ready(true)); CheckRpcSendOk( DEBUG_LOCATION, kNumEcho1Rpcs, RpcOptions().set_rpc_service(SERVICE_ECHO1).set_wait_for_ready(true)); CheckRpcSendOk( DEBUG_LOCATION, kNumEcho2Rpcs, RpcOptions().set_rpc_service(SERVICE_ECHO2).set_wait_for_ready(true)); // Make sure RPCs all go to the correct backend. for (size_t i = 0; i < 2; ++i) { EXPECT_EQ(kNumEchoRpcs / 2, backends_[i]->backend_service()->request_count()); EXPECT_EQ(0, backends_[i]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[i]->backend_service2()->request_count()); } EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); EXPECT_EQ(kNumEcho1Rpcs, backends_[2]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[2]->backend_service2()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service1()->request_count()); EXPECT_EQ(kNumEcho2Rpcs, backends_[3]->backend_service2()->request_count()); } TEST_P(LdsRdsTest, XdsRoutingPrefixMatchingCaseInsensitive) { CreateAndStartBackends(3); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const size_t kNumEcho1Rpcs = 10; const size_t kNumEchoRpcs = 30; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); // Populating Route Configurations for LDS. RouteConfiguration new_route_config = default_route_config_; // First route will not match, since it's case-sensitive. // Second route will match with same path. auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/GrPc.TeStInG.EcHoTeSt1SErViCe"); route1->mutable_route()->set_cluster(kNewCluster1Name); auto* route2 = new_route_config.mutable_virtual_hosts(0)->add_routes(); route2->mutable_match()->set_prefix("/GrPc.TeStInG.EcHoTeSt1SErViCe"); route2->mutable_match()->mutable_case_sensitive()->set_value(false); route2->mutable_route()->set_cluster(kNewCluster2Name); auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_wait_for_ready(true)); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs, RpcOptions() .set_rpc_service(SERVICE_ECHO1) .set_rpc_method(METHOD_ECHO1) .set_wait_for_ready(true)); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); EXPECT_EQ(kNumEcho1Rpcs, backends_[2]->backend_service1()->request_count()); } TEST_P(LdsRdsTest, XdsRoutingPathRegexMatching) { CreateAndStartBackends(4); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const size_t kNumEcho1Rpcs = 10; const size_t kNumEcho2Rpcs = 20; const size_t kNumEchoRpcs = 30; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 2)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(3, 4)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); // Populating Route Configurations for LDS. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); // Will match "/grpc.testing.EchoTest1Service/" route1->mutable_match()->mutable_safe_regex()->set_regex(".*1.*"); route1->mutable_route()->set_cluster(kNewCluster1Name); auto* route2 = new_route_config.mutable_virtual_hosts(0)->add_routes(); // Will match "/grpc.testing.EchoTest2Service/" route2->mutable_match()->mutable_safe_regex()->set_regex(".*2.*"); route2->mutable_route()->set_cluster(kNewCluster2Name); auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); WaitForAllBackends(DEBUG_LOCATION, 0, 2); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_wait_for_ready(true)); CheckRpcSendOk( DEBUG_LOCATION, kNumEcho1Rpcs, RpcOptions().set_rpc_service(SERVICE_ECHO1).set_wait_for_ready(true)); CheckRpcSendOk( DEBUG_LOCATION, kNumEcho2Rpcs, RpcOptions().set_rpc_service(SERVICE_ECHO2).set_wait_for_ready(true)); // Make sure RPCs all go to the correct backend. for (size_t i = 0; i < 2; ++i) { EXPECT_EQ(kNumEchoRpcs / 2, backends_[i]->backend_service()->request_count()); EXPECT_EQ(0, backends_[i]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[i]->backend_service2()->request_count()); } EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); EXPECT_EQ(kNumEcho1Rpcs, backends_[2]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[2]->backend_service2()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service1()->request_count()); EXPECT_EQ(kNumEcho2Rpcs, backends_[3]->backend_service2()->request_count()); } TEST_P(LdsRdsTest, XdsRoutingWeightedCluster) { CreateAndStartBackends(3); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const char* kNotUsedClusterName = "not_used_cluster"; const size_t kNumEchoRpcs = 10; // RPCs that will go to a fixed backend. const size_t kWeight75 = 75; const size_t kWeight25 = 25; const double kErrorTolerance = 0.05; const double kWeight75Percent = static_cast(kWeight75) / 100; const double kWeight25Percent = static_cast(kWeight25) / 100; const size_t kNumEcho1Rpcs = ComputeIdealNumRpcs(kWeight75Percent, kErrorTolerance); // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); // Populating Route Configurations for LDS. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* weighted_cluster1 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster1->set_name(kNewCluster1Name); weighted_cluster1->mutable_weight()->set_value(kWeight75); auto* weighted_cluster2 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster2->set_name(kNewCluster2Name); weighted_cluster2->mutable_weight()->set_value(kWeight25); // Cluster with weight 0 will not be used. auto* weighted_cluster3 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster3->set_name(kNotUsedClusterName); weighted_cluster3->mutable_weight()->set_value(0); route1->mutable_route() ->mutable_weighted_clusters() ->mutable_total_weight() ->set_value(kWeight75 + kWeight25); auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); WaitForAllBackends(DEBUG_LOCATION, 0, 1); WaitForAllBackends(DEBUG_LOCATION, 1, 3, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_rpc_service(SERVICE_ECHO1)); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs, RpcOptions().set_rpc_service(SERVICE_ECHO1)); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); const int weight_75_request_count = backends_[1]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); const int weight_25_request_count = backends_[2]->backend_service1()->request_count(); gpr_log(GPR_INFO, "target_75 received %d rpcs and target_25 received %d rpcs", weight_75_request_count, weight_25_request_count); EXPECT_THAT(static_cast(weight_75_request_count) / kNumEcho1Rpcs, ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); EXPECT_THAT(static_cast(weight_25_request_count) / kNumEcho1Rpcs, ::testing::DoubleNear(kWeight25Percent, kErrorTolerance)); } TEST_P(LdsRdsTest, RouteActionWeightedTargetDefaultRoute) { CreateAndStartBackends(3); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const size_t kWeight75 = 75; const size_t kWeight25 = 25; const double kErrorTolerance = 0.05; const double kWeight75Percent = static_cast(kWeight75) / 100; const double kWeight25Percent = static_cast(kWeight25) / 100; const size_t kNumEchoRpcs = ComputeIdealNumRpcs(kWeight75Percent, kErrorTolerance); // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); // Populating Route Configurations for LDS. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix(""); auto* weighted_cluster1 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster1->set_name(kNewCluster1Name); weighted_cluster1->mutable_weight()->set_value(kWeight75); auto* weighted_cluster2 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster2->set_name(kNewCluster2Name); weighted_cluster2->mutable_weight()->set_value(kWeight25); route1->mutable_route() ->mutable_weighted_clusters() ->mutable_total_weight() ->set_value(kWeight75 + kWeight25); SetRouteConfiguration(balancer_.get(), new_route_config); WaitForAllBackends(DEBUG_LOCATION, 1, 3); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); // Make sure RPCs all go to the correct backend. EXPECT_EQ(0, backends_[0]->backend_service()->request_count()); const int weight_75_request_count = backends_[1]->backend_service()->request_count(); const int weight_25_request_count = backends_[2]->backend_service()->request_count(); gpr_log(GPR_INFO, "target_75 received %d rpcs and target_25 received %d rpcs", weight_75_request_count, weight_25_request_count); EXPECT_THAT(static_cast(weight_75_request_count) / kNumEchoRpcs, ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); EXPECT_THAT(static_cast(weight_25_request_count) / kNumEchoRpcs, ::testing::DoubleNear(kWeight25Percent, kErrorTolerance)); } TEST_P(LdsRdsTest, XdsRoutingWeightedClusterUpdateWeights) { CreateAndStartBackends(4); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const char* kNewCluster3Name = "new_cluster_3"; const char* kNewEdsService3Name = "new_eds_service_name_3"; const size_t kNumEchoRpcs = 10; const size_t kWeight75 = 75; const size_t kWeight25 = 25; const size_t kWeight50 = 50; const double kErrorTolerance = 0.05; const double kWeight75Percent = static_cast(kWeight75) / 100; const double kWeight25Percent = static_cast(kWeight25) / 100; const double kWeight50Percent = static_cast(kWeight50) / 100; const size_t kNumEcho1Rpcs7525 = ComputeIdealNumRpcs(kWeight75Percent, kErrorTolerance); const size_t kNumEcho1Rpcs5050 = ComputeIdealNumRpcs(kWeight50Percent, kErrorTolerance); // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); EdsResourceArgs args3({ {"locality0", CreateEndpointsForBackends(3, 4)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args3, kNewEdsService3Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); Cluster new_cluster3 = default_cluster_; new_cluster3.set_name(kNewCluster3Name); new_cluster3.mutable_eds_cluster_config()->set_service_name( kNewEdsService3Name); balancer_->ads_service()->SetCdsResource(new_cluster3); // Populating Route Configurations. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* weighted_cluster1 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster1->set_name(kNewCluster1Name); weighted_cluster1->mutable_weight()->set_value(kWeight75); auto* weighted_cluster2 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster2->set_name(kNewCluster2Name); weighted_cluster2->mutable_weight()->set_value(kWeight25); route1->mutable_route() ->mutable_weighted_clusters() ->mutable_total_weight() ->set_value(kWeight75 + kWeight25); auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); WaitForAllBackends(DEBUG_LOCATION, 0, 1, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_timeout_ms(5000)); WaitForAllBackends( DEBUG_LOCATION, 1, 3, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_rpc_service(SERVICE_ECHO1).set_timeout_ms(5000)); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_timeout_ms(5000)); CheckRpcSendOk( DEBUG_LOCATION, kNumEcho1Rpcs7525, RpcOptions().set_rpc_service(SERVICE_ECHO1).set_timeout_ms(5000)); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); const int weight_75_request_count = backends_[1]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[1]->backend_service2()->request_count()); EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); const int weight_25_request_count = backends_[2]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[3]->backend_service()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service1()->request_count()); gpr_log(GPR_INFO, "target_75 received %d rpcs and target_25 received %d rpcs", weight_75_request_count, weight_25_request_count); EXPECT_THAT(static_cast(weight_75_request_count) / kNumEcho1Rpcs7525, ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); EXPECT_THAT(static_cast(weight_25_request_count) / kNumEcho1Rpcs7525, ::testing::DoubleNear(kWeight25Percent, kErrorTolerance)); // Change Route Configurations: same clusters different weights. weighted_cluster1->mutable_weight()->set_value(kWeight50); weighted_cluster2->mutable_weight()->set_value(kWeight50); // Change default route to a new cluster to help to identify when new // polices are seen by the client. default_route->mutable_route()->set_cluster(kNewCluster3Name); SetRouteConfiguration(balancer_.get(), new_route_config); ResetBackendCounters(); WaitForAllBackends(DEBUG_LOCATION, 3, 4, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_timeout_ms(5000)); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_timeout_ms(5000)); CheckRpcSendOk( DEBUG_LOCATION, kNumEcho1Rpcs5050, RpcOptions().set_rpc_service(SERVICE_ECHO1).set_timeout_ms(5000)); // Make sure RPCs all go to the correct backend. EXPECT_EQ(0, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); const int weight_50_request_count_1 = backends_[1]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); const int weight_50_request_count_2 = backends_[2]->backend_service1()->request_count(); EXPECT_EQ(kNumEchoRpcs, backends_[3]->backend_service()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service1()->request_count()); EXPECT_THAT( static_cast(weight_50_request_count_1) / kNumEcho1Rpcs5050, ::testing::DoubleNear(kWeight50Percent, kErrorTolerance)); EXPECT_THAT( static_cast(weight_50_request_count_2) / kNumEcho1Rpcs5050, ::testing::DoubleNear(kWeight50Percent, kErrorTolerance)); } TEST_P(LdsRdsTest, XdsRoutingWeightedClusterUpdateClusters) { CreateAndStartBackends(4); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const char* kNewCluster3Name = "new_cluster_3"; const char* kNewEdsService3Name = "new_eds_service_name_3"; const size_t kNumEchoRpcs = 10; const size_t kWeight75 = 75; const size_t kWeight25 = 25; const size_t kWeight50 = 50; const double kErrorTolerance = 0.05; const double kWeight75Percent = static_cast(kWeight75) / 100; const double kWeight25Percent = static_cast(kWeight25) / 100; const double kWeight50Percent = static_cast(kWeight50) / 100; const size_t kNumEcho1Rpcs7525 = ComputeIdealNumRpcs(kWeight75Percent, kErrorTolerance); const size_t kNumEcho1Rpcs5050 = ComputeIdealNumRpcs(kWeight50Percent, kErrorTolerance); // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); EdsResourceArgs args3({ {"locality0", CreateEndpointsForBackends(3, 4)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args3, kNewEdsService3Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); Cluster new_cluster3 = default_cluster_; new_cluster3.set_name(kNewCluster3Name); new_cluster3.mutable_eds_cluster_config()->set_service_name( kNewEdsService3Name); balancer_->ads_service()->SetCdsResource(new_cluster3); // Populating Route Configurations. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* weighted_cluster1 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster1->set_name(kNewCluster1Name); weighted_cluster1->mutable_weight()->set_value(kWeight75); auto* weighted_cluster2 = route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); weighted_cluster2->set_name(kDefaultClusterName); weighted_cluster2->mutable_weight()->set_value(kWeight25); route1->mutable_route() ->mutable_weighted_clusters() ->mutable_total_weight() ->set_value(kWeight75 + kWeight25); auto* default_route = new_route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); WaitForBackend(DEBUG_LOCATION, 0); WaitForBackend(DEBUG_LOCATION, 1, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_rpc_service(SERVICE_ECHO1)); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs7525, RpcOptions().set_rpc_service(SERVICE_ECHO1)); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); int weight_25_request_count = backends_[0]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); int weight_75_request_count = backends_[1]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); EXPECT_EQ(0, backends_[2]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service1()->request_count()); gpr_log(GPR_INFO, "target_75 received %d rpcs and target_25 received %d rpcs", weight_75_request_count, weight_25_request_count); EXPECT_THAT(static_cast(weight_75_request_count) / kNumEcho1Rpcs7525, ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); EXPECT_THAT(static_cast(weight_25_request_count) / kNumEcho1Rpcs7525, ::testing::DoubleNear(kWeight25Percent, kErrorTolerance)); // Change Route Configurations: new set of clusters with different weights. weighted_cluster1->mutable_weight()->set_value(kWeight50); weighted_cluster2->set_name(kNewCluster2Name); weighted_cluster2->mutable_weight()->set_value(kWeight50); SetRouteConfiguration(balancer_.get(), new_route_config); ResetBackendCounters(); WaitForBackend(DEBUG_LOCATION, 2, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_rpc_service(SERVICE_ECHO1)); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs5050, RpcOptions().set_rpc_service(SERVICE_ECHO1)); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); const int weight_50_request_count_1 = backends_[1]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); const int weight_50_request_count_2 = backends_[2]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[3]->backend_service()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service1()->request_count()); EXPECT_THAT( static_cast(weight_50_request_count_1) / kNumEcho1Rpcs5050, ::testing::DoubleNear(kWeight50Percent, kErrorTolerance)); EXPECT_THAT( static_cast(weight_50_request_count_2) / kNumEcho1Rpcs5050, ::testing::DoubleNear(kWeight50Percent, kErrorTolerance)); // Change Route Configurations. weighted_cluster1->mutable_weight()->set_value(kWeight75); weighted_cluster2->set_name(kNewCluster3Name); weighted_cluster2->mutable_weight()->set_value(kWeight25); SetRouteConfiguration(balancer_.get(), new_route_config); ResetBackendCounters(); WaitForBackend(DEBUG_LOCATION, 3, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_rpc_service(SERVICE_ECHO1)); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs7525, RpcOptions().set_rpc_service(SERVICE_ECHO1)); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); weight_75_request_count = backends_[1]->backend_service1()->request_count(); EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); EXPECT_EQ(0, backends_[2]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[3]->backend_service()->request_count()); weight_25_request_count = backends_[3]->backend_service1()->request_count(); gpr_log(GPR_INFO, "target_75 received %d rpcs and target_25 received %d rpcs", weight_75_request_count, weight_25_request_count); EXPECT_THAT(static_cast(weight_75_request_count) / kNumEcho1Rpcs7525, ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); EXPECT_THAT(static_cast(weight_25_request_count) / kNumEcho1Rpcs7525, ::testing::DoubleNear(kWeight25Percent, kErrorTolerance)); } TEST_P(LdsRdsTest, XdsRoutingClusterUpdateClusters) { CreateAndStartBackends(2); const char* kNewClusterName = "new_cluster"; const char* kNewEdsServiceName = "new_eds_service_name"; const size_t kNumEchoRpcs = 5; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsServiceName)); // Populate new CDS resources. Cluster new_cluster = default_cluster_; new_cluster.set_name(kNewClusterName); new_cluster.mutable_eds_cluster_config()->set_service_name( kNewEdsServiceName); balancer_->ads_service()->SetCdsResource(new_cluster); // Send Route Configuration. RouteConfiguration new_route_config = default_route_config_; SetRouteConfiguration(balancer_.get(), new_route_config); WaitForAllBackends(DEBUG_LOCATION, 0, 1); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); // Change Route Configurations: new default cluster. auto* default_route = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); default_route->mutable_route()->set_cluster(kNewClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); WaitForAllBackends(DEBUG_LOCATION, 1, 2); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); // Make sure RPCs all go to the correct backend. EXPECT_EQ(kNumEchoRpcs, backends_[1]->backend_service()->request_count()); } TEST_P(LdsRdsTest, XdsRoutingClusterUpdateClustersWithPickingDelays) { // Start with only backend 1 up, but the default cluster pointing to // backend 0, which is down. CreateBackends(2); StartBackend(1); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Start an RPC with wait_for_ready=true and no deadline. This will // stay pending until backend 0 is reachable. LongRunningRpc rpc; rpc.StartRpc(stub_.get(), RpcOptions().set_wait_for_ready(true).set_timeout_ms(0)); // Send a non-wait_for_ready RPC, which should fail. This tells us // that the client has received the update and attempted to connect. constexpr char kErrorMessageRegex[] = "connections to all backends failing; last error: " "(UNKNOWN: (ipv6:%5B::1%5D|ipv4:127.0.0.1):[0-9]+: " "Failed to connect to remote host: Connection refused|" "UNAVAILABLE: (ipv6:%5B::1%5D|ipv4:127.0.0.1):[0-9]+: " "Failed to connect to remote host: FD shutdown)"; CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::UNAVAILABLE, kErrorMessageRegex); // Now create a new cluster, pointing to backend 1. const char* kNewClusterName = "new_cluster"; const char* kNewEdsServiceName = "new_eds_service_name"; EdsResourceArgs args1({{"locality0", CreateEndpointsForBackends(1, 2)}}); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsServiceName)); // Populate new CDS resources. Cluster new_cluster = default_cluster_; new_cluster.set_name(kNewClusterName); new_cluster.mutable_eds_cluster_config()->set_service_name( kNewEdsServiceName); balancer_->ads_service()->SetCdsResource(new_cluster); // Send a update RouteConfiguration to use backend 1. RouteConfiguration new_route_config = default_route_config_; auto* default_route = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); default_route->mutable_route()->set_cluster(kNewClusterName); SetRouteConfiguration(balancer_.get(), new_route_config); // Wait for RPCs to go to the new backend: 1, this ensures that the client // has processed the update. WaitForBackend( DEBUG_LOCATION, 1, [&](const RpcResult& result) { if (!result.status.ok()) { EXPECT_EQ(result.status.error_code(), StatusCode::UNAVAILABLE); EXPECT_THAT(result.status.error_message(), ::testing::MatchesRegex(kErrorMessageRegex)); } }, WaitForBackendOptions().set_reset_counters(false)); // Bring up the backend 0. Yhis will allow the delayed RPC to finally // complete. StartBackend(0); Status status = rpc.GetStatus(); EXPECT_TRUE(status.ok()) << "code=" << status.error_code() << " message=" << status.error_message(); // Make sure RPCs went to the correct backends. EXPECT_EQ(1, backends_[0]->backend_service()->request_count()); EXPECT_EQ(1, backends_[1]->backend_service()->request_count()); } TEST_P(LdsRdsTest, XdsRoutingApplyXdsTimeout) { const auto kTimeoutGrpcHeaderMax = grpc_core::Duration::Milliseconds(1500); const auto kTimeoutMaxStreamDuration = grpc_core::Duration::Milliseconds(2500); const auto kTimeoutHttpMaxStreamDuration = grpc_core::Duration::Milliseconds(3500); const auto kTimeoutApplication = grpc_core::Duration::Milliseconds(4500); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const char* kNewCluster3Name = "new_cluster_3"; const char* kNewEdsService3Name = "new_eds_service_name_3"; // Populate new EDS resources. EdsResourceArgs args({{"locality0", {MakeNonExistantEndpoint()}}}); EdsResourceArgs args1({{"locality0", {MakeNonExistantEndpoint()}}}); EdsResourceArgs args2({{"locality0", {MakeNonExistantEndpoint()}}}); EdsResourceArgs args3({{"locality0", {MakeNonExistantEndpoint()}}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args3, kNewEdsService3Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); Cluster new_cluster3 = default_cluster_; new_cluster3.set_name(kNewCluster3Name); new_cluster3.mutable_eds_cluster_config()->set_service_name( kNewEdsService3Name); balancer_->ads_service()->SetCdsResource(new_cluster3); // Construct listener. auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); // Set up HTTP max_stream_duration of 3.5 seconds SetProtoDuration( kTimeoutHttpMaxStreamDuration, http_connection_manager.mutable_common_http_protocol_options() ->mutable_max_stream_duration()); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); // Construct route config. RouteConfiguration new_route_config = default_route_config_; // route 1: Set max_stream_duration of 2.5 seconds, Set // grpc_timeout_header_max of 1.5 auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service/Echo1"); route1->mutable_route()->set_cluster(kNewCluster1Name); auto* max_stream_duration = route1->mutable_route()->mutable_max_stream_duration(); SetProtoDuration(kTimeoutMaxStreamDuration, max_stream_duration->mutable_max_stream_duration()); SetProtoDuration(kTimeoutGrpcHeaderMax, max_stream_duration->mutable_grpc_timeout_header_max()); // route 2: Set max_stream_duration of 2.5 seconds auto* route2 = new_route_config.mutable_virtual_hosts(0)->add_routes(); route2->mutable_match()->set_path("/grpc.testing.EchoTest2Service/Echo2"); route2->mutable_route()->set_cluster(kNewCluster2Name); max_stream_duration = route2->mutable_route()->mutable_max_stream_duration(); SetProtoDuration(kTimeoutMaxStreamDuration, max_stream_duration->mutable_max_stream_duration()); // route 3: No timeout values in route configuration auto* route3 = new_route_config.mutable_virtual_hosts(0)->add_routes(); route3->mutable_match()->set_path("/grpc.testing.EchoTestService/Echo"); route3->mutable_route()->set_cluster(kNewCluster3Name); // Set listener and route config. SetListenerAndRouteConfiguration(balancer_.get(), std::move(listener), new_route_config); // Test grpc_timeout_header_max of 1.5 seconds applied grpc_core::Timestamp t0 = NowFromCycleCounter(); grpc_core::Timestamp t1 = t0 + (kTimeoutGrpcHeaderMax * grpc_test_slowdown_factor()); grpc_core::Timestamp t2 = t0 + (kTimeoutMaxStreamDuration * grpc_test_slowdown_factor()); CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions() .set_rpc_service(SERVICE_ECHO1) .set_rpc_method(METHOD_ECHO1) .set_wait_for_ready(true) .set_timeout(kTimeoutApplication)); EXPECT_THAT(NowFromCycleCounter(), AdjustedClockInRange(t1, t2)); // Test max_stream_duration of 2.5 seconds applied t0 = NowFromCycleCounter(); t1 = t0 + (kTimeoutMaxStreamDuration * grpc_test_slowdown_factor()); t2 = t0 + (kTimeoutHttpMaxStreamDuration * grpc_test_slowdown_factor()); CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions() .set_rpc_service(SERVICE_ECHO2) .set_rpc_method(METHOD_ECHO2) .set_wait_for_ready(true) .set_timeout(kTimeoutApplication)); EXPECT_THAT(NowFromCycleCounter(), AdjustedClockInRange(t1, t2)); // Test http_stream_duration of 3.5 seconds applied t0 = NowFromCycleCounter(); t1 = t0 + (kTimeoutHttpMaxStreamDuration * grpc_test_slowdown_factor()); t2 = t0 + (kTimeoutApplication * grpc_test_slowdown_factor()); CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions().set_wait_for_ready(true).set_timeout(kTimeoutApplication)); EXPECT_THAT(NowFromCycleCounter(), AdjustedClockInRange(t1, t2)); } TEST_P(LdsRdsTest, XdsRoutingApplyApplicationTimeoutWhenXdsTimeoutExplicit) { const auto kTimeoutMaxStreamDuration = grpc_core::Duration::Milliseconds(2500); const auto kTimeoutHttpMaxStreamDuration = grpc_core::Duration::Milliseconds(3500); const auto kTimeoutApplication = grpc_core::Duration::Milliseconds(4500); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; // Populate new EDS resources. EdsResourceArgs args({{"locality0", {MakeNonExistantEndpoint()}}}); EdsResourceArgs args1({{"locality0", {MakeNonExistantEndpoint()}}}); EdsResourceArgs args2({{"locality0", {MakeNonExistantEndpoint()}}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); // Construct listener. auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); // Set up HTTP max_stream_duration of 3.5 seconds SetProtoDuration( kTimeoutHttpMaxStreamDuration, http_connection_manager.mutable_common_http_protocol_options() ->mutable_max_stream_duration()); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); // Construct route config. RouteConfiguration new_route_config = default_route_config_; // route 1: Set max_stream_duration of 2.5 seconds, Set // grpc_timeout_header_max of 0 auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_path("/grpc.testing.EchoTest1Service/Echo1"); route1->mutable_route()->set_cluster(kNewCluster1Name); auto* max_stream_duration = route1->mutable_route()->mutable_max_stream_duration(); SetProtoDuration(kTimeoutMaxStreamDuration, max_stream_duration->mutable_max_stream_duration()); SetProtoDuration(grpc_core::Duration::Zero(), max_stream_duration->mutable_grpc_timeout_header_max()); // route 2: Set max_stream_duration to 0 auto* route2 = new_route_config.mutable_virtual_hosts(0)->add_routes(); route2->mutable_match()->set_path("/grpc.testing.EchoTest2Service/Echo2"); route2->mutable_route()->set_cluster(kNewCluster2Name); max_stream_duration = route2->mutable_route()->mutable_max_stream_duration(); SetProtoDuration(grpc_core::Duration::Zero(), max_stream_duration->mutable_max_stream_duration()); // Set listener and route config. SetListenerAndRouteConfiguration(balancer_.get(), std::move(listener), new_route_config); // Test application timeout is applied for route 1 auto t0 = system_clock::now(); CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions() .set_rpc_service(SERVICE_ECHO1) .set_rpc_method(METHOD_ECHO1) .set_wait_for_ready(true) .set_timeout(kTimeoutApplication)); auto elapsed_nano_seconds = std::chrono::duration_cast(system_clock::now() - t0); EXPECT_GT( elapsed_nano_seconds.count(), (kTimeoutApplication * grpc_test_slowdown_factor()).millis() * 1000); // Test application timeout is applied for route 2 t0 = system_clock::now(); CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions() .set_rpc_service(SERVICE_ECHO2) .set_rpc_method(METHOD_ECHO2) .set_wait_for_ready(true) .set_timeout(kTimeoutApplication)); elapsed_nano_seconds = std::chrono::duration_cast( system_clock::now() - t0); EXPECT_GT( elapsed_nano_seconds.count(), (kTimeoutApplication * grpc_test_slowdown_factor()).millis() * 1000); } TEST_P(LdsRdsTest, XdsRoutingApplyApplicationTimeoutWhenHttpTimeoutExplicit) { const auto kTimeoutApplication = grpc_core::Duration::Milliseconds(4500); // Populate new EDS resources. EdsResourceArgs args({{"locality0", {MakeNonExistantEndpoint()}}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); auto listener = default_listener_; HttpConnectionManager http_connection_manager; listener.mutable_api_listener()->mutable_api_listener()->UnpackTo( &http_connection_manager); // Set up HTTP max_stream_duration to be explicit 0 auto* duration = http_connection_manager.mutable_common_http_protocol_options() ->mutable_max_stream_duration(); duration->set_seconds(0); duration->set_nanos(0); listener.mutable_api_listener()->mutable_api_listener()->PackFrom( http_connection_manager); // Set listener and route config. SetListenerAndRouteConfiguration(balancer_.get(), std::move(listener), default_route_config_); // Test application timeout is applied for route 1 auto t0 = system_clock::now(); CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions().set_wait_for_ready(true).set_timeout(kTimeoutApplication)); auto elapsed_nano_seconds = std::chrono::duration_cast(system_clock::now() - t0); EXPECT_GT( elapsed_nano_seconds.count(), (kTimeoutApplication * grpc_test_slowdown_factor()).millis() * 1000); } // Test to ensure application-specified deadline won't be affected when // the xDS config does not specify a timeout. TEST_P(LdsRdsTest, XdsRoutingWithOnlyApplicationTimeout) { const auto kTimeoutApplication = grpc_core::Duration::Milliseconds(4500); // Populate new EDS resources. EdsResourceArgs args({{"locality0", {MakeNonExistantEndpoint()}}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); auto t0 = system_clock::now(); CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions().set_wait_for_ready(true).set_timeout(kTimeoutApplication)); auto elapsed_nano_seconds = std::chrono::duration_cast(system_clock::now() - t0); EXPECT_GT( elapsed_nano_seconds.count(), (kTimeoutApplication * grpc_test_slowdown_factor()).millis() * 1000); } TEST_P(LdsRdsTest, XdsRetryPolicyNumRetries) { CreateAndStartBackends(1); const size_t kNumRetries = 3; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Construct route config to set retry policy. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); auto* retry_policy = route1->mutable_route()->mutable_retry_policy(); retry_policy->set_retry_on( "5xx,cancelled,deadline-exceeded,internal,resource-exhausted," "unavailable"); retry_policy->mutable_num_retries()->set_value(kNumRetries); SetRouteConfiguration(balancer_.get(), new_route_config); // Ensure we retried the correct number of times on all supported status. CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::CANCELLED, "", RpcOptions().set_server_expected_error(StatusCode::CANCELLED)); EXPECT_EQ(kNumRetries + 1, backends_[0]->backend_service()->request_count()); ResetBackendCounters(); CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "", RpcOptions().set_server_expected_error(StatusCode::DEADLINE_EXCEEDED)); EXPECT_EQ(kNumRetries + 1, backends_[0]->backend_service()->request_count()); ResetBackendCounters(); CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::INTERNAL, "", RpcOptions().set_server_expected_error(StatusCode::INTERNAL)); EXPECT_EQ(kNumRetries + 1, backends_[0]->backend_service()->request_count()); ResetBackendCounters(); CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::RESOURCE_EXHAUSTED, "", RpcOptions().set_server_expected_error(StatusCode::RESOURCE_EXHAUSTED)); EXPECT_EQ(kNumRetries + 1, backends_[0]->backend_service()->request_count()); ResetBackendCounters(); CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::UNAVAILABLE, "", RpcOptions().set_server_expected_error(StatusCode::UNAVAILABLE)); EXPECT_EQ(kNumRetries + 1, backends_[0]->backend_service()->request_count()); ResetBackendCounters(); // Ensure we don't retry on an unsupported status. CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::UNAUTHENTICATED, "", RpcOptions().set_server_expected_error(StatusCode::UNAUTHENTICATED)); EXPECT_EQ(1, backends_[0]->backend_service()->request_count()); } TEST_P(LdsRdsTest, XdsRetryPolicyAtVirtualHostLevel) { CreateAndStartBackends(1); const size_t kNumRetries = 3; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Construct route config to set retry policy. RouteConfiguration new_route_config = default_route_config_; auto* retry_policy = new_route_config.mutable_virtual_hosts(0)->mutable_retry_policy(); retry_policy->set_retry_on( "cancelled,deadline-exceeded,internal,resource-exhausted,unavailable"); retry_policy->mutable_num_retries()->set_value(kNumRetries); SetRouteConfiguration(balancer_.get(), new_route_config); // Ensure we retried the correct number of times on a supported status. CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "", RpcOptions().set_server_expected_error(StatusCode::DEADLINE_EXCEEDED)); EXPECT_EQ(kNumRetries + 1, backends_[0]->backend_service()->request_count()); } TEST_P(LdsRdsTest, XdsRetryPolicyLongBackOff) { CreateAndStartBackends(1); // Set num retries to 3, but due to longer back off, we expect only 1 retry // will take place. const size_t kNumRetries = 3; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Construct route config to set retry policy. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); auto* retry_policy = route1->mutable_route()->mutable_retry_policy(); retry_policy->set_retry_on( "5xx,cancelled,deadline-exceeded,internal,resource-exhausted," "unavailable"); retry_policy->mutable_num_retries()->set_value(kNumRetries); // Set backoff to 1 second, 1/2 of rpc timeout of 2 second. SetProtoDuration( grpc_core::Duration::Seconds(1), retry_policy->mutable_retry_back_off()->mutable_base_interval()); SetRouteConfiguration(balancer_.get(), new_route_config); // No need to set max interval and just let it be the default of 10x of base. // We expect 1 retry before the RPC times out with DEADLINE_EXCEEDED. CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions().set_timeout_ms(2500).set_server_expected_error( StatusCode::CANCELLED)); EXPECT_EQ(1 + 1, backends_[0]->backend_service()->request_count()); } TEST_P(LdsRdsTest, XdsRetryPolicyMaxBackOff) { CreateAndStartBackends(1); // Set num retries to 3, but due to longer back off, we expect only 2 retry // will take place, while the 2nd one will obey the max backoff. const size_t kNumRetries = 3; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Construct route config to set retry policy. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); auto* retry_policy = route1->mutable_route()->mutable_retry_policy(); retry_policy->set_retry_on( "5xx,cancelled,deadline-exceeded,internal,resource-exhausted," "unavailable"); retry_policy->mutable_num_retries()->set_value(kNumRetries); // Set backoff to 1 second. SetProtoDuration( grpc_core::Duration::Seconds(1), retry_policy->mutable_retry_back_off()->mutable_base_interval()); // Set max interval to be the same as base, so 2 retries will take 2 seconds // and both retries will take place before the 2.5 seconds rpc timeout. // Tested to ensure if max is not set, this test will be the same as // XdsRetryPolicyLongBackOff and we will only see 1 retry in that case. SetProtoDuration( grpc_core::Duration::Seconds(1), retry_policy->mutable_retry_back_off()->mutable_max_interval()); SetRouteConfiguration(balancer_.get(), new_route_config); // Send an initial RPC to make sure we get connected (we don't want // the channel startup time to affect the retry timing). CheckRpcSendOk(DEBUG_LOCATION); ResetBackendCounters(); // We expect 2 retry before the RPC times out with DEADLINE_EXCEEDED. CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "Deadline Exceeded", RpcOptions().set_timeout_ms(2500).set_server_expected_error( StatusCode::CANCELLED)); EXPECT_EQ(2 + 1, backends_[0]->backend_service()->request_count()); } TEST_P(LdsRdsTest, XdsRetryPolicyUnsupportedStatusCode) { CreateAndStartBackends(1); const size_t kNumRetries = 3; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Construct route config to set retry policy. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); auto* retry_policy = route1->mutable_route()->mutable_retry_policy(); retry_policy->set_retry_on("5xx"); retry_policy->mutable_num_retries()->set_value(kNumRetries); SetRouteConfiguration(balancer_.get(), new_route_config); // We expect no retry. CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "", RpcOptions().set_server_expected_error(StatusCode::DEADLINE_EXCEEDED)); EXPECT_EQ(1, backends_[0]->backend_service()->request_count()); } TEST_P(LdsRdsTest, XdsRetryPolicyUnsupportedStatusCodeWithVirtualHostLevelRetry) { CreateAndStartBackends(1); const size_t kNumRetries = 3; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Construct route config to set retry policy with no supported retry_on // statuses. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); auto* retry_policy = route1->mutable_route()->mutable_retry_policy(); retry_policy->set_retry_on("5xx"); retry_policy->mutable_num_retries()->set_value(kNumRetries); // Construct a virtual host level retry policy with supported statuses. auto* virtual_host_retry_policy = new_route_config.mutable_virtual_hosts(0)->mutable_retry_policy(); virtual_host_retry_policy->set_retry_on( "cancelled,deadline-exceeded,internal,resource-exhausted,unavailable"); virtual_host_retry_policy->mutable_num_retries()->set_value(kNumRetries); SetRouteConfiguration(balancer_.get(), new_route_config); // We expect no retry. CheckRpcSendFailure( DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, "", RpcOptions().set_server_expected_error(StatusCode::DEADLINE_EXCEEDED)); EXPECT_EQ(1, backends_[0]->backend_service()->request_count()); } TEST_P(LdsRdsTest, XdsRetryPolicyInvalidNumRetriesZero) { CreateAndStartBackends(1); // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Construct route config to set retry policy. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); auto* retry_policy = route1->mutable_route()->mutable_retry_policy(); retry_policy->set_retry_on("deadline-exceeded"); // Setting num_retries to zero is not valid. retry_policy->mutable_num_retries()->set_value(0); SetRouteConfiguration(balancer_.get(), new_route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr( "RouteAction RetryPolicy num_retries set to invalid value 0.")); } TEST_P(LdsRdsTest, XdsRetryPolicyRetryBackOffMissingBaseInterval) { CreateAndStartBackends(1); // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); // Construct route config to set retry policy. RouteConfiguration new_route_config = default_route_config_; auto* route1 = new_route_config.mutable_virtual_hosts(0)->mutable_routes(0); auto* retry_policy = route1->mutable_route()->mutable_retry_policy(); retry_policy->set_retry_on("deadline-exceeded"); retry_policy->mutable_num_retries()->set_value(1); // RetryBackoff is there but base interval is missing. SetProtoDuration( grpc_core::Duration::Milliseconds(250), retry_policy->mutable_retry_back_off()->mutable_max_interval()); SetRouteConfiguration(balancer_.get(), new_route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr( "RouteAction RetryPolicy RetryBackoff missing base interval.")); } TEST_P(LdsRdsTest, XdsRoutingHeadersMatching) { CreateAndStartBackends(2); const char* kNewClusterName = "new_cluster"; const char* kNewEdsServiceName = "new_eds_service_name"; const size_t kNumEcho1Rpcs = 100; const size_t kNumEchoRpcs = 5; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsServiceName)); // Populate new CDS resources. Cluster new_cluster = default_cluster_; new_cluster.set_name(kNewClusterName); new_cluster.mutable_eds_cluster_config()->set_service_name( kNewEdsServiceName); balancer_->ads_service()->SetCdsResource(new_cluster); // Populating Route Configurations for LDS. RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* header_matcher1 = route1->mutable_match()->add_headers(); header_matcher1->set_name("header1"); header_matcher1->set_exact_match("POST,PUT,GET"); auto* header_matcher2 = route1->mutable_match()->add_headers(); header_matcher2->set_name("header2"); header_matcher2->mutable_safe_regex_match()->set_regex("[a-z]*"); auto* header_matcher3 = route1->mutable_match()->add_headers(); header_matcher3->set_name("header3"); header_matcher3->mutable_range_match()->set_start(1); header_matcher3->mutable_range_match()->set_end(1000); auto* header_matcher4 = route1->mutable_match()->add_headers(); header_matcher4->set_name("header4"); header_matcher4->set_present_match(false); auto* header_matcher5 = route1->mutable_match()->add_headers(); header_matcher5->set_name("header5"); header_matcher5->set_present_match(true); auto* header_matcher6 = route1->mutable_match()->add_headers(); header_matcher6->set_name("header6"); header_matcher6->set_prefix_match("/grpc"); auto* header_matcher7 = route1->mutable_match()->add_headers(); header_matcher7->set_name("header7"); header_matcher7->set_suffix_match(".cc"); header_matcher7->set_invert_match(true); route1->mutable_route()->set_cluster(kNewClusterName); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); std::vector> metadata = { {"header1", "POST"}, {"header2", "blah"}, {"header3", "1"}, {"header5", "anything"}, {"header6", "/grpc.testing.EchoTest1Service/"}, {"header1", "PUT"}, {"header7", "grpc.java"}, {"header1", "GET"}, }; const auto header_match_rpc_options = RpcOptions() .set_rpc_service(SERVICE_ECHO1) .set_rpc_method(METHOD_ECHO1) .set_metadata(std::move(metadata)); // Make sure all backends are up. WaitForBackend(DEBUG_LOCATION, 0); WaitForBackend(DEBUG_LOCATION, 1, /*check_status=*/nullptr, WaitForBackendOptions(), header_match_rpc_options); // Send RPCs. CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs, header_match_rpc_options); EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service2()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); EXPECT_EQ(kNumEcho1Rpcs, backends_[1]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service2()->request_count()); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } TEST_P(LdsRdsTest, XdsRoutingHeadersMatchingSpecialHeaderContentType) { CreateAndStartBackends(2); const char* kNewClusterName = "new_cluster"; const char* kNewEdsServiceName = "new_eds_service_name"; const size_t kNumEchoRpcs = 100; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsServiceName)); // Populate new CDS resources. Cluster new_cluster = default_cluster_; new_cluster.set_name(kNewClusterName); new_cluster.mutable_eds_cluster_config()->set_service_name( kNewEdsServiceName); balancer_->ads_service()->SetCdsResource(new_cluster); // Populating Route Configurations for LDS. RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix(""); auto* header_matcher1 = route1->mutable_match()->add_headers(); header_matcher1->set_name("content-type"); header_matcher1->set_exact_match("notapplication/grpc"); route1->mutable_route()->set_cluster(kNewClusterName); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); auto* header_matcher2 = default_route->mutable_match()->add_headers(); header_matcher2->set_name("content-type"); header_matcher2->set_exact_match("application/grpc"); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); // Make sure the backend is up. WaitForAllBackends(DEBUG_LOCATION, 0, 1); // Send RPCs. CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs); EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } TEST_P(LdsRdsTest, XdsRoutingHeadersMatchingSpecialCasesToIgnore) { CreateAndStartBackends(2); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const size_t kNumEchoRpcs = 100; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); // Populating Route Configurations for LDS. RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix(""); auto* header_matcher1 = route1->mutable_match()->add_headers(); header_matcher1->set_name("grpc-foo-bin"); header_matcher1->set_present_match(true); route1->mutable_route()->set_cluster(kNewCluster1Name); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); // Send headers which will mismatch each route std::vector> metadata = { {"grpc-foo-bin", "grpc-foo-bin"}, }; WaitForAllBackends(DEBUG_LOCATION, 0, 1); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_metadata(metadata)); // Verify that only the default backend got RPCs since all previous routes // were mismatched. EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } TEST_P(LdsRdsTest, XdsRoutingRuntimeFractionMatching) { CreateAndStartBackends(2); const char* kNewClusterName = "new_cluster"; const char* kNewEdsServiceName = "new_eds_service_name"; const double kErrorTolerance = 0.05; const size_t kRouteMatchNumerator = 25; const double kRouteMatchPercent = static_cast(kRouteMatchNumerator) / 100; const size_t kNumRpcs = ComputeIdealNumRpcs(kRouteMatchPercent, kErrorTolerance); // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsServiceName)); // Populate new CDS resources. Cluster new_cluster = default_cluster_; new_cluster.set_name(kNewClusterName); new_cluster.mutable_eds_cluster_config()->set_service_name( kNewEdsServiceName); balancer_->ads_service()->SetCdsResource(new_cluster); // Populating Route Configurations for LDS. RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match() ->mutable_runtime_fraction() ->mutable_default_value() ->set_numerator(kRouteMatchNumerator); route1->mutable_route()->set_cluster(kNewClusterName); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); WaitForAllBackends(DEBUG_LOCATION, 0, 2); CheckRpcSendOk(DEBUG_LOCATION, kNumRpcs); const int default_backend_count = backends_[0]->backend_service()->request_count(); const int matched_backend_count = backends_[1]->backend_service()->request_count(); EXPECT_THAT(static_cast(default_backend_count) / kNumRpcs, ::testing::DoubleNear(1 - kRouteMatchPercent, kErrorTolerance)); EXPECT_THAT(static_cast(matched_backend_count) / kNumRpcs, ::testing::DoubleNear(kRouteMatchPercent, kErrorTolerance)); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } TEST_P(LdsRdsTest, XdsRoutingHeadersMatchingUnmatchCases) { CreateAndStartBackends(4); const char* kNewCluster1Name = "new_cluster_1"; const char* kNewEdsService1Name = "new_eds_service_name_1"; const char* kNewCluster2Name = "new_cluster_2"; const char* kNewEdsService2Name = "new_eds_service_name_2"; const char* kNewCluster3Name = "new_cluster_3"; const char* kNewEdsService3Name = "new_eds_service_name_3"; const size_t kNumEcho1Rpcs = 100; const size_t kNumEchoRpcs = 5; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); EdsResourceArgs args2({ {"locality0", CreateEndpointsForBackends(2, 3)}, }); EdsResourceArgs args3({ {"locality0", CreateEndpointsForBackends(3, 4)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsService1Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args2, kNewEdsService2Name)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args3, kNewEdsService3Name)); // Populate new CDS resources. Cluster new_cluster1 = default_cluster_; new_cluster1.set_name(kNewCluster1Name); new_cluster1.mutable_eds_cluster_config()->set_service_name( kNewEdsService1Name); balancer_->ads_service()->SetCdsResource(new_cluster1); Cluster new_cluster2 = default_cluster_; new_cluster2.set_name(kNewCluster2Name); new_cluster2.mutable_eds_cluster_config()->set_service_name( kNewEdsService2Name); balancer_->ads_service()->SetCdsResource(new_cluster2); Cluster new_cluster3 = default_cluster_; new_cluster3.set_name(kNewCluster3Name); new_cluster3.mutable_eds_cluster_config()->set_service_name( kNewEdsService3Name); balancer_->ads_service()->SetCdsResource(new_cluster3); // Populating Route Configurations for LDS. RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* header_matcher1 = route1->mutable_match()->add_headers(); header_matcher1->set_name("header1"); header_matcher1->set_exact_match("POST"); route1->mutable_route()->set_cluster(kNewCluster1Name); auto route2 = route_config.mutable_virtual_hosts(0)->add_routes(); route2->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* header_matcher2 = route2->mutable_match()->add_headers(); header_matcher2->set_name("header2"); header_matcher2->mutable_range_match()->set_start(1); header_matcher2->mutable_range_match()->set_end(1000); route2->mutable_route()->set_cluster(kNewCluster2Name); auto route3 = route_config.mutable_virtual_hosts(0)->add_routes(); route3->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); auto* header_matcher3 = route3->mutable_match()->add_headers(); header_matcher3->set_name("header3"); header_matcher3->mutable_safe_regex_match()->set_regex("[a-z]*"); route3->mutable_route()->set_cluster(kNewCluster3Name); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); // Send headers which will mismatch each route std::vector> metadata = { {"header1", "POST"}, {"header2", "1000"}, {"header3", "123"}, {"header1", "GET"}, }; WaitForAllBackends(DEBUG_LOCATION, 0, 1); CheckRpcSendOk(DEBUG_LOCATION, kNumEchoRpcs, RpcOptions().set_metadata(metadata)); CheckRpcSendOk(DEBUG_LOCATION, kNumEcho1Rpcs, RpcOptions() .set_rpc_service(SERVICE_ECHO1) .set_rpc_method(METHOD_ECHO1) .set_metadata(metadata)); // Verify that only the default backend got RPCs since all previous routes // were mismatched. for (size_t i = 1; i < 4; ++i) { EXPECT_EQ(0, backends_[i]->backend_service()->request_count()); EXPECT_EQ(0, backends_[i]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[i]->backend_service2()->request_count()); } EXPECT_EQ(kNumEchoRpcs, backends_[0]->backend_service()->request_count()); EXPECT_EQ(kNumEcho1Rpcs, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service2()->request_count()); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } TEST_P(LdsRdsTest, XdsRoutingChangeRoutesWithoutChangingClusters) { CreateAndStartBackends(2); const char* kNewClusterName = "new_cluster"; const char* kNewEdsServiceName = "new_eds_service_name"; // Populate new EDS resources. EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends(0, 1)}, }); EdsResourceArgs args1({ {"locality0", CreateEndpointsForBackends(1, 2)}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); balancer_->ads_service()->SetEdsResource( BuildEdsResource(args1, kNewEdsServiceName)); // Populate new CDS resources. Cluster new_cluster = default_cluster_; new_cluster.set_name(kNewClusterName); new_cluster.mutable_eds_cluster_config()->set_service_name( kNewEdsServiceName); balancer_->ads_service()->SetCdsResource(new_cluster); // Populating Route Configurations for LDS. RouteConfiguration route_config = default_route_config_; auto* route1 = route_config.mutable_virtual_hosts(0)->mutable_routes(0); route1->mutable_match()->set_prefix("/grpc.testing.EchoTest1Service/"); route1->mutable_route()->set_cluster(kNewClusterName); auto* default_route = route_config.mutable_virtual_hosts(0)->add_routes(); default_route->mutable_match()->set_prefix(""); default_route->mutable_route()->set_cluster(kDefaultClusterName); SetRouteConfiguration(balancer_.get(), route_config); // Make sure all backends are up and that requests for each RPC // service go to the right backends. WaitForBackend(DEBUG_LOCATION, 0, /*check_status=*/nullptr, WaitForBackendOptions().set_reset_counters(false)); WaitForBackend(DEBUG_LOCATION, 1, /*check_status=*/nullptr, WaitForBackendOptions().set_reset_counters(false), RpcOptions().set_rpc_service(SERVICE_ECHO1)); WaitForBackend(DEBUG_LOCATION, 0, /*check_status=*/nullptr, WaitForBackendOptions().set_reset_counters(false), RpcOptions().set_rpc_service(SERVICE_ECHO2)); // Requests for services Echo and Echo2 should have gone to backend 0. EXPECT_EQ(1, backends_[0]->backend_service()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(1, backends_[0]->backend_service2()->request_count()); // Requests for service Echo1 should have gone to backend 1. EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); EXPECT_EQ(1, backends_[1]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service2()->request_count()); // Now send an update that changes the first route to match a // different RPC service, and wait for the client to make the change. route1->mutable_match()->set_prefix("/grpc.testing.EchoTest2Service/"); SetRouteConfiguration(balancer_.get(), route_config); WaitForBackend(DEBUG_LOCATION, 1, /*check_status=*/nullptr, WaitForBackendOptions(), RpcOptions().set_rpc_service(SERVICE_ECHO2)); // Now repeat the earlier test, making sure all traffic goes to the // right place. WaitForBackend(DEBUG_LOCATION, 0, /*check_status=*/nullptr, WaitForBackendOptions().set_reset_counters(false)); WaitForBackend(DEBUG_LOCATION, 0, /*check_status=*/nullptr, WaitForBackendOptions().set_reset_counters(false), RpcOptions().set_rpc_service(SERVICE_ECHO1)); WaitForBackend(DEBUG_LOCATION, 1, /*check_status=*/nullptr, WaitForBackendOptions().set_reset_counters(false), RpcOptions().set_rpc_service(SERVICE_ECHO2)); // Requests for services Echo and Echo1 should have gone to backend 0. EXPECT_EQ(1, backends_[0]->backend_service()->request_count()); EXPECT_EQ(1, backends_[0]->backend_service1()->request_count()); EXPECT_EQ(0, backends_[0]->backend_service2()->request_count()); // Requests for service Echo2 should have gone to backend 1. EXPECT_EQ(0, backends_[1]->backend_service()->request_count()); EXPECT_EQ(0, backends_[1]->backend_service1()->request_count()); EXPECT_EQ(1, backends_[1]->backend_service2()->request_count()); } // Test that we NACK unknown filter types in VirtualHost. TEST_P(LdsRdsTest, RejectsUnknownHttpFilterTypeInVirtualHost) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom(Listener()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("no filter registered for config type " "envoy.config.listener.v3.Listener")); } // Test that we ignore optional unknown filter types in VirtualHost. TEST_P(LdsRdsTest, IgnoresOptionalUnknownHttpFilterTypeInVirtualHost) { CreateAndStartBackends(1); if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config(); ::envoy::config::route::v3::FilterConfig filter_config; filter_config.mutable_config()->PackFrom(Listener()); filter_config.set_is_optional(true); (*per_filter_config)["unknown"].PackFrom(filter_config); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK filters without configs in VirtualHost. TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInVirtualHost) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"]; SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "no filter config specified for filter name unknown")); } // Test that we NACK filters without configs in FilterConfig in VirtualHost. TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInFilterConfigInVirtualHost) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom( ::envoy::config::route::v3::FilterConfig()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "no filter config specified for filter name unknown")); } // Test that we ignore optional filters without configs in VirtualHost. TEST_P(LdsRdsTest, IgnoresOptionalHttpFilterWithoutConfigInVirtualHost) { if (GetParam().use_v2()) return; // Filters supported in v3 only. CreateAndStartBackends(1); RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config(); ::envoy::config::route::v3::FilterConfig filter_config; filter_config.set_is_optional(true); (*per_filter_config)["unknown"].PackFrom(filter_config); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); EdsResourceArgs args({ {"locality0", CreateEndpointsForBackends()}, }); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK unparseable filter types in VirtualHost. TEST_P(LdsRdsTest, RejectsUnparseableHttpFilterTypeInVirtualHost) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0)->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom( envoy::extensions::filters::http::router::v3::Router()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr("router filter does not support config override")); } // Test that we NACK unknown filter types in Route. TEST_P(LdsRdsTest, RejectsUnknownHttpFilterTypeInRoute) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom(Listener()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("no filter registered for config type " "envoy.config.listener.v3.Listener")); } // Test that we ignore optional unknown filter types in Route. TEST_P(LdsRdsTest, IgnoresOptionalUnknownHttpFilterTypeInRoute) { if (GetParam().use_v2()) return; // Filters supported in v3 only. CreateAndStartBackends(1); RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_typed_per_filter_config(); ::envoy::config::route::v3::FilterConfig filter_config; filter_config.mutable_config()->PackFrom(Listener()); filter_config.set_is_optional(true); (*per_filter_config)["unknown"].PackFrom(filter_config); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK filters without configs in Route. TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInRoute) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"]; SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "no filter config specified for filter name unknown")); } // Test that we NACK filters without configs in FilterConfig in Route. TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInFilterConfigInRoute) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom( ::envoy::config::route::v3::FilterConfig()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "no filter config specified for filter name unknown")); } // Test that we ignore optional filters without configs in Route. TEST_P(LdsRdsTest, IgnoresOptionalHttpFilterWithoutConfigInRoute) { if (GetParam().use_v2()) return; // Filters supported in v3 only. CreateAndStartBackends(1); RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_typed_per_filter_config(); ::envoy::config::route::v3::FilterConfig filter_config; filter_config.set_is_optional(true); (*per_filter_config)["unknown"].PackFrom(filter_config); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK unparseable filter types in Route. TEST_P(LdsRdsTest, RejectsUnparseableHttpFilterTypeInRoute) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* per_filter_config = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom( envoy::extensions::filters::http::router::v3::Router()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr("router filter does not support config override")); } // Test that we NACK unknown filter types in ClusterWeight. TEST_P(LdsRdsTest, RejectsUnknownHttpFilterTypeInClusterWeight) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* cluster_weight = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_route() ->mutable_weighted_clusters() ->add_clusters(); cluster_weight->set_name(kDefaultClusterName); cluster_weight->mutable_weight()->set_value(100); auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom(Listener()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr("no filter registered for config type " "envoy.config.listener.v3.Listener")); } // Test that we ignore optional unknown filter types in ClusterWeight. TEST_P(LdsRdsTest, IgnoresOptionalUnknownHttpFilterTypeInClusterWeight) { if (GetParam().use_v2()) return; // Filters supported in v3 only. CreateAndStartBackends(1); RouteConfiguration route_config = default_route_config_; auto* cluster_weight = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_route() ->mutable_weighted_clusters() ->add_clusters(); cluster_weight->set_name(kDefaultClusterName); cluster_weight->mutable_weight()->set_value(100); auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config(); ::envoy::config::route::v3::FilterConfig filter_config; filter_config.mutable_config()->PackFrom(Listener()); filter_config.set_is_optional(true); (*per_filter_config)["unknown"].PackFrom(filter_config); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK filters without configs in ClusterWeight. TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInClusterWeight) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* cluster_weight = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_route() ->mutable_weighted_clusters() ->add_clusters(); cluster_weight->set_name(kDefaultClusterName); cluster_weight->mutable_weight()->set_value(100); auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"]; SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "no filter config specified for filter name unknown")); } // Test that we NACK filters without configs in FilterConfig in ClusterWeight. TEST_P(LdsRdsTest, RejectsHttpFilterWithoutConfigInFilterConfigInClusterWeight) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* cluster_weight = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_route() ->mutable_weighted_clusters() ->add_clusters(); cluster_weight->set_name(kDefaultClusterName); cluster_weight->mutable_weight()->set_value(100); auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom( ::envoy::config::route::v3::FilterConfig()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT(response_state->error_message, ::testing::HasSubstr( "no filter config specified for filter name unknown")); } // Test that we ignore optional filters without configs in ClusterWeight. TEST_P(LdsRdsTest, IgnoresOptionalHttpFilterWithoutConfigInClusterWeight) { if (GetParam().use_v2()) return; // Filters supported in v3 only. CreateAndStartBackends(1); RouteConfiguration route_config = default_route_config_; auto* cluster_weight = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_route() ->mutable_weighted_clusters() ->add_clusters(); cluster_weight->set_name(kDefaultClusterName); cluster_weight->mutable_weight()->set_value(100); auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config(); ::envoy::config::route::v3::FilterConfig filter_config; filter_config.set_is_optional(true); (*per_filter_config)["unknown"].PackFrom(filter_config); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); EdsResourceArgs args({{"locality0", CreateEndpointsForBackends()}}); balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); WaitForAllBackends(DEBUG_LOCATION); auto response_state = RouteConfigurationResponseState(balancer_.get()); ASSERT_TRUE(response_state.has_value()); EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); } // Test that we NACK unparseable filter types in ClusterWeight. TEST_P(LdsRdsTest, RejectsUnparseableHttpFilterTypeInClusterWeight) { if (GetParam().use_v2()) return; // Filters supported in v3 only. RouteConfiguration route_config = default_route_config_; auto* cluster_weight = route_config.mutable_virtual_hosts(0) ->mutable_routes(0) ->mutable_route() ->mutable_weighted_clusters() ->add_clusters(); cluster_weight->set_name(kDefaultClusterName); cluster_weight->mutable_weight()->set_value(100); auto* per_filter_config = cluster_weight->mutable_typed_per_filter_config(); (*per_filter_config)["unknown"].PackFrom( envoy::extensions::filters::http::router::v3::Router()); SetListenerAndRouteConfiguration(balancer_.get(), default_listener_, route_config); const auto response_state = WaitForRdsNack(DEBUG_LOCATION); ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; EXPECT_THAT( response_state->error_message, ::testing::HasSubstr("router filter does not support config override")); } } // namespace } // namespace testing } // namespace grpc int main(int argc, char** argv) { grpc::testing::TestEnvironment env(&argc, argv); ::testing::InitGoogleTest(&argc, argv); // Make the backup poller poll very frequently in order to pick up // updates from all the subchannels's FDs. GPR_GLOBAL_CONFIG_SET(grpc_client_channel_backup_poll_interval_ms, 1); #if TARGET_OS_IPHONE // Workaround Apple CFStream bug gpr_setenv("grpc_cfstream", "0"); #endif grpc_init(); grpc_core::XdsHttpFilterRegistry::RegisterFilter( absl::make_unique( "grpc.testing.client_only_http_filter", /* supported_on_clients = */ true, /* supported_on_servers = */ false, /* is_terminal_filter */ false), {"grpc.testing.client_only_http_filter"}); grpc_core::XdsHttpFilterRegistry::RegisterFilter( absl::make_unique( "grpc.testing.server_only_http_filter", /* supported_on_clients = */ false, /* supported_on_servers = */ true, /* is_terminal_filter */ false), {"grpc.testing.server_only_http_filter"}); grpc_core::XdsHttpFilterRegistry::RegisterFilter( absl::make_unique( "grpc.testing.terminal_http_filter", /* supported_on_clients = */ true, /* supported_on_servers = */ true, /* is_terminal_filter */ true), {"grpc.testing.terminal_http_filter"}); const auto result = RUN_ALL_TESTS(); grpc_shutdown(); return result; }