| // 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 <chrono> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/core/ext/filters/client_channel/backup_poller.h" |
| #include "src/core/lib/config/config_vars.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/xds_end2end_test_lib.h" |
| |
| namespace grpc { |
| namespace testing { |
| namespace { |
| |
| using std::chrono::system_clock; |
| |
| using LdsTest = XdsEnd2endTest; |
| |
| INSTANTIATE_TEST_SUITE_P(XdsTest, LdsTest, ::testing::Values(XdsTestType()), |
| &XdsTestType::Name); |
| |
| // Testing just one example of an invalid resource here. |
| // Unit tests for XdsListenerResourceType have exhaustive tests for all |
| // of the invalid cases. |
| TEST_P(LdsTest, NacksInvalidListener) { |
| 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 we go into TRANSIENT_FAILURE if the Listener is not an API |
| // listener. |
| TEST_P(LdsTest, NotAnApiListener) { |
| Listener listener = default_server_listener_; |
| listener.set_name(kServerName); |
| auto hcm = ServerHcmAccessor().Unpack(listener); |
| auto* rds = hcm.mutable_rds(); |
| rds->set_route_config_name(kDefaultRouteConfigurationName); |
| rds->mutable_config_source()->mutable_self(); |
| ServerHcmAccessor().Pack(hcm, &listener); |
| balancer_->ads_service()->SetLdsResource(listener); |
| // RPCs should fail. |
| CheckRpcSendFailure( |
| DEBUG_LOCATION, StatusCode::UNAVAILABLE, |
| absl::StrCat(kServerName, ": UNAVAILABLE: not an API listener")); |
| // We should have ACKed the LDS resource. |
| const auto deadline = |
| absl::Now() + (absl::Seconds(30) * grpc_test_slowdown_factor()); |
| while (true) { |
| ASSERT_LT(absl::Now(), deadline) << "timed out waiting for LDS ACK"; |
| auto response_state = balancer_->ads_service()->lds_response_state(); |
| if (response_state.has_value()) { |
| EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); |
| break; |
| } |
| absl::SleepFor(absl::Seconds(1) * grpc_test_slowdown_factor()); |
| } |
| } |
| |
| 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) * grpc_test_slowdown_factor()); |
| 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; |
| absl::SleepFor(absl::Seconds(1) * grpc_test_slowdown_factor()); |
| } |
| 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 LdsRdsInteractionTest = XdsEnd2endTest; |
| |
| INSTANTIATE_TEST_SUITE_P( |
| XdsTest, LdsRdsInteractionTest, |
| ::testing::Values(XdsTestType().set_enable_rds_testing()), |
| &XdsTestType::Name); |
| |
| TEST_P(LdsRdsInteractionTest, SwitchFromRdsToInlineRouteConfig) { |
| 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)); |
| WaitForBackend(DEBUG_LOCATION, 0); |
| // RDS should have been ACKed. |
| auto response_state = balancer_->ads_service()->rds_response_state(); |
| ASSERT_TRUE(response_state.has_value()); |
| EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); |
| // Now recreate the LDS resource with an inline route config 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); |
| Listener listener = default_listener_; |
| HttpConnectionManager http_connection_manager = |
| ClientHcmAccessor().Unpack(listener); |
| *http_connection_manager.mutable_route_config() = new_route_config; |
| ClientHcmAccessor().Pack(http_connection_manager, &listener); |
| balancer_->ads_service()->SetLdsResource(listener); |
| // Wait for client to start using backend 1. |
| WaitForBackend(DEBUG_LOCATION, 1); |
| // Send an update to the original RDS resource, which the client |
| // should no longer be subscribed to. We need this RouteConfig to be |
| // different than the original one so that the update does not get |
| // squelched by XdsClient, so we add a second domain to the vhost that |
| // will not actually be used. |
| new_route_config = default_route_config_; |
| new_route_config.mutable_virtual_hosts(0)->add_domains("foo.example.com"); |
| balancer_->ads_service()->SetRdsResource(new_route_config); |
| // Wait for RDS ACK to know that the client saw the change. |
| // TODO(roth): The client does not actually ACK here, it just sends an |
| // unsubscription request, but our fake xDS server is incorrectly treating |
| // that as an ACK. When we have time, fix the behavior of the fake |
| // xDS server, and then change this test to ensure that there is no RDS |
| // ACK within the 30-second timeout period. |
| const auto deadline = |
| absl::Now() + (absl::Seconds(30) * grpc_test_slowdown_factor()); |
| while (true) { |
| ASSERT_LT(absl::Now(), deadline) << "timed out waiting for RDS ACK"; |
| response_state = balancer_->ads_service()->rds_response_state(); |
| if (response_state.has_value()) break; |
| absl::SleepFor(absl::Seconds(1) * grpc_test_slowdown_factor()); |
| } |
| EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); |
| // Make sure RPCs are still going to backend 1. This shows that the |
| // client did not replace its route config with the one from the RDS |
| // resource that it should no longer be using. |
| ResetBackendCounters(); |
| CheckRpcSendOk(DEBUG_LOCATION); |
| EXPECT_EQ(0, backends_[0]->backend_service()->request_count()); |
| EXPECT_EQ(1, backends_[1]->backend_service()->request_count()); |
| } |
| |
| TEST_P(LdsRdsInteractionTest, SwitchFromInlineRouteConfigToRds) { |
| CreateAndStartBackends(2); |
| // Create an LDS resource with an inline RouteConfig pointing to a |
| // different CDS and EDS resource, sending traffic to backend 0. |
| 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); |
| EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); |
| balancer_->ads_service()->SetEdsResource( |
| BuildEdsResource(args, kNewEdsResourceName)); |
| RouteConfiguration route_config = default_route_config_; |
| route_config.mutable_virtual_hosts(0) |
| ->mutable_routes(0) |
| ->mutable_route() |
| ->set_cluster(kNewClusterName); |
| Listener listener = default_listener_; |
| HttpConnectionManager http_connection_manager = |
| ClientHcmAccessor().Unpack(listener); |
| *http_connection_manager.mutable_route_config() = route_config; |
| ClientHcmAccessor().Pack(http_connection_manager, &listener); |
| balancer_->ads_service()->SetLdsResource(listener); |
| // Start the client and make sure traffic goes to backend 0. |
| WaitForBackend(DEBUG_LOCATION, 0); |
| // RDS should not have been ACKed, because the RouteConfig was inlined. |
| ASSERT_FALSE(balancer_->ads_service()->rds_response_state().has_value()); |
| // Change the LDS resource to point to an RDS resource. The LDS resource |
| // configures the fault injection filter with a config that fails all RPCs. |
| // However, the RDS resource has a typed_per_filter_config override that |
| // disables the fault injection filter. The RDS resource points to a |
| // new cluster that sends traffic to backend 1. |
| args = EdsResourceArgs({{"locality0", CreateEndpointsForBackends(1, 2)}}); |
| balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); |
| route_config = default_route_config_; |
| auto* config_map = route_config.mutable_virtual_hosts(0) |
| ->mutable_routes(0) |
| ->mutable_typed_per_filter_config(); |
| (*config_map)["envoy.fault"].PackFrom( |
| envoy::extensions::filters::http::fault::v3::HTTPFault()); |
| envoy::extensions::filters::http::fault::v3::HTTPFault http_fault; |
| auto* abort_percentage = http_fault.mutable_abort()->mutable_percentage(); |
| abort_percentage->set_numerator(100); |
| abort_percentage->set_denominator(abort_percentage->HUNDRED); |
| http_fault.mutable_abort()->set_grpc_status( |
| static_cast<uint32_t>(StatusCode::ABORTED)); |
| listener = default_listener_; |
| http_connection_manager = ClientHcmAccessor().Unpack(listener); |
| *http_connection_manager.add_http_filters() = |
| http_connection_manager.http_filters(0); |
| auto* filter = http_connection_manager.mutable_http_filters(0); |
| filter->set_name("envoy.fault"); |
| filter->mutable_typed_config()->PackFrom(http_fault); |
| ClientHcmAccessor().Pack(http_connection_manager, &listener); |
| SetListenerAndRouteConfiguration(balancer_.get(), std::move(listener), |
| route_config); |
| // Wait for traffic to switch to backend 1. There should be no RPC |
| // failures here; if there are, that indicates that the client started |
| // using the new LDS resource before it saw the new RDS resource. |
| WaitForBackend(DEBUG_LOCATION, 1); |
| } |
| |
| TEST_P(LdsRdsInteractionTest, HcmConfigUpdatedWithoutRdsChange) { |
| CreateAndStartBackends(1); |
| // 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)); |
| WaitForBackend(DEBUG_LOCATION, 0); |
| // LDS should have been ACKed. |
| auto response_state = balancer_->ads_service()->lds_response_state(); |
| ASSERT_TRUE(response_state.has_value()); |
| EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); |
| // Now update the LDS resource to add the fault injection filter with |
| // a config that fails all RPCs. |
| envoy::extensions::filters::http::fault::v3::HTTPFault http_fault; |
| auto* abort_percentage = http_fault.mutable_abort()->mutable_percentage(); |
| abort_percentage->set_numerator(100); |
| abort_percentage->set_denominator(abort_percentage->HUNDRED); |
| http_fault.mutable_abort()->set_grpc_status( |
| static_cast<uint32_t>(StatusCode::ABORTED)); |
| Listener listener = default_listener_; |
| HttpConnectionManager http_connection_manager = |
| ClientHcmAccessor().Unpack(listener); |
| *http_connection_manager.add_http_filters() = |
| http_connection_manager.http_filters(0); |
| auto* filter = http_connection_manager.mutable_http_filters(0); |
| filter->set_name("envoy.fault"); |
| filter->mutable_typed_config()->PackFrom(http_fault); |
| ClientHcmAccessor().Pack(http_connection_manager, &listener); |
| SetListenerAndRouteConfiguration(balancer_.get(), std::move(listener), |
| default_route_config_); |
| // Wait for the LDS update to be ACKed. |
| const auto deadline = |
| absl::Now() + (absl::Seconds(30) * grpc_test_slowdown_factor()); |
| 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; |
| absl::SleepFor(absl::Seconds(1) * grpc_test_slowdown_factor()); |
| } |
| EXPECT_EQ(response_state->state, AdsServiceImpl::ResponseState::ACKED); |
| // Now RPCs should fail with ABORTED status. |
| CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::ABORTED, "Fault injected"); |
| } |
| |
| TEST_P(LdsRdsInteractionTest, LdsUpdateChangesHcmConfigAndRdsResourceName) { |
| 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)); |
| WaitForBackend(DEBUG_LOCATION, 0); |
| // Change the LDS resource to point to an RDS resource. The LDS resource |
| // configures the fault injection filter with a config that fails all RPCs. |
| // However, the RDS resource has a typed_per_filter_config override that |
| // disables the fault injection filter. The RDS resource points to a |
| // new cluster that sends traffic to backend 1. |
| 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 route_config = default_route_config_; |
| route_config.set_name("new_route_config"); |
| route_config.mutable_virtual_hosts(0) |
| ->mutable_routes(0) |
| ->mutable_route() |
| ->set_cluster(kNewClusterName); |
| auto* config_map = route_config.mutable_virtual_hosts(0) |
| ->mutable_routes(0) |
| ->mutable_typed_per_filter_config(); |
| (*config_map)["envoy.fault"].PackFrom( |
| envoy::extensions::filters::http::fault::v3::HTTPFault()); |
| envoy::extensions::filters::http::fault::v3::HTTPFault http_fault; |
| auto* abort_percentage = http_fault.mutable_abort()->mutable_percentage(); |
| abort_percentage->set_numerator(100); |
| abort_percentage->set_denominator(abort_percentage->HUNDRED); |
| http_fault.mutable_abort()->set_grpc_status( |
| static_cast<uint32_t>(StatusCode::ABORTED)); |
| Listener listener = default_listener_; |
| HttpConnectionManager http_connection_manager = |
| ClientHcmAccessor().Unpack(listener); |
| *http_connection_manager.add_http_filters() = |
| http_connection_manager.http_filters(0); |
| auto* filter = http_connection_manager.mutable_http_filters(0); |
| filter->set_name("envoy.fault"); |
| filter->mutable_typed_config()->PackFrom(http_fault); |
| ClientHcmAccessor().Pack(http_connection_manager, &listener); |
| SetListenerAndRouteConfiguration(balancer_.get(), std::move(listener), |
| route_config); |
| // Wait for traffic to switch to backend 1. There should be no RPC |
| // failures here; if there are, that indicates that the client started |
| // using the new LDS resource before it saw the new RDS resource. |
| WaitForBackend(DEBUG_LOCATION, 1); |
| } |
| |
| using LdsRdsTest = XdsEnd2endTest; |
| |
| // Test with and without RDS. |
| INSTANTIATE_TEST_SUITE_P( |
| XdsTest, LdsRdsTest, |
| ::testing::Values(XdsTestType(), XdsTestType().set_enable_rds_testing()), |
| &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 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); |
| } |
| |
| TEST_P(LdsRdsTest, NoMatchingRoute) { |
| RouteConfiguration route_config = default_route_config_; |
| route_config.mutable_virtual_hosts(0) |
| ->mutable_routes(0) |
| ->mutable_match() |
| ->set_prefix("/unknown/method"); |
| SetRouteConfiguration(balancer_.get(), route_config); |
| CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::UNAVAILABLE, |
| "No matching route found in xDS route config"); |
| // 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); |
| } |
| |
| TEST_P(LdsRdsTest, EmptyRouteList) { |
| RouteConfiguration route_config = default_route_config_; |
| route_config.mutable_virtual_hosts(0)->clear_routes(); |
| SetRouteConfiguration(balancer_.get(), route_config); |
| CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::UNAVAILABLE, |
| "No matching route found in xDS route config"); |
| // 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); |
| } |
| |
| // Testing just one example of an invalid resource here. |
| // Unit tests for XdsRouteConfigResourceType have exhaustive tests for all |
| // of the invalid cases. |
| TEST_P(LdsRdsTest, NacksInvalidRouteConfig) { |
| RouteConfiguration route_config = default_route_config_; |
| route_config.mutable_virtual_hosts(0)->mutable_routes(0)->clear_match(); |
| SetRouteConfiguration(balancer_.get(), route_config); |
| const auto response_state = WaitForRdsNack(DEBUG_LOCATION); |
| ASSERT_TRUE(response_state.has_value()) << "timed out waiting for NACK"; |
| EXPECT_EQ( |
| response_state->error_message, |
| absl::StrCat( |
| "xDS response validation errors: [resource index 0: ", |
| GetParam().enable_rds_testing() |
| ? "route_config_name: INVALID_ARGUMENT: " |
| "errors validating RouteConfiguration resource: [" |
| "field:" |
| : "server.example.com: INVALID_ARGUMENT: " |
| "errors validating ApiListener: [" |
| "field:api_listener.api_listener.value[" |
| "envoy.extensions.filters.network.http_connection_manager.v3" |
| ".HttpConnectionManager].route_config.", |
| "virtual_hosts[0].routes[0].match " |
| "error:field not present]]")); |
| } |
| |
| // 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"); |
| } |
| |
| // 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<double>(kWeight75) / 100; |
| const double kWeight25Percent = static_cast<double>(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); |
| 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<double>(weight_75_request_count) / kNumEcho1Rpcs, |
| ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); |
| EXPECT_THAT(static_cast<double>(weight_25_request_count) / kNumEcho1Rpcs, |
| ::testing::DoubleNear(kWeight25Percent, kErrorTolerance)); |
| } |
| |
| TEST_P(LdsRdsTest, XdsRoutingWeightedClusterNoIntegerOverflow) { |
| 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 kNumEchoRpcs = 10; // RPCs that will go to a fixed backend. |
| const uint32_t kWeight1 = std::numeric_limits<uint32_t>::max() / 3; |
| const uint32_t kWeight2 = std::numeric_limits<uint32_t>::max() - kWeight1; |
| const double kErrorTolerance = 0.05; |
| const double kWeight1Percent = |
| static_cast<double>(kWeight1) / std::numeric_limits<uint32_t>::max(); |
| const double kWeight2Percent = |
| static_cast<double>(kWeight2) / std::numeric_limits<uint32_t>::max(); |
| const size_t kNumEcho1Rpcs = |
| ComputeIdealNumRpcs(kWeight2Percent, 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(kWeight1); |
| auto* weighted_cluster2 = |
| route1->mutable_route()->mutable_weighted_clusters()->add_clusters(); |
| weighted_cluster2->set_name(kNewCluster2Name); |
| weighted_cluster2->mutable_weight()->set_value(kWeight2); |
| 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 weight1_request_count = |
| backends_[1]->backend_service1()->request_count(); |
| EXPECT_EQ(0, backends_[2]->backend_service()->request_count()); |
| const int weight2_request_count = |
| backends_[2]->backend_service1()->request_count(); |
| gpr_log(GPR_INFO, "target1 received %d rpcs and target2 received %d rpcs", |
| weight1_request_count, weight2_request_count); |
| EXPECT_THAT(static_cast<double>(weight1_request_count) / kNumEcho1Rpcs, |
| ::testing::DoubleNear(kWeight1Percent, kErrorTolerance)); |
| EXPECT_THAT(static_cast<double>(weight2_request_count) / kNumEcho1Rpcs, |
| ::testing::DoubleNear(kWeight2Percent, 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<double>(kWeight75) / 100; |
| const double kWeight25Percent = static_cast<double>(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); |
| 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<double>(weight_75_request_count) / kNumEchoRpcs, |
| ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); |
| EXPECT_THAT(static_cast<double>(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<double>(kWeight75) / 100; |
| const double kWeight25Percent = static_cast<double>(kWeight25) / 100; |
| const double kWeight50Percent = static_cast<double>(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); |
| 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<double>(weight_75_request_count) / kNumEcho1Rpcs7525, |
| ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); |
| EXPECT_THAT(static_cast<double>(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<double>(weight_50_request_count_1) / kNumEcho1Rpcs5050, |
| ::testing::DoubleNear(kWeight50Percent, kErrorTolerance)); |
| EXPECT_THAT( |
| static_cast<double>(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<double>(kWeight75) / 100; |
| const double kWeight25Percent = static_cast<double>(kWeight25) / 100; |
| const double kWeight50Percent = static_cast<double>(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); |
| 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<double>(weight_75_request_count) / kNumEcho1Rpcs7525, |
| ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); |
| EXPECT_THAT(static_cast<double>(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<double>(weight_50_request_count_1) / kNumEcho1Rpcs5050, |
| ::testing::DoubleNear(kWeight50Percent, kErrorTolerance)); |
| EXPECT_THAT( |
| static_cast<double>(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<double>(weight_75_request_count) / kNumEcho1Rpcs7525, |
| ::testing::DoubleNear(kWeight75Percent, kErrorTolerance)); |
| EXPECT_THAT(static_cast<double>(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. |
| CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::UNAVAILABLE, |
| MakeConnectionFailureRegex( |
| "connections to all backends failing; last error: ")); |
| // 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(MakeConnectionFailureRegex( |
| "connections to all backends failing; last error: "))); |
| } |
| }, |
| 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<std::chrono::nanoseconds>(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<std::chrono::nanoseconds>( |
| 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<std::chrono::nanoseconds>(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<std::chrono::nanoseconds>(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, 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<std::pair<std::string, std::string>> 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<std::pair<std::string, std::string>> 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<double>(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<double>(default_backend_count) / kNumRpcs, |
| ::testing::DoubleNear(1 - kRouteMatchPercent, kErrorTolerance)); |
| EXPECT_THAT(static_cast<double>(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<std::pair<std::string, std::string>> 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()); |
| } |
| |
| } // 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. |
| grpc_core::ConfigVars::Overrides overrides; |
| overrides.client_channel_backup_poll_interval_ms = 1; |
| grpc_core::ConfigVars::SetOverrides(overrides); |
| #if TARGET_OS_IPHONE |
| // Workaround Apple CFStream bug |
| grpc_core::SetEnv("grpc_cfstream", "0"); |
| #endif |
| grpc_init(); |
| const auto result = RUN_ALL_TESTS(); |
| grpc_shutdown(); |
| return result; |
| } |