Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
246 changes: 86 additions & 160 deletions test/integration/circuit_breakers_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -196,117 +196,45 @@ TEST_P(CircuitBreakersIntegrationTest, CircuitBreakerRuntimeProto) {
#endif
}

class OutlierDetectionIntegrationTest : public HttpProtocolIntegrationTest {
// Parameterized integration test. The parameters in the tuple are:
// - name of the test
// - basic (legacy) outlier detection config snippet
// - outlier detection extension config snippet to be added to the cluster
// - response code to be sent from upstream
// - optional response header value
class OutlierDetectionIntegrationTest
: public HttpProtocolIntegrationTestWithParams<std::tuple<
std::string, absl::string_view, absl::string_view, uint32_t, absl::string_view>> {
Comment on lines +206 to +207
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this a struct rather than a tuple with explanatory comments and const indices.

Or, better for this purpose, don't use parameterized testing for this at all - it's much clearer to just have member variables in the test class, so instead of something like

TEST_P(OutlierDetectionIntegrationTest, TestEverything) {
  auto x = GetParam(1).x;
  auto y = GetParam(1).y;
  auto foo = initializeXY(x, y);
  auto expected = GetParam(1).expected;
  EXPECT_THAT(doStuff(foo), Eq(expected));
}

INSTANTIATE_TEST_SUITE_P(
  TestArgStruct{"1And2ResultsIn3", 1, 2, 3},
  TestArgStruct{"4And5ResultsIn9", 4, 5, 9},
);

you just do

TEST_F(OutlierDetectionIntegrationTest, Test1and2ResultsIn3) {
  auto foo = initializeXY(1, 2);
  EXPECT_THAT(doStuff(foo), Eq(3));
}

TEST_F(OutlierDetectionIntegrationTest, Test4and5ResultsIn9) {
  auto foo = initializeXY(4, 5);
  EXPECT_THAT(doStuff(foo), Eq(9));
}

Even though if there are many tests this may result in more lines (it typically doesn't actually because the fields usually end up one line each, and passing the same values into helper functions is also usually one line), it makes for test code that's dramatically easier to read and understand, and you don't need to include special stringification functions and struct/tuple definitions etc.

Helper functions in the test class, member variables in the test class for persistent stuff, and custom matchers, will typically be fewer lines overall than a monolithic "do all the same stuff with different inputs" function - you can still put all the repetitive stuff inside helper functions.

In general if you use TEST_P with only one test, you shouldn't be using TEST_P - it's useful for the combinatorial thing where you want to run multiple tests with a range of configuration states.

public:
void initialize() override { HttpProtocolIntegrationTest::initialize(); }
};
static constexpr size_t TEST_NAME = 0;
static constexpr size_t BASIC_OD_CONFIG = 1;
static constexpr size_t EXT_OD_CONFIG = 2;
static constexpr size_t RESPONSE_CODE = 3;
static constexpr size_t OPTIONAL_HEADER = 4;

INSTANTIATE_TEST_SUITE_P(
Protocols, OutlierDetectionIntegrationTest,
testing::ValuesIn(HttpProtocolIntegrationTest::getProtocolTestParamsWithoutHTTP3()),
static std::string protocolTestParamsToString(const ::testing::TestParamInfo<ParamType>& params) {

HttpProtocolIntegrationTest::protocolTestParamsToString);

// Test verifies that empty outlier detection setting in protocol options
// do not interfere with existing outlier detection.
TEST_P(OutlierDetectionIntegrationTest, NoClusterOverwrite) {
config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
auto* static_resources = bootstrap.mutable_static_resources();
auto* cluster = static_resources->mutable_clusters(0);

cluster->mutable_common_lb_config()->mutable_healthy_panic_threshold()->set_value(0);

auto* outlier_detection = cluster->mutable_outlier_detection();

TestUtility::loadFromYaml(R"EOF(
return absl::StrCat(
HttpProtocolIntegrationTestBase::testNameFromTestParams(std::get<0>(params.param)), "_",
std::get<TEST_NAME>(std::get<1>(params.param)));
}
// Set of basic outlier detection configs.
static constexpr absl::string_view consecutive_5xx_only = R"EOF(
consecutive_5xx: 2
max_ejection_percent: 100
)EOF",
*outlier_detection);

ConfigHelper::HttpProtocolOptions protocol_options;
std::string protocol_options_yaml;
if (absl::StrContains(::testing::UnitTest::GetInstance()->current_test_info()->name(),
"Http2Upstream")) {
protocol_options_yaml += R"EOF(
explicit_http_config:
http2_protocol_options: {}
)EOF";
} else {
ASSERT(absl::StrContains(::testing::UnitTest::GetInstance()->current_test_info()->name(),
"HttpUpstream"));
protocol_options_yaml += R"EOF(
explicit_http_config:
http_protocol_options: {}
)EOF";
}
TestUtility::loadFromYaml(protocol_options_yaml, protocol_options);
ConfigHelper::setProtocolOptions(*bootstrap.mutable_static_resources()->mutable_clusters(0),
protocol_options);
});

initialize();

codec_client_ = makeHttpConnection(lookupPort("http"));
// return en error from upstream server.
default_response_headers_.setStatus(500);
for (auto i = 1; i <= 2; i++) {
auto response =
sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0);

ASSERT_TRUE(response->waitForEndStream());
// 500 error should be propagated to downstream client.
EXPECT_EQ("500", response->headers().getStatusValue());
}

// Send another request. It should not reach upstream and should be handled by envoy.
// The only existing endpoint in the cluster has been marked as unhealthy.
IntegrationStreamDecoderPtr response =
codec_client_->makeHeaderOnlyRequest(default_request_headers_);
ASSERT_TRUE(response->waitForEndStream());
EXPECT_TRUE(response->complete());
EXPECT_EQ("503", response->headers().getStatusValue());

codec_client_->close();
}

// Test verifies that non-5xx codes defined in cluster's protocol options
// are thread as errors and cause outlier detection to mark a host as unhealthy.
TEST_P(OutlierDetectionIntegrationTest, ClusterOverwriteNon5xxAsErrors) {
config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
auto* static_resources = bootstrap.mutable_static_resources();
auto* cluster = static_resources->mutable_clusters(0);

cluster->mutable_common_lb_config()->mutable_healthy_panic_threshold()->set_value(0);

auto* outlier_detection = cluster->mutable_outlier_detection();

TestUtility::loadFromYaml(R"EOF(
consecutive_5xx: 2
static constexpr absl::string_view gateway_only = R"EOF(
consecutive_5xx: 0
consecutive_gateway_failure: 2
enforcing_consecutive_gateway_failure: 100
max_ejection_percent: 100
)EOF",
*outlier_detection);

ConfigHelper::HttpProtocolOptions protocol_options;
std::string protocol_options_yaml;
if (absl::StrContains(::testing::UnitTest::GetInstance()->current_test_info()->name(),
"Http2Upstream")) {
protocol_options_yaml += R"EOF(
explicit_http_config:
http2_protocol_options: {}
)EOF";
} else {
ASSERT(absl::StrContains(::testing::UnitTest::GetInstance()->current_test_info()->name(),
"HttpUpstream"));
protocol_options_yaml += R"EOF(
explicit_http_config:
http_protocol_options: {}
)EOF";
}
)EOF";

// Configure any response with code 300-305 or response test-header containing
// string "treat-as-error" to be treated as 5xx code.
protocol_options_yaml += R"EOF(
// set of different outlier detection extension configs.
// Configure any response with code 300-305 or response test-header containing
// string "treat-as-error" to be treated as 5xx code.
static constexpr absl::string_view error_3xx_and_header = R"EOF(
outlier_detection:
error_matcher:
or_match:
Expand All @@ -322,46 +250,25 @@ TEST_P(OutlierDetectionIntegrationTest, ClusterOverwriteNon5xxAsErrors) {
- name: "test-header"
string_match:
contains: "treat-as-error"
)EOF",

TestUtility::loadFromYaml(protocol_options_yaml, protocol_options);
ConfigHelper::setProtocolOptions(*bootstrap.mutable_static_resources()->mutable_clusters(0),
protocol_options);
});

initialize();

codec_client_ = makeHttpConnection(lookupPort("http"));

// respond with status code 301. It should be treated as error.
default_response_headers_.setStatus(301);
auto response =
sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0);
ASSERT_TRUE(response->waitForEndStream());
EXPECT_EQ("301", response->headers().getStatusValue());

// Respond with status code 200 with "test-header".
// It should be treated as error.
default_response_headers_.setStatus(200);
default_response_headers_.appendCopy(Http::LowerCaseString("test-header"), "treat-as-error");

response =
sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0);
ASSERT_TRUE(response->waitForEndStream());
EXPECT_EQ("200", response->headers().getStatusValue());
)EOF";

// Now send a request. It will be captured by Envoy and 503 will be returned as the only upstream
// is unhealthy now..
response = codec_client_->makeHeaderOnlyRequest(default_request_headers_);
ASSERT_TRUE(response->waitForEndStream());
EXPECT_TRUE(response->complete());
EXPECT_EQ("503", response->headers().getStatusValue());
static constexpr absl::string_view gateway_error = R"EOF(
outlier_detection:
error_matcher:
http_response_headers_match:
headers:
- name: ":status"
range_match:
start: 502
end: 503
)EOF";
};

codec_client_->close();
}
// Test verifies that 5xx gateway errors configured in cluster protocol options are
// forwarded to outlier detection in the original form and are not converted to code 500.
TEST_P(OutlierDetectionIntegrationTest, ClusterOverwriteGatewayErrors) {
// Test configures outlier detection extensions in cluster.
// Then it sends several http requests and responses.
// Responses match configured outlier extensions and cause the endpoint to be marked
// as unhealthy.
TEST_P(OutlierDetectionIntegrationTest, ExtensionsTest) {
config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) {
auto* static_resources = bootstrap.mutable_static_resources();
auto* cluster = static_resources->mutable_clusters(0);
Expand All @@ -370,12 +277,7 @@ TEST_P(OutlierDetectionIntegrationTest, ClusterOverwriteGatewayErrors) {

auto* outlier_detection = cluster->mutable_outlier_detection();

TestUtility::loadFromYaml(R"EOF(
consecutive_5xx: 0
consecutive_gateway_failure: 2
enforcing_consecutive_gateway_failure: 100
max_ejection_percent: 100
)EOF",
TestUtility::loadFromYaml(std::string(std::get<BASIC_OD_CONFIG>(std::get<1>(GetParam()))),
*outlier_detection);

ConfigHelper::HttpProtocolOptions protocol_options;
Expand All @@ -395,36 +297,35 @@ TEST_P(OutlierDetectionIntegrationTest, ClusterOverwriteGatewayErrors) {
)EOF";
}

protocol_options_yaml += R"EOF(
outlier_detection:
error_matcher:
http_response_headers_match:
headers:
- name: ":status"
range_match:
start: 502
end: 503
)EOF",
// Add config outlier detection extension config
protocol_options_yaml += std::get<EXT_OD_CONFIG>(std::get<1>(GetParam()));

TestUtility::loadFromYaml(protocol_options_yaml, protocol_options);
TestUtility::loadFromYaml(protocol_options_yaml, protocol_options);
ConfigHelper::setProtocolOptions(*bootstrap.mutable_static_resources()->mutable_clusters(0),
protocol_options);
});

initialize();

codec_client_ = makeHttpConnection(lookupPort("http"));
default_response_headers_.setStatus(502);
// Set the response code.
const uint32_t response_code = std::get<RESPONSE_CODE>(std::get<1>(GetParam()));
default_response_headers_.setStatus(response_code);
// Add header if test parameter includes one.
if (!std::get<OPTIONAL_HEADER>(std::get<1>(GetParam())).empty()) {
default_response_headers_.appendCopy(Http::LowerCaseString("test-header"),
std::get<OPTIONAL_HEADER>(std::get<1>(GetParam())));
}
for (auto i = 1; i <= 2; i++) {
auto response =
sendRequestAndWaitForResponse(default_request_headers_, 0, default_response_headers_, 0);

ASSERT_TRUE(response->waitForEndStream());
EXPECT_EQ("502", response->headers().getStatusValue());
EXPECT_EQ(std::to_string(response_code), response->headers().getStatusValue());
}

// Now send a request. It will be captured by Envoy and 503 will be returned as the only upstream
// is unhealthy now..
// is unhealthy now.
IntegrationStreamDecoderPtr response =
codec_client_->makeHeaderOnlyRequest(default_request_headers_);
ASSERT_TRUE(response->waitForEndStream());
Expand All @@ -434,5 +335,30 @@ TEST_P(OutlierDetectionIntegrationTest, ClusterOverwriteGatewayErrors) {
codec_client_->close();
}

INSTANTIATE_TEST_SUITE_P(
Protocols, OutlierDetectionIntegrationTest,
testing::Combine(
testing::ValuesIn(HttpProtocolIntegrationTestBase::getProtocolTestParamsWithoutHTTP3()),
testing::Values(
// Regression test. Test verifies that empty outlier detection setting in protocol
// options do not interfere with existing outlier detection.
std::make_tuple(std::string("test1"),
OutlierDetectionIntegrationTest::consecutive_5xx_only, "", 500, ""),
// In this test, outlier extensions define 3xx codes as errors.
std::make_tuple(std::string("test2"),
OutlierDetectionIntegrationTest::consecutive_5xx_only,
OutlierDetectionIntegrationTest::error_3xx_and_header, 301, ""),
// Test verifies that when outlier extensions include gateway code 502, that code
// is forwarded in original form and is not converted to generic code 500.
std::make_tuple(std::string("test3"), OutlierDetectionIntegrationTest::gateway_only,
OutlierDetectionIntegrationTest::gateway_error, 502, ""),
// Test verifies that responses where a matcher matches not code, but response
// header are treated as errors.
std::make_tuple(std::string("test4"),
OutlierDetectionIntegrationTest::consecutive_5xx_only,
OutlierDetectionIntegrationTest::error_3xx_and_header, 200,
"treat-as-error"))),
OutlierDetectionIntegrationTest::protocolTestParamsToString);

} // namespace
} // namespace Envoy
23 changes: 14 additions & 9 deletions test/integration/http_protocol_integration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "absl/strings/str_cat.h"

namespace Envoy {
std::vector<HttpProtocolTestParams> HttpProtocolIntegrationTest::getProtocolTestParams(
std::vector<HttpProtocolTestParams> HttpProtocolIntegrationTestBase::getProtocolTestParams(
const std::vector<Http::CodecType>& downstream_protocols,
const std::vector<Http::CodecType>& upstream_protocols) {
std::vector<HttpProtocolTestParams> ret;
Expand Down Expand Up @@ -54,16 +54,21 @@ std::vector<HttpProtocolTestParams> HttpProtocolIntegrationTest::getProtocolTest
return ret;
}

std::string HttpProtocolIntegrationTest::protocolTestParamsToString(
std::string
HttpProtocolIntegrationTestBase::testNameFromTestParams(const HttpProtocolTestParams& params) {
return absl::StrCat((params.version == Network::Address::IpVersion::v4 ? "IPv4_" : "IPv6_"),
downstreamToString(params.downstream_protocol),
upstreamToString(params.upstream_protocol),
http2ImplementationToString(params.http2_implementation),
params.use_universal_header_validator ? "Uhv" : "Legacy");
}

std::string HttpProtocolIntegrationTestBase::protocolTestParamsToString(
const ::testing::TestParamInfo<HttpProtocolTestParams>& params) {
return absl::StrCat((params.param.version == Network::Address::IpVersion::v4 ? "IPv4_" : "IPv6_"),
downstreamToString(params.param.downstream_protocol),
upstreamToString(params.param.upstream_protocol),
http2ImplementationToString(params.param.http2_implementation),
params.param.use_universal_header_validator ? "Uhv" : "Legacy");
return testNameFromTestParams(params.param);
}

void HttpProtocolIntegrationTest::setUpstreamOverrideStreamErrorOnInvalidHttpMessage() {
void HttpProtocolIntegrationTestBase::setUpstreamOverrideStreamErrorOnInvalidHttpMessage() {
config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void {
RELEASE_ASSERT(bootstrap.mutable_static_resources()->clusters_size() >= 1, "");
ConfigHelper::HttpProtocolOptions protocol_options;
Expand All @@ -83,7 +88,7 @@ void HttpProtocolIntegrationTest::setUpstreamOverrideStreamErrorOnInvalidHttpMes
});
}

void HttpProtocolIntegrationTest::setDownstreamOverrideStreamErrorOnInvalidHttpMessage() {
void HttpProtocolIntegrationTestBase::setDownstreamOverrideStreamErrorOnInvalidHttpMessage() {
config_helper_.addConfigModifier(
[](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager&
hcm) -> void {
Expand Down
Loading