mirror of https://github.com/grpc/grpc.git
Support RDS updates on the server (#27851)
* Port changes from #27388 * Reviewer comments * Fix resource timeout issue * Cleanup * Fix clang-tidy * Revert benchmark * Restructure * clang-tidy * Automated change: Fix sanity tests * Partial commit * Reviewer comments * Fixes * Reviewer comments * Reviewer comments * Reviewer comments * Reviewer comments * clang-format * Fix FaultInjection tests * clang-tidy Co-authored-by: yashykt <yashykt@users.noreply.github.com>pull/28144/head^2
parent
11f440775b
commit
25446c468a
22 changed files with 1701 additions and 585 deletions
@ -0,0 +1,247 @@ |
|||||||
|
//
|
||||||
|
//
|
||||||
|
// Copyright 2021 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 <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include "src/core/ext/xds/xds_routing.h" |
||||||
|
|
||||||
|
#include <cctype> |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
namespace { |
||||||
|
enum MatchType { |
||||||
|
EXACT_MATCH, |
||||||
|
SUFFIX_MATCH, |
||||||
|
PREFIX_MATCH, |
||||||
|
UNIVERSE_MATCH, |
||||||
|
INVALID_MATCH, |
||||||
|
}; |
||||||
|
|
||||||
|
// Returns true if match succeeds.
|
||||||
|
bool DomainMatch(MatchType match_type, absl::string_view domain_pattern_in, |
||||||
|
absl::string_view expected_host_name_in) { |
||||||
|
// Normalize the args to lower-case. Domain matching is case-insensitive.
|
||||||
|
std::string domain_pattern = std::string(domain_pattern_in); |
||||||
|
std::string expected_host_name = std::string(expected_host_name_in); |
||||||
|
std::transform(domain_pattern.begin(), domain_pattern.end(), |
||||||
|
domain_pattern.begin(), |
||||||
|
[](unsigned char c) { return std::tolower(c); }); |
||||||
|
std::transform(expected_host_name.begin(), expected_host_name.end(), |
||||||
|
expected_host_name.begin(), |
||||||
|
[](unsigned char c) { return std::tolower(c); }); |
||||||
|
if (match_type == EXACT_MATCH) { |
||||||
|
return domain_pattern == expected_host_name; |
||||||
|
} else if (match_type == SUFFIX_MATCH) { |
||||||
|
// Asterisk must match at least one char.
|
||||||
|
if (expected_host_name.size() < domain_pattern.size()) return false; |
||||||
|
absl::string_view pattern_suffix(domain_pattern.c_str() + 1); |
||||||
|
absl::string_view host_suffix(expected_host_name.c_str() + |
||||||
|
expected_host_name.size() - |
||||||
|
pattern_suffix.size()); |
||||||
|
return pattern_suffix == host_suffix; |
||||||
|
} else if (match_type == PREFIX_MATCH) { |
||||||
|
// Asterisk must match at least one char.
|
||||||
|
if (expected_host_name.size() < domain_pattern.size()) return false; |
||||||
|
absl::string_view pattern_prefix(domain_pattern.c_str(), |
||||||
|
domain_pattern.size() - 1); |
||||||
|
absl::string_view host_prefix(expected_host_name.c_str(), |
||||||
|
pattern_prefix.size()); |
||||||
|
return pattern_prefix == host_prefix; |
||||||
|
} else { |
||||||
|
return match_type == UNIVERSE_MATCH; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
MatchType DomainPatternMatchType(absl::string_view domain_pattern) { |
||||||
|
if (domain_pattern.empty()) return INVALID_MATCH; |
||||||
|
if (domain_pattern.find('*') == std::string::npos) return EXACT_MATCH; |
||||||
|
if (domain_pattern == "*") return UNIVERSE_MATCH; |
||||||
|
if (domain_pattern[0] == '*') return SUFFIX_MATCH; |
||||||
|
if (domain_pattern[domain_pattern.size() - 1] == '*') return PREFIX_MATCH; |
||||||
|
return INVALID_MATCH; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
absl::optional<size_t> XdsRouting::FindVirtualHostForDomain( |
||||||
|
const VirtualHostListIterator& vhost_iterator, absl::string_view domain) { |
||||||
|
// Find the best matched virtual host.
|
||||||
|
// The search order for 4 groups of domain patterns:
|
||||||
|
// 1. Exact match.
|
||||||
|
// 2. Suffix match (e.g., "*ABC").
|
||||||
|
// 3. Prefix match (e.g., "ABC*").
|
||||||
|
// 4. Universe match (i.e., "*").
|
||||||
|
// Within each group, longest match wins.
|
||||||
|
// If the same best matched domain pattern appears in multiple virtual
|
||||||
|
// hosts, the first matched virtual host wins.
|
||||||
|
absl::optional<size_t> target_index; |
||||||
|
MatchType best_match_type = INVALID_MATCH; |
||||||
|
size_t longest_match = 0; |
||||||
|
// Check each domain pattern in each virtual host to determine the best
|
||||||
|
// matched virtual host.
|
||||||
|
for (size_t i = 0; i < vhost_iterator.Size(); ++i) { |
||||||
|
const auto& domains = vhost_iterator.GetDomainsForVirtualHost(i); |
||||||
|
for (const std::string& domain_pattern : domains) { |
||||||
|
// Check the match type first. Skip the pattern if it's not better
|
||||||
|
// than current match.
|
||||||
|
const MatchType match_type = DomainPatternMatchType(domain_pattern); |
||||||
|
// This should be caught by RouteConfigParse().
|
||||||
|
GPR_ASSERT(match_type != INVALID_MATCH); |
||||||
|
if (match_type > best_match_type) continue; |
||||||
|
if (match_type == best_match_type && |
||||||
|
domain_pattern.size() <= longest_match) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
// Skip if match fails.
|
||||||
|
if (!DomainMatch(match_type, domain_pattern, domain)) continue; |
||||||
|
// Choose this match.
|
||||||
|
target_index = i; |
||||||
|
best_match_type = match_type; |
||||||
|
longest_match = domain_pattern.size(); |
||||||
|
if (best_match_type == EXACT_MATCH) break; |
||||||
|
} |
||||||
|
if (best_match_type == EXACT_MATCH) break; |
||||||
|
} |
||||||
|
return target_index; |
||||||
|
} |
||||||
|
|
||||||
|
namespace { |
||||||
|
|
||||||
|
bool HeadersMatch(const std::vector<HeaderMatcher>& header_matchers, |
||||||
|
grpc_metadata_batch* initial_metadata) { |
||||||
|
for (const auto& header_matcher : header_matchers) { |
||||||
|
std::string concatenated_value; |
||||||
|
if (!header_matcher.Match(XdsRouting::GetHeaderValue( |
||||||
|
initial_metadata, header_matcher.name(), &concatenated_value))) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
bool UnderFraction(const uint32_t fraction_per_million) { |
||||||
|
// Generate a random number in [0, 1000000).
|
||||||
|
const uint32_t random_number = rand() % 1000000; |
||||||
|
return random_number < fraction_per_million; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
absl::optional<size_t> XdsRouting::GetRouteForRequest( |
||||||
|
const RouteListIterator& route_list_iterator, absl::string_view path, |
||||||
|
grpc_metadata_batch* initial_metadata) { |
||||||
|
for (size_t i = 0; i < route_list_iterator.Size(); ++i) { |
||||||
|
const XdsApi::Route::Matchers& matchers = |
||||||
|
route_list_iterator.GetMatchersForRoute(i); |
||||||
|
if (matchers.path_matcher.Match(path) && |
||||||
|
HeadersMatch(matchers.header_matchers, initial_metadata) && |
||||||
|
(!matchers.fraction_per_million.has_value() || |
||||||
|
UnderFraction(*matchers.fraction_per_million))) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
return absl::nullopt; |
||||||
|
} |
||||||
|
|
||||||
|
bool XdsRouting::IsValidDomainPattern(absl::string_view domain_pattern) { |
||||||
|
return DomainPatternMatchType(domain_pattern) != INVALID_MATCH; |
||||||
|
} |
||||||
|
|
||||||
|
absl::optional<absl::string_view> XdsRouting::GetHeaderValue( |
||||||
|
grpc_metadata_batch* initial_metadata, absl::string_view header_name, |
||||||
|
std::string* concatenated_value) { |
||||||
|
// Note: If we ever allow binary headers here, we still need to
|
||||||
|
// special-case ignore "grpc-tags-bin" and "grpc-trace-bin", since
|
||||||
|
// they are not visible to the LB policy in grpc-go.
|
||||||
|
if (absl::EndsWith(header_name, "-bin")) { |
||||||
|
return absl::nullopt; |
||||||
|
} else if (header_name == "content-type") { |
||||||
|
return "application/grpc"; |
||||||
|
} |
||||||
|
return grpc_metadata_batch_get_value(initial_metadata, header_name, |
||||||
|
concatenated_value); |
||||||
|
} |
||||||
|
|
||||||
|
namespace { |
||||||
|
|
||||||
|
const XdsHttpFilterImpl::FilterConfig* FindFilterConfigOverride( |
||||||
|
const std::string& instance_name, |
||||||
|
const XdsApi::RdsUpdate::VirtualHost& vhost, const XdsApi::Route& route, |
||||||
|
const XdsApi::Route::RouteAction::ClusterWeight* cluster_weight) { |
||||||
|
// Check ClusterWeight, if any.
|
||||||
|
if (cluster_weight != nullptr) { |
||||||
|
auto it = cluster_weight->typed_per_filter_config.find(instance_name); |
||||||
|
if (it != cluster_weight->typed_per_filter_config.end()) return &it->second; |
||||||
|
} |
||||||
|
// Check Route.
|
||||||
|
auto it = route.typed_per_filter_config.find(instance_name); |
||||||
|
if (it != route.typed_per_filter_config.end()) return &it->second; |
||||||
|
// Check VirtualHost.
|
||||||
|
it = vhost.typed_per_filter_config.find(instance_name); |
||||||
|
if (it != vhost.typed_per_filter_config.end()) return &it->second; |
||||||
|
// Not found.
|
||||||
|
return nullptr; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
XdsRouting::GeneratePerHttpFilterConfigsResult |
||||||
|
XdsRouting::GeneratePerHTTPFilterConfigs( |
||||||
|
const std::vector<XdsApi::LdsUpdate::HttpConnectionManager::HttpFilter>& |
||||||
|
http_filters, |
||||||
|
const XdsApi::RdsUpdate::VirtualHost& vhost, const XdsApi::Route& route, |
||||||
|
const XdsApi::Route::RouteAction::ClusterWeight* cluster_weight, |
||||||
|
grpc_channel_args* args) { |
||||||
|
GeneratePerHttpFilterConfigsResult result; |
||||||
|
result.args = args; |
||||||
|
for (const auto& http_filter : http_filters) { |
||||||
|
// Find filter. This is guaranteed to succeed, because it's checked
|
||||||
|
// at config validation time in the XdsApi code.
|
||||||
|
const XdsHttpFilterImpl* filter_impl = |
||||||
|
XdsHttpFilterRegistry::GetFilterForType( |
||||||
|
http_filter.config.config_proto_type_name); |
||||||
|
GPR_ASSERT(filter_impl != nullptr); |
||||||
|
// If there is not actually any C-core filter associated with this
|
||||||
|
// xDS filter, then it won't need any config, so skip it.
|
||||||
|
if (filter_impl->channel_filter() == nullptr) continue; |
||||||
|
// Allow filter to add channel args that may affect service config
|
||||||
|
// parsing.
|
||||||
|
result.args = filter_impl->ModifyChannelArgs(result.args); |
||||||
|
// Find config override, if any.
|
||||||
|
const XdsHttpFilterImpl::FilterConfig* config_override = |
||||||
|
FindFilterConfigOverride(http_filter.name, vhost, route, |
||||||
|
cluster_weight); |
||||||
|
// Generate service config for filter.
|
||||||
|
auto method_config_field = |
||||||
|
filter_impl->GenerateServiceConfig(http_filter.config, config_override); |
||||||
|
if (!method_config_field.ok()) { |
||||||
|
grpc_channel_args_destroy(result.args); |
||||||
|
result.args = nullptr; |
||||||
|
result.error = GRPC_ERROR_CREATE_FROM_CPP_STRING(absl::StrCat( |
||||||
|
"failed to generate method config for HTTP filter ", http_filter.name, |
||||||
|
": ", method_config_field.status().ToString())); |
||||||
|
break; |
||||||
|
} |
||||||
|
result.per_filter_configs[method_config_field->service_config_field_name] |
||||||
|
.push_back(method_config_field->element); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace grpc_core
|
@ -0,0 +1,98 @@ |
|||||||
|
//
|
||||||
|
//
|
||||||
|
// Copyright 2021 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.
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef GRPC_CORE_EXT_XDS_XDS_ROUTING_H |
||||||
|
#define GRPC_CORE_EXT_XDS_XDS_ROUTING_H |
||||||
|
|
||||||
|
#include <grpc/support/port_platform.h> |
||||||
|
|
||||||
|
#include <vector> |
||||||
|
|
||||||
|
#include "absl/strings/string_view.h" |
||||||
|
|
||||||
|
#include <grpc/support/log.h> |
||||||
|
|
||||||
|
#include "src/core/ext/xds/xds_api.h" |
||||||
|
#include "src/core/lib/matchers/matchers.h" |
||||||
|
#include "src/core/lib/transport/metadata_batch.h" |
||||||
|
|
||||||
|
namespace grpc_core { |
||||||
|
|
||||||
|
class XdsRouting { |
||||||
|
public: |
||||||
|
class VirtualHostListIterator { |
||||||
|
public: |
||||||
|
virtual ~VirtualHostListIterator() = default; |
||||||
|
// Returns the number of virtual hosts in the list.
|
||||||
|
virtual size_t Size() const = 0; |
||||||
|
// Returns the domain list for the virtual host at the specified index.
|
||||||
|
virtual const std::vector<std::string>& GetDomainsForVirtualHost( |
||||||
|
size_t index) const = 0; |
||||||
|
}; |
||||||
|
|
||||||
|
class RouteListIterator { |
||||||
|
public: |
||||||
|
virtual ~RouteListIterator() = default; |
||||||
|
// Number of routes.
|
||||||
|
virtual size_t Size() const = 0; |
||||||
|
// Returns the matchers for the route at the specified index.
|
||||||
|
virtual const XdsApi::Route::Matchers& GetMatchersForRoute( |
||||||
|
size_t index) const = 0; |
||||||
|
}; |
||||||
|
|
||||||
|
// Returns the index of the selected virtual host in the list.
|
||||||
|
static absl::optional<size_t> FindVirtualHostForDomain( |
||||||
|
const VirtualHostListIterator& vhost_iterator, absl::string_view domain); |
||||||
|
|
||||||
|
// Returns the index in route_list_iterator to use for a request with
|
||||||
|
// the specified path and metadata, or nullopt if no route matches.
|
||||||
|
static absl::optional<size_t> GetRouteForRequest( |
||||||
|
const RouteListIterator& route_list_iterator, absl::string_view path, |
||||||
|
grpc_metadata_batch* initial_metadata); |
||||||
|
|
||||||
|
// Returns true if \a domain_pattern is a valid domain pattern, false
|
||||||
|
// otherwise.
|
||||||
|
static bool IsValidDomainPattern(absl::string_view domain_pattern); |
||||||
|
|
||||||
|
// Returns the metadata value(s) for the specified key.
|
||||||
|
// As special cases, binary headers return a value of absl::nullopt, and
|
||||||
|
// "content-type" header returns "application/grpc".
|
||||||
|
static absl::optional<absl::string_view> GetHeaderValue( |
||||||
|
grpc_metadata_batch* initial_metadata, absl::string_view header_name, |
||||||
|
std::string* concatenated_value); |
||||||
|
|
||||||
|
struct GeneratePerHttpFilterConfigsResult { |
||||||
|
// Map of field name to list of elements for that field
|
||||||
|
std::map<std::string, std::vector<std::string>> per_filter_configs; |
||||||
|
grpc_error_handle error = GRPC_ERROR_NONE; |
||||||
|
// Guaranteed to be nullptr if error is GRPC_ERROR_NONE
|
||||||
|
grpc_channel_args* args = nullptr; |
||||||
|
}; |
||||||
|
|
||||||
|
// Generates a map of per_filter_configs. \a args is consumed.
|
||||||
|
static GeneratePerHttpFilterConfigsResult GeneratePerHTTPFilterConfigs( |
||||||
|
const std::vector<XdsApi::LdsUpdate::HttpConnectionManager::HttpFilter>& |
||||||
|
http_filters, |
||||||
|
const XdsApi::RdsUpdate::VirtualHost& vhost, const XdsApi::Route& route, |
||||||
|
const XdsApi::Route::RouteAction::ClusterWeight* cluster_weight, |
||||||
|
grpc_channel_args* args); |
||||||
|
}; |
||||||
|
|
||||||
|
} // namespace grpc_core
|
||||||
|
|
||||||
|
#endif // GRPC_CORE_EXT_XDS_XDS_ROUTING_H
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue