Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Router Check Tool HeaderMatcher and sendLocalReply #12810

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
655dc9f
HeaderMatcher in router check tool
kb000 Aug 25, 2020
0ec2d35
Exercise SendLocalReply in router check tool
kb000 Aug 25, 2020
6460b50
lint
kb000 Aug 25, 2020
fecf2e5
lint .rst
kb000 Aug 25, 2020
8a66232
Fix .rst indent
kb000 Aug 25, 2020
1107791
Fix logic errors in present_match
kb000 Aug 26, 2020
eba29e6
clang_tidy
kb000 Aug 26, 2020
e07cff3
Describe invert_match
kb000 Aug 26, 2020
16ab0ec
clang_tidy again
kb000 Aug 26, 2020
6d9cc95
Soft-deprecate request_header_fields and response_header_fields
kb000 Aug 26, 2020
b7c60b4
format
kb000 Aug 26, 2020
eb898b6
Remove deprecated fields from router_check_tool unit tests.
kb000 Aug 27, 2020
471edad
Revert "Remove deprecated fields from router_check_tool unit tests."
kb000 Aug 28, 2020
e49a07e
Inline encode_functions
kb000 Aug 28, 2020
e252976
Bring back docs for deprecated *_header_fields
kb000 Aug 28, 2020
5457fe0
Come on linux_x64 why don't you understand me
kb000 Aug 31, 2020
b9d73ed
again
kb000 Sep 1, 2020
990cee3
again
kb000 Sep 1, 2020
695b89e
Merge branch 'master' into dev/kb000/router_check_contenttype_2
kb000 Sep 1, 2020
000fdfe
Merge branch 'master' into dev/kb000/router_check_contenttype_2
kb000 Sep 1, 2020
61de36d
a new field in EncodeFunctions??
kb000 Sep 1, 2020
375c988
Remove tests for deprecated config
kb000 Sep 1, 2020
8c5b149
Undo format changes
kb000 Sep 1, 2020
0913c25
Update test/tools/router_check/validation.proto
kb000 Sep 2, 2020
a5073ac
comments
kb000 Sep 2, 2020
744d843
Doc changes for comments
kb000 Sep 2, 2020
f88f9cd
Use HeaderUtility::matchHeaders
kb000 Sep 3, 2020
c44d461
reorder headers
kb000 Sep 3, 2020
012e9e6
Remove deprecated fields
kb000 Sep 3, 2020
f2578be
clang_tidy
kb000 Sep 3, 2020
a28d068
clang_tidy
kb000 Sep 3, 2020
16c1226
Address comments
kb000 Sep 4, 2020
f2cc096
link router_check_tool from release notes
kb000 Sep 4, 2020
3cfdd90
Merge branch 'master' into dev/kb000/router_check_contenttype_2
kb000 Sep 4, 2020
9b827ed
Add default case to toString
kb000 Sep 4, 2020
f34dc24
NOT_REACHED outside switch
kb000 Sep 4, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions docs/root/configuration/operations/tools/router_check.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ expects a cluster name match of "instant-server".::
host_rewrite: ...,
path_rewrite: ...,
path_redirect: ...,
request_header_fields:
- key: ...,
value: ...
response_header_fields:
- key: ...,
value: ...
request_header_matches:
- name: ...,
exact_match: ...
response_header_matches:
- name: ...,
exact_match: ...
- name: ...,
presence_match: ...

test_name
*(required, string)* The name of a test object.
Expand Down Expand Up @@ -150,16 +152,24 @@ validate
*(optional, string)* Match the returned redirect path.

request_header_fields, response_header_fields
*(optional, array)* Match the listed header fields. Examples header fields include the "path", "cookie",
*(optional, array, deprecated)* Match the listed header fields. Example header fields include the "path", "cookie",
and "date" fields. The header fields are checked after all other test cases. Thus, the header fields checked
will be those of the redirected or rewritten routes when applicable.
These fields are deprecated. Use request_header_matches, response_header_matches instead.

key
*(required, string)* The name of the header field to match.

value
*(required, string)* The value of the header field to match.

request_header_matches, response_header_matches
*(optional, array)* Matchers for the listed headers. Example header fields include the "path", "cookie",
and "date" fields, as well as custom headers set in the input or by the route. The header fields are checked
after all other test cases. Thus, the header fields checked will be those of the redirected or rewritten
routes when applicable.
- Matchers are specified as :ref:`HeaderMatchers <envoy_api_msg_route.HeaderMatcher>`, and behave the same way.

Coverage
--------

Expand Down
2 changes: 2 additions & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ New Features
* router: added new :ref:`host_rewrite_path_regex <envoy_v3_api_field_config.route.v3.RouteAction.host_rewrite_path_regex>`
option, which allows rewriting Host header based on path.
* router: added support for DYNAMIC_METADATA :ref:`header formatter <config_http_conn_man_headers_custom_request_headers>`.
* router_check_tool: added support for `request_header_matches`, `response_header_matches` to :ref:`router check tool <config_tools_router_check_tool>`.
* signal: added support for calling fatal error handlers without envoy's signal handler, via FatalErrorHandler::callFatalErrorHandlers().
* stats: added optional histograms to :ref:`cluster stats <config_cluster_manager_cluster_stats_request_response_sizes>`
that track headers and body sizes of requests and responses.
Expand Down Expand Up @@ -124,3 +125,4 @@ Deprecated
:ref:`match <envoy_v3_api_field_config.tap.v3.TapConfig.match>` field.
* ext_authz: the :ref:`dynamic metadata <envoy_v3_api_field_service.auth.v3.OkHttpResponse.dynamic_metadata>` field in :ref:`OkHttpResponse <envoy_v3_api_msg_service.auth.v3.OkHttpResponse>`
has been deprecated in favor of :ref:`dynamic metadata <envoy_v3_api_field_service.auth.v3.CheckResponse.dynamic_metadata>` field in :ref:`CheckResponse <envoy_v3_api_msg_service.auth.v3.CheckResponse>`.
* router_check_tool: `request_header_fields`, `response_header_fields` config deprecated in favor of `request_header_matches`, `response_header_matches`.
kb000 marked this conversation as resolved.
Show resolved Hide resolved
5 changes: 4 additions & 1 deletion test/tools/router_check/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@ envoy_cc_test_library(
envoy_proto_library(
name = "validation_proto",
srcs = ["validation.proto"],
deps = ["@envoy_api//envoy/config/core/v3:pkg"],
deps = [
"@envoy_api//envoy/config/core/v3:pkg",
"@envoy_api//envoy/config/route/v3:pkg",
],
)
173 changes: 146 additions & 27 deletions test/tools/router_check/router.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,47 @@

#include "test/test_common/printers.h"

namespace {
const std::string
toString(envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase specifier) {
switch (specifier) {
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kExactMatch:
return "exact_match";
break;
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::
kHiddenEnvoyDeprecatedRegexMatch:
return "regex_match";
break;
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kSafeRegexMatch:
return "safe_regex_match";
break;
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kRangeMatch:
return "range_match";
break;
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPresentMatch:
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::
HEADER_MATCH_SPECIFIER_NOT_SET:
return "present_match";
break;
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPrefixMatch:
return "prefix_match";
break;
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kSuffixMatch:
return "suffix_match";
break;
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kContainsMatch:
return "contains_match";
break;
}
NOT_REACHED_GCOVR_EXCL_LINE;
}

const std::string toString(const Envoy::Http::HeaderEntry* entry) {
return entry == nullptr ? "NULL" : std::string(entry->value().getStringView());
}

} // namespace

namespace Envoy {
// static
ToolConfig ToolConfig::create(const envoy::RouterCheckToolSchema::ValidationItem& check_config) {
Expand Down Expand Up @@ -110,6 +151,7 @@ void RouterCheckTool::finalizeHeaders(ToolConfig& tool_config,
if (tool_config.route_->directResponseEntry() != nullptr) {
tool_config.route_->directResponseEntry()->rewritePathHeader(*tool_config.request_headers_,
true);
sendLocalReply(tool_config, *tool_config.route_->directResponseEntry());
tool_config.route_->directResponseEntry()->finalizeResponseHeaders(
*tool_config.response_headers_, stream_info);
} else if (tool_config.route_->routeEntry() != nullptr) {
Expand All @@ -123,6 +165,27 @@ void RouterCheckTool::finalizeHeaders(ToolConfig& tool_config,
headers_finalized_ = true;
}

void RouterCheckTool::sendLocalReply(ToolConfig& tool_config,
const Router::DirectResponseEntry& entry) {
auto encode_functions = Envoy::Http::Utility::EncodeFunctions{
nullptr, nullptr,
[&](Envoy::Http::ResponseHeaderMapPtr&& headers, bool end_stream) -> void {
UNREFERENCED_PARAMETER(end_stream);
Http::HeaderMapImpl::copyFrom(*tool_config.response_headers_->header_map_, *headers);
},
[&](Envoy::Buffer::Instance& data, bool end_stream) -> void {
UNREFERENCED_PARAMETER(data);
UNREFERENCED_PARAMETER(end_stream);
}};

bool is_grpc = false;
bool is_head_request = false;
Envoy::Http::Utility::LocalReplyData local_reply_data{
is_grpc, entry.responseCode(), entry.responseBody(), absl::nullopt, is_head_request};

Envoy::Http::Utility::sendLocalReply(false, encode_functions, local_reply_data);
}

RouterCheckTool::RouterCheckTool(
std::unique_ptr<NiceMock<Server::Configuration::MockServerFactoryContext>> factory_context,
std::unique_ptr<Router::ConfigImpl> config, std::unique_ptr<Stats::IsolatedStoreImpl> stats,
Expand Down Expand Up @@ -176,8 +239,8 @@ bool RouterCheckTool::compareEntries(const std::string& expected_routes) {
[this](auto&... params) -> bool { return this->compareRewritePath(params...); },
[this](auto&... params) -> bool { return this->compareRewriteHost(params...); },
[this](auto&... params) -> bool { return this->compareRedirectPath(params...); },
[this](auto&... params) -> bool { return this->compareRequestHeaderField(params...); },
[this](auto&... params) -> bool { return this->compareResponseHeaderField(params...); },
[this](auto&... params) -> bool { return this->compareRequestHeaderFields(params...); },
[this](auto&... params) -> bool { return this->compareResponseHeaderFields(params...); },
};
finalizeHeaders(tool_config, stream_info);
// Call appropriate function for each match case.
Expand Down Expand Up @@ -336,64 +399,120 @@ bool RouterCheckTool::compareRedirectPath(
return compareRedirectPath(tool_config, expected.path_redirect().value());
}

bool RouterCheckTool::compareRequestHeaderField(
bool RouterCheckTool::compareRequestHeaderFields(
ToolConfig& tool_config, const envoy::RouterCheckToolSchema::ValidationAssert& expected) {
bool no_failures = true;
if (expected.request_header_matches().data()) {
for (const envoy::config::route::v3::HeaderMatcher& header :
expected.request_header_matches()) {
if (!matchHeaderField(*tool_config.request_headers_, header, "request_header_matches")) {
no_failures = false;
}
}
}
// TODO(kb000) : Remove deprecated request_header_fields.
if (expected.request_header_fields().data()) {
for (const envoy::config::core::v3::HeaderValue& header : expected.request_header_fields()) {
if (!compareRequestHeaderField(tool_config, header.key(), header.value())) {
auto actual = tool_config.request_headers_->get_(header.key());
auto const& expected = header.value();
if (!compareResults(actual, expected, "request_header_fields")) {
no_failures = false;
}
}
}
return no_failures;
}

bool RouterCheckTool::compareRequestHeaderField(ToolConfig& tool_config, const std::string& field,
const std::string& expected) {
std::string actual = tool_config.request_headers_->get_(field);
return compareResults(actual, expected, "request_header_fields");
}

bool RouterCheckTool::compareResponseHeaderField(
bool RouterCheckTool::compareResponseHeaderFields(
ToolConfig& tool_config, const envoy::RouterCheckToolSchema::ValidationAssert& expected) {
bool no_failures = true;
if (expected.response_header_matches().data()) {
for (const envoy::config::route::v3::HeaderMatcher& header :
expected.response_header_matches()) {
if (!matchHeaderField(*tool_config.response_headers_, header, "response_header_matches")) {
no_failures = false;
}
}
}
// TODO(kb000) : Remove deprecated response_header_fields.
if (expected.response_header_fields().data()) {
for (const envoy::config::core::v3::HeaderValue& header : expected.response_header_fields()) {
if (!compareResponseHeaderField(tool_config, header.key(), header.value())) {
auto actual = tool_config.response_headers_->get_(header.key());
auto const& expected = header.value();
if (!compareResults(actual, expected, "response_header_fields")) {
no_failures = false;
}
}
}
return no_failures;
}

bool RouterCheckTool::compareResponseHeaderField(ToolConfig& tool_config, const std::string& field,
const std::string& expected) {
std::string actual = tool_config.response_headers_->get_(field);
return compareResults(actual, expected, "response_header_fields");
template <typename HeaderMap>
bool RouterCheckTool::matchHeaderField(const HeaderMap& header_map,
const envoy::config::route::v3::HeaderMatcher& header,
const std::string test_type) {
Envoy::Http::HeaderUtility::HeaderData expected_header_data{header};
if (Envoy::Http::HeaderUtility::matchHeaders(header_map, expected_header_data)) {
return true;
}

// Test failed. Decide on what to log.
kb000 marked this conversation as resolved.
Show resolved Hide resolved
std::string actual, expected;
std::string match_test_type{test_type + "." + ::toString(header.header_match_specifier_case())};
switch (header.header_match_specifier_case()) {
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kExactMatch:
actual =
header.name() + ": " + ::toString(header_map.get(Http::LowerCaseString(header.name())));
expected = header.name() + ": " + header.exact_match();
reportFailure(actual, expected, match_test_type, !header.invert_match());
break;
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::kPresentMatch:
case envoy::config::route::v3::HeaderMatcher::HeaderMatchSpecifierCase::
HEADER_MATCH_SPECIFIER_NOT_SET:
actual = "has(" + header.name() + "):" + (header.invert_match() ? "true" : "false");
expected = "has(" + header.name() + "):" + (header.invert_match() ? "false" : "true");
reportFailure(actual, expected, match_test_type);
break;
default:
actual =
header.name() + ": " + ::toString(header_map.get(Http::LowerCaseString(header.name())));
tests_.back().second.emplace_back("actual: [" + actual + "], test type: " + match_test_type);
break;
}

return false;
}

bool RouterCheckTool::compareResults(const std::string& actual, const std::string& expected,
const std::string& test_type) {
if (expected == actual) {
return true;
const std::string& test_type, const bool expect_match) {
if ((expected == actual) != expect_match) {
reportFailure(actual, expected, test_type, expect_match);
return false;
}
tests_.back().second.emplace_back("expected: [" + expected + "], actual: [" + actual +
"], test type: " + test_type);
return false;
return true;
}

void RouterCheckTool::reportFailure(const std::string& actual, const std::string& expected,
const std::string& test_type, const bool expect_match) {
tests_.back().second.emplace_back("expected: [" + expected + "], " +
"actual: " + (expect_match ? "" : "NOT ") + "[" + actual +
"]," + " test type: " + test_type);
}

void RouterCheckTool::printResults() {
// Output failure details to stdout if details_ flag is set to true
for (const auto& test_result : tests_) {
// All test names are printed if the details_ flag is true unless only_show_failures_ is also
// true.
// All test names are printed if the details_ flag is true unless only_show_failures_ is
// also true.
if ((details_ && !only_show_failures_) ||
(only_show_failures_ && !test_result.second.empty())) {
std::cout << test_result.first << std::endl;
for (const auto& failure : test_result.second) {
std::cerr << failure << std::endl;
if (test_result.second.empty()) {
std::cout << test_result.first << std::endl;
} else {
std::cerr << test_result.first << std::endl;
for (const auto& failure : test_result.second) {
std::cerr << failure << std::endl;
}
}
}
}
Expand Down
26 changes: 18 additions & 8 deletions test/tools/router_check/router.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ class RouterCheckTool : Logger::Loggable<Logger::Id::testing> {
*/
void finalizeHeaders(ToolConfig& tool_config, Envoy::StreamInfo::StreamInfoImpl stream_info);

/*
* Performs direct-response reply actions for a response entry.
*/
void sendLocalReply(ToolConfig& tool_config, const Router::DirectResponseEntry& entry);

bool compareCluster(ToolConfig& tool_config, const std::string& expected);
bool compareCluster(ToolConfig& tool_config,
const envoy::RouterCheckToolSchema::ValidationAssert& expected);
Expand All @@ -129,23 +134,28 @@ class RouterCheckTool : Logger::Loggable<Logger::Id::testing> {
bool compareRedirectPath(ToolConfig& tool_config, const std::string& expected);
bool compareRedirectPath(ToolConfig& tool_config,
const envoy::RouterCheckToolSchema::ValidationAssert& expected);
bool compareRequestHeaderField(ToolConfig& tool_config, const std::string& field,
const std::string& expected);
bool compareRequestHeaderField(ToolConfig& tool_config,
const envoy::RouterCheckToolSchema::ValidationAssert& expected);
bool compareResponseHeaderField(ToolConfig& tool_config, const std::string& field,
const std::string& expected);
bool compareResponseHeaderField(ToolConfig& tool_config,
bool compareRequestHeaderFields(ToolConfig& tool_config,
const envoy::RouterCheckToolSchema::ValidationAssert& expected);
bool compareResponseHeaderFields(ToolConfig& tool_config,
const envoy::RouterCheckToolSchema::ValidationAssert& expected);
template <typename HeaderMap>
bool matchHeaderField(const HeaderMap& header_map,
const envoy::config::route::v3::HeaderMatcher& header,
const std::string test_type);

/**
* Compare the expected and actual route parameter values. Print out match details if details_
* flag is set.
* @param actual holds the actual route returned by the router.
* @param expected holds the expected parameter value of the route.
* @param expect_match negates the expectation if false.
* @return bool if actual and expected match.
*/
bool compareResults(const std::string& actual, const std::string& expected,
const std::string& test_type);
const std::string& test_type, const bool expect_match = true);

void reportFailure(const std::string& actual, const std::string& expected,
const std::string& test_type, const bool expect_match = true);

void printResults();

Expand Down
Loading