mirror of https://github.com/grpc/grpc.git
The C based gRPC (C++, Python, Ruby, Objective-C, PHP, C#)
https://grpc.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
730 lines
30 KiB
730 lines
30 KiB
// 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 <memory> |
|
#include <string> |
|
#include <vector> |
|
|
|
#include <gmock/gmock.h> |
|
#include <gtest/gtest.h> |
|
|
|
#include "absl/memory/memory.h" |
|
#include "absl/strings/str_cat.h" |
|
|
|
#include <grpcpp/create_channel.h> |
|
#include <grpcpp/security/credentials.h> |
|
|
|
#include "src/core/ext/filters/client_channel/backup_poller.h" |
|
#include "src/core/lib/config/config_vars.h" |
|
#include "src/cpp/client/secure_credentials.h" |
|
#include "src/proto/grpc/testing/xds/v3/cluster.grpc.pb.h" |
|
#include "src/proto/grpc/testing/xds/v3/endpoint.grpc.pb.h" |
|
#include "src/proto/grpc/testing/xds/v3/http_connection_manager.grpc.pb.h" |
|
#include "src/proto/grpc/testing/xds/v3/listener.grpc.pb.h" |
|
#include "src/proto/grpc/testing/xds/v3/route.grpc.pb.h" |
|
#include "test/core/util/resolve_localhost_ip46.h" |
|
#include "test/core/util/test_config.h" |
|
#include "test/cpp/end2end/xds/xds_end2end_test_lib.h" |
|
|
|
#ifndef DISABLED_XDS_PROTO_IN_CC |
|
|
|
#include "src/cpp/server/csds/csds.h" |
|
#include "src/proto/grpc/testing/xds/v3/csds.grpc.pb.h" |
|
|
|
namespace grpc { |
|
namespace testing { |
|
namespace { |
|
|
|
using ::envoy::admin::v3::ClientResourceStatus; |
|
using ::envoy::config::cluster::v3::Cluster; |
|
using ::envoy::config::endpoint::v3::ClusterLoadAssignment; |
|
using ::envoy::config::listener::v3::Listener; |
|
using ::envoy::config::route::v3::RouteConfiguration; |
|
using ::envoy::extensions::filters::network::http_connection_manager::v3:: |
|
HttpConnectionManager; |
|
|
|
MATCHER_P4(EqNode, id, user_agent_name, user_agent_version, client_features, |
|
"equals Node") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(id, arg.id(), result_listener); |
|
ok &= ::testing::ExplainMatchResult(user_agent_name, arg.user_agent_name(), |
|
result_listener); |
|
ok &= ::testing::ExplainMatchResult( |
|
user_agent_version, arg.user_agent_version(), result_listener); |
|
ok &= ::testing::ExplainMatchResult(client_features, arg.client_features(), |
|
result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P6(EqGenericXdsConfig, type_url, name, version_info, xds_config, |
|
client_status, error_state, "equals GenericXdsConfig") { |
|
bool ok = true; |
|
ok &= |
|
::testing::ExplainMatchResult(type_url, arg.type_url(), result_listener); |
|
ok &= ::testing::ExplainMatchResult(name, arg.name(), result_listener); |
|
ok &= ::testing::ExplainMatchResult(version_info, arg.version_info(), |
|
result_listener); |
|
ok &= ::testing::ExplainMatchResult(xds_config, arg.xds_config(), |
|
result_listener); |
|
ok &= ::testing::ExplainMatchResult(client_status, arg.client_status(), |
|
result_listener); |
|
ok &= ::testing::ExplainMatchResult(error_state, arg.error_state(), |
|
result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P2(EqListener, name, api_listener, "equals Listener") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(name, arg.name(), result_listener); |
|
ok &= ::testing::ExplainMatchResult( |
|
api_listener, arg.api_listener().api_listener(), result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P(EqHttpConnectionManagerNotRds, route_config, |
|
"equals HttpConnectionManager") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(route_config, arg.route_config(), |
|
result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P(EqRouteConfigurationName, name, "equals RouteConfiguration") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(name, arg.name(), result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P2(EqRouteConfiguration, name, cluster_name, |
|
"equals RouteConfiguration") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(name, arg.name(), result_listener); |
|
ok &= ::testing::ExplainMatchResult( |
|
::testing::ElementsAre(::testing::Property( |
|
&envoy::config::route::v3::VirtualHost::routes, |
|
::testing::ElementsAre(::testing::Property( |
|
&envoy::config::route::v3::Route::route, |
|
::testing::Property( |
|
&envoy::config::route::v3::RouteAction::cluster, |
|
cluster_name))))), |
|
arg.virtual_hosts(), result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P(EqCluster, name, "equals Cluster") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(name, arg.name(), result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P(EqEndpoint, port, "equals Endpoint") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult( |
|
port, arg.address().socket_address().port_value(), result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P2(EqLocalityLbEndpoints, port, weight, "equals LocalityLbEndpoints") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult( |
|
::testing::ElementsAre(::testing::Property( |
|
&envoy::config::endpoint::v3::LbEndpoint::endpoint, |
|
EqEndpoint(port))), |
|
arg.lb_endpoints(), result_listener); |
|
ok &= ::testing::ExplainMatchResult( |
|
weight, arg.load_balancing_weight().value(), result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P(EqClusterLoadAssignmentName, cluster_name, |
|
"equals ClusterLoadAssignment") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(cluster_name, arg.cluster_name(), |
|
result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P3(EqClusterLoadAssignment, cluster_name, port, weight, |
|
"equals ClusterLoadAssignment") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(cluster_name, arg.cluster_name(), |
|
result_listener); |
|
ok &= ::testing::ExplainMatchResult( |
|
::testing::ElementsAre(EqLocalityLbEndpoints(port, weight)), |
|
arg.endpoints(), result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P2(EqUpdateFailureState, details, version_info, |
|
"equals UpdateFailureState") { |
|
bool ok = true; |
|
ok &= ::testing::ExplainMatchResult(details, arg.details(), result_listener); |
|
ok &= ::testing::ExplainMatchResult(version_info, arg.version_info(), |
|
result_listener); |
|
return ok; |
|
} |
|
|
|
MATCHER_P(UnpackListener, matcher, "is a Listener") { |
|
Listener config; |
|
if (!::testing::ExplainMatchResult(true, arg.UnpackTo(&config), |
|
result_listener)) { |
|
return false; |
|
} |
|
return ::testing::ExplainMatchResult(matcher, config, result_listener); |
|
} |
|
|
|
MATCHER_P(UnpackRouteConfiguration, matcher, "is a RouteConfiguration") { |
|
RouteConfiguration config; |
|
if (!::testing::ExplainMatchResult(true, arg.UnpackTo(&config), |
|
result_listener)) { |
|
return false; |
|
} |
|
return ::testing::ExplainMatchResult(matcher, config, result_listener); |
|
} |
|
|
|
MATCHER_P(UnpackHttpConnectionManager, matcher, "is a HttpConnectionManager") { |
|
HttpConnectionManager config; |
|
if (!::testing::ExplainMatchResult(true, arg.UnpackTo(&config), |
|
result_listener)) { |
|
return false; |
|
} |
|
return ::testing::ExplainMatchResult(matcher, config, result_listener); |
|
} |
|
|
|
MATCHER_P(UnpackCluster, matcher, "is a Cluster") { |
|
Cluster config; |
|
if (!::testing::ExplainMatchResult(true, arg.UnpackTo(&config), |
|
result_listener)) { |
|
return false; |
|
} |
|
return ::testing::ExplainMatchResult(matcher, config, result_listener); |
|
} |
|
|
|
MATCHER_P(UnpackClusterLoadAssignment, matcher, "is a ClusterLoadAssignment") { |
|
ClusterLoadAssignment config; |
|
if (!::testing::ExplainMatchResult(true, arg.UnpackTo(&config), |
|
result_listener)) { |
|
return false; |
|
} |
|
return ::testing::ExplainMatchResult(matcher, config, result_listener); |
|
} |
|
|
|
MATCHER(IsRdsEnabledHCM, "is a RDS enabled HttpConnectionManager") { |
|
return ::testing::ExplainMatchResult( |
|
UnpackHttpConnectionManager( |
|
::testing::Property(&HttpConnectionManager::has_rds, true)), |
|
arg, result_listener); |
|
} |
|
|
|
MATCHER_P2(EqNoRdsHCM, route_configuration_name, cluster_name, |
|
"equals RDS disabled HttpConnectionManager") { |
|
return ::testing::ExplainMatchResult( |
|
UnpackHttpConnectionManager(EqHttpConnectionManagerNotRds( |
|
EqRouteConfiguration(route_configuration_name, cluster_name))), |
|
arg, result_listener); |
|
} |
|
|
|
class ClientStatusDiscoveryServiceTest : public XdsEnd2endTest { |
|
public: |
|
ClientStatusDiscoveryServiceTest() { |
|
admin_server_thread_ = std::make_unique<AdminServerThread>(this); |
|
admin_server_thread_->Start(); |
|
std::string admin_server_address = |
|
grpc_core::LocalIpAndPort(admin_server_thread_->port()); |
|
admin_channel_ = grpc::CreateChannel( |
|
admin_server_address, |
|
std::make_shared<SecureChannelCredentials>( |
|
grpc_fake_transport_security_credentials_create())); |
|
csds_stub_ = |
|
envoy::service::status::v3::ClientStatusDiscoveryService::NewStub( |
|
admin_channel_); |
|
if (GetParam().use_csds_streaming()) { |
|
stream_ = csds_stub_->StreamClientStatus(&stream_context_); |
|
} |
|
} |
|
|
|
~ClientStatusDiscoveryServiceTest() override { |
|
if (stream_ != nullptr) { |
|
EXPECT_TRUE(stream_->WritesDone()); |
|
Status status = stream_->Finish(); |
|
EXPECT_TRUE(status.ok()) << "code=" << status.error_code() |
|
<< " message=" << status.error_message(); |
|
} |
|
admin_server_thread_->Shutdown(); |
|
} |
|
|
|
envoy::service::status::v3::ClientStatusResponse FetchCsdsResponse() { |
|
envoy::service::status::v3::ClientStatusResponse response; |
|
if (!GetParam().use_csds_streaming()) { |
|
// Fetch through unary pulls |
|
ClientContext context; |
|
Status status = csds_stub_->FetchClientStatus( |
|
&context, envoy::service::status::v3::ClientStatusRequest(), |
|
&response); |
|
EXPECT_TRUE(status.ok()) << "code=" << status.error_code() |
|
<< " message=" << status.error_message(); |
|
} else { |
|
// Fetch through streaming pulls |
|
EXPECT_TRUE( |
|
stream_->Write(envoy::service::status::v3::ClientStatusRequest())); |
|
EXPECT_TRUE(stream_->Read(&response)); |
|
} |
|
return response; |
|
} |
|
|
|
private: |
|
// Server thread for CSDS server. |
|
class AdminServerThread : public ServerThread { |
|
public: |
|
explicit AdminServerThread(XdsEnd2endTest* test_obj) |
|
: ServerThread(test_obj) {} |
|
|
|
private: |
|
const char* Type() override { return "Admin"; } |
|
|
|
void RegisterAllServices(ServerBuilder* builder) override { |
|
builder->RegisterService(&csds_service_); |
|
} |
|
void StartAllServices() override {} |
|
void ShutdownAllServices() override {} |
|
|
|
grpc::xds::experimental::ClientStatusDiscoveryService csds_service_; |
|
}; |
|
|
|
std::unique_ptr<AdminServerThread> admin_server_thread_; |
|
std::shared_ptr<Channel> admin_channel_; |
|
std::unique_ptr< |
|
envoy::service::status::v3::ClientStatusDiscoveryService::Stub> |
|
csds_stub_; |
|
ClientContext stream_context_; |
|
std::unique_ptr< |
|
ClientReaderWriter<envoy::service::status::v3::ClientStatusRequest, |
|
envoy::service::status::v3::ClientStatusResponse>> |
|
stream_; |
|
}; |
|
|
|
// Run CSDS tests with RDS enabled and disabled. |
|
// These need to run with the bootstrap from an env var instead of from |
|
// a channel arg, since there needs to be a global XdsClient instance. |
|
INSTANTIATE_TEST_SUITE_P( |
|
XdsTest, ClientStatusDiscoveryServiceTest, |
|
::testing::Values( |
|
XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), |
|
XdsTestType() |
|
.set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) |
|
.set_enable_rds_testing(), |
|
XdsTestType() |
|
.set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) |
|
.set_use_csds_streaming(), |
|
XdsTestType() |
|
.set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) |
|
.set_enable_rds_testing() |
|
.set_use_csds_streaming()), |
|
&XdsTestType::Name); |
|
|
|
TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpVanilla) { |
|
CreateAndStartBackends(1); |
|
const size_t kNumRpcs = 5; |
|
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); |
|
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); |
|
// Send several RPCs to ensure the xDS setup works |
|
CheckRpcSendOk(DEBUG_LOCATION, kNumRpcs); |
|
// Fetches the client config |
|
auto csds_response = FetchCsdsResponse(); |
|
gpr_log(GPR_INFO, "xDS config dump: %s", csds_response.DebugString().c_str()); |
|
EXPECT_EQ(1, csds_response.config_size()); |
|
const auto& client_config = csds_response.config(0); |
|
// Validate the Node information |
|
EXPECT_THAT(client_config.node(), |
|
EqNode("xds_end2end_test", ::testing::HasSubstr("C-core"), |
|
::testing::HasSubstr(grpc_version_string()), |
|
::testing::ElementsAre( |
|
"envoy.lb.does_not_support_overprovisioning"))); |
|
// Listener matcher depends on whether RDS is enabled. |
|
::testing::Matcher<google::protobuf::Any> api_listener_matcher; |
|
if (GetParam().enable_rds_testing()) { |
|
api_listener_matcher = IsRdsEnabledHCM(); |
|
} else { |
|
api_listener_matcher = |
|
EqNoRdsHCM(kDefaultRouteConfigurationName, kDefaultClusterName); |
|
} |
|
// Construct list of all matchers. |
|
std::vector<::testing::Matcher< |
|
envoy::service::status::v3::ClientConfig_GenericXdsConfig>> |
|
matchers = { |
|
// Listener |
|
EqGenericXdsConfig( |
|
kLdsTypeUrl, kServerName, "1", |
|
UnpackListener(EqListener(kServerName, api_listener_matcher)), |
|
ClientResourceStatus::ACKED, ::testing::_), |
|
// Cluster |
|
EqGenericXdsConfig(kCdsTypeUrl, kDefaultClusterName, "1", |
|
UnpackCluster(EqCluster(kDefaultClusterName)), |
|
ClientResourceStatus::ACKED, ::testing::_), |
|
// ClusterLoadAssignment |
|
EqGenericXdsConfig( |
|
kEdsTypeUrl, kDefaultEdsServiceName, "1", |
|
UnpackClusterLoadAssignment(EqClusterLoadAssignment( |
|
kDefaultEdsServiceName, backends_[0]->port(), |
|
kDefaultLocalityWeight)), |
|
ClientResourceStatus::ACKED, ::testing::_), |
|
}; |
|
// If RDS is enabled, add matcher for RDS resource. |
|
if (GetParam().enable_rds_testing()) { |
|
matchers.push_back(EqGenericXdsConfig( |
|
kRdsTypeUrl, kDefaultRouteConfigurationName, "1", |
|
UnpackRouteConfiguration(EqRouteConfiguration( |
|
kDefaultRouteConfigurationName, kDefaultClusterName)), |
|
ClientResourceStatus::ACKED, ::testing::_)); |
|
} |
|
// Validate the dumped xDS configs |
|
EXPECT_THAT(client_config.generic_xds_configs(), |
|
::testing::UnorderedElementsAreArray(matchers)) |
|
<< "Actual: " << client_config.DebugString(); |
|
} |
|
|
|
TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpEmpty) { |
|
// The CSDS service should not fail if XdsClient is not initialized or there |
|
// is no working xDS configs. |
|
FetchCsdsResponse(); |
|
} |
|
|
|
TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpListenerError) { |
|
CreateAndStartBackends(1); |
|
int kFetchConfigRetries = 3; |
|
int kFetchIntervalMilliseconds = 200; |
|
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); |
|
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); |
|
// Ensure the xDS resolver has working configs. |
|
CheckRpcSendOk(DEBUG_LOCATION); |
|
// Bad Listener should be rejected. |
|
Listener listener; |
|
listener.set_name(kServerName); |
|
balancer_->ads_service()->SetLdsResource(listener); |
|
// The old xDS configs should still be effective. |
|
CheckRpcSendOk(DEBUG_LOCATION); |
|
::testing::Matcher<google::protobuf::Any> api_listener_matcher; |
|
if (GetParam().enable_rds_testing()) { |
|
api_listener_matcher = IsRdsEnabledHCM(); |
|
} else { |
|
api_listener_matcher = |
|
EqNoRdsHCM(kDefaultRouteConfigurationName, kDefaultClusterName); |
|
} |
|
for (int i = 0; i < kFetchConfigRetries; ++i) { |
|
auto csds_response = FetchCsdsResponse(); |
|
// Check if error state is propagated |
|
bool ok = ::testing::Value( |
|
csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kLdsTypeUrl, kServerName, "1", |
|
UnpackListener(EqListener(kServerName, api_listener_matcher)), |
|
ClientResourceStatus::NACKED, |
|
EqUpdateFailureState( |
|
::testing::HasSubstr( |
|
"Listener has neither address nor ApiListener"), |
|
"2")))); |
|
if (ok) return; // TEST PASSED! |
|
gpr_sleep_until( |
|
grpc_timeout_milliseconds_to_deadline(kFetchIntervalMilliseconds)); |
|
} |
|
FAIL() << "error_state not seen in CSDS responses"; |
|
} |
|
|
|
TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpRouteError) { |
|
CreateAndStartBackends(1); |
|
int kFetchConfigRetries = 3; |
|
int kFetchIntervalMilliseconds = 200; |
|
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); |
|
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); |
|
// Ensure the xDS resolver has working configs. |
|
CheckRpcSendOk(DEBUG_LOCATION); |
|
// Bad route config will be rejected. |
|
RouteConfiguration route_config; |
|
route_config.set_name(kDefaultRouteConfigurationName); |
|
route_config.add_virtual_hosts(); |
|
SetRouteConfiguration(balancer_.get(), route_config); |
|
// The old xDS configs should still be effective. |
|
CheckRpcSendOk(DEBUG_LOCATION); |
|
for (int i = 0; i < kFetchConfigRetries; ++i) { |
|
auto csds_response = FetchCsdsResponse(); |
|
bool ok = false; |
|
if (GetParam().enable_rds_testing()) { |
|
ok = ::testing::Value( |
|
csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kRdsTypeUrl, kDefaultRouteConfigurationName, "1", |
|
UnpackRouteConfiguration(EqRouteConfiguration( |
|
kDefaultRouteConfigurationName, kDefaultClusterName)), |
|
ClientResourceStatus::NACKED, |
|
EqUpdateFailureState( |
|
::testing::HasSubstr( |
|
"field:virtual_hosts[0].domains error:must be non-empty"), |
|
"2")))); |
|
} else { |
|
ok = ::testing::Value( |
|
csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kLdsTypeUrl, kServerName, "1", |
|
UnpackListener(EqListener( |
|
kServerName, EqNoRdsHCM(kDefaultRouteConfigurationName, |
|
kDefaultClusterName))), |
|
ClientResourceStatus::NACKED, |
|
EqUpdateFailureState( |
|
::testing::HasSubstr( |
|
"field:api_listener.api_listener.value[envoy.extensions" |
|
".filters.network.http_connection_manager.v3" |
|
".HttpConnectionManager].route_config.virtual_hosts[0]" |
|
".domains error:must be non-empty"), |
|
"2")))); |
|
} |
|
if (ok) return; // TEST PASSED! |
|
gpr_sleep_until( |
|
grpc_timeout_milliseconds_to_deadline(kFetchIntervalMilliseconds)); |
|
} |
|
FAIL() << "error_state not seen in CSDS responses"; |
|
} |
|
|
|
TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpClusterError) { |
|
CreateAndStartBackends(1); |
|
int kFetchConfigRetries = 3; |
|
int kFetchIntervalMilliseconds = 200; |
|
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); |
|
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); |
|
// Ensure the xDS resolver has working configs. |
|
CheckRpcSendOk(DEBUG_LOCATION); |
|
// Listener without any route, will be rejected. |
|
Cluster cluster; |
|
cluster.set_name(kDefaultClusterName); |
|
balancer_->ads_service()->SetCdsResource(cluster); |
|
// The old xDS configs should still be effective. |
|
CheckRpcSendOk(DEBUG_LOCATION); |
|
for (int i = 0; i < kFetchConfigRetries; ++i) { |
|
auto csds_response = FetchCsdsResponse(); |
|
// Check if error state is propagated |
|
bool ok = ::testing::Value( |
|
csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kCdsTypeUrl, kDefaultClusterName, "1", |
|
UnpackCluster(EqCluster(kDefaultClusterName)), |
|
ClientResourceStatus::NACKED, |
|
EqUpdateFailureState(::testing::HasSubstr("unknown discovery type"), |
|
"2")))); |
|
if (ok) return; // TEST PASSED! |
|
gpr_sleep_until( |
|
grpc_timeout_milliseconds_to_deadline(kFetchIntervalMilliseconds)); |
|
} |
|
FAIL() << "error_state not seen in CSDS responses"; |
|
} |
|
|
|
TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpEndpointError) { |
|
CreateAndStartBackends(1); |
|
int kFetchConfigRetries = 3; |
|
int kFetchIntervalMilliseconds = 200; |
|
EdsResourceArgs args({{"locality0", CreateEndpointsForBackends(0, 1)}}); |
|
balancer_->ads_service()->SetEdsResource(BuildEdsResource(args)); |
|
// Ensure the xDS resolver has working configs. |
|
CheckRpcSendOk(DEBUG_LOCATION); |
|
// Bad endpoint config will be rejected. |
|
ClusterLoadAssignment cluster_load_assignment; |
|
cluster_load_assignment.set_cluster_name(kDefaultEdsServiceName); |
|
auto* endpoints = cluster_load_assignment.add_endpoints(); |
|
endpoints->mutable_load_balancing_weight()->set_value(1); |
|
auto* endpoint = endpoints->add_lb_endpoints()->mutable_endpoint(); |
|
endpoint->mutable_address()->mutable_socket_address()->set_port_value(1 << 1); |
|
balancer_->ads_service()->SetEdsResource(cluster_load_assignment); |
|
// The old xDS configs should still be effective. |
|
CheckRpcSendOk(DEBUG_LOCATION); |
|
for (int i = 0; i < kFetchConfigRetries; ++i) { |
|
auto csds_response = FetchCsdsResponse(); |
|
// Check if error state is propagated |
|
bool ok = ::testing::Value( |
|
csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kEdsTypeUrl, kDefaultEdsServiceName, "1", |
|
UnpackClusterLoadAssignment(EqClusterLoadAssignment( |
|
kDefaultEdsServiceName, backends_[0]->port(), |
|
kDefaultLocalityWeight)), |
|
ClientResourceStatus::NACKED, |
|
EqUpdateFailureState( |
|
::testing::HasSubstr( |
|
"errors parsing EDS resource: [" |
|
"field:endpoints[0].locality error:field not present]"), |
|
"2")))); |
|
if (ok) return; // TEST PASSED! |
|
gpr_sleep_until( |
|
grpc_timeout_milliseconds_to_deadline(kFetchIntervalMilliseconds)); |
|
} |
|
FAIL() << "error_state not seen in CSDS responses"; |
|
} |
|
|
|
TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpListenerRequested) { |
|
int kTimeoutMillisecond = 1000; |
|
balancer_->ads_service()->UnsetResource(kLdsTypeUrl, kServerName); |
|
CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, |
|
"Deadline Exceeded", |
|
RpcOptions().set_timeout_ms(kTimeoutMillisecond)); |
|
auto csds_response = FetchCsdsResponse(); |
|
EXPECT_THAT(csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kLdsTypeUrl, kServerName, ::testing::_, ::testing::_, |
|
ClientResourceStatus::REQUESTED, ::testing::_))); |
|
} |
|
|
|
TEST_P(ClientStatusDiscoveryServiceTest, XdsConfigDumpClusterRequested) { |
|
int kTimeoutMillisecond = 1000; |
|
std::string kClusterName1 = "cluster-1"; |
|
std::string kClusterName2 = "cluster-2"; |
|
// Create a route config requesting two non-existing clusters |
|
RouteConfiguration route_config; |
|
route_config.set_name(kDefaultRouteConfigurationName); |
|
auto* vh = route_config.add_virtual_hosts(); |
|
// The VirtualHost must match the domain name, otherwise will cause resolver |
|
// transient failure. |
|
vh->add_domains("*"); |
|
auto* routes1 = vh->add_routes(); |
|
routes1->mutable_match()->set_prefix(""); |
|
routes1->mutable_route()->set_cluster(kClusterName1); |
|
auto* routes2 = vh->add_routes(); |
|
routes2->mutable_match()->set_prefix(""); |
|
routes2->mutable_route()->set_cluster(kClusterName2); |
|
SetRouteConfiguration(balancer_.get(), route_config); |
|
// Try to get the configs plumb through |
|
CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::DEADLINE_EXCEEDED, |
|
"Deadline Exceeded", |
|
RpcOptions().set_timeout_ms(kTimeoutMillisecond)); |
|
auto csds_response = FetchCsdsResponse(); |
|
EXPECT_THAT(csds_response.config(0).generic_xds_configs(), |
|
::testing::AllOf( |
|
::testing::Contains(EqGenericXdsConfig( |
|
kCdsTypeUrl, kClusterName1, ::testing::_, ::testing::_, |
|
ClientResourceStatus::REQUESTED, ::testing::_)), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kCdsTypeUrl, kClusterName2, ::testing::_, ::testing::_, |
|
ClientResourceStatus::REQUESTED, ::testing::_)))); |
|
} |
|
|
|
class CsdsShortAdsTimeoutTest : public ClientStatusDiscoveryServiceTest { |
|
protected: |
|
void SetUp() override { |
|
// Shorten the ADS subscription timeout to speed up the test run. |
|
InitClient(BootstrapBuilder(), /*lb_expected_authority=*/"", |
|
/*xds_resource_does_not_exist_timeout_ms=*/2000); |
|
} |
|
}; |
|
|
|
// Run CSDS tests with RDS enabled and disabled. |
|
// These need to run with the bootstrap from an env var instead of from |
|
// a channel arg, since there needs to be a global XdsClient instance. |
|
INSTANTIATE_TEST_SUITE_P( |
|
XdsTest, CsdsShortAdsTimeoutTest, |
|
::testing::Values( |
|
XdsTestType().set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar), |
|
XdsTestType() |
|
.set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) |
|
.set_enable_rds_testing(), |
|
XdsTestType() |
|
.set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) |
|
.set_use_csds_streaming(), |
|
XdsTestType() |
|
.set_bootstrap_source(XdsTestType::kBootstrapFromEnvVar) |
|
.set_enable_rds_testing() |
|
.set_use_csds_streaming()), |
|
&XdsTestType::Name); |
|
|
|
TEST_P(CsdsShortAdsTimeoutTest, XdsConfigDumpListenerDoesNotExist) { |
|
int kTimeoutMillisecond = 1000000; // 1000s wait for the transient failure. |
|
balancer_->ads_service()->UnsetResource(kLdsTypeUrl, kServerName); |
|
CheckRpcSendFailure(DEBUG_LOCATION, StatusCode::UNAVAILABLE, |
|
absl::StrCat("empty address list: ", kServerName, |
|
": xDS listener resource does not exist"), |
|
RpcOptions().set_timeout_ms(kTimeoutMillisecond)); |
|
auto csds_response = FetchCsdsResponse(); |
|
EXPECT_THAT(csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kLdsTypeUrl, kServerName, ::testing::_, ::testing::_, |
|
ClientResourceStatus::DOES_NOT_EXIST, ::testing::_))); |
|
} |
|
|
|
TEST_P(CsdsShortAdsTimeoutTest, XdsConfigDumpRouteConfigDoesNotExist) { |
|
if (!GetParam().enable_rds_testing()) return; |
|
int kTimeoutMillisecond = 1000000; // 1000s wait for the transient failure. |
|
balancer_->ads_service()->UnsetResource(kRdsTypeUrl, |
|
kDefaultRouteConfigurationName); |
|
CheckRpcSendFailure( |
|
DEBUG_LOCATION, StatusCode::UNAVAILABLE, |
|
absl::StrCat("empty address list: ", kDefaultRouteConfigurationName, |
|
": xDS route configuration resource does not exist"), |
|
RpcOptions().set_timeout_ms(kTimeoutMillisecond)); |
|
auto csds_response = FetchCsdsResponse(); |
|
EXPECT_THAT( |
|
csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kRdsTypeUrl, kDefaultRouteConfigurationName, ::testing::_, |
|
::testing::_, ClientResourceStatus::DOES_NOT_EXIST, ::testing::_))); |
|
} |
|
|
|
TEST_P(CsdsShortAdsTimeoutTest, XdsConfigDumpClusterDoesNotExist) { |
|
int kTimeoutMillisecond = 1000000; // 1000s wait for the transient failure. |
|
balancer_->ads_service()->UnsetResource(kCdsTypeUrl, kDefaultClusterName); |
|
CheckRpcSendFailure( |
|
DEBUG_LOCATION, StatusCode::UNAVAILABLE, |
|
absl::StrCat("CDS resource \"", kDefaultClusterName, "\" does not exist"), |
|
RpcOptions().set_timeout_ms(kTimeoutMillisecond)); |
|
auto csds_response = FetchCsdsResponse(); |
|
EXPECT_THAT(csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kCdsTypeUrl, kDefaultClusterName, ::testing::_, ::testing::_, |
|
ClientResourceStatus::DOES_NOT_EXIST, ::testing::_))); |
|
} |
|
|
|
TEST_P(CsdsShortAdsTimeoutTest, XdsConfigDumpEndpointDoesNotExist) { |
|
int kTimeoutMillisecond = 1000000; // 1000s wait for the transient failure. |
|
balancer_->ads_service()->UnsetResource(kEdsTypeUrl, kDefaultEdsServiceName); |
|
CheckRpcSendFailure( |
|
DEBUG_LOCATION, StatusCode::UNAVAILABLE, |
|
"no children in weighted_target policy: EDS resource eds_service_name " |
|
"does not exist", |
|
RpcOptions().set_timeout_ms(kTimeoutMillisecond)); |
|
auto csds_response = FetchCsdsResponse(); |
|
EXPECT_THAT( |
|
csds_response.config(0).generic_xds_configs(), |
|
::testing::Contains(EqGenericXdsConfig( |
|
kEdsTypeUrl, kDefaultEdsServiceName, ::testing::_, ::testing::_, |
|
ClientResourceStatus::DOES_NOT_EXIST, ::testing::_))); |
|
} |
|
|
|
} // namespace |
|
} // namespace testing |
|
} // namespace grpc |
|
|
|
#endif // DISABLED_XDS_PROTO_IN_CC |
|
|
|
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; |
|
}
|
|
|