| // 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 "test/cpp/end2end/xds/xds_utils.h" |
| |
| #include <functional> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <thread> |
| #include <vector> |
| |
| #include "absl/memory/memory.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/str_replace.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/optional.h" |
| |
| #include <grpcpp/security/tls_certificate_provider.h> |
| |
| #include "src/core/ext/filters/http/server/http_server_filter.h" |
| #include "src/core/ext/xds/xds_channel_args.h" |
| #include "src/core/ext/xds/xds_client_grpc.h" |
| #include "src/core/lib/gpr/tmpfile.h" |
| #include "src/core/lib/gprpp/env.h" |
| #include "src/core/lib/surface/server.h" |
| #include "src/cpp/client/secure_credentials.h" |
| #include "src/proto/grpc/testing/xds/v3/router.grpc.pb.h" |
| #include "test/core/util/resolve_localhost_ip46.h" |
| |
| namespace grpc { |
| namespace testing { |
| |
| using ::envoy::config::cluster::v3::Cluster; |
| using ::envoy::config::core::v3::HealthStatus; |
| 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; |
| |
| // |
| // XdsBootstrapBuilder |
| // |
| |
| std::string XdsBootstrapBuilder::Build() { |
| std::vector<std::string> fields; |
| fields.push_back(MakeXdsServersText(servers_)); |
| if (!client_default_listener_resource_name_template_.empty()) { |
| fields.push_back( |
| absl::StrCat(" \"client_default_listener_resource_name_template\": \"", |
| client_default_listener_resource_name_template_, "\"")); |
| } |
| fields.push_back(MakeNodeText()); |
| if (!server_listener_resource_name_template_.empty()) { |
| fields.push_back( |
| absl::StrCat(" \"server_listener_resource_name_template\": \"", |
| server_listener_resource_name_template_, "\"")); |
| } |
| fields.push_back(MakeCertificateProviderText()); |
| fields.push_back(MakeAuthorityText()); |
| return absl::StrCat("{", absl::StrJoin(fields, ",\n"), "}"); |
| } |
| |
| std::string XdsBootstrapBuilder::MakeXdsServersText( |
| absl::Span<const std::string> server_uris) { |
| constexpr char kXdsServerTemplate[] = |
| " {\n" |
| " \"server_uri\": \"<SERVER_URI>\",\n" |
| " \"channel_creds\": [\n" |
| " {\n" |
| " \"type\": \"<SERVER_CREDS_TYPE>\"\n" |
| " }\n" |
| " ],\n" |
| " \"server_features\": [<SERVER_FEATURES>]\n" |
| " }"; |
| std::vector<std::string> server_features; |
| if (ignore_resource_deletion_) { |
| server_features.push_back("\"ignore_resource_deletion\""); |
| } |
| std::vector<std::string> servers; |
| for (absl::string_view server_uri : server_uris) { |
| servers.emplace_back(absl::StrReplaceAll( |
| kXdsServerTemplate, |
| {{"<SERVER_URI>", server_uri}, |
| {"<SERVER_CREDS_TYPE>", xds_channel_creds_type_}, |
| {"<SERVER_FEATURES>", absl::StrJoin(server_features, ", ")}})); |
| } |
| return absl::StrCat(" \"xds_servers\": [\n", |
| absl::StrJoin(servers, ",\n"), "\n ]"); |
| } |
| |
| std::string XdsBootstrapBuilder::MakeNodeText() { |
| constexpr char kXdsNode[] = |
| " \"node\": {\n" |
| " \"id\": \"xds_end2end_test\",\n" |
| " \"cluster\": \"test\",\n" |
| " \"metadata\": {\n" |
| " \"foo\": \"bar\"\n" |
| " },\n" |
| " \"locality\": {\n" |
| " \"region\": \"corp\",\n" |
| " \"zone\": \"svl\",\n" |
| " \"sub_zone\": \"mp3\"\n" |
| " }\n" |
| " }"; |
| return kXdsNode; |
| } |
| |
| std::string XdsBootstrapBuilder::MakeCertificateProviderText() { |
| std::vector<std::string> entries; |
| for (const auto& p : plugins_) { |
| const std::string& key = p.first; |
| const PluginInfo& plugin_info = p.second; |
| std::vector<std::string> fields; |
| fields.push_back(absl::StrFormat(" \"%s\": {", key)); |
| if (!plugin_info.plugin_config.empty()) { |
| fields.push_back( |
| absl::StrFormat(" \"plugin_name\": \"%s\",", plugin_info.name)); |
| fields.push_back(absl::StrCat(" \"config\": {\n", |
| plugin_info.plugin_config, "\n }")); |
| } else { |
| fields.push_back( |
| absl::StrFormat(" \"plugin_name\": \"%s\"", plugin_info.name)); |
| } |
| fields.push_back(" }"); |
| entries.push_back(absl::StrJoin(fields, "\n")); |
| } |
| return absl::StrCat(" \"certificate_providers\": {\n", |
| absl::StrJoin(entries, ",\n"), " \n}"); |
| } |
| |
| std::string XdsBootstrapBuilder::MakeAuthorityText() { |
| std::vector<std::string> entries; |
| for (const auto& p : authorities_) { |
| const std::string& name = p.first; |
| const AuthorityInfo& authority_info = p.second; |
| std::vector<std::string> fields = { |
| MakeXdsServersText(authority_info.servers)}; |
| if (!authority_info.client_listener_resource_name_template.empty()) { |
| fields.push_back(absl::StrCat( |
| "\"client_listener_resource_name_template\": \"", |
| authority_info.client_listener_resource_name_template, "\"")); |
| } |
| entries.push_back(absl::StrCat(absl::StrFormat("\"%s\": {\n ", name), |
| absl::StrJoin(fields, ",\n"), "\n}")); |
| } |
| return absl::StrCat("\"authorities\": {\n", absl::StrJoin(entries, ",\n"), |
| "\n}"); |
| } |
| |
| // |
| // XdsResourceUtils::ClientHcmAccessor |
| // |
| |
| HttpConnectionManager XdsResourceUtils::ClientHcmAccessor::Unpack( |
| const Listener& listener) const { |
| HttpConnectionManager http_connection_manager; |
| listener.api_listener().api_listener().UnpackTo(&http_connection_manager); |
| return http_connection_manager; |
| } |
| |
| void XdsResourceUtils::ClientHcmAccessor::Pack(const HttpConnectionManager& hcm, |
| Listener* listener) const { |
| auto* api_listener = listener->mutable_api_listener()->mutable_api_listener(); |
| api_listener->PackFrom(hcm); |
| } |
| |
| // |
| // XdsResourceUtils::ServerHcmAccessor |
| // |
| |
| HttpConnectionManager XdsResourceUtils::ServerHcmAccessor::Unpack( |
| const Listener& listener) const { |
| HttpConnectionManager http_connection_manager; |
| listener.default_filter_chain().filters().at(0).typed_config().UnpackTo( |
| &http_connection_manager); |
| return http_connection_manager; |
| } |
| |
| void XdsResourceUtils::ServerHcmAccessor::Pack(const HttpConnectionManager& hcm, |
| Listener* listener) const { |
| auto* filters = listener->mutable_default_filter_chain()->mutable_filters(); |
| if (filters->empty()) filters->Add(); |
| filters->at(0).mutable_typed_config()->PackFrom(hcm); |
| } |
| |
| // |
| // XdsResourceUtils |
| // |
| |
| const char XdsResourceUtils::kDefaultLocalityRegion[] = |
| "xds_default_locality_region"; |
| const char XdsResourceUtils::kDefaultLocalityZone[] = |
| "xds_default_locality_zone"; |
| |
| const char XdsResourceUtils::kServerName[] = "server.example.com"; |
| const char XdsResourceUtils::kDefaultRouteConfigurationName[] = |
| "route_config_name"; |
| const char XdsResourceUtils::kDefaultClusterName[] = "cluster_name"; |
| const char XdsResourceUtils::kDefaultEdsServiceName[] = "eds_service_name"; |
| const char XdsResourceUtils::kDefaultServerRouteConfigurationName[] = |
| "default_server_route_config_name"; |
| |
| Listener XdsResourceUtils::DefaultListener() { |
| Listener listener; |
| listener.set_name(kServerName); |
| ClientHcmAccessor().Pack(DefaultHcm(), &listener); |
| return listener; |
| } |
| |
| RouteConfiguration XdsResourceUtils::DefaultRouteConfig() { |
| RouteConfiguration route_config; |
| route_config.set_name(kDefaultRouteConfigurationName); |
| auto* virtual_host = route_config.add_virtual_hosts(); |
| virtual_host->add_domains("*"); |
| auto* route = virtual_host->add_routes(); |
| route->mutable_match()->set_prefix(""); |
| route->mutable_route()->set_cluster(kDefaultClusterName); |
| return route_config; |
| } |
| |
| Cluster XdsResourceUtils::DefaultCluster() { |
| Cluster cluster; |
| cluster.set_name(kDefaultClusterName); |
| cluster.set_type(Cluster::EDS); |
| auto* eds_config = cluster.mutable_eds_cluster_config(); |
| eds_config->mutable_eds_config()->mutable_self(); |
| eds_config->set_service_name(kDefaultEdsServiceName); |
| cluster.set_lb_policy(Cluster::ROUND_ROBIN); |
| return cluster; |
| } |
| |
| Listener XdsResourceUtils::DefaultServerListener() { |
| Listener listener; |
| listener.mutable_address()->mutable_socket_address()->set_address( |
| grpc_core::LocalIp()); |
| ServerHcmAccessor().Pack(DefaultHcm(), &listener); |
| return listener; |
| } |
| |
| RouteConfiguration XdsResourceUtils::DefaultServerRouteConfig() { |
| RouteConfiguration route_config; |
| route_config.set_name(kDefaultServerRouteConfigurationName); |
| auto* virtual_host = route_config.add_virtual_hosts(); |
| virtual_host->add_domains("*"); |
| auto* route = virtual_host->add_routes(); |
| route->mutable_match()->set_prefix(""); |
| route->mutable_non_forwarding_action(); |
| return route_config; |
| } |
| |
| HttpConnectionManager XdsResourceUtils::DefaultHcm() { |
| HttpConnectionManager http_connection_manager; |
| auto* filter = http_connection_manager.add_http_filters(); |
| filter->set_name("router"); |
| filter->mutable_typed_config()->PackFrom( |
| envoy::extensions::filters::http::router::v3::Router()); |
| return http_connection_manager; |
| } |
| |
| std::string XdsResourceUtils::GetServerListenerName(int port) { |
| return absl::StrCat("grpc/server?xds.resource.listening_address=", |
| grpc_core::LocalIp(), ":", port); |
| } |
| |
| Listener XdsResourceUtils::PopulateServerListenerNameAndPort( |
| const Listener& listener_template, int port) { |
| Listener listener = listener_template; |
| listener.set_name(GetServerListenerName(port)); |
| listener.mutable_address()->mutable_socket_address()->set_port_value(port); |
| return listener; |
| } |
| |
| void XdsResourceUtils::SetListenerAndRouteConfiguration( |
| AdsServiceImpl* ads_service, Listener listener, |
| const RouteConfiguration& route_config, bool use_rds, |
| const HcmAccessor& hcm_accessor) { |
| HttpConnectionManager http_connection_manager = hcm_accessor.Unpack(listener); |
| if (use_rds) { |
| auto* rds = http_connection_manager.mutable_rds(); |
| rds->set_route_config_name(route_config.name()); |
| rds->mutable_config_source()->mutable_self(); |
| ads_service->SetRdsResource(route_config); |
| } else { |
| *http_connection_manager.mutable_route_config() = route_config; |
| } |
| hcm_accessor.Pack(http_connection_manager, &listener); |
| ads_service->SetLdsResource(listener); |
| } |
| |
| void XdsResourceUtils::SetRouteConfiguration( |
| AdsServiceImpl* ads_service, const RouteConfiguration& route_config, |
| bool use_rds, const Listener* listener_to_copy) { |
| if (use_rds) { |
| ads_service->SetRdsResource(route_config); |
| } else { |
| Listener listener(listener_to_copy == nullptr ? DefaultListener() |
| : *listener_to_copy); |
| HttpConnectionManager http_connection_manager = |
| ClientHcmAccessor().Unpack(listener); |
| *(http_connection_manager.mutable_route_config()) = route_config; |
| ClientHcmAccessor().Pack(http_connection_manager, &listener); |
| ads_service->SetLdsResource(listener); |
| } |
| } |
| |
| std::string XdsResourceUtils::LocalityNameString(absl::string_view sub_zone) { |
| return absl::StrFormat("{region=\"%s\", zone=\"%s\", sub_zone=\"%s\"}", |
| kDefaultLocalityRegion, kDefaultLocalityZone, |
| sub_zone); |
| } |
| |
| ClusterLoadAssignment XdsResourceUtils::BuildEdsResource( |
| const EdsResourceArgs& args, absl::string_view eds_service_name) { |
| ClusterLoadAssignment assignment; |
| assignment.set_cluster_name(eds_service_name); |
| for (const auto& locality : args.locality_list) { |
| auto* endpoints = assignment.add_endpoints(); |
| endpoints->mutable_load_balancing_weight()->set_value(locality.lb_weight); |
| endpoints->set_priority(locality.priority); |
| endpoints->mutable_locality()->set_region(kDefaultLocalityRegion); |
| endpoints->mutable_locality()->set_zone(kDefaultLocalityZone); |
| endpoints->mutable_locality()->set_sub_zone(locality.sub_zone); |
| for (size_t i = 0; i < locality.endpoints.size(); ++i) { |
| const auto& endpoint = locality.endpoints[i]; |
| auto* lb_endpoints = endpoints->add_lb_endpoints(); |
| if (locality.endpoints.size() > i && |
| locality.endpoints[i].health_status != HealthStatus::UNKNOWN) { |
| lb_endpoints->set_health_status(endpoint.health_status); |
| } |
| if (locality.endpoints.size() > i && endpoint.lb_weight >= 1) { |
| lb_endpoints->mutable_load_balancing_weight()->set_value( |
| endpoint.lb_weight); |
| } |
| auto* endpoint_proto = lb_endpoints->mutable_endpoint(); |
| auto* socket_address = |
| endpoint_proto->mutable_address()->mutable_socket_address(); |
| socket_address->set_address(grpc_core::LocalIp()); |
| socket_address->set_port_value(endpoint.port); |
| for (int port : endpoint.additional_ports) { |
| socket_address = endpoint_proto->add_additional_addresses() |
| ->mutable_address() |
| ->mutable_socket_address(); |
| socket_address->set_address(grpc_core::LocalIp()); |
| socket_address->set_port_value(port); |
| } |
| } |
| } |
| if (!args.drop_categories.empty()) { |
| auto* policy = assignment.mutable_policy(); |
| for (const auto& p : args.drop_categories) { |
| const std::string& name = p.first; |
| const uint32_t parts_per_million = p.second; |
| auto* drop_overload = policy->add_drop_overloads(); |
| drop_overload->set_category(name); |
| auto* drop_percentage = drop_overload->mutable_drop_percentage(); |
| drop_percentage->set_numerator(parts_per_million); |
| drop_percentage->set_denominator(args.drop_denominator); |
| } |
| } |
| return assignment; |
| } |
| |
| } // namespace testing |
| } // namespace grpc |