Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
503e8c6
test-server: dynamic delay filter extension [DRAFT]
oschaaf Jun 29, 2020
b44cf2c
Merge remote-tracking branch 'upstream/master' into test-server-dynam…
oschaaf Jun 29, 2020
159599f
Fix tests, improve naming
oschaaf Jun 29, 2020
3ec87c0
s/guage/gauge/
oschaaf Jun 29, 2020
277c786
Review feedback
oschaaf Jun 29, 2020
a34b8cd
Merge remote-tracking branch 'upstream/master' into test-server-dynam…
oschaaf Jun 29, 2020
5d6fb7e
Review feedback + tests + some polish
oschaaf Jul 1, 2020
d7bfb51
Add TODO, cover bad header config in test.
oschaaf Jul 1, 2020
caf8c78
Enhance testing, (doc) comments, and assert on assumptions
oschaaf Jul 2, 2020
aeddcdd
Add override keyword to destructor
oschaaf Jul 2, 2020
b1069da
Review feedback: fix atomic initialization & remove "using namespace …
oschaaf Jul 3, 2020
e0f88a0
clang-tidy: add NOLINT for MUTABLE_CONSTRUCT_ON_FIRST_USE
oschaaf Jul 3, 2020
c40e9ce
Partial review feedback
oschaaf Jul 6, 2020
6113345
save state
oschaaf Jul 7, 2020
1cd6cd7
Merge remote-tracking branch 'upstream/master' into test-server-dynam…
oschaaf Jul 8, 2020
8ee78db
review feedback
oschaaf Jul 8, 2020
3f006f6
Move comment out of method body to fix coverage
oschaaf Jul 8, 2020
3a875cd
Merge commit '611334586db1c81d7e2c97875b769c867e8c6a95' into test-ser…
oschaaf Jul 9, 2020
3e3c6ef
save state: Review feedback + fault filter base
oschaaf Jul 9, 2020
ac456f3
Update documentation & fix destructor
oschaaf Jul 9, 2020
9be5334
Partial review feedback
oschaaf Jul 13, 2020
ad5e34c
Review feedback
oschaaf Jul 13, 2020
3c80a0b
Fix missing dependency
oschaaf Jul 13, 2020
8f74535
clang-tidy: fix include
oschaaf Jul 13, 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
1 change: 1 addition & 0 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ envoy_cc_binary(
name = "nighthawk_test_server",
repository = "@envoy",
deps = [
"//source/server:http_dynamic_delay_filter_config",
"//source/server:http_test_server_filter_config",
"@envoy//source/exe:envoy_main_entry_lib",
],
Expand Down
20 changes: 19 additions & 1 deletion api/server/response_options.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,31 @@ package nighthawk.server;
import "google/protobuf/wrappers.proto";
import "validate/validate.proto";
import "envoy/api/v2/core/base.proto";
import "google/protobuf/duration.proto";

// Options that control the test server response.
message ConcurrencyBasedLinearDelay {
// Minimal delay to add to replies.
google.protobuf.Duration minimal_delay = 1 [(validate.rules).duration.gte.nanos = 0];
// Factor to use when adding latency as concurrency increases.
google.protobuf.Duration concurrency_delay_factor = 2 [(validate.rules).duration.gte.nanos = 0];
}

// Options that control the test server response. Can be provided via request
// headers as well as via static file-based configuration. In case both are
// provided, a merge will happen, in which case the header-provided
// configuration will override.
message ResponseOptions {
// List of additional response headers.
repeated envoy.api.v2.core.HeaderValueOption response_headers = 1;
// Number of 'a' characters in the the response body.
uint32 response_body_size = 2 [(validate.rules).uint32 = {lte: 4194304}];
// If true, then echo request headers in the response body.
bool echo_request_headers = 3;

oneof oneof_delay_options {
// Static delay duration.
google.protobuf.Duration static_delay = 4 [(validate.rules).duration.gte.nanos = 0];
// Concurrency based linear delay configuration.
ConcurrencyBasedLinearDelay concurrency_based_linear_delay = 5;
}
}
51 changes: 49 additions & 2 deletions source/server/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,53 @@ licenses(["notice"]) # Apache 2

envoy_package()

envoy_cc_library(
name = "well_known_headers_lib",
hdrs = ["well_known_headers.h"],
repository = "@envoy",
deps = [
"@envoy//source/common/http:headers_lib_with_external_headers",
"@envoy//source/common/singleton:const_singleton_with_external_headers",
],
)

envoy_cc_library(
name = "configuration_lib",
srcs = ["configuration.cc"],
hdrs = ["configuration.h"],
repository = "@envoy",
deps = [
"//api/server:response_options_proto_cc_proto",
"@envoy//source/common/protobuf:message_validator_lib_with_external_headers",
"@envoy//source/common/protobuf:utility_lib_with_external_headers",
"@envoy//source/common/singleton:const_singleton_with_external_headers",
],
)

envoy_cc_library(
name = "http_test_server_filter_lib",
srcs = ["http_test_server_filter.cc"],
hdrs = ["http_test_server_filter.h"],
repository = "@envoy",
deps = [
":configuration_lib",
":well_known_headers_lib",
"//api/server:response_options_proto_cc_proto",
"@envoy//source/exe:envoy_common_lib_with_external_headers",
],
)

envoy_cc_library(
name = "http_dynamic_delay_filter_lib",
srcs = ["http_dynamic_delay_filter.cc"],
hdrs = ["http_dynamic_delay_filter.h"],
repository = "@envoy",
deps = [
":configuration_lib",
":well_known_headers_lib",
"//api/server:response_options_proto_cc_proto",
"@envoy//source/common/protobuf:message_validator_lib_with_external_headers",
"@envoy//source/common/protobuf:utility_lib_with_external_headers",
"@envoy//source/exe:envoy_common_lib_with_external_headers",
"@envoy//source/extensions/filters/http/fault:fault_filter_lib",
],
)

Expand All @@ -30,3 +67,13 @@ envoy_cc_library(
"@envoy//include/envoy/server:filter_config_interface_with_external_headers",
],
)

envoy_cc_library(
name = "http_dynamic_delay_filter_config",
srcs = ["http_dynamic_delay_filter_config.cc"],
repository = "@envoy",
deps = [
":http_dynamic_delay_filter_lib",
"@envoy//include/envoy/server:filter_config_interface_with_external_headers",
],
)
109 changes: 61 additions & 48 deletions source/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,48 +16,46 @@ bazel build -c opt :nighthawk_test_server

## Configuring the test server


`test-server.yaml` sample content

```yaml
static_resources:
listeners:
# define an origin server on :10000 that always returns "lorem ipsum..."
- address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
generate_request_id: false
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: service
domains:
- "*"
http_filters:
- name: envoy.fault
config:
max_active_faults: 100
delay:
header_delay: {}
percentage:
numerator: 100
- name: test-server # before envoy.router because order matters!
config:
response_body_size: 10
response_headers:
- { header: { key: "foo", value: "bar"} }
- { header: { key: "foo", value: "bar2"}, append: true }
- { header: { key: "x-nh", value: "1"}}
- name: envoy.router
config:
dynamic_stats: false
# define an origin server on :10000 that always returns "lorem ipsum..."
- address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
generate_request_id: false
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: service
domains:
- "*"
http_filters:
- name: dynamic-delay
config:
static_delay: 0.5s
- name: test-server # before envoy.router because order matters!
config:
response_body_size: 10
response_headers:
- { header: { key: "foo", value: "bar" } }
- {
header: { key: "foo", value: "bar2" },
append: true,
}
- { header: { key: "x-nh", value: "1" } }
- name: envoy.router
config:
dynamic_stats: false
admin:
access_log_path: /tmp/envoy.log
address:
Expand All @@ -68,21 +66,23 @@ admin:

## Response Options config

The ResponseOptions proto can be used in the test-server filter config or passed in `x-nighthawk-test-server-config``
request header.
The [ResponseOptions proto](/api/server/response_options.proto) is shared by
the `Test Server` and `Dynamic Delay` filter extensions. Each filter will
interpret the parts that are relevant to it. This allows specifying what
a response should look like in a single message, which can be done at request
time via the optional `x-nighthawk-test-server-config` request-header.

The following parameters are available:
### Test Server

* `response_body_size` - number of 'a' characters repeated in the response body.
* `response_headers` - list of headers to add to response. If `append` is set to
- `response_body_size` - number of 'a' characters repeated in the response body.
- `response_headers` - list of headers to add to response. If `append` is set to
`true`, then the header is appended.
* `echo_request_headers` - if set to `true`, then append the dump of request headers to the response
- `echo_request_headers` - if set to `true`, then append the dump of request headers to the response
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should add a bullet here for oneof_delay_options, which is your new field in ResponseOptions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think you missed this, unless this is dependent on the result of the larger thread about the use of the fault filter.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The later revisions have the new options documented in a new section right below this one, does that work for you?

body.

The response options could be used to test and debug proxy or server configuration, for
example, to verify request headers that are added by intermediate proxy:
The response options above could be used to test and debug proxy or server configuration, for example, to verify request headers that are added by intermediate proxy:

```
```bash
$ curl -6 -v [::1]:8080/nighthawk

* Trying ::1:8080...
Expand Down Expand Up @@ -122,15 +122,28 @@ Request Headers:
This example shows that intermediate proxy has added `x-forwarded-proto` and
`x-forwarded-for` request headers.

## Running the test server
### Dynamic Delay

The Dynamic Delay interprets the `oneof_delay_options` part in the [ResponseOptions proto](/api/server/response_options.proto). If specified, it can be used to:

- Configure a static delay via `static_delay`.
- Configure a delay which linearly increase as the number of active requests grows, representing a simplified model of an overloaded server, via `concurrency_based_linear_delay`.

All delays have a millisecond-level granularity.

At the time of writing this, there is a [known issue](https://github.com/envoyproxy/nighthawk/issues/392) with merging configuration provided via
request headers into the statically configured configuration. The current recommendation is to
use either static, or dynamic configuration (delivered per request header), but not both at the
same time.

## Running the test server

```
# If you already have Envoy running, you might need to set --base-id to allow the test-server to start.
➜ /bazel-bin/nighthawk/source/server/server --config-path /path/to/test-server-server.yaml

# Verify the test server with a curl command similar to:
➜ curl -H "x-envoy-fault-delay-request: 1000" -H "x-nighthawk-test-server-config: {response_body_size:20}" -vv 127.0.0.1:10000
➜ curl -H "x-nighthawk-test-server-config: {response_body_size:20, static_delay: \"0s\"}" -vv 127.0.0.1:10000
```

```bash
Expand Down
45 changes: 45 additions & 0 deletions source/server/configuration.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "server/configuration.h"

#include <string>

#include "external/envoy/source/common/protobuf/message_validator_impl.h"
#include "external/envoy/source/common/protobuf/utility.h"

#include "api/server/response_options.pb.validate.h"

#include "absl/strings/numbers.h"

namespace Nighthawk {
namespace Server {
namespace Configuration {

bool mergeJsonConfig(absl::string_view json, nighthawk::server::ResponseOptions& config,
std::string& error_message) {
error_message = "";
try {
nighthawk::server::ResponseOptions json_config;
auto& validation_visitor = Envoy::ProtobufMessage::getStrictValidationVisitor();
Envoy::MessageUtil::loadFromJson(std::string(json), json_config, validation_visitor);
config.MergeFrom(json_config);
Envoy::MessageUtil::validate(config, validation_visitor);
} catch (const Envoy::EnvoyException& exception) {
error_message = fmt::format("Error merging json config: {}", exception.what());
}
return error_message == "";
}

void applyConfigToResponseHeaders(Envoy::Http::ResponseHeaderMap& response_headers,
nighthawk::server::ResponseOptions& response_options) {
for (const auto& header_value_option : response_options.response_headers()) {
const auto& header = header_value_option.header();
auto lower_case_key = Envoy::Http::LowerCaseString(header.key());
if (!header_value_option.append().value()) {
response_headers.remove(lower_case_key);
}
response_headers.addCopy(lower_case_key, header.value());
}
}

} // namespace Configuration
} // namespace Server
} // namespace Nighthawk
36 changes: 36 additions & 0 deletions source/server/configuration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#pragma once

#include <string>

#include "envoy/http/header_map.h"

#include "api/server/response_options.pb.h"

namespace Nighthawk {
namespace Server {
namespace Configuration {

/**
* Merges a json string containing configuration into a ResponseOptions instance.
*
* @param json Json-formatted seralization of ResponseOptions to merge into the configuration.
* @param config The target that the json string should be merged into.
* @param error_message Set to an error message if one occurred, else set to an empty string.
* @return bool false if an error occurred.
*/
bool mergeJsonConfig(absl::string_view json, nighthawk::server::ResponseOptions& config,
std::string& error_message);

/**
* Applies ResponseOptions onto a HeaderMap containing response headers.
*
* @param response_headers Response headers to transform to reflect the passed in response
* options.
* @param response_options Configuration specifying how to transform the header map.
*/
void applyConfigToResponseHeaders(Envoy::Http::ResponseHeaderMap& response_headers,
nighthawk::server::ResponseOptions& response_options);

} // namespace Configuration
} // namespace Server
} // namespace Nighthawk
Loading