diff --git a/DEVELOPER.md b/DEVELOPER.md index 6044f98cbb75..904e9432b8b9 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -24,6 +24,8 @@ Below is a list of additional documentation to aid the development process: - [Envoy filter example project (how to consume and extend Envoy as a submodule)](https://github.com/envoyproxy/envoy-filter-example) +- [Performance testing Envoy with `tcmalloc`/`pprof`](https://github.com/envoyproxy/envoy/tree/bazel/PPROF.md) + And some documents on components of Envoy architecture: - [Envoy flow control](https://github.com/envoyproxy/envoy/blob/master/source/docs/flow_control.md) diff --git a/api/bazel/api_build_system.bzl b/api/bazel/api_build_system.bzl index 497d82c5ccc0..af275ec53c87 100644 --- a/api/bazel/api_build_system.bzl +++ b/api/bazel/api_build_system.bzl @@ -88,7 +88,16 @@ def api_proto_library_internal(visibility = ["//visibility:private"], **kwargs): # gRPC stub generation. # TODO(htuch): Automatically generate go_proto_library and go_grpc_library # from api_proto_library. -def api_proto_library(name, visibility = ["//visibility:private"], srcs = [], deps = [], has_services = 0, require_py = 1): +def api_proto_library( + name, + visibility = ["//visibility:private"], + srcs = [], + deps = [], + external_proto_deps = [], + external_cc_proto_deps = [], + has_services = 0, + linkstatic = None, + require_py = 1): # This is now vestigial, since there are no direct consumers in # the data plane API. However, we want to maintain native proto_library support # in the proto graph to (1) support future C++ use of native rules with @@ -99,7 +108,7 @@ def api_proto_library(name, visibility = ["//visibility:private"], srcs = [], de native.proto_library( name = name, srcs = srcs, - deps = deps + [ + deps = deps + external_proto_deps + [ "@com_google_protobuf//:any_proto", "@com_google_protobuf//:descriptor_proto", "@com_google_protobuf//:duration_proto", @@ -107,7 +116,6 @@ def api_proto_library(name, visibility = ["//visibility:private"], srcs = [], de "@com_google_protobuf//:struct_proto", "@com_google_protobuf//:timestamp_proto", "@com_google_protobuf//:wrappers_proto", - "@googleapis//:api_httpbody_protos_proto", "@googleapis//:http_api_protos_proto", "@googleapis//:rpc_status_protos_lib", "@com_github_gogo_protobuf//:gogo_proto", @@ -123,8 +131,9 @@ def api_proto_library(name, visibility = ["//visibility:private"], srcs = [], de pgv_cc_proto_library( name = _Suffix(name, _CC_SUFFIX), srcs = srcs, + linkstatic = linkstatic, deps = [_LibrarySuffix(d, _CC_SUFFIX) for d in deps], - external_deps = [ + external_deps = external_cc_proto_deps + [ "@com_google_protobuf//:cc_wkt_protos", "@googleapis//:http_api_protos", "@googleapis//:rpc_status_protos", @@ -132,8 +141,19 @@ def api_proto_library(name, visibility = ["//visibility:private"], srcs = [], de ], visibility = ["//visibility:public"], ) + py_export_suffixes = [] if (require_py == 1): api_py_proto_library(name, srcs, deps, has_services) + py_export_suffixes = ["_py", "_py_genproto"] + + # Allow unlimited visibility for consumers + export_suffixes = ["", "_cc", "_cc_validate", "_cc_proto", "_cc_proto_genproto"] + py_export_suffixes + for s in export_suffixes: + native.alias( + name = name + "_export" + s, + actual = name + s, + visibility = ["//visibility:public"], + ) def api_cc_test(name, srcs, proto_deps): native.cc_test( diff --git a/api/envoy/api/v2/route/route.proto b/api/envoy/api/v2/route/route.proto index a497a1871f9e..ec66094f4d4c 100644 --- a/api/envoy/api/v2/route/route.proto +++ b/api/envoy/api/v2/route/route.proto @@ -267,7 +267,7 @@ message RouteMatch { // * The regex */b[io]t* matches the path */bot* // * The regex */b[io]t* does not match the path */bite* // * The regex */b[io]t* does not match the path */bit/bot* - string regex = 3; + string regex = 3 [(validate.rules).string.max_bytes = 1024]; } // Indicates that prefix/path matching should be case insensitive. The default @@ -310,7 +310,7 @@ message CorsPolicy { // Specifies regex patterns that match allowed origins. // // An origin is allowed if either allow_origin or allow_origin_regex match. - repeated string allow_origin_regex = 8; + repeated string allow_origin_regex = 8 [(validate.rules).repeated .items.string.max_bytes = 1024]; // Specifies the content for the *access-control-allow-methods* header. string allow_methods = 2; @@ -338,7 +338,7 @@ message RouteAction { // Indicates the upstream cluster to which the request should be routed // to. - string cluster = 1; + string cluster = 1 [(validate.rules).string.min_bytes = 1]; // Envoy will determine the cluster to route to by reading the value of the // HTTP header named by cluster_header from the request headers. If the @@ -349,7 +349,7 @@ message RouteAction { // // Internally, Envoy always uses the HTTP/2 *:authority* header to represent the HTTP/1 // *Host* header. Thus, if attempting to match on *Host*, match on *:authority* instead. - string cluster_header = 2; + string cluster_header = 2 [(validate.rules).string.min_bytes = 1]; // Multiple upstream clusters can be specified for a given route. The // request is routed to one of the upstream clusters based on weights @@ -763,7 +763,7 @@ message VirtualCluster { // * The regex */rides/\d+* matches the path */rides/0* // * The regex */rides/\d+* matches the path */rides/123* // * The regex */rides/\d+* does not match the path */rides/123/456* - string pattern = 1 [(validate.rules).string.min_bytes = 1]; + string pattern = 1 [(validate.rules).string = {min_bytes: 1, max_bytes: 1024}]; // Specifies the name of the virtual cluster. The virtual cluster name as well // as the virtual host name are used when emitting statistics. The statistics are emitted by the @@ -959,7 +959,7 @@ message HeaderMatcher { // * The regex *\d{3}* matches the value *123* // * The regex *\d{3}* does not match the value *1234* // * The regex *\d{3}* does not match the value *123.456* - string regex_match = 5; + string regex_match = 5 [(validate.rules).string.max_bytes = 1024]; // If specified, header match will be performed based on range. // The rule will match if the request header value is within this range. @@ -1009,7 +1009,7 @@ message HeaderMatcher { message QueryParameterMatcher { // Specifies the name of a key that must be present in the requested // *path*'s query string. - string name = 1 [(validate.rules).string.min_bytes = 1]; + string name = 1 [(validate.rules).string = {min_bytes: 1, max_bytes: 1024}]; // Specifies the value of the key. If the value is absent, a request // that contains the key in its query string will match, whether the diff --git a/api/envoy/config/filter/accesslog/v2/accesslog.proto b/api/envoy/config/filter/accesslog/v2/accesslog.proto index 6642560694ea..fb1ec8e71afd 100644 --- a/api/envoy/config/filter/accesslog/v2/accesslog.proto +++ b/api/envoy/config/filter/accesslog/v2/accesslog.proto @@ -162,6 +162,6 @@ message ResponseFlagFilter { // This field is optional. If it is not specified, then any response flag will pass // the filter check. repeated string flags = 1 [(validate.rules).repeated .items.string = { - in: ["LH", "UH", "UT", "LR", "UR", "UF", "UC", "UO", "NR", "DI", "FI", "RL", "UAEX"] + in: ["LH", "UH", "UT", "LR", "UR", "UF", "UC", "UO", "NR", "DI", "FI", "RL", "UAEX", "RLSE"] }]; } diff --git a/api/envoy/config/filter/http/rate_limit/v2/rate_limit.proto b/api/envoy/config/filter/http/rate_limit/v2/rate_limit.proto index 9d3f50e7542b..d79764745ab7 100644 --- a/api/envoy/config/filter/http/rate_limit/v2/rate_limit.proto +++ b/api/envoy/config/filter/http/rate_limit/v2/rate_limit.proto @@ -34,4 +34,10 @@ message RateLimit { // The timeout in milliseconds for the rate limit service RPC. If not // set, this defaults to 20ms. google.protobuf.Duration timeout = 4 [(gogoproto.stdduration) = true]; + + // The filter's behaviour in case the rate limiting service does + // not respond back. When it is set to true, Envoy will not allow traffic in case of + // communication failure between rate limiting service and the proxy. + // Defaults to false. + bool failure_mode_deny = 5; } diff --git a/api/envoy/config/filter/network/rate_limit/v2/rate_limit.proto b/api/envoy/config/filter/network/rate_limit/v2/rate_limit.proto index 240129499ace..fe579f01e444 100644 --- a/api/envoy/config/filter/network/rate_limit/v2/rate_limit.proto +++ b/api/envoy/config/filter/network/rate_limit/v2/rate_limit.proto @@ -26,4 +26,10 @@ message RateLimit { // The timeout in milliseconds for the rate limit service RPC. If not // set, this defaults to 20ms. google.protobuf.Duration timeout = 4 [(gogoproto.stdduration) = true]; + + // The filter's behaviour in case the rate limiting service does + // not respond back. When it is set to true, Envoy will not allow traffic in case of + // communication failure between rate limiting service and the proxy. + // Defaults to false. + bool failure_mode_deny = 5; } diff --git a/api/envoy/config/metrics/v2/stats.proto b/api/envoy/config/metrics/v2/stats.proto index 121f59a9f85a..f3471643c93b 100644 --- a/api/envoy/config/metrics/v2/stats.proto +++ b/api/envoy/config/metrics/v2/stats.proto @@ -133,7 +133,7 @@ message TagSpecifier { // ``http.user_agent.downstream_cx_total`` as the tag extracted name. The tag // ``envoy.http_conn_manager_prefix`` will be added with the tag value // ``connection_manager_1``. - string regex = 2; + string regex = 2 [(validate.rules).string.max_bytes = 1024]; // Specifies a fixed tag value for the ``tag_name``. string fixed_value = 3; diff --git a/api/envoy/data/accesslog/v2/accesslog.proto b/api/envoy/data/accesslog/v2/accesslog.proto index 129e996d0afc..a289730b4587 100644 --- a/api/envoy/data/accesslog/v2/accesslog.proto +++ b/api/envoy/data/accesslog/v2/accesslog.proto @@ -184,6 +184,9 @@ message ResponseFlags { // Indicates if the request was deemed unauthorized and the reason for it. Unauthorized unauthorized_details = 13; + + // Indicates that the request was rejected because there was an error in rate limit service. + bool rate_limit_service_error = 14; } // [#not-implemented-hide:] diff --git a/api/envoy/type/matcher/string.proto b/api/envoy/type/matcher/string.proto index afb419a613b3..4fdea1f5859d 100644 --- a/api/envoy/type/matcher/string.proto +++ b/api/envoy/type/matcher/string.proto @@ -44,6 +44,6 @@ message StringMatcher { // * The regex *\d{3}* matches the value *123* // * The regex *\d{3}* does not match the value *1234* // * The regex *\d{3}* does not match the value *123.456* - string regex = 4; + string regex = 4 [(validate.rules).string.max_bytes = 1024]; } } diff --git a/bazel/PPROF.md b/bazel/PPROF.md new file mode 100644 index 000000000000..09e7982f2d87 --- /dev/null +++ b/bazel/PPROF.md @@ -0,0 +1,139 @@ +# Memory consumption testing with `pprof` + +To use `pprof` to analyze performance and memory consumption in Envoy, you can +use the built-in statically linked profiler, or dynamically link it in to a +specific place yourself. + +# Linking + +## Static Linking + +Static linking is already available (because of a `HeapProfilerDump()` call +inside +[`Envoy::Profiler::Heap::forceLink()`](https://github.com/envoyproxy/envoy/blob/master/source/common/profiler/profiler.cc#L21-L26)). + +### Compiling a statically-linked Envoy + +Build the static binary using bazel: + + $ bazel build //source/exe:envoy-static + +### Running a statically-linked Envoy with `pprof` + +And run the binary with a `HEAPPROFILE` environment variable, like so: + + $ HEAPPROFILE=/tmp/mybin.hprof bazel-bin/source/exe/envoy-static + +`HEAPPROFILE` sets a location for the profiler output. A statically-linked +binary must be run with this environment variable; a dynamically-linked binary +will populate the working directory by default. (See *Methodology*.) + +## Dynamic Linking + +### Adding `tcmalloc_dep` to Envoy + +A statically-linked Envoy will profile everything. In a dynamically-linked +Envoy, you must add the HeapProfiler instructions yourself. +`HeapProfilerStart()` will start recording allocations, `HeapProfilerStop()` +will stop recording, and `HeapProfilerDump()` will dump an output to the +specified directory. (See [Gperftools Heap +Profiler](https://gperftools.github.io/gperftools/heapprofile.html).) + +To add a `HeapProfiler` breakpoint yourself, add `tcmalloc` as a +dependency under the `envoy_cc_library` rule: + +`source/exe/BUILD` + +```c++ + envoy_cc_library( + name = "envoy_common_lib", ++ tcmalloc_dep = 1, + deps = [ + ... + ) +``` + +It is then necessary to add `HeapProfilerStart()` and `HeapProfilerDump()` +breakpoints somewhere in Envoy. One place to start profiling is at the +instantiation of `MainCommonBase::MainCommonBase`: + +`source/exe/main_common.cc` + +```c++ + // includes + #include "gperftools/heap-profiler.h" + ... + MainCommonBase::MainCommonBase(...) : ... { ++ HeapProfilerStart("main_common_base"); // first line + ... + } +``` + +`source/server/server.cc` + +```c++ + // includes + #include "gperftools/heap-profiler.h" + ... + void InstanceImpl::Initialize(...) : ... { + ... ++ HeapProfilerDump("main_common_base"); // last line + } +``` + +Once these changes have been made in your working directory, it might make sense to +save the diff as a patch (`git diff > file`), which can then be quickly +applied/unapplied for testing and commiting. (`git apply`, `git apply -R`) + +Build the binary using bazel, and run the binary without any environment variables: + + $ bazel build //source/exe:envoy + $ bazel-bin/source/exe/envoy + +This will dump your profiler output to the working directory. + +# Methodology + +For consistent testing, it makes sense to run Envoy for a constant amount of +time across trials: + + $ timeout bazel-bin/source/exe/envoy + +Envoy will print to stdout something like: + + Starting tracking the heap + +And then a series of stdouts like: + + Dumping heap profile to (100 MB currently in use) + Dumping heap profile to (200 MB currently in use) + ... + +This will generate a series of files; if you statically-linked, these are +wherever `HEAPPROFILE` points to. Otherwise, they are in the current directory +by default. They'll be named something like `main_common_base.0001.heap`, +`main_common_base.0002.heap`, etc. + +*NB:* There is no reason this needs to be titled `main_common_base`. Whatever +flag you supply `HeapProfilerStart` / `HeapProfilerDump` will become the +filename. Multiple sections of code could be profiled simultaneously by setting +multiple `HeapProfilerStart()` / `HeapProfilerStop()` breakpoints with unique +identifiers. + +# Analyzing with `pprof` + +[pprof](https://github.com/google/pprof) can read these heap files in a +number of ways. Most convenient for first-order inspection might be `pprof -top` +or `pprof -text`: + + $ pprof -text bazel-bin/source/exe/envoy main_common_base* | head -n5 + File: envoy + Build ID: ... + Type: inuse_space + Showing nodes accounting for 6402800.62kB, 98.59% of 6494044.58kB total + Dropped ... nodes (cum <= ...kB) + +More complex flame/graph charts can be generated and viewed in a browser, which +is often more helpful than text-based output: + + $ pprof -http=localhost:9999 bazel-bin/source/exe/envoy main_common_base* diff --git a/bazel/envoy_build_system.bzl b/bazel/envoy_build_system.bzl index e66f38be1470..6aede4ad339f 100644 --- a/bazel/envoy_build_system.bzl +++ b/bazel/envoy_build_system.bzl @@ -1,4 +1,5 @@ load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library", "py_proto_library") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library") def envoy_package(): native.package(default_visibility = ["//visibility:public"]) @@ -398,55 +399,23 @@ def _proto_header(proto_path): return None # Envoy proto targets should be specified with this function. -def envoy_proto_library( - name, - srcs = [], - deps = [], - external_deps = [], - generate_python = True): - # Ideally this would be native.{proto_library, cc_proto_library}. - # Unfortunately, this doesn't work with http_api_protos due to the PGV - # requirement to also use them in the non-native protobuf.bzl - # cc_proto_library; you end up with the same file built twice. So, also - # using protobuf.bzl cc_proto_library here. - cc_proto_deps = [] - py_proto_deps = ["@com_google_protobuf//:protobuf_python"] - +def envoy_proto_library(name, external_deps = [], **kwargs): + external_proto_deps = [] + external_cc_proto_deps = [] if "api_httpbody_protos" in external_deps: - cc_proto_deps.append("@googleapis//:api_httpbody_protos") - py_proto_deps.append("@googleapis//:api_httpbody_protos_py") - - if "http_api_protos" in external_deps: - cc_proto_deps.append("@googleapis//:http_api_protos") - py_proto_deps.append("@googleapis//:http_api_protos_py") - - if "well_known_protos" in external_deps: - # WKT is already included for Python as part of standard deps above. - cc_proto_deps.append("@com_google_protobuf//:cc_wkt_protos") - - cc_proto_library( - name = name, - srcs = srcs, - default_runtime = "@com_google_protobuf//:protobuf", - protoc = "@com_google_protobuf//:protoc", - deps = deps + cc_proto_deps, + external_cc_proto_deps.append("@googleapis//:api_httpbody_protos") + external_proto_deps.append("@googleapis//:api_httpbody_protos_proto") + return api_proto_library( + name, + external_cc_proto_deps = external_cc_proto_deps, + external_proto_deps = external_proto_deps, # Avoid generating .so, we don't need it, can interfere with builds # such as OSS-Fuzz. linkstatic = 1, - alwayslink = 1, visibility = ["//visibility:public"], + **kwargs ) - if generate_python: - py_proto_library( - name = name + "_py", - srcs = srcs, - default_runtime = "@com_google_protobuf//:protobuf_python", - protoc = "@com_google_protobuf//:protoc", - deps = deps + py_proto_deps, - visibility = ["//visibility:public"], - ) - # Envoy proto descriptor targets should be specified with this function. # This is used for testing only. def envoy_proto_descriptor(name, out, srcs = [], external_deps = []): diff --git a/ci/WORKSPACE b/ci/WORKSPACE index c7f6e59e8e1c..823c4ea3a1db 100644 --- a/ci/WORKSPACE +++ b/ci/WORKSPACE @@ -23,5 +23,3 @@ load("@com_lyft_protoc_gen_validate//bazel:go_proto_library.bzl", "go_proto_repo go_proto_repositories(shared=0) go_rules_dependencies() go_register_toolchains() -load("@io_bazel_rules_go//proto:def.bzl", "proto_register_toolchains") -proto_register_toolchains() diff --git a/ci/WORKSPACE.filter.example b/ci/WORKSPACE.filter.example index f423bea64bb0..539b8eac217f 100644 --- a/ci/WORKSPACE.filter.example +++ b/ci/WORKSPACE.filter.example @@ -22,5 +22,3 @@ load("@com_lyft_protoc_gen_validate//bazel:go_proto_library.bzl", "go_proto_repo go_proto_repositories(shared=0) go_rules_dependencies() go_register_toolchains() -load("@io_bazel_rules_go//proto:def.bzl", "proto_register_toolchains") -proto_register_toolchains() diff --git a/docs/root/configuration/http_filters/rate_limit_filter.rst b/docs/root/configuration/http_filters/rate_limit_filter.rst index dcac97e337cb..52754a018674 100644 --- a/docs/root/configuration/http_filters/rate_limit_filter.rst +++ b/docs/root/configuration/http_filters/rate_limit_filter.rst @@ -16,6 +16,9 @@ apply to a request. Each configuration results in a descriptor being sent to the If the rate limit service is called, and the response for any of the descriptors is over limit, a 429 response is returned. +If there is an error in calling rate limit service or rate limit service returns an error and :ref:`failure_mode_deny ` is +set to true, a 500 response is returned. + .. _config_http_filters_rate_limit_composing_actions: Composing Actions @@ -108,6 +111,8 @@ The buffer filter outputs statistics in the *cluster..rate ok, Counter, Total under limit responses from the rate limit service error, Counter, Total errors contacting the rate limit service over_limit, Counter, total over limit responses from the rate limit service + failure_mode_allowed, Counter, "Total requests that were error(s) but were allowed through because + of :ref:`failure_mode_deny ` set to false." Runtime ------- diff --git a/docs/root/configuration/network_filters/rate_limit_filter.rst b/docs/root/configuration/network_filters/rate_limit_filter.rst index 4cefd6813699..ff9771bba806 100644 --- a/docs/root/configuration/network_filters/rate_limit_filter.rst +++ b/docs/root/configuration/network_filters/rate_limit_filter.rst @@ -25,6 +25,8 @@ following statistics: ok, Counter, Total under limit responses from the rate limit service cx_closed, Counter, Total connections closed due to an over limit response from the rate limit service active, Gauge, Total active requests to the rate limit service + failure_mode_allowed, Counter, "Total requests that were error(s) but were allowed through because + of :ref:`failure_mode_deny ` set to false." Runtime ------- diff --git a/docs/root/configuration/statistics.rst b/docs/root/configuration/statistics.rst index 17f7afd2dfab..af4d908cebf8 100644 --- a/docs/root/configuration/statistics.rst +++ b/docs/root/configuration/statistics.rst @@ -21,6 +21,7 @@ Server related statistics are rooted at *server.* with following statistics: :widths: 1, 1, 2 uptime, Gauge, Current server uptime in seconds + concurrency, Gauge, Number of worker threads memory_allocated, Gauge, Current amount of allocated memory in bytes. Total of both new and old Envoy processes on hot restart. memory_heap_size, Gauge, Current reserved heap size in bytes. New Envoy process heap size on hot restart. live, Gauge, "1 if the server is not currently draining, 0 otherwise" diff --git a/docs/root/install/tools/route_table_check_tool.rst b/docs/root/install/tools/route_table_check_tool.rst index f6b9ed2f87b6..fa0056d430c2 100644 --- a/docs/root/install/tools/route_table_check_tool.rst +++ b/docs/root/install/tools/route_table_check_tool.rst @@ -8,10 +8,11 @@ The tool can also be used to check whether a path redirect, path rewrite, or hos match what is expected. Input - The tool expects two input JSON files: + The tool expects two input files: - 1. A router config JSON file. The router config JSON file schema is found in - :ref:`config `. + 1. A v2 router config file (YAML or JSON). The router config file schema is found in + :ref:`config ` and the config file extension + must reflect its file type (for instance, .json for JSON and .yaml for YAML). 2. A tool config JSON file. The tool config JSON file schema is found in :ref:`config `. @@ -47,19 +48,19 @@ Building bazel build //test/tools/router_check:router_check_tool Running - The tool takes two input json files and an optional command line parameter ``--details``. The + The tool takes two input files and an optional command line parameter ``--details``. The expected order of command line arguments is: - 1. The router configuration json file. + 1. The router configuration file. 2. The tool configuration json file. 3. The optional details flag. :: - bazel-bin/test/tools/router_check/router_check_tool router_config.json tool_config.json + bazel-bin/test/tools/router_check/router_check_tool router_config.(yaml|json) tool_config.json - bazel-bin/test/tools/router_check/router_check_tool router_config.json tool_config.json --details + bazel-bin/test/tools/router_check/router_check_tool router_config.(yaml|json) tool_config.json --details Testing A bash shell script test can be run with bazel. The test compares routes using different router and - tool configuration json files. The configuration json files can be found in + tool configuration files. The configuration files can be found in test/tools/router_check/test/config/... . :: bazel test //test/tools/router_check/... diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index bb4661c3d10f..3a5cb14200a7 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -12,6 +12,7 @@ Version history `google.api.HttpBody `_. * cluster: added :ref:`option ` to merge health check/weight/metadata updates within the given duration. +* config: regex validation added to limit to a maximum of 1024 characters. * config: v1 disabled by default. v1 support remains available until October via flipping --v2-config-only=false. * config: v1 disabled by default. v1 support remains available until October via setting :option:`--allow-deprecated-v1-api`. * fault: added support for fractional percentages in :ref:`FaultDelay ` @@ -38,6 +39,14 @@ Version history * http: added generic :ref:`Upgrade support `. * http: better handling of HEAD requests. Now sending transfer-encoding: chunked rather than content-length: 0. +* http: fixed missing support for appending to predefined inline headers, e.g. + *authorization*, in features that interact with request and response headers, + e.g. :ref:`request_headers_to_add + `. For example, a + request header *authorization: token1* will appear as *authorization: + token1,token2*, after having :ref:`request_headers_to_add + ` with *authorization: + token2* applied. * http: response filters not applied to early error paths such as http_parser generated 400s. * http: :ref:`hpack_table_size ` now controls dynamic table size of both: encoder and decoder. @@ -65,6 +74,9 @@ Version history * upstream: require opt-in to use the :ref:`x-envoy-orignal-dst-host ` header for overriding destination address when using the :ref:`Original Destination ` load balancing policy. +* ratelimit: added :ref:`failure_mode_deny ` option to control traffic flow in + case of rate limit service error. +* route checker: Added v2 config support and removed support for v1 configs. 1.7.0 =============== diff --git a/include/envoy/api/api.h b/include/envoy/api/api.h index a5e52f9fef9a..39b498163568 100644 --- a/include/envoy/api/api.h +++ b/include/envoy/api/api.h @@ -3,6 +3,7 @@ #include #include +#include "envoy/common/time.h" #include "envoy/event/dispatcher.h" #include "envoy/filesystem/filesystem.h" #include "envoy/stats/store.h" @@ -20,9 +21,10 @@ class Api { /** * Allocate a dispatcher. + * @param time_source the time source. * @return Event::DispatcherPtr which is owned by the caller. */ - virtual Event::DispatcherPtr allocateDispatcher() PURE; + virtual Event::DispatcherPtr allocateDispatcher(TimeSource& time_source) PURE; /** * Create/open a local file that supports async appending. diff --git a/include/envoy/common/time.h b/include/envoy/common/time.h index fb09f1db76ab..6fd7e6e7198b 100644 --- a/include/envoy/common/time.h +++ b/include/envoy/common/time.h @@ -16,6 +16,8 @@ typedef std::chrono::time_point MonotonicTime; /** * Abstraction for getting the current system time. Useful for testing. + * + * TODO(#4160): eliminate this class and pass TimeSource everywhere. */ class SystemTimeSource { public: @@ -28,7 +30,10 @@ class SystemTimeSource { }; /** - * Abstraction for getting the current monotonically increasing time. Useful for testing. + * Abstraction for getting the current monotonically increasing time. Useful for + * testing. + * + * TODO(#4160): eliminate this class and pass TimeSource everywhere. */ class MonotonicTimeSource { public: @@ -39,4 +44,46 @@ class MonotonicTimeSource { */ virtual MonotonicTime currentTime() PURE; }; + +/** + * Captures a system-time source, capable of computing both monotonically increasing + * and real time. + * + * TODO(#4160): currently this is just a container for SystemTimeSource and + * MonotonicTimeSource but we should clean that up and just have this as the + * base class. Once that's done, TimeSource will be a pure interface. + */ +class TimeSource { +public: + TimeSource(SystemTimeSource& system, MonotonicTimeSource& monotonic) + : system_(system), monotonic_(monotonic) {} + + /** + * @return the current system time; not guaranteed to be monotonically increasing. + */ + SystemTime systemTime() { return system_.currentTime(); } + + /** + * @return the current monotonic time. + */ + MonotonicTime monotonicTime() { return monotonic_.currentTime(); } + + /** + * Compares two time-sources for equality; this is needed for mocks. + */ + bool operator==(const TimeSource& ts) const { + return &system_ == &ts.system_ && &monotonic_ == &ts.monotonic_; + } + + // TODO(jmarantz): Eliminate these methods and the SystemTimeSource and MonotonicTimeSource + // classes, and change method calls to work directly off of TimeSource. + SystemTimeSource& system() { return system_; } + MonotonicTimeSource& monotonic() { return monotonic_; } + +private: + // These are pointers rather than references in order to support assignment. + SystemTimeSource& system_; + MonotonicTimeSource& monotonic_; +}; + } // namespace Envoy diff --git a/include/envoy/event/dispatcher.h b/include/envoy/event/dispatcher.h index ab618647085a..05da014c06a7 100644 --- a/include/envoy/event/dispatcher.h +++ b/include/envoy/event/dispatcher.h @@ -32,6 +32,16 @@ class Dispatcher { public: virtual ~Dispatcher() {} + /** + * Returns a time-source to use with this dispatcher. + * + * TODO(#4160) the implementations currently manage timer events that + * ignore the time-source, and thus can't be mocked or faked. So it's + * difficult to mock time in an integration test without mocking out + * the dispatcher. + */ + virtual TimeSource& timeSource() PURE; + /** * Clear any items in the deferred deletion queue. */ diff --git a/include/envoy/http/header_map.h b/include/envoy/http/header_map.h index efbf38638eb2..fa02952a2b28 100644 --- a/include/envoy/http/header_map.h +++ b/include/envoy/http/header_map.h @@ -154,13 +154,17 @@ class HeaderString { bool operator!=(const char* rhs) const { return 0 != strcmp(c_str(), rhs); } private: - union { + union Buffer { + // This should reference inline_buffer_ for Type::Inline. char* dynamic_; const char* ref_; } buffer_; + // Capacity in both Type::Inline and Type::Dynamic cases must be at least MinDynamicCapacity in + // header_map_impl.cc. union { char inline_buffer_[128]; + // Since this is a union, this is only valid for type_ == Type::Dynamic. uint32_t dynamic_capacity_; }; @@ -321,10 +325,12 @@ class HeaderMap { /** * Add a reference header to the map. Both key and value MUST point to data that will live beyond * the lifetime of any request/response using the string (since a codec may optimize for zero - * copy). Nothing will be copied. + * copy). The key will not be copied and a best effort will be made not to + * copy the value (but this may happen when comma concatenating, see below). * - * Calling addReference multiple times for the same header will result in multiple headers being - * present in the HeaderMap. + * Calling addReference multiple times for the same header will result in: + * - Comma concatenation for predefined inline headers. + * - Multiple headers being present in the HeaderMap for other headers. * * @param key specifies the name of the header to add; it WILL NOT be copied. * @param value specifies the value of the header to add; it WILL NOT be copied. @@ -336,8 +342,9 @@ class HeaderMap { * the lifetime of any request/response using the string (since a codec may optimize for zero * copy). The value will be copied. * - * Calling addReferenceKey multiple times for the same header will result in multiple headers - * being present in the HeaderMap. + * Calling addReference multiple times for the same header will result in: + * - Comma concatenation for predefined inline headers. + * - Multiple headers being present in the HeaderMap for other headers. * * @param key specifies the name of the header to add; it WILL NOT be copied. * @param value specifies the value of the header to add; it WILL be copied. @@ -349,8 +356,9 @@ class HeaderMap { * live beyond the lifetime of any request/response using the string (since a codec may optimize * for zero copy). The value will be copied. * - * Calling addReferenceKey multiple times for the same header will result in multiple headers - * being present in the HeaderMap. + * Calling addReference multiple times for the same header will result in: + * - Comma concatenation for predefined inline headers. + * - Multiple headers being present in the HeaderMap for other headers. * * @param key specifies the name of the header to add; it WILL NOT be copied. * @param value specifies the value of the header to add; it WILL be copied. @@ -360,8 +368,9 @@ class HeaderMap { /** * Add a header by copying both the header key and the value. * - * Calling addCopy multiple times for the same header will result in multiple headers being - * present in the HeaderMap. + * Calling addCopy multiple times for the same header will result in: + * - Comma concatenation for predefined inline headers. + * - Multiple headers being present in the HeaderMap for other headers. * * @param key specifies the name of the header to add; it WILL be copied. * @param value specifies the value of the header to add; it WILL be copied. @@ -371,8 +380,9 @@ class HeaderMap { /** * Add a header by copying both the header key and the value. * - * Calling addCopy multiple times for the same header will result in multiple headers being - * present in the HeaderMap. + * Calling addCopy multiple times for the same header will result in: + * - Comma concatenation for predefined inline headers. + * - Multiple headers being present in the HeaderMap for other headers. * * @param key specifies the name of the header to add; it WILL be copied. * @param value specifies the value of the header to add; it WILL be copied. diff --git a/include/envoy/request_info/request_info.h b/include/envoy/request_info/request_info.h index 8d8a01119674..8af4c7e01cb8 100644 --- a/include/envoy/request_info/request_info.h +++ b/include/envoy/request_info/request_info.h @@ -48,8 +48,10 @@ enum ResponseFlag { RateLimited = 0x800, // Request was unauthorized by external authorization service. UnauthorizedExternalService = 0x1000, + // Unable to call Ratelimit service. + RateLimitServiceError = 0x2000, // ATTENTION: MAKE SURE THIS REMAINS EQUAL TO THE LAST FLAG. - LastFlag = UnauthorizedExternalService + LastFlag = RateLimitServiceError }; /** diff --git a/include/envoy/secret/BUILD b/include/envoy/secret/BUILD index 6124d51a3210..bf632fa49c9e 100644 --- a/include/envoy/secret/BUILD +++ b/include/envoy/secret/BUILD @@ -8,10 +8,17 @@ load( envoy_package() +envoy_cc_library( + name = "secret_callbacks_interface", + hdrs = ["secret_callbacks.h"], +) + envoy_cc_library( name = "secret_provider_interface", hdrs = ["secret_provider.h"], deps = [ + ":secret_callbacks_interface", + "//include/envoy/common:callback", "//include/envoy/ssl:tls_certificate_config_interface", ], ) @@ -22,5 +29,6 @@ envoy_cc_library( deps = [ ":secret_provider_interface", "@envoy_api//envoy/api/v2/auth:cert_cc", + "@envoy_api//envoy/api/v2/core:config_source_cc", ], ) diff --git a/include/envoy/secret/secret_callbacks.h b/include/envoy/secret/secret_callbacks.h new file mode 100644 index 000000000000..b4ee4b814837 --- /dev/null +++ b/include/envoy/secret/secret_callbacks.h @@ -0,0 +1,19 @@ +#pragma once + +#include "envoy/common/pure.h" + +namespace Envoy { +namespace Secret { + +/** + * Callbacks invoked by a dynamic secret provider. + */ +class SecretCallbacks { +public: + virtual ~SecretCallbacks() {} + + virtual void onAddOrUpdateSecret() PURE; +}; + +} // namespace Secret +} // namespace Envoy diff --git a/include/envoy/secret/secret_manager.h b/include/envoy/secret/secret_manager.h index 85f5e7228f72..47d27ac6cbdb 100644 --- a/include/envoy/secret/secret_manager.h +++ b/include/envoy/secret/secret_manager.h @@ -6,12 +6,17 @@ #include "envoy/secret/secret_provider.h" namespace Envoy { + +namespace Server { +namespace Configuration { +class TransportSocketFactoryContext; +} // namespace Configuration +} // namespace Server + namespace Secret { /** - * A manager for static secrets. - * - * TODO(jaebong) Support dynamic secrets. + * A manager for static and dynamic secrets. */ class SecretManager { public: @@ -37,6 +42,20 @@ class SecretManager { */ virtual TlsCertificateConfigProviderSharedPtr createInlineTlsCertificateProvider( const envoy::api::v2::auth::TlsCertificate& tls_certificate) PURE; + + /** + * Finds and returns a dynamic secret provider associated to SDS config. Create + * a new one if such provider does not exist. + * + * @param config_source a protobuf message object containing a SDS config source. + * @param config_name a name that uniquely refers to the SDS config source. + * @param secret_provider_context context that provides components for creating and initializing + * secret provider. + * @return TlsCertificateConfigProviderSharedPtr the dynamic TLS secret provider. + */ + virtual TlsCertificateConfigProviderSharedPtr findOrCreateDynamicSecretProvider( + const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) PURE; }; } // namespace Secret diff --git a/include/envoy/secret/secret_provider.h b/include/envoy/secret/secret_provider.h index b6f9f26ca76c..a624a7a42045 100644 --- a/include/envoy/secret/secret_provider.h +++ b/include/envoy/secret/secret_provider.h @@ -1,6 +1,10 @@ #pragma once +#include + +#include "envoy/common/callback.h" #include "envoy/common/pure.h" +#include "envoy/secret/secret_callbacks.h" #include "envoy/ssl/tls_certificate_config.h" namespace Envoy { @@ -18,7 +22,14 @@ template class SecretProvider { */ virtual const SecretType* secret() const PURE; - // TODO(lizan): Add more methods for dynamic secret provider. + /** + * Add secret update callback into secret provider. + * It is safe to call this method by main thread and is safe to be invoked + * on main thread. + * @param callback callback that is executed by secret provider. + * @return CallbackHandle the handle which can remove that update callback. + */ + virtual Common::CallbackHandle* addUpdateCallback(std::function callback) PURE; }; typedef SecretProvider TlsCertificateConfigProvider; diff --git a/include/envoy/server/BUILD b/include/envoy/server/BUILD index 35f6829a7fad..3a367b75343f 100644 --- a/include/envoy/server/BUILD +++ b/include/envoy/server/BUILD @@ -177,6 +177,7 @@ envoy_cc_library( hdrs = ["transport_socket_config.h"], deps = [ "//include/envoy/event:dispatcher_interface", + "//include/envoy/init:init_interface", "//include/envoy/local_info:local_info_interface", "//include/envoy/network:transport_socket_interface", "//include/envoy/runtime:runtime_interface", diff --git a/include/envoy/server/filter_config.h b/include/envoy/server/filter_config.h index a3a3dbb3b219..997d773ad2bc 100644 --- a/include/envoy/server/filter_config.h +++ b/include/envoy/server/filter_config.h @@ -131,9 +131,16 @@ class FactoryContext { virtual const envoy::api::v2::core::Metadata& listenerMetadata() const PURE; /** - * @return SystemTimeSource& a reference to the top-level SystemTime source. + * @return SystemTimeSource& a reference to the system time source. + * TODO(#4160): This method should be eliminated, and call-sites and mocks should + * be converted to work with timeSource() below. */ virtual SystemTimeSource& systemTimeSource() PURE; + + /** + * @return TimeSource& a reference to the time source. + */ + virtual TimeSource& timeSource() PURE; }; class ListenerFactoryContext : public FactoryContext { diff --git a/include/envoy/server/instance.h b/include/envoy/server/instance.h index 4fef027d9b9d..b85c2bc045c6 100644 --- a/include/envoy/server/instance.h +++ b/include/envoy/server/instance.h @@ -192,6 +192,11 @@ class Instance { */ virtual const LocalInfo::LocalInfo& localInfo() PURE; + /** + * @return the time source used for the server. + */ + virtual TimeSource& timeSource() PURE; + /** * @return the flush interval of stats sinks. */ diff --git a/include/envoy/server/transport_socket_config.h b/include/envoy/server/transport_socket_config.h index 143be130d079..5f082fda925d 100644 --- a/include/envoy/server/transport_socket_config.h +++ b/include/envoy/server/transport_socket_config.h @@ -3,6 +3,7 @@ #include #include "envoy/event/dispatcher.h" +#include "envoy/init/init.h" #include "envoy/local_info/local_info.h" #include "envoy/network/transport_socket.h" #include "envoy/runtime/runtime.h" @@ -63,6 +64,18 @@ class TransportSocketFactoryContext { * @return the server-wide stats store. */ virtual Stats::Store& stats() PURE; + + /** + * Pass an init manager to register dynamic secret provider. + * @param init_manager instance of init manager. + */ + virtual void setInitManager(Init::Manager& init_manager) PURE; + + /** + * @return a pointer pointing to the instance of an init manager, or nullptr + * if not set. + */ + virtual Init::Manager* initManager() PURE; }; class TransportSocketConfigFactory { diff --git a/include/envoy/ssl/BUILD b/include/envoy/ssl/BUILD index 315b9a5071d5..e4e8582d124f 100644 --- a/include/envoy/ssl/BUILD +++ b/include/envoy/ssl/BUILD @@ -22,7 +22,7 @@ envoy_cc_library( name = "context_config_interface", hdrs = ["context_config.h"], deps = [ - ":tls_certificate_config_interface", + "//include/envoy/secret:secret_provider_interface", ], ) diff --git a/include/envoy/ssl/context_config.h b/include/envoy/ssl/context_config.h index 749cc8451656..dd3ee7f92a78 100644 --- a/include/envoy/ssl/context_config.h +++ b/include/envoy/ssl/context_config.h @@ -5,7 +5,8 @@ #include #include "envoy/common/pure.h" -#include "envoy/ssl/tls_certificate_config.h" +#include "envoy/secret/secret_callbacks.h" +#include "envoy/secret/secret_provider.h" namespace Envoy { namespace Ssl { @@ -95,6 +96,19 @@ class ContextConfig { * @return The maximum TLS protocol version to negotiate. */ virtual unsigned maxProtocolVersion() const PURE; + + /** + * @return true if the ContextConfig is able to provide secrets to create SSL context, + * and false if dynamic secrets are expected but are not downloaded from SDS server yet. + */ + virtual bool isReady() const PURE; + + /** + * Add secret callback into context config. When dynamic secrets are in use and new secrets + * are downloaded from SDS server, this callback is invoked to update SSL context. + * @param callback callback that is executed by context config. + */ + virtual void setSecretUpdateCallback(Secret::SecretCallbacks& callback) PURE; }; class ClientContextConfig : public virtual ContextConfig { diff --git a/include/envoy/upstream/cluster_manager.h b/include/envoy/upstream/cluster_manager.h index c7e7aba180af..ae9d5b767d23 100644 --- a/include/envoy/upstream/cluster_manager.h +++ b/include/envoy/upstream/cluster_manager.h @@ -209,6 +209,11 @@ class ClusterManager { addThreadLocalClusterUpdateCallbacks(ClusterUpdateCallbacks& callbacks) PURE; virtual ClusterManagerFactory& clusterManagerFactory() PURE; + + /** + * @return TimeSource& the time-source used with the cluster manager. + */ + virtual TimeSource& timeSource() PURE; }; typedef std::unique_ptr ClusterManagerPtr; diff --git a/include/envoy/upstream/host_description.h b/include/envoy/upstream/host_description.h index 3cac6809d372..700907fe3791 100644 --- a/include/envoy/upstream/host_description.h +++ b/include/envoy/upstream/host_description.h @@ -108,6 +108,11 @@ class HostDescription { * @return the address used to health check the host. */ virtual Network::Address::InstanceConstSharedPtr healthCheckAddress() const PURE; + + /** + * Set the address used to health check the host. + */ + virtual void setHealthCheckAddress(Network::Address::InstanceConstSharedPtr) PURE; }; typedef std::shared_ptr HostDescriptionConstSharedPtr; diff --git a/include/envoy/upstream/load_balancer.h b/include/envoy/upstream/load_balancer.h index 2a6157d0112f..31122bf9a828 100644 --- a/include/envoy/upstream/load_balancer.h +++ b/include/envoy/upstream/load_balancer.h @@ -10,6 +10,13 @@ namespace Envoy { namespace Upstream { +// Mapping from a priority to how much of the total traffic load should be directed to this +// priority. For example, {50, 30, 20} means that 50% of traffic should go to P0, 30% to P1 +// and 20% to P2. +// +// This should either sum to 100 or consist of all zeros. +typedef std::vector PriorityLoad; + /** * Context information passed to a load balancer to use when choosing a host. Not all load * balancers make use of all context information. @@ -43,6 +50,33 @@ class LoadBalancerContext { * balancing. */ virtual const Http::HeaderMap* downstreamHeaders() const PURE; + + /** + * Called to retrieve a reference to the priority load data that should be used when selecting a + * priority. Implementations may return the provided original reference to make no changes, or + * return a reference to alternative PriorityLoad held internally. + * + * @param priority_state current priority state of the cluster being being load balanced. + * @param original_priority_load the cached priority load for the cluster being load balanced. + * @return a reference to the priority load data that should be used to select a priority. + * + */ + virtual const PriorityLoad& + determinePriorityLoad(const PrioritySet& priority_set, + const PriorityLoad& original_priority_load) PURE; + + /** + * Called to determine whether we should reperform host selection. The load balancer + * will retry host selection until either this function returns true or hostSelectionRetryCount is + * reached. + */ + virtual bool shouldSelectAnotherHost(const Host& host) PURE; + + /** + * Called to determine how many times host selection should be retried until the filter is + * ignored. + */ + virtual uint32_t hostSelectionRetryCount() const PURE; }; /** diff --git a/include/envoy/upstream/upstream.h b/include/envoy/upstream/upstream.h index 655bd2de4570..7f4d68b06131 100644 --- a/include/envoy/upstream/upstream.h +++ b/include/envoy/upstream/upstream.h @@ -45,6 +45,15 @@ class Host : virtual public HostDescription { FAILED_EDS_HEALTH = 0x04, }; + enum class ActiveHealthFailureType { + // The failure type is unknown, all hosts' failure types are initialized as UNKNOWN + UNKNOWN, + // The host is actively responding it's unhealthy + UNHEALTHY, + // The host is timing out + TIMEOUT, + }; + /** * @return host specific counters. */ @@ -98,6 +107,17 @@ class Host : virtual public HostDescription { */ virtual bool healthy() const PURE; + /** + * Returns the host's ActiveHealthFailureType. Types are specified in ActiveHealthFailureType. + */ + virtual ActiveHealthFailureType getActiveHealthFailureType() const PURE; + + /** + * Set the most recent health failure type for a host. Types are specified in + * ActiveHealthFailureType. + */ + virtual void setActiveHealthFailureType(ActiveHealthFailureType flag) PURE; + /** * Set the host's health checker monitor. Monitors are assumed to be thread safe, however * a new monitor must be installed before the host is used across threads. Thus, diff --git a/source/common/api/api_impl.cc b/source/common/api/api_impl.cc index 86f418939e10..f36aa7a9724b 100644 --- a/source/common/api/api_impl.cc +++ b/source/common/api/api_impl.cc @@ -9,8 +9,8 @@ namespace Envoy { namespace Api { -Event::DispatcherPtr Impl::allocateDispatcher() { - return Event::DispatcherPtr{new Event::DispatcherImpl()}; +Event::DispatcherPtr Impl::allocateDispatcher(TimeSource& time_source) { + return Event::DispatcherPtr{new Event::DispatcherImpl(time_source)}; } Impl::Impl(std::chrono::milliseconds file_flush_interval_msec) diff --git a/source/common/api/api_impl.h b/source/common/api/api_impl.h index b7f0f6fb609a..567493fdaa0e 100644 --- a/source/common/api/api_impl.h +++ b/source/common/api/api_impl.h @@ -17,7 +17,7 @@ class Impl : public Api::Api { Impl(std::chrono::milliseconds file_flush_interval_msec); // Api::Api - Event::DispatcherPtr allocateDispatcher() override; + Event::DispatcherPtr allocateDispatcher(TimeSource& time_source) override; Filesystem::FileSharedPtr createFile(const std::string& path, Event::Dispatcher& dispatcher, Thread::BasicLockable& lock, Stats::Store& stats_store) override; diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 3746377a8b1b..6e86eaa5acb9 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -44,6 +44,7 @@ namespace Logger { FUNCTION(router) \ FUNCTION(runtime) \ FUNCTION(stats) \ + FUNCTION(secret) \ FUNCTION(testing) \ FUNCTION(thrift) \ FUNCTION(tracing) \ diff --git a/source/common/common/perf_annotation.cc b/source/common/common/perf_annotation.cc index 81eee48276da..77523b8d640f 100644 --- a/source/common/common/perf_annotation.cc +++ b/source/common/common/perf_annotation.cc @@ -18,11 +18,10 @@ namespace Envoy { PerfOperation::PerfOperation() - : start_time_(ProdMonotonicTimeSource::instance_.currentTime()), - context_(PerfAnnotationContext::getOrCreate()) {} + : context_(PerfAnnotationContext::getOrCreate()), start_time_(context_->currentTime()) {} void PerfOperation::record(absl::string_view category, absl::string_view description) { - const MonotonicTime end_time = ProdMonotonicTimeSource::instance_.currentTime(); + const MonotonicTime end_time = context_->currentTime(); const std::chrono::nanoseconds duration = std::chrono::duration_cast(end_time - start_time_); context_->record(duration, category, description); diff --git a/source/common/common/perf_annotation.h b/source/common/common/perf_annotation.h index 66bdd07deb09..36442289704d 100644 --- a/source/common/common/perf_annotation.h +++ b/source/common/common/perf_annotation.h @@ -86,6 +86,9 @@ class PerfAnnotationContext { void record(std::chrono::nanoseconds duration, absl::string_view category, absl::string_view description); + /** @return MonotonicTime the current time */ + MonotonicTime currentTime() { return time_source_.currentTime(); } + /** * Renders the aggregated statistics as a string. * @return std::string the performance data as a formatted string. @@ -138,6 +141,7 @@ class PerfAnnotationContext { #else DurationStatsMap duration_stats_map_; #endif + ProdMonotonicTimeSource time_source_; }; /** @@ -162,8 +166,8 @@ class PerfOperation { void record(absl::string_view category, absl::string_view description); private: - MonotonicTime start_time_; PerfAnnotationContext* context_; + MonotonicTime start_time_; }; } // namespace Envoy diff --git a/source/common/common/token_bucket_impl.cc b/source/common/common/token_bucket_impl.cc index 993a061ddb4b..a46848ad34ff 100644 --- a/source/common/common/token_bucket_impl.cc +++ b/source/common/common/token_bucket_impl.cc @@ -4,14 +4,15 @@ namespace Envoy { -TokenBucketImpl::TokenBucketImpl(uint64_t max_tokens, double fill_rate, - MonotonicTimeSource& time_source) +TokenBucketImpl::TokenBucketImpl(uint64_t max_tokens, MonotonicTimeSource& monotonic_time_source, + double fill_rate) : max_tokens_(max_tokens), fill_rate_(std::abs(fill_rate)), tokens_(max_tokens), - last_fill_(time_source.currentTime()), time_source_(time_source) {} + last_fill_(monotonic_time_source.currentTime()), + monotonic_time_source_(monotonic_time_source) {} bool TokenBucketImpl::consume(uint64_t tokens) { if (tokens_ < max_tokens_) { - const auto time_now = time_source_.currentTime(); + const auto time_now = monotonic_time_source_.currentTime(); tokens_ = std::min((std::chrono::duration(time_now - last_fill_).count() * fill_rate_) + tokens_, max_tokens_); @@ -26,4 +27,4 @@ bool TokenBucketImpl::consume(uint64_t tokens) { return true; } -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/source/common/common/token_bucket_impl.h b/source/common/common/token_bucket_impl.h index b9f43648e30c..d859424a35b8 100644 --- a/source/common/common/token_bucket_impl.h +++ b/source/common/common/token_bucket_impl.h @@ -14,12 +14,12 @@ class TokenBucketImpl : public TokenBucket { public: /** * @param max_tokens supplies the maximun number of tokens in the bucket. + * @param time_source supplies the time source. * @param fill_rate supplies the number of tokens that will return to the bucket on each second. * The default is 1. - * @param time_source supplies the time source. The default is ProdMonotonicTimeSource. */ - explicit TokenBucketImpl(uint64_t max_tokens, double fill_rate = 1, - MonotonicTimeSource& time_source = ProdMonotonicTimeSource::instance_); + explicit TokenBucketImpl(uint64_t max_tokens, MonotonicTimeSource& time_source, + double fill_rate = 1); bool consume(uint64_t tokens = 1) override; @@ -28,7 +28,7 @@ class TokenBucketImpl : public TokenBucket { const double fill_rate_; double tokens_; MonotonicTime last_fill_; - MonotonicTimeSource& time_source_; + MonotonicTimeSource& monotonic_time_source_; }; } // namespace Envoy diff --git a/source/common/common/utility.cc b/source/common/common/utility.cc index 35e3d4b579e9..33d1ac04c6c2 100644 --- a/source/common/common/utility.cc +++ b/source/common/common/utility.cc @@ -201,9 +201,6 @@ std::string DateFormatter::now() { return fromTime(current_time_t); } -ProdSystemTimeSource ProdSystemTimeSource::instance_; -ProdMonotonicTimeSource ProdMonotonicTimeSource::instance_; - ConstMemoryStreamBuffer::ConstMemoryStreamBuffer(const char* data, size_t size) { // std::streambuf won't modify `data`, but the interface still requires a char* for convenience, // so we need to const_cast. diff --git a/source/common/common/utility.h b/source/common/common/utility.h index 452d80192fca..0373b4b36c86 100644 --- a/source/common/common/utility.h +++ b/source/common/common/utility.h @@ -102,8 +102,6 @@ class ProdSystemTimeSource : public SystemTimeSource { public: // SystemTimeSource SystemTime currentTime() override { return std::chrono::system_clock::now(); } - - static ProdSystemTimeSource instance_; }; /** @@ -113,8 +111,6 @@ class ProdMonotonicTimeSource : public MonotonicTimeSource { public: // MonotonicTimeSource MonotonicTime currentTime() override { return std::chrono::steady_clock::now(); } - - static ProdMonotonicTimeSource instance_; }; /** diff --git a/source/common/config/BUILD b/source/common/config/BUILD index cd52a50719dc..179a9d32c95b 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -242,6 +242,7 @@ envoy_cc_library( hdrs = ["protobuf_link_hacks.h"], deps = [ "@envoy_api//envoy/service/discovery/v2:ads_cc", + "@envoy_api//envoy/service/discovery/v2:sds_cc", "@envoy_api//envoy/service/ratelimit/v2:rls_cc", ], ) diff --git a/source/common/config/grpc_mux_impl.cc b/source/common/config/grpc_mux_impl.cc index 3cd1008a5a54..0af5b7c50574 100644 --- a/source/common/config/grpc_mux_impl.cc +++ b/source/common/config/grpc_mux_impl.cc @@ -12,9 +12,9 @@ namespace Config { GrpcMuxImpl::GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, Grpc::AsyncClientPtr async_client, Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, - Runtime::RandomGenerator& random, MonotonicTimeSource& time_source) + Runtime::RandomGenerator& random) : local_info_(local_info), async_client_(std::move(async_client)), - service_method_(service_method), random_(random), time_source_(time_source) { + service_method_(service_method), random_(random), time_source_(dispatcher.timeSource()) { Config::Utility::checkLocalInfo("ads", local_info); retry_timer_ = dispatcher.createTimer([this]() -> void { establishNewStream(); }); backoff_strategy_ = std::make_unique(RETRY_INITIAL_DELAY_MS, @@ -111,9 +111,11 @@ GrpcMuxWatchPtr GrpcMuxImpl::subscribe(const std::string& type_url, // TODO(gsagula): move TokenBucketImpl params to a config. if (!api_state_[type_url].subscribed_) { // Bucket contains 100 tokens maximum and refills at 5 tokens/sec. - api_state_[type_url].limit_request_ = std::make_unique(100, 5, time_source_); + api_state_[type_url].limit_request_ = + std::make_unique(100, time_source_.monotonic(), 5); // Bucket contains 1 token maximum and refills 1 token on every ~5 seconds. - api_state_[type_url].limit_log_ = std::make_unique(1, 0.2, time_source_); + api_state_[type_url].limit_log_ = + std::make_unique(1, time_source_.monotonic(), 0.2); api_state_[type_url].request_.set_type_url(type_url); api_state_[type_url].request_.mutable_node()->MergeFrom(local_info_.node()); api_state_[type_url].subscribed_ = true; diff --git a/source/common/config/grpc_mux_impl.h b/source/common/config/grpc_mux_impl.h index 155d354404a4..0fceb67a4575 100644 --- a/source/common/config/grpc_mux_impl.h +++ b/source/common/config/grpc_mux_impl.h @@ -26,8 +26,7 @@ class GrpcMuxImpl : public GrpcMux, public: GrpcMuxImpl(const LocalInfo::LocalInfo& local_info, Grpc::AsyncClientPtr async_client, Event::Dispatcher& dispatcher, const Protobuf::MethodDescriptor& service_method, - Runtime::RandomGenerator& random, - MonotonicTimeSource& time_source = ProdMonotonicTimeSource::instance_); + Runtime::RandomGenerator& random); ~GrpcMuxImpl(); void start() override; @@ -104,7 +103,7 @@ class GrpcMuxImpl : public GrpcMux, std::list subscriptions_; Event::TimerPtr retry_timer_; Runtime::RandomGenerator& random_; - MonotonicTimeSource& time_source_; + TimeSource time_source_; BackOffStrategyPtr backoff_strategy_; }; diff --git a/source/common/config/protobuf_link_hacks.h b/source/common/config/protobuf_link_hacks.h index 6792f3e797c1..6a9284625355 100644 --- a/source/common/config/protobuf_link_hacks.h +++ b/source/common/config/protobuf_link_hacks.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/service/discovery/v2/ads.pb.h" +#include "envoy/service/discovery/v2/sds.pb.h" #include "envoy/service/ratelimit/v2/rls.pb.h" namespace Envoy { @@ -9,4 +10,5 @@ namespace Envoy { // This file should be included ONLY if this hack is required. const envoy::service::discovery::v2::AdsDummy _ads_dummy; const envoy::service::ratelimit::v2::RateLimitRequest _rls_dummy; +const envoy::service::discovery::v2::SdsDummy _sds_dummy; } // namespace Envoy diff --git a/source/common/config/resources.h b/source/common/config/resources.h index 03f0c9a2efd8..69ed2d91a46d 100644 --- a/source/common/config/resources.h +++ b/source/common/config/resources.h @@ -15,6 +15,7 @@ class TypeUrlValues { const std::string Listener{"type.googleapis.com/envoy.api.v2.Listener"}; const std::string Cluster{"type.googleapis.com/envoy.api.v2.Cluster"}; const std::string ClusterLoadAssignment{"type.googleapis.com/envoy.api.v2.ClusterLoadAssignment"}; + const std::string Secret{"type.googleapis.com/envoy.api.v2.auth.Secret"}; const std::string RouteConfiguration{"type.googleapis.com/envoy.api.v2.RouteConfiguration"}; }; diff --git a/source/common/event/dispatched_thread.h b/source/common/event/dispatched_thread.h index 600e5906d8ae..e5cd327f72a9 100644 --- a/source/common/event/dispatched_thread.h +++ b/source/common/event/dispatched_thread.h @@ -38,7 +38,7 @@ namespace Event { */ class DispatchedThreadImpl : Logger::Loggable { public: - DispatchedThreadImpl() : dispatcher_(new DispatcherImpl()) {} + DispatchedThreadImpl(TimeSource& time_source) : dispatcher_(new DispatcherImpl(time_source)) {} /** * Start the thread. diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index ff8c3a1c76b2..af409f8cb40a 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -24,14 +24,14 @@ namespace Envoy { namespace Event { -DispatcherImpl::DispatcherImpl() - : DispatcherImpl(Buffer::WatermarkFactoryPtr{new Buffer::WatermarkBufferFactory}) { +DispatcherImpl::DispatcherImpl(TimeSource& time_source) + : DispatcherImpl(time_source, Buffer::WatermarkFactoryPtr{new Buffer::WatermarkBufferFactory}) { // The dispatcher won't work as expected if libevent hasn't been configured to use threads. RELEASE_ASSERT(Libevent::Global::initialized(), ""); } -DispatcherImpl::DispatcherImpl(Buffer::WatermarkFactoryPtr&& factory) - : buffer_factory_(std::move(factory)), base_(event_base_new()), +DispatcherImpl::DispatcherImpl(TimeSource& time_source, Buffer::WatermarkFactoryPtr&& factory) + : time_source_(time_source), buffer_factory_(std::move(factory)), base_(event_base_new()), deferred_delete_timer_(createTimer([this]() -> void { clearDeferredDeleteList(); })), post_timer_(createTimer([this]() -> void { runPostCallbacks(); })), current_to_delete_(&to_delete_1_) { diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index e1ab0c47e76b..ffc136e2f8f5 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -5,6 +5,7 @@ #include #include +#include "envoy/common/time.h" #include "envoy/event/deferred_deletable.h" #include "envoy/event/dispatcher.h" #include "envoy/network/connection_handler.h" @@ -21,8 +22,8 @@ namespace Event { */ class DispatcherImpl : Logger::Loggable, public Dispatcher { public: - DispatcherImpl(); - DispatcherImpl(Buffer::WatermarkFactoryPtr&& factory); + explicit DispatcherImpl(TimeSource& time_source); + DispatcherImpl(TimeSource& time_source, Buffer::WatermarkFactoryPtr&& factory); ~DispatcherImpl(); /** @@ -31,6 +32,7 @@ class DispatcherImpl : Logger::Loggable, public Dispatcher { event_base& base() { return *base_; } // Event::Dispatcher + TimeSource& timeSource() override { return time_source_; } void clearDeferredDeleteList() override; Network::ConnectionPtr createServerConnection(Network::ConnectionSocketPtr&& socket, @@ -66,6 +68,7 @@ class DispatcherImpl : Logger::Loggable, public Dispatcher { return run_tid_ == 0 || run_tid_ == Thread::Thread::currentThreadId(); } + TimeSource time_source_; Thread::ThreadId run_tid_{}; Buffer::WatermarkFactoryPtr buffer_factory_; Libevent::BasePtr base_; diff --git a/source/common/grpc/async_client_impl.cc b/source/common/grpc/async_client_impl.cc index 8d6f15c765a4..852270d25110 100644 --- a/source/common/grpc/async_client_impl.cc +++ b/source/common/grpc/async_client_impl.cc @@ -207,7 +207,7 @@ AsyncRequestImpl::AsyncRequestImpl(AsyncClientImpl& parent, current_span_ = parent_span.spawnChild(Tracing::EgressConfig::get(), "async " + parent.remote_cluster_name_ + " egress", - ProdSystemTimeSource::instance_.currentTime()); + parent.timeSource().systemTime()); current_span_->setTag(Tracing::Tags::get().UPSTREAM_CLUSTER, parent.remote_cluster_name_); current_span_->setTag(Tracing::Tags::get().COMPONENT, Tracing::Tags::get().PROXY); } diff --git a/source/common/grpc/async_client_impl.h b/source/common/grpc/async_client_impl.h index 06d8d173dc73..3841a911be11 100644 --- a/source/common/grpc/async_client_impl.h +++ b/source/common/grpc/async_client_impl.h @@ -25,6 +25,8 @@ class AsyncClientImpl final : public AsyncClient { AsyncStream* start(const Protobuf::MethodDescriptor& service_method, AsyncStreamCallbacks& callbacks) override; + TimeSource& timeSource() { return cm_.timeSource(); } + private: Upstream::ClusterManager& cm_; const std::string remote_cluster_name_; diff --git a/source/common/grpc/google_async_client_impl.cc b/source/common/grpc/google_async_client_impl.cc index 48ea1a16e527..7fdc57768d6c 100644 --- a/source/common/grpc/google_async_client_impl.cc +++ b/source/common/grpc/google_async_client_impl.cc @@ -387,7 +387,7 @@ GoogleAsyncRequestImpl::GoogleAsyncRequestImpl( callbacks_(callbacks) { current_span_ = parent_span.spawnChild(Tracing::EgressConfig::get(), "async " + parent.stat_prefix_ + " egress", - ProdSystemTimeSource::instance_.currentTime()); + parent.timeSource().systemTime()); current_span_->setTag(Tracing::Tags::get().UPSTREAM_CLUSTER, parent.stat_prefix_); current_span_->setTag(Tracing::Tags::get().COMPONENT, Tracing::Tags::get().PROXY); } diff --git a/source/common/grpc/google_async_client_impl.h b/source/common/grpc/google_async_client_impl.h index 72b7c95d82b4..1c91801c6061 100644 --- a/source/common/grpc/google_async_client_impl.h +++ b/source/common/grpc/google_async_client_impl.h @@ -165,6 +165,8 @@ class GoogleAsyncClientImpl final : public AsyncClient, Logger::Loggable createChannel(const envoy::api::v2::core::GrpcService::GoogleGrpc& config); diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index e9e435cd7121..b4a875ac5457 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -112,6 +112,10 @@ void AsyncStreamImpl::encodeTrailers(HeaderMapPtr&& trailers) { } void AsyncStreamImpl::sendHeaders(HeaderMap& headers, bool end_stream) { + if (Http::Headers::get().MethodValues.Head == headers.Method()->value().c_str()) { + is_head_request_ = true; + } + is_grpc_request_ = Grpc::Common::hasGrpcContentType(headers); headers.insertEnvoyInternalRequest().value().setReference( Headers::get().EnvoyInternalRequestValues.True); diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 801479bd43af..c1c578f5251c 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -282,7 +282,7 @@ class AsyncStreamImpl : public AsyncClient::Stream, encodeHeaders(std::move(headers), end_stream); }, [this](Buffer::Instance& data, bool end_stream) -> void { encodeData(data, end_stream); }, - remote_closed_, code, body); + remote_closed_, code, body, is_head_request_); } // The async client won't pause if sending an Expect: 100-Continue so simply // swallows any incoming encode100Continue. @@ -308,6 +308,7 @@ class AsyncStreamImpl : public AsyncClient::Stream, bool remote_closed_{}; Buffer::InstancePtr buffered_body_; bool is_grpc_request_{}; + bool is_head_request_{false}; friend class AsyncClientImpl; }; diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 3c5a9d56c8a3..c0fb71b5bf6e 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -425,7 +425,7 @@ void ConnectionManagerImpl::ActiveStream::onIdleTimeout() { } else { sendLocalReply(request_headers_ != nullptr && Grpc::Common::hasGrpcContentType(*request_headers_), - Http::Code::RequestTimeout, "stream timeout", nullptr); + Http::Code::RequestTimeout, "stream timeout", nullptr, is_head_request_); } } @@ -482,6 +482,10 @@ const Network::Connection* ConnectionManagerImpl::ActiveStream::connection() { void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, bool end_stream) { request_headers_ = std::move(headers); + if (Http::Headers::get().MethodValues.Head == request_headers_->Method()->value().c_str()) { + is_head_request_ = true; + } + const bool upgrade_rejected = createFilterChain() == false; maybeEndDecode(end_stream); @@ -513,7 +517,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, request_info_.protocol(protocol); if (!connection_manager_.config_.http1Settings().accept_http_10_) { // Send "Upgrade Required" if HTTP/1.0 support is not explictly configured on. - sendLocalReply(false, Code::UpgradeRequired, "", nullptr); + sendLocalReply(false, Code::UpgradeRequired, "", nullptr, is_head_request_); return; } else { // HTTP/1.0 defaults to single-use connections. Make sure the connection @@ -537,7 +541,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, } else { // Require host header. For HTTP/1.1 Host has already been translated to :authority. sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), Code::BadRequest, "", - nullptr); + nullptr, is_head_request_); return; } } @@ -551,7 +555,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, // envoy users who do not wish to proxy large headers. if (request_headers_->byteSize() > (60 * 1024)) { sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), - Code::RequestHeaderFieldsTooLarge, "", nullptr); + Code::RequestHeaderFieldsTooLarge, "", nullptr, is_head_request_); return; } @@ -562,8 +566,8 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, // don't support that currently. if (!request_headers_->Path() || request_headers_->Path()->value().c_str()[0] != '/') { connection_manager_.stats_.named_.downstream_rq_non_relative_path_.inc(); - sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), Code::NotFound, "", - nullptr); + sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), Code::NotFound, "", nullptr, + is_head_request_); return; } @@ -608,7 +612,7 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, // Do not allow WebSocket upgrades if the route does not support it. connection_manager_.stats_.named_.downstream_rq_ws_on_non_ws_route_.inc(); sendLocalReply(Grpc::Common::hasGrpcContentType(*request_headers_), Code::Forbidden, "", - nullptr); + nullptr, is_head_request_); return; } // Allow non websocket requests to go through websocket enabled routes. @@ -924,7 +928,7 @@ void ConnectionManagerImpl::ActiveStream::refreshCachedRoute() { void ConnectionManagerImpl::ActiveStream::sendLocalReply( bool is_grpc_request, Code code, const std::string& body, - std::function modify_headers) { + std::function modify_headers, bool is_head_request) { Utility::sendLocalReply(is_grpc_request, [this, modify_headers](HeaderMapPtr&& headers, bool end_stream) -> void { if (modify_headers != nullptr) { @@ -940,7 +944,7 @@ void ConnectionManagerImpl::ActiveStream::sendLocalReply( // request instead. encodeData(nullptr, data, end_stream); }, - state_.destroyed_, code, body); + state_.destroyed_, code, body, is_head_request); } void ConnectionManagerImpl::ActiveStream::encode100ContinueHeaders( @@ -1586,19 +1590,19 @@ void ConnectionManagerImpl::ActiveStreamEncoderFilter::responseDataTooLarge() { parent_.state_.encoder_filters_streaming_ = true; stopped_ = false; - Http::Utility::sendLocalReply(Grpc::Common::hasGrpcContentType(*parent_.request_headers_), - [&](HeaderMapPtr&& response_headers, bool end_stream) -> void { - parent_.response_headers_ = std::move(response_headers); - parent_.response_encoder_->encodeHeaders( - *parent_.response_headers_, end_stream); - parent_.state_.local_complete_ = end_stream; - }, - [&](Buffer::Instance& data, bool end_stream) -> void { - parent_.response_encoder_->encodeData(data, end_stream); - parent_.state_.local_complete_ = end_stream; - }, - parent_.state_.destroyed_, Http::Code::InternalServerError, - CodeUtility::toString(Http::Code::InternalServerError)); + Http::Utility::sendLocalReply( + Grpc::Common::hasGrpcContentType(*parent_.request_headers_), + [&](HeaderMapPtr&& response_headers, bool end_stream) -> void { + parent_.response_headers_ = std::move(response_headers); + parent_.response_encoder_->encodeHeaders(*parent_.response_headers_, end_stream); + parent_.state_.local_complete_ = end_stream; + }, + [&](Buffer::Instance& data, bool end_stream) -> void { + parent_.response_encoder_->encodeData(data, end_stream); + parent_.state_.local_complete_ = end_stream; + }, + parent_.state_.destroyed_, Http::Code::InternalServerError, + CodeUtility::toString(Http::Code::InternalServerError), parent_.is_head_request_); parent_.maybeEndEncode(parent_.state_.local_complete_); } else { resetStream(); diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index f7a0b0fa6b92..1aeaa303bea5 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -170,7 +170,8 @@ class ConnectionManagerImpl : Logger::Loggable, } void sendLocalReply(Code code, const std::string& body, std::function modify_headers) override { - parent_.sendLocalReply(is_grpc_request_, code, body, modify_headers); + parent_.sendLocalReply(is_grpc_request_, code, body, modify_headers, + parent_.is_head_request_); } void encode100ContinueHeaders(HeaderMapPtr&& headers) override; void encodeHeaders(HeaderMapPtr&& headers, bool end_stream) override; @@ -278,7 +279,8 @@ class ConnectionManagerImpl : Logger::Loggable, void addEncodedData(ActiveStreamEncoderFilter& filter, Buffer::Instance& data, bool streaming); HeaderMap& addEncodedTrailers(); void sendLocalReply(bool is_grpc_request, Code code, const std::string& body, - std::function modify_headers); + std::function modify_headers, + bool is_head_request); void encode100ContinueHeaders(ActiveStreamEncoderFilter* filter, HeaderMap& headers); void encodeHeaders(ActiveStreamEncoderFilter* filter, HeaderMap& headers, bool end_stream); void encodeData(ActiveStreamEncoderFilter* filter, Buffer::Instance& data, bool end_stream); @@ -404,6 +406,7 @@ class ConnectionManagerImpl : Logger::Loggable, // By default, we will assume there are no 100-Continue headers. If encode100ContinueHeaders // is ever called, this is set to true so commonContinue resumes processing the 100-Continue. bool has_continue_headers_{}; + bool is_head_request_{false}; }; typedef std::unique_ptr ActiveStreamPtr; diff --git a/source/common/http/header_map_impl.cc b/source/common/http/header_map_impl.cc index 28728515ef12..86c656901bc1 100644 --- a/source/common/http/header_map_impl.cc +++ b/source/common/http/header_map_impl.cc @@ -14,9 +14,25 @@ namespace Envoy { namespace Http { +namespace { +constexpr size_t MinDynamicCapacity{32}; +// This includes the NULL (StringUtil::itoa technically only needs 21). +constexpr size_t MaxIntegerLength{32}; + +void validateCapacity(size_t new_capacity) { + // If the resizing will cause buffer overflow due to hitting uint32_t::max, an OOM is likely + // imminent. Fast-fail rather than allow a buffer overflow attack (issue #1421) + RELEASE_ASSERT(new_capacity <= std::numeric_limits::max(), ""); + ASSERT(new_capacity >= MinDynamicCapacity); +} + +} // namespace + HeaderString::HeaderString() : type_(Type::Inline) { buffer_.dynamic_ = inline_buffer_; clear(); + static_assert(sizeof(inline_buffer_) >= MaxIntegerLength, ""); + static_assert(MinDynamicCapacity >= MaxIntegerLength, ""); } HeaderString::HeaderString(const LowerCaseString& ref_value) : type_(Type::Reference) { @@ -67,13 +83,17 @@ void HeaderString::freeDynamic() { void HeaderString::append(const char* data, uint32_t size) { switch (type_) { case Type::Reference: { - // Switch back to inline and fall through. We do not actually append to the static string - // currently which would require a copy. - type_ = Type::Inline; - buffer_.dynamic_ = inline_buffer_; - string_length_ = 0; - - FALLTHRU; + // Rather than be too clever and optimize this uncommon case, we dynamically + // allocate and copy. + type_ = Type::Dynamic; + dynamic_capacity_ = + std::max(MinDynamicCapacity, static_cast((string_length_ + size) * 2)); + validateCapacity(dynamic_capacity_); + char* buf = static_cast(malloc(dynamic_capacity_)); + RELEASE_ASSERT(buf != nullptr, ""); + memcpy(buf, buffer_.ref_, string_length_); + buffer_.dynamic_ = buf; + break; } case Type::Inline: { @@ -88,11 +108,10 @@ void HeaderString::append(const char* data, uint32_t size) { case Type::Dynamic: { // We can get here either because we didn't fit in inline or we are already dynamic. if (type_ == Type::Inline) { - const uint64_t new_capacity = (static_cast(string_length_) + size) * 2; - // If the resizing will cause buffer overflow due to hitting uint32_t::max, an OOM is likely - // imminent. Fast-fail rather than allow a buffer overflow attack (issue #1421) - RELEASE_ASSERT(new_capacity <= std::numeric_limits::max(), ""); + const size_t new_capacity = (string_length_ + size) * 2; + validateCapacity(new_capacity); buffer_.dynamic_ = static_cast(malloc(new_capacity)); + RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); memcpy(buffer_.dynamic_, inline_buffer_, string_length_); dynamic_capacity_ = new_capacity; type_ = Type::Dynamic; @@ -100,7 +119,9 @@ void HeaderString::append(const char* data, uint32_t size) { if (size + 1 + string_length_ > dynamic_capacity_) { // Need to reallocate. dynamic_capacity_ = (string_length_ + size) * 2; + validateCapacity(dynamic_capacity_); buffer_.dynamic_ = static_cast(realloc(buffer_.dynamic_, dynamic_capacity_)); + RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); } } } @@ -149,14 +170,18 @@ void HeaderString::setCopy(const char* data, uint32_t size) { // We can get here either because we didn't fit in inline or we are already dynamic. if (type_ == Type::Inline) { dynamic_capacity_ = size * 2; + validateCapacity(dynamic_capacity_); buffer_.dynamic_ = static_cast(malloc(dynamic_capacity_)); + RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); type_ = Type::Dynamic; } else { if (size + 1 > dynamic_capacity_) { // Need to reallocate. Use free/malloc to avoid the copy since we are about to overwrite. dynamic_capacity_ = size * 2; + validateCapacity(dynamic_capacity_); free(buffer_.dynamic_); buffer_.dynamic_ = static_cast(malloc(dynamic_capacity_)); + RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); } } } @@ -178,8 +203,14 @@ void HeaderString::setInteger(uint64_t value) { } case Type::Inline: + // buffer_.dynamic_ should always point at inline_buffer_ for Type::Inline. + ASSERT(buffer_.dynamic_ == inline_buffer_); case Type::Dynamic: { // Whether dynamic or inline the buffer is guaranteed to be large enough. + ASSERT(type_ == Type::Inline || dynamic_capacity_ >= MaxIntegerLength); + // It's safe to use buffer.dynamic_, since buffer.ref_ is union aliased. + // This better not change without verifying assumptions across this file. + static_assert(offsetof(Buffer, dynamic_) == offsetof(Buffer, ref_), ""); string_length_ = StringUtil::itoa(buffer_.dynamic_, 32, value); } } @@ -323,8 +354,12 @@ void HeaderMapImpl::insertByKey(HeaderString&& key, HeaderString&& value) { if (cb) { key.clear(); StaticLookupResponse ref_lookup_response = cb(*this); - ASSERT(*ref_lookup_response.entry_ == nullptr); // This function doesn't handle append. - maybeCreateInline(ref_lookup_response.entry_, *ref_lookup_response.key_, std::move(value)); + if (*ref_lookup_response.entry_ == nullptr) { + maybeCreateInline(ref_lookup_response.entry_, *ref_lookup_response.key_, std::move(value)); + } else { + appendToHeader((*ref_lookup_response.entry_)->value(), value.c_str()); + value.clear(); + } } else { std::list::iterator i = headers_.insert(std::move(key), std::move(value)); i->entry_ = i; diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index a08878e609d1..24d9d6d49c3e 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -244,11 +244,10 @@ void RequestStreamEncoderImpl::encodeHeaders(const HeaderMap& headers, bool end_ if (!method || !path) { throw CodecClientException(":method and :path must be specified"); } - if (method->value() == Headers::get().MethodValues.Head.c_str()) { head_request_ = true; } - + connection_.onEncodeHeaders(headers); connection_.reserveBuffer(std::max(4096U, path->value().size() + 4096)); connection_.copyToBuffer(method->value().c_str(), method->value().size()); connection_.addCharToBuffer(' '); @@ -685,9 +684,10 @@ StreamEncoder& ClientConnectionImpl::newStream(StreamDecoder& response_decoder) return *request_encoder_; } -void ClientConnectionImpl::onEncodeComplete() { - // Transfer head request state into the pending response before we reuse the encoder. - pending_responses_.back().head_request_ = request_encoder_->headRequest(); +void ClientConnectionImpl::onEncodeHeaders(const HeaderMap& headers) { + if (headers.Method()->value() == Headers::get().MethodValues.Head.c_str()) { + pending_responses_.back().head_request_ = true; + } } int ClientConnectionImpl::onHeadersComplete(HeaderMapImplPtr&& headers) { diff --git a/source/common/http/http1/codec_impl.h b/source/common/http/http1/codec_impl.h index 63a8d5540db3..6f8cafd2c971 100644 --- a/source/common/http/http1/codec_impl.h +++ b/source/common/http/http1/codec_impl.h @@ -98,7 +98,6 @@ class ResponseStreamEncoderImpl : public StreamEncoderImpl { class RequestStreamEncoderImpl : public StreamEncoderImpl { public: RequestStreamEncoderImpl(ConnectionImpl& connection) : StreamEncoderImpl(connection) {} - bool headRequest() { return head_request_; } // Http::StreamEncoder @@ -123,6 +122,11 @@ class ConnectionImpl : public virtual Connection, protected Logger::Loggable void { callbacks.encodeHeaders(std::move(headers), end_stream); @@ -249,13 +249,13 @@ void Utility::sendLocalReply(bool is_grpc, StreamDecoderFilterCallbacks& callbac [&](Buffer::Instance& data, bool end_stream) -> void { callbacks.encodeData(data, end_stream); }, - is_reset, response_code, body_text); + is_reset, response_code, body_text, is_head_request); } void Utility::sendLocalReply( bool is_grpc, std::function encode_headers, std::function encode_data, const bool& is_reset, - Code response_code, const std::string& body_text) { + Code response_code, const std::string& body_text, bool is_head_request) { // encode_headers() may reset the stream, so the stream must not be reset before calling it. ASSERT(!is_reset); // Respond with a gRPC trailers-only response if the request is gRPC @@ -265,7 +265,7 @@ void Utility::sendLocalReply( {Headers::get().ContentType, Headers::get().ContentTypeValues.Grpc}, {Headers::get().GrpcStatus, std::to_string(enumToInt(Grpc::Utility::httpToGrpcStatus(enumToInt(response_code))))}}}; - if (!body_text.empty()) { + if (!body_text.empty() && !is_head_request) { // TODO: GrpcMessage should be percent-encoded response_headers->insertGrpcMessage().value(body_text); } @@ -279,6 +279,12 @@ void Utility::sendLocalReply( response_headers->insertContentLength().value(body_text.size()); response_headers->insertContentType().value(Headers::get().ContentTypeValues.Text); } + + if (is_head_request) { + encode_headers(std::move(response_headers), true); + return; + } + encode_headers(std::move(response_headers), body_text.empty()); // encode_headers()) may have changed the referenced is_reset so we need to test it if (!body_text.empty() && !is_reset) { diff --git a/source/common/http/utility.h b/source/common/http/utility.h index 8a11ae5129f2..65787cb24e0a 100644 --- a/source/common/http/utility.h +++ b/source/common/http/utility.h @@ -131,9 +131,10 @@ Http1Settings parseHttp1Settings(const envoy::api::v2::core::Http1ProtocolOption * @param response_code supplies the HTTP response code. * @param body_text supplies the optional body text which is sent using the text/plain content * type. + * @param is_head_request tells if this is a response to a HEAD request */ void sendLocalReply(bool is_grpc, StreamDecoderFilterCallbacks& callbacks, const bool& is_reset, - Code response_code, const std::string& body_text); + Code response_code, const std::string& body_text, bool is_head_request); /** * Create a locally generated response using the provided lambdas. @@ -150,7 +151,8 @@ void sendLocalReply(bool is_grpc, StreamDecoderFilterCallbacks& callbacks, const void sendLocalReply(bool is_grpc, std::function encode_headers, std::function encode_data, - const bool& is_reset, Code response_code, const std::string& body_text); + const bool& is_reset, Code response_code, const std::string& body_text, + bool is_head_request = false); struct GetLastAddressFromXffInfo { // Last valid address pulled from the XFF header. diff --git a/source/common/ratelimit/BUILD b/source/common/ratelimit/BUILD index 60ea694e3a06..2038bf255ce7 100644 --- a/source/common/ratelimit/BUILD +++ b/source/common/ratelimit/BUILD @@ -14,7 +14,7 @@ envoy_cc_library( srcs = ["ratelimit_impl.cc"], hdrs = ["ratelimit_impl.h"], deps = [ - ":ratelimit_proto", + ":ratelimit_proto_cc", "//include/envoy/grpc:async_client_interface", "//include/envoy/grpc:async_client_manager_interface", "//include/envoy/ratelimit:ratelimit_interface", diff --git a/source/common/request_info/utility.cc b/source/common/request_info/utility.cc index 27a01a6b2445..6d71182fff22 100644 --- a/source/common/request_info/utility.cc +++ b/source/common/request_info/utility.cc @@ -19,6 +19,7 @@ const std::string ResponseFlagUtils::DELAY_INJECTED = "DI"; const std::string ResponseFlagUtils::FAULT_INJECTED = "FI"; const std::string ResponseFlagUtils::RATE_LIMITED = "RL"; const std::string ResponseFlagUtils::UNAUTHORIZED_EXTERNAL_SERVICE = "UAEX"; +const std::string ResponseFlagUtils::RATELIMIT_SERVICE_ERROR = "RLSE"; void ResponseFlagUtils::appendString(std::string& result, const std::string& append) { if (result.empty()) { @@ -31,7 +32,7 @@ void ResponseFlagUtils::appendString(std::string& result, const std::string& app const std::string ResponseFlagUtils::toShortString(const RequestInfo& request_info) { std::string result; - static_assert(ResponseFlag::LastFlag == 0x1000, "A flag has been added. Fix this code."); + static_assert(ResponseFlag::LastFlag == 0x2000, "A flag has been added. Fix this code."); if (request_info.hasResponseFlag(ResponseFlag::FailedLocalHealthCheck)) { appendString(result, FAILED_LOCAL_HEALTH_CHECK); @@ -85,6 +86,10 @@ const std::string ResponseFlagUtils::toShortString(const RequestInfo& request_in appendString(result, UNAUTHORIZED_EXTERNAL_SERVICE); } + if (request_info.hasResponseFlag(ResponseFlag::RateLimitServiceError)) { + appendString(result, RATELIMIT_SERVICE_ERROR); + } + return result.empty() ? NONE : result; } @@ -104,6 +109,7 @@ absl::optional ResponseFlagUtils::toResponseFlag(const std::string {ResponseFlagUtils::FAULT_INJECTED, ResponseFlag::FaultInjected}, {ResponseFlagUtils::RATE_LIMITED, ResponseFlag::RateLimited}, {ResponseFlagUtils::UNAUTHORIZED_EXTERNAL_SERVICE, ResponseFlag::UnauthorizedExternalService}, + {ResponseFlagUtils::RATELIMIT_SERVICE_ERROR, ResponseFlag::RateLimitServiceError}, }; const auto& it = map.find(flag); if (it != map.end()) { diff --git a/source/common/request_info/utility.h b/source/common/request_info/utility.h index 879bccd196cf..209a8a963dde 100644 --- a/source/common/request_info/utility.h +++ b/source/common/request_info/utility.h @@ -34,6 +34,7 @@ class ResponseFlagUtils { const static std::string FAULT_INJECTED; const static std::string RATE_LIMITED; const static std::string UNAUTHORIZED_EXTERNAL_SERVICE; + const static std::string RATELIMIT_SERVICE_ERROR; }; /** diff --git a/source/common/router/header_formatter.cc b/source/common/router/header_formatter.cc index b76fd90f5bbe..d171b82d25c7 100644 --- a/source/common/router/header_formatter.cc +++ b/source/common/router/header_formatter.cc @@ -11,6 +11,7 @@ #include "common/json/json_loader.h" #include "common/request_info/utility.h" +#include "absl/strings/str_cat.h" #include "absl/types/optional.h" namespace Envoy { @@ -143,9 +144,13 @@ RequestInfoHeaderFormatter::RequestInfoHeaderFormatter(absl::string_view field_n } field_extractor_ = [this, pattern](const Envoy::RequestInfo::RequestInfo& request_info) { const auto& formatters = start_time_formatters_.at(pattern); - ASSERT(formatters.size() == 1); Http::HeaderMapImpl empty_map; - return formatters.at(0)->format(empty_map, empty_map, empty_map, request_info); + std::string formatted; + for (const auto& formatter : formatters) { + absl::StrAppend(&formatted, + formatter->format(empty_map, empty_map, empty_map, request_info)); + } + return formatted; }; } else if (field_name.find("UPSTREAM_METADATA") == 0) { field_extractor_ = diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 544cda92b8f7..0423256bcc10 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -779,7 +779,7 @@ Filter::UpstreamRequest::UpstreamRequest(Filter& parent, Http::ConnectionPool::I if (parent_.config_.start_child_span_) { span_ = parent_.callbacks_->activeSpan().spawnChild( parent_.callbacks_->tracingConfig(), "router " + parent.cluster_->name() + " egress", - ProdSystemTimeSource::instance_.currentTime()); + parent.timeSource().systemTime()); span_->setTag(Tracing::Tags::get().COMPONENT, Tracing::Tags::get().PROXY); } diff --git a/source/common/router/router.h b/source/common/router/router.h index 53ce57103288..7709d6b840a0 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -115,6 +115,7 @@ class FilterConfig { } ShadowWriter& shadowWriter() { return *shadow_writer_; } + TimeSource& timeSource() { return cm_.timeSource(); } Stats::Scope& scope_; const LocalInfo::LocalInfo& local_info_; @@ -138,7 +139,7 @@ typedef std::shared_ptr FilterConfigSharedPtr; */ class Filter : Logger::Loggable, public Http::StreamDecoderFilter, - public Upstream::LoadBalancerContext { + public Upstream::LoadBalancerContextBase { public: Filter(FilterConfig& config) : config_(config), downstream_response_started_(false), downstream_end_stream_(false), @@ -345,6 +346,7 @@ class Filter : Logger::Loggable, // Called immediately after a non-5xx header is received from upstream, performs stats accounting // and handle difference between gRPC and non-gRPC requests. void handleNon5xxResponseHeaders(const Http::HeaderMap& headers, bool end_stream); + TimeSource& timeSource() { return config_.timeSource(); } FilterConfig& config_; Http::StreamDecoderFilterCallbacks* callbacks_{}; diff --git a/source/common/secret/BUILD b/source/common/secret/BUILD index 598d8249fa5e..045b4ef70865 100644 --- a/source/common/secret/BUILD +++ b/source/common/secret/BUILD @@ -13,8 +13,11 @@ envoy_cc_library( srcs = ["secret_manager_impl.cc"], hdrs = ["secret_manager_impl.h"], deps = [ + ":sds_api_lib", ":secret_provider_impl_lib", "//include/envoy/secret:secret_manager_interface", + "//include/envoy/server:transport_socket_config_interface", + "//source/common/common:assert_lib", "//source/common/common:minimal_logger_lib", "@envoy_api//envoy/api/v2/auth:cert_cc", ], @@ -30,3 +33,24 @@ envoy_cc_library( "@envoy_api//envoy/api/v2/auth:cert_cc", ], ) + +envoy_cc_library( + name = "sds_api_lib", + srcs = ["sds_api.cc"], + hdrs = ["sds_api.h"], + deps = [ + "//include/envoy/config:subscription_interface", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/init:init_interface", + "//include/envoy/local_info:local_info_interface", + "//include/envoy/runtime:runtime_interface", + "//include/envoy/secret:secret_provider_interface", + "//include/envoy/stats:stats_interface", + "//source/common/common:callback_impl_lib", + "//source/common/common:cleanup_lib", + "//source/common/config:resources_lib", + "//source/common/config:subscription_factory_lib", + "//source/common/protobuf:utility_lib", + "//source/common/ssl:tls_certificate_config_impl_lib", + ], +) diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc new file mode 100644 index 000000000000..3129c3c05c85 --- /dev/null +++ b/source/common/secret/sds_api.cc @@ -0,0 +1,85 @@ +#include "common/secret/sds_api.h" + +#include + +#include "envoy/api/v2/auth/cert.pb.validate.h" + +#include "common/config/resources.h" +#include "common/config/subscription_factory.h" +#include "common/protobuf/utility.h" +#include "common/ssl/tls_certificate_config_impl.h" + +namespace Envoy { +namespace Secret { + +SdsApi::SdsApi(const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, Stats::Store& stats, + Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, + const envoy::api::v2::core::ConfigSource& sds_config, std::string sds_config_name, + std::function destructor_cb) + : local_info_(local_info), dispatcher_(dispatcher), random_(random), stats_(stats), + cluster_manager_(cluster_manager), sds_config_(sds_config), sds_config_name_(sds_config_name), + secret_hash_(0), clean_up_(destructor_cb) { + // TODO(JimmyCYJ): Implement chained_init_manager, so that multiple init_manager + // can be chained together to behave as one init_manager. In that way, we let + // two listeners which share same SdsApi to register at separate init managers, and + // each init manager has a chance to initialize its targets. + init_manager.registerTarget(*this); +} + +void SdsApi::initialize(std::function callback) { + initialize_callback_ = callback; + + subscription_ = Envoy::Config::SubscriptionFactory::subscriptionFromConfigSource< + envoy::api::v2::auth::Secret>( + sds_config_, local_info_, dispatcher_, cluster_manager_, random_, stats_, + /* rest_legacy_constructor */ nullptr, + "envoy.service.discovery.v2.SecretDiscoveryService.FetchSecrets", + "envoy.service.discovery.v2.SecretDiscoveryService.StreamSecrets"); + Config::Utility::checkLocalInfo("sds", local_info_); + + subscription_->start({sds_config_name_}, *this); +} + +void SdsApi::onConfigUpdate(const ResourceVector& resources, const std::string&) { + if (resources.empty()) { + throw EnvoyException( + fmt::format("Missing SDS resources for {} in onConfigUpdate()", sds_config_name_)); + } + if (resources.size() != 1) { + throw EnvoyException(fmt::format("Unexpected SDS secrets length: {}", resources.size())); + } + const auto& secret = resources[0]; + MessageUtil::validate(secret); + if (secret.name() != sds_config_name_) { + throw EnvoyException( + fmt::format("Unexpected SDS secret (expecting {}): {}", sds_config_name_, secret.name())); + } + + const uint64_t new_hash = MessageUtil::hash(secret); + if (new_hash != secret_hash_ && + secret.type_case() == envoy::api::v2::auth::Secret::TypeCase::kTlsCertificate) { + secret_hash_ = new_hash; + tls_certificate_secrets_ = + std::make_unique(secret.tls_certificate()); + + update_callback_manager_.runCallbacks(); + } + + runInitializeCallbackIfAny(); +} + +void SdsApi::onConfigUpdateFailed(const EnvoyException*) { + // We need to allow server startup to continue, even if we have a bad config. + runInitializeCallbackIfAny(); +} + +void SdsApi::runInitializeCallbackIfAny() { + if (initialize_callback_) { + initialize_callback_(); + initialize_callback_ = nullptr; + } +} + +} // namespace Secret +} // namespace Envoy diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h new file mode 100644 index 000000000000..f9211e3832b2 --- /dev/null +++ b/source/common/secret/sds_api.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/api/v2/core/config_source.pb.h" +#include "envoy/config/subscription.h" +#include "envoy/event/dispatcher.h" +#include "envoy/init/init.h" +#include "envoy/local_info/local_info.h" +#include "envoy/runtime/runtime.h" +#include "envoy/secret/secret_callbacks.h" +#include "envoy/secret/secret_provider.h" +#include "envoy/stats/stats.h" +#include "envoy/upstream/cluster_manager.h" + +#include "common/common/callback_impl.h" +#include "common/common/cleanup.h" + +namespace Envoy { +namespace Secret { + +/** + * SDS API implementation that fetches secrets from SDS server via Subscription. + */ +class SdsApi : public Init::Target, + public TlsCertificateConfigProvider, + public Config::SubscriptionCallbacks { +public: + SdsApi(const LocalInfo::LocalInfo& local_info, Event::Dispatcher& dispatcher, + Runtime::RandomGenerator& random, Stats::Store& stats, + Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, + const envoy::api::v2::core::ConfigSource& sds_config, std::string sds_config_name, + std::function destructor_cb); + + // Init::Target + void initialize(std::function callback) override; + + // Config::SubscriptionCallbacks + void onConfigUpdate(const ResourceVector& resources, const std::string& version_info) override; + void onConfigUpdateFailed(const EnvoyException* e) override; + std::string resourceName(const ProtobufWkt::Any& resource) override { + return MessageUtil::anyConvert(resource).name(); + } + + // SecretProvider + const Ssl::TlsCertificateConfig* secret() const override { + return tls_certificate_secrets_.get(); + } + + Common::CallbackHandle* addUpdateCallback(std::function callback) override { + return update_callback_manager_.add(callback); + } + +private: + void runInitializeCallbackIfAny(); + + const LocalInfo::LocalInfo& local_info_; + Event::Dispatcher& dispatcher_; + Runtime::RandomGenerator& random_; + Stats::Store& stats_; + Upstream::ClusterManager& cluster_manager_; + + const envoy::api::v2::core::ConfigSource sds_config_; + std::unique_ptr> subscription_; + std::function initialize_callback_; + const std::string sds_config_name_; + + uint64_t secret_hash_; + Cleanup clean_up_; + Ssl::TlsCertificateConfigPtr tls_certificate_secrets_; + Common::CallbackManager<> update_callback_manager_; +}; + +typedef std::unique_ptr SdsApiPtr; + +} // namespace Secret +} // namespace Envoy diff --git a/source/common/secret/secret_manager_impl.cc b/source/common/secret/secret_manager_impl.cc index f3f2c9549ea8..862c9ae09901 100644 --- a/source/common/secret/secret_manager_impl.cc +++ b/source/common/secret/secret_manager_impl.cc @@ -3,6 +3,7 @@ #include "envoy/common/exception.h" #include "common/common/assert.h" +#include "common/secret/sds_api.h" #include "common/secret/secret_provider_impl.h" #include "common/ssl/tls_certificate_config_impl.h" @@ -37,5 +38,37 @@ TlsCertificateConfigProviderSharedPtr SecretManagerImpl::createInlineTlsCertific return std::make_shared(tls_certificate); } +void SecretManagerImpl::removeDynamicSecretProvider(const std::string& map_key) { + ENVOY_LOG(debug, "Unregister secret provider. hash key: {}", map_key); + + RELEASE_ASSERT(dynamic_secret_providers_.erase(map_key) == 1, ""); +} + +TlsCertificateConfigProviderSharedPtr SecretManagerImpl::findOrCreateDynamicSecretProvider( + const envoy::api::v2::core::ConfigSource& sds_config_source, const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) { + const std::string map_key = std::to_string(MessageUtil::hash(sds_config_source)) + config_name; + + auto secret_provider = dynamic_secret_providers_[map_key].lock(); + if (!secret_provider) { + ASSERT(secret_provider_context.initManager() != nullptr); + + // SdsApi is owned by ListenerImpl and ClusterInfo which are destroyed before + // SecretManagerImpl. It is safe to invoke this callback at the destructor of SdsApi. + std::function unregister_secret_provider = [map_key, this]() { + removeDynamicSecretProvider(map_key); + }; + + secret_provider = std::make_shared( + secret_provider_context.localInfo(), secret_provider_context.dispatcher(), + secret_provider_context.random(), secret_provider_context.stats(), + secret_provider_context.clusterManager(), *secret_provider_context.initManager(), + sds_config_source, config_name, unregister_secret_provider); + dynamic_secret_providers_[map_key] = secret_provider; + } + + return secret_provider; +} + } // namespace Secret } // namespace Envoy diff --git a/source/common/secret/secret_manager_impl.h b/source/common/secret/secret_manager_impl.h index 6af790f50c42..b68047281d36 100644 --- a/source/common/secret/secret_manager_impl.h +++ b/source/common/secret/secret_manager_impl.h @@ -4,6 +4,7 @@ #include "envoy/secret/secret_manager.h" #include "envoy/secret/secret_provider.h" +#include "envoy/server/transport_socket_config.h" #include "envoy/ssl/tls_certificate_config.h" #include "common/common/logger.h" @@ -11,17 +12,31 @@ namespace Envoy { namespace Secret { -class SecretManagerImpl : public SecretManager, Logger::Loggable { +class SecretManagerImpl : public SecretManager, Logger::Loggable { public: void addStaticSecret(const envoy::api::v2::auth::Secret& secret) override; + TlsCertificateConfigProviderSharedPtr findStaticTlsCertificateProvider(const std::string& name) const override; + TlsCertificateConfigProviderSharedPtr createInlineTlsCertificateProvider( const envoy::api::v2::auth::TlsCertificate& tls_certificate) override; + TlsCertificateConfigProviderSharedPtr findOrCreateDynamicSecretProvider( + const envoy::api::v2::core::ConfigSource& config_source, const std::string& config_name, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context) override; + private: + // Remove dynamic secret provider which has been deleted. + void removeDynamicSecretProvider(const std::string& map_key); + + // Manages pairs of secret name and TlsCertificateConfigProviderSharedPtr. std::unordered_map static_tls_certificate_providers_; + + // map hash code of SDS config source and SdsApi object. + std::unordered_map> + dynamic_secret_providers_; }; } // namespace Secret diff --git a/source/common/secret/secret_provider_impl.h b/source/common/secret/secret_provider_impl.h index 9ac79c66009c..959eecef5fa9 100644 --- a/source/common/secret/secret_provider_impl.h +++ b/source/common/secret/secret_provider_impl.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "envoy/api/v2/auth/cert.pb.h" #include "envoy/secret/secret_provider.h" #include "envoy/ssl/tls_certificate_config.h" @@ -13,6 +15,8 @@ class TlsCertificateConfigProviderImpl : public TlsCertificateConfigProvider { const Ssl::TlsCertificateConfig* secret() const override { return tls_certificate_.get(); } + Common::CallbackHandle* addUpdateCallback(std::function) override { return nullptr; } + private: Ssl::TlsCertificateConfigPtr tls_certificate_; }; diff --git a/source/common/ssl/BUILD b/source/common/ssl/BUILD index abc98f802ff0..8a4a4b015432 100644 --- a/source/common/ssl/BUILD +++ b/source/common/ssl/BUILD @@ -19,6 +19,7 @@ envoy_cc_library( ":utility_lib", "//include/envoy/network:connection_interface", "//include/envoy/network:transport_socket_interface", + "//include/envoy/stats:stats_macros", "//source/common/common:assert_lib", "//source/common/common:empty_string", "//source/common/common:minimal_logger_lib", @@ -34,8 +35,9 @@ envoy_cc_library( "ssl", ], deps = [ - "//include/envoy/secret:secret_manager_interface", + "//include/envoy/secret:secret_callbacks_interface", "//include/envoy/secret:secret_provider_interface", + "//include/envoy/server:transport_socket_config_interface", "//include/envoy/ssl:context_config_interface", "//source/common/common:assert_lib", "//source/common/common:empty_string", diff --git a/source/common/ssl/context_config_impl.cc b/source/common/ssl/context_config_impl.cc index fd9f49ed210c..242b041a527a 100644 --- a/source/common/ssl/context_config_impl.cc +++ b/source/common/ssl/context_config_impl.cc @@ -16,26 +16,31 @@ namespace Ssl { namespace { -Secret::TlsCertificateConfigProviderSharedPtr -getTlsCertificateConfigProvider(const envoy::api::v2::auth::CommonTlsContext& config, - Secret::SecretManager& secret_manager) { +Secret::TlsCertificateConfigProviderSharedPtr getTlsCertificateConfigProvider( + const envoy::api::v2::auth::CommonTlsContext& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) { if (!config.tls_certificates().empty()) { const auto& tls_certificate = config.tls_certificates(0); if (!tls_certificate.has_certificate_chain() && !tls_certificate.has_private_key()) { return nullptr; } - return secret_manager.createInlineTlsCertificateProvider(config.tls_certificates(0)); + return factory_context.secretManager().createInlineTlsCertificateProvider( + config.tls_certificates(0)); } if (!config.tls_certificate_sds_secret_configs().empty()) { const auto& sds_secret_config = config.tls_certificate_sds_secret_configs(0); - - auto secret_provider = - secret_manager.findStaticTlsCertificateProvider(sds_secret_config.name()); - if (!secret_provider) { - throw EnvoyException( - fmt::format("Static secret is not defined: {}", sds_secret_config.name())); + if (!sds_secret_config.has_sds_config()) { + // static secret + auto secret_provider = factory_context.secretManager().findStaticTlsCertificateProvider( + sds_secret_config.name()); + if (!secret_provider) { + throw EnvoyException(fmt::format("Unknown static secret: {}", sds_secret_config.name())); + } + return secret_provider; + } else { + return factory_context.secretManager().findOrCreateDynamicSecretProvider( + sds_secret_config.sds_config(), sds_secret_config.name(), factory_context); } - return secret_provider; } return nullptr; } @@ -58,8 +63,9 @@ const std::string ContextConfigImpl::DEFAULT_CIPHER_SUITES = const std::string ContextConfigImpl::DEFAULT_ECDH_CURVES = "X25519:P-256"; -ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContext& config, - Secret::SecretManager& secret_manager) +ContextConfigImpl::ContextConfigImpl( + const envoy::api::v2::auth::CommonTlsContext& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) : alpn_protocols_(RepeatedPtrUtil::join(config.alpn_protocols(), ",")), alt_alpn_protocols_(config.deprecated_v1().alt_alpn_protocols()), cipher_suites_(StringUtil::nonEmptyStringOrDefault( @@ -73,7 +79,8 @@ ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContex Config::DataSource::read(config.validation_context().crl(), true)), certificate_revocation_list_path_( Config::DataSource::getPath(config.validation_context().crl()).value_or(EMPTY_STRING)), - tls_certficate_provider_(getTlsCertificateConfigProvider(config, secret_manager)), + secret_callback_(nullptr), + tls_certficate_provider_(getTlsCertificateConfigProvider(config, factory_context)), verify_subject_alt_name_list_(config.validation_context().verify_subject_alt_name().begin(), config.validation_context().verify_subject_alt_name().end()), verify_certificate_hash_list_(config.validation_context().verify_certificate_hash().begin(), @@ -85,6 +92,7 @@ ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContex tlsVersionFromProto(config.tls_params().tls_minimum_protocol_version(), TLS1_VERSION)), max_protocol_version_( tlsVersionFromProto(config.tls_params().tls_maximum_protocol_version(), TLS1_2_VERSION)) { + if (ca_cert_.empty()) { if (!certificate_revocation_list_.empty()) { throw EnvoyException(fmt::format("Failed to load CRL from {} without trusted CA", @@ -101,6 +109,12 @@ ContextConfigImpl::ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContex } } +ContextConfigImpl::~ContextConfigImpl() { + if (tls_certficate_provider_.get() != nullptr && secret_callback_ != nullptr) { + tls_certficate_provider_->removeUpdateCallback(*secret_callback_); + } +} + unsigned ContextConfigImpl::tlsVersionFromProto( const envoy::api::v2::auth::TlsParameters_TlsProtocol& version, unsigned default_version) { switch (version) { @@ -122,8 +136,9 @@ unsigned ContextConfigImpl::tlsVersionFromProto( } ClientContextConfigImpl::ClientContextConfigImpl( - const envoy::api::v2::auth::UpstreamTlsContext& config, Secret::SecretManager& secret_manager) - : ContextConfigImpl(config.common_tls_context(), secret_manager), + const envoy::api::v2::auth::UpstreamTlsContext& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) + : ContextConfigImpl(config.common_tls_context(), factory_context), server_name_indication_(config.sni()), allow_renegotiation_(config.allow_renegotiation()) { // BoringSSL treats this as a C string, so embedded NULL characters will not // be handled correctly. @@ -137,19 +152,21 @@ ClientContextConfigImpl::ClientContextConfigImpl( } } -ClientContextConfigImpl::ClientContextConfigImpl(const Json::Object& config, - Secret::SecretManager& secret_manager) +ClientContextConfigImpl::ClientContextConfigImpl( + const Json::Object& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) : ClientContextConfigImpl( [&config] { envoy::api::v2::auth::UpstreamTlsContext upstream_tls_context; Config::TlsContextJson::translateUpstreamTlsContext(config, upstream_tls_context); return upstream_tls_context; }(), - secret_manager) {} + factory_context) {} ServerContextConfigImpl::ServerContextConfigImpl( - const envoy::api::v2::auth::DownstreamTlsContext& config, Secret::SecretManager& secret_manager) - : ContextConfigImpl(config.common_tls_context(), secret_manager), + const envoy::api::v2::auth::DownstreamTlsContext& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) + : ContextConfigImpl(config.common_tls_context(), factory_context), require_client_certificate_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, require_client_certificate, false)), session_ticket_keys_([&config] { @@ -180,15 +197,16 @@ ServerContextConfigImpl::ServerContextConfigImpl( } } -ServerContextConfigImpl::ServerContextConfigImpl(const Json::Object& config, - Secret::SecretManager& secret_manager) +ServerContextConfigImpl::ServerContextConfigImpl( + const Json::Object& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) : ServerContextConfigImpl( [&config] { envoy::api::v2::auth::DownstreamTlsContext downstream_tls_context; Config::TlsContextJson::translateDownstreamTlsContext(config, downstream_tls_context); return downstream_tls_context; }(), - secret_manager) {} + factory_context) {} // Append a SessionTicketKey to keys, initializing it with key_data. // Throws if key_data is invalid. diff --git a/source/common/ssl/context_config_impl.h b/source/common/ssl/context_config_impl.h index cb6b1c92c969..ee378e5208a1 100644 --- a/source/common/ssl/context_config_impl.h +++ b/source/common/ssl/context_config_impl.h @@ -4,8 +4,9 @@ #include #include "envoy/api/v2/auth/cert.pb.h" -#include "envoy/secret/secret_manager.h" +#include "envoy/secret/secret_callbacks.h" #include "envoy/secret/secret_provider.h" +#include "envoy/server/transport_socket_config.h" #include "envoy/ssl/context_config.h" #include "common/common/empty_string.h" @@ -18,6 +19,8 @@ static const std::string INLINE_STRING = ""; class ContextConfigImpl : public virtual Ssl::ContextConfig { public: + ~ContextConfigImpl() override; + // Ssl::ContextConfig const std::string& alpnProtocols() const override { return alpn_protocols_; } const std::string& altAlpnProtocols() const override { return alt_alpn_protocols_; } @@ -51,9 +54,25 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { unsigned minProtocolVersion() const override { return min_protocol_version_; }; unsigned maxProtocolVersion() const override { return max_protocol_version_; }; + bool isReady() const override { + // either tls_certficate_provider_ is nullptr or + // tls_certficate_provider_->secret() is NOT nullptr. + return !tls_certficate_provider_ || tls_certficate_provider_->secret(); + } + + void setSecretUpdateCallback(Secret::SecretCallbacks& callback) override { + if (tls_certficate_provider_) { + if (secret_callback_) { + tls_certficate_provider_->removeUpdateCallback(*secret_callback_); + } + secret_callback_ = &callback; + tls_certficate_provider_->addUpdateCallback(callback); + } + } + protected: ContextConfigImpl(const envoy::api::v2::auth::CommonTlsContext& config, - Secret::SecretManager& secret_manager); + Server::Configuration::TransportSocketFactoryContext& factory_context); private: static unsigned @@ -71,6 +90,7 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { const std::string ca_cert_path_; const std::string certificate_revocation_list_; const std::string certificate_revocation_list_path_; + Secret::SecretCallbacks* secret_callback_; Secret::TlsCertificateConfigProviderSharedPtr tls_certficate_provider_; const std::vector verify_subject_alt_name_list_; const std::vector verify_certificate_hash_list_; @@ -82,10 +102,12 @@ class ContextConfigImpl : public virtual Ssl::ContextConfig { class ClientContextConfigImpl : public ContextConfigImpl, public ClientContextConfig { public: - explicit ClientContextConfigImpl(const envoy::api::v2::auth::UpstreamTlsContext& config, - Secret::SecretManager& secret_manager); - explicit ClientContextConfigImpl(const Json::Object& config, - Secret::SecretManager& secret_manager); + explicit ClientContextConfigImpl( + const envoy::api::v2::auth::UpstreamTlsContext& config, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context); + explicit ClientContextConfigImpl( + const Json::Object& config, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context); // Ssl::ClientContextConfig const std::string& serverNameIndication() const override { return server_name_indication_; } @@ -98,10 +120,12 @@ class ClientContextConfigImpl : public ContextConfigImpl, public ClientContextCo class ServerContextConfigImpl : public ContextConfigImpl, public ServerContextConfig { public: - explicit ServerContextConfigImpl(const envoy::api::v2::auth::DownstreamTlsContext& config, - Secret::SecretManager& secret_manager); - explicit ServerContextConfigImpl(const Json::Object& config, - Secret::SecretManager& secret_manager); + explicit ServerContextConfigImpl( + const envoy::api::v2::auth::DownstreamTlsContext& config, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context); + explicit ServerContextConfigImpl( + const Json::Object& config, + Server::Configuration::TransportSocketFactoryContext& secret_provider_context); // Ssl::ServerContextConfig bool requireClientCertificate() const override { return require_client_certificate_; } diff --git a/source/common/ssl/context_manager_impl.cc b/source/common/ssl/context_manager_impl.cc index 0409a608332b..2b5958a8152a 100644 --- a/source/common/ssl/context_manager_impl.cc +++ b/source/common/ssl/context_manager_impl.cc @@ -22,6 +22,10 @@ void ContextManagerImpl::removeEmptyContexts() { ClientContextSharedPtr ContextManagerImpl::createSslClientContext(Stats::Scope& scope, const ClientContextConfig& config) { + if (!config.isReady()) { + return nullptr; + } + ClientContextSharedPtr context = std::make_shared(scope, config); removeEmptyContexts(); contexts_.emplace_back(context); @@ -31,6 +35,10 @@ ContextManagerImpl::createSslClientContext(Stats::Scope& scope, const ClientCont ServerContextSharedPtr ContextManagerImpl::createSslServerContext(Stats::Scope& scope, const ServerContextConfig& config, const std::vector& server_names) { + if (!config.isReady()) { + return nullptr; + } + ServerContextSharedPtr context = std::make_shared(scope, config, server_names, runtime_); removeEmptyContexts(); diff --git a/source/common/ssl/ssl_socket.cc b/source/common/ssl/ssl_socket.cc index 3f634c7d9fa2..29a6e5deba6e 100644 --- a/source/common/ssl/ssl_socket.cc +++ b/source/common/ssl/ssl_socket.cc @@ -17,6 +17,26 @@ using Envoy::Network::PostIoAction; namespace Envoy { namespace Ssl { +namespace { + +// This SslSocket will be used when SSL secret is not fetched from SDS server. +class NotReadySslSocket : public Network::TransportSocket { +public: + // Network::TransportSocket + void setTransportSocketCallbacks(Network::TransportSocketCallbacks&) override {} + std::string protocol() const override { return EMPTY_STRING; } + bool canFlushClose() override { return true; } + void closeSocket(Network::ConnectionEvent) override {} + Network::IoResult doRead(Buffer::Instance&) override { return {PostIoAction::Close, 0, false}; } + Network::IoResult doWrite(Buffer::Instance&, bool) override { + return {PostIoAction::Close, 0, false}; + } + void onConnected() override {} + const Ssl::Connection* ssl() const override { return nullptr; } +}; + +} // namespace + SslSocket::SslSocket(ContextSharedPtr ctx, InitialState state) : ctx_(std::dynamic_pointer_cast(ctx)), ssl_(ctx_->newSsl()) { if (state == InitialState::Client) { @@ -384,31 +404,67 @@ std::string SslSocket::subjectLocalCertificate() const { return getSubjectFromCertificate(cert); } +namespace { +SslSocketFactoryStats generateStats(const std::string& prefix, Stats::Scope& store) { + return { + ALL_SSL_SOCKET_FACTORY_STATS(POOL_COUNTER_PREFIX(store, prefix + "_ssl_socket_factory."))}; +} +} // namespace + ClientSslSocketFactory::ClientSslSocketFactory(ClientContextConfigPtr config, Ssl::ContextManager& manager, Stats::Scope& stats_scope) - : manager_(manager), stats_scope_(stats_scope), config_(std::move(config)), - ssl_ctx_(manager_.createSslClientContext(stats_scope_, *config_)) {} + : manager_(manager), stats_scope_(stats_scope), stats_(generateStats("client", stats_scope)), + config_(std::move(config)), + ssl_ctx_(manager_.createSslClientContext(stats_scope_, *config_)) { + config_->setSecretUpdateCallback(*this); +} Network::TransportSocketPtr ClientSslSocketFactory::createTransportSocket() const { - return std::make_unique(ssl_ctx_, Ssl::InitialState::Client); + if (ssl_ctx_) { + return std::make_unique(ssl_ctx_, Ssl::InitialState::Client); + } else { + ENVOY_LOG(debug, "Create NotReadySslSocket"); + stats_.upstream_connection_reset_by_sds_.inc(); + return std::make_unique(); + } } bool ClientSslSocketFactory::implementsSecureTransport() const { return true; } +void ClientSslSocketFactory::onAddOrUpdateSecret() { + ENVOY_LOG(debug, "Secret is updated."); + ssl_ctx_ = manager_.createSslClientContext(stats_scope_, *config_); + stats_.ssl_context_update_by_sds_.inc(); +} + ServerSslSocketFactory::ServerSslSocketFactory(ServerContextConfigPtr config, Ssl::ContextManager& manager, Stats::Scope& stats_scope, const std::vector& server_names) - : manager_(manager), stats_scope_(stats_scope), config_(std::move(config)), - server_names_(server_names), - ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_)) {} + : manager_(manager), stats_scope_(stats_scope), stats_(generateStats("server", stats_scope)), + config_(std::move(config)), server_names_(server_names), + ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_)) { + config_->setSecretUpdateCallback(*this); +} Network::TransportSocketPtr ServerSslSocketFactory::createTransportSocket() const { - return std::make_unique(ssl_ctx_, Ssl::InitialState::Server); + if (ssl_ctx_) { + return std::make_unique(ssl_ctx_, Ssl::InitialState::Server); + } else { + ENVOY_LOG(debug, "Create NotReadySslSocket"); + stats_.downstream_connection_reset_by_sds_.inc(); + return std::make_unique(); + } } bool ServerSslSocketFactory::implementsSecureTransport() const { return true; } +void ServerSslSocketFactory::onAddOrUpdateSecret() { + ENVOY_LOG(debug, "Secret is updated."); + ssl_ctx_ = manager_.createSslServerContext(stats_scope_, *config_, server_names_); + stats_.ssl_context_update_by_sds_.inc(); +} + } // namespace Ssl } // namespace Envoy diff --git a/source/common/ssl/ssl_socket.h b/source/common/ssl/ssl_socket.h index c6b8f92b4d63..b195e3d4ed7d 100644 --- a/source/common/ssl/ssl_socket.h +++ b/source/common/ssl/ssl_socket.h @@ -5,7 +5,9 @@ #include "envoy/network/connection.h" #include "envoy/network/transport_socket.h" +#include "envoy/secret/secret_callbacks.h" #include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" #include "common/common/logger.h" #include "common/ssl/context_impl.h" @@ -15,6 +17,20 @@ namespace Envoy { namespace Ssl { +// clang-format off +#define ALL_SSL_SOCKET_FACTORY_STATS(COUNTER) \ + COUNTER(ssl_context_update_by_sds) \ + COUNTER(upstream_connection_reset_by_sds) \ + COUNTER(downstream_connection_reset_by_sds) +// clang-format on + +/** + * Wrapper struct for SSL socket factory stats. @see stats_macros.h + */ +struct SslSocketFactoryStats { + ALL_SSL_SOCKET_FACTORY_STATS(GENERATE_COUNTER_STRUCT) +}; + enum class InitialState { Client, Server }; class SslSocket : public Network::TransportSocket, @@ -67,7 +83,9 @@ class SslSocket : public Network::TransportSocket, mutable std::string cached_url_encoded_pem_encoded_peer_certificate_; }; -class ClientSslSocketFactory : public Network::TransportSocketFactory { +class ClientSslSocketFactory : public Network::TransportSocketFactory, + public Secret::SecretCallbacks, + Logger::Loggable { public: ClientSslSocketFactory(ClientContextConfigPtr config, Ssl::ContextManager& manager, Stats::Scope& stats_scope); @@ -75,14 +93,20 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory { Network::TransportSocketPtr createTransportSocket() const override; bool implementsSecureTransport() const override; + // Secret::SecretCallbacks + void onAddOrUpdateSecret() override; + private: Ssl::ContextManager& manager_; Stats::Scope& stats_scope_; + SslSocketFactoryStats stats_; ClientContextConfigPtr config_; ClientContextSharedPtr ssl_ctx_; }; -class ServerSslSocketFactory : public Network::TransportSocketFactory { +class ServerSslSocketFactory : public Network::TransportSocketFactory, + public Secret::SecretCallbacks, + Logger::Loggable { public: ServerSslSocketFactory(ServerContextConfigPtr config, Ssl::ContextManager& manager, Stats::Scope& stats_scope, const std::vector& server_names); @@ -90,9 +114,13 @@ class ServerSslSocketFactory : public Network::TransportSocketFactory { Network::TransportSocketPtr createTransportSocket() const override; bool implementsSecureTransport() const override; + // Secret::SecretCallbacks + void onAddOrUpdateSecret() override; + private: Ssl::ContextManager& manager_; Stats::Scope& stats_scope_; + SslSocketFactoryStats stats_; ServerContextConfigPtr config_; const std::vector server_names_; ServerContextSharedPtr ssl_ctx_; diff --git a/source/common/tcp_proxy/BUILD b/source/common/tcp_proxy/BUILD index 61de5015db42..589a6357e238 100644 --- a/source/common/tcp_proxy/BUILD +++ b/source/common/tcp_proxy/BUILD @@ -36,6 +36,7 @@ envoy_cc_library( "//source/common/network:utility_lib", "//source/common/request_info:request_info_lib", "//source/common/router:metadatamatchcriteria_lib", + "//source/common/upstream:load_balancer_lib", "@envoy_api//envoy/config/filter/network/tcp_proxy/v2:tcp_proxy_cc", ], ) diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 923f992deb2e..3fa588f3702d 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -24,6 +24,7 @@ #include "common/network/filter_impl.h" #include "common/network/utility.h" #include "common/request_info/request_info_impl.h" +#include "common/upstream/load_balancer_impl.h" namespace Envoy { namespace TcpProxy { @@ -140,8 +141,8 @@ typedef std::shared_ptr ConfigSharedPtr; * be proxied back and forth between the two connections. */ class Filter : public Network::ReadFilter, + public Upstream::LoadBalancerContextBase, Tcp::ConnectionPool::Callbacks, - Upstream::LoadBalancerContext, protected Logger::Loggable { public: Filter(ConfigSharedPtr config, Upstream::ClusterManager& cluster_manager); @@ -159,7 +160,6 @@ class Filter : public Network::ReadFilter, Upstream::HostDescriptionConstSharedPtr host) override; // Upstream::LoadBalancerContext - absl::optional computeHashKey() override { return {}; } const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return config_->metadataMatchCriteria(); } @@ -168,8 +168,6 @@ class Filter : public Network::ReadFilter, return &read_callbacks_->connection(); } - const Http::HeaderMap* downstreamHeaders() const override { return nullptr; } - // These two functions allow enabling/disabling reads on the upstream and downstream connections. // They are called by the Downstream/Upstream Watermark callbacks to limit buffering. void readDisableUpstream(bool disable); diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index 2d43d7509e81..5dc2bdd54ca6 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -406,6 +406,8 @@ envoy_cc_library( "//source/common/stats:isolated_store_lib", "//source/common/stats:stats_lib", "//source/common/upstream:locality_lib", + "//source/server:init_manager_lib", + "//source/server:transport_socket_config_lib", "@envoy_api//envoy/api/v2/core:base_cc", "@envoy_api//envoy/api/v2/endpoint:endpoint_cc", ], diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 4be71f6cee18..3e71e271651a 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -172,8 +172,7 @@ ClusterManagerImpl::ClusterManagerImpl(const envoy::config::bootstrap::v2::Boots const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager, Event::Dispatcher& main_thread_dispatcher, - Server::Admin& admin, SystemTimeSource& system_time_source, - MonotonicTimeSource& monotonic_time_source) + Server::Admin& admin) : factory_(factory), runtime_(runtime), stats_(stats), tls_(tls.allocateSlot()), random_(random), log_manager_(log_manager), bind_config_(bootstrap.cluster_manager().upstream_bind_config()), local_info_(local_info), @@ -181,14 +180,15 @@ ClusterManagerImpl::ClusterManagerImpl(const envoy::config::bootstrap::v2::Boots init_helper_([this](Cluster& cluster) { onClusterInit(cluster); }), config_tracker_entry_( admin.getConfigTracker().add("clusters", [this] { return dumpClusterConfigs(); })), - system_time_source_(system_time_source), dispatcher_(main_thread_dispatcher) { + time_source_(main_thread_dispatcher.timeSource()), dispatcher_(main_thread_dispatcher) { async_client_manager_ = std::make_unique(*this, tls); const auto& cm_config = bootstrap.cluster_manager(); if (cm_config.has_outlier_detection()) { const std::string event_log_file_path = cm_config.outlier_detection().event_log_path(); if (!event_log_file_path.empty()) { outlier_event_logger_.reset(new Outlier::EventLoggerImpl( - log_manager, event_log_file_path, system_time_source, monotonic_time_source)); + log_manager, event_log_file_path, dispatcher_.timeSource().system(), + dispatcher_.timeSource().monotonic())); } } @@ -305,7 +305,7 @@ ClusterManagerImpl::ClusterManagerImpl(const envoy::config::bootstrap::v2::Boots Config::Utility::factoryForGrpcApiConfigSource( *async_client_manager_, load_stats_config, stats) ->create(), - main_thread_dispatcher, ProdMonotonicTimeSource::instance_)); + main_thread_dispatcher, timeSource().monotonic())); } } @@ -615,7 +615,7 @@ void ClusterManagerImpl::loadCluster(const envoy::api::v2::Cluster& cluster, } cluster_map[cluster_reference.info()->name()] = std::make_unique( - cluster, version_info, added_via_api, std::move(new_cluster), system_time_source_); + cluster, version_info, added_via_api, std::move(new_cluster), time_source_.system()); const auto cluster_entry_it = cluster_map.find(cluster_reference.info()->name()); // If an LB is thread aware, create it here. The LB is not initialized until cluster pre-init @@ -1173,8 +1173,7 @@ ClusterManagerPtr ProdClusterManagerFactory::clusterManagerFromProto( Server::Admin& admin) { return ClusterManagerPtr{new ClusterManagerImpl(bootstrap, *this, stats, tls, runtime, random, local_info, log_manager, main_thread_dispatcher_, - admin, ProdSystemTimeSource::instance_, - ProdMonotonicTimeSource::instance_)}; + admin)}; } Http::ConnectionPool::InstancePtr ProdClusterManagerFactory::allocateConnPool( diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index 426d4fac292d..45fa479e7b94 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -165,9 +165,7 @@ class ClusterManagerImpl : public ClusterManager, Logger::LoggablesetActiveHealthFailureType(Host::ActiveHealthFailureType::UNKNOWN); host->setHealthChecker( HealthCheckHostMonitorPtr{new HealthCheckHostMonitorImpl(shared_from_this(), host)}); active_sessions_[host]->start(); diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index 0ca6b6794a3d..d3172183f67c 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -50,7 +50,7 @@ HealthCheckerFactory::create(const envoy::api::v2::core::HealthCheck& hc_config, HealthCheckEventLoggerPtr event_logger; if (!hc_config.event_log_path().empty()) { event_logger = std::make_unique( - log_manager, ProdSystemTimeSource::instance_, hc_config.event_log_path()); + log_manager, dispatcher.timeSource().system(), hc_config.event_log_path()); } switch (hc_config.health_checker_case()) { case envoy::api::v2::core::HealthCheck::HealthCheckerCase::kHttpHealthCheck: @@ -199,6 +199,7 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onResponseComplete() { if (isHealthCheckSucceeded()) { handleSuccess(); } else { + host_->setActiveHealthFailureType(Host::ActiveHealthFailureType::UNHEALTHY); handleFailure(envoy::data::core::v2alpha::HealthCheckFailureType::ACTIVE); } @@ -214,6 +215,7 @@ void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onResponseComplete() { } void HttpHealthCheckerImpl::HttpActiveHealthCheckSession::onTimeout() { + host_->setActiveHealthFailureType(Host::ActiveHealthFailureType::TIMEOUT); ENVOY_CONN_LOG(debug, "connection/stream timeout health_flags={}", *client_, HostUtility::healthFlagsToString(*host_)); @@ -295,6 +297,8 @@ void TcpHealthCheckerImpl::TcpActiveHealthCheckSession::onData(Buffer::Instance& if (!parent_.reuse_connection_) { client_->close(Network::ConnectionCloseType::NoFlush); } + } else { + host_->setActiveHealthFailureType(Host::ActiveHealthFailureType::UNHEALTHY); } } @@ -351,6 +355,7 @@ void TcpHealthCheckerImpl::TcpActiveHealthCheckSession::onInterval() { } void TcpHealthCheckerImpl::TcpActiveHealthCheckSession::onTimeout() { + host_->setActiveHealthFailureType(Host::ActiveHealthFailureType::TIMEOUT); client_->close(Network::ConnectionCloseType::NoFlush); } diff --git a/source/common/upstream/health_discovery_service.cc b/source/common/upstream/health_discovery_service.cc index 35573b69df4e..815992a0e306 100644 --- a/source/common/upstream/health_discovery_service.cc +++ b/source/common/upstream/health_discovery_service.cc @@ -81,7 +81,16 @@ HdsDelegate::sendResponse() { if (host->healthy()) { endpoint->set_health_status(envoy::api::v2::core::HealthStatus::HEALTHY); } else { - endpoint->set_health_status(envoy::api::v2::core::HealthStatus::UNHEALTHY); + if (host->getActiveHealthFailureType() == Host::ActiveHealthFailureType::TIMEOUT) { + endpoint->set_health_status(envoy::api::v2::core::HealthStatus::TIMEOUT); + } else if (host->getActiveHealthFailureType() == + Host::ActiveHealthFailureType::UNHEALTHY) { + endpoint->set_health_status(envoy::api::v2::core::HealthStatus::UNHEALTHY); + } else if (host->getActiveHealthFailureType() == Host::ActiveHealthFailureType::UNKNOWN) { + endpoint->set_health_status(envoy::api::v2::core::HealthStatus::UNHEALTHY); + } else { + NOT_REACHED_GCOVR_EXCL_LINE; + } } } } @@ -220,6 +229,7 @@ ClusterInfoConstSharedPtr ProdClusterInfoFactory::createClusterInfo( Envoy::Server::Configuration::TransportSocketFactoryContextImpl factory_context( ssl_context_manager, *scope, cm, local_info, dispatcher, random, stats); + // TODO(JimmyCYJ): Support SDS for HDS cluster. Network::TransportSocketFactoryPtr socket_factory = Upstream::createTransportSocketFactory(cluster, factory_context); diff --git a/source/common/upstream/load_balancer_impl.cc b/source/common/upstream/load_balancer_impl.cc index 98dc98bd13e8..94cea9fcf9a1 100644 --- a/source/common/upstream/load_balancer_impl.cc +++ b/source/common/upstream/load_balancer_impl.cc @@ -96,7 +96,15 @@ void LoadBalancerBase::recalculatePerPriorityState(uint32_t priority) { } } -HostSet& LoadBalancerBase::chooseHostSet() { +HostSet& LoadBalancerBase::chooseHostSet(LoadBalancerContext* context) { + if (context) { + const auto& per_priority_load = + context->determinePriorityLoad(priority_set_, per_priority_load_); + + const uint32_t priority = choosePriority(random_.random(), per_priority_load); + return *priority_set_.hostSetsPerPriority()[priority]; + } + const uint32_t priority = choosePriority(random_.random(), per_priority_load_); return *priority_set_.hostSetsPerPriority()[priority]; } @@ -267,6 +275,24 @@ bool ZoneAwareLoadBalancerBase::earlyExitNonLocalityRouting() { return false; } +HostConstSharedPtr LoadBalancerBase::chooseHost(LoadBalancerContext* context) { + HostConstSharedPtr host; + const size_t max_attempts = context ? context->hostSelectionRetryCount() + 1 : 1; + for (size_t i = 0; i < max_attempts; ++i) { + host = chooseHostOnce(context); + + // If host selection failed or the host is accepted by the filter, return. + // Otherwise, try again. + // Note: in the future we might want to allow retrying when chooseHostOnce returns nullptr. + if (!host || !context || !context->shouldSelectAnotherHost(*host)) { + return host; + } + } + + // If we didnt find anything, return the last host. + return host; +} + bool LoadBalancerBase::isGlobalPanic(const HostSet& host_set) { uint64_t global_panic_threshold = std::min( 100, runtime_.snapshot().getInteger(RuntimePanicThreshold, default_healthy_panic_percent_)); @@ -345,8 +371,9 @@ uint32_t ZoneAwareLoadBalancerBase::tryChooseLocalLocalityHosts(const HostSet& h return i; } -ZoneAwareLoadBalancerBase::HostsSource ZoneAwareLoadBalancerBase::hostSourceToUse() { - HostSet& host_set = chooseHostSet(); +ZoneAwareLoadBalancerBase::HostsSource +ZoneAwareLoadBalancerBase::hostSourceToUse(LoadBalancerContext* context) { + HostSet& host_set = chooseHostSet(context); HostsSource hosts_source; hosts_source.priority_ = host_set.priority(); @@ -471,8 +498,8 @@ void EdfLoadBalancerBase::refresh(uint32_t priority) { } } -HostConstSharedPtr EdfLoadBalancerBase::chooseHost(LoadBalancerContext*) { - const HostsSource hosts_source = hostSourceToUse(); +HostConstSharedPtr EdfLoadBalancerBase::chooseHostOnce(LoadBalancerContext* context) { + const HostsSource hosts_source = hostSourceToUse(context); auto scheduler_it = scheduler_.find(hosts_source); // We should always have a scheduler for any return value from // hostSourceToUse() via the construction in refresh(); @@ -513,8 +540,8 @@ HostConstSharedPtr LeastRequestLoadBalancer::unweightedHostPick(const HostVector } } -HostConstSharedPtr RandomLoadBalancer::chooseHost(LoadBalancerContext*) { - const HostVector& hosts_to_use = hostSourceToHosts(hostSourceToUse()); +HostConstSharedPtr RandomLoadBalancer::chooseHostOnce(LoadBalancerContext* context) { + const HostVector& hosts_to_use = hostSourceToHosts(hostSourceToUse(context)); if (hosts_to_use.empty()) { return nullptr; } diff --git a/source/common/upstream/load_balancer_impl.h b/source/common/upstream/load_balancer_impl.h index ad52e0e5d55b..cfbf20120f74 100644 --- a/source/common/upstream/load_balancer_impl.h +++ b/source/common/upstream/load_balancer_impl.h @@ -21,7 +21,7 @@ static constexpr uint32_t kDefaultOverProvisioningFactor = 140; /** * Base class for all LB implementations. */ -class LoadBalancerBase { +class LoadBalancerBase : public LoadBalancer { public: // A utility function to chose a priority level based on a precomputed hash and // a priority vector in the style of per_priority_load_ @@ -29,7 +29,24 @@ class LoadBalancerBase { // Returns the priority, a number between 0 and per_priority_load.size()-1 static uint32_t choosePriority(uint64_t hash, const std::vector& per_priority_load); + HostConstSharedPtr chooseHost(LoadBalancerContext* context) override; + protected: + /** + * By implementing this method instead of chooseHost, host selection will + * be subject to host filters specified by LoadBalancerContext. + * + * Host selection will be retried up to the number specified by + * hostSelectionRetryCount on LoadBalancerContext, and if no hosts are found + * within the allowed attempts, the host that was selected during the last + * attempt will be returned. + * + * If host selection is idempotent (i.e. retrying will not change the outcome), + * sub classes should override chooseHost to avoid the unnecessary overhead of + * retrying host selection. + */ + virtual HostConstSharedPtr chooseHostOnce(LoadBalancerContext* context) PURE; + /** * For the given host_set @return if we should be in a panic mode or not. For example, if the * majority of hosts are unhealthy we'll be likely in a panic mode. In this case we'll route @@ -42,7 +59,7 @@ class LoadBalancerBase { const envoy::api::v2::Cluster::CommonLbConfig& common_config); // Choose host set randomly, based on the per_priority_load_; - HostSet& chooseHostSet(); + HostSet& chooseHostSet(LoadBalancerContext* context); uint32_t percentageLoad(uint32_t priority) const { return per_priority_load_[priority]; } @@ -64,6 +81,26 @@ class LoadBalancerBase { std::vector per_priority_health_; }; +class LoadBalancerContextBase : public LoadBalancerContext { +public: + absl::optional computeHashKey() override { return {}; } + + const Network::Connection* downstreamConnection() const override { return nullptr; } + + const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return nullptr; } + + const Http::HeaderMap* downstreamHeaders() const override { return nullptr; } + + const PriorityLoad& determinePriorityLoad(const PrioritySet&, + const PriorityLoad& original_priority_load) override { + return original_priority_load; + } + + bool shouldSelectAnotherHost(const Host&) override { return false; } + + uint32_t hostSelectionRetryCount() const override { return 0; } +}; + /** * Base class for zone aware load balancers */ @@ -130,7 +167,7 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { /** * Pick the host source to use, doing zone aware routing when the hosts are sufficiently healthy. */ - HostsSource hostSourceToUse(); + HostsSource hostSourceToUse(LoadBalancerContext* context); /** * Index into priority_set via hosts source descriptor. @@ -218,15 +255,15 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { * This base class also supports unweighted selection which derived classes can use to customize * behavior. Derived classes can also override how host weight is determined when in weighted mode. */ -class EdfLoadBalancerBase : public LoadBalancer, public ZoneAwareLoadBalancerBase { +class EdfLoadBalancerBase : public ZoneAwareLoadBalancerBase { public: EdfLoadBalancerBase(const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, Runtime::Loader& runtime, Runtime::RandomGenerator& random, const envoy::api::v2::Cluster::CommonLbConfig& common_config); - // Upstream::LoadBalancer - HostConstSharedPtr chooseHost(LoadBalancerContext* context) override; + // Upstream::LoadBalancerBase + HostConstSharedPtr chooseHostOnce(LoadBalancerContext* context) override; protected: struct Scheduler { @@ -339,7 +376,7 @@ class LeastRequestLoadBalancer : public EdfLoadBalancerBase { /** * Random load balancer that picks a random host out of all hosts. */ -class RandomLoadBalancer : public LoadBalancer, ZoneAwareLoadBalancerBase { +class RandomLoadBalancer : public ZoneAwareLoadBalancerBase { public: RandomLoadBalancer(const PrioritySet& priority_set, const PrioritySet* local_priority_set, ClusterStats& stats, Runtime::Loader& runtime, @@ -348,8 +385,8 @@ class RandomLoadBalancer : public LoadBalancer, ZoneAwareLoadBalancerBase { : ZoneAwareLoadBalancerBase(priority_set, local_priority_set, stats, runtime, random, common_config) {} - // Upstream::LoadBalancer - HostConstSharedPtr chooseHost(LoadBalancerContext* context) override; + // Upstream::LoadBalancerBase + HostConstSharedPtr chooseHostOnce(LoadBalancerContext* context) override; }; /** diff --git a/source/common/upstream/logical_dns_cluster.cc b/source/common/upstream/logical_dns_cluster.cc index 7868e9d9a902..c8bf6c73f3c5 100644 --- a/source/common/upstream/logical_dns_cluster.cc +++ b/source/common/upstream/logical_dns_cluster.cc @@ -94,15 +94,6 @@ void LogicalDnsCluster::startResolve() { Network::Address::InstanceConstSharedPtr new_address = Network::Utility::getAddressWithPort(*address_list.front(), Network::Utility::portFromTcpUrl(dns_url_)); - if (!current_resolved_address_ || !(*new_address == *current_resolved_address_)) { - current_resolved_address_ = new_address; - // Capture URL to avoid a race with another update. - tls_->runOnAllThreads([this, new_address]() -> void { - PerThreadCurrentHostData& data = tls_->getTyped(); - data.current_resolved_address_ = new_address; - }); - } - if (!logical_host_) { // TODO(mattklein123): The logical host is only used in /clusters admin output. We used // to show the friendly DNS name in that output, but currently there is no way to @@ -129,6 +120,19 @@ void LogicalDnsCluster::startResolve() { priority, std::move(priority_state_manager.priorityState()[priority].first), absl::nullopt, absl::nullopt, absl::nullopt); } + + if (!current_resolved_address_ || !(*new_address == *current_resolved_address_)) { + current_resolved_address_ = new_address; + + // Make sure that we have an updated health check address. + logical_host_->setHealthCheckAddress(new_address); + + // Capture URL to avoid a race with another update. + tls_->runOnAllThreads([this, new_address]() -> void { + PerThreadCurrentHostData& data = tls_->getTyped(); + data.current_resolved_address_ = new_address; + }); + } } onPreInitComplete(); diff --git a/source/common/upstream/logical_dns_cluster.h b/source/common/upstream/logical_dns_cluster.h index beece913b62f..4b55c1595949 100644 --- a/source/common/upstream/logical_dns_cluster.h +++ b/source/common/upstream/logical_dns_cluster.h @@ -54,6 +54,17 @@ class LogicalDnsCluster : public ClusterImplBase { createConnection(Event::Dispatcher& dispatcher, const Network::ConnectionSocket::OptionsSharedPtr& options) const override; + // Upstream::HostDescription + // Override setting health check address, since for logical DNS the registered host has 0.0.0.0 + // as its address (see mattklein123's comment in logical_dns_cluster.cc why this is), + // while the health check address needs the resolved address to do the health checking, so we + // set it here. + void setHealthCheckAddress(Network::Address::InstanceConstSharedPtr address) override { + const auto& port_value = parent_.lbEndpoint().endpoint().health_check_config().port_value(); + health_check_address_ = + port_value == 0 ? address : Network::Utility::getAddressWithPort(*address, port_value); + } + LogicalDnsCluster& parent_; }; @@ -95,6 +106,8 @@ class LogicalDnsCluster : public ClusterImplBase { Network::Address::InstanceConstSharedPtr healthCheckAddress() const override { return health_check_address_; } + // Setting health check address is usually done at initialization. This is NOP by default. + void setHealthCheckAddress(Network::Address::InstanceConstSharedPtr) override {} uint32_t priority() const { return locality_lb_endpoint_.priority(); } Network::Address::InstanceConstSharedPtr address_; HostConstSharedPtr logical_host_; diff --git a/source/common/upstream/outlier_detection_impl.cc b/source/common/upstream/outlier_detection_impl.cc index 1166902b7ee4..c69a28745603 100644 --- a/source/common/upstream/outlier_detection_impl.cc +++ b/source/common/upstream/outlier_detection_impl.cc @@ -25,7 +25,7 @@ DetectorSharedPtr DetectorImplFactory::createForCluster( Runtime::Loader& runtime, EventLoggerSharedPtr event_logger) { if (cluster_config.has_outlier_detection()) { return DetectorImpl::create(cluster, cluster_config.outlier_detection(), dispatcher, runtime, - ProdMonotonicTimeSource::instance_, event_logger); + dispatcher.timeSource().monotonic(), event_logger); } else { return nullptr; } diff --git a/source/common/upstream/thread_aware_lb_impl.h b/source/common/upstream/thread_aware_lb_impl.h index 448a1f06b234..5bbc46c212bf 100644 --- a/source/common/upstream/thread_aware_lb_impl.h +++ b/source/common/upstream/thread_aware_lb_impl.h @@ -28,6 +28,11 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL LoadBalancerFactorySharedPtr factory() override { return factory_; } void initialize() override; + // Upstream::LoadBalancerBase + HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } + protected: ThreadAwareLoadBalancerBase(const PrioritySet& priority_set, ClusterStats& stats, Runtime::Loader& runtime, Runtime::RandomGenerator& random, diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 5e3e4fa91c22..529c9bb1257a 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -508,6 +508,7 @@ ClusterImplBase::ClusterImplBase( Server::Configuration::TransportSocketFactoryContext& factory_context, Stats::ScopePtr&& stats_scope, bool added_via_api) : runtime_(runtime) { + factory_context.setInitManager(init_manager_); auto socket_factory = createTransportSocketFactory(cluster, factory_context); info_ = std::make_unique(cluster, factory_context.clusterManager().bindConfig(), runtime, std::move(socket_factory), @@ -571,6 +572,10 @@ void ClusterImplBase::onPreInitComplete() { } initialization_started_ = true; + init_manager_.initialize([this]() { onInitDone(); }); +} + +void ClusterImplBase::onInitDone() { if (health_checker_ && pending_initialize_health_checks_ == 0) { for (auto& host_set : prioritySet().hostSetsPerPriority()) { pending_initialize_health_checks_ += host_set->hosts().size(); diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 9d34dba7ce53..d2a726069724 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -40,6 +40,8 @@ #include "common/upstream/outlier_detection_impl.h" #include "common/upstream/resource_manager_impl.h" +#include "server/init_manager_impl.h" + namespace Envoy { namespace Upstream { @@ -122,6 +124,8 @@ class HostDescriptionImpl : virtual public HostDescription { Network::Address::InstanceConstSharedPtr healthCheckAddress() const override { return health_check_address_; } + // Setting health check address is usually done at initialization. This is NOP by default. + void setHealthCheckAddress(Network::Address::InstanceConstSharedPtr) override {} const envoy::api::v2::core::Locality& locality() const override { return locality_; } protected: @@ -166,6 +170,14 @@ class HostImpl : public HostDescriptionImpl, void healthFlagClear(HealthFlag flag) override { health_flags_ &= ~enumToInt(flag); } bool healthFlagGet(HealthFlag flag) const override { return health_flags_ & enumToInt(flag); } void healthFlagSet(HealthFlag flag) override { health_flags_ |= enumToInt(flag); } + + ActiveHealthFailureType getActiveHealthFailureType() const override { + return active_health_failure_type_; + } + void setActiveHealthFailureType(ActiveHealthFailureType type) override { + active_health_failure_type_ = type; + } + void setHealthChecker(HealthCheckHostMonitorPtr&& health_checker) override { health_checker_ = std::move(health_checker); } @@ -186,6 +198,7 @@ class HostImpl : public HostDescriptionImpl, private: std::atomic health_flags_{}; + ActiveHealthFailureType active_health_failure_type_{}; std::atomic weight_; std::atomic used_; }; @@ -507,12 +520,21 @@ class ClusterImplBase : public Cluster, protected Logger::Loggable(options_.statsOptions(), restarter_->statsAllocator()); + server_ = std::make_unique( - options_, local_address, default_test_hooks_, *restarter_, *stats_store_, access_log_lock, - component_factory_, std::make_unique(), *tls_); + options_, time_source_, local_address, default_test_hooks_, *restarter_, *stats_store_, + access_log_lock, component_factory_, std::make_unique(), + *tls_); break; } case Server::Mode::Validate: diff --git a/source/exe/main_common.h b/source/exe/main_common.h index 040041dd366c..5355bc2a4f3c 100644 --- a/source/exe/main_common.h +++ b/source/exe/main_common.h @@ -49,6 +49,9 @@ class MainCommonBase { protected: Envoy::OptionsImpl& options_; ProdComponentFactory component_factory_; + ProdSystemTimeSource system_time_source_; + ProdMonotonicTimeSource monotonic_time_source_; + TimeSource time_source_; DefaultTestHooks default_test_hooks_; std::unique_ptr tls_; std::unique_ptr restarter_; diff --git a/source/extensions/access_loggers/http_grpc/grpc_access_log_impl.cc b/source/extensions/access_loggers/http_grpc/grpc_access_log_impl.cc index 6804cdd9bd61..2453280c3580 100644 --- a/source/extensions/access_loggers/http_grpc/grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/http_grpc/grpc_access_log_impl.cc @@ -85,7 +85,7 @@ void HttpGrpcAccessLog::responseFlagsToAccessLogResponseFlags( envoy::data::accesslog::v2::AccessLogCommon& common_access_log, const RequestInfo::RequestInfo& request_info) { - static_assert(RequestInfo::ResponseFlag::LastFlag == 0x1000, + static_assert(RequestInfo::ResponseFlag::LastFlag == 0x2000, "A flag has been added. Fix this code."); if (request_info.hasResponseFlag(RequestInfo::ResponseFlag::FailedLocalHealthCheck)) { @@ -141,6 +141,10 @@ void HttpGrpcAccessLog::responseFlagsToAccessLogResponseFlags( envoy::data::accesslog::v2::ResponseFlags_Unauthorized_Reason:: ResponseFlags_Unauthorized_Reason_EXTERNAL_SERVICE); } + + if (request_info.hasResponseFlag(RequestInfo::ResponseFlag::RateLimitServiceError)) { + common_access_log.mutable_response_flags()->set_rate_limit_service_error(true); + } } void HttpGrpcAccessLog::log(const Http::HeaderMap* request_headers, diff --git a/source/extensions/filters/http/jwt_authn/filter.cc b/source/extensions/filters/http/jwt_authn/filter.cc index 9ae057236d68..ac23444423e9 100644 --- a/source/extensions/filters/http/jwt_authn/filter.cc +++ b/source/extensions/filters/http/jwt_authn/filter.cc @@ -21,7 +21,6 @@ void Filter::onDestroy() { Http::FilterHeadersStatus Filter::decodeHeaders(Http::HeaderMap& headers, bool) { ENVOY_LOG(debug, "Called Filter : {}", __func__); - // Remove headers configured to pass payload auth_->sanitizePayloadHeaders(headers); @@ -51,8 +50,8 @@ void Filter::onComplete(const Status& status) { // verification failed Http::Code code = Http::Code::Unauthorized; // return failure reason as message body - Http::Utility::sendLocalReply(false, *decoder_callbacks_, false, code, - ::google::jwt_verify::getStatusString(status)); + decoder_callbacks_->sendLocalReply(code, ::google::jwt_verify::getStatusString(status), + nullptr); return; } diff --git a/source/extensions/filters/http/ratelimit/ratelimit.cc b/source/extensions/filters/http/ratelimit/ratelimit.cc index 956bc56d210a..fa4122ef8dc1 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit.cc +++ b/source/extensions/filters/http/ratelimit/ratelimit.cc @@ -147,6 +147,15 @@ void Filter::complete(RateLimit::LimitStatus status, Http::HeaderMapPtr&& header callbacks_->sendLocalReply(Http::Code::TooManyRequests, "", [this](Http::HeaderMap& headers) { addHeaders(headers); }); callbacks_->requestInfo().setResponseFlag(RequestInfo::ResponseFlag::RateLimited); + } else if (status == RateLimit::LimitStatus::Error) { + if (config_->failureModeAllow()) { + cluster_->statsScope().counter("ratelimit.failure_mode_allowed").inc(); + callbacks_->continueDecoding(); + } else { + state_ = State::Responded; + callbacks_->sendLocalReply(Http::Code::InternalServerError, "", nullptr); + callbacks_->requestInfo().setResponseFlag(RequestInfo::ResponseFlag::RateLimitServiceError); + } } else if (!initiating_call_) { callbacks_->continueDecoding(); } diff --git a/source/extensions/filters/http/ratelimit/ratelimit.h b/source/extensions/filters/http/ratelimit/ratelimit.h index 5d46592fd92f..4f411a797b0a 100644 --- a/source/extensions/filters/http/ratelimit/ratelimit.h +++ b/source/extensions/filters/http/ratelimit/ratelimit.h @@ -37,8 +37,8 @@ class FilterConfig { : domain_(config.domain()), stage_(static_cast(config.stage())), request_type_(config.request_type().empty() ? stringToType("both") : stringToType(config.request_type())), - local_info_(local_info), scope_(scope), runtime_(runtime), cm_(cm) {} - + local_info_(local_info), scope_(scope), runtime_(runtime), cm_(cm), + failure_mode_deny_(config.failure_mode_deny()) {} const std::string& domain() const { return domain_; } const LocalInfo::LocalInfo& localInfo() const { return local_info_; } uint64_t stage() const { return stage_; } @@ -47,6 +47,8 @@ class FilterConfig { Upstream::ClusterManager& cm() { return cm_; } FilterRequestType requestType() const { return request_type_; } + bool failureModeAllow() const { return !failure_mode_deny_; } + private: static FilterRequestType stringToType(const std::string& request_type) { if (request_type == "internal") { @@ -66,6 +68,7 @@ class FilterConfig { Stats::Scope& scope_; Runtime::Loader& runtime_; Upstream::ClusterManager& cm_; + const bool failure_mode_deny_; }; typedef std::shared_ptr FilterConfigSharedPtr; diff --git a/source/extensions/filters/http/rbac/rbac_filter.cc b/source/extensions/filters/http/rbac/rbac_filter.cc index fd82b6fe4c7a..8c1c17272d8b 100644 --- a/source/extensions/filters/http/rbac/rbac_filter.cc +++ b/source/extensions/filters/http/rbac/rbac_filter.cc @@ -80,24 +80,16 @@ Http::FilterHeadersStatus RoleBasedAccessControlFilter::decodeHeaders(Http::Head shadow_resp_code = resp_code_403; } - const auto& filter_metadata = callbacks_->requestInfo().dynamicMetadata().filter_metadata(); - const auto filter_it = filter_metadata.find(HttpFilterNames::get().Rbac); - if (filter_it != filter_metadata.end()) { - ProtobufWkt::Struct metrics; - - if (!effective_policy_id.empty()) { - ProtobufWkt::Value policy_id; - policy_id.set_string_value(effective_policy_id); - (*metrics.mutable_fields())[shadow_policy_id_field] = policy_id; - } - - ProtobufWkt::Value resp_code; - resp_code.set_string_value(shadow_resp_code); - (*metrics.mutable_fields())[shadow_resp_code_field] = resp_code; - - auto filter_meta = filter_metadata.at(HttpFilterNames::get().Rbac); - filter_meta.MergeFrom(metrics); + ProtobufWkt::Struct metrics; + + auto& fields = *metrics.mutable_fields(); + if (!effective_policy_id.empty()) { + *fields[shadow_policy_id_field].mutable_string_value() = effective_policy_id; } + + *fields[shadow_resp_code_field].mutable_string_value() = shadow_resp_code; + + callbacks_->requestInfo().setDynamicMetadata(HttpFilterNames::get().Rbac, metrics); } const auto& engine = diff --git a/source/extensions/filters/network/ratelimit/ratelimit.cc b/source/extensions/filters/network/ratelimit/ratelimit.cc index dc33acdff773..b74f94d0e0fd 100644 --- a/source/extensions/filters/network/ratelimit/ratelimit.cc +++ b/source/extensions/filters/network/ratelimit/ratelimit.cc @@ -16,8 +16,7 @@ namespace RateLimitFilter { Config::Config(const envoy::config::filter::network::rate_limit::v2::RateLimit& config, Stats::Scope& scope, Runtime::Loader& runtime) : domain_(config.domain()), stats_(generateStats(config.stat_prefix(), scope)), - runtime_(runtime) { - + runtime_(runtime), failure_mode_deny_(config.failure_mode_deny()) { for (const auto& descriptor : config.descriptors()) { RateLimit::Descriptor new_descriptor; for (const auto& entry : descriptor.entries()) { @@ -85,11 +84,18 @@ void Filter::complete(RateLimit::LimitStatus status, Http::HeaderMapPtr&&) { break; } - // We fail open if there is an error contacting the service. if (status == RateLimit::LimitStatus::OverLimit && config_->runtime().snapshot().featureEnabled("ratelimit.tcp_filter_enforcing", 100)) { config_->stats().cx_closed_.inc(); filter_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + } else if (status == RateLimit::LimitStatus::Error) { + if (config_->failureModeAllow()) { + config_->stats().failure_mode_allowed_.inc(); + filter_callbacks_->continueReading(); + } else { + config_->stats().cx_closed_.inc(); + filter_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); + } } else { // We can get completion inline, so only call continue if that isn't happening. if (!calling_limit_) { diff --git a/source/extensions/filters/network/ratelimit/ratelimit.h b/source/extensions/filters/network/ratelimit/ratelimit.h index 9ad7718a8185..a19de07b8b41 100644 --- a/source/extensions/filters/network/ratelimit/ratelimit.h +++ b/source/extensions/filters/network/ratelimit/ratelimit.h @@ -27,6 +27,7 @@ namespace RateLimitFilter { COUNTER(error) \ COUNTER(over_limit) \ COUNTER(ok) \ + COUNTER(failure_mode_allowed) \ COUNTER(cx_closed) \ GAUGE (active) // clang-format on @@ -49,6 +50,7 @@ class Config { const std::vector& descriptors() { return descriptors_; } Runtime::Loader& runtime() { return runtime_; } const InstanceStats& stats() { return stats_; } + bool failureModeAllow() const { return !failure_mode_deny_; }; private: static InstanceStats generateStats(const std::string& name, Stats::Scope& scope); @@ -57,6 +59,7 @@ class Config { std::vector descriptors_; const InstanceStats stats_; Runtime::Loader& runtime_; + const bool failure_mode_deny_; }; typedef std::shared_ptr ConfigSharedPtr; diff --git a/source/extensions/filters/network/redis_proxy/BUILD b/source/extensions/filters/network/redis_proxy/BUILD index 7dafa10e3da5..fac0c3feda79 100644 --- a/source/extensions/filters/network/redis_proxy/BUILD +++ b/source/extensions/filters/network/redis_proxy/BUILD @@ -73,6 +73,7 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/network:filter_lib", "//source/common/protobuf:utility_lib", + "//source/common/upstream:load_balancer_lib", "@envoy_api//envoy/config/filter/network/redis_proxy/v2:redis_proxy_cc", ], ) diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h index 13cb0031ef70..45ae4227b3cb 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -15,6 +15,7 @@ #include "common/buffer/buffer_impl.h" #include "common/network/filter_impl.h" #include "common/protobuf/utility.h" +#include "common/upstream/load_balancer_impl.h" #include "extensions/filters/network/redis_proxy/codec_impl.h" #include "extensions/filters/network/redis_proxy/conn_pool.h" @@ -161,14 +162,11 @@ class InstanceImpl : public Instance { Envoy::Common::CallbackHandle* local_host_set_member_update_cb_handle_; }; - struct LbContextImpl : public Upstream::LoadBalancerContext { + struct LbContextImpl : public Upstream::LoadBalancerContextBase { LbContextImpl(const std::string& hash_key) : hash_key_(std::hash()(hash_key)) {} // TODO(danielhochman): convert to HashUtil::xxHash64 when we have a migration strategy. // Upstream::LoadBalancerContext absl::optional computeHashKey() override { return hash_key_; } - const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return nullptr; } - const Network::Connection* downstreamConnection() const override { return nullptr; } - const Http::HeaderMap* downstreamHeaders() const override { return nullptr; } const absl::optional hash_key_; }; diff --git a/source/extensions/filters/network/thrift_proxy/router/BUILD b/source/extensions/filters/network/thrift_proxy/router/BUILD index c1af5362604e..ac11bbd1bd2d 100644 --- a/source/extensions/filters/network/thrift_proxy/router/BUILD +++ b/source/extensions/filters/network/thrift_proxy/router/BUILD @@ -40,6 +40,7 @@ envoy_cc_library( "//include/envoy/upstream:load_balancer_interface", "//include/envoy/upstream:thread_local_cluster_interface", "//source/common/common:logger_lib", + "//source/common/upstream:load_balancer_lib", "//source/extensions/filters/network:well_known_names", "//source/extensions/filters/network/thrift_proxy:app_exception_lib", "//source/extensions/filters/network/thrift_proxy:conn_manager_lib", diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.h b/source/extensions/filters/network/thrift_proxy/router/router_impl.h index d25b713a8e1c..c0fa16fae442 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.h +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.h @@ -9,6 +9,7 @@ #include "envoy/upstream/load_balancer.h" #include "common/common/logger.h" +#include "common/upstream/load_balancer_impl.h" #include "extensions/filters/network/thrift_proxy/conn_manager.h" #include "extensions/filters/network/thrift_proxy/filters/filter.h" @@ -86,7 +87,7 @@ class RouteMatcher { }; class Router : public Tcp::ConnectionPool::UpstreamCallbacks, - public Upstream::LoadBalancerContext, + public Upstream::LoadBalancerContextBase, public ProtocolConverter, Logger::Loggable { public: @@ -104,10 +105,7 @@ class Router : public Tcp::ConnectionPool::UpstreamCallbacks, ThriftFilters::FilterStatus messageEnd() override; // Upstream::LoadBalancerContext - absl::optional computeHashKey() override { return {}; } - const Envoy::Router::MetadataMatchCriteria* metadataMatchCriteria() override { return nullptr; } const Network::Connection* downstreamConnection() const override; - const Http::HeaderMap* downstreamHeaders() const override { return nullptr; } // Tcp::ConnectionPool::UpstreamCallbacks void onUpstreamData(Buffer::Instance& data, bool end_stream) override; diff --git a/source/extensions/tracers/zipkin/tracer.cc b/source/extensions/tracers/zipkin/tracer.cc index a3bc9072d302..e95719262fc6 100644 --- a/source/extensions/tracers/zipkin/tracer.cc +++ b/source/extensions/tracers/zipkin/tracer.cc @@ -28,7 +28,7 @@ SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::string& span } // Create an all-new span, with no parent id - SpanPtr span_ptr(new Span()); + SpanPtr span_ptr(new Span(time_source_)); span_ptr->setName(span_name); uint64_t random_number = random_generator_.random(); span_ptr->setId(random_number); @@ -36,10 +36,9 @@ SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::string& span if (trace_id_128bit_) { span_ptr->setTraceIdHigh(random_generator_.random()); } - int64_t start_time_micro = - std::chrono::duration_cast( - ProdMonotonicTimeSource::instance_.currentTime().time_since_epoch()) - .count(); + int64_t start_time_micro = std::chrono::duration_cast( + time_source_.monotonicTime().time_since_epoch()) + .count(); span_ptr->setStartTime(start_time_micro); // Set the timestamp globally for the span and also for the CS annotation @@ -58,7 +57,7 @@ SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::string& span SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::string& span_name, SystemTime timestamp, SpanContext& previous_context) { - SpanPtr span_ptr(new Span()); + SpanPtr span_ptr(new Span(time_source_)); Annotation annotation; uint64_t timestamp_micro; @@ -114,10 +113,9 @@ SpanPtr Tracer::startSpan(const Tracing::Config& config, const std::string& span // Keep the same sampled flag span_ptr->setSampled(previous_context.sampled()); - int64_t start_time_micro = - std::chrono::duration_cast( - ProdMonotonicTimeSource::instance_.currentTime().time_since_epoch()) - .count(); + int64_t start_time_micro = std::chrono::duration_cast( + time_source_.monotonicTime().time_since_epoch()) + .count(); span_ptr->setStartTime(start_time_micro); span_ptr->setTracer(this); diff --git a/source/extensions/tracers/zipkin/tracer.h b/source/extensions/tracers/zipkin/tracer.h index d74606a3bff4..f177da30a7b1 100644 --- a/source/extensions/tracers/zipkin/tracer.h +++ b/source/extensions/tracers/zipkin/tracer.h @@ -59,9 +59,11 @@ class Tracer : public TracerInterface { * @param trace_id_128bit Whether 128bit ids should be used. */ Tracer(const std::string& service_name, Network::Address::InstanceConstSharedPtr address, - Runtime::RandomGenerator& random_generator, const bool trace_id_128bit) + Runtime::RandomGenerator& random_generator, const bool trace_id_128bit, + TimeSource& time_source) : service_name_(service_name), address_(address), reporter_(nullptr), - random_generator_(random_generator), trace_id_128bit_(trace_id_128bit) {} + random_generator_(random_generator), trace_id_128bit_(trace_id_128bit), + time_source_(time_source) {} /** * Creates a "root" Zipkin span. @@ -114,6 +116,7 @@ class Tracer : public TracerInterface { ReporterPtr reporter_; Runtime::RandomGenerator& random_generator_; const bool trace_id_128bit_; + TimeSource time_source_; }; typedef std::unique_ptr TracerPtr; diff --git a/source/extensions/tracers/zipkin/util.cc b/source/extensions/tracers/zipkin/util.cc index 9ea8091a68e6..447621f0a59d 100644 --- a/source/extensions/tracers/zipkin/util.cc +++ b/source/extensions/tracers/zipkin/util.cc @@ -47,9 +47,9 @@ void Util::addArrayToJson(std::string& target, const std::vector& j mergeJsons(target, stringified_json_array, field_name); } -uint64_t Util::generateRandom64() { +uint64_t Util::generateRandom64(TimeSource& time_source) { uint64_t seed = std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + time_source.systemTime().time_since_epoch()) .count(); std::mt19937_64 rand_64(seed); return rand_64(); diff --git a/source/extensions/tracers/zipkin/util.h b/source/extensions/tracers/zipkin/util.h index cee928f8a9af..ce86f73080e9 100644 --- a/source/extensions/tracers/zipkin/util.h +++ b/source/extensions/tracers/zipkin/util.h @@ -3,6 +3,8 @@ #include #include +#include "envoy/common/time.h" + namespace Envoy { namespace Extensions { namespace Tracers { @@ -45,7 +47,7 @@ class Util { /** * Returns a randomly-generated 64-bit integer number. */ - static uint64_t generateRandom64(); + static uint64_t generateRandom64(TimeSource& time_source); }; } // namespace Zipkin diff --git a/source/extensions/tracers/zipkin/zipkin_core_types.cc b/source/extensions/tracers/zipkin/zipkin_core_types.cc index ca0870744f88..028ec2afada4 100644 --- a/source/extensions/tracers/zipkin/zipkin_core_types.cc +++ b/source/extensions/tracers/zipkin/zipkin_core_types.cc @@ -141,7 +141,7 @@ const std::string BinaryAnnotation::toJson() { const std::string Span::EMPTY_HEX_STRING_ = "0000000000000000"; -Span::Span(const Span& span) { +Span::Span(const Span& span) : time_source_(span.time_source_) { trace_id_ = span.traceId(); if (span.isSetTraceIdHigh()) { trace_id_high_ = span.traceIdHigh(); @@ -227,25 +227,23 @@ void Span::finish() { Annotation ss; ss.setEndpoint(annotations_[0].endpoint()); ss.setTimestamp(std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + time_source_.systemTime().time_since_epoch()) .count()); ss.setValue(ZipkinCoreConstants::get().SERVER_SEND); annotations_.push_back(std::move(ss)); } else if (annotations_[0].value() == ZipkinCoreConstants::get().CLIENT_SEND) { // Need to set the CR annotation Annotation cr; - const uint64_t stop_timestamp = - std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) - .count(); + const uint64_t stop_timestamp = std::chrono::duration_cast( + time_source_.systemTime().time_since_epoch()) + .count(); cr.setEndpoint(annotations_[0].endpoint()); cr.setTimestamp(stop_timestamp); cr.setValue(ZipkinCoreConstants::get().CLIENT_RECV); annotations_.push_back(std::move(cr)); - const int64_t monotonic_stop_time = - std::chrono::duration_cast( - ProdMonotonicTimeSource::instance_.currentTime().time_since_epoch()) - .count(); + const int64_t monotonic_stop_time = std::chrono::duration_cast( + time_source_.monotonicTime().time_since_epoch()) + .count(); setDuration(monotonic_stop_time - monotonic_start_time_); } diff --git a/source/extensions/tracers/zipkin/zipkin_core_types.h b/source/extensions/tracers/zipkin/zipkin_core_types.h index 5eeccef9f2e1..bd83a1fa8607 100644 --- a/source/extensions/tracers/zipkin/zipkin_core_types.h +++ b/source/extensions/tracers/zipkin/zipkin_core_types.h @@ -3,6 +3,7 @@ #include #include "envoy/common/pure.h" +#include "envoy/common/time.h" #include "envoy/network/address.h" #include "common/common/hex.h" @@ -304,9 +305,9 @@ class Span : public ZipkinBase { /** * Default constructor. Creates an empty span. */ - Span() + explicit Span(TimeSource& time_source) : trace_id_(0), name_(), id_(0), debug_(false), sampled_(false), monotonic_start_time_(0), - tracer_(nullptr) {} + tracer_(nullptr), time_source_(time_source) {} /** * Sets the span's trace id attribute. @@ -561,6 +562,7 @@ class Span : public ZipkinBase { absl::optional trace_id_high_; int64_t monotonic_start_time_; TracerInterface* tracer_; + TimeSource time_source_; }; } // namespace Zipkin diff --git a/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc b/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc index 5d892f883bb4..e122d7fface2 100644 --- a/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc +++ b/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc @@ -76,7 +76,7 @@ Driver::Driver(const Json::Object& config, Upstream::ClusterManager& cluster_man tls_->set([this, collector_endpoint, &random_generator, trace_id_128bit]( Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { TracerPtr tracer(new Tracer(local_info_.clusterName(), local_info_.address(), random_generator, - trace_id_128bit)); + trace_id_128bit, cm_.timeSource())); tracer->setReporter( ReporterImpl::NewInstance(std::ref(*this), std::ref(dispatcher), collector_endpoint)); return ThreadLocal::ThreadLocalObjectSharedPtr{new TlsTracer(std::move(tracer), *this)}; diff --git a/source/extensions/transport_sockets/alts/BUILD b/source/extensions/transport_sockets/alts/BUILD index da086ff2d6e2..e3b9374c1cab 100644 --- a/source/extensions/transport_sockets/alts/BUILD +++ b/source/extensions/transport_sockets/alts/BUILD @@ -53,3 +53,9 @@ envoy_cc_library( "//source/common/buffer:buffer_lib", ], ) + +envoy_cc_library( + name = "noop_transport_socket_callbacks_lib", + hdrs = ["noop_transport_socket_callbacks.h"], + deps = ["//include/envoy/network:transport_socket_interface"], +) diff --git a/source/extensions/transport_sockets/alts/noop_transport_socket_callbacks.h b/source/extensions/transport_sockets/alts/noop_transport_socket_callbacks.h new file mode 100644 index 000000000000..3fe6a038104a --- /dev/null +++ b/source/extensions/transport_sockets/alts/noop_transport_socket_callbacks.h @@ -0,0 +1,35 @@ +#include "envoy/network/transport_socket.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Alts { + +/** + * A TransportSocketCallbacks for wrapped TransportSocket object. Some + * TransportSocket implementation wraps another socket which does actual I/O. + * This class is used by the wrapped socket as its callbacks instead of the real + * connection to hold back callbacks from the underlying socket to connection. + */ +class NoOpTransportSocketCallbacks : public Network::TransportSocketCallbacks { +public: + explicit NoOpTransportSocketCallbacks(Network::TransportSocketCallbacks& parent) + : parent_(parent) {} + + int fd() const override { return parent_.fd(); } + Network::Connection& connection() override { return parent_.connection(); } + bool shouldDrainReadBuffer() override { return false; } + /* + * No-op for these two methods to hold back the callbacks. + */ + void setReadBufferReady() override {} + void raiseEvent(Network::ConnectionEvent) override {} + +private: + Network::TransportSocketCallbacks& parent_; +}; + +} // namespace Alts +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/ssl/config.cc b/source/extensions/transport_sockets/ssl/config.cc index c3f69f301983..d211d8607586 100644 --- a/source/extensions/transport_sockets/ssl/config.cc +++ b/source/extensions/transport_sockets/ssl/config.cc @@ -18,7 +18,7 @@ Network::TransportSocketFactoryPtr UpstreamSslSocketFactory::createTransportSock Server::Configuration::TransportSocketFactoryContext& context) { auto client_config = std::make_unique( MessageUtil::downcastAndValidate(message), - context.secretManager()); + context); return std::make_unique( std::move(client_config), context.sslContextManager(), context.statsScope()); } @@ -36,7 +36,7 @@ Network::TransportSocketFactoryPtr DownstreamSslSocketFactory::createTransportSo const std::vector& server_names) { auto server_config = std::make_unique( MessageUtil::downcastAndValidate(message), - context.secretManager()); + context); return std::make_unique( std::move(server_config), context.sslContextManager(), context.statsScope(), server_names); } diff --git a/source/server/config_validation/api.cc b/source/server/config_validation/api.cc index f83d46a98410..293a3161c7f1 100644 --- a/source/server/config_validation/api.cc +++ b/source/server/config_validation/api.cc @@ -8,8 +8,8 @@ namespace Api { ValidationImpl::ValidationImpl(std::chrono::milliseconds file_flush_interval_msec) : Impl(file_flush_interval_msec) {} -Event::DispatcherPtr ValidationImpl::allocateDispatcher() { - return Event::DispatcherPtr{new Event::ValidationDispatcher()}; +Event::DispatcherPtr ValidationImpl::allocateDispatcher(TimeSource& time_source) { + return Event::DispatcherPtr{new Event::ValidationDispatcher(time_source)}; } } // namespace Api diff --git a/source/server/config_validation/api.h b/source/server/config_validation/api.h index 710b775e16ae..2c568ab4c665 100644 --- a/source/server/config_validation/api.h +++ b/source/server/config_validation/api.h @@ -16,7 +16,7 @@ class ValidationImpl : public Impl { public: ValidationImpl(std::chrono::milliseconds file_flush_interval_msec); - Event::DispatcherPtr allocateDispatcher() override; + Event::DispatcherPtr allocateDispatcher(TimeSource&) override; }; } // namespace Api diff --git a/source/server/config_validation/async_client.cc b/source/server/config_validation/async_client.cc index d29fcefa2d32..991bbcc44e62 100644 --- a/source/server/config_validation/async_client.cc +++ b/source/server/config_validation/async_client.cc @@ -3,6 +3,8 @@ namespace Envoy { namespace Http { +ValidationAsyncClient::ValidationAsyncClient(TimeSource& time_source) : dispatcher_(time_source) {} + AsyncClient::Request* ValidationAsyncClient::send(MessagePtr&&, Callbacks&, const absl::optional&) { diff --git a/source/server/config_validation/async_client.h b/source/server/config_validation/async_client.h index a5050b2acef7..fa1f76811561 100644 --- a/source/server/config_validation/async_client.h +++ b/source/server/config_validation/async_client.h @@ -19,6 +19,8 @@ namespace Http { */ class ValidationAsyncClient : public AsyncClient { public: + ValidationAsyncClient(TimeSource& time_source); + // Http::AsyncClient AsyncClient::Request* send(MessagePtr&& request, Callbacks& callbacks, const absl::optional& timeout) override; diff --git a/source/server/config_validation/cluster_manager.cc b/source/server/config_validation/cluster_manager.cc index e2d98f74c03d..f90877ed4d76 100644 --- a/source/server/config_validation/cluster_manager.cc +++ b/source/server/config_validation/cluster_manager.cc @@ -39,8 +39,8 @@ ValidationClusterManager::ValidationClusterManager( AccessLog::AccessLogManager& log_manager, Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin) : ClusterManagerImpl(bootstrap, factory, stats, tls, runtime, random, local_info, log_manager, - main_thread_dispatcher, admin, ProdSystemTimeSource::instance_, - ProdMonotonicTimeSource::instance_) {} + main_thread_dispatcher, admin), + async_client_(main_thread_dispatcher.timeSource()) {} Http::ConnectionPool::Instance* ValidationClusterManager::httpConnPoolForCluster(const std::string&, ResourcePriority, diff --git a/source/server/config_validation/dispatcher.h b/source/server/config_validation/dispatcher.h index ec9c1fac8213..bb1e4d34fc96 100644 --- a/source/server/config_validation/dispatcher.h +++ b/source/server/config_validation/dispatcher.h @@ -16,6 +16,8 @@ namespace Event { */ class ValidationDispatcher : public DispatcherImpl { public: + ValidationDispatcher(TimeSource& time_source) : DispatcherImpl(time_source) {} + Network::ClientConnectionPtr createClientConnection(Network::Address::InstanceConstSharedPtr, Network::Address::InstanceConstSharedPtr, Network::TransportSocketPtr&&, diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index 8dc49dc87edc..90b57e8f2622 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -22,7 +22,10 @@ bool validateConfig(Options& options, Network::Address::InstanceConstSharedPtr l Stats::IsolatedStoreImpl stats_store; try { - ValidationInstance server(options, local_address, stats_store, access_log_lock, + ProdSystemTimeSource system_time_source; + ProdMonotonicTimeSource monotonic_time_source; + TimeSource time_source(system_time_source, monotonic_time_source); + ValidationInstance server(options, time_source, local_address, stats_store, access_log_lock, component_factory); std::cout << "configuration '" << options.configPath() << "' OK" << std::endl; server.shutdown(); @@ -32,16 +35,17 @@ bool validateConfig(Options& options, Network::Address::InstanceConstSharedPtr l } } -ValidationInstance::ValidationInstance(Options& options, +ValidationInstance::ValidationInstance(Options& options, TimeSource& time_source, Network::Address::InstanceConstSharedPtr local_address, Stats::IsolatedStoreImpl& store, Thread::BasicLockable& access_log_lock, ComponentFactory& component_factory) - : options_(options), stats_store_(store), + : options_(options), time_source_(time_source), stats_store_(store), api_(new Api::ValidationImpl(options.fileFlushIntervalMsec())), - dispatcher_(api_->allocateDispatcher()), singleton_manager_(new Singleton::ManagerImpl()), + dispatcher_(api_->allocateDispatcher(time_source)), + singleton_manager_(new Singleton::ManagerImpl()), access_log_manager_(*api_, *dispatcher_, access_log_lock, store), - listener_manager_(*this, *this, *this, ProdSystemTimeSource::instance_) { + listener_manager_(*this, *this, *this, time_source) { try { initialize(options, local_address, component_factory); } catch (const EnvoyException& e) { diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index 95d89d002504..b985dd4955c6 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -52,7 +52,8 @@ class ValidationInstance : Logger::Loggable, public ListenerComponentFactory, public WorkerFactory { public: - ValidationInstance(Options& options, Network::Address::InstanceConstSharedPtr local_address, + ValidationInstance(Options& options, TimeSource& time_source, + Network::Address::InstanceConstSharedPtr local_address, Stats::IsolatedStoreImpl& store, Thread::BasicLockable& access_log_lock, ComponentFactory& component_factory); @@ -92,6 +93,7 @@ class ValidationInstance : Logger::Loggable, Tracing::HttpTracer& httpTracer() override { return config_->httpTracer(); } ThreadLocal::Instance& threadLocal() override { return thread_local_; } const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } + TimeSource& timeSource() override { return time_source_; } std::chrono::milliseconds statsFlushInterval() const override { return config_->statsFlushInterval(); @@ -136,6 +138,7 @@ class ValidationInstance : Logger::Loggable, ComponentFactory& component_factory); Options& options_; + TimeSource time_source_; Stats::IsolatedStoreImpl& stats_store_; ThreadLocal::InstanceImpl thread_local_; Api::ApiPtr api_; diff --git a/source/server/configuration_impl.cc b/source/server/configuration_impl.cc index 57ff50ec322a..9c51ec48298c 100644 --- a/source/server/configuration_impl.cc +++ b/source/server/configuration_impl.cc @@ -53,6 +53,7 @@ void MainImpl::initialize(const envoy::config::bootstrap::v2::Bootstrap& bootstr server.secretManager().addStaticSecret(secrets[i]); } + ENVOY_LOG(info, "loading {} cluster(s)", bootstrap.static_resources().clusters().size()); cluster_manager_ = cluster_manager_factory.clusterManagerFromProto( bootstrap, server.stats(), server.threadLocal(), server.runtime(), server.random(), server.localInfo(), server.accessLogManager(), server.admin()); diff --git a/source/server/guarddog_impl.cc b/source/server/guarddog_impl.cc index e6ea334bbf50..2449bcd85ffa 100644 --- a/source/server/guarddog_impl.cc +++ b/source/server/guarddog_impl.cc @@ -14,7 +14,7 @@ namespace Envoy { namespace Server { GuardDogImpl::GuardDogImpl(Stats::Scope& stats_scope, const Server::Configuration::Main& config, - MonotonicTimeSource& tsource) + TimeSource& tsource) : time_source_(tsource), miss_timeout_(config.wdMissTimeout()), megamiss_timeout_(config.wdMegaMissTimeout()), kill_timeout_(config.wdKillTimeout()), multi_kill_timeout_(config.wdMultiKillTimeout()), @@ -37,7 +37,7 @@ GuardDogImpl::~GuardDogImpl() { stop(); } void GuardDogImpl::threadRoutine() { do { - const auto now = time_source_.currentTime(); + const auto now = time_source_.monotonicTime(); bool seen_one_multi_timeout(false); Thread::LockGuard guard(wd_lock_); for (auto& watched_dog : watched_dogs_) { diff --git a/source/server/guarddog_impl.h b/source/server/guarddog_impl.h index 31b161e725ec..543f7de8e188 100644 --- a/source/server/guarddog_impl.h +++ b/source/server/guarddog_impl.h @@ -38,7 +38,7 @@ class GuardDogImpl : public GuardDog { * See the configuration documentation for details on the timeout settings. */ GuardDogImpl(Stats::Scope& stats_scope, const Server::Configuration::Main& config, - MonotonicTimeSource& tsource); + TimeSource& tsource); ~GuardDogImpl(); /** @@ -75,7 +75,7 @@ class GuardDogImpl : public GuardDog { bool megamiss_alerted_{}; }; - MonotonicTimeSource& time_source_; + TimeSource time_source_; const std::chrono::milliseconds miss_timeout_; const std::chrono::milliseconds megamiss_timeout_; const std::chrono::milliseconds kill_timeout_; diff --git a/source/server/http/admin.cc b/source/server/http/admin.cc index 9a7c072880e3..583f7f4cba9c 100644 --- a/source/server/http/admin.cc +++ b/source/server/http/admin.cc @@ -853,8 +853,8 @@ void AdminFilter::onComplete() { } } -AdminImpl::NullRouteConfigProvider::NullRouteConfigProvider() - : config_(new Router::NullConfigImpl()) {} +AdminImpl::NullRouteConfigProvider::NullRouteConfigProvider(TimeSource& time_source) + : config_(new Router::NullConfigImpl()), time_source_(time_source) {} AdminImpl::AdminImpl(const std::string& access_log_path, const std::string& profile_path, const std::string& address_out_path, @@ -865,6 +865,7 @@ AdminImpl::AdminImpl(const std::string& access_log_path, const std::string& prof stats_(Http::ConnectionManagerImpl::generateStats("http.admin.", server_.stats())), tracing_stats_( Http::ConnectionManagerImpl::generateTracingStats("http.admin.", no_op_store_)), + route_config_provider_(server.timeSource()), handlers_{ {"/", "Admin home page", MAKE_ADMIN_HANDLER(handlerAdminHome), false, false}, {"/certs", "print certs on machine", MAKE_ADMIN_HANDLER(handlerCerts), false, false}, diff --git a/source/server/http/admin.h b/source/server/http/admin.h index 45ebeb391df3..78957e06dc80 100644 --- a/source/server/http/admin.h +++ b/source/server/http/admin.h @@ -133,16 +133,15 @@ class AdminImpl : public Admin, * Implementation of RouteConfigProvider that returns a static null route config. */ struct NullRouteConfigProvider : public Router::RouteConfigProvider { - NullRouteConfigProvider(); + NullRouteConfigProvider(TimeSource& time_source); // Router::RouteConfigProvider Router::ConfigConstSharedPtr config() override { return config_; } absl::optional configInfo() const override { return {}; } - SystemTime lastUpdated() const override { - return ProdSystemTimeSource::instance_.currentTime(); - } + SystemTime lastUpdated() const override { return time_source_.systemTime(); } Router::ConfigConstSharedPtr config_; + TimeSource& time_source_; }; friend class AdminStatsTest; diff --git a/source/server/init_manager_impl.h b/source/server/init_manager_impl.h index bc953dada2af..2a90799b439e 100644 --- a/source/server/init_manager_impl.h +++ b/source/server/init_manager_impl.h @@ -9,6 +9,7 @@ namespace Server { /** * Implementation of Init::Manager for use during post cluster manager init / pre listening. + * TODO(JimmyCYJ): Move InitManagerImpl into a new subdirectory in source/ called init/. */ class InitManagerImpl : public Init::Manager { public: diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 03afc4c08e1e..c4ece6422abd 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -231,6 +231,7 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st parent_.server_.sslContextManager(), *listener_scope_, parent_.server_.clusterManager(), parent_.server_.localInfo(), parent_.server_.dispatcher(), parent_.server_.random(), parent_.server_.stats()); + factory_context.setInitManager(initManager()); addFilterChain( PROTOBUF_GET_WRAPPED_OR_DEFAULT(filter_chain_match, destination_port, 0), destination_ips, server_names, filter_chain_match.transport_protocol(), application_protocols, @@ -525,7 +526,7 @@ void ListenerImpl::debugLog(const std::string& message) { } void ListenerImpl::initialize() { - last_updated_ = systemTimeSource().currentTime(); + last_updated_ = timeSource().systemTime(); // If workers have already started, we shift from using the global init manager to using a local // per listener init manager. See ~ListenerImpl() for why we gate the onListenerWarmed() call // with initialize_canceled_. @@ -572,13 +573,12 @@ void ListenerImpl::setSocket(const Network::SocketSharedPtr& socket) { ListenerManagerImpl::ListenerManagerImpl(Instance& server, ListenerComponentFactory& listener_factory, - WorkerFactory& worker_factory, - SystemTimeSource& system_time_source) - : server_(server), system_time_source_(system_time_source), factory_(listener_factory), + WorkerFactory& worker_factory, TimeSource& time_source) + : server_(server), time_source_(time_source), factory_(listener_factory), stats_(generateStats(server.stats())), config_tracker_entry_(server.admin().getConfigTracker().add( "listeners", [this] { return dumpListenerConfigs(); })) { - for (uint32_t i = 0; i < std::max(1U, server.options().concurrency()); i++) { + for (uint32_t i = 0; i < server.options().concurrency(); i++) { workers_.emplace_back(worker_factory.createWorker()); } } diff --git a/source/server/listener_manager_impl.h b/source/server/listener_manager_impl.h index acfe249b906d..84ae38aaae20 100644 --- a/source/server/listener_manager_impl.h +++ b/source/server/listener_manager_impl.h @@ -102,7 +102,7 @@ struct ListenerManagerStats { class ListenerManagerImpl : public ListenerManager, Logger::Loggable { public: ListenerManagerImpl(Instance& server, ListenerComponentFactory& listener_factory, - WorkerFactory& worker_factory, SystemTimeSource& system_time_source); + WorkerFactory& worker_factory, TimeSource& time_source); void onListenerWarmed(ListenerImpl& listener); @@ -121,7 +121,7 @@ class ListenerManagerImpl : public ListenerManager, Logger::LoggableallocateDispatcher()), + : options_(options), time_source_(time_source), restarter_(restarter), + start_time_(time(nullptr)), original_start_time_(start_time_), stats_store_(store), + thread_local_(tls), api_(new Api::Impl(options.fileFlushIntervalMsec())), + dispatcher_(api_->allocateDispatcher(time_source)), singleton_manager_(new Singleton::ManagerImpl()), handler_(new ConnectionHandlerImpl(ENVOY_LOGGER(), *dispatcher_)), - random_generator_(std::move(random_generator)), listener_component_factory_(*this), + random_generator_(std::move(random_generator)), + secret_manager_(new Secret::SecretManagerImpl()), listener_component_factory_(*this), worker_factory_(thread_local_, *api_, hooks), - secret_manager_(new Secret::SecretManagerImpl()), dns_resolver_(dispatcher_->createDnsResolver({})), access_log_manager_(*api_, *dispatcher_, access_log_lock, store), terminated_(false) { @@ -137,7 +139,6 @@ void InstanceImpl::flushStats() { server_stats_->total_connections_.set(numConnections() + info.num_connections_); server_stats_->days_until_first_cert_expiring_.set( sslContextManager().daysUntilFirstCertExpires()); - server_stats_->hot_restart_epoch_.set(options_.restartEpoch()); InstanceUtil::flushMetricsToSinks(config_->statsSinks(), stats_store_.source()); // TODO(ramaraochavali): consider adding different flush interval for histograms. if (stat_flush_timer_ != nullptr) { @@ -214,7 +215,7 @@ void InstanceImpl::initialize(Options& options, // Handle configuration that needs to take place prior to the main configuration load. InstanceUtil::loadBootstrapConfig(bootstrap_, options); - bootstrap_config_update_time_ = ProdSystemTimeSource::instance_.currentTime(); + bootstrap_config_update_time_ = time_source_.systemTime(); // Needs to happen as early as possible in the instantiation to preempt the objects that require // stats. @@ -223,6 +224,9 @@ void InstanceImpl::initialize(Options& options, server_stats_.reset( new ServerStats{ALL_SERVER_STATS(POOL_GAUGE_PREFIX(stats_store_, "server."))}); + server_stats_->concurrency_.set(options_.concurrency()); + server_stats_->hot_restart_epoch_.set(options_.restartEpoch()); + failHealthcheck(false); uint64_t version_int; @@ -255,8 +259,8 @@ void InstanceImpl::initialize(Options& options, loadServerFlags(initial_config.flagsPath()); // Workers get created first so they register for thread local updates. - listener_manager_.reset(new ListenerManagerImpl( - *this, listener_component_factory_, worker_factory_, ProdSystemTimeSource::instance_)); + listener_manager_.reset( + new ListenerManagerImpl(*this, listener_component_factory_, worker_factory_, time_source_)); // The main thread is also registered for thread local updates so that code that does not care // whether it runs on the main thread or on workers can still use TLS. @@ -315,8 +319,7 @@ void InstanceImpl::initialize(Options& options, // GuardDog (deadlock detection) object and thread setup before workers are // started and before our own run() loop runs. - guard_dog_.reset( - new Server::GuardDogImpl(stats_store_, *config_, ProdMonotonicTimeSource::instance_)); + guard_dog_.reset(new Server::GuardDogImpl(stats_store_, *config_, time_source_)); } void InstanceImpl::startWorkers() { diff --git a/source/server/server.h b/source/server/server.h index 40cb5001539e..71b4af2a7e2b 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -41,6 +41,7 @@ namespace Server { // clang-format off #define ALL_SERVER_STATS(GAUGE) \ GAUGE(uptime) \ + GAUGE(concurrency) \ GAUGE(memory_allocated) \ GAUGE(memory_heap_size) \ GAUGE(live) \ @@ -134,8 +135,9 @@ class InstanceImpl : Logger::Loggable, public Instance { /** * @throw EnvoyException if initialization fails. */ - InstanceImpl(Options& options, Network::Address::InstanceConstSharedPtr local_address, - TestHooks& hooks, HotRestart& restarter, Stats::StoreRoot& store, + InstanceImpl(Options& options, TimeSource& time_source, + Network::Address::InstanceConstSharedPtr local_address, TestHooks& hooks, + HotRestart& restarter, Stats::StoreRoot& store, Thread::BasicLockable& access_log_lock, ComponentFactory& component_factory, Runtime::RandomGeneratorPtr&& random_generator, ThreadLocal::Instance& tls); @@ -177,6 +179,7 @@ class InstanceImpl : Logger::Loggable, public Instance { Tracing::HttpTracer& httpTracer() override; ThreadLocal::Instance& threadLocal() override { return thread_local_; } const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } + TimeSource& timeSource() override { return time_source_; } std::chrono::milliseconds statsFlushInterval() const override { return config_->statsFlushInterval(); @@ -193,6 +196,7 @@ class InstanceImpl : Logger::Loggable, public Instance { void terminate(); Options& options_; + TimeSource time_source_; HotRestart& restarter_; const time_t start_time_; time_t original_start_time_; @@ -206,11 +210,11 @@ class InstanceImpl : Logger::Loggable, public Instance { Network::ConnectionHandlerPtr handler_; Runtime::RandomGeneratorPtr random_generator_; Runtime::LoaderPtr runtime_loader_; + std::unique_ptr secret_manager_; std::unique_ptr ssl_context_manager_; ProdListenerComponentFactory listener_component_factory_; ProdWorkerFactory worker_factory_; std::unique_ptr listener_manager_; - std::unique_ptr secret_manager_; std::unique_ptr config_; Network::DnsResolverSharedPtr dns_resolver_; Event::TimerPtr stat_flush_timer_; diff --git a/source/server/transport_socket_config_impl.h b/source/server/transport_socket_config_impl.h index d5adcd09a575..c9747cc6b541 100644 --- a/source/server/transport_socket_config_impl.h +++ b/source/server/transport_socket_config_impl.h @@ -24,12 +24,12 @@ class TransportSocketFactoryContextImpl : public TransportSocketFactoryContext { Stats::Scope& statsScope() const override { return stats_scope_; } - Upstream::ClusterManager& clusterManager() override { return cluster_manager_; } - Secret::SecretManager& secretManager() override { return cluster_manager_.clusterManagerFactory().secretManager(); } + Upstream::ClusterManager& clusterManager() override { return cluster_manager_; } + const LocalInfo::LocalInfo& localInfo() override { return local_info_; } Event::Dispatcher& dispatcher() override { return dispatcher_; } @@ -38,6 +38,10 @@ class TransportSocketFactoryContextImpl : public TransportSocketFactoryContext { Stats::Store& stats() override { return stats_; } + void setInitManager(Init::Manager& init_manager) override { init_manager_ = &init_manager; } + + Init::Manager* initManager() override { return init_manager_; } + private: Ssl::ContextManager& context_manager_; Stats::Scope& stats_scope_; @@ -46,6 +50,7 @@ class TransportSocketFactoryContextImpl : public TransportSocketFactoryContext { Event::Dispatcher& dispatcher_; Envoy::Runtime::RandomGenerator& random_; Stats::Store& stats_; + Init::Manager* init_manager_{}; }; } // namespace Configuration diff --git a/source/server/watchdog_impl.h b/source/server/watchdog_impl.h index 68d06690eb00..8898d2a07e34 100644 --- a/source/server/watchdog_impl.h +++ b/source/server/watchdog_impl.h @@ -20,9 +20,9 @@ class WatchDogImpl : public WatchDog { * @param thread_id A system thread ID (such as from Thread::currentThreadId()) * @param interval WatchDog timer interval (used after startWatchdog()) */ - WatchDogImpl(int32_t thread_id, MonotonicTimeSource& tsource, std::chrono::milliseconds interval) + WatchDogImpl(int32_t thread_id, TimeSource& tsource, std::chrono::milliseconds interval) : thread_id_(thread_id), time_source_(tsource), - latest_touch_time_since_epoch_(tsource.currentTime().time_since_epoch()), + latest_touch_time_since_epoch_(tsource.monotonicTime().time_since_epoch()), timer_interval_(interval) {} int32_t threadId() const override { return thread_id_; } @@ -33,12 +33,12 @@ class WatchDogImpl : public WatchDog { // Server::WatchDog void startWatchdog(Event::Dispatcher& dispatcher) override; void touch() override { - latest_touch_time_since_epoch_.store(time_source_.currentTime().time_since_epoch()); + latest_touch_time_since_epoch_.store(time_source_.monotonicTime().time_since_epoch()); } private: const int32_t thread_id_; - MonotonicTimeSource& time_source_; + TimeSource time_source_; std::atomic latest_touch_time_since_epoch_; Event::TimerPtr timer_; const std::chrono::milliseconds timer_interval_; diff --git a/source/server/worker_impl.cc b/source/server/worker_impl.cc index 6c18db816944..0e1e55e72dca 100644 --- a/source/server/worker_impl.cc +++ b/source/server/worker_impl.cc @@ -15,7 +15,7 @@ namespace Envoy { namespace Server { WorkerPtr ProdWorkerFactory::createWorker() { - Event::DispatcherPtr dispatcher(api_.allocateDispatcher()); + Event::DispatcherPtr dispatcher(api_.allocateDispatcher(time_source_)); return WorkerPtr{new WorkerImpl( tls_, hooks_, std::move(dispatcher), Network::ConnectionHandlerPtr{new ConnectionHandlerImpl(ENVOY_LOGGER(), *dispatcher)})}; diff --git a/source/server/worker_impl.h b/source/server/worker_impl.h index ea018c3b73d5..3ef7075ee806 100644 --- a/source/server/worker_impl.h +++ b/source/server/worker_impl.h @@ -20,8 +20,9 @@ namespace Server { class ProdWorkerFactory : public WorkerFactory, Logger::Loggable { public: - ProdWorkerFactory(ThreadLocal::Instance& tls, Api::Api& api, TestHooks& hooks) - : tls_(tls), api_(api), hooks_(hooks) {} + ProdWorkerFactory(ThreadLocal::Instance& tls, Api::Api& api, TestHooks& hooks, + TimeSource& time_source) + : tls_(tls), api_(api), hooks_(hooks), time_source_(time_source) {} // Server::WorkerFactory WorkerPtr createWorker() override; @@ -30,6 +31,7 @@ class ProdWorkerFactory : public WorkerFactory, Logger::Loggable all_response_flags = { @@ -834,6 +835,7 @@ name: envoy.file_access_log RequestInfo::ResponseFlag::FaultInjected, RequestInfo::ResponseFlag::RateLimited, RequestInfo::ResponseFlag::UnauthorizedExternalService, + RequestInfo::ResponseFlag::RateLimitServiceError, }; InstanceSharedPtr log = AccessLogFactory::fromProto(parseAccessLogFromV2Yaml(yaml), context_); @@ -863,7 +865,7 @@ name: envoy.file_access_log "Proto constraint validation failed (AccessLogFilterValidationError.ResponseFlagFilter: " "[\"embedded message failed validation\"] | caused by " "ResponseFlagFilterValidationError.Flags[i]: [\"value must be in list \" [\"LH\" \"UH\" " - "\"UT\" \"LR\" \"UR\" \"UF\" \"UC\" \"UO\" \"NR\" \"DI\" \"FI\" \"RL\" \"UAEX\"]]): " + "\"UT\" \"LR\" \"UR\" \"UF\" \"UC\" \"UO\" \"NR\" \"DI\" \"FI\" \"RL\" \"UAEX\" \"RLSE\"]]): " "response_flag_filter {\n flags: \"UnsupportedFlag\"\n}\n"); } diff --git a/test/common/common/token_bucket_impl_test.cc b/test/common/common/token_bucket_impl_test.cc index e31717892be1..ae0b87b0b0ad 100644 --- a/test/common/common/token_bucket_impl_test.cc +++ b/test/common/common/token_bucket_impl_test.cc @@ -18,24 +18,25 @@ class TokenBucketImplTest : public testing::Test { using time_point = std::chrono::steady_clock::time_point; protected: - NiceMock time_source_; + NiceMock mock_monotonic_time_source_; }; // Verifies TokenBucket initialization. TEST_F(TokenBucketImplTest, Initialization) { - TokenBucketImpl token_bucket{1, -1.0, time_source_}; + TokenBucketImpl token_bucket{1, mock_monotonic_time_source_, -1.0}; EXPECT_TRUE(token_bucket.consume()); - EXPECT_CALL(time_source_, currentTime()).WillOnce(Return(time_point{})); + EXPECT_CALL(mock_monotonic_time_source_, currentTime()).WillOnce(Return(time_point{})); EXPECT_FALSE(token_bucket.consume()); } // Verifies TokenBucket's maximum capacity. TEST_F(TokenBucketImplTest, MaxBucketSize) { - TokenBucketImpl token_bucket{3, 1, time_source_}; + TokenBucketImpl token_bucket{3, mock_monotonic_time_source_, 1}; EXPECT_TRUE(token_bucket.consume(3)); - EXPECT_CALL(time_source_, currentTime()).WillOnce(Return(time_point(std::chrono::seconds(10)))); + EXPECT_CALL(mock_monotonic_time_source_, currentTime()) + .WillOnce(Return(time_point(std::chrono::seconds(10)))); EXPECT_FALSE(token_bucket.consume(4)); EXPECT_TRUE(token_bucket.consume(3)); @@ -44,36 +45,35 @@ TEST_F(TokenBucketImplTest, MaxBucketSize) { // Verifies that TokenBucket can consume and refill tokens. TEST_F(TokenBucketImplTest, ConsumeAndRefill) { { - TokenBucketImpl token_bucket{10, 1, time_source_}; + TokenBucketImpl token_bucket{10, mock_monotonic_time_source_, 1}; EXPECT_FALSE(token_bucket.consume(20)); EXPECT_TRUE(token_bucket.consume(9)); - EXPECT_CALL(time_source_, currentTime()).WillOnce(Return(time_point{})); + EXPECT_CALL(mock_monotonic_time_source_, currentTime()).WillOnce(Return(time_point{})); EXPECT_TRUE(token_bucket.consume()); - EXPECT_CALL(time_source_, currentTime()) + EXPECT_CALL(mock_monotonic_time_source_, currentTime()) .WillOnce(Return(time_point(std::chrono::milliseconds(999)))); EXPECT_FALSE(token_bucket.consume()); - EXPECT_CALL(time_source_, currentTime()) + EXPECT_CALL(mock_monotonic_time_source_, currentTime()) .WillOnce(Return(time_point(std::chrono::milliseconds(5999)))); EXPECT_FALSE(token_bucket.consume(6)); - EXPECT_CALL(time_source_, currentTime()) + EXPECT_CALL(mock_monotonic_time_source_, currentTime()) .WillRepeatedly(Return(time_point(std::chrono::milliseconds(6000)))); EXPECT_TRUE(token_bucket.consume(6)); EXPECT_FALSE(token_bucket.consume()); } - ASSERT_TRUE(Mock::VerifyAndClear(&time_source_)); + ASSERT_TRUE(Mock::VerifyAndClear(&mock_monotonic_time_source_)); { - TokenBucketImpl token_bucket{1, 0.5, time_source_}; - + TokenBucketImpl token_bucket{1, mock_monotonic_time_source_, 0.5}; EXPECT_TRUE(token_bucket.consume()); - EXPECT_CALL(time_source_, currentTime()) + EXPECT_CALL(mock_monotonic_time_source_, currentTime()) .Times(3) .WillOnce(Return(time_point(std::chrono::milliseconds(500)))) .WillOnce(Return(time_point(std::chrono::milliseconds(1500)))) @@ -83,7 +83,7 @@ TEST_F(TokenBucketImplTest, ConsumeAndRefill) { EXPECT_FALSE(token_bucket.consume()); EXPECT_TRUE(token_bucket.consume()); - ASSERT_TRUE(Mock::VerifyAndClear(&time_source_)); + ASSERT_TRUE(Mock::VerifyAndClear(&mock_monotonic_time_source_)); } } diff --git a/test/common/config/BUILD b/test/common/config/BUILD index e57d4f569f41..85b0b00c56dd 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -30,6 +30,7 @@ envoy_cc_test_library( "//source/common/event:dispatcher_lib", "//test/mocks/config:config_mocks", "//test/test_common:environment_lib", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/api/v2:eds_cc", ], diff --git a/test/common/config/filesystem_subscription_test_harness.h b/test/common/config/filesystem_subscription_test_harness.h index 3ad7886969d2..155cabb0c8ac 100644 --- a/test/common/config/filesystem_subscription_test_harness.h +++ b/test/common/config/filesystem_subscription_test_harness.h @@ -9,6 +9,7 @@ #include "test/common/config/subscription_test_harness.h" #include "test/mocks/config/mocks.h" #include "test/test_common/environment.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -27,7 +28,7 @@ typedef FilesystemSubscriptionImpl class FilesystemSubscriptionTestHarness : public SubscriptionTestHarness { public: FilesystemSubscriptionTestHarness() - : path_(TestEnvironment::temporaryPath("eds.json")), + : path_(TestEnvironment::temporaryPath("eds.json")), dispatcher_(test_time_.timeSource()), subscription_(dispatcher_, path_, stats_) {} ~FilesystemSubscriptionTestHarness() { EXPECT_EQ(0, ::unlink(path_.c_str())); } @@ -94,6 +95,7 @@ class FilesystemSubscriptionTestHarness : public SubscriptionTestHarness { const std::string path_; std::string version_; + DangerousDeprecatedTestTime test_time_; Event::DispatcherImpl dispatcher_; NiceMock> callbacks_; FilesystemEdsSubscriptionImpl subscription_; diff --git a/test/common/config/grpc_mux_impl_test.cc b/test/common/config/grpc_mux_impl_test.cc index 170fc64e8956..cd2293c5d0e5 100644 --- a/test/common/config/grpc_mux_impl_test.cc +++ b/test/common/config/grpc_mux_impl_test.cc @@ -34,20 +34,18 @@ namespace { // is provided in [grpc_]subscription_impl_test.cc. class GrpcMuxImplTest : public testing::Test { public: - GrpcMuxImplTest() : async_client_(new Grpc::MockAsyncClient()), time_source_{} {} + GrpcMuxImplTest() + : async_client_(new Grpc::MockAsyncClient()), + mock_time_source_(mock_system_time_, mock_monotonic_time_) { + dispatcher_.setTimeSource(mock_time_source_); + } void setup() { - EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb timer_cb) { - timer_cb_ = timer_cb; - timer_ = new Event::MockTimer(); - return timer_; - })); - grpc_mux_.reset(new GrpcMuxImpl( local_info_, std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, time_source_)); + random_)); } void expectSendMessage(const std::string& type_url, @@ -76,12 +74,12 @@ class GrpcMuxImplTest : public testing::Test { NiceMock dispatcher_; Runtime::MockRandomGenerator random_; Grpc::MockAsyncClient* async_client_; - Event::MockTimer* timer_; - Event::TimerCb timer_cb_; Grpc::MockAsyncStream async_stream_; std::unique_ptr grpc_mux_; NiceMock callbacks_; - NiceMock time_source_; + NiceMock mock_system_time_; + NiceMock mock_monotonic_time_; + TimeSource mock_time_source_; NiceMock local_info_; }; @@ -107,6 +105,14 @@ TEST_F(GrpcMuxImplTest, MultipleTypeUrlStreams) { // Validate behavior when multiple type URL watches are maintained and the stream is reset. TEST_F(GrpcMuxImplTest, ResetStream) { + Event::MockTimer* timer = nullptr; + Event::TimerCb timer_cb; + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([&timer, &timer_cb](Event::TimerCb cb) { + timer_cb = cb; + EXPECT_EQ(nullptr, timer); + timer = new Event::MockTimer(); + return timer; + })); setup(); InSequence s; auto foo_sub = grpc_mux_->subscribe("foo", {"x", "y"}, callbacks_); @@ -119,13 +125,14 @@ TEST_F(GrpcMuxImplTest, ResetStream) { grpc_mux_->start(); EXPECT_CALL(random_, random()); - EXPECT_CALL(*timer_, enableTimer(_)); + ASSERT_TRUE(timer != nullptr); // initialized from dispatcher mock. + EXPECT_CALL(*timer, enableTimer(_)); grpc_mux_->onRemoteClose(Grpc::Status::GrpcStatus::Canceled, ""); EXPECT_CALL(*async_client_, start(_, _)).WillOnce(Return(&async_stream_)); expectSendMessage("foo", {"x", "y"}, ""); expectSendMessage("bar", {}, ""); expectSendMessage("baz", {"z"}, ""); - timer_cb_(); + timer_cb(); expectSendMessage("baz", {}, ""); expectSendMessage("foo", {}, ""); @@ -310,7 +317,7 @@ TEST_F(GrpcMuxImplTest, TooManyRequests) { EXPECT_CALL(async_stream_, sendMessage(_, false)).Times(AtLeast(100)); EXPECT_CALL(*async_client_, start(_, _)).WillOnce(Return(&async_stream_)); - EXPECT_CALL(time_source_, currentTime()) + EXPECT_CALL(mock_monotonic_time_, currentTime()) .WillRepeatedly(Return(std::chrono::steady_clock::time_point{})); const auto onReceiveMessage = [&](uint64_t burst) { @@ -336,7 +343,7 @@ TEST_F(GrpcMuxImplTest, TooManyRequests) { onReceiveMessage(1)); // Logging limiter waits for 5s, so a second warning message is expected. - EXPECT_CALL(time_source_, currentTime()) + EXPECT_CALL(mock_monotonic_time_, currentTime()) .Times(4) .WillOnce(Return(std::chrono::steady_clock::time_point{})) .WillOnce(Return(std::chrono::steady_clock::time_point{std::chrono::seconds(5)})) @@ -425,7 +432,7 @@ TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyClusterName) { local_info_, std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, time_source_), + random_), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); @@ -438,7 +445,7 @@ TEST_F(GrpcMuxImplTest, BadLocalInfoEmptyNodeName) { local_info_, std::unique_ptr(async_client_), dispatcher_, *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v2.AggregatedDiscoveryService.StreamAggregatedResources"), - random_, time_source_), + random_), EnvoyException, "ads: node 'id' and 'cluster' are required. Set it either in 'node' config or via " "--service-node and --service-cluster options."); diff --git a/test/common/event/BUILD b/test/common/event/BUILD index 575787b6c01f..b1aaef611407 100644 --- a/test/common/event/BUILD +++ b/test/common/event/BUILD @@ -15,6 +15,7 @@ envoy_cc_test( "//source/common/event:dispatcher_includes", "//source/common/event:dispatcher_lib", "//test/mocks:common_lib", + "//test/test_common:test_time_lib", ], ) @@ -27,6 +28,7 @@ envoy_cc_test( "//source/common/event:dispatcher_lib", "//test/mocks:common_lib", "//test/test_common:environment_lib", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) @@ -41,5 +43,6 @@ envoy_cc_test( "//test/mocks:common_lib", "//test/mocks/server:server_mocks", "//test/mocks/stats:stats_mocks", + "//test/test_common:test_time_lib", ], ) diff --git a/test/common/event/dispatched_thread_impl_test.cc b/test/common/event/dispatched_thread_impl_test.cc index a177dcb6bd79..915dc9bee502 100644 --- a/test/common/event/dispatched_thread_impl_test.cc +++ b/test/common/event/dispatched_thread_impl_test.cc @@ -8,6 +8,7 @@ #include "test/mocks/common.h" #include "test/mocks/server/mocks.h" #include "test/mocks/stats/mocks.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -21,12 +22,13 @@ namespace Event { class DispatchedThreadTest : public testing::Test { protected: DispatchedThreadTest() - : config_(1000, 1000, 1000, 1000), guard_dog_(fakestats_, config_, time_source_) {} + : config_(1000, 1000, 1000, 1000), guard_dog_(fakestats_, config_, test_time_.timeSource()), + thread_(test_time_.timeSource()) {} void SetUp() { thread_.start(guard_dog_); } NiceMock config_; NiceMock fakestats_; - ProdMonotonicTimeSource time_source_; + DangerousDeprecatedTestTime test_time_; Envoy::Server::GuardDogImpl guard_dog_; DispatchedThreadImpl thread_; }; diff --git a/test/common/event/dispatcher_impl_test.cc b/test/common/event/dispatcher_impl_test.cc index 4b2abbf2e7e3..5a8ed141b26e 100644 --- a/test/common/event/dispatcher_impl_test.cc +++ b/test/common/event/dispatcher_impl_test.cc @@ -5,6 +5,7 @@ #include "common/event/dispatcher_impl.h" #include "test/mocks/common.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -25,7 +26,8 @@ class TestDeferredDeletable : public DeferredDeletable { TEST(DeferredDeleteTest, DeferredDelete) { InSequence s; - DispatcherImpl dispatcher; + DangerousDeprecatedTestTime test_time; + DispatcherImpl dispatcher(test_time.timeSource()); ReadyWatcher watcher1; dispatcher.deferredDelete( @@ -55,7 +57,9 @@ TEST(DeferredDeleteTest, DeferredDelete) { class DispatcherImplTest : public ::testing::Test { protected: - DispatcherImplTest() : dispatcher_(std::make_unique()), work_finished_(false) { + DispatcherImplTest() + : dispatcher_(std::make_unique(test_time_.timeSource())), + work_finished_(false) { dispatcher_thread_ = std::make_unique([this]() { // Must create a keepalive timer to keep the dispatcher from exiting. std::chrono::milliseconds time_interval(500); @@ -72,6 +76,8 @@ class DispatcherImplTest : public ::testing::Test { dispatcher_thread_->join(); } + DangerousDeprecatedTestTime test_time_; + std::unique_ptr dispatcher_thread_; DispatcherPtr dispatcher_; Thread::MutexBasicLockable mu_; diff --git a/test/common/event/file_event_impl_test.cc b/test/common/event/file_event_impl_test.cc index c2b7dd70e867..888247a006b2 100644 --- a/test/common/event/file_event_impl_test.cc +++ b/test/common/event/file_event_impl_test.cc @@ -6,6 +6,7 @@ #include "test/mocks/common.h" #include "test/test_common/environment.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -15,6 +16,8 @@ namespace Event { class FileEventImplTest : public testing::Test { public: + FileEventImplTest() : dispatcher_(test_time_.timeSource()) {} + void SetUp() override { int rc = socketpair(AF_UNIX, SOCK_DGRAM, 0, fds_); ASSERT_EQ(0, rc); @@ -30,6 +33,8 @@ class FileEventImplTest : public testing::Test { protected: int fds_[2]; + DangerousDeprecatedTestTime test_time_; + DispatcherImpl dispatcher_; }; class FileEventImplActivateTest : public testing::TestWithParam {}; @@ -47,7 +52,8 @@ TEST_P(FileEventImplActivateTest, Activate) { } ASSERT_NE(-1, fd); - DispatcherImpl dispatcher; + DangerousDeprecatedTestTime test_time; + DispatcherImpl dispatcher(test_time.timeSource()); ReadyWatcher read_event; EXPECT_CALL(read_event, ready()).Times(1); ReadyWatcher write_event; @@ -79,41 +85,39 @@ TEST_P(FileEventImplActivateTest, Activate) { } TEST_F(FileEventImplTest, EdgeTrigger) { - DispatcherImpl dispatcher; ReadyWatcher read_event; EXPECT_CALL(read_event, ready()).Times(1); ReadyWatcher write_event; EXPECT_CALL(write_event, ready()).Times(1); - Event::FileEventPtr file_event = - dispatcher.createFileEvent(fds_[0], - [&](uint32_t events) -> void { - if (events & FileReadyType::Read) { - read_event.ready(); - } + Event::FileEventPtr file_event = dispatcher_.createFileEvent( + fds_[0], + [&](uint32_t events) -> void { + if (events & FileReadyType::Read) { + read_event.ready(); + } - if (events & FileReadyType::Write) { - write_event.ready(); - } - }, - FileTriggerType::Edge, FileReadyType::Read | FileReadyType::Write); + if (events & FileReadyType::Write) { + write_event.ready(); + } + }, + FileTriggerType::Edge, FileReadyType::Read | FileReadyType::Write); - dispatcher.run(Event::Dispatcher::RunType::NonBlock); + dispatcher_.run(Event::Dispatcher::RunType::NonBlock); } TEST_F(FileEventImplTest, LevelTrigger) { - DispatcherImpl dispatcher; ReadyWatcher read_event; EXPECT_CALL(read_event, ready()).Times(2); ReadyWatcher write_event; EXPECT_CALL(write_event, ready()).Times(2); int count = 2; - Event::FileEventPtr file_event = dispatcher.createFileEvent( + Event::FileEventPtr file_event = dispatcher_.createFileEvent( fds_[0], [&](uint32_t events) -> void { if (count-- == 0) { - dispatcher.exit(); + dispatcher_.exit(); return; } if (events & FileReadyType::Read) { @@ -126,40 +130,39 @@ TEST_F(FileEventImplTest, LevelTrigger) { }, FileTriggerType::Level, FileReadyType::Read | FileReadyType::Write); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); } TEST_F(FileEventImplTest, SetEnabled) { - DispatcherImpl dispatcher; ReadyWatcher read_event; EXPECT_CALL(read_event, ready()).Times(2); ReadyWatcher write_event; EXPECT_CALL(write_event, ready()).Times(2); - Event::FileEventPtr file_event = - dispatcher.createFileEvent(fds_[0], - [&](uint32_t events) -> void { - if (events & FileReadyType::Read) { - read_event.ready(); - } + Event::FileEventPtr file_event = dispatcher_.createFileEvent( + fds_[0], + [&](uint32_t events) -> void { + if (events & FileReadyType::Read) { + read_event.ready(); + } - if (events & FileReadyType::Write) { - write_event.ready(); - } - }, - FileTriggerType::Edge, FileReadyType::Read | FileReadyType::Write); + if (events & FileReadyType::Write) { + write_event.ready(); + } + }, + FileTriggerType::Edge, FileReadyType::Read | FileReadyType::Write); file_event->setEnabled(FileReadyType::Read); - dispatcher.run(Event::Dispatcher::RunType::NonBlock); + dispatcher_.run(Event::Dispatcher::RunType::NonBlock); file_event->setEnabled(FileReadyType::Write); - dispatcher.run(Event::Dispatcher::RunType::NonBlock); + dispatcher_.run(Event::Dispatcher::RunType::NonBlock); file_event->setEnabled(0); - dispatcher.run(Event::Dispatcher::RunType::NonBlock); + dispatcher_.run(Event::Dispatcher::RunType::NonBlock); file_event->setEnabled(FileReadyType::Read | FileReadyType::Write); - dispatcher.run(Event::Dispatcher::RunType::NonBlock); + dispatcher_.run(Event::Dispatcher::RunType::NonBlock); } } // namespace Event diff --git a/test/common/filesystem/BUILD b/test/common/filesystem/BUILD index 8ae711154adb..c7ba54253d90 100644 --- a/test/common/filesystem/BUILD +++ b/test/common/filesystem/BUILD @@ -36,5 +36,6 @@ envoy_cc_test( "//source/common/event:dispatcher_lib", "//source/common/filesystem:watcher_lib", "//test/test_common:environment_lib", + "//test/test_common:test_time_lib", ], ) diff --git a/test/common/filesystem/watcher_impl_test.cc b/test/common/filesystem/watcher_impl_test.cc index 5f78c0ecd17f..4ddb442beced 100644 --- a/test/common/filesystem/watcher_impl_test.cc +++ b/test/common/filesystem/watcher_impl_test.cc @@ -6,6 +6,7 @@ #include "common/filesystem/watcher_impl.h" #include "test/test_common/environment.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -13,14 +14,21 @@ namespace Envoy { namespace Filesystem { +class WatcherImplTest : public testing::Test { +protected: + WatcherImplTest() : dispatcher_(test_time_.timeSource()) {} + + DangerousDeprecatedTestTime test_time_; + Event::DispatcherImpl dispatcher_; +}; + class WatchCallback { public: MOCK_METHOD1(called, void(uint32_t)); }; -TEST(WatcherImplTest, All) { - Event::DispatcherImpl dispatcher; - Filesystem::WatcherPtr watcher = dispatcher.createFilesystemWatcher(); +TEST_F(WatcherImplTest, All) { + Filesystem::WatcherPtr watcher = dispatcher_.createFilesystemWatcher(); unlink(TestEnvironment::temporaryPath("envoy_test/watcher_target").c_str()); unlink(TestEnvironment::temporaryPath("envoy_test/watcher_link").c_str()); @@ -43,24 +51,23 @@ TEST(WatcherImplTest, All) { watcher->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_link"), Watcher::Events::MovedTo, [&](uint32_t events) -> void { callback.called(events); - dispatcher.exit(); + dispatcher_.exit(); }); rename(TestEnvironment::temporaryPath("envoy_test/watcher_new_link").c_str(), TestEnvironment::temporaryPath("envoy_test/watcher_link").c_str()); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); rc = symlink(TestEnvironment::temporaryPath("envoy_test/watcher_new_target").c_str(), TestEnvironment::temporaryPath("envoy_test/watcher_new_link").c_str()); EXPECT_EQ(0, rc); rename(TestEnvironment::temporaryPath("envoy_test/watcher_new_link").c_str(), TestEnvironment::temporaryPath("envoy_test/watcher_link").c_str()); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); } -TEST(WatcherImplTest, Create) { - Event::DispatcherImpl dispatcher; - Filesystem::WatcherPtr watcher = dispatcher.createFilesystemWatcher(); +TEST_F(WatcherImplTest, Create) { + Filesystem::WatcherPtr watcher = dispatcher_.createFilesystemWatcher(); unlink(TestEnvironment::temporaryPath("envoy_test/watcher_target").c_str()); unlink(TestEnvironment::temporaryPath("envoy_test/watcher_link").c_str()); @@ -75,11 +82,11 @@ TEST(WatcherImplTest, Create) { watcher->addWatch(TestEnvironment::temporaryPath("envoy_test/watcher_link"), Watcher::Events::MovedTo, [&](uint32_t events) -> void { callback.called(events); - dispatcher.exit(); + dispatcher_.exit(); }); { std::ofstream file(TestEnvironment::temporaryPath("envoy_test/other_file")); } - dispatcher.run(Event::Dispatcher::RunType::NonBlock); + dispatcher_.run(Event::Dispatcher::RunType::NonBlock); int rc = symlink(TestEnvironment::temporaryPath("envoy_test/watcher_target").c_str(), TestEnvironment::temporaryPath("envoy_test/watcher_new_link").c_str()); @@ -89,12 +96,11 @@ TEST(WatcherImplTest, Create) { TestEnvironment::temporaryPath("envoy_test/watcher_link").c_str()); EXPECT_EQ(0, rc); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); } -TEST(WatcherImplTest, BadPath) { - Event::DispatcherImpl dispatcher; - Filesystem::WatcherPtr watcher = dispatcher.createFilesystemWatcher(); +TEST_F(WatcherImplTest, BadPath) { + Filesystem::WatcherPtr watcher = dispatcher_.createFilesystemWatcher(); EXPECT_THROW( watcher->addWatch("this_is_not_a_file", Watcher::Events::MovedTo, [&](uint32_t) -> void {}), @@ -105,9 +111,8 @@ TEST(WatcherImplTest, BadPath) { EnvoyException); } -TEST(WatcherImplTest, ParentDirectoryRemoved) { - Event::DispatcherImpl dispatcher; - Filesystem::WatcherPtr watcher = dispatcher.createFilesystemWatcher(); +TEST_F(WatcherImplTest, ParentDirectoryRemoved) { + Filesystem::WatcherPtr watcher = dispatcher_.createFilesystemWatcher(); mkdir(TestEnvironment::temporaryPath("envoy_test_empty").c_str(), S_IRWXU); @@ -121,12 +126,11 @@ TEST(WatcherImplTest, ParentDirectoryRemoved) { int rc = rmdir(TestEnvironment::temporaryPath("envoy_test_empty").c_str()); EXPECT_EQ(0, rc); - dispatcher.run(Event::Dispatcher::RunType::NonBlock); + dispatcher_.run(Event::Dispatcher::RunType::NonBlock); } -TEST(WatcherImplTest, RootDirectoryPath) { - Event::DispatcherImpl dispatcher; - Filesystem::WatcherPtr watcher = dispatcher.createFilesystemWatcher(); +TEST_F(WatcherImplTest, RootDirectoryPath) { + Filesystem::WatcherPtr watcher = dispatcher_.createFilesystemWatcher(); EXPECT_NO_THROW(watcher->addWatch("/", Watcher::Events::MovedTo, [&](uint32_t) -> void {})); } diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index 3294b133c88a..f518d30645af 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -18,7 +18,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:upstream_mocks", - "//test/proto:helloworld_proto", + "//test/proto:helloworld_proto_cc", ], ) @@ -40,7 +40,7 @@ envoy_cc_test( deps = [ "//source/common/buffer:buffer_lib", "//source/common/grpc:codec_lib", - "//test/proto:helloworld_proto", + "//test/proto:helloworld_proto_cc", ], ) @@ -51,7 +51,7 @@ envoy_cc_test( "//source/common/grpc:common_lib", "//source/common/http:headers_lib", "//test/mocks/upstream:upstream_mocks", - "//test/proto:helloworld_proto", + "//test/proto:helloworld_proto_cc", "//test/test_common:utility_lib", ], ) @@ -66,7 +66,8 @@ envoy_cc_test( "//source/common/tracing:http_tracer_lib", "//test/mocks/grpc:grpc_mocks", "//test/mocks/tracing:tracing_mocks", - "//test/proto:helloworld_proto", + "//test/proto:helloworld_proto_cc", + "//test/test_common:test_time_lib", ] + envoy_select_google_grpc(["//source/common/grpc:google_async_client_lib"]), ) @@ -98,7 +99,9 @@ envoy_cc_test_library( "//source/common/http/http2:conn_pool_lib", "//test/integration:integration_lib", "//test/mocks/local_info:local_info_mocks", + "//test/mocks/server:server_mocks", "//test/proto:helloworld_proto", + "//test/test_common:test_time_lib", ], ) diff --git a/test/common/grpc/google_async_client_impl_test.cc b/test/common/grpc/google_async_client_impl_test.cc index 9dc85bca0b0c..8f0e85d77bf0 100644 --- a/test/common/grpc/google_async_client_impl_test.cc +++ b/test/common/grpc/google_async_client_impl_test.cc @@ -7,6 +7,7 @@ #include "test/mocks/grpc/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/proto/helloworld.pb.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -44,7 +45,8 @@ class MockStubFactory : public GoogleStubFactory { class EnvoyGoogleAsyncClientImplTest : public testing::Test { public: EnvoyGoogleAsyncClientImplTest() - : method_descriptor_(helloworld::Greeter::descriptor()->FindMethodByName("SayHello")) { + : dispatcher_(test_time_.timeSource()), + method_descriptor_(helloworld::Greeter::descriptor()->FindMethodByName("SayHello")) { envoy::api::v2::core::GrpcService config; auto* google_grpc = config.mutable_google_grpc(); google_grpc->set_target_uri("fake_address"); @@ -54,6 +56,7 @@ class EnvoyGoogleAsyncClientImplTest : public testing::Test { stats_store_, config); } + DangerousDeprecatedTestTime test_time_; Event::DispatcherImpl dispatcher_; std::unique_ptr tls_; MockStubFactory stub_factory_; diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index c82740d66859..51162fa4f5d5 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -13,11 +13,12 @@ #include "test/integration/fake_upstream.h" #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" -#include "test/mocks/secret/mocks.h" +#include "test/mocks/server/mocks.h" #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/proto/helloworld.pb.h" #include "test/test_common/environment.h" +#include "test/test_common/test_time.h" using testing::_; using testing::Invoke; @@ -202,7 +203,8 @@ class HelloworldRequest : public MockAsyncRequestCallbacksFindMethodByName("SayHello")) {} + : method_descriptor_(helloworld::Greeter::descriptor()->FindMethodByName("SayHello")), + dispatcher_(test_time_.timeSource()) {} virtual void initialize() { if (fake_upstream_ == nullptr) { @@ -393,6 +395,7 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { FakeHttpConnectionPtr fake_connection_; std::vector fake_streams_; const Protobuf::MethodDescriptor* method_descriptor_; + DangerousDeprecatedTestTime test_time_; Event::DispatcherImpl dispatcher_; DispatcherHelper dispatcher_helper_{dispatcher_}; Stats::IsolatedStoreImpl* stats_store_ = new Stats::IsolatedStoreImpl(); @@ -461,7 +464,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { tls_cert->mutable_private_key()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); } - auto cfg = std::make_unique(tls_context, secret_manager_); + auto cfg = std::make_unique(tls_context, factory_context_); mock_cluster_info_->transport_socket_factory_ = std::make_unique( std::move(cfg), context_manager_, *stats_store_); @@ -491,7 +494,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); } - auto cfg = std::make_unique(tls_context, secret_manager_); + auto cfg = std::make_unique(tls_context, factory_context_); static Stats::Scope* upstream_stats_store = new Stats::IsolatedStoreImpl(); return std::make_unique( @@ -499,7 +502,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { } bool use_client_cert_{}; - NiceMock secret_manager_; + testing::NiceMock factory_context_; }; } // namespace diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 13c84e925b46..dac7c002f7e5 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -28,6 +28,7 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/stats:stats_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_time_lib", ], ) @@ -51,6 +52,7 @@ envoy_cc_test( "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) @@ -94,8 +96,6 @@ envoy_cc_test_library( envoy_proto_library( name = "conn_manager_impl_fuzz_proto", srcs = ["conn_manager_impl_fuzz.proto"], - external_deps = ["well_known_protos"], - generate_python = 0, deps = [ "//test/fuzz:common_proto", ], @@ -106,7 +106,7 @@ envoy_cc_fuzz_test( srcs = ["conn_manager_impl_fuzz_test.cc"], corpus = "conn_manager_impl_corpus", deps = [ - ":conn_manager_impl_fuzz_proto", + ":conn_manager_impl_fuzz_proto_cc", "//source/common/common:empty_string", "//source/common/http:conn_manager_lib", "//source/common/http:date_provider_lib", @@ -121,6 +121,7 @@ envoy_cc_fuzz_test( "//test/mocks/ssl:ssl_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_time_lib", "@envoy_api//envoy/config/filter/network/http_connection_manager/v2:http_connection_manager_cc", ], ) @@ -160,6 +161,7 @@ envoy_cc_test( "//test/mocks/ssl:ssl_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_time_lib", ], ) @@ -207,7 +209,6 @@ envoy_cc_test( envoy_proto_library( name = "header_map_impl_fuzz_proto", srcs = ["header_map_impl_fuzz.proto"], - external_deps = ["well_known_protos"], ) envoy_cc_fuzz_test( @@ -215,7 +216,7 @@ envoy_cc_fuzz_test( srcs = ["header_map_impl_fuzz_test.cc"], corpus = "header_map_impl_corpus", deps = [ - ":header_map_impl_fuzz_proto", + ":header_map_impl_fuzz_proto_cc", "//source/common/http:header_map_lib", ], ) @@ -250,7 +251,7 @@ envoy_cc_fuzz_test( srcs = ["utility_fuzz_test.cc"], corpus = "utility_corpus", deps = [ - ":utility_fuzz_proto", + ":utility_fuzz_proto_cc", "//source/common/http:utility_lib", "//test/test_common:utility_lib", ], diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index 7ade4bbefba3..c81ac4dffcfa 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -741,6 +741,31 @@ TEST_F(AsyncClientImplTest, StreamTimeout) { cm_.thread_local_cluster_.cluster_.info_->stats_store_.counter("upstream_rq_504").value()); } +TEST_F(AsyncClientImplTest, StreamTimeoutHeadReply) { + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](StreamDecoder&, + ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + return nullptr; + })); + + MessagePtr message{new RequestMessageImpl()}; + HttpTestUtility::addDefaultHeaders(message->headers(), "HEAD"); + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&message->headers()), true)); + timer_ = new NiceMock(&dispatcher_); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40))); + EXPECT_CALL(stream_encoder_.stream_, resetStream(_)); + + TestHeaderMapImpl expected_timeout{ + {":status", "504"}, {"content-length", "24"}, {"content-type", "text/plain"}}; + EXPECT_CALL(stream_callbacks_, onHeaders_(HeaderMapEqualRef(&expected_timeout), true)); + + AsyncClient::Stream* stream = + client_.start(stream_callbacks_, std::chrono::milliseconds(40), false); + stream->sendHeaders(message->headers(), true); + timer_->callback_(); +} + TEST_F(AsyncClientImplTest, RequestTimeout) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, diff --git a/test/common/http/codec_client_test.cc b/test/common/http/codec_client_test.cc index 5cc31cb0ef81..b2175f45fc45 100644 --- a/test/common/http/codec_client_test.cc +++ b/test/common/http/codec_client_test.cc @@ -18,6 +18,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -59,6 +60,7 @@ class CodecClientTest : public testing::Test { ~CodecClientTest() { EXPECT_EQ(0U, client_->numActiveRequests()); } + DangerousDeprecatedTestTime test_time_; Event::MockDispatcher dispatcher_; Network::MockClientConnection* connection_; Http::MockClientConnection* codec_; @@ -260,7 +262,7 @@ TEST_F(CodecClientTest, WatermarkPassthrough) { class CodecNetworkTest : public testing::TestWithParam { public: CodecNetworkTest() { - dispatcher_.reset(new Event::DispatcherImpl); + dispatcher_.reset(new Event::DispatcherImpl(test_time_.timeSource())); upstream_listener_ = dispatcher_->createListener(socket_, listener_callbacks_, true, false); Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( socket_.localAddress(), source_address_, Network::Test::createRawBufferSocket(), nullptr); @@ -326,6 +328,7 @@ class CodecNetworkTest : public testing::TestWithParam configInfo() const override { return {}; } - SystemTime lastUpdated() const override { - return ProdSystemTimeSource::instance_.currentTime(); - } + SystemTime lastUpdated() const override { return time_source_.systemTime(); } + TimeSource& time_source_; std::shared_ptr route_config_{new NiceMock()}; }; FuzzConfig() - : stats_{{ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(fake_stats_), POOL_GAUGE(fake_stats_), + : route_config_provider_(test_time_.timeSource()), + stats_{{ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(fake_stats_), POOL_GAUGE(fake_stats_), POOL_HISTOGRAM(fake_stats_))}, "", fake_stats_}, @@ -114,6 +117,7 @@ class FuzzConfig : public ConnectionManagerConfig { MockStreamEncoderFilter* encoder_filter_{}; NiceMock filter_factory_; absl::optional idle_timeout_; + DangerousDeprecatedTestTime test_time_; RouteConfigProvider route_config_provider_; std::string server_name_; Stats::IsolatedStoreImpl fake_stats_; @@ -286,10 +290,10 @@ class FuzzStream { const test::common::http::ResponseAction& response_action) { const bool end_stream = response_action.end_stream(); switch (response_action.response_action_selector_case()) { - case test::common::http::ResponseAction::kContinue100Headers: { + case test::common::http::ResponseAction::kContinueHeaders: { if (state == StreamState::PendingHeaders) { auto headers = std::make_unique( - Fuzz::fromHeaders(response_action.continue_100_headers())); + Fuzz::fromHeaders(response_action.continue_headers())); headers->setReferenceKey(Headers::get().Status, "100"); decoder_filter_->callbacks_->encode100ContinueHeaders(std::move(headers)); } diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 9e23e99d74be..90e6e21f45a3 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -38,6 +38,7 @@ #include "test/mocks/tracing/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -62,18 +63,19 @@ namespace Http { class HttpConnectionManagerImplTest : public Test, public ConnectionManagerConfig { public: struct RouteConfigProvider : public Router::RouteConfigProvider { + RouteConfigProvider(TimeSource& time_source) : time_source_(time_source) {} + // Router::RouteConfigProvider Router::ConfigConstSharedPtr config() override { return route_config_; } absl::optional configInfo() const override { return {}; } - SystemTime lastUpdated() const override { - return ProdSystemTimeSource::instance_.currentTime(); - } + SystemTime lastUpdated() const override { return time_source_.systemTime(); } + TimeSource& time_source_; std::shared_ptr route_config_{new NiceMock()}; }; HttpConnectionManagerImplTest() - : access_log_path_("dummy_path"), + : route_config_provider_(test_time_.timeSource()), access_log_path_("dummy_path"), access_logs_{ AccessLog::InstanceSharedPtr{new Extensions::AccessLoggers::File::FileAccessLog( access_log_path_, {}, AccessLog::AccessLogFormatUtils::defaultAccessLogFormatter(), @@ -162,7 +164,8 @@ class HttpConnectionManagerImplTest : public Test, public ConnectionManagerConfi setUpBufferLimits(); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); })); @@ -274,6 +277,8 @@ class HttpConnectionManagerImplTest : public Test, public ConnectionManagerConfi bool proxy100Continue() const override { return proxy_100_continue_; } const Http::Http1Settings& http1Settings() const override { return http1_settings_; } + DangerousDeprecatedTestTime test_time_; + RouteConfigProvider route_config_provider_; NiceMock tracer_; NiceMock runtime_; NiceMock log_manager_; @@ -299,7 +304,6 @@ class HttpConnectionManagerImplTest : public Test, public ConnectionManagerConfi NiceMock local_info_; NiceMock factory_context_; std::unique_ptr ssl_connection_; - RouteConfigProvider route_config_provider_; TracingConnectionManagerConfigPtr tracing_config_; SlowDateProviderImpl date_provider_; MockStream stream_; @@ -359,11 +363,12 @@ TEST_F(HttpConnectionManagerImplTest, HeaderOnlyRequestAndResponse) { // Test not charging stats on the second call. if (data.length() == 4) { - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); } else { - HeaderMapPtr headers{ - new TestHeaderMapImpl{{":authority", "host"}, {":path", "/healthcheck"}}}; + HeaderMapPtr headers{new TestHeaderMapImpl{ + {":authority", "host"}, {":path", "/healthcheck"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); } @@ -417,7 +422,8 @@ TEST_F(HttpConnectionManagerImplTest, 100ContinueResponse) { decoder = &conn_manager_->newStream(encoder); // Test not charging stats on the second call. - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); HeaderMapPtr continue_headers{new TestHeaderMapImpl{{":status", "100"}}}; @@ -522,8 +528,8 @@ TEST_F(HttpConnectionManagerImplTest, InvalidPathWithDualFilter) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{ - new TestHeaderMapImpl{{":authority", "host"}, {":path", "http://api.lyft.com/"}}}; + HeaderMapPtr headers{new TestHeaderMapImpl{ + {":authority", "host"}, {":path", "http://api.lyft.com/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); data.drain(4); })); @@ -1112,7 +1118,8 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNotConfigured) { .WillRepeatedly(Invoke([&](Buffer::Instance& data) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); data.drain(4); @@ -1172,7 +1179,8 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutRouteOverride) { EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(30))); decoder->decodeHeaders(std::move(headers), false); @@ -1200,7 +1208,8 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutRouteZeroOverride) { EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; EXPECT_CALL(*idle_timer, disableTimer()); decoder->decodeHeaders(std::move(headers), false); @@ -1224,7 +1233,8 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; EXPECT_CALL(*idle_timer, enableTimer(_)); decoder->decodeHeaders(std::move(headers), false); @@ -1263,7 +1273,8 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNormalTermination) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; EXPECT_CALL(*idle_timer, enableTimer(_)); decoder->decodeHeaders(std::move(headers), false); @@ -1291,7 +1302,8 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; EXPECT_CALL(*idle_timer, enableTimer(_)); decoder->decodeHeaders(std::move(headers), false); @@ -1343,7 +1355,8 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterUpstreamHeaders) StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; EXPECT_CALL(*idle_timer, enableTimer(_)); decoder->decodeHeaders(std::move(headers), false); @@ -1391,7 +1404,8 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterBidiData) { StreamDecoder* decoder; EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; EXPECT_CALL(*idle_timer, enableTimer(_)); decoder->decodeHeaders(std::move(headers), false); @@ -1898,7 +1912,8 @@ TEST_F(HttpConnectionManagerImplTest, DrainClose) { NiceMock encoder; EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { decoder = &conn_manager_->newStream(encoder); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); })); @@ -1931,7 +1946,8 @@ TEST_F(HttpConnectionManagerImplTest, ResponseBeforeRequestComplete) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); })); @@ -1974,7 +1990,8 @@ TEST_F(HttpConnectionManagerImplTest, ResponseStartBeforeRequestComplete) { NiceMock encoder; EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { decoder = &conn_manager_->newStream(encoder); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); })); @@ -2077,7 +2094,8 @@ TEST_F(HttpConnectionManagerImplTest, IdleTimeout) { StreamDecoder* decoder = nullptr; EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { decoder = &conn_manager_->newStream(encoder); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); Buffer::OwnedImpl fake_data("hello"); @@ -2117,7 +2135,8 @@ TEST_F(HttpConnectionManagerImplTest, IntermediateBufferingEarlyResponse) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); Buffer::OwnedImpl fake_data("hello"); @@ -2161,7 +2180,8 @@ TEST_F(HttpConnectionManagerImplTest, DoubleBuffering) { Buffer::OwnedImpl fake_data_copy("hello"); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); decoder->decodeData(fake_data, true); })); @@ -2200,7 +2220,8 @@ TEST_F(HttpConnectionManagerImplTest, ZeroByteDataFiltering) { StreamDecoder* decoder = nullptr; EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); })); @@ -2236,7 +2257,8 @@ TEST_F(HttpConnectionManagerImplTest, FilterAddTrailersInTrailersCallback) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); Buffer::OwnedImpl fake_data("hello"); @@ -2317,7 +2339,8 @@ TEST_F(HttpConnectionManagerImplTest, FilterAddTrailersInDataCallbackNoTrailers) EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); Buffer::OwnedImpl fake_data("hello"); @@ -2403,7 +2426,8 @@ TEST_F(HttpConnectionManagerImplTest, FilterAddBodyInTrailersCallback) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); Buffer::OwnedImpl fake_data("hello"); @@ -2479,7 +2503,8 @@ TEST_F(HttpConnectionManagerImplTest, FilterAddBodyDuringDecodeData) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); Buffer::OwnedImpl data1("hello"); @@ -2542,7 +2567,8 @@ TEST_F(HttpConnectionManagerImplTest, FilterAddBodyInline) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); })); @@ -2587,7 +2613,8 @@ TEST_F(HttpConnectionManagerImplTest, FilterClearRouteCache) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); })); @@ -2783,7 +2810,8 @@ TEST_F(HttpConnectionManagerImplTest, UnderlyingConnectionWatermarksPassedOnWith setupFilterChain(2, 2); EXPECT_CALL(filter_callbacks_.connection_, aboveHighWatermark()).Times(0); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); })); EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true)) @@ -2843,7 +2871,8 @@ TEST_F(HttpConnectionManagerImplTest, UnderlyingConnectionWatermarksUnwoundWithL setupFilterChain(2, 2); EXPECT_CALL(filter_callbacks_.connection_, aboveHighWatermark()).Times(0); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); })); EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true)) @@ -2956,7 +2985,8 @@ TEST_F(HttpConnectionManagerImplTest, HitRequestBufferLimitsIntermediateFilter) EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); Buffer::OwnedImpl fake_data("hello"); @@ -3049,13 +3079,46 @@ TEST_F(HttpConnectionManagerImplTest, HitResponseBufferLimitsAfterHeaders) { EXPECT_EQ(1U, stats_.named_.rs_too_large_.value()); } +TEST_F(HttpConnectionManagerImplTest, FilterHeadReply) { + InSequence s; + setup(false, ""); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { + StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "HEAD"}}}; + decoder->decodeHeaders(std::move(headers), true); + data.drain(4); + })); + + setupFilterChain(1, 1); + + EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true)) + .WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus { + decoder_filters_[0]->callbacks_->sendLocalReply(Code::BadRequest, "Bad request", nullptr); + return FilterHeadersStatus::Continue; + })); + + EXPECT_CALL(*encoder_filters_[0], encodeHeaders(_, true)) + .WillOnce(Invoke([&](HeaderMap& headers, bool) -> FilterHeadersStatus { + EXPECT_STREQ("11", headers.ContentLength()->value().c_str()); + return FilterHeadersStatus::Continue; + })); + EXPECT_CALL(response_encoder_, encodeHeaders(_, true)); + expectOnDestroy(); + // Kick off the incoming data. + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + TEST_F(HttpConnectionManagerImplTest, FilterAddBodyContinuation) { InSequence s; setup(false, ""); EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), true); })); @@ -3102,7 +3165,8 @@ TEST_F(HttpConnectionManagerImplTest, MultipleFilters) { EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); - HeaderMapPtr headers{new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}}}; + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; decoder->decodeHeaders(std::move(headers), false); Buffer::OwnedImpl fake_data("hello"); diff --git a/test/common/http/header_map_impl_corpus/clusterfuzz-testcase-minimized-header_map_impl_fuzz_test-5689833624698880 b/test/common/http/header_map_impl_corpus/clusterfuzz-testcase-minimized-header_map_impl_fuzz_test-5689833624698880 new file mode 100644 index 000000000000..75d99a6a2857 --- /dev/null +++ b/test/common/http/header_map_impl_corpus/clusterfuzz-testcase-minimized-header_map_impl_fuzz_test-5689833624698880 @@ -0,0 +1 @@ +actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { set_reference { } } actions { } actions { get_and_mutate { append: "" } } actions { } actions { get_and_mutate { set_integer: 0 } } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } diff --git a/test/common/http/header_map_impl_test.cc b/test/common/http/header_map_impl_test.cc index 7d8bb9413ece..dc55e0d2e7ae 100644 --- a/test/common/http/header_map_impl_test.cc +++ b/test/common/http/header_map_impl_test.cc @@ -110,7 +110,7 @@ TEST(HeaderStringTest, All) { HeaderString string(static_string); EXPECT_EQ(HeaderString::Type::Reference, string.type()); string.append("a", 1); - EXPECT_STREQ("a", string.c_str()); + EXPECT_STREQ("HELLOa", string.c_str()); } // Copy inline @@ -425,11 +425,37 @@ TEST(HeaderMapImplTest, SetRemovesAllValues) { } TEST(HeaderMapImplTest, DoubleInlineAdd) { - HeaderMapImpl headers; - headers.addReferenceKey(Headers::get().ContentLength, 5); - EXPECT_DEBUG_DEATH(headers.addReferenceKey(Headers::get().ContentLength, 6), ""); - EXPECT_STREQ("5", headers.ContentLength()->value().c_str()); - EXPECT_EQ(1UL, headers.size()); + { + HeaderMapImpl headers; + const std::string foo("foo"); + const std::string bar("bar"); + headers.addReference(Headers::get().ContentLength, foo); + headers.addReference(Headers::get().ContentLength, bar); + EXPECT_STREQ("foo,bar", headers.ContentLength()->value().c_str()); + EXPECT_EQ(1UL, headers.size()); + } + { + HeaderMapImpl headers; + headers.addReferenceKey(Headers::get().ContentLength, "foo"); + headers.addReferenceKey(Headers::get().ContentLength, "bar"); + EXPECT_STREQ("foo,bar", headers.ContentLength()->value().c_str()); + EXPECT_EQ(1UL, headers.size()); + } + { + HeaderMapImpl headers; + headers.addReferenceKey(Headers::get().ContentLength, 5); + headers.addReferenceKey(Headers::get().ContentLength, 6); + EXPECT_STREQ("5,6", headers.ContentLength()->value().c_str()); + EXPECT_EQ(1UL, headers.size()); + } + { + HeaderMapImpl headers; + const std::string foo("foo"); + headers.addReference(Headers::get().ContentLength, foo); + headers.addReferenceKey(Headers::get().ContentLength, 6); + EXPECT_STREQ("foo,6", headers.ContentLength()->value().c_str()); + EXPECT_EQ(1UL, headers.size()); + } } TEST(HeaderMapImplTest, DoubleInlineSet) { @@ -666,6 +692,16 @@ TEST(HeaderMapImplTest, TestAppendHeader) { HeaderMapImpl::appendToHeader(value3, ""); EXPECT_EQ(value3, "empty"); } + // Regression test for appending to an empty string with a short string, then + // setting integer. + { + const std::string empty; + HeaderString value4(empty); + HeaderMapImpl::appendToHeader(value4, " "); + value4.setInteger(0); + EXPECT_STREQ("0", value4.c_str()); + EXPECT_EQ(1U, value4.size()); + } } TEST(HeaderMapImplTest, PseudoHeaderOrder) { diff --git a/test/common/http/http1/BUILD b/test/common/http/http1/BUILD index e685bff6f328..66da7a23b6e8 100644 --- a/test/common/http/http1/BUILD +++ b/test/common/http/http1/BUILD @@ -45,6 +45,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index e2706d83e4b2..cf7ffb2eb4f2 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -17,6 +17,7 @@ #include "test/mocks/runtime/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -79,7 +80,7 @@ class ConnPoolImplForTest : public ConnPoolImpl { test_client.codec_ = new NiceMock(); test_client.connect_timer_ = new NiceMock(&mock_dispatcher_); std::shared_ptr cluster{new NiceMock()}; - test_client.client_dispatcher_.reset(new Event::DispatcherImpl); + test_client.client_dispatcher_.reset(new Event::DispatcherImpl(test_time_.timeSource())); Network::ClientConnectionPtr connection{test_client.connection_}; test_client.codec_client_ = new CodecClientForTest( std::move(connection), test_client.codec_, @@ -110,6 +111,7 @@ class ConnPoolImplForTest : public ConnPoolImpl { EXPECT_FALSE(upstream_ready_enabled_); } + DangerousDeprecatedTestTime test_time_; Event::MockDispatcher& mock_dispatcher_; NiceMock* mock_upstream_ready_timer_; std::vector test_clients_; diff --git a/test/common/http/http2/BUILD b/test/common/http/http2/BUILD index b9f7908934b7..80a760348764 100644 --- a/test/common/http/http2/BUILD +++ b/test/common/http/http2/BUILD @@ -13,8 +13,6 @@ envoy_package() envoy_proto_library( name = "codec_impl_fuzz_proto", srcs = ["codec_impl_fuzz.proto"], - external_deps = ["well_known_protos"], - generate_python = 0, deps = ["//test/fuzz:common_proto"], ) @@ -23,7 +21,7 @@ envoy_cc_fuzz_test( srcs = ["codec_impl_fuzz_test.cc"], corpus = "codec_impl_corpus", deps = [ - ":codec_impl_fuzz_proto", + ":codec_impl_fuzz_proto_cc", "//source/common/http:header_map_lib", "//source/common/http/http2:codec_lib", "//test/fuzz:utility_lib", @@ -65,5 +63,6 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_time_lib", ], ) diff --git a/test/common/http/http2/codec_impl_corpus/100-continue b/test/common/http/http2/codec_impl_corpus/100-continue index 28a1ec9e46ff..48b2c8a3d91f 100644 --- a/test/common/http/http2/codec_impl_corpus/100-continue +++ b/test/common/http/http2/codec_impl_corpus/100-continue @@ -29,7 +29,7 @@ actions { stream_action { stream_id: 0 response { - continue_100_headers { + continue_headers { headers { key: ":status" value: "100" diff --git a/test/common/http/http2/codec_impl_corpus/clusterfuzz-testcase-minimized-codec_impl_fuzz_test-5728207897624576 b/test/common/http/http2/codec_impl_corpus/clusterfuzz-testcase-minimized-codec_impl_fuzz_test-5728207897624576 new file mode 100644 index 000000000000..30ada91a1a8a --- /dev/null +++ b/test/common/http/http2/codec_impl_corpus/clusterfuzz-testcase-minimized-codec_impl_fuzz_test-5728207897624576 @@ -0,0 +1 @@ +actions { new_stream { request_headers { headers { key: " " value: " " } headers { value: "�" } headers { key: ":method" value: "GET" } } } } actions { mutate { buffer: 2 offset: 2 value: 2 } } actions { quiesce_drain { } } diff --git a/test/common/http/http2/codec_impl_corpus/example b/test/common/http/http2/codec_impl_corpus/example index 8ea4e2eec664..65a0fb0d12f0 100644 --- a/test/common/http/http2/codec_impl_corpus/example +++ b/test/common/http/http2/codec_impl_corpus/example @@ -221,7 +221,7 @@ actions { stream_action { stream_id: 3 request { - reset: 0 + reset_stream: 0 } } } @@ -252,7 +252,7 @@ actions { stream_action { stream_id: 4 response { - reset: 0 + reset_stream: 0 } } } diff --git a/test/common/http/http2/codec_impl_fuzz.proto b/test/common/http/http2/codec_impl_fuzz.proto index 62e85b34c5b1..9b7a3a9a8387 100644 --- a/test/common/http/http2/codec_impl_fuzz.proto +++ b/test/common/http/http2/codec_impl_fuzz.proto @@ -15,11 +15,11 @@ message NewStream { message DirectionalAction { oneof directional_action_selector { - test.fuzz.Headers continue_100_headers = 1; + test.fuzz.Headers continue_headers = 1; test.fuzz.Headers headers = 2; uint32 data = 3; test.fuzz.Headers trailers = 4; - uint32 reset = 5; + uint32 reset_stream = 5; bool read_disable = 6; } bool end_stream = 7; diff --git a/test/common/http/http2/codec_impl_fuzz_test.cc b/test/common/http/http2/codec_impl_fuzz_test.cc index 0e0b6f9952dc..8460aad55689 100644 --- a/test/common/http/http2/codec_impl_fuzz_test.cc +++ b/test/common/http/http2/codec_impl_fuzz_test.cc @@ -99,10 +99,9 @@ class Stream : public LinkedObject { const test::common::http::http2::DirectionalAction& directional_action) { const bool end_stream = directional_action.end_stream(); switch (directional_action.directional_action_selector_case()) { - case test::common::http::http2::DirectionalAction::kContinue100Headers: { + case test::common::http::http2::DirectionalAction::kContinueHeaders: { if (state == StreamState::PendingHeaders) { - Http::TestHeaderMapImpl headers = - Fuzz::fromHeaders(directional_action.continue_100_headers()); + Http::TestHeaderMapImpl headers = Fuzz::fromHeaders(directional_action.continue_headers()); headers.setReferenceKey(Headers::get().Status, "100"); encoder.encode100ContinueHeaders(headers); } @@ -130,10 +129,10 @@ class Stream : public LinkedObject { } break; } - case test::common::http::http2::DirectionalAction::kReset: { + case test::common::http::http2::DirectionalAction::kResetStream: { if (state != StreamState::Closed) { encoder.getStream().resetStream( - static_cast(directional_action.reset())); + static_cast(directional_action.reset_stream())); request_state_ = response_state_ = StreamState::Closed; } break; @@ -273,9 +272,14 @@ DEFINE_PROTO_FUZZER(const test::common::http::http2::CodecImplFuzzTestCase& inpu // the response encoder and can complete Stream initialization. std::list pending_streams; std::list streams; + // For new streams when we aren't expecting one (e.g. as a result of a mutation). + NiceMock orphan_request_decoder; ON_CALL(server_callbacks, newStream(_)) .WillByDefault(Invoke([&](StreamEncoder& encoder) -> StreamDecoder& { + if (pending_streams.empty()) { + return orphan_request_decoder; + } auto stream_ptr = pending_streams.front()->removeFromList(pending_streams); Stream* const stream = stream_ptr.get(); stream_ptr->moveIntoListBack(std::move(stream_ptr), streams); diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index 67e37b7bb315..40e98992c2d3 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -15,6 +15,7 @@ #include "test/mocks/runtime/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -77,7 +78,7 @@ class Http2ConnPoolImplTest : public testing::Test { test_client.connection_ = new NiceMock(); test_client.codec_ = new NiceMock(); test_client.connect_timer_ = new NiceMock(&dispatcher_); - test_client.client_dispatcher_.reset(new Event::DispatcherImpl); + test_client.client_dispatcher_.reset(new Event::DispatcherImpl(test_time_.timeSource())); std::shared_ptr cluster{new NiceMock()}; Network::ClientConnectionPtr connection{test_client.connection_}; @@ -101,6 +102,7 @@ class Http2ConnPoolImplTest : public testing::Test { MOCK_METHOD0(onClientDestroy, void()); + DangerousDeprecatedTestTime test_time_; NiceMock dispatcher_; std::shared_ptr cluster_{new NiceMock()}; Upstream::HostSharedPtr host_{Upstream::makeTestHost(cluster_, "tcp://127.0.0.1:80")}; diff --git a/test/common/http/utility_test.cc b/test/common/http/utility_test.cc index 2a3473d80168..93476a8f8a33 100644 --- a/test/common/http/utility_test.cc +++ b/test/common/http/utility_test.cc @@ -333,7 +333,7 @@ TEST(HttpUtility, SendLocalReply) { EXPECT_CALL(callbacks, encodeHeaders_(_, false)); EXPECT_CALL(callbacks, encodeData(_, true)); - Utility::sendLocalReply(false, callbacks, is_reset, Http::Code::PayloadTooLarge, "large"); + Utility::sendLocalReply(false, callbacks, is_reset, Http::Code::PayloadTooLarge, "large", false); } TEST(HttpUtility, SendLocalGrpcReply) { @@ -348,7 +348,7 @@ TEST(HttpUtility, SendLocalGrpcReply) { EXPECT_NE(headers.GrpcMessage(), nullptr); EXPECT_STREQ(headers.GrpcMessage()->value().c_str(), "large"); })); - Utility::sendLocalReply(true, callbacks, is_reset, Http::Code::PayloadTooLarge, "large"); + Utility::sendLocalReply(true, callbacks, is_reset, Http::Code::PayloadTooLarge, "large", false); } TEST(HttpUtility, SendLocalReplyDestroyedEarly) { @@ -359,7 +359,18 @@ TEST(HttpUtility, SendLocalReplyDestroyedEarly) { is_reset = true; })); EXPECT_CALL(callbacks, encodeData(_, true)).Times(0); - Utility::sendLocalReply(false, callbacks, is_reset, Http::Code::PayloadTooLarge, "large"); + Utility::sendLocalReply(false, callbacks, is_reset, Http::Code::PayloadTooLarge, "large", false); +} + +TEST(HttpUtility, SendLocalReplyHeadRequest) { + MockStreamDecoderFilterCallbacks callbacks; + bool is_reset = false; + EXPECT_CALL(callbacks, encodeHeaders_(_, true)) + .WillOnce(Invoke([&](const HeaderMap& headers, bool) -> void { + EXPECT_STREQ(headers.ContentLength()->value().c_str(), + fmt::format("{}", strlen("large")).c_str()); + })); + Utility::sendLocalReply(false, callbacks, is_reset, Http::Code::PayloadTooLarge, "large", true); } TEST(HttpUtility, TestExtractHostPathFromUri) { diff --git a/test/common/network/BUILD b/test/common/network/BUILD index fe0fdd4198c8..ba4eadec5208 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -51,6 +51,7 @@ envoy_cc_test( "//test/mocks/stats:stats_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:test_time_lib", ], ) @@ -72,6 +73,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) @@ -139,6 +141,7 @@ envoy_cc_test( "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index fd4f98ba85b8..8ae698340f0d 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -20,6 +20,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -76,7 +77,8 @@ INSTANTIATE_TEST_CASE_P(IpVersions, ConnectionImplDeathTest, TestUtility::ipTestParamsToString); TEST_P(ConnectionImplDeathTest, BadFd) { - Event::DispatcherImpl dispatcher; + DangerousDeprecatedTestTime test_time; + Event::DispatcherImpl dispatcher(test_time.timeSource()); EXPECT_DEATH_LOG_TO_STDERR( ConnectionImpl(dispatcher, std::make_unique(-1, nullptr, nullptr), Network::Test::createRawBufferSocket(), false), @@ -87,7 +89,7 @@ class ConnectionImplTest : public testing::TestWithParam { public: void setUpBasicConnection() { if (dispatcher_.get() == nullptr) { - dispatcher_.reset(new Event::DispatcherImpl); + dispatcher_.reset(new Event::DispatcherImpl(test_time_.timeSource())); } listener_ = dispatcher_->createListener(socket_, listener_callbacks_, true, false); @@ -149,7 +151,8 @@ class ConnectionImplTest : public testing::TestWithParam { ASSERT(dispatcher_.get() == nullptr); MockBufferFactory* factory = new StrictMock; - dispatcher_.reset(new Event::DispatcherImpl(Buffer::WatermarkFactoryPtr{factory})); + dispatcher_.reset( + new Event::DispatcherImpl(test_time_.timeSource(), Buffer::WatermarkFactoryPtr{factory})); // The first call to create a client session will get a MockBuffer. // Other calls for server sessions will by default get a normal OwnedImpl. EXPECT_CALL(*factory, create_(_, _)) @@ -166,6 +169,7 @@ class ConnectionImplTest : public testing::TestWithParam { } protected: + DangerousDeprecatedTestTime test_time_; Event::DispatcherPtr dispatcher_; Stats::IsolatedStoreImpl stats_store_; Network::TcpListenSocket socket_{Network::Test::getAnyAddress(GetParam()), nullptr, true}; @@ -229,7 +233,7 @@ TEST_P(ConnectionImplTest, CloseDuringConnectCallback) { } TEST_P(ConnectionImplTest, ImmediateConnectError) { - dispatcher_.reset(new Event::DispatcherImpl); + dispatcher_.reset(new Event::DispatcherImpl(test_time_.timeSource())); // Using a broadcast/multicast address as the connection destiantion address causes an // immediate error return from connect(). @@ -801,7 +805,7 @@ TEST_P(ConnectionImplTest, BindFailureTest) { source_address_ = Network::Address::InstanceConstSharedPtr{ new Network::Address::Ipv6Instance(address_string, 0)}; } - dispatcher_.reset(new Event::DispatcherImpl); + dispatcher_.reset(new Event::DispatcherImpl(test_time_.timeSource())); listener_ = dispatcher_->createListener(socket_, listener_callbacks_, true, false); client_connection_ = dispatcher_->createClientConnection( @@ -1195,7 +1199,7 @@ class ReadBufferLimitTest : public ConnectionImplTest { public: void readBufferLimitTest(uint32_t read_buffer_limit, uint32_t expected_chunk_size) { const uint32_t buffer_size = 256 * 1024; - dispatcher_.reset(new Event::DispatcherImpl); + dispatcher_.reset(new Event::DispatcherImpl(test_time_.timeSource())); listener_ = dispatcher_->createListener(socket_, listener_callbacks_, true, false); client_connection_ = dispatcher_->createClientConnection( @@ -1264,13 +1268,17 @@ TEST_P(ReadBufferLimitTest, SomeLimit) { readBufferLimitTest(read_buffer_limit, read_buffer_limit - 1 + 16384); } -class TcpClientConnectionImplTest : public testing::TestWithParam {}; +class TcpClientConnectionImplTest : public testing::TestWithParam { +protected: + TcpClientConnectionImplTest() : dispatcher_(test_time_.timeSource()) {} + DangerousDeprecatedTestTime test_time_; + Event::DispatcherImpl dispatcher_; +}; INSTANTIATE_TEST_CASE_P(IpVersions, TcpClientConnectionImplTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); TEST_P(TcpClientConnectionImplTest, BadConnectNotConnRefused) { - Event::DispatcherImpl dispatcher; Address::InstanceConstSharedPtr address; if (GetParam() == Network::Address::IpVersion::v4) { // Connecting to 255.255.255.255 will cause a perm error and not ECONNREFUSED which is a @@ -1281,25 +1289,24 @@ TEST_P(TcpClientConnectionImplTest, BadConnectNotConnRefused) { address = Utility::resolveUrl("tcp://[ff00::]:1"); } ClientConnectionPtr connection = - dispatcher.createClientConnection(address, Network::Address::InstanceConstSharedPtr(), - Network::Test::createRawBufferSocket(), nullptr); + dispatcher_.createClientConnection(address, Network::Address::InstanceConstSharedPtr(), + Network::Test::createRawBufferSocket(), nullptr); connection->connect(); connection->noDelay(true); connection->close(ConnectionCloseType::NoFlush); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); } TEST_P(TcpClientConnectionImplTest, BadConnectConnRefused) { - Event::DispatcherImpl dispatcher; // Connecting to an invalid port on localhost will cause ECONNREFUSED which is a different code // path from other errors. Test this also. - ClientConnectionPtr connection = dispatcher.createClientConnection( + ClientConnectionPtr connection = dispatcher_.createClientConnection( Utility::resolveUrl( fmt::format("tcp://{}:1", Network::Test::getLoopbackAddressUrlString(GetParam()))), Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); connection->connect(); connection->noDelay(true); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); } } // namespace Network diff --git a/test/common/network/dns_impl_test.cc b/test/common/network/dns_impl_test.cc index b718ed21a800..136c686a6348 100644 --- a/test/common/network/dns_impl_test.cc +++ b/test/common/network/dns_impl_test.cc @@ -25,6 +25,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "ares.h" @@ -319,15 +320,21 @@ class DnsResolverImplPeer { DnsResolverImpl* resolver_; }; -TEST(DnsImplConstructor, SupportsCustomResolvers) { - Event::DispatcherImpl dispatcher; +class DnsImplConstructor : public testing::Test { +protected: + DnsImplConstructor() : dispatcher_(test_time_.timeSource()) {} + DangerousDeprecatedTestTime test_time_; + Event::DispatcherImpl dispatcher_; +}; + +TEST_F(DnsImplConstructor, SupportsCustomResolvers) { char addr4str[INET_ADDRSTRLEN]; // we pick a port that isn't 53 as the default resolve.conf might be // set to point to localhost. auto addr4 = Network::Utility::parseInternetAddressAndPort("127.0.0.1:54"); char addr6str[INET6_ADDRSTRLEN]; auto addr6 = Network::Utility::parseInternetAddressAndPort("[::1]:54"); - auto resolver = dispatcher.createDnsResolver({addr4, addr6}); + auto resolver = dispatcher_.createDnsResolver({addr4, addr6}); auto peer = std::unique_ptr{ new DnsResolverImplPeer(dynamic_cast(resolver.get()))}; ares_addr_port_node* resolvers; @@ -369,11 +376,10 @@ class CustomInstance : public Address::Instance { Address::Ipv4Instance instance_; }; -TEST(DnsImplConstructor, SupportCustomAddressInstances) { - Event::DispatcherImpl dispatcher; +TEST_F(DnsImplConstructor, SupportCustomAddressInstances) { auto test_instance(std::make_shared("127.0.0.1", 45)); EXPECT_EQ(test_instance->asString(), "127.0.0.1:borked_port_45"); - auto resolver = dispatcher.createDnsResolver({test_instance}); + auto resolver = dispatcher_.createDnsResolver({test_instance}); auto peer = std::unique_ptr{ new DnsResolverImplPeer(dynamic_cast(resolver.get()))}; ares_addr_port_node* resolvers; @@ -386,17 +392,18 @@ TEST(DnsImplConstructor, SupportCustomAddressInstances) { ares_free_data(resolvers); } -TEST(DnsImplConstructor, BadCustomResolvers) { - Event::DispatcherImpl dispatcher; +TEST_F(DnsImplConstructor, BadCustomResolvers) { envoy::api::v2::core::Address pipe_address; pipe_address.mutable_pipe()->set_path("foo"); auto pipe_instance = Network::Utility::protobufAddressToAddress(pipe_address); - EXPECT_THROW_WITH_MESSAGE(dispatcher.createDnsResolver({pipe_instance}), EnvoyException, + EXPECT_THROW_WITH_MESSAGE(dispatcher_.createDnsResolver({pipe_instance}), EnvoyException, "DNS resolver 'foo' is not an IP address"); } class DnsImplTest : public testing::TestWithParam { public: + DnsImplTest() : dispatcher_(test_time_.timeSource()) {} + void SetUp() override { resolver_ = dispatcher_.createDnsResolver({}); @@ -427,6 +434,7 @@ class DnsImplTest : public testing::TestWithParam { Network::TcpListenSocketPtr socket_; Stats::IsolatedStoreImpl stats_store_; std::unique_ptr listener_; + DangerousDeprecatedTestTime test_time_; Event::DispatcherImpl dispatcher_; DnsResolverSharedPtr resolver_; }; diff --git a/test/common/network/listener_impl_test.cc b/test/common/network/listener_impl_test.cc index dcf6c7bb8e4e..b674b2a02ce9 100644 --- a/test/common/network/listener_impl_test.cc +++ b/test/common/network/listener_impl_test.cc @@ -6,6 +6,7 @@ #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -22,7 +23,8 @@ static void errorCallbackTest(Address::IpVersion version) { // Force the error callback to fire by closing the socket under the listener. We run this entire // test in the forked process to avoid confusion when the fork happens. Stats::IsolatedStoreImpl stats_store; - Event::DispatcherImpl dispatcher; + DangerousDeprecatedTestTime test_time; + Event::DispatcherImpl dispatcher(test_time.timeSource()); Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(version), nullptr, true); @@ -76,10 +78,13 @@ class ListenerImplTest : public testing::TestWithParam { ListenerImplTest() : version_(GetParam()), alt_address_(Network::Test::findOrCheckFreePort( - Network::Test::getCanonicalLoopbackAddress(version_), Address::SocketType::Stream)) {} + Network::Test::getCanonicalLoopbackAddress(version_), Address::SocketType::Stream)), + dispatcher_(test_time_.timeSource()) {} const Address::IpVersion version_; const Address::InstanceConstSharedPtr alt_address_; + DangerousDeprecatedTestTime test_time_; + Event::DispatcherImpl dispatcher_; }; INSTANTIATE_TEST_CASE_P(IpVersions, ListenerImplTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), @@ -87,7 +92,6 @@ INSTANTIATE_TEST_CASE_P(IpVersions, ListenerImplTest, // Test that socket options are set after the listener is setup. TEST_P(ListenerImplTest, SetListeningSocketOptionsSuccess) { - Event::DispatcherImpl dispatcher; Network::MockListenerCallbacks listener_callbacks; Network::MockConnectionHandler connection_handler; @@ -97,12 +101,11 @@ TEST_P(ListenerImplTest, SetListeningSocketOptionsSuccess) { socket.addOption(option); EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_LISTENING)) .WillOnce(Return(true)); - TestListenerImpl listener(dispatcher, socket, listener_callbacks, true, false); + TestListenerImpl listener(dispatcher_, socket, listener_callbacks, true, false); } // Test that an exception is thrown if there is an error setting socket options. TEST_P(ListenerImplTest, SetListeningSocketOptionsError) { - Event::DispatcherImpl dispatcher; Network::MockListenerCallbacks listener_callbacks; Network::MockConnectionHandler connection_handler; @@ -112,7 +115,7 @@ TEST_P(ListenerImplTest, SetListeningSocketOptionsError) { socket.addOption(option); EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_LISTENING)) .WillOnce(Return(false)); - EXPECT_THROW_WITH_MESSAGE(TestListenerImpl(dispatcher, socket, listener_callbacks, true, false), + EXPECT_THROW_WITH_MESSAGE(TestListenerImpl(dispatcher_, socket, listener_callbacks, true, false), CreateListenerException, fmt::format("cannot set post-listen socket option on socket: {}", socket.localAddress()->asString())); @@ -120,18 +123,17 @@ TEST_P(ListenerImplTest, SetListeningSocketOptionsError) { TEST_P(ListenerImplTest, UseActualDst) { Stats::IsolatedStoreImpl stats_store; - Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(version_), nullptr, true); Network::TcpListenSocket socketDst(alt_address_, nullptr, false); Network::MockListenerCallbacks listener_callbacks1; Network::MockConnectionHandler connection_handler; // Do not redirect since use_original_dst is false. - Network::TestListenerImpl listener(dispatcher, socket, listener_callbacks1, true, true); + Network::TestListenerImpl listener(dispatcher_, socket, listener_callbacks1, true, true); Network::MockListenerCallbacks listener_callbacks2; - Network::TestListenerImpl listenerDst(dispatcher, socketDst, listener_callbacks2, false, false); + Network::TestListenerImpl listenerDst(dispatcher_, socketDst, listener_callbacks2, false, false); - Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( + Network::ClientConnectionPtr client_connection = dispatcher_.createClientConnection( socket.localAddress(), Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); client_connection->connect(); @@ -141,7 +143,7 @@ TEST_P(ListenerImplTest, UseActualDst) { EXPECT_CALL(listener_callbacks2, onAccept_(_, _)).Times(0); EXPECT_CALL(listener_callbacks1, onAccept_(_, _)) .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket, bool) -> void { - Network::ConnectionPtr new_connection = dispatcher.createServerConnection( + Network::ConnectionPtr new_connection = dispatcher_.createServerConnection( std::move(socket), Network::Test::createRawBufferSocket()); listener_callbacks1.onNewConnection(std::move(new_connection)); })); @@ -150,24 +152,23 @@ TEST_P(ListenerImplTest, UseActualDst) { EXPECT_EQ(*conn->localAddress(), *socket.localAddress()); client_connection->close(ConnectionCloseType::NoFlush); conn->close(ConnectionCloseType::NoFlush); - dispatcher.exit(); + dispatcher_.exit(); })); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); } TEST_P(ListenerImplTest, WildcardListenerUseActualDst) { Stats::IsolatedStoreImpl stats_store; - Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getAnyAddress(version_), nullptr, true); Network::MockListenerCallbacks listener_callbacks; Network::MockConnectionHandler connection_handler; // Do not redirect since use_original_dst is false. - Network::TestListenerImpl listener(dispatcher, socket, listener_callbacks, true, true); + Network::TestListenerImpl listener(dispatcher_, socket, listener_callbacks, true, true); auto local_dst_address = Network::Utility::getAddressWithPort( *Network::Test::getCanonicalLoopbackAddress(version_), socket.localAddress()->ip()->port()); - Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( + Network::ClientConnectionPtr client_connection = dispatcher_.createClientConnection( local_dst_address, Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); client_connection->connect(); @@ -176,7 +177,7 @@ TEST_P(ListenerImplTest, WildcardListenerUseActualDst) { EXPECT_CALL(listener_callbacks, onAccept_(_, _)) .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket, bool) -> void { - Network::ConnectionPtr new_connection = dispatcher.createServerConnection( + Network::ConnectionPtr new_connection = dispatcher_.createServerConnection( std::move(socket), Network::Test::createRawBufferSocket()); listener_callbacks.onNewConnection(std::move(new_connection)); })); @@ -185,10 +186,10 @@ TEST_P(ListenerImplTest, WildcardListenerUseActualDst) { EXPECT_EQ(*conn->localAddress(), *local_dst_address); client_connection->close(ConnectionCloseType::NoFlush); conn->close(ConnectionCloseType::NoFlush); - dispatcher.exit(); + dispatcher_.exit(); })); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); } // Test for the correct behavior when a listener is configured with an ANY address that allows @@ -197,7 +198,6 @@ TEST_P(ListenerImplTest, WildcardListenerUseActualDst) { // is an IPv4 connection. TEST_P(ListenerImplTest, WildcardListenerIpv4Compat) { Stats::IsolatedStoreImpl stats_store; - Event::DispatcherImpl dispatcher; auto option = std::make_unique(); auto options = std::make_shared>(); EXPECT_CALL(*option, setOption(_, envoy::api::v2::core::SocketOption::STATE_PREBIND)) @@ -211,13 +211,13 @@ TEST_P(ListenerImplTest, WildcardListenerIpv4Compat) { ASSERT_TRUE(socket.localAddress()->ip()->isAnyAddress()); // Do not redirect since use_original_dst is false. - Network::TestListenerImpl listener(dispatcher, socket, listener_callbacks, true, true); + Network::TestListenerImpl listener(dispatcher_, socket, listener_callbacks, true, true); auto listener_address = Network::Utility::getAddressWithPort( *Network::Test::getCanonicalLoopbackAddress(version_), socket.localAddress()->ip()->port()); auto local_dst_address = Network::Utility::getAddressWithPort( *Network::Utility::getCanonicalIpv4LoopbackAddress(), socket.localAddress()->ip()->port()); - Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( + Network::ClientConnectionPtr client_connection = dispatcher_.createClientConnection( local_dst_address, Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); client_connection->connect(); @@ -228,7 +228,7 @@ TEST_P(ListenerImplTest, WildcardListenerIpv4Compat) { EXPECT_CALL(listener_callbacks, onAccept_(_, _)) .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket, bool) -> void { - Network::ConnectionPtr new_connection = dispatcher.createServerConnection( + Network::ConnectionPtr new_connection = dispatcher_.createServerConnection( std::move(socket), Network::Test::createRawBufferSocket()); listener_callbacks.onNewConnection(std::move(new_connection)); })); @@ -238,10 +238,10 @@ TEST_P(ListenerImplTest, WildcardListenerIpv4Compat) { EXPECT_EQ(*conn->localAddress(), *local_dst_address); client_connection->close(ConnectionCloseType::NoFlush); conn->close(ConnectionCloseType::NoFlush); - dispatcher.exit(); + dispatcher_.exit(); })); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_.run(Event::Dispatcher::RunType::Block); } } // namespace Network diff --git a/test/common/ratelimit/ratelimit_impl_test.cc b/test/common/ratelimit/ratelimit_impl_test.cc index 7c2fe8e9d7b1..1ff219a9dc4d 100644 --- a/test/common/ratelimit/ratelimit_impl_test.cc +++ b/test/common/ratelimit/ratelimit_impl_test.cc @@ -47,6 +47,8 @@ class RateLimitGrpcClientTest : public testing::TestWithParam { Grpc::AsyncClientPtr{async_client_}, absl::optional(), "envoy.service.ratelimit.v2.RateLimitService.ShouldRateLimit")); } else { + // Force link time dependency on deprecated message type. + pb::lyft::ratelimit::RateLimit _ignore; client_.reset(new GrpcClientImpl(Grpc::AsyncClientPtr{async_client_}, absl::optional(), "pb.lyft.ratelimit.RateLimitService.ShouldRateLimit")); diff --git a/test/common/request_info/utility_test.cc b/test/common/request_info/utility_test.cc index d2e677d2d324..cecf3dd4aefe 100644 --- a/test/common/request_info/utility_test.cc +++ b/test/common/request_info/utility_test.cc @@ -14,7 +14,7 @@ namespace Envoy { namespace RequestInfo { TEST(ResponseFlagUtilsTest, toShortStringConversion) { - static_assert(ResponseFlag::LastFlag == 0x1000, "A flag has been added. Fix this code."); + static_assert(ResponseFlag::LastFlag == 0x2000, "A flag has been added. Fix this code."); std::vector> expected = { std::make_pair(ResponseFlag::FailedLocalHealthCheck, "LH"), @@ -30,6 +30,7 @@ TEST(ResponseFlagUtilsTest, toShortStringConversion) { std::make_pair(ResponseFlag::FaultInjected, "FI"), std::make_pair(ResponseFlag::RateLimited, "RL"), std::make_pair(ResponseFlag::UnauthorizedExternalService, "UAEX"), + std::make_pair(ResponseFlag::RateLimitServiceError, "RLSE"), }; for (const auto& test_case : expected) { @@ -58,7 +59,7 @@ TEST(ResponseFlagUtilsTest, toShortStringConversion) { } TEST(ResponseFlagsUtilsTest, toResponseFlagConversion) { - static_assert(ResponseFlag::LastFlag == 0x1000, "A flag has been added. Fix this code."); + static_assert(ResponseFlag::LastFlag == 0x2000, "A flag has been added. Fix this code."); std::vector> expected = { std::make_pair("LH", ResponseFlag::FailedLocalHealthCheck), @@ -74,6 +75,7 @@ TEST(ResponseFlagsUtilsTest, toResponseFlagConversion) { std::make_pair("FI", ResponseFlag::FaultInjected), std::make_pair("RL", ResponseFlag::RateLimited), std::make_pair("UAEX", ResponseFlag::UnauthorizedExternalService), + std::make_pair("RLSE", ResponseFlag::RateLimitServiceError), }; EXPECT_FALSE(ResponseFlagUtils::toResponseFlag("NonExistentFlag").has_value()); diff --git a/test/common/router/BUILD b/test/common/router/BUILD index 5207bb84845b..b7acdb092b4b 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -14,7 +14,7 @@ envoy_cc_test( name = "config_impl_test", srcs = ["config_impl_test.cc"], deps = [ - ":route_fuzz_proto", + ":route_fuzz_proto_cc", "//source/common/config:metadata_lib", "//source/common/config:rds_json_lib", "//source/common/http:header_map_lib", @@ -33,10 +33,9 @@ envoy_cc_test( envoy_proto_library( name = "header_parser_fuzz_proto", srcs = ["header_parser_fuzz.proto"], - generate_python = 0, deps = [ "//test/fuzz:common_proto", - "@envoy_api//envoy/api/v2/core:base_cc_proto", + "@envoy_api//envoy/api/v2/core:base_export", ], ) @@ -45,7 +44,7 @@ envoy_cc_fuzz_test( srcs = ["header_parser_fuzz_test.cc"], corpus = "header_parser_corpus", deps = [ - ":header_parser_fuzz_proto", + ":header_parser_fuzz_proto_cc", "//source/common/http:header_map_lib", "//source/common/router:header_parser_lib", "//test/fuzz:utility_lib", @@ -88,10 +87,9 @@ envoy_cc_test( envoy_proto_library( name = "route_fuzz_proto", srcs = ["route_fuzz.proto"], - generate_python = 0, deps = [ "//test/fuzz:common_proto", - "@envoy_api//envoy/api/v2:rds_cc_proto", + "@envoy_api//envoy/api/v2:rds_export", ], ) @@ -100,7 +98,7 @@ envoy_cc_fuzz_test( srcs = ["route_fuzz_test.cc"], corpus = "route_corpus", deps = [ - ":route_fuzz_proto", + ":route_fuzz_proto_cc", "//source/common/router:config_lib", "//test/fuzz:utility_lib", "//test/mocks/server:server_mocks", diff --git a/test/common/router/header_formatter_test.cc b/test/common/router/header_formatter_test.cc index 000549573560..ceaad6c6b483 100644 --- a/test/common/router/header_formatter_test.cc +++ b/test/common/router/header_formatter_test.cc @@ -617,6 +617,10 @@ match: { prefix: "/new_endpoint" } key: "x-request-start" value: "%START_TIME(%s.%3f)%" append: true + - header: + key: "x-request-start-multiple" + value: "%START_TIME(%s.%3f)% %START_TIME% %START_TIME(%s)%" + append: true - header: key: "x-request-start-f" value: "%START_TIME(f)%" @@ -640,14 +644,17 @@ match: { prefix: "/new_endpoint" } // Initialize start_time as 2018-04-03T23:06:09.123Z in microseconds. const SystemTime start_time(std::chrono::microseconds(1522796769123456)); - EXPECT_CALL(request_info, startTime()).Times(4).WillRepeatedly(Return(start_time)); + EXPECT_CALL(request_info, startTime()).Times(7).WillRepeatedly(Return(start_time)); resp_header_parser->evaluateHeaders(headerMap, request_info); EXPECT_TRUE(headerMap.has("x-client-ip")); + EXPECT_TRUE(headerMap.has("x-request-start-multiple")); EXPECT_TRUE(headerMap.has("x-safe")); EXPECT_FALSE(headerMap.has("x-nope")); EXPECT_TRUE(headerMap.has("x-request-start")); EXPECT_EQ("1522796769.123", headerMap.get_("x-request-start")); + EXPECT_EQ("1522796769.123 2018-04-03T23:06:09.123Z 1522796769", + headerMap.get_("x-request-start-multiple")); EXPECT_TRUE(headerMap.has("x-request-start-f")); EXPECT_EQ("f", headerMap.get_("x-request-start-f")); EXPECT_TRUE(headerMap.has("x-request-start-default")); diff --git a/test/common/router/header_parser_corpus/clusterfuzz-testcase-header_parser_fuzz_test-5107723602493440 b/test/common/router/header_parser_corpus/clusterfuzz-testcase-header_parser_fuzz_test-5107723602493440 new file mode 100644 index 000000000000..1866c52aa309 --- /dev/null +++ b/test/common/router/header_parser_corpus/clusterfuzz-testcase-header_parser_fuzz_test-5107723602493440 @@ -0,0 +1,14 @@ +headers_to_add { +} +headers_to_add { + header { + key: "te" + value: "l" + } +} +headers_to_add { + header { + key: "te" + value: "@" + } +} diff --git a/test/common/router/header_parser_corpus/clusterfuzz-testcase-header_parser_fuzz_test-6195059702628352 b/test/common/router/header_parser_corpus/clusterfuzz-testcase-header_parser_fuzz_test-6195059702628352 new file mode 100644 index 000000000000..b1d85a401670 --- /dev/null +++ b/test/common/router/header_parser_corpus/clusterfuzz-testcase-header_parser_fuzz_test-6195059702628352 @@ -0,0 +1,8 @@ +headers_to_add { + header { + value: "%START_TIMEY()%T %START_TIME(f, %�{{{{{_�����������85request_i: 1�227 f55555_n555555555555%85nfo 5#555.55f, %1f, %85/5_inf %8,,,,,,,,,,,,,,,,,,,,,,,,,55555 start_timefo 5#5555#555.55f, %1f, %85/55ime: 15227 f %1f, %8555555555 %85/5555Fme: 15227 f-5555_inf 965L5559f)%" + } +} +request_info { + start_time: 1522796769123 +} diff --git a/test/common/router/header_parser_fuzz_test.cc b/test/common/router/header_parser_fuzz_test.cc index 73bcfbb66a17..ed28befac9ff 100644 --- a/test/common/router/header_parser_fuzz_test.cc +++ b/test/common/router/header_parser_fuzz_test.cc @@ -1,7 +1,7 @@ #include "common/http/header_map_impl.h" #include "common/router/header_parser.h" -#include "test/common/router/header_parser_fuzz.pb.h" +#include "test/common/router/header_parser_fuzz.pb.validate.h" #include "test/fuzz/fuzz_runner.h" #include "test/fuzz/utility.h" @@ -10,6 +10,7 @@ namespace Fuzz { DEFINE_PROTO_FUZZER(const test::common::router::TestCase& input) { try { + MessageUtil::validate(input); Router::HeaderParserPtr parser = Router::HeaderParser::configure(input.headers_to_add(), input.headers_to_remove()); Http::HeaderMapImpl header_map; diff --git a/test/common/router/route_corpus/clusterfuzz-testcase-minimized-route_fuzz_test-5198208916520960 b/test/common/router/route_corpus/clusterfuzz-testcase-minimized-route_fuzz_test-5198208916520960 new file mode 100644 index 000000000000..c47ed58e4b5e --- /dev/null +++ b/test/common/router/route_corpus/clusterfuzz-testcase-minimized-route_fuzz_test-5198208916520960 @@ -0,0 +1 @@ +config { virtual_hosts { name: " " domains: "*" routes { match { path: "" } route { cluster: "" } } } } headers { headers { key: ":path" } } diff --git a/test/common/secret/BUILD b/test/common/secret/BUILD index c65489952a84..cbb7aff3e74d 100644 --- a/test/common/secret/BUILD +++ b/test/common/secret/BUILD @@ -15,9 +15,30 @@ envoy_cc_test( "//test/common/ssl/test_data:certs", ], deps = [ + "//source/common/secret:sds_api_lib", "//source/common/secret:secret_manager_impl_lib", + "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", "//test/test_common:registry_lib", "//test/test_common:utility_lib", ], ) + +envoy_cc_test( + name = "sds_api_test", + srcs = ["sds_api_test.cc"], + data = [ + "//test/common/ssl/test_data:certs", + ], + deps = [ + "//source/common/secret:sds_api_lib", + "//test/mocks/grpc:grpc_mocks", + "//test/mocks/init:init_mocks", + "//test/mocks/secret:secret_mocks", + "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", + "//test/test_common:registry_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/service/discovery/v2:sds_cc", + ], +) diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc new file mode 100644 index 000000000000..17435fe5c8d8 --- /dev/null +++ b/test/common/secret/sds_api_test.cc @@ -0,0 +1,170 @@ +#include + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/common/exception.h" +#include "envoy/service/discovery/v2/sds.pb.h" + +#include "common/secret/sds_api.h" + +#include "test/mocks/grpc/mocks.h" +#include "test/mocks/init/mocks.h" +#include "test/mocks/secret/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; + +namespace Envoy { +namespace Secret { +namespace { + +class SdsApiTest : public testing::Test {}; + +// Validate that SdsApi object is created and initialized successfully. +TEST_F(SdsApiTest, BasicTest) { + ::testing::InSequence s; + const envoy::service::discovery::v2::SdsDummy dummy; + NiceMock server; + NiceMock init_manager; + EXPECT_CALL(init_manager, registerTarget(_)); + + envoy::api::v2::core::ConfigSource config_source; + config_source.mutable_api_config_source()->set_api_type( + envoy::api::v2::core::ApiConfigSource::GRPC); + auto grpc_service = config_source.mutable_api_config_source()->add_grpc_services(); + auto google_grpc = grpc_service->mutable_google_grpc(); + google_grpc->set_target_uri("fake_address"); + google_grpc->set_stat_prefix("test"); + SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), + server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + + NiceMock* grpc_client{new NiceMock()}; + NiceMock* factory{new NiceMock()}; + EXPECT_CALL(server.cluster_manager_.async_client_manager_, factoryForGrpcService(_, _, _)) + .WillOnce(Invoke([factory](const envoy::api::v2::core::GrpcService&, Stats::Scope&, bool) { + return Grpc::AsyncClientFactoryPtr{factory}; + })); + EXPECT_CALL(*factory, create()).WillOnce(Invoke([grpc_client] { + return Grpc::AsyncClientPtr{grpc_client}; + })); + EXPECT_CALL(init_manager.initialized_, ready()); + init_manager.initialize(); +} + +// Validate that SdsApi updates secrets successfully if a good secret is passed to onConfigUpdate(). +TEST_F(SdsApiTest, SecretUpdateSuccess) { + NiceMock server; + NiceMock init_manager; + envoy::api::v2::core::ConfigSource config_source; + SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), + server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + + NiceMock secret_callback; + auto handle = + sds_api.addUpdateCallback([&secret_callback]() { secret_callback.onAddOrUpdateSecret(); }); + + std::string yaml = + R"EOF( + name: "abc.com" + tls_certificate: + certificate_chain: + filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem" + )EOF"; + + Protobuf::RepeatedPtrField secret_resources; + auto secret_config = secret_resources.Add(); + MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), *secret_config); + EXPECT_CALL(secret_callback, onAddOrUpdateSecret()); + sds_api.onConfigUpdate(secret_resources, ""); + + const std::string cert_pem = "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem"; + EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), + sds_api.secret()->certificateChain()); + + const std::string key_pem = "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem"; + EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(key_pem)), + sds_api.secret()->privateKey()); + + handle->remove(); +} + +// Validate that SdsApi throws exception if an empty secret is passed to onConfigUpdate(). +TEST_F(SdsApiTest, EmptyResource) { + NiceMock server; + NiceMock init_manager; + envoy::api::v2::core::ConfigSource config_source; + SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), + server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + + Protobuf::RepeatedPtrField secret_resources; + + EXPECT_THROW_WITH_MESSAGE(sds_api.onConfigUpdate(secret_resources, ""), EnvoyException, + "Missing SDS resources for abc.com in onConfigUpdate()"); +} + +// Validate that SdsApi throws exception if multiple secrets are passed to onConfigUpdate(). +TEST_F(SdsApiTest, SecretUpdateWrongSize) { + NiceMock server; + NiceMock init_manager; + envoy::api::v2::core::ConfigSource config_source; + SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), + server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + + std::string yaml = + R"EOF( + name: "abc.com" + tls_certificate: + certificate_chain: + filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem" + )EOF"; + + Protobuf::RepeatedPtrField secret_resources; + auto secret_config_1 = secret_resources.Add(); + MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), *secret_config_1); + auto secret_config_2 = secret_resources.Add(); + MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), *secret_config_2); + + EXPECT_THROW_WITH_MESSAGE(sds_api.onConfigUpdate(secret_resources, ""), EnvoyException, + "Unexpected SDS secrets length: 2"); +} + +// Validate that SdsApi throws exception if secret name passed to onConfigUpdate() +// does not match configured name. +TEST_F(SdsApiTest, SecretUpdateWrongSecretName) { + NiceMock server; + NiceMock init_manager; + envoy::api::v2::core::ConfigSource config_source; + SdsApi sds_api(server.localInfo(), server.dispatcher(), server.random(), server.stats(), + server.clusterManager(), init_manager, config_source, "abc.com", []() {}); + + std::string yaml = + R"EOF( + name: "wrong.name.com" + tls_certificate: + certificate_chain: + filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem" + )EOF"; + + Protobuf::RepeatedPtrField secret_resources; + auto secret_config = secret_resources.Add(); + MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), *secret_config); + + EXPECT_THROW_WITH_MESSAGE(sds_api.onConfigUpdate(secret_resources, ""), EnvoyException, + "Unexpected SDS secret (expecting abc.com): wrong.name.com"); +} + +} // namespace +} // namespace Secret +} // namespace Envoy diff --git a/test/common/secret/secret_manager_impl_test.cc b/test/common/secret/secret_manager_impl_test.cc index bf27f559fd4f..f957d3c301ca 100644 --- a/test/common/secret/secret_manager_impl_test.cc +++ b/test/common/secret/secret_manager_impl_test.cc @@ -3,14 +3,19 @@ #include "envoy/api/v2/auth/cert.pb.h" #include "envoy/common/exception.h" +#include "common/secret/sds_api.h" #include "common/secret/secret_manager_impl.h" +#include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::Return; +using testing::ReturnRef; + namespace Envoy { namespace Secret { namespace { @@ -69,6 +74,51 @@ name: "abc.com" "Secret type not implemented"); } +TEST_F(SecretManagerImplTest, SdsDynamicSecretUpdateSuccess) { + Server::MockInstance server; + std::unique_ptr secret_manager(new SecretManagerImpl()); + + NiceMock secret_context; + + envoy::api::v2::core::ConfigSource config_source; + { + NiceMock local_info; + NiceMock dispatcher; + NiceMock random; + Stats::IsolatedStoreImpl stats; + NiceMock cluster_manager; + NiceMock init_manager; + EXPECT_CALL(secret_context, localInfo()).WillOnce(ReturnRef(local_info)); + EXPECT_CALL(secret_context, dispatcher()).WillOnce(ReturnRef(dispatcher)); + EXPECT_CALL(secret_context, random()).WillOnce(ReturnRef(random)); + EXPECT_CALL(secret_context, stats()).WillOnce(ReturnRef(stats)); + EXPECT_CALL(secret_context, clusterManager()).WillOnce(ReturnRef(cluster_manager)); + EXPECT_CALL(secret_context, initManager()).WillRepeatedly(Return(&init_manager)); + + auto secret_provider = + secret_manager->findOrCreateDynamicSecretProvider(config_source, "abc.com", secret_context); + std::string yaml = + R"EOF( +name: "abc.com" +tls_certificate: + certificate_chain: + filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem" + )EOF"; + Protobuf::RepeatedPtrField secret_resources; + auto secret_config = secret_resources.Add(); + MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), *secret_config); + std::dynamic_pointer_cast(secret_provider)->onConfigUpdate(secret_resources, ""); + const std::string cert_pem = "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem"; + EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), + secret_provider->secret()->certificateChain()); + const std::string key_pem = "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_key.pem"; + EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(key_pem)), + secret_provider->secret()->privateKey()); + } +} + } // namespace } // namespace Secret } // namespace Envoy diff --git a/test/common/ssl/BUILD b/test/common/ssl/BUILD index 70a45354264f..a0a47930f095 100644 --- a/test/common/ssl/BUILD +++ b/test/common/ssl/BUILD @@ -36,11 +36,11 @@ envoy_cc_test( "//test/mocks/buffer:buffer_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/secret:secret_mocks", "//test/mocks/server:server_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) @@ -57,13 +57,13 @@ envoy_cc_test( ], deps = [ "//source/common/json:json_loader_lib", - "//source/common/secret:secret_manager_impl_lib", "//source/common/ssl:context_config_lib", "//source/common/ssl:context_lib", "//source/common/stats:isolated_store_lib", "//source/common/stats:stats_lib", "//test/mocks/runtime:runtime_mocks", "//test/mocks/secret:secret_mocks", + "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", ], ) diff --git a/test/common/ssl/context_impl_test.cc b/test/common/ssl/context_impl_test.cc index 4c533ea17c19..70dcc0e9badc 100644 --- a/test/common/ssl/context_impl_test.cc +++ b/test/common/ssl/context_impl_test.cc @@ -2,7 +2,7 @@ #include #include "common/json/json_loader.h" -#include "common/secret/secret_manager_impl.h" +#include "common/secret/sds_api.h" #include "common/ssl/context_config_impl.h" #include "common/ssl/context_impl.h" #include "common/stats/isolated_store_impl.h" @@ -10,12 +10,14 @@ #include "test/common/ssl/ssl_certs_test.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/secret/mocks.h" +#include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" using testing::NiceMock; +using testing::ReturnRef; namespace Envoy { namespace Ssl { @@ -81,7 +83,7 @@ TEST_F(SslContextImplTest, TestCipherSuites) { )EOF"; Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(json); - ClientContextConfigImpl cfg(*loader, secret_manager_); + ClientContextConfigImpl cfg(*loader, factory_context_); Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; @@ -100,7 +102,7 @@ TEST_F(SslContextImplTest, TestExpiringCert) { )EOF"; Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(json); - ClientContextConfigImpl cfg(*loader, secret_manager_); + ClientContextConfigImpl cfg(*loader, factory_context_); Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; @@ -123,7 +125,7 @@ TEST_F(SslContextImplTest, TestExpiredCert) { )EOF"; Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(json); - ClientContextConfigImpl cfg(*loader, secret_manager_); + ClientContextConfigImpl cfg(*loader, factory_context_); Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; @@ -141,7 +143,7 @@ TEST_F(SslContextImplTest, TestGetCertInformation) { )EOF"; Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(json); - ClientContextConfigImpl cfg(*loader, secret_manager_); + ClientContextConfigImpl cfg(*loader, factory_context_); Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; @@ -167,7 +169,7 @@ TEST_F(SslContextImplTest, TestGetCertInformation) { TEST_F(SslContextImplTest, TestNoCert) { Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString("{}"); - ClientContextConfigImpl cfg(*loader, secret_manager_); + ClientContextConfigImpl cfg(*loader, factory_context_); Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; @@ -180,7 +182,6 @@ class SslServerContextImplTicketTest : public SslContextImplTest { public: static void loadConfig(ServerContextConfigImpl& cfg) { Runtime::MockLoader runtime; - NiceMock secret_manager; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; ServerContextSharedPtr server_ctx( @@ -196,15 +197,15 @@ class SslServerContextImplTicketTest : public SslContextImplTest { server_cert->mutable_private_key()->set_filename( TestEnvironment::substitute("{{ test_tmpdir }}/unittestkey.pem")); - NiceMock secret_manager; - ServerContextConfigImpl server_context_config(cfg, secret_manager); + NiceMock factory_context; + ServerContextConfigImpl server_context_config(cfg, factory_context); loadConfig(server_context_config); } static void loadConfigJson(const std::string& json) { Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(json); - NiceMock secret_manager; - ServerContextConfigImpl cfg(*loader, secret_manager); + NiceMock factory_context; + ServerContextConfigImpl cfg(*loader, factory_context); loadConfig(cfg); } }; @@ -361,28 +362,28 @@ class ClientContextConfigImplTest : public SslCertsTest {}; // Validate that empty SNI (according to C string rules) fails config validation. TEST(ClientContextConfigImplTest, EmptyServerNameIndication) { envoy::api::v2::auth::UpstreamTlsContext tls_context; - NiceMock secret_manager; + NiceMock factory_context; tls_context.set_sni(std::string("\000", 1)); EXPECT_THROW_WITH_MESSAGE( - ClientContextConfigImpl client_context_config(tls_context, secret_manager), EnvoyException, + ClientContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException, "SNI names containing NULL-byte are not allowed"); tls_context.set_sni(std::string("a\000b", 3)); EXPECT_THROW_WITH_MESSAGE( - ClientContextConfigImpl client_context_config(tls_context, secret_manager), EnvoyException, + ClientContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException, "SNI names containing NULL-byte are not allowed"); } // Validate that values other than a hex-encoded SHA-256 fail config validation. TEST(ClientContextConfigImplTest, InvalidCertificateHash) { envoy::api::v2::auth::UpstreamTlsContext tls_context; - NiceMock secret_manager; + NiceMock factory_context; tls_context.mutable_common_tls_context() ->mutable_validation_context() // This is valid hex-encoded string, but it doesn't represent SHA-256 (80 vs 64 chars). ->add_verify_certificate_hash("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - ClientContextConfigImpl client_context_config(tls_context, secret_manager); + ClientContextConfigImpl client_context_config(tls_context, factory_context); Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; @@ -393,12 +394,12 @@ TEST(ClientContextConfigImplTest, InvalidCertificateHash) { // Validate that values other than a base64-encoded SHA-256 fail config validation. TEST(ClientContextConfigImplTest, InvalidCertificateSpki) { envoy::api::v2::auth::UpstreamTlsContext tls_context; - NiceMock secret_manager; + NiceMock factory_context; tls_context.mutable_common_tls_context() ->mutable_validation_context() // Not a base64-encoded string. ->add_verify_certificate_spki("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); - ClientContextConfigImpl client_context_config(tls_context, secret_manager); + ClientContextConfigImpl client_context_config(tls_context, factory_context); Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; @@ -410,14 +411,48 @@ TEST(ClientContextConfigImplTest, InvalidCertificateSpki) { // TODO(PiotrSikora): Support multiple TLS certificates. TEST(ClientContextConfigImplTest, MultipleTlsCertificates) { envoy::api::v2::auth::UpstreamTlsContext tls_context; - NiceMock secret_manager; + NiceMock factory_context; tls_context.mutable_common_tls_context()->add_tls_certificates(); tls_context.mutable_common_tls_context()->add_tls_certificates(); EXPECT_THROW_WITH_MESSAGE( - ClientContextConfigImpl client_context_config(tls_context, secret_manager), EnvoyException, + ClientContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException, "Multiple TLS certificates are not supported for client contexts"); } +TEST(ClientContextConfigImplTest, TlsCertificatesAndSdsConfig) { + envoy::api::v2::auth::UpstreamTlsContext tls_context; + NiceMock factory_context; + tls_context.mutable_common_tls_context()->add_tls_certificates(); + tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs(); + EXPECT_THROW_WITH_MESSAGE( + ClientContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException, + "Multiple TLS certificates are not supported for client contexts"); +} + +TEST(ClientContextConfigImplTest, SecretNotReady) { + envoy::api::v2::auth::UpstreamTlsContext tls_context; + NiceMock local_info; + NiceMock dispatcher; + NiceMock random; + Stats::IsolatedStoreImpl stats; + NiceMock cluster_manager; + NiceMock init_manager; + NiceMock factory_context; + EXPECT_CALL(factory_context, localInfo()).WillOnce(ReturnRef(local_info)); + EXPECT_CALL(factory_context, dispatcher()).WillOnce(ReturnRef(dispatcher)); + EXPECT_CALL(factory_context, random()).WillOnce(ReturnRef(random)); + EXPECT_CALL(factory_context, stats()).WillOnce(ReturnRef(stats)); + EXPECT_CALL(factory_context, clusterManager()).WillOnce(ReturnRef(cluster_manager)); + EXPECT_CALL(factory_context, initManager()).WillRepeatedly(Return(&init_manager)); + auto sds_secret_configs = + tls_context.mutable_common_tls_context()->mutable_tls_certificate_sds_secret_configs()->Add(); + sds_secret_configs->set_name("abc.com"); + sds_secret_configs->mutable_sds_config(); + ClientContextConfigImpl client_context_config(tls_context, factory_context); + // When sds secret is not downloaded, config is not ready. + EXPECT_FALSE(client_context_config.isReady()); +} + TEST(ClientContextConfigImplTest, StaticTlsCertificates) { envoy::api::v2::auth::Secret secret_config; @@ -432,16 +467,15 @@ name: "abc.com" MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), secret_config); - std::unique_ptr secret_manager(new Secret::SecretManagerImpl()); - secret_manager->addStaticSecret(secret_config); - envoy::api::v2::auth::UpstreamTlsContext tls_context; tls_context.mutable_common_tls_context() ->mutable_tls_certificate_sds_secret_configs() ->Add() ->set_name("abc.com"); - ClientContextConfigImpl client_context_config(tls_context, *secret_manager.get()); + NiceMock factory_context; + factory_context.secretManager().addStaticSecret(secret_config); + ClientContextConfigImpl client_context_config(tls_context, factory_context); const std::string cert_pem = "{{ test_rundir }}/test/common/ssl/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), @@ -465,9 +499,8 @@ name: "abc.com" MessageUtil::loadFromYaml(TestEnvironment::substitute(yaml), secret_config); - std::unique_ptr secret_manager(new Secret::SecretManagerImpl()); - - secret_manager->addStaticSecret(secret_config); + NiceMock factory_context; + factory_context.secretManager().addStaticSecret(secret_config); envoy::api::v2::auth::UpstreamTlsContext tls_context; tls_context.mutable_common_tls_context() @@ -476,8 +509,8 @@ name: "abc.com" ->set_name("missing"); EXPECT_THROW_WITH_MESSAGE( - ClientContextConfigImpl client_context_config(tls_context, *secret_manager.get()), - EnvoyException, "Static secret is not defined: missing"); + ClientContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException, + "Unknown static secret: missing"); } // Multiple TLS certificates are not yet supported, but one is expected for @@ -485,23 +518,60 @@ name: "abc.com" // TODO(PiotrSikora): Support multiple TLS certificates. TEST(ServerContextConfigImplTest, MultipleTlsCertificates) { envoy::api::v2::auth::DownstreamTlsContext tls_context; - NiceMock secret_manager; + NiceMock factory_context; EXPECT_THROW_WITH_MESSAGE( - ServerContextConfigImpl client_context_config(tls_context, secret_manager), EnvoyException, + ServerContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException, "A single TLS certificate is required for server contexts"); tls_context.mutable_common_tls_context()->add_tls_certificates(); tls_context.mutable_common_tls_context()->add_tls_certificates(); EXPECT_THROW_WITH_MESSAGE( - ServerContextConfigImpl client_context_config(tls_context, secret_manager), EnvoyException, + ServerContextConfigImpl client_context_config(tls_context, factory_context), EnvoyException, "A single TLS certificate is required for server contexts"); } +TEST(ServerContextConfigImplTest, TlsCertificatesAndSdsConfig) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + NiceMock factory_context; + EXPECT_THROW_WITH_MESSAGE( + ServerContextConfigImpl server_context_config(tls_context, factory_context), EnvoyException, + "A single TLS certificate is required for server contexts"); + tls_context.mutable_common_tls_context()->add_tls_certificates(); + tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs(); + EXPECT_THROW_WITH_MESSAGE( + ServerContextConfigImpl server_context_config(tls_context, factory_context), EnvoyException, + "A single TLS certificate is required for server contexts"); +} + +TEST(ServerContextConfigImplTest, SecretNotReady) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + NiceMock local_info; + NiceMock dispatcher; + NiceMock random; + Stats::IsolatedStoreImpl stats; + NiceMock cluster_manager; + NiceMock init_manager; + NiceMock factory_context; + EXPECT_CALL(factory_context, localInfo()).WillOnce(ReturnRef(local_info)); + EXPECT_CALL(factory_context, dispatcher()).WillOnce(ReturnRef(dispatcher)); + EXPECT_CALL(factory_context, random()).WillOnce(ReturnRef(random)); + EXPECT_CALL(factory_context, stats()).WillOnce(ReturnRef(stats)); + EXPECT_CALL(factory_context, clusterManager()).WillOnce(ReturnRef(cluster_manager)); + EXPECT_CALL(factory_context, initManager()).WillRepeatedly(Return(&init_manager)); + auto sds_secret_configs = + tls_context.mutable_common_tls_context()->mutable_tls_certificate_sds_secret_configs()->Add(); + sds_secret_configs->set_name("abc.com"); + sds_secret_configs->mutable_sds_config(); + ServerContextConfigImpl server_context_config(tls_context, factory_context); + // When sds secret is not downloaded, config is not ready. + EXPECT_FALSE(server_context_config.isReady()); +} + // TlsCertificate messages must have a cert for servers. TEST(ServerContextImplTest, TlsCertificateNonEmpty) { envoy::api::v2::auth::DownstreamTlsContext tls_context; - NiceMock secret_manager; + NiceMock factory_context; tls_context.mutable_common_tls_context()->add_tls_certificates(); - ServerContextConfigImpl client_context_config(tls_context, secret_manager); + ServerContextConfigImpl client_context_config(tls_context, factory_context); Runtime::MockLoader runtime; ContextManagerImpl manager(runtime); Stats::IsolatedStoreImpl store; @@ -514,7 +584,7 @@ TEST(ServerContextImplTest, TlsCertificateNonEmpty) { // Cannot ignore certificate expiration without a trusted CA. TEST(ServerContextConfigImplTest, InvalidIgnoreCertsNoCA) { envoy::api::v2::auth::DownstreamTlsContext tls_context; - NiceMock secret_manager; + NiceMock factory_context; envoy::api::v2::auth::CertificateValidationContext* server_validation_ctx = tls_context.mutable_common_tls_context()->mutable_validation_context(); @@ -522,7 +592,7 @@ TEST(ServerContextConfigImplTest, InvalidIgnoreCertsNoCA) { server_validation_ctx->set_allow_expired_certificate(true); EXPECT_THROW_WITH_MESSAGE( - ServerContextConfigImpl server_context_config(tls_context, secret_manager), EnvoyException, + ServerContextConfigImpl server_context_config(tls_context, factory_context), EnvoyException, "Certificate validity period is always ignored without trusted CA"); envoy::api::v2::auth::TlsCertificate* server_cert = @@ -534,19 +604,19 @@ TEST(ServerContextConfigImplTest, InvalidIgnoreCertsNoCA) { server_validation_ctx->set_allow_expired_certificate(false); - EXPECT_NO_THROW(ServerContextConfigImpl server_context_config(tls_context, secret_manager)); + EXPECT_NO_THROW(ServerContextConfigImpl server_context_config(tls_context, factory_context)); server_validation_ctx->set_allow_expired_certificate(true); EXPECT_THROW_WITH_MESSAGE( - ServerContextConfigImpl server_context_config(tls_context, secret_manager), EnvoyException, + ServerContextConfigImpl server_context_config(tls_context, factory_context), EnvoyException, "Certificate validity period is always ignored without trusted CA"); // But once you add a trusted CA, you should be able to create the context. server_validation_ctx->mutable_trusted_ca()->set_filename( TestEnvironment::substitute("{{ test_rundir }}/test/common/ssl/test_data/ca_cert.pem")); - EXPECT_NO_THROW(ServerContextConfigImpl server_context_config(tls_context, secret_manager)); + EXPECT_NO_THROW(ServerContextConfigImpl server_context_config(tls_context, factory_context)); } } // namespace Ssl diff --git a/test/common/ssl/ssl_certs_test.h b/test/common/ssl/ssl_certs_test.h index 37b85cad3f45..ad345ef1b2c2 100644 --- a/test/common/ssl/ssl_certs_test.h +++ b/test/common/ssl/ssl_certs_test.h @@ -1,6 +1,6 @@ #pragma once -#include "test/mocks/secret/mocks.h" +#include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "gtest/gtest.h" @@ -12,6 +12,6 @@ class SslCertsTest : public testing::Test { TestEnvironment::exec({TestEnvironment::runfilesPath("test/common/ssl/gen_unittest_certs.sh")}); } - testing::NiceMock secret_manager_; + testing::NiceMock factory_context_; }; } // namespace Envoy diff --git a/test/common/ssl/ssl_socket_test.cc b/test/common/ssl/ssl_socket_test.cc index f0a1df4d2f57..4e79e5be24d1 100644 --- a/test/common/ssl/ssl_socket_test.cc +++ b/test/common/ssl/ssl_socket_test.cc @@ -24,7 +24,7 @@ #include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" -#include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -51,15 +51,16 @@ void testUtil(const std::string& client_ctx_json, const std::string& server_ctx_ bool expect_success, const Network::Address::IpVersion version) { Stats::IsolatedStoreImpl stats_store; Runtime::MockLoader runtime; - NiceMock secret_manager; + testing::NiceMock factory_context; Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); - auto server_cfg = std::make_unique(*server_ctx_loader, secret_manager); + auto server_cfg = std::make_unique(*server_ctx_loader, factory_context); ContextManagerImpl manager(runtime); Ssl::ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, stats_store, std::vector{}); - Event::DispatcherImpl dispatcher; + DangerousDeprecatedTestTime test_time; + Event::DispatcherImpl dispatcher(test_time.timeSource()); Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(version), nullptr, true); Network::MockListenerCallbacks callbacks; @@ -67,7 +68,7 @@ void testUtil(const std::string& client_ctx_json, const std::string& server_ctx_ Network::ListenerPtr listener = dispatcher.createListener(socket, callbacks, true, false); Json::ObjectSharedPtr client_ctx_loader = TestEnvironment::jsonLoadFromString(client_ctx_json); - auto client_cfg = std::make_unique(*client_ctx_loader, secret_manager); + auto client_cfg = std::make_unique(*client_ctx_loader, factory_context); Ssl::ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, stats_store); Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( @@ -148,7 +149,7 @@ const std::string testUtilV2(const envoy::api::v2::Listener& server_proto, const Network::Address::IpVersion version) { Stats::IsolatedStoreImpl stats_store; Runtime::MockLoader runtime; - NiceMock secret_manager; + testing::NiceMock factory_context; ContextManagerImpl manager(runtime); std::string new_session = EMPTY_STRING; @@ -158,18 +159,19 @@ const std::string testUtilV2(const envoy::api::v2::Listener& server_proto, std::vector server_names(filter_chain.filter_chain_match().server_names().begin(), filter_chain.filter_chain_match().server_names().end()); auto server_cfg = - std::make_unique(filter_chain.tls_context(), secret_manager); + std::make_unique(filter_chain.tls_context(), factory_context); Ssl::ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, stats_store, server_names); - Event::DispatcherImpl dispatcher; + DangerousDeprecatedTestTime test_time; + Event::DispatcherImpl dispatcher(test_time.timeSource()); Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(version), nullptr, true); NiceMock callbacks; Network::MockConnectionHandler connection_handler; Network::ListenerPtr listener = dispatcher.createListener(socket, callbacks, true, false); - auto client_cfg = std::make_unique(client_ctx_proto, secret_manager); + auto client_cfg = std::make_unique(client_ctx_proto, factory_context); ClientContextSharedPtr client_ctx(manager.createSslClientContext(stats_store, *client_cfg)); ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, stats_store); Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( @@ -296,7 +298,13 @@ void configureServerAndExpiredClientCertificate(envoy::api::v2::Listener& listen } // namespace class SslSocketTest : public SslCertsTest, - public testing::WithParamInterface {}; + public testing::WithParamInterface { +protected: + SslSocketTest() : dispatcher_(std::make_unique(test_time_.timeSource())) {} + + DangerousDeprecatedTestTime test_time_; + std::unique_ptr dispatcher_; +}; INSTANTIATE_TEST_CASE_P(IpVersions, SslSocketTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), @@ -1538,19 +1546,18 @@ TEST_P(SslSocketTest, FlushCloseDuringHandshake) { )EOF"; Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); - auto server_cfg = std::make_unique(*server_ctx_loader, secret_manager_); + auto server_cfg = std::make_unique(*server_ctx_loader, factory_context_); ContextManagerImpl manager(runtime); Ssl::ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, stats_store, std::vector{}); - Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); Network::MockListenerCallbacks callbacks; Network::MockConnectionHandler connection_handler; - Network::ListenerPtr listener = dispatcher.createListener(socket, callbacks, true, false); + Network::ListenerPtr listener = dispatcher_->createListener(socket, callbacks, true, false); - Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( socket.localAddress(), Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); client_connection->connect(); @@ -1561,7 +1568,7 @@ TEST_P(SslSocketTest, FlushCloseDuringHandshake) { Network::MockConnectionCallbacks server_connection_callbacks; EXPECT_CALL(callbacks, onAccept_(_, _)) .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket, bool) -> void { - Network::ConnectionPtr new_connection = dispatcher.createServerConnection( + Network::ConnectionPtr new_connection = dispatcher_->createServerConnection( std::move(socket), server_ssl_socket_factory.createTransportSocket()); callbacks.onNewConnection(std::move(new_connection)); })); @@ -1577,9 +1584,9 @@ TEST_P(SslSocketTest, FlushCloseDuringHandshake) { EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) - .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher.exit(); })); + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); })); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_->run(Event::Dispatcher::RunType::Block); } // Test that half-close is sent and received correctly @@ -1596,18 +1603,17 @@ TEST_P(SslSocketTest, HalfClose) { )EOF"; Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); - auto server_cfg = std::make_unique(*server_ctx_loader, secret_manager_); + auto server_cfg = std::make_unique(*server_ctx_loader, factory_context_); ContextManagerImpl manager(runtime); Ssl::ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, stats_store, std::vector{}); - Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); Network::MockListenerCallbacks listener_callbacks; Network::MockConnectionHandler connection_handler; Network::ListenerPtr listener = - dispatcher.createListener(socket, listener_callbacks, true, false); + dispatcher_->createListener(socket, listener_callbacks, true, false); std::shared_ptr server_read_filter(new Network::MockReadFilter()); std::shared_ptr client_read_filter(new Network::MockReadFilter()); @@ -1617,9 +1623,9 @@ TEST_P(SslSocketTest, HalfClose) { )EOF"; Json::ObjectSharedPtr client_ctx_loader = TestEnvironment::jsonLoadFromString(client_ctx_json); - auto client_cfg = std::make_unique(*client_ctx_loader, secret_manager_); + auto client_cfg = std::make_unique(*client_ctx_loader, factory_context_); ClientSslSocketFactory client_ssl_socket_factory(std::move(client_cfg), manager, stats_store); - Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( socket.localAddress(), Network::Address::InstanceConstSharedPtr(), client_ssl_socket_factory.createTransportSocket(), nullptr); client_connection->enableHalfClose(true); @@ -1632,7 +1638,7 @@ TEST_P(SslSocketTest, HalfClose) { Network::MockConnectionCallbacks server_connection_callbacks; EXPECT_CALL(listener_callbacks, onAccept_(_, _)) .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket, bool) -> void { - Network::ConnectionPtr new_connection = dispatcher.createServerConnection( + Network::ConnectionPtr new_connection = dispatcher_->createServerConnection( std::move(socket), server_ssl_socket_factory.createTransportSocket()); listener_callbacks.onNewConnection(std::move(new_connection)); })); @@ -1661,15 +1667,15 @@ TEST_P(SslSocketTest, HalfClose) { EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); EXPECT_CALL(*server_read_filter, onData(BufferStringEqual("world"), true)); EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) - .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher.exit(); })); + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); })); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_->run(Event::Dispatcher::RunType::Block); } TEST_P(SslSocketTest, ClientAuthMultipleCAs) { Stats::IsolatedStoreImpl stats_store; Runtime::MockLoader runtime; - NiceMock secret_manager; + testing::NiceMock factory_context; std::string server_ctx_json = R"EOF( { @@ -1680,17 +1686,16 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { )EOF"; Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); - auto server_cfg = std::make_unique(*server_ctx_loader, secret_manager); + auto server_cfg = std::make_unique(*server_ctx_loader, factory_context); ContextManagerImpl manager(runtime); Ssl::ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, stats_store, std::vector{}); - Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); Network::MockListenerCallbacks callbacks; Network::MockConnectionHandler connection_handler; - Network::ListenerPtr listener = dispatcher.createListener(socket, callbacks, true, false); + Network::ListenerPtr listener = dispatcher_->createListener(socket, callbacks, true, false); std::string client_ctx_json = R"EOF( { @@ -1700,9 +1705,9 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { )EOF"; Json::ObjectSharedPtr client_ctx_loader = TestEnvironment::jsonLoadFromString(client_ctx_json); - auto client_cfg = std::make_unique(*client_ctx_loader, secret_manager); + auto client_cfg = std::make_unique(*client_ctx_loader, factory_context); ClientSslSocketFactory ssl_socket_factory(std::move(client_cfg), manager, stats_store); - Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( socket.localAddress(), Network::Address::InstanceConstSharedPtr(), ssl_socket_factory.createTransportSocket(), nullptr); @@ -1723,7 +1728,7 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { Network::MockConnectionCallbacks server_connection_callbacks; EXPECT_CALL(callbacks, onAccept_(_, _)) .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket, bool) -> void { - Network::ConnectionPtr new_connection = dispatcher.createServerConnection( + Network::ConnectionPtr new_connection = dispatcher_->createServerConnection( std::move(socket), server_ssl_socket_factory.createTransportSocket()); callbacks.onNewConnection(std::move(new_connection)); })); @@ -1737,11 +1742,11 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { server_connection->close(Network::ConnectionCloseType::NoFlush); client_connection->close(Network::ConnectionCloseType::NoFlush); - dispatcher.exit(); + dispatcher_->exit(); })); EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_EQ(1UL, stats_store.counter("ssl.handshake").value()); } @@ -1757,30 +1762,33 @@ void testTicketSessionResumption(const std::string& server_ctx_json1, const Network::Address::IpVersion ip_version) { Stats::IsolatedStoreImpl stats_store; Runtime::MockLoader runtime; - NiceMock secret_manager; + testing::NiceMock factory_context; ContextManagerImpl manager(runtime); Json::ObjectSharedPtr server_ctx_loader1 = TestEnvironment::jsonLoadFromString(server_ctx_json1); Json::ObjectSharedPtr server_ctx_loader2 = TestEnvironment::jsonLoadFromString(server_ctx_json2); - auto server_cfg1 = std::make_unique(*server_ctx_loader1, secret_manager); - auto server_cfg2 = std::make_unique(*server_ctx_loader2, secret_manager); + auto server_cfg1 = + std::make_unique(*server_ctx_loader1, factory_context); + auto server_cfg2 = + std::make_unique(*server_ctx_loader2, factory_context); Ssl::ServerSslSocketFactory server_ssl_socket_factory1(std::move(server_cfg1), manager, stats_store, server_names1); Ssl::ServerSslSocketFactory server_ssl_socket_factory2(std::move(server_cfg2), manager, stats_store, server_names2); - Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket1(Network::Test::getCanonicalLoopbackAddress(ip_version), nullptr, true); Network::TcpListenSocket socket2(Network::Test::getCanonicalLoopbackAddress(ip_version), nullptr, true); NiceMock callbacks; Network::MockConnectionHandler connection_handler; + DangerousDeprecatedTestTime test_time; + Event::DispatcherImpl dispatcher(test_time.timeSource()); Network::ListenerPtr listener1 = dispatcher.createListener(socket1, callbacks, true, false); Network::ListenerPtr listener2 = dispatcher.createListener(socket2, callbacks, true, false); Json::ObjectSharedPtr client_ctx_loader = TestEnvironment::jsonLoadFromString(client_ctx_json); - auto client_cfg = std::make_unique(*client_ctx_loader, secret_manager); + auto client_cfg = std::make_unique(*client_ctx_loader, factory_context); ClientSslSocketFactory ssl_socket_factory(std::move(client_cfg), manager, stats_store); Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( socket1.localAddress(), Network::Address::InstanceConstSharedPtr(), @@ -2121,25 +2129,24 @@ TEST_P(SslSocketTest, ClientAuthCrossListenerSessionResumption) { )EOF"; Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); - auto server_cfg = std::make_unique(*server_ctx_loader, secret_manager_); + auto server_cfg = std::make_unique(*server_ctx_loader, factory_context_); Json::ObjectSharedPtr server2_ctx_loader = TestEnvironment::jsonLoadFromString(server2_ctx_json); auto server2_cfg = - std::make_unique(*server2_ctx_loader, secret_manager_); + std::make_unique(*server2_ctx_loader, factory_context_); ContextManagerImpl manager(runtime); Ssl::ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, stats_store, std::vector{}); Ssl::ServerSslSocketFactory server2_ssl_socket_factory(std::move(server2_cfg), manager, stats_store, std::vector{}); - Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); Network::TcpListenSocket socket2(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); Network::MockListenerCallbacks callbacks; Network::MockConnectionHandler connection_handler; - Network::ListenerPtr listener = dispatcher.createListener(socket, callbacks, true, false); - Network::ListenerPtr listener2 = dispatcher.createListener(socket2, callbacks, true, false); + Network::ListenerPtr listener = dispatcher_->createListener(socket, callbacks, true, false); + Network::ListenerPtr listener2 = dispatcher_->createListener(socket2, callbacks, true, false); std::string client_ctx_json = R"EOF( { @@ -2149,9 +2156,9 @@ TEST_P(SslSocketTest, ClientAuthCrossListenerSessionResumption) { )EOF"; Json::ObjectSharedPtr client_ctx_loader = TestEnvironment::jsonLoadFromString(client_ctx_json); - auto client_cfg = std::make_unique(*client_ctx_loader, secret_manager_); + auto client_cfg = std::make_unique(*client_ctx_loader, factory_context_); ClientSslSocketFactory ssl_socket_factory(std::move(client_cfg), manager, stats_store); - Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( socket.localAddress(), Network::Address::InstanceConstSharedPtr(), ssl_socket_factory.createTransportSocket(), nullptr); @@ -2168,7 +2175,7 @@ TEST_P(SslSocketTest, ClientAuthCrossListenerSessionResumption) { accepted_socket->localAddress() == socket.localAddress() ? server_ssl_socket_factory : server2_ssl_socket_factory; - Network::ConnectionPtr new_connection = dispatcher.createServerConnection( + Network::ConnectionPtr new_connection = dispatcher_->createServerConnection( std::move(accepted_socket), tsf.createTransportSocket()); callbacks.onNewConnection(std::move(new_connection)); })); @@ -2188,16 +2195,16 @@ TEST_P(SslSocketTest, ClientAuthCrossListenerSessionResumption) { EXPECT_TRUE(SSL_SESSION_is_resumable(ssl_session)); server_connection->close(Network::ConnectionCloseType::NoFlush); client_connection->close(Network::ConnectionCloseType::NoFlush); - dispatcher.exit(); + dispatcher_->exit(); })); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_->run(Event::Dispatcher::RunType::Block); // 1 for client, 1 for server EXPECT_EQ(2UL, stats_store.counter("ssl.handshake").value()); - client_connection = dispatcher.createClientConnection( + client_connection = dispatcher_->createClientConnection( socket2.localAddress(), Network::Address::InstanceConstSharedPtr(), ssl_socket_factory.createTransportSocket(), nullptr); client_connection->addConnectionCallbacks(client_connection_callbacks); @@ -2214,9 +2221,9 @@ TEST_P(SslSocketTest, ClientAuthCrossListenerSessionResumption) { })); EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) - .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher.exit(); })); + .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { dispatcher_->exit(); })); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_EQ(1UL, stats_store.counter("ssl.connection_error").value()); EXPECT_EQ(0UL, stats_store.counter("ssl.session_reused").value()); @@ -2236,19 +2243,18 @@ TEST_P(SslSocketTest, SslError) { )EOF"; Json::ObjectSharedPtr server_ctx_loader = TestEnvironment::jsonLoadFromString(server_ctx_json); - auto server_cfg = std::make_unique(*server_ctx_loader, secret_manager_); + auto server_cfg = std::make_unique(*server_ctx_loader, factory_context_); ContextManagerImpl manager(runtime); Ssl::ServerSslSocketFactory server_ssl_socket_factory(std::move(server_cfg), manager, stats_store, std::vector{}); - Event::DispatcherImpl dispatcher; Network::TcpListenSocket socket(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true); Network::MockListenerCallbacks callbacks; Network::MockConnectionHandler connection_handler; - Network::ListenerPtr listener = dispatcher.createListener(socket, callbacks, true, false); + Network::ListenerPtr listener = dispatcher_->createListener(socket, callbacks, true, false); - Network::ClientConnectionPtr client_connection = dispatcher.createClientConnection( + Network::ClientConnectionPtr client_connection = dispatcher_->createClientConnection( socket.localAddress(), Network::Address::InstanceConstSharedPtr(), Network::Test::createRawBufferSocket(), nullptr); client_connection->connect(); @@ -2259,7 +2265,7 @@ TEST_P(SslSocketTest, SslError) { Network::MockConnectionCallbacks server_connection_callbacks; EXPECT_CALL(callbacks, onAccept_(_, _)) .WillOnce(Invoke([&](Network::ConnectionSocketPtr& socket, bool) -> void { - Network::ConnectionPtr new_connection = dispatcher.createServerConnection( + Network::ConnectionPtr new_connection = dispatcher_->createServerConnection( std::move(socket), server_ssl_socket_factory.createTransportSocket()); callbacks.onNewConnection(std::move(new_connection)); })); @@ -2272,10 +2278,10 @@ TEST_P(SslSocketTest, SslError) { EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { client_connection->close(Network::ConnectionCloseType::NoFlush); - dispatcher.exit(); + dispatcher_->exit(); })); - dispatcher.run(Event::Dispatcher::RunType::Block); + dispatcher_->run(Event::Dispatcher::RunType::Block); EXPECT_EQ(1UL, stats_store.counter("ssl.connection_error").value()); } @@ -2574,13 +2580,12 @@ TEST_P(SslSocketTest, GetRequestedServerName) { GetParam()); } -class SslReadBufferLimitTest : public SslCertsTest, - public testing::WithParamInterface { +class SslReadBufferLimitTest : public SslSocketTest { public: void initialize() { server_ctx_loader_ = TestEnvironment::jsonLoadFromString(server_ctx_json_); auto server_cfg = - std::make_unique(*server_ctx_loader_, secret_manager_); + std::make_unique(*server_ctx_loader_, factory_context_); manager_.reset(new ContextManagerImpl(runtime_)); server_ssl_socket_factory_.reset(new ServerSslSocketFactory( std::move(server_cfg), *manager_, stats_store_, std::vector{})); @@ -2589,7 +2594,7 @@ class SslReadBufferLimitTest : public SslCertsTest, client_ctx_loader_ = TestEnvironment::jsonLoadFromString(client_ctx_json_); auto client_cfg = - std::make_unique(*client_ctx_loader_, secret_manager_); + std::make_unique(*client_ctx_loader_, factory_context_); client_ssl_socket_factory_.reset( new ClientSslSocketFactory(std::move(client_cfg), *manager_, stats_store_)); @@ -2668,7 +2673,8 @@ class SslReadBufferLimitTest : public SslCertsTest, void singleWriteTest(uint32_t read_buffer_limit, uint32_t bytes_to_write) { MockWatermarkBuffer* client_write_buffer = nullptr; MockBufferFactory* factory = new StrictMock; - dispatcher_.reset(new Event::DispatcherImpl(Buffer::WatermarkFactoryPtr{factory})); + dispatcher_.reset( + new Event::DispatcherImpl(test_time_.timeSource(), Buffer::WatermarkFactoryPtr{factory})); // By default, expect 4 buffers to be created - the client and server read and write buffers. EXPECT_CALL(*factory, create_(_, _)) @@ -2736,7 +2742,6 @@ class SslReadBufferLimitTest : public SslCertsTest, } Stats::IsolatedStoreImpl stats_store_; - Event::DispatcherPtr dispatcher_{new Event::DispatcherImpl}; Network::TcpListenSocket socket_{Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true}; Network::MockListenerCallbacks listener_callbacks_; diff --git a/test/common/thread_local/BUILD b/test/common/thread_local/BUILD index f731091ec689..26049fc8c07c 100644 --- a/test/common/thread_local/BUILD +++ b/test/common/thread_local/BUILD @@ -15,5 +15,6 @@ envoy_cc_test( "//source/common/event:dispatcher_lib", "//source/common/thread_local:thread_local_lib", "//test/mocks/event:event_mocks", + "//test/test_common:test_time_lib", ], ) diff --git a/test/common/thread_local/thread_local_impl_test.cc b/test/common/thread_local/thread_local_impl_test.cc index 0eb123a9ca5d..cc523d79b2e6 100644 --- a/test/common/thread_local/thread_local_impl_test.cc +++ b/test/common/thread_local/thread_local_impl_test.cc @@ -2,6 +2,7 @@ #include "common/thread_local/thread_local_impl.h" #include "test/mocks/event/mocks.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" @@ -113,8 +114,10 @@ TEST_F(ThreadLocalInstanceImplTest, RunOnAllThreads) { // Validate ThreadLocal::InstanceImpl's dispatcher() behavior. TEST(ThreadLocalInstanceImplDispatcherTest, Dispatcher) { InstanceImpl tls; - Event::DispatcherImpl main_dispatcher; - Event::DispatcherImpl thread_dispatcher; + + DangerousDeprecatedTestTime test_time; + Event::DispatcherImpl main_dispatcher(test_time.timeSource()); + Event::DispatcherImpl thread_dispatcher(test_time.timeSource()); tls.registerThread(main_dispatcher, true); tls.registerThread(thread_dispatcher, false); diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index d2aca58d3feb..3630c0a1609d 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -141,12 +141,9 @@ class TestClusterManagerImpl : public ClusterManagerImpl { Runtime::RandomGenerator& random, const LocalInfo::LocalInfo& local_info, AccessLog::AccessLogManager& log_manager, Event::Dispatcher& main_thread_dispatcher, Server::Admin& admin, - SystemTimeSource& system_time_source, - MonotonicTimeSource& monotonic_time_source, MockLocalClusterUpdate& local_cluster_update) : ClusterManagerImpl(bootstrap, factory, stats, tls, runtime, random, local_info, log_manager, - main_thread_dispatcher, admin, system_time_source, - monotonic_time_source), + main_thread_dispatcher, admin), local_cluster_update_(local_cluster_update) {} protected: @@ -166,11 +163,14 @@ envoy::config::bootstrap::v2::Bootstrap parseBootstrapFromV2Yaml(const std::stri class ClusterManagerImplTest : public testing::Test { public: + ClusterManagerImplTest() : time_source_(system_time_source_, monotonic_time_source_) { + factory_.dispatcher_.setTimeSource(time_source_); + } + void create(const envoy::config::bootstrap::v2::Bootstrap& bootstrap) { cluster_manager_.reset(new ClusterManagerImpl( bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.random_, - factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, system_time_source_, - monotonic_time_source_)); + factory_.local_info_, log_manager_, factory_.dispatcher_, admin_)); } void createWithLocalClusterUpdate(const bool enable_merge_window = true) { @@ -202,8 +202,7 @@ class ClusterManagerImplTest : public testing::Test { cluster_manager_.reset(new TestClusterManagerImpl( bootstrap, factory_, factory_.stats_, factory_.tls_, factory_.runtime_, factory_.random_, - factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, system_time_source_, - monotonic_time_source_, local_cluster_update_)); + factory_.local_info_, log_manager_, factory_.dispatcher_, admin_, local_cluster_update_)); } void checkStats(uint64_t added, uint64_t modified, uint64_t removed, uint64_t active, @@ -244,6 +243,7 @@ class ClusterManagerImplTest : public testing::Test { NiceMock system_time_source_; NiceMock monotonic_time_source_; MockLocalClusterUpdate local_cluster_update_; + TimeSource time_source_; }; envoy::config::bootstrap::v2::Bootstrap parseBootstrapFromJson(const std::string& json_string) { diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 1975164850bf..d6aaaaf0b0ce 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -1022,6 +1022,8 @@ TEST_F(HttpHealthCheckerImplTest, HttpFail) { Host::HealthFlag::FAILED_ACTIVE_HC)); EXPECT_FALSE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthy()); + EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), + Host::ActiveHealthFailureType::UNHEALTHY); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->callback_(); @@ -1107,6 +1109,9 @@ TEST_F(HttpHealthCheckerImplTest, Timeout) { EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( Host::HealthFlag::FAILED_ACTIVE_HC)); EXPECT_FALSE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthy()); + + EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), + Host::ActiveHealthFailureType::TIMEOUT); } TEST_F(HttpHealthCheckerImplTest, DynamicAddAndRemove) { @@ -1822,6 +1827,33 @@ TEST_F(TcpHealthCheckerImplTest, DataWithoutReusingConnection) { EXPECT_EQ(0UL, cluster_->info_->stats_store_.counter("health_check.failure").value()); } +// Tests an unsuccessful healthcheck, where the endpoint sends wrong data +TEST_F(TcpHealthCheckerImplTest, WrongData) { + InSequence s; + + setupDataDontReuseConnection(); + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; + expectSessionCreate(); + expectClientCreate(); + EXPECT_CALL(*connection_, write(_, _)).Times(1); + EXPECT_CALL(*timeout_timer_, enableTimer(_)); + health_checker_->start(); + + connection_->raiseEvent(Network::ConnectionEvent::Connected); + + // Not the expected response + Buffer::OwnedImpl response; + add_uint8(response, 3); + read_filter_->onData(response, false); + + // These are the expected metric results after testing. + EXPECT_EQ(0UL, cluster_->info_->stats_store_.counter("health_check.success").value()); + // TODO(lilika): This should indicate a failure + EXPECT_EQ(0UL, cluster_->info_->stats_store_.counter("health_check.failure").value()); + EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), + Host::ActiveHealthFailureType::UNHEALTHY); +} TEST_F(TcpHealthCheckerImplTest, Timeout) { InSequence s; @@ -1847,6 +1879,8 @@ TEST_F(TcpHealthCheckerImplTest, Timeout) { EXPECT_CALL(*timeout_timer_, disableTimer()); EXPECT_CALL(*interval_timer_, enableTimer(_)); timeout_timer_->callback_(); + EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), + Host::ActiveHealthFailureType::TIMEOUT); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthy()); expectClientCreate(); diff --git a/test/common/upstream/load_balancer_benchmark.cc b/test/common/upstream/load_balancer_benchmark.cc index a0d0e148fdb2..bf07fbf734b3 100644 --- a/test/common/upstream/load_balancer_benchmark.cc +++ b/test/common/upstream/load_balancer_benchmark.cc @@ -98,13 +98,10 @@ BENCHMARK(BM_MaglevLoadBalancerBuildTable) ->Arg(500) ->Unit(benchmark::kMillisecond); -class TestLoadBalancerContext : public LoadBalancerContext { +class TestLoadBalancerContext : public LoadBalancerContextBase { public: // Upstream::LoadBalancerContext absl::optional computeHashKey() override { return hash_key_; } - const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return nullptr; } - const Network::Connection* downstreamConnection() const override { return nullptr; } - const Http::HeaderMap* downstreamHeaders() const override { return nullptr; } absl::optional hash_key_; }; diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc index cf447c2f7a8b..19ea7e8e3e0f 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/common/upstream/load_balancer_impl_test.cc @@ -18,6 +18,7 @@ using testing::ElementsAre; using testing::NiceMock; using testing::Return; +using testing::ReturnRef; namespace Envoy { namespace Upstream { @@ -48,6 +49,10 @@ class TestLb : public LoadBalancerBase { : LoadBalancerBase(priority_set, stats, runtime, random, common_config) {} using LoadBalancerBase::chooseHostSet; using LoadBalancerBase::percentageLoad; + + HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; + } }; class LoadBalancerBaseTest : public LoadBalancerTestBase { @@ -82,14 +87,17 @@ INSTANTIATE_TEST_CASE_P(PrimaryOrFailover, LoadBalancerBaseTest, ::testing::Valu // Basic test of host set selection. TEST_P(LoadBalancerBaseTest, PrioritySelection) { + NiceMock context; updateHostSet(host_set_, 1 /* num_hosts */, 0 /* num_healthy_hosts */); updateHostSet(failover_host_set_, 1, 0); + PriorityLoad priority_load = {100, 0}; + EXPECT_CALL(context, determinePriorityLoad(_, _)).WillRepeatedly(ReturnRef(priority_load)); // With both the primary and failover hosts unhealthy, we should select an // unhealthy primary host. EXPECT_EQ(100, lb_.percentageLoad(0)); EXPECT_EQ(0, lb_.percentageLoad(1)); - EXPECT_EQ(&host_set_, &lb_.chooseHostSet()); + EXPECT_EQ(&host_set_, &lb_.chooseHostSet(&context)); // Update the priority set with a new priority level P=2 and ensure the host // is chosen @@ -98,7 +106,8 @@ TEST_P(LoadBalancerBaseTest, PrioritySelection) { EXPECT_EQ(0, lb_.percentageLoad(0)); EXPECT_EQ(0, lb_.percentageLoad(1)); EXPECT_EQ(100, lb_.percentageLoad(2)); - EXPECT_EQ(&tertiary_host_set_, &lb_.chooseHostSet()); + priority_load = {0, 0, 100}; + EXPECT_EQ(&tertiary_host_set_, &lb_.chooseHostSet(&context)); // Now add a healthy host in P=0 and make sure it is immediately selected. updateHostSet(host_set_, 1 /* num_hosts */, 1 /* num_healthy_hosts */); @@ -106,13 +115,30 @@ TEST_P(LoadBalancerBaseTest, PrioritySelection) { host_set_.runCallbacks({}, {}); EXPECT_EQ(100, lb_.percentageLoad(0)); EXPECT_EQ(0, lb_.percentageLoad(2)); - EXPECT_EQ(&host_set_, &lb_.chooseHostSet()); + priority_load = {100, 0, 0}; + EXPECT_EQ(&host_set_, &lb_.chooseHostSet(&context)); // Remove the healthy host and ensure we fail back over to tertiary_host_set_ updateHostSet(host_set_, 1 /* num_hosts */, 0 /* num_healthy_hosts */); EXPECT_EQ(0, lb_.percentageLoad(0)); EXPECT_EQ(100, lb_.percentageLoad(2)); - EXPECT_EQ(&tertiary_host_set_, &lb_.chooseHostSet()); + priority_load = {0, 0, 100}; + EXPECT_EQ(&tertiary_host_set_, &lb_.chooseHostSet(&context)); +} + +// Test of host set selection with priority filter +TEST_P(LoadBalancerBaseTest, PrioritySelectionWithFilter) { + NiceMock context; + + PriorityLoad priority_load = {0, 100}; + // return a filter that excludes priority 0 + EXPECT_CALL(context, determinePriorityLoad(_, _)).WillOnce(ReturnRef(priority_load)); + + updateHostSet(host_set_, 1 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 1, 1); + + // Since we've excluded P0, we should pick the failover host set + EXPECT_EQ(failover_host_set_.priority(), lb_.chooseHostSet(&context).priority()); } TEST_P(LoadBalancerBaseTest, OverProvisioningFactor) { @@ -496,6 +522,50 @@ TEST_P(RoundRobinLoadBalancerTest, MaxUnhealthyPanic) { EXPECT_EQ(3UL, stats_.lb_healthy_panic_.value()); } +// Test of host set selection with host filter +TEST_P(RoundRobinLoadBalancerTest, HostSelectionWithFilter) { + NiceMock context; + + HostVectorSharedPtr hosts(new HostVector( + {makeTestHost(info_, "tcp://127.0.0.1:80"), makeTestHost(info_, "tcp://127.0.0.1:81")})); + HostsPerLocalitySharedPtr hosts_per_locality = makeHostsPerLocality( + {{makeTestHost(info_, "tcp://127.0.0.1:80")}, {makeTestHost(info_, "tcp://127.0.0.1:81")}}); + + hostSet().hosts_ = *hosts; + hostSet().healthy_hosts_ = *hosts; + hostSet().healthy_hosts_per_locality_ = hosts_per_locality; + + init(false); + + // return a predicate that only accepts the first host + EXPECT_CALL(context, shouldSelectAnotherHost(_)) + .WillRepeatedly(Invoke([&](const Host& host) -> bool { + return host.address()->asString() != hostSet().hosts_[0]->address()->asString(); + })); + PriorityLoad priority_load; + + if (GetParam()) { + priority_load = {100, 0}; + } else { + priority_load = {0, 100}; + } + EXPECT_CALL(context, determinePriorityLoad(_, _)).WillRepeatedly(ReturnRef(priority_load)); + EXPECT_CALL(context, hostSelectionRetryCount()).WillRepeatedly(Return(2)); + + // Calling chooseHost multiple times always returns host one, since the filter will reject + // the other host. + EXPECT_EQ(hostSet().hosts_[0], lb_->chooseHost(&context)); + EXPECT_EQ(hostSet().hosts_[0], lb_->chooseHost(&context)); + EXPECT_EQ(hostSet().hosts_[0], lb_->chooseHost(&context)); + + // By setting the retry counter to zero, we effectively disable the filter. + EXPECT_CALL(context, hostSelectionRetryCount()).WillRepeatedly(Return(0)); + + EXPECT_EQ(hostSet().hosts_[1], lb_->chooseHost(&context)); + EXPECT_EQ(hostSet().hosts_[0], lb_->chooseHost(&context)); + EXPECT_EQ(hostSet().hosts_[1], lb_->chooseHost(&context)); +} + TEST_P(RoundRobinLoadBalancerTest, ZoneAwareSmallCluster) { HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:80"), makeTestHost(info_, "tcp://127.0.0.1:81"), diff --git a/test/common/upstream/logical_dns_cluster_test.cc b/test/common/upstream/logical_dns_cluster_test.cc index 7ae43186c52b..44bf2ebe260f 100644 --- a/test/common/upstream/logical_dns_cluster_test.cc +++ b/test/common/upstream/logical_dns_cluster_test.cc @@ -82,7 +82,7 @@ class LogicalDnsClusterTest : public testing::Test { } void testBasicSetup(const std::string& config, const std::string& expected_address, - ConfigType config_type = ConfigType::V2_YAML) { + const uint32_t expected_port, ConfigType config_type = ConfigType::V2_YAML) { expectResolve(Network::DnsLookupFamily::V4Only, expected_address); if (config_type == ConfigType::V1_JSON) { setupFromV1Json(config); @@ -106,6 +106,12 @@ class LogicalDnsClusterTest : public testing::Test { cluster_->prioritySet().hostSetsPerPriority()[0]->healthyHosts()[0]); HostSharedPtr logical_host = cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]; + // Regression test for issue #3908. Make sure we do not get "0.0.0.0:0" as the logical host's + // health check address. + EXPECT_NE("0.0.0.0:0", logical_host->healthCheckAddress()->asString()); + EXPECT_EQ(fmt::format("127.0.0.1:{}", expected_port), + logical_host->healthCheckAddress()->asString()); + EXPECT_CALL(dispatcher_, createClientConnection_( PointeesEq(Network::Utility::resolveUrl("tcp://127.0.0.1:443")), _, _, _)) @@ -120,6 +126,10 @@ class LogicalDnsClusterTest : public testing::Test { EXPECT_CALL(*resolve_timer_, enableTimer(_)); dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2", "127.0.0.3"})); + EXPECT_NE("0.0.0.0:0", logical_host->healthCheckAddress()->asString()); + EXPECT_EQ(fmt::format("127.0.0.1:{}", expected_port), + logical_host->healthCheckAddress()->asString()); + EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); EXPECT_CALL(dispatcher_, createClientConnection_( @@ -148,6 +158,10 @@ class LogicalDnsClusterTest : public testing::Test { EXPECT_CALL(*resolve_timer_, enableTimer(_)); dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3", "127.0.0.1", "127.0.0.2"})); + EXPECT_NE("0.0.0.0:0", logical_host->healthCheckAddress()->asString()); + EXPECT_EQ(fmt::format("127.0.0.3:{}", expected_port), + logical_host->healthCheckAddress()->asString()); + EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); EXPECT_CALL(dispatcher_, createClientConnection_( @@ -393,9 +407,10 @@ TEST_F(LogicalDnsClusterTest, Basic) { port_value: 8000 )EOF"; - testBasicSetup(json, "foo.bar.com", ConfigType::V1_JSON); - testBasicSetup(basic_yaml_hosts, "foo.bar.com"); - testBasicSetup(basic_yaml_load_assignment, "foo.bar.com"); + testBasicSetup(json, "foo.bar.com", 443, ConfigType::V1_JSON); + testBasicSetup(basic_yaml_hosts, "foo.bar.com", 443); + // Expect to override the health check address port value. + testBasicSetup(basic_yaml_load_assignment, "foo.bar.com", 8000); } } // namespace Upstream diff --git a/test/common/upstream/maglev_lb_test.cc b/test/common/upstream/maglev_lb_test.cc index 68fb21827fc3..fa7f974898b5 100644 --- a/test/common/upstream/maglev_lb_test.cc +++ b/test/common/upstream/maglev_lb_test.cc @@ -6,15 +6,12 @@ namespace Envoy { namespace Upstream { -class TestLoadBalancerContext : public LoadBalancerContext { +class TestLoadBalancerContext : public LoadBalancerContextBase { public: TestLoadBalancerContext(uint64_t hash_key) : hash_key_(hash_key) {} // Upstream::LoadBalancerContext absl::optional computeHashKey() override { return hash_key_; } - const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return nullptr; } - const Network::Connection* downstreamConnection() const override { return nullptr; } - const Http::HeaderMap* downstreamHeaders() const override { return nullptr; } absl::optional hash_key_; }; diff --git a/test/common/upstream/original_dst_cluster_test.cc b/test/common/upstream/original_dst_cluster_test.cc index da1d163776f7..8659d3d10494 100644 --- a/test/common/upstream/original_dst_cluster_test.cc +++ b/test/common/upstream/original_dst_cluster_test.cc @@ -36,7 +36,7 @@ namespace Envoy { namespace Upstream { namespace OriginalDstClusterTest { -class TestLoadBalancerContext : public LoadBalancerContext { +class TestLoadBalancerContext : public LoadBalancerContextBase { public: TestLoadBalancerContext(const Network::Connection* connection) : connection_(connection) {} TestLoadBalancerContext(const Network::Connection* connection, const std::string& key, @@ -48,8 +48,8 @@ class TestLoadBalancerContext : public LoadBalancerContext { // Upstream::LoadBalancerContext absl::optional computeHashKey() override { return 0; } const Network::Connection* downstreamConnection() const override { return connection_; } - const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return nullptr; } const Http::HeaderMap* downstreamHeaders() const override { return downstream_headers_.get(); } + absl::optional hash_key_; const Network::Connection* connection_; Http::HeaderMapPtr downstream_headers_; diff --git a/test/common/upstream/ring_hash_lb_test.cc b/test/common/upstream/ring_hash_lb_test.cc index c26d1b307def..fd852caa2b58 100644 --- a/test/common/upstream/ring_hash_lb_test.cc +++ b/test/common/upstream/ring_hash_lb_test.cc @@ -22,15 +22,12 @@ using testing::Return; namespace Envoy { namespace Upstream { -class TestLoadBalancerContext : public LoadBalancerContext { +class TestLoadBalancerContext : public LoadBalancerContextBase { public: TestLoadBalancerContext(uint64_t hash_key) : hash_key_(hash_key) {} // Upstream::LoadBalancerContext absl::optional computeHashKey() override { return hash_key_; } - const Router::MetadataMatchCriteria* metadataMatchCriteria() override { return nullptr; } - const Network::Connection* downstreamConnection() const override { return nullptr; } - const Http::HeaderMap* downstreamHeaders() const override { return nullptr; } absl::optional hash_key_; }; diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc index b17de82617d7..4814ffb3e1fe 100644 --- a/test/common/upstream/subset_lb_test.cc +++ b/test/common/upstream/subset_lb_test.cc @@ -87,7 +87,7 @@ class TestMetadataMatchCriteria : public Router::MetadataMatchCriteria { std::vector matches_; }; -class TestLoadBalancerContext : public LoadBalancerContext { +class TestLoadBalancerContext : public LoadBalancerContextBase { public: TestLoadBalancerContext( std::initializer_list::value_type> metadata_matches) diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index 22c84839c10d..2ead1479033a 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -84,7 +84,7 @@ class ConfigTest { NiceMock component_factory_; NiceMock worker_factory_; Server::ListenerManagerImpl listener_manager_{server_, component_factory_, worker_factory_, - ProdSystemTimeSource::instance_}; + server_.timeSource()}; Runtime::RandomGeneratorImpl random_; NiceMock os_sys_calls_; TestThreadsafeSingletonInjector os_calls{&os_sys_calls_}; diff --git a/test/extensions/access_loggers/http_grpc/grpc_access_log_impl_test.cc b/test/extensions/access_loggers/http_grpc/grpc_access_log_impl_test.cc index 95cffa3e29a0..b3f7a0da39a3 100644 --- a/test/extensions/access_loggers/http_grpc/grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/http_grpc/grpc_access_log_impl_test.cc @@ -434,6 +434,7 @@ TEST(responseFlagsToAccessLogResponseFlagsTest, All) { common_access_log_expected.mutable_response_flags()->mutable_unauthorized_details()->set_reason( envoy::data::accesslog::v2::ResponseFlags_Unauthorized_Reason:: ResponseFlags_Unauthorized_Reason_EXTERNAL_SERVICE); + common_access_log_expected.mutable_response_flags()->set_rate_limit_service_error(true); EXPECT_EQ(common_access_log_expected.DebugString(), common_access_log.DebugString()); } diff --git a/test/extensions/filters/http/grpc_json_transcoder/BUILD b/test/extensions/filters/http/grpc_json_transcoder/BUILD index f0744a54b712..65c913db317a 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/BUILD +++ b/test/extensions/filters/http/grpc_json_transcoder/BUILD @@ -24,7 +24,7 @@ envoy_extension_cc_test( "//source/extensions/filters/http/grpc_json_transcoder:json_transcoder_filter_lib", "//test/mocks/http:http_mocks", "//test/mocks/upstream:upstream_mocks", - "//test/proto:bookstore_proto", + "//test/proto:bookstore_proto_cc", "//test/test_common:environment_lib", "//test/test_common:utility_lib", ], @@ -54,7 +54,7 @@ envoy_extension_cc_test( "//source/common/http:header_map_lib", "//source/extensions/filters/http/grpc_json_transcoder:config", "//test/integration:http_integration_lib", - "//test/proto:bookstore_proto", + "//test/proto:bookstore_proto_cc", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc index eb3f8803de90..536d373f21aa 100644 --- a/test/extensions/filters/http/jwt_authn/filter_integration_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_integration_test.cc @@ -87,6 +87,27 @@ TEST_P(LocalJwksIntegrationTest, ExpiredToken) { EXPECT_STREQ("401", response->headers().Status()->value().c_str()); } +TEST_P(LocalJwksIntegrationTest, ExpiredTokenHeadReply) { + config_helper_.addFilter(getFilterConfig(true)); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeHeaderOnlyRequest(Http::TestHeaderMapImpl{ + {":method", "HEAD"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"Authorization", "Bearer " + std::string(ExpiredToken)}, + }); + + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("401", response->headers().Status()->value().c_str()); + EXPECT_STRNE("0", response->headers().ContentLength()->value().c_str()); + EXPECT_STREQ("", response->body().c_str()); +} + // The test case with a fake upstream for remote Jwks server. class RemoteJwksIntegrationTest : public HttpProtocolIntegrationTest { public: diff --git a/test/extensions/filters/http/ratelimit/ratelimit_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_test.cc index 8d49e933ac4f..ed1c6cfb7ef0 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_test.cc @@ -46,10 +46,10 @@ class HttpRateLimitFilterTest : public testing::Test { .WillByDefault(Return(true)); } - void SetUpTest(const std::string json) { - Json::ObjectSharedPtr json_config = Json::Factory::loadFromString(json); + void SetUpTest(const std::string& yaml) { envoy::config::filter::http::rate_limit::v2::RateLimit proto_config{}; - Config::FilterJson::translateHttpRateLimitFilter(*json_config, proto_config); + MessageUtil::loadFromYaml(yaml, proto_config); + config_.reset(new FilterConfig(proto_config, local_info_, stats_store_, runtime_, cm_)); client_ = new RateLimit::MockClient(); @@ -64,10 +64,13 @@ class HttpRateLimitFilterTest : public testing::Test { .emplace_back(vh_rate_limit_); } + const std::string fail_close_config_ = R"EOF( + domain: foo + failure_mode_deny: true + )EOF"; + const std::string filter_config_ = R"EOF( - { - "domain": "foo" - } + domain: foo )EOF"; FilterConfigSharedPtr config_; @@ -328,6 +331,37 @@ TEST_F(HttpRateLimitFilterTest, ErrorResponse) { EXPECT_EQ( 1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_.counter("ratelimit.error").value()); + EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("ratelimit.failure_mode_allowed") + .value()); +} + +TEST_F(HttpRateLimitFilterTest, ErrorResponseWithFailureModeAllowOff) { + SetUpTest(fail_close_config_); + InSequence s; + + EXPECT_CALL(route_rate_limit_, populateDescriptors(_, _, _, _, _)) + .WillOnce(SetArgReferee<1>(descriptor_)); + EXPECT_CALL(*client_, limit(_, _, _, _)) + .WillOnce(WithArgs<0>(Invoke([&](RateLimit::RequestCallbacks& callbacks) -> void { + request_callbacks_ = &callbacks; + }))); + + EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, + filter_->decodeHeaders(request_headers_, false)); + + request_callbacks_->complete(RateLimit::LimitStatus::Error, nullptr); + + EXPECT_CALL(filter_callbacks_.request_info_, + setResponseFlag(RequestInfo::ResponseFlag::RateLimitServiceError)) + .Times(0); + + EXPECT_EQ( + 1U, + cm_.thread_local_cluster_.cluster_.info_->stats_store_.counter("ratelimit.error").value()); + EXPECT_EQ(0U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("ratelimit.failure_mode_allowed") + .value()); } TEST_F(HttpRateLimitFilterTest, LimitResponse) { diff --git a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc index 9e766f7bc4f5..4f90220d13ac 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_integration_test.cc @@ -66,6 +66,29 @@ TEST_P(RBACIntegrationTest, Denied) { EXPECT_STREQ("403", response->headers().Status()->value().c_str()); } +TEST_P(RBACIntegrationTest, DeniedHeadReply) { + config_helper_.addFilter(RBAC_CONFIG); + initialize(); + + codec_client_ = makeHttpConnection(lookupPort("http")); + + auto response = codec_client_->makeRequestWithBody( + Http::TestHeaderMapImpl{ + {":method", "HEAD"}, + {":path", "/"}, + {":scheme", "http"}, + {":authority", "host"}, + {"x-forwarded-for", "10.0.0.1"}, + }, + 1024); + response->waitForEndStream(); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("403", response->headers().Status()->value().c_str()); + ASSERT_TRUE(response->headers().ContentLength()); + EXPECT_STRNE("0", response->headers().ContentLength()->value().c_str()); + EXPECT_STREQ("", response->body().c_str()); +} + TEST_P(RBACIntegrationTest, RouteOverride) { config_helper_.addConfigModifier( [](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& cfg) { diff --git a/test/extensions/filters/http/rbac/rbac_filter_test.cc b/test/extensions/filters/http/rbac/rbac_filter_test.cc index 0fbf392bec61..18f4686fab5d 100644 --- a/test/extensions/filters/http/rbac/rbac_filter_test.cc +++ b/test/extensions/filters/http/rbac/rbac_filter_test.cc @@ -54,13 +54,21 @@ class RoleBasedAccessControlFilterTest : public testing::Test { ON_CALL(connection_, localAddress()).WillByDefault(ReturnRef(address_)); } + void setMetadata() { + ON_CALL(req_info_, setDynamicMetadata(HttpFilterNames::get().Rbac, _)) + .WillByDefault(Invoke([this](const std::string&, const ProtobufWkt::Struct& obj) { + req_info_.metadata_.mutable_filter_metadata()->insert( + Protobuf::MapPair( + HttpFilterNames::get().Rbac, obj)); + })); + } + NiceMock callbacks_; NiceMock connection_{}; NiceMock req_info_; Stats::IsolatedStoreImpl store_; RoleBasedAccessControlFilterConfigSharedPtr config_; - envoy::api::v2::core::Metadata metadata_; RoleBasedAccessControlFilter filter_; Network::Address::InstanceConstSharedPtr address_; Http::TestHeaderMapImpl headers_; @@ -80,6 +88,7 @@ TEST_F(RoleBasedAccessControlFilterTest, Allowed) { TEST_F(RoleBasedAccessControlFilterTest, Denied) { setDestinationPort(456); + setMetadata(); Http::TestHeaderMapImpl response_headers{ {":status", "403"}, @@ -92,6 +101,10 @@ TEST_F(RoleBasedAccessControlFilterTest, Denied) { EXPECT_EQ(Http::FilterHeadersStatus::StopIteration, filter_.decodeHeaders(headers_, true)); EXPECT_EQ(1U, config_->stats().denied_.value()); EXPECT_EQ(1U, config_->stats().shadow_allowed_.value()); + + auto filter_meta = req_info_.dynamicMetadata().filter_metadata().at(HttpFilterNames::get().Rbac); + EXPECT_EQ("200", filter_meta.fields().at("shadow_response_code").string_value()); + EXPECT_EQ("bar", filter_meta.fields().at("shadow_effective_policyID").string_value()); } TEST_F(RoleBasedAccessControlFilterTest, RouteLocalOverride) { diff --git a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc index fa0cd1ebd2b9..362a2e9c1d6c 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -22,6 +22,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" @@ -49,7 +50,8 @@ class ProxyProtocolTest : public testing::TestWithParam { public: ProxyProtocolTest() - : socket_(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true), + : dispatcher_(test_time_.timeSource()), + socket_(Network::Test::getCanonicalLoopbackAddress(GetParam()), nullptr, true), connection_handler_(new Server::ConnectionHandlerImpl(ENVOY_LOGGER(), dispatcher_)), name_("proxy"), filter_chain_(Network::Test::createEmptyFilterChainWithRawBufferSockets()) { @@ -142,6 +144,7 @@ class ProxyProtocolTest : public testing::TestWithParam { public: WildcardProxyProtocolTest() - : socket_(Network::Test::getAnyAddress(GetParam()), nullptr, true), + : dispatcher_(test_time_.timeSource()), + socket_(Network::Test::getAnyAddress(GetParam()), nullptr, true), local_dst_address_(Network::Utility::getAddressWithPort( *Network::Test::getCanonicalLoopbackAddress(GetParam()), socket_.localAddress()->ip()->port())), @@ -939,6 +943,7 @@ class WildcardProxyProtocolTest : public testing::TestWithParam runtime_; ConfigSharedPtr config_; @@ -97,6 +116,7 @@ TEST_F(RateLimitFilterTest, BadRatelimitConfig) { TEST_F(RateLimitFilterTest, OK) { InSequence s; + SetUpTest(filter_config_); EXPECT_CALL(*client_, limit(_, "foo", testing::ContainerEq(std::vector{ @@ -125,6 +145,7 @@ TEST_F(RateLimitFilterTest, OK) { TEST_F(RateLimitFilterTest, OverLimit) { InSequence s; + SetUpTest(filter_config_); EXPECT_CALL(*client_, limit(_, "foo", _, _)) .WillOnce(WithArgs<0>(Invoke([&](RateLimit::RequestCallbacks& callbacks) -> void { @@ -148,6 +169,7 @@ TEST_F(RateLimitFilterTest, OverLimit) { TEST_F(RateLimitFilterTest, OverLimitNotEnforcing) { InSequence s; + SetUpTest(filter_config_); EXPECT_CALL(*client_, limit(_, "foo", _, _)) .WillOnce(WithArgs<0>(Invoke([&](RateLimit::RequestCallbacks& callbacks) -> void { @@ -174,6 +196,7 @@ TEST_F(RateLimitFilterTest, OverLimitNotEnforcing) { TEST_F(RateLimitFilterTest, Error) { InSequence s; + SetUpTest(filter_config_); EXPECT_CALL(*client_, limit(_, "foo", _, _)) .WillOnce(WithArgs<0>(Invoke([&](RateLimit::RequestCallbacks& callbacks) -> void { @@ -194,10 +217,12 @@ TEST_F(RateLimitFilterTest, Error) { EXPECT_EQ(1U, stats_store_.counter("ratelimit.name.total").value()); EXPECT_EQ(1U, stats_store_.counter("ratelimit.name.error").value()); + EXPECT_EQ(1U, stats_store_.counter("ratelimit.name.failure_mode_allowed").value()); } TEST_F(RateLimitFilterTest, Disconnect) { InSequence s; + SetUpTest(filter_config_); EXPECT_CALL(*client_, limit(_, "foo", _, _)) .WillOnce(WithArgs<0>(Invoke([&](RateLimit::RequestCallbacks& callbacks) -> void { @@ -216,6 +241,7 @@ TEST_F(RateLimitFilterTest, Disconnect) { TEST_F(RateLimitFilterTest, ImmediateOK) { InSequence s; + SetUpTest(filter_config_); EXPECT_CALL(filter_callbacks_, continueReading()).Times(0); EXPECT_CALL(*client_, limit(_, "foo", _, _)) @@ -237,6 +263,7 @@ TEST_F(RateLimitFilterTest, ImmediateOK) { TEST_F(RateLimitFilterTest, RuntimeDisable) { InSequence s; + SetUpTest(filter_config_); EXPECT_CALL(runtime_.snapshot_, featureEnabled("ratelimit.tcp_filter_enabled", 100)) .WillOnce(Return(false)); @@ -247,6 +274,30 @@ TEST_F(RateLimitFilterTest, RuntimeDisable) { EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data, false)); } +TEST_F(RateLimitFilterTest, ErrorResponseWithFailureModeAllowOff) { + InSequence s; + SetUpTest(fail_close_config_); + + EXPECT_CALL(*client_, limit(_, "foo", _, _)) + .WillOnce(WithArgs<0>(Invoke([&](RateLimit::RequestCallbacks& callbacks) -> void { + request_callbacks_ = &callbacks; + }))); + + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onNewConnection()); + Buffer::OwnedImpl data("hello"); + EXPECT_EQ(Network::FilterStatus::StopIteration, filter_->onData(data, false)); + request_callbacks_->complete(RateLimit::LimitStatus::Error, nullptr); + + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onData(data, false)); + + EXPECT_CALL(*client_, cancel()).Times(0); + filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); + + EXPECT_EQ(1U, stats_store_.counter("ratelimit.name.total").value()); + EXPECT_EQ(1U, stats_store_.counter("ratelimit.name.error").value()); + EXPECT_EQ(0U, stats_store_.counter("ratelimit.name.failure_mode_allowed").value()); +} + } // namespace RateLimitFilter } // namespace NetworkFilters } // namespace Extensions diff --git a/test/extensions/stats_sinks/hystrix/hystrix_test.cc b/test/extensions/stats_sinks/hystrix/hystrix_test.cc index 81be09a24aed..7e0aff5a4169 100644 --- a/test/extensions/stats_sinks/hystrix/hystrix_test.cc +++ b/test/extensions/stats_sinks/hystrix/hystrix_test.cc @@ -4,6 +4,7 @@ #include "extensions/stat_sinks/hystrix/hystrix.h" +#include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/mocks/upstream/mocks.h" @@ -13,10 +14,12 @@ #include "gtest/gtest.h" using testing::_; +using testing::HasSubstr; using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Return; +using testing::ReturnRef; namespace Envoy { namespace Extensions { @@ -428,6 +431,42 @@ TEST_F(HystrixSinkTest, AddAndRemoveClusters) { // Check that old values of test_cluster2 were deleted. validateResults(cluster_message_map[cluster2_name_], 0, 0, 0, 0, 0, window_size_); } + +TEST_F(HystrixSinkTest, HystrixEventStreamHandler) { + InSequence s; + Buffer::OwnedImpl buffer = createClusterAndCallbacks(); + // Register callback to sink. + sink_->registerConnection(&callbacks_); + + // This value doesn't matter in handlerHystrixEventStream + absl::string_view path_and_query; + + Http::HeaderMapImpl response_headers; + + NiceMock admin_stream_mock; + NiceMock connection_mock; + + auto addr_instance_ = Envoy::Network::Utility::parseInternetAddress("2.3.4.5", 123, false); + + ON_CALL(admin_stream_mock, getDecoderFilterCallbacks()).WillByDefault(ReturnRef(callbacks_)); + ON_CALL(callbacks_, connection()).WillByDefault(Return(&connection_mock)); + ON_CALL(connection_mock, remoteAddress()).WillByDefault(ReturnRef(addr_instance_)); + + ASSERT_EQ( + sink_->handlerHystrixEventStream(path_and_query, response_headers, buffer, admin_stream_mock), + Http::Code::OK); + + // Check that response_headers has been set correctly + EXPECT_EQ(response_headers.ContentType()->value(), "text/event-stream"); + EXPECT_EQ(response_headers.CacheControl()->value(), "no-cache"); + EXPECT_EQ(response_headers.Connection()->value(), "close"); + EXPECT_EQ(response_headers.AccessControlAllowOrigin()->value(), "*"); + + std::string access_control_allow_headers = + std::string(response_headers.AccessControlAllowHeaders()->value().getStringView()); + EXPECT_THAT(access_control_allow_headers, HasSubstr("Accept")); +} + } // namespace Hystrix } // namespace StatSinks } // namespace Extensions diff --git a/test/extensions/tracers/zipkin/BUILD b/test/extensions/tracers/zipkin/BUILD index e6d81b9e8dee..61220ae4e468 100644 --- a/test/extensions/tracers/zipkin/BUILD +++ b/test/extensions/tracers/zipkin/BUILD @@ -39,6 +39,7 @@ envoy_extension_cc_test( "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/tracing:tracing_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/tracers/zipkin/span_buffer_test.cc b/test/extensions/tracers/zipkin/span_buffer_test.cc index bcb13fa67794..c151478c72fa 100644 --- a/test/extensions/tracers/zipkin/span_buffer_test.cc +++ b/test/extensions/tracers/zipkin/span_buffer_test.cc @@ -1,5 +1,7 @@ #include "extensions/tracers/zipkin/span_buffer.h" +#include "test/test_common/test_time.h" + #include "gtest/gtest.h" namespace Envoy { @@ -8,17 +10,18 @@ namespace Tracers { namespace Zipkin { TEST(ZipkinSpanBufferTest, defaultConstructorEndToEnd) { + DangerousDeprecatedTestTime test_time; SpanBuffer buffer; EXPECT_EQ(0ULL, buffer.pendingSpans()); EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - EXPECT_FALSE(buffer.addSpan(Span())); + EXPECT_FALSE(buffer.addSpan(Span(test_time.timeSource()))); buffer.allocateBuffer(2); EXPECT_EQ(0ULL, buffer.pendingSpans()); EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - buffer.addSpan(Span()); + buffer.addSpan(Span(test_time.timeSource())); EXPECT_EQ(1ULL, buffer.pendingSpans()); std::string expected_json_array_string = "[{" R"("traceId":"0000000000000000",)" @@ -33,8 +36,8 @@ TEST(ZipkinSpanBufferTest, defaultConstructorEndToEnd) { EXPECT_EQ(0ULL, buffer.pendingSpans()); EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - buffer.addSpan(Span()); - buffer.addSpan(Span()); + buffer.addSpan(Span(test_time.timeSource())); + buffer.addSpan(Span(test_time.timeSource())); expected_json_array_string = "[" "{" R"("traceId":"0000000000000000",)" @@ -60,12 +63,13 @@ TEST(ZipkinSpanBufferTest, defaultConstructorEndToEnd) { } TEST(ZipkinSpanBufferTest, sizeConstructorEndtoEnd) { + DangerousDeprecatedTestTime test_time; SpanBuffer buffer(2); EXPECT_EQ(0ULL, buffer.pendingSpans()); EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - buffer.addSpan(Span()); + buffer.addSpan(Span(test_time.timeSource())); EXPECT_EQ(1ULL, buffer.pendingSpans()); std::string expected_json_array_string = "[{" R"("traceId":"0000000000000000",)" @@ -80,8 +84,8 @@ TEST(ZipkinSpanBufferTest, sizeConstructorEndtoEnd) { EXPECT_EQ(0ULL, buffer.pendingSpans()); EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - buffer.addSpan(Span()); - buffer.addSpan(Span()); + buffer.addSpan(Span(test_time.timeSource())); + buffer.addSpan(Span(test_time.timeSource())); expected_json_array_string = "[" "{" R"("traceId":"0000000000000000",)" diff --git a/test/extensions/tracers/zipkin/tracer_test.cc b/test/extensions/tracers/zipkin/tracer_test.cc index 740b4370b45c..656189f27902 100644 --- a/test/extensions/tracers/zipkin/tracer_test.cc +++ b/test/extensions/tracers/zipkin/tracer_test.cc @@ -10,6 +10,7 @@ #include "test/mocks/common.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/tracing/mocks.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -34,11 +35,19 @@ class TestReporterImpl : public Reporter { std::vector reported_spans_; }; -TEST(ZipkinTracerTest, spanCreation) { +class ZipkinTracerTest : public testing::Test { +protected: + ZipkinTracerTest() : time_source_(test_time_.timeSource()) {} + + DangerousDeprecatedTestTime test_time_; + TimeSource time_source_; +}; + +TEST_F(ZipkinTracerTest, spanCreation) { Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressAndPort("127.0.0.1:9000"); NiceMock random_generator; - Tracer tracer("my_service_name", addr, random_generator, false); + Tracer tracer("my_service_name", addr, random_generator, false, time_source_); NiceMock mock_start_time; SystemTime timestamp = mock_start_time.currentTime(); @@ -175,7 +184,7 @@ TEST(ZipkinTracerTest, spanCreation) { // ============== ON_CALL(config, operationName()).WillByDefault(Return(Tracing::OperationName::Ingress)); - const uint generated_parent_id = Util::generateRandom64(); + const uint generated_parent_id = Util::generateRandom64(test_time_.timeSource()); SpanContext modified_root_span_context(root_span_context.trace_id_high(), root_span_context.trace_id(), root_span_context.id(), generated_parent_id, root_span_context.sampled()); @@ -217,11 +226,11 @@ TEST(ZipkinTracerTest, spanCreation) { EXPECT_FALSE(new_shared_context_span->isSetDuration()); } -TEST(ZipkinTracerTest, finishSpan) { +TEST_F(ZipkinTracerTest, finishSpan) { Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressAndPort("127.0.0.1:9000"); NiceMock random_generator; - Tracer tracer("my_service_name", addr, random_generator, false); + Tracer tracer("my_service_name", addr, random_generator, false, test_time_.timeSource()); NiceMock mock_start_time; SystemTime timestamp = mock_start_time.currentTime(); @@ -301,11 +310,11 @@ TEST(ZipkinTracerTest, finishSpan) { EXPECT_EQ("my_service_name", endpoint.serviceName()); } -TEST(ZipkinTracerTest, finishNotSampledSpan) { +TEST_F(ZipkinTracerTest, finishNotSampledSpan) { Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressAndPort("127.0.0.1:9000"); NiceMock random_generator; - Tracer tracer("my_service_name", addr, random_generator, false); + Tracer tracer("my_service_name", addr, random_generator, false, time_source_); NiceMock mock_start_time; SystemTime timestamp = mock_start_time.currentTime(); @@ -330,11 +339,11 @@ TEST(ZipkinTracerTest, finishNotSampledSpan) { EXPECT_EQ(0ULL, reporter_object->reportedSpans().size()); } -TEST(ZipkinTracerTest, SpanSampledPropagatedToChild) { +TEST_F(ZipkinTracerTest, SpanSampledPropagatedToChild) { Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressAndPort("127.0.0.1:9000"); NiceMock random_generator; - Tracer tracer("my_service_name", addr, random_generator, false); + Tracer tracer("my_service_name", addr, random_generator, false, time_source_); NiceMock mock_start_time; SystemTime timestamp = mock_start_time.currentTime(); @@ -359,11 +368,11 @@ TEST(ZipkinTracerTest, SpanSampledPropagatedToChild) { EXPECT_FALSE(child_span2->sampled()); } -TEST(ZipkinTracerTest, RootSpan128bitTraceId) { +TEST_F(ZipkinTracerTest, RootSpan128bitTraceId) { Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressAndPort("127.0.0.1:9000"); NiceMock random_generator; - Tracer tracer("my_service_name", addr, random_generator, true); + Tracer tracer("my_service_name", addr, random_generator, true, time_source_); NiceMock mock_start_time; SystemTime timestamp = mock_start_time.currentTime(); diff --git a/test/extensions/tracers/zipkin/util_test.cc b/test/extensions/tracers/zipkin/util_test.cc index 0b27f554b2df..0472b640ed09 100644 --- a/test/extensions/tracers/zipkin/util_test.cc +++ b/test/extensions/tracers/zipkin/util_test.cc @@ -1,5 +1,7 @@ #include "extensions/tracers/zipkin/util.h" +#include "test/test_common/test_time.h" + #include "gtest/gtest.h" namespace Envoy { @@ -8,7 +10,8 @@ namespace Tracers { namespace Zipkin { TEST(ZipkinUtilTest, utilTests) { - EXPECT_EQ(typeid(uint64_t).name(), typeid(Util::generateRandom64()).name()); + DangerousDeprecatedTestTime time; + EXPECT_EQ(typeid(uint64_t).name(), typeid(Util::generateRandom64(time.timeSource())).name()); // Test JSON merging diff --git a/test/extensions/tracers/zipkin/zipkin_core_types_test.cc b/test/extensions/tracers/zipkin/zipkin_core_types_test.cc index 1aa63e55e106..e9a569fee423 100644 --- a/test/extensions/tracers/zipkin/zipkin_core_types_test.cc +++ b/test/extensions/tracers/zipkin/zipkin_core_types_test.cc @@ -5,6 +5,8 @@ #include "extensions/tracers/zipkin/zipkin_core_constants.h" #include "extensions/tracers/zipkin/zipkin_core_types.h" +#include "test/test_common/test_time.h" + #include "gtest/gtest.h" namespace Envoy { @@ -86,8 +88,9 @@ TEST(ZipkinCoreTypesAnnotationTest, defaultConstructor) { EXPECT_EQ("", ann.value()); EXPECT_FALSE(ann.isSetEndpoint()); + DangerousDeprecatedTestTime test_time; uint64_t timestamp = std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + test_time.timeSource().systemTime().time_since_epoch()) .count(); ann.setTimestamp(timestamp); EXPECT_EQ(timestamp, ann.timestamp()); @@ -144,8 +147,9 @@ TEST(ZipkinCoreTypesAnnotationTest, customConstructor) { Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressAndPort("127.0.0.1:3306"); Endpoint ep(std::string("my_service"), addr); + DangerousDeprecatedTestTime test_time; uint64_t timestamp = std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + test_time.timeSource().systemTime().time_since_epoch()) .count(); Annotation ann(timestamp, ZipkinCoreConstants::get().CLIENT_SEND, ep); @@ -168,8 +172,9 @@ TEST(ZipkinCoreTypesAnnotationTest, copyConstructor) { Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressAndPort("127.0.0.1:3306"); Endpoint ep(std::string("my_service"), addr); + DangerousDeprecatedTestTime test_time; uint64_t timestamp = std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + test_time.timeSource().systemTime().time_since_epoch()) .count(); Annotation ann(timestamp, ZipkinCoreConstants::get().CLIENT_SEND, ep); Annotation ann2(ann); @@ -185,8 +190,9 @@ TEST(ZipkinCoreTypesAnnotationTest, assignmentOperator) { Network::Address::InstanceConstSharedPtr addr = Network::Utility::parseInternetAddressAndPort("127.0.0.1:3306"); Endpoint ep(std::string("my_service"), addr); + DangerousDeprecatedTestTime test_time; uint64_t timestamp = std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + test_time.timeSource().systemTime().time_since_epoch()) .count(); Annotation ann(timestamp, ZipkinCoreConstants::get().CLIENT_SEND, ep); Annotation ann2 = ann; @@ -283,7 +289,8 @@ TEST(ZipkinCoreTypesBinaryAnnotationTest, assignmentOperator) { } TEST(ZipkinCoreTypesSpanTest, defaultConstructor) { - Span span; + DangerousDeprecatedTestTime test_time; + Span span(test_time.timeSource()); EXPECT_EQ(0ULL, span.id()); EXPECT_EQ(0ULL, span.traceId()); @@ -303,40 +310,40 @@ TEST(ZipkinCoreTypesSpanTest, defaultConstructor) { R"("annotations":[],"binaryAnnotations":[]})", span.toJson()); - uint64_t id = Util::generateRandom64(); + uint64_t id = Util::generateRandom64(test_time.timeSource()); std::string id_hex = Hex::uint64ToHex(id); span.setId(id); EXPECT_EQ(id, span.id()); EXPECT_EQ(id_hex, span.idAsHexString()); - id = Util::generateRandom64(); + id = Util::generateRandom64(test_time.timeSource()); id_hex = Hex::uint64ToHex(id); span.setParentId(id); EXPECT_EQ(id, span.parentId()); EXPECT_EQ(id_hex, span.parentIdAsHexString()); EXPECT_TRUE(span.isSetParentId()); - id = Util::generateRandom64(); + id = Util::generateRandom64(test_time.timeSource()); id_hex = Hex::uint64ToHex(id); span.setTraceId(id); EXPECT_EQ(id, span.traceId()); EXPECT_EQ(id_hex, span.traceIdAsHexString()); - id = Util::generateRandom64(); + id = Util::generateRandom64(test_time.timeSource()); id_hex = Hex::uint64ToHex(id); span.setTraceIdHigh(id); EXPECT_EQ(id, span.traceIdHigh()); EXPECT_TRUE(span.isSetTraceIdHigh()); int64_t timestamp = std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + test_time.timeSource().systemTime().time_since_epoch()) .count(); span.setTimestamp(timestamp); EXPECT_EQ(timestamp, span.timestamp()); EXPECT_TRUE(span.isSetTimestamp()); int64_t start_time = std::chrono::duration_cast( - ProdMonotonicTimeSource::instance_.currentTime().time_since_epoch()) + test_time.timeSource().monotonicTime().time_since_epoch()) .count(); span.setStartTime(start_time); EXPECT_EQ(start_time, span.startTime()); @@ -483,15 +490,16 @@ TEST(ZipkinCoreTypesSpanTest, defaultConstructor) { } TEST(ZipkinCoreTypesSpanTest, copyConstructor) { - Span span; + DangerousDeprecatedTestTime test_time; + Span span(test_time.timeSource()); - uint64_t id = Util::generateRandom64(); + uint64_t id = Util::generateRandom64(test_time.timeSource()); std::string id_hex = Hex::uint64ToHex(id); span.setId(id); span.setParentId(id); span.setTraceId(id); int64_t timestamp = std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + test_time.timeSource().systemTime().time_since_epoch()) .count(); span.setTimestamp(timestamp); span.setDuration(3000LL); @@ -519,15 +527,16 @@ TEST(ZipkinCoreTypesSpanTest, copyConstructor) { } TEST(ZipkinCoreTypesSpanTest, assignmentOperator) { - Span span; + DangerousDeprecatedTestTime test_time; + Span span(test_time.timeSource()); - uint64_t id = Util::generateRandom64(); + uint64_t id = Util::generateRandom64(test_time.timeSource()); std::string id_hex = Hex::uint64ToHex(id); span.setId(id); span.setParentId(id); span.setTraceId(id); int64_t timestamp = std::chrono::duration_cast( - ProdSystemTimeSource::instance_.currentTime().time_since_epoch()) + test_time.timeSource().systemTime().time_since_epoch()) .count(); span.setTimestamp(timestamp); span.setDuration(3000LL); @@ -555,7 +564,8 @@ TEST(ZipkinCoreTypesSpanTest, assignmentOperator) { } TEST(ZipkinCoreTypesSpanTest, setTag) { - Span span; + DangerousDeprecatedTestTime test_time; + Span span(test_time.timeSource()); span.setTag("key1", "value1"); span.setTag("key2", "value2"); diff --git a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc index 818cca0d9c87..8aa4452fc30c 100644 --- a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc +++ b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc @@ -38,6 +38,8 @@ namespace Zipkin { class ZipkinDriverTest : public Test { public: + ZipkinDriverTest() : time_source_(test_time_.timeSource()) {} + void setup(Json::Object& config, bool init_timer) { ON_CALL(cm_, httpAsyncClientForCluster("fake_cluster")) .WillByDefault(ReturnRef(cm_.async_client_)); @@ -64,6 +66,12 @@ class ZipkinDriverTest : public Test { setup(*loader, true); } + // TODO(#4160): Currently time_source_ is initialized from DangerousDeprecatedTestTime, which uses + // real time, not mock-time. When that is switched to use mock-time intead, I think + // generateRandom64() may not be as random as we want, and we'll need to inject entropy + // appropriate for the test. + uint64_t generateRandom64() { return Util::generateRandom64(time_source_); } + const std::string operation_name_{"test"}; Http::TestHeaderMapImpl request_headers_{ {":authority", "api.lyft.com"}, {":path", "/"}, {":method", "GET"}, {"x-request-id", "foo"}}; @@ -80,6 +88,8 @@ class ZipkinDriverTest : public Test { NiceMock random_; NiceMock config_; + DangerousDeprecatedTestTime test_time_; + TimeSource& time_source_; }; TEST_F(ZipkinDriverTest, InitializeDriver) { @@ -277,8 +287,8 @@ TEST_F(ZipkinDriverTest, NoB3ContextSampledFalse) { TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleTrue) { setupValidDriver(); - request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(Util::generateRandom64())); - request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(Util::generateRandom64())); + request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(generateRandom64())); + request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(generateRandom64())); EXPECT_EQ(nullptr, request_headers_.XB3Sampled()); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, @@ -291,8 +301,8 @@ TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleTrue) { TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleFalse) { setupValidDriver(); - request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(Util::generateRandom64())); - request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(Util::generateRandom64())); + request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(generateRandom64())); + request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(generateRandom64())); EXPECT_EQ(nullptr, request_headers_.XB3Sampled()); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, @@ -369,8 +379,8 @@ TEST_F(ZipkinDriverTest, PropagateB3SampledWithTrue) { TEST_F(ZipkinDriverTest, PropagateB3SampleFalse) { setupValidDriver(); - request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(Util::generateRandom64())); - request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(Util::generateRandom64())); + request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(generateRandom64())); + request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(generateRandom64())); request_headers_.insertXB3Sampled().value(ZipkinCoreConstants::get().NOT_SAMPLED); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, @@ -405,9 +415,9 @@ TEST_F(ZipkinDriverTest, ZipkinSpanTest) { // Test setTag() with SR annotated span // ==== - const std::string trace_id = Hex::uint64ToHex(Util::generateRandom64()); - const std::string span_id = Hex::uint64ToHex(Util::generateRandom64()); - const std::string parent_id = Hex::uint64ToHex(Util::generateRandom64()); + const std::string trace_id = Hex::uint64ToHex(generateRandom64()); + const std::string span_id = Hex::uint64ToHex(generateRandom64()); + const std::string parent_id = Hex::uint64ToHex(generateRandom64()); const std::string context = trace_id + ";" + span_id + ";" + parent_id + ";" + ZipkinCoreConstants::get().CLIENT_SEND; @@ -445,9 +455,9 @@ TEST_F(ZipkinDriverTest, ZipkinSpanTest) { TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { setupValidDriver(); - const std::string trace_id = Hex::uint64ToHex(Util::generateRandom64()); - const std::string span_id = Hex::uint64ToHex(Util::generateRandom64()); - const std::string parent_id = Hex::uint64ToHex(Util::generateRandom64()); + const std::string trace_id = Hex::uint64ToHex(generateRandom64()); + const std::string span_id = Hex::uint64ToHex(generateRandom64()); + const std::string parent_id = Hex::uint64ToHex(generateRandom64()); request_headers_.insertXB3TraceId().value(trace_id); request_headers_.insertXB3SpanId().value(span_id); @@ -469,11 +479,11 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3Headers128TraceIdTest) { setupValidDriver(); - const uint64_t trace_id_high = Util::generateRandom64(); - const uint64_t trace_id_low = Util::generateRandom64(); + const uint64_t trace_id_high = generateRandom64(); + const uint64_t trace_id_low = generateRandom64(); const std::string trace_id = Hex::uint64ToHex(trace_id_high) + Hex::uint64ToHex(trace_id_low); - const std::string span_id = Hex::uint64ToHex(Util::generateRandom64()); - const std::string parent_id = Hex::uint64ToHex(Util::generateRandom64()); + const std::string span_id = Hex::uint64ToHex(generateRandom64()); + const std::string parent_id = Hex::uint64ToHex(generateRandom64()); request_headers_.insertXB3TraceId().value(trace_id); request_headers_.insertXB3SpanId().value(span_id); @@ -498,8 +508,8 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { setupValidDriver(); request_headers_.insertXB3TraceId().value(std::string("xyz")); - request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(Util::generateRandom64())); - request_headers_.insertXB3ParentSpanId().value(Hex::uint64ToHex(Util::generateRandom64())); + request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(generateRandom64())); + request_headers_.insertXB3ParentSpanId().value(Hex::uint64ToHex(generateRandom64())); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, true}); @@ -509,9 +519,9 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidSpanIdB3HeadersTest) { setupValidDriver(); - request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(Util::generateRandom64())); + request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(generateRandom64())); request_headers_.insertXB3SpanId().value(std::string("xyz")); - request_headers_.insertXB3ParentSpanId().value(Hex::uint64ToHex(Util::generateRandom64())); + request_headers_.insertXB3ParentSpanId().value(Hex::uint64ToHex(generateRandom64())); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, true}); @@ -521,8 +531,8 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidSpanIdB3HeadersTest) { TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidParentIdB3HeadersTest) { setupValidDriver(); - request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(Util::generateRandom64())); - request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(Util::generateRandom64())); + request_headers_.insertXB3TraceId().value(Hex::uint64ToHex(generateRandom64())); + request_headers_.insertXB3SpanId().value(Hex::uint64ToHex(generateRandom64())); request_headers_.insertXB3ParentSpanId().value(std::string("xyz")); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, diff --git a/test/extensions/transport_sockets/alts/BUILD b/test/extensions/transport_sockets/alts/BUILD index 171a97fd28e4..d0ef9cb73ecc 100644 --- a/test/extensions/transport_sockets/alts/BUILD +++ b/test/extensions/transport_sockets/alts/BUILD @@ -32,3 +32,13 @@ envoy_extension_cc_test( "//test/mocks/event:event_mocks", ], ) + +envoy_extension_cc_test( + name = "noop_transport_socket_callbacks_test", + srcs = ["noop_transport_socket_callbacks_test.cc"], + extension_name = "envoy.transport_sockets.alts", + deps = [ + "//source/extensions/transport_sockets/alts:noop_transport_socket_callbacks_lib", + "//test/mocks/network:network_mocks", + ], +) diff --git a/test/extensions/transport_sockets/alts/noop_transport_socket_callbacks_test.cc b/test/extensions/transport_sockets/alts/noop_transport_socket_callbacks_test.cc new file mode 100644 index 000000000000..ca9915c148db --- /dev/null +++ b/test/extensions/transport_sockets/alts/noop_transport_socket_callbacks_test.cc @@ -0,0 +1,58 @@ +#include "extensions/transport_sockets/alts/noop_transport_socket_callbacks.h" + +#include "test/mocks/network/mocks.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Alts { +namespace { + +class TestTransportSocketCallbacks : public Network::TransportSocketCallbacks { +public: + explicit TestTransportSocketCallbacks(Network::Connection& connection) + : connection_(connection) {} + + int fd() const override { return 1; } + Network::Connection& connection() override { return connection_; } + bool shouldDrainReadBuffer() override { return false; } + void setReadBufferReady() override { set_read_buffer_ready_ = true; } + void raiseEvent(Network::ConnectionEvent) override { event_raised_ = true; } + + bool event_raised() const { return event_raised_; } + bool set_read_buffer_ready() const { return set_read_buffer_ready_; } + +private: + bool event_raised_{false}; + bool set_read_buffer_ready_{false}; + Network::Connection& connection_; +}; + +class NoOpTransportSocketCallbacksTest : public testing::Test { +protected: + NoOpTransportSocketCallbacksTest() + : wrapper_callbacks_(connection_), wrapped_callbacks_(wrapper_callbacks_) {} + + Network::MockConnection connection_; + TestTransportSocketCallbacks wrapper_callbacks_; + NoOpTransportSocketCallbacks wrapped_callbacks_; +}; + +TEST_F(NoOpTransportSocketCallbacksTest, TestAllCallbacks) { + EXPECT_EQ(wrapper_callbacks_.fd(), wrapped_callbacks_.fd()); + EXPECT_EQ(&connection_, &wrapped_callbacks_.connection()); + EXPECT_FALSE(wrapped_callbacks_.shouldDrainReadBuffer()); + + wrapped_callbacks_.setReadBufferReady(); + EXPECT_FALSE(wrapper_callbacks_.set_read_buffer_ready()); + wrapped_callbacks_.raiseEvent(Network::ConnectionEvent::Connected); + EXPECT_FALSE(wrapper_callbacks_.event_raised()); +} + +} // namespace +} // namespace Alts +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/test/fuzz/BUILD b/test/fuzz/BUILD index 1bfa00cbb7b8..25192eddd07d 100644 --- a/test/fuzz/BUILD +++ b/test/fuzz/BUILD @@ -12,8 +12,7 @@ envoy_package() envoy_proto_library( name = "common_proto", srcs = ["common.proto"], - generate_python = 0, - deps = ["@envoy_api//envoy/api/v2/core:base_cc_proto"], + deps = ["@envoy_api//envoy/api/v2/core:base_export"], ) envoy_cc_test_library( @@ -37,6 +36,7 @@ envoy_cc_test_library( deps = [ "//source/common/common:minimal_logger_lib", "//source/common/common:thread_lib", + "//source/common/common:utility_lib", "//source/common/event:libevent_lib", "//test/test_common:environment_lib", ], @@ -46,7 +46,7 @@ envoy_cc_test_library( name = "utility_lib", hdrs = ["utility.h"], deps = [ - ":common_proto", + ":common_proto_cc", "//source/common/network:utility_lib", "//test/common/access_log:test_util", "//test/mocks/upstream:upstream_mocks", diff --git a/test/fuzz/fuzz_runner.cc b/test/fuzz/fuzz_runner.cc index 23d1108c07b7..c4f1b4d35f32 100644 --- a/test/fuzz/fuzz_runner.cc +++ b/test/fuzz/fuzz_runner.cc @@ -2,6 +2,7 @@ #include "common/common/logger.h" #include "common/common/thread.h" +#include "common/common/utility.h" #include "common/event/libevent.h" #include "test/test_common/environment.h" @@ -11,6 +12,13 @@ namespace Fuzz { spdlog::level::level_enum Runner::log_level_; +uint32_t PerTestEnvironment::test_num_; + +PerTestEnvironment::PerTestEnvironment() + : test_tmpdir_(TestEnvironment::temporaryPath(fmt::format("fuzz_{}", test_num_++))) { + TestEnvironment::createPath(test_tmpdir_); +} + void Runner::setupEnvironment(int argc, char** argv, spdlog::level::level_enum default_log_level) { Event::Libevent::Global::initialize(); @@ -30,6 +38,6 @@ void Runner::setupEnvironment(int argc, char** argv, spdlog::level::level_enum d } // namespace Envoy extern "C" int LLVMFuzzerInitialize(int* /*argc*/, char*** argv) { - Envoy::Fuzz::Runner::setupEnvironment(1, *argv, spdlog::level::off); + Envoy::Fuzz::Runner::setupEnvironment(1, *argv, spdlog::level::critical); return 0; } diff --git a/test/fuzz/fuzz_runner.h b/test/fuzz/fuzz_runner.h index e8c26497da59..fd4484e8529a 100644 --- a/test/fuzz/fuzz_runner.h +++ b/test/fuzz/fuzz_runner.h @@ -11,6 +11,20 @@ namespace Envoy { namespace Fuzz { +// Each test may need a sub-environment of that provided by //test/test_common:environment_lib, +// since each fuzz invocation runs in the same process, but might want a distinct tmp sandbox for +// example. +class PerTestEnvironment { +public: + PerTestEnvironment(); + + std::string temporaryPath(const std::string& path) const { return test_tmpdir_ + "/" + path; } + +private: + static uint32_t test_num_; + const std::string test_tmpdir_; +}; + class Runner { public: /** diff --git a/test/integration/BUILD b/test/integration/BUILD index 25534d61eec2..8848ce4b98fe 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -34,7 +34,7 @@ envoy_cc_test( "//source/extensions/transport_sockets/ssl:config", "//test/common/grpc:grpc_client_integration_lib", "//test/mocks/runtime:runtime_mocks", - "//test/mocks/secret:secret_mocks", + "//test/mocks/server:server_mocks", "//test/test_common:network_utility_lib", "//test/test_common:utility_lib", "@envoy_api//envoy/api/v2:cds_cc", @@ -60,7 +60,7 @@ py_binary( envoy_proto_library( name = "capture_fuzz_proto", srcs = [":capture_fuzz.proto"], - external_deps = ["well_known_protos"], + require_py = 1, ) envoy_cc_test( @@ -301,6 +301,7 @@ envoy_cc_test_library( "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", "//test/test_common:printers_lib", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) @@ -423,7 +424,7 @@ envoy_cc_test( "//source/common/buffer:zero_copy_input_stream_lib", "//source/common/grpc:codec_lib", "//source/common/grpc:common_lib", - "//source/common/ratelimit:ratelimit_proto", + "//source/common/ratelimit:ratelimit_proto_cc", "//source/extensions/filters/http/ratelimit:config", "//test/common/grpc:grpc_client_integration_lib", "@envoy_api//envoy/service/ratelimit/v2:rls_cc", @@ -462,6 +463,35 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "sds_dynamic_integration_test", + srcs = [ + "sds_dynamic_integration_test.cc", + ], + data = [ + "//test/config/integration/certs", + ], + deps = [ + ":http_integration_lib", + "//source/common/config:protobuf_link_hacks", + "//source/common/config:resources_lib", + "//source/common/event:dispatcher_includes", + "//source/common/event:dispatcher_lib", + "//source/common/network:connection_lib", + "//source/common/network:utility_lib", + "//source/common/ssl:context_config_lib", + "//source/common/ssl:context_lib", + "//source/extensions/filters/listener/tls_inspector:config", + "//source/extensions/transport_sockets/ssl:config", + "//test/common/grpc:grpc_client_integration_lib", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/secret:secret_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/transport_socket/capture/v2alpha:capture_cc", + "@envoy_api//envoy/data/tap/v2alpha:capture_cc", + ], +) + envoy_cc_test( name = "ssl_integration_test", srcs = [ @@ -567,7 +597,7 @@ envoy_cc_test( "//source/common/http:header_map_lib", "//source/extensions/filters/listener/tls_inspector:config", "//source/extensions/transport_sockets/ssl:config", - "//test/mocks/secret:secret_mocks", + "//test/mocks/server:server_mocks", "//test/test_common:utility_lib", ], ) @@ -581,7 +611,7 @@ envoy_cc_test_library( "h1_fuzz.h", ], deps = [ - ":capture_fuzz_proto", + ":capture_fuzz_proto_cc", ":http_integration_lib", "//source/common/common:assert_lib", "//source/common/common:logger_lib", diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 37239700ede4..0e81ec82b003 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -21,7 +21,7 @@ #include "test/integration/http_integration.h" #include "test/integration/utility.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/secret/mocks.h" +#include "test/mocks/server/mocks.h" #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" @@ -117,7 +117,7 @@ class AdsIntegrationTest : public AdsIntegrationBaseTest, TestEnvironment::runfilesPath("test/config/integration/certs/upstreamcert.pem")); tls_cert->mutable_private_key()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/upstreamkey.pem")); - auto cfg = std::make_unique(tls_context, secret_manager_); + auto cfg = std::make_unique(tls_context, factory_context_); static Stats::Scope* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); return std::make_unique( @@ -299,7 +299,7 @@ class AdsIntegrationTest : public AdsIntegrationBaseTest, return dynamic_cast(*message_ptr); } - testing::NiceMock secret_manager_; + testing::NiceMock factory_context_; Runtime::MockLoader runtime_; Ssl::ContextManagerImpl context_manager_{runtime_}; FakeStreamPtr ads_stream_; diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index 29f2289a5ce9..02677e1aacbe 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -1,4 +1,4 @@ -#include "fake_upstream.h" +#include "test/integration/fake_upstream.h" #include #include @@ -19,6 +19,7 @@ #include "server/connection_handler_impl.h" +#include "test/integration/utility.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" #include "test/test_common/utility.h" @@ -356,7 +357,7 @@ FakeUpstream::FakeUpstream(Network::TransportSocketFactoryPtr&& transport_socket Network::SocketPtr&& listen_socket, FakeHttpConnection::Type type, bool enable_half_close) : http_type_(type), socket_(std::move(listen_socket)), api_(new Api::Impl(milliseconds(10000))), - dispatcher_(api_->allocateDispatcher()), + dispatcher_(api_->allocateDispatcher(test_time_.timeSource())), handler_(new Server::ConnectionHandlerImpl(ENVOY_LOGGER(), *dispatcher_)), allow_unexpected_disconnects_(false), enable_half_close_(enable_half_close), listener_(*this), filter_chain_(Network::Test::createEmptyFilterChain(std::move(transport_socket_factory))) { diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index 052ac04e60df..4e9271cd7d6e 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -28,6 +28,7 @@ #include "common/stats/isolated_store_impl.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" namespace Envoy { @@ -580,6 +581,7 @@ class FakeUpstream : Logger::Loggable, Thread::ThreadPtr thread_; Thread::CondVar new_connection_event_; Api::ApiPtr api_; + DangerousDeprecatedTestTime test_time_; Event::DispatcherPtr dispatcher_; Network::ConnectionHandlerPtr handler_; std::list new_connections_ GUARDED_BY(lock_); diff --git a/test/integration/hds_integration_test.cc b/test/integration/hds_integration_test.cc index c2a40e35d1eb..bc7ab05db302 100644 --- a/test/integration/hds_integration_test.cc +++ b/test/integration/hds_integration_test.cc @@ -118,7 +118,7 @@ class HdsIntegrationTest : public HttpIntegrationTest, // one HTTP health_check envoy::service::discovery::v2::HealthCheckSpecifier makeHttpHealthCheckSpecifier() { envoy::service::discovery::v2::HealthCheckSpecifier server_health_check_specifier_; - server_health_check_specifier_.mutable_interval()->set_seconds(1); + server_health_check_specifier_.mutable_interval()->set_nanos(100000000); // 0.1 seconds auto* health_check = server_health_check_specifier_.add_cluster_health_checks(); @@ -126,12 +126,12 @@ class HdsIntegrationTest : public HttpIntegrationTest, Network::Utility::addressToProtobufAddress( *host_upstream_->localAddress(), *health_check->add_locality_endpoints()->add_endpoints()->mutable_address()); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_region("some_region"); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_zone("some_zone"); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_sub_zone("crete"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_region("middle_earth"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_zone("shire"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_sub_zone("hobbiton"); - health_check->add_health_checks()->mutable_timeout()->set_seconds(2); - health_check->mutable_health_checks(0)->mutable_interval()->set_seconds(1); + health_check->add_health_checks()->mutable_timeout()->set_seconds(MaxTimeout); + health_check->mutable_health_checks(0)->mutable_interval()->set_seconds(MaxTimeout); health_check->mutable_health_checks(0)->mutable_unhealthy_threshold()->set_value(2); health_check->mutable_health_checks(0)->mutable_healthy_threshold()->set_value(2); health_check->mutable_health_checks(0)->mutable_grpc_health_check(); @@ -145,7 +145,7 @@ class HdsIntegrationTest : public HttpIntegrationTest, // one TCP health_check envoy::service::discovery::v2::HealthCheckSpecifier makeTcpHealthCheckSpecifier() { envoy::service::discovery::v2::HealthCheckSpecifier server_health_check_specifier_; - server_health_check_specifier_.mutable_interval()->set_seconds(1); + server_health_check_specifier_.mutable_interval()->set_nanos(100000000); // 0.1 seconds auto* health_check = server_health_check_specifier_.add_cluster_health_checks(); @@ -153,12 +153,12 @@ class HdsIntegrationTest : public HttpIntegrationTest, Network::Utility::addressToProtobufAddress( *host_upstream_->localAddress(), *health_check->add_locality_endpoints()->add_endpoints()->mutable_address()); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_region("some_region"); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_zone("some_zone"); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_sub_zone("crete"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_region("middle_earth"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_zone("eriador"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_sub_zone("rivendell"); - health_check->add_health_checks()->mutable_timeout()->set_seconds(2); - health_check->mutable_health_checks(0)->mutable_interval()->set_seconds(1); + health_check->add_health_checks()->mutable_timeout()->set_seconds(MaxTimeout); + health_check->mutable_health_checks(0)->mutable_interval()->set_seconds(MaxTimeout); health_check->mutable_health_checks(0)->mutable_unhealthy_threshold()->set_value(2); health_check->mutable_health_checks(0)->mutable_healthy_threshold()->set_value(2); auto* tcp_hc = health_check->mutable_health_checks(0)->mutable_tcp_health_check(); @@ -169,24 +169,39 @@ class HdsIntegrationTest : public HttpIntegrationTest, } // Checks if Envoy reported the health status of an endpoint correctly - void checkEndpointHealthResponse(envoy::service::discovery::v2::EndpointHealth endpoint, + bool checkEndpointHealthResponse(envoy::service::discovery::v2::EndpointHealth endpoint, envoy::api::v2::core::HealthStatus healthy, Network::Address::InstanceConstSharedPtr address) { - EXPECT_EQ(healthy, endpoint.health_status()); - EXPECT_EQ(address->ip()->port(), endpoint.endpoint().address().socket_address().port_value()); - EXPECT_EQ(address->ip()->addressAsString(), - endpoint.endpoint().address().socket_address().address()); + if (healthy != endpoint.health_status()) { + return false; + } + if (address->ip()->port() != endpoint.endpoint().address().socket_address().port_value()) { + return false; + } + if (address->ip()->addressAsString() != + endpoint.endpoint().address().socket_address().address()) { + return false; + } + return true; } // Checks if the cluster counters are correct - void checkCounters(int requests, int response_s, int successes, int failures) { + void checkCounters(int requests, int responses, int successes, int failures) { EXPECT_EQ(requests, test_server_->counter("hds_delegate.requests")->value()); - EXPECT_EQ(response_s, test_server_->counter("hds_delegate.responses")->value()); + EXPECT_LE(responses, test_server_->counter("hds_delegate.responses")->value()); EXPECT_EQ(successes, test_server_->counter("cluster.anna.health_check.success")->value()); EXPECT_EQ(failures, test_server_->counter("cluster.anna.health_check.failure")->value()); } + void waitForEndpointHealthResponse(envoy::api::v2::core::HealthStatus healthy) { + ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + while (!checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), + healthy, host_upstream_->localAddress())) { + ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + } + } + static constexpr uint32_t upstream_endpoints_ = 0; FakeHttpConnectionPtr hds_fake_connection_; @@ -201,6 +216,7 @@ class HdsIntegrationTest : public HttpIntegrationTest, FakeHttpConnectionPtr host2_fake_connection_; FakeRawConnectionPtr host_fake_raw_connection_; + static constexpr int MaxTimeout = 100; envoy::service::discovery::v2::HealthCheckRequest envoy_msg_; envoy::service::discovery::v2::HealthCheckRequestOrEndpointHealthResponse response_; envoy::service::discovery::v2::HealthCheckSpecifier server_health_check_specifier_; @@ -234,13 +250,9 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyHttp) { host_stream_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false); host_stream_->encodeData(1024, true); - // Envoy reports back to server - ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + // Receive updates until the one we expect arrives + waitForEndpointHealthResponse(envoy::api::v2::core::HealthStatus::HEALTHY); - // Check that the response is correct - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), - envoy::api::v2::core::HealthStatus::HEALTHY, - host_upstream_->localAddress()); checkCounters(1, 2, 1, 0); // Clean up connections @@ -253,6 +265,7 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyHttp) { TEST_P(HdsIntegrationTest, SingleEndpointTimeoutHttp) { initialize(); server_health_check_specifier_ = makeHttpHealthCheckSpecifier(); + server_health_check_specifier_.mutable_cluster_health_checks(0) ->mutable_health_checks(0) ->mutable_timeout() @@ -260,7 +273,8 @@ TEST_P(HdsIntegrationTest, SingleEndpointTimeoutHttp) { server_health_check_specifier_.mutable_cluster_health_checks(0) ->mutable_health_checks(0) ->mutable_timeout() - ->set_nanos(800000000); + ->set_nanos(100000000); // 0.1 seconds + // Server <--> Envoy waitForHdsStream(); ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, envoy_msg_)); @@ -275,14 +289,9 @@ TEST_P(HdsIntegrationTest, SingleEndpointTimeoutHttp) { // Endpoint doesn't repond to the health check - // Envoy reports back to server - ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + // Receive updates until the one we expect arrives + waitForEndpointHealthResponse(envoy::api::v2::core::HealthStatus::TIMEOUT); - // Check that the response is correct - // TODO(lilika): Ideally this would be envoy::api::v2::core::HealthStatus::TIMEOUT - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), - envoy::api::v2::core::HealthStatus::UNHEALTHY, - host_upstream_->localAddress()); checkCounters(1, 2, 0, 1); // Clean up connections @@ -312,13 +321,9 @@ TEST_P(HdsIntegrationTest, SingleEndpointUnhealthyHttp) { host_stream_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "404"}}, false); host_stream_->encodeData(1024, true); - // Envoy reports back to server - ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + // Receive updates until the one we expect arrives + waitForEndpointHealthResponse(envoy::api::v2::core::HealthStatus::UNHEALTHY); - // Check that the response is correct - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), - envoy::api::v2::core::HealthStatus::UNHEALTHY, - host_upstream_->localAddress()); checkCounters(1, 2, 0, 1); // Clean up connections @@ -346,7 +351,8 @@ TEST_P(HdsIntegrationTest, SingleEndpointTimeoutTcp) { server_health_check_specifier_.mutable_cluster_health_checks(0) ->mutable_health_checks(0) ->mutable_timeout() - ->set_nanos(800000000); + ->set_nanos(100000000); // 0.1 seconds + hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); @@ -355,18 +361,12 @@ TEST_P(HdsIntegrationTest, SingleEndpointTimeoutTcp) { ASSERT_TRUE(host_upstream_->waitForRawConnection(host_fake_raw_connection_)); ASSERT_TRUE( host_fake_raw_connection_->waitForData(FakeRawConnection::waitForInexactMatch("Ping"))); - host_upstream_->set_allow_unexpected_disconnects(true); - // No response from the endpoint - // Envoy reports back to server - ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + // No response from the endpoint - // Check that the response is correct - auto endpoint = response_.endpoint_health_response().endpoints_health(0); - EXPECT_EQ(envoy::api::v2::core::HealthStatus::UNHEALTHY, endpoint.health_status()); - EXPECT_EQ(host_upstream_->localAddress()->ip()->port(), - endpoint.endpoint().address().socket_address().port_value()); + // Receive updates until the one we expect arrives + waitForEndpointHealthResponse(envoy::api::v2::core::HealthStatus::TIMEOUT); // Clean up connections cleanupHostConnections(); @@ -396,14 +396,8 @@ TEST_P(HdsIntegrationTest, SingleEndpointHealthyTcp) { RELEASE_ASSERT(result, result.message()); host_upstream_->set_allow_unexpected_disconnects(true); - // Envoy reports back to server - ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); - - // Check that the response is correct - auto endpoint = response_.endpoint_health_response().endpoints_health(0); - EXPECT_EQ(envoy::api::v2::core::HealthStatus::HEALTHY, endpoint.health_status()); - EXPECT_EQ(host_upstream_->localAddress()->ip()->port(), - endpoint.endpoint().address().socket_address().port_value()); + // Receive updates until the one we expect arrives + waitForEndpointHealthResponse(envoy::api::v2::core::HealthStatus::HEALTHY); // Clean up connections cleanupHostConnections(); @@ -421,6 +415,10 @@ TEST_P(HdsIntegrationTest, SingleEndpointUnhealthyTcp) { // Server asks for health checking server_health_check_specifier_ = makeTcpHealthCheckSpecifier(); + server_health_check_specifier_.mutable_cluster_health_checks(0) + ->mutable_health_checks(0) + ->mutable_timeout() + ->set_seconds(2); hds_stream_->startGrpcStream(); hds_stream_->sendGrpcMessage(server_health_check_specifier_); test_server_->waitForCounterGe("hds_delegate.requests", ++hds_requests_); @@ -433,14 +431,8 @@ TEST_P(HdsIntegrationTest, SingleEndpointUnhealthyTcp) { RELEASE_ASSERT(result, result.message()); host_upstream_->set_allow_unexpected_disconnects(true); - // Envoy reports back to server - ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); - - // Check that the response is correct - auto endpoint = response_.endpoint_health_response().endpoints_health(0); - EXPECT_EQ(envoy::api::v2::core::HealthStatus::UNHEALTHY, endpoint.health_status()); - EXPECT_EQ(host_upstream_->localAddress()->ip()->port(), - endpoint.endpoint().address().socket_address().port_value()); + // Receive updates until the one we expect arrives + waitForEndpointHealthResponse(envoy::api::v2::core::HealthStatus::UNHEALTHY); // Clean up connections cleanupHostConnections(); @@ -476,16 +468,17 @@ TEST_P(HdsIntegrationTest, TwoEndpointsSameLocality) { host2_stream_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false); host2_stream_->encodeData(1024, true); - // Envoy reports back to server + // Receive updates until the one we expect arrives ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + while (!(checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), + envoy::api::v2::core::HealthStatus::UNHEALTHY, + host_upstream_->localAddress()) && + checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(1), + envoy::api::v2::core::HealthStatus::HEALTHY, + host2_upstream_->localAddress()))) { + ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + } - // Check that the response is correct - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), - envoy::api::v2::core::HealthStatus::UNHEALTHY, - host_upstream_->localAddress()); - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(1), - envoy::api::v2::core::HealthStatus::HEALTHY, - host2_upstream_->localAddress()); checkCounters(1, 2, 1, 1); // Clean up connections @@ -505,8 +498,8 @@ TEST_P(HdsIntegrationTest, TwoEndpointsDifferentLocality) { Network::Utility::addressToProtobufAddress( *host2_upstream_->localAddress(), *health_check->add_locality_endpoints()->add_endpoints()->mutable_address()); - health_check->mutable_locality_endpoints(1)->mutable_locality()->set_region("different_region"); - health_check->mutable_locality_endpoints(1)->mutable_locality()->set_zone("different_zone"); + health_check->mutable_locality_endpoints(1)->mutable_locality()->set_region("plakias"); + health_check->mutable_locality_endpoints(1)->mutable_locality()->set_zone("fragokastelo"); health_check->mutable_locality_endpoints(1)->mutable_locality()->set_sub_zone("emplisi"); // Server <--> Envoy @@ -527,16 +520,17 @@ TEST_P(HdsIntegrationTest, TwoEndpointsDifferentLocality) { host2_stream_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false); host2_stream_->encodeData(1024, true); - // Envoy reports back to server + // Receive updates until the one we expect arrives ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + while (!(checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), + envoy::api::v2::core::HealthStatus::UNHEALTHY, + host_upstream_->localAddress()) && + checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(1), + envoy::api::v2::core::HealthStatus::HEALTHY, + host2_upstream_->localAddress()))) { + ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + } - // Check that the response is correct - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), - envoy::api::v2::core::HealthStatus::UNHEALTHY, - host_upstream_->localAddress()); - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(1), - envoy::api::v2::core::HealthStatus::HEALTHY, - host2_upstream_->localAddress()); checkCounters(1, 2, 1, 1); // Clean up connections @@ -557,12 +551,12 @@ TEST_P(HdsIntegrationTest, TwoEndpointsDifferentClusters) { Network::Utility::addressToProtobufAddress( *host2_upstream_->localAddress(), *health_check->add_locality_endpoints()->add_endpoints()->mutable_address()); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_region("peculiar_region"); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_zone("peculiar_zone"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_region("kounopetra"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_zone("emplisi"); health_check->mutable_locality_endpoints(0)->mutable_locality()->set_sub_zone("paris"); - health_check->add_health_checks()->mutable_timeout()->set_seconds(2); - health_check->mutable_health_checks(0)->mutable_interval()->set_seconds(1); + health_check->add_health_checks()->mutable_timeout()->set_seconds(MaxTimeout); + health_check->mutable_health_checks(0)->mutable_interval()->set_seconds(MaxTimeout); health_check->mutable_health_checks(0)->mutable_unhealthy_threshold()->set_value(2); health_check->mutable_health_checks(0)->mutable_healthy_threshold()->set_value(2); health_check->mutable_health_checks(0)->mutable_grpc_health_check(); @@ -587,16 +581,17 @@ TEST_P(HdsIntegrationTest, TwoEndpointsDifferentClusters) { host2_stream_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false); host2_stream_->encodeData(1024, true); - // Envoy reports back to server + // Receive updates until the one we expect arrives ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + while (!(checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), + envoy::api::v2::core::HealthStatus::UNHEALTHY, + host_upstream_->localAddress()) && + checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(1), + envoy::api::v2::core::HealthStatus::HEALTHY, + host2_upstream_->localAddress()))) { + ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + } - // Check that the response is correct - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), - envoy::api::v2::core::HealthStatus::UNHEALTHY, - host_upstream_->localAddress()); - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(1), - envoy::api::v2::core::HealthStatus::HEALTHY, - host2_upstream_->localAddress()); checkCounters(1, 2, 0, 1); EXPECT_EQ(1, test_server_->counter("cluster.cat.health_check.success")->value()); EXPECT_EQ(0, test_server_->counter("cluster.cat.health_check.failure")->value()); @@ -628,20 +623,16 @@ TEST_P(HdsIntegrationTest, TestUpdateMessage) { host_stream_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "200"}}, false); host_stream_->encodeData(1024, true); - // Envoy reports back to server - ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + // Receive updates until the one we expect arrives + waitForEndpointHealthResponse(envoy::api::v2::core::HealthStatus::HEALTHY); - // Check that the response is correct - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), - envoy::api::v2::core::HealthStatus::HEALTHY, - host_upstream_->localAddress()); checkCounters(1, 2, 1, 0); cleanupHostConnections(); // New HealthCheckSpecifier message envoy::service::discovery::v2::HealthCheckSpecifier new_message; - new_message.mutable_interval()->set_seconds(1); + new_message.mutable_interval()->set_nanos(100000000); // 0.1 seconds auto* health_check = new_message.add_cluster_health_checks(); @@ -650,12 +641,12 @@ TEST_P(HdsIntegrationTest, TestUpdateMessage) { *host2_upstream_->localAddress(), *health_check->add_locality_endpoints()->add_endpoints()->mutable_address()); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_region("peculiar_region"); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_zone("peculiar_zone"); - health_check->mutable_locality_endpoints(0)->mutable_locality()->set_sub_zone("paris"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_region("matala"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_zone("tilburg"); + health_check->mutable_locality_endpoints(0)->mutable_locality()->set_sub_zone("rivendell"); - health_check->add_health_checks()->mutable_timeout()->set_seconds(2); - health_check->mutable_health_checks(0)->mutable_interval()->set_seconds(1); + health_check->add_health_checks()->mutable_timeout()->set_seconds(MaxTimeout); + health_check->mutable_health_checks(0)->mutable_interval()->set_seconds(MaxTimeout); health_check->mutable_health_checks(0)->mutable_unhealthy_threshold()->set_value(2); health_check->mutable_health_checks(0)->mutable_healthy_threshold()->set_value(2); health_check->mutable_health_checks(0)->mutable_grpc_health_check(); @@ -671,17 +662,18 @@ TEST_P(HdsIntegrationTest, TestUpdateMessage) { ASSERT_TRUE(host2_fake_connection_->waitForNewStream(*dispatcher_, host2_stream_)); ASSERT_TRUE(host2_stream_->waitForEndStream(*dispatcher_)); host2_upstream_->set_allow_unexpected_disconnects(true); + // Endpoint responds to the health check host2_stream_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "404"}}, false); host2_stream_->encodeData(1024, true); - // Envoy reports back to server + // Receive updates until the one we expect arrives ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); - - // Check that the response is correct - checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), - envoy::api::v2::core::HealthStatus::UNHEALTHY, - host2_upstream_->localAddress()); + while (!checkEndpointHealthResponse(response_.endpoint_health_response().endpoints_health(0), + envoy::api::v2::core::HealthStatus::UNHEALTHY, + host2_upstream_->localAddress())) { + ASSERT_TRUE(hds_stream_->waitForGrpcMessage(*dispatcher_, response_)); + } // Clean up connections cleanupHostConnections(); diff --git a/test/integration/header_integration_test.cc b/test/integration/header_integration_test.cc index e25e7b79a4f6..29eaf276d971 100644 --- a/test/integration/header_integration_test.cc +++ b/test/integration/header_integration_test.cc @@ -120,6 +120,26 @@ stat_prefix: header_test - header: key: "x-real-ip" value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%" + - name: append-same-headers + domains: ["append-same-headers.com"] + request_headers_to_add: + - header: + key: "x-foo" + value: "value1" + - header: + key: "authorization" + value: "token1" + routes: + - match: { prefix: "/test" } + route: + cluster: cluster_0 + request_headers_to_add: + - header: + key: "x-foo" + value: "value2" + - header: + key: "authorization" + value: "token2" )EOF"; } // namespace @@ -915,4 +935,39 @@ TEST_P(HeaderIntegrationTest, TestXFFParsing) { }); } +// Validates behavior around same header appending (both predefined headers and +// other). +TEST_P(HeaderIntegrationTest, TestAppendSameHeaders) { + initializeFilter(HeaderMode::Append, false); + performRequest( + Http::TestHeaderMapImpl{ + {":method", "GET"}, + {":path", "/test"}, + {":scheme", "http"}, + {":authority", "append-same-headers.com"}, + {"authorization", "token3"}, + {"x-foo", "value3"}, + }, + Http::TestHeaderMapImpl{ + {":authority", "append-same-headers.com"}, + {":path", "/test"}, + {":method", "GET"}, + {"authorization", "token3,token2,token1"}, + {"x-foo", "value3"}, + {"x-foo", "value2"}, + {"x-foo", "value1"}, + }, + Http::TestHeaderMapImpl{ + {"server", "envoy"}, + {"content-length", "0"}, + {":status", "200"}, + {"x-unmodified", "response"}, + }, + Http::TestHeaderMapImpl{ + {"server", "envoy"}, + {"x-unmodified", "response"}, + {":status", "200"}, + }); +} + } // namespace Envoy diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 67ec481704df..a3451fb4cdca 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -14,6 +14,7 @@ #include "common/api/api_impl.h" #include "common/buffer/buffer_impl.h" #include "common/common/fmt.h" +#include "common/http/headers.h" #include "common/network/connection_impl.h" #include "common/network/utility.h" #include "common/protobuf/utility.h" @@ -79,7 +80,6 @@ IntegrationCodecClient::IntegrationCodecClient( connection_->addConnectionCallbacks(callbacks_); setCodecConnectionCallbacks(codec_callbacks_); dispatcher.run(Event::Dispatcher::RunType::Block); - EXPECT_TRUE(connected_); } void IntegrationCodecClient::flushWrite() { @@ -169,7 +169,7 @@ IntegrationCodecClientPtr HttpIntegrationTest::makeHttpConnection(uint32_t port) } IntegrationCodecClientPtr -HttpIntegrationTest::makeHttpConnection(Network::ClientConnectionPtr&& conn) { +HttpIntegrationTest::makeRawHttpConnection(Network::ClientConnectionPtr&& conn) { std::shared_ptr cluster{new NiceMock()}; Upstream::HostDescriptionConstSharedPtr host_description{Upstream::makeTestHostDescription( cluster, fmt::format("tcp://{}:80", Network::Test::getLoopbackAddressUrlString(version_)))}; @@ -177,6 +177,13 @@ HttpIntegrationTest::makeHttpConnection(Network::ClientConnectionPtr&& conn) { *dispatcher_, std::move(conn), host_description, downstream_protocol_)}; } +IntegrationCodecClientPtr +HttpIntegrationTest::makeHttpConnection(Network::ClientConnectionPtr&& conn) { + auto codec = makeRawHttpConnection(std::move(conn)); + EXPECT_TRUE(codec->connected()); + return codec; +} + HttpIntegrationTest::HttpIntegrationTest(Http::CodecClient::Type downstream_protocol, Network::Address::IpVersion version, const std::string& config) diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index 6d86ef8df5d7..b3f342ba5071 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -25,7 +25,8 @@ class IntegrationCodecClient : public Http::CodecClientProd { IntegrationStreamDecoderPtr makeHeaderOnlyRequest(const Http::HeaderMap& headers); IntegrationStreamDecoderPtr makeRequestWithBody(const Http::HeaderMap& headers, uint64_t body_size); - bool sawGoAway() { return saw_goaway_; } + bool sawGoAway() const { return saw_goaway_; } + bool connected() const { return connected_; } void sendData(Http::StreamEncoder& encoder, absl::string_view data, bool end_stream); void sendData(Http::StreamEncoder& encoder, Buffer::Instance& data, bool end_stream); void sendData(Http::StreamEncoder& encoder, uint64_t size, bool end_stream); @@ -81,6 +82,9 @@ class HttpIntegrationTest : public BaseIntegrationTest { protected: IntegrationCodecClientPtr makeHttpConnection(uint32_t port); + // Makes a http connection object without checking its connected state. + IntegrationCodecClientPtr makeRawHttpConnection(Network::ClientConnectionPtr&& conn); + // Makes a http connection object with asserting a connected state. IntegrationCodecClientPtr makeHttpConnection(Network::ClientConnectionPtr&& conn); // Sets downstream_protocol_ and alters the HTTP connection manager codec type in the diff --git a/test/integration/idle_timeout_integration_test.cc b/test/integration/idle_timeout_integration_test.cc index 3006fcf25a28..f03006dcb116 100644 --- a/test/integration/idle_timeout_integration_test.cc +++ b/test/integration/idle_timeout_integration_test.cc @@ -26,12 +26,12 @@ class IdleTimeoutIntegrationTest : public HttpProtocolIntegrationTest { HttpProtocolIntegrationTest::initialize(); } - IntegrationStreamDecoderPtr setupPerStreamIdleTimeoutTest() { + IntegrationStreamDecoderPtr setupPerStreamIdleTimeoutTest(const char* method = "GET") { initialize(); fake_upstreams_[0]->set_allow_unexpected_disconnects(true); codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); auto encoder_decoder = - codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", "GET"}, + codec_client_->startRequest(Http::TestHeaderMapImpl{{":method", method}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", "host"}}); @@ -75,7 +75,6 @@ TEST_P(IdleTimeoutIntegrationTest, PerStreamIdleTimeoutAfterDownstreamHeaders) { auto response = setupPerStreamIdleTimeoutTest(); waitForTimeout(*response); - EXPECT_FALSE(upstream_request_->complete()); EXPECT_EQ(0U, upstream_request_->bodyLength()); EXPECT_TRUE(response->complete()); @@ -83,6 +82,20 @@ TEST_P(IdleTimeoutIntegrationTest, PerStreamIdleTimeoutAfterDownstreamHeaders) { EXPECT_EQ("stream timeout", response->body()); } +// Per-stream idle timeout after having sent downstream head request. +TEST_P(IdleTimeoutIntegrationTest, PerStreamIdleTimeoutHeadRequestAfterDownstreamHeadRequest) { + auto response = setupPerStreamIdleTimeoutTest("HEAD"); + + waitForTimeout(*response); + EXPECT_FALSE(upstream_request_->complete()); + EXPECT_EQ(0U, upstream_request_->bodyLength()); + EXPECT_TRUE(response->complete()); + EXPECT_STREQ("408", response->headers().Status()->value().c_str()); + EXPECT_STREQ(fmt::format("{}", strlen("stream timeout")).c_str(), + response->headers().ContentLength()->value().c_str()); + EXPECT_EQ("", response->body()); +} + // Global per-stream idle timeout applies if there is no per-stream idle timeout. TEST_P(IdleTimeoutIntegrationTest, GlobalPerStreamIdleTimeoutAfterDownstreamHeaders) { enable_global_idle_timeout_ = true; diff --git a/test/integration/integration.cc b/test/integration/integration.cc index ab8754717eae..b0e854e803b9 100644 --- a/test/integration/integration.cc +++ b/test/integration/integration.cc @@ -214,7 +214,8 @@ BaseIntegrationTest::BaseIntegrationTest(Network::Address::IpVersion version, const std::string& config) : api_(new Api::Impl(std::chrono::milliseconds(10000))), mock_buffer_factory_(new NiceMock), - dispatcher_(new Event::DispatcherImpl(Buffer::WatermarkFactoryPtr{mock_buffer_factory_})), + dispatcher_(new Event::DispatcherImpl(test_time_.timeSource(), + Buffer::WatermarkFactoryPtr{mock_buffer_factory_})), version_(version), config_helper_(version, config), default_log_level_(TestEnvironment::getOptions().logLevel()) { // This is a hack, but there are situations where we disconnect fake upstream connections and diff --git a/test/integration/integration.h b/test/integration/integration.h index e55836285018..0e126243022e 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -14,6 +14,7 @@ #include "test/mocks/buffer/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" #include "spdlog/spdlog.h" @@ -160,6 +161,8 @@ class BaseIntegrationTest : Logger::Loggable { const std::vector& port_names); Api::ApiPtr api_; + DangerousDeprecatedTestTime test_time_; + MockBufferFactory* mock_buffer_factory_; // Will point to the dispatcher's factory. Event::DispatcherPtr dispatcher_; diff --git a/test/integration/ratelimit_integration_test.cc b/test/integration/ratelimit_integration_test.cc index 768510c3575e..02cbbaae183e 100644 --- a/test/integration/ratelimit_integration_test.cc +++ b/test/integration/ratelimit_integration_test.cc @@ -4,6 +4,8 @@ #include "common/grpc/codec.h" #include "common/grpc/common.h" +#include "source/common/ratelimit/ratelimit.pb.h" + #include "test/common/grpc/grpc_client_integration.h" #include "test/integration/http_integration.h" @@ -22,7 +24,11 @@ class RatelimitGrpcClientIntegrationParamTest ~RatelimitGrpcClientIntegrationParamTest() {} Network::Address::IpVersion ipVersion() const override { return std::get<0>(GetParam()); } Grpc::ClientType clientType() const override { return std::get<1>(GetParam()); } - bool useDataPlaneProto() const { return std::get<2>(GetParam()); } + bool useDataPlaneProto() const { + // Force link time dependency on deprecated message type. + pb::lyft::ratelimit::RateLimit _ignore; + return std::get<2>(GetParam()); + } }; class RatelimitIntegrationTest : public HttpIntegrationTest, @@ -38,8 +44,14 @@ class RatelimitIntegrationTest : public HttpIntegrationTest, } void initialize() override { - config_helper_.addFilter( - "{ name: envoy.rate_limit, config: { domain: some_domain, timeout: 0.5s } }"); + if (failure_mode_deny_) { + config_helper_.addFilter("{ name: envoy.rate_limit, config: { domain: some_domain, " + "failure_mode_deny: true, timeout: 0.5s } }"); + + } else { + config_helper_.addFilter( + "{ name: envoy.rate_limit, config: { domain: some_domain, timeout: 0.5s } }"); + } config_helper_.addConfigModifier([this](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { auto* ratelimit_cluster = bootstrap.mutable_static_resources()->add_clusters(); ratelimit_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); @@ -170,10 +182,19 @@ class RatelimitIntegrationTest : public HttpIntegrationTest, const uint64_t request_size_ = 1024; const uint64_t response_size_ = 512; + bool failure_mode_deny_ = false; +}; + +// Test that verifies failure mode cases. +class RatelimitFailureModeIntegrationTest : public RatelimitIntegrationTest { +public: + RatelimitFailureModeIntegrationTest() { failure_mode_deny_ = true; } }; INSTANTIATE_TEST_CASE_P(IpVersionsClientType, RatelimitIntegrationTest, RATELIMIT_GRPC_CLIENT_INTEGRATION_PARAMS); +INSTANTIATE_TEST_CASE_P(IpVersionsClientType, RatelimitFailureModeIntegrationTest, + RATELIMIT_GRPC_CLIENT_INTEGRATION_PARAMS); TEST_P(RatelimitIntegrationTest, Ok) { initiateClientConnection(); @@ -262,6 +283,7 @@ TEST_P(RatelimitIntegrationTest, Error) { EXPECT_EQ(nullptr, test_server_->counter("cluster.cluster_0.ratelimit.ok")); EXPECT_EQ(nullptr, test_server_->counter("cluster.cluster_0.ratelimit.over_limit")); EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.ratelimit.error")->value()); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.ratelimit.failure_mode_allowed")->value()); } TEST_P(RatelimitIntegrationTest, Timeout) { @@ -310,5 +332,19 @@ TEST_P(RatelimitIntegrationTest, FailedConnect) { cleanup(); } +TEST_P(RatelimitFailureModeIntegrationTest, ErrorWithFailureModeOff) { + initiateClientConnection(); + waitForRatelimitRequest(); + ratelimit_request_->encodeHeaders(Http::TestHeaderMapImpl{{":status", "503"}}, true); + // Rate limiter fail closed + waitForFailedUpstreamResponse(500); + cleanup(); + + EXPECT_EQ(nullptr, test_server_->counter("cluster.cluster_0.ratelimit.ok")); + EXPECT_EQ(nullptr, test_server_->counter("cluster.cluster_0.ratelimit.over_limit")); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_0.ratelimit.error")->value()); + EXPECT_EQ(nullptr, test_server_->counter("cluster.cluster_0.ratelimit.failure_mode_allowed")); +} + } // namespace } // namespace Envoy diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc new file mode 100644 index 000000000000..191a615240a2 --- /dev/null +++ b/test/integration/sds_dynamic_integration_test.cc @@ -0,0 +1,345 @@ +#include +#include + +#include "envoy/service/discovery/v2/sds.pb.h" + +#include "common/config/resources.h" +#include "common/event/dispatcher_impl.h" +#include "common/network/connection_impl.h" +#include "common/network/utility.h" +#include "common/ssl/context_config_impl.h" +#include "common/ssl/context_manager_impl.h" + +#include "test/common/grpc/grpc_client_integration.h" +#include "test/integration/http_integration.h" +#include "test/integration/server.h" +#include "test/integration/ssl_utility.h" +#include "test/mocks/init/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/secret/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/test_common/network_utility.h" +#include "test/test_common/utility.h" + +#include "absl/strings/match.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "integration.h" +#include "utility.h" + +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Ssl { + +// Hack to force linking of the service: https://github.com/google/protobuf/issues/4221. +const envoy::service::discovery::v2::SdsDummy _sds_dummy; + +// Sds integration base class with following support: +// * functions to create sds upstream, and send sds response +// * functions to create secret protobuf. +class SdsDynamicIntegrationBaseTest : public HttpIntegrationTest, + public Grpc::GrpcClientIntegrationParamTest { +public: + SdsDynamicIntegrationBaseTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, ipVersion()) {} + +protected: + void createSdsStream(FakeUpstream& upstream) { + sds_upstream_ = &upstream; + AssertionResult result1 = sds_upstream_->waitForHttpConnection(*dispatcher_, sds_connection_); + RELEASE_ASSERT(result1, result1.message()); + + AssertionResult result2 = sds_connection_->waitForNewStream(*dispatcher_, sds_stream_); + RELEASE_ASSERT(result2, result2.message()); + sds_stream_->startGrpcStream(); + } + + envoy::api::v2::auth::Secret getServerSecret() { + envoy::api::v2::auth::Secret secret; + secret.set_name("server_cert"); + auto* tls_certificate = secret.mutable_tls_certificate(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/servercert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/serverkey.pem")); + return secret; + } + + envoy::api::v2::auth::Secret getClientSecret() { + envoy::api::v2::auth::Secret secret; + secret.set_name("client_cert"); + auto* tls_certificate = secret.mutable_tls_certificate(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/clientcert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/clientkey.pem")); + return secret; + } + + envoy::api::v2::auth::Secret getWrongSecret() { + envoy::api::v2::auth::Secret secret; + secret.set_name("wrong_cert"); + return secret; + } + + void sendSdsResponse(const envoy::api::v2::auth::Secret& secret) { + envoy::api::v2::DiscoveryResponse discovery_response; + discovery_response.set_version_info("1"); + discovery_response.set_type_url(Config::TypeUrl::get().Secret); + discovery_response.add_resources()->PackFrom(secret); + + sds_stream_->sendGrpcMessage(discovery_response); + } + + void cleanUpSdsConnection() { + ASSERT(sds_upstream_ != nullptr); + + // Don't ASSERT fail if an ADS reconnect ends up unparented. + sds_upstream_->set_allow_unexpected_disconnects(true); + AssertionResult result = sds_connection_->close(); + RELEASE_ASSERT(result, result.message()); + result = sds_connection_->waitForDisconnect(); + RELEASE_ASSERT(result, result.message()); + sds_connection_.reset(); + } + + void PrintServerCounters() { + std::cerr << "all counters" << std::endl; + for (const auto& c : test_server_->counters()) { + std::cerr << "counter: " << c->name() << ", value: " << c->value() << std::endl; + } + } + + Runtime::MockLoader runtime_; + Ssl::ContextManagerImpl context_manager_{runtime_}; + FakeHttpConnectionPtr sds_connection_; + FakeUpstream* sds_upstream_{}; + FakeStreamPtr sds_stream_; +}; + +// Downstream SDS integration test: static Listener with ssl cert from SDS +class SdsDynamicDownstreamIntegrationTest : public SdsDynamicIntegrationBaseTest { +public: + void initialize() override { + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* common_tls_context = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_filter_chains(0) + ->mutable_tls_context() + ->mutable_common_tls_context(); + common_tls_context->add_alpn_protocols("http/1.1"); + + auto* validation_context = common_tls_context->mutable_validation_context(); + validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); + validation_context->add_verify_certificate_hash( + "E0:F3:C8:CE:5E:2E:A3:05:F0:70:1F:F5:12:E3:6E:2E:" + "97:92:82:84:A2:28:BC:F7:73:32:D3:39:30:A1:B6:FD"); + + // Modify the listener ssl cert to use SDS from sds_cluster + auto* secret_config = common_tls_context->add_tls_certificate_sds_secret_configs(); + secret_config->set_name("server_cert"); + auto* config_source = secret_config->mutable_sds_config(); + auto* api_config_source = config_source->mutable_api_config_source(); + api_config_source->set_api_type(envoy::api::v2::core::ApiConfigSource::GRPC); + auto* grpc_service = api_config_source->add_grpc_services(); + setGrpcService(*grpc_service, "sds_cluster", fake_upstreams_.back()->localAddress()); + + // Add a static sds cluster + auto* sds_cluster = bootstrap.mutable_static_resources()->add_clusters(); + sds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + sds_cluster->set_name("sds_cluster"); + sds_cluster->mutable_http2_protocol_options(); + }); + + HttpIntegrationTest::initialize(); + client_ssl_ctx_ = createClientSslTransportSocketFactory(false, false, context_manager_); + } + + void createUpstreams() override { + HttpIntegrationTest::createUpstreams(); + // SDS upstream + fake_upstreams_.emplace_back( + new FakeUpstream(0, FakeHttpConnection::Type::HTTP2, version_, enable_half_close_)); + } + + void TearDown() override { + cleanUpSdsConnection(); + + client_ssl_ctx_.reset(); + cleanupUpstreamAndDownstream(); + fake_upstream_connection_.reset(); + codec_client_.reset(); + } + + Network::ClientConnectionPtr makeSslClientConnection() { + Network::Address::InstanceConstSharedPtr address = getSslAddress(version_, lookupPort("http")); + return dispatcher_->createClientConnection(address, Network::Address::InstanceConstSharedPtr(), + client_ssl_ctx_->createTransportSocket(), nullptr); + } + +private: + Network::TransportSocketFactoryPtr client_ssl_ctx_; +}; + +INSTANTIATE_TEST_CASE_P(IpVersionsClientType, SdsDynamicDownstreamIntegrationTest, + GRPC_CLIENT_INTEGRATION_PARAMS); + +// A test that SDS server send a good server secret for a static listener. +// The first ssl request should be OK. +TEST_P(SdsDynamicDownstreamIntegrationTest, BasicSuccess) { + pre_worker_start_test_steps_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getServerSecret()); + }; + initialize(); + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + testRouterHeaderOnlyRequestAndResponse(true, &creator); +} + +// A test that SDS server send a bad secret for a static listener, +// The first ssl request should fail at connecting. +// then SDS send a good server secret, the second request should be OK. +TEST_P(SdsDynamicDownstreamIntegrationTest, WrongSecretFirst) { + pre_worker_start_test_steps_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getWrongSecret()); + }; + initialize(); + + codec_client_ = makeRawHttpConnection(makeSslClientConnection()); + // the connection state is not connected. + EXPECT_FALSE(codec_client_->connected()); + codec_client_->connection()->close(Network::ConnectionCloseType::NoFlush); + + sendSdsResponse(getServerSecret()); + + // Wait for ssl_context_updated_by_sds counter. + if (version_ == Network::Address::IpVersion::v4) { + test_server_->waitForCounterGe( + "listener.127.0.0.1_0.server_ssl_socket_factory.ssl_context_update_by_sds", 1); + } else { + test_server_->waitForCounterGe( + "listener.[__1]_0.server_ssl_socket_factory.ssl_context_update_by_sds", 1); + } + + ConnectionCreationFunction creator = [&]() -> Network::ClientConnectionPtr { + return makeSslClientConnection(); + }; + testRouterHeaderOnlyRequestAndResponse(true, &creator); +} + +// Upstream SDS integration test: a static cluster has ssl cert from SDS. +class SdsDynamicUpstreamIntegrationTest : public SdsDynamicIntegrationBaseTest { +public: + void initialize() override { + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + // add sds cluster first. + auto* sds_cluster = bootstrap.mutable_static_resources()->add_clusters(); + sds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + sds_cluster->set_name("sds_cluster"); + sds_cluster->mutable_http2_protocol_options(); + + // change the first cluster with ssl and sds. + auto* secret_config = bootstrap.mutable_static_resources() + ->mutable_clusters(0) + ->mutable_tls_context() + ->mutable_common_tls_context() + ->add_tls_certificate_sds_secret_configs(); + + secret_config->set_name("client_cert"); + auto* config_source = secret_config->mutable_sds_config(); + auto* api_config_source = config_source->mutable_api_config_source(); + api_config_source->set_api_type(envoy::api::v2::core::ApiConfigSource::GRPC); + auto* grpc_service = api_config_source->add_grpc_services(); + setGrpcService(*grpc_service, "sds_cluster", fake_upstreams_.back()->localAddress()); + }); + + HttpIntegrationTest::initialize(); + registerTestServerPorts({"http"}); + } + + void TearDown() override { + cleanUpSdsConnection(); + + cleanupUpstreamAndDownstream(); + fake_upstream_connection_.reset(); + codec_client_.reset(); + + test_server_.reset(); + fake_upstreams_.clear(); + } + + void createUpstreams() override { + // This is for backend with ssl + fake_upstreams_.emplace_back(new FakeUpstream(createUpstreamSslContext(context_manager_), 0, + FakeHttpConnection::Type::HTTP1, version_)); + // This is sds. + fake_upstreams_.emplace_back( + new FakeUpstream(0, FakeHttpConnection::Type::HTTP2, version_, enable_half_close_)); + } +}; + +INSTANTIATE_TEST_CASE_P(IpVersions, SdsDynamicUpstreamIntegrationTest, + GRPC_CLIENT_INTEGRATION_PARAMS); + +// To test a static cluster with sds. SDS send a good client secret first. +// The first request should work. +TEST_P(SdsDynamicUpstreamIntegrationTest, BasicSuccess) { + pre_worker_start_test_steps_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getClientSecret()); + }; + + initialize(); + fake_upstreams_[0]->set_allow_unexpected_disconnects(true); + + // There is a race condition here; there are two static clusters: + // backend cluster_0 with sds and sds_cluser. cluster_0 is created first, its init_manager + // is called so it issues a sds call, but fail since sds_cluster is not added yet. + // so cluster_0 is initialized with an empty secret. initialize() will not wait and will return. + // the testing request will be called, even though in the pre_workder_function, a good sds is + // send, the cluster will be updated with good secret, the testing request may fail if it is + // before context is updated. Hence, need to wait for context_update counter. + test_server_->waitForCounterGe( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + + testRouterHeaderOnlyRequestAndResponse(true); +} + +// To test a static cluster with sds. SDS send a bad client secret first. +// The first request should fail with 503, then SDS sends a good client secret, +// the second request should work. +TEST_P(SdsDynamicUpstreamIntegrationTest, WrongSecretFirst) { + pre_worker_start_test_steps_ = [this]() { + createSdsStream(*(fake_upstreams_[1])); + sendSdsResponse(getWrongSecret()); + }; + initialize(); + fake_upstreams_[0]->set_allow_unexpected_disconnects(true); + + // Make a simple request, should get 503 + BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest( + lookupPort("http"), "GET", "/test/long/url", "", downstream_protocol_, version_); + ASSERT_TRUE(response->complete()); + EXPECT_STREQ("503", response->headers().Status()->value().c_str()); + + // To flush out the reset connection from the first request in upstream. + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + + sendSdsResponse(getClientSecret()); + test_server_->waitForCounterGe( + "cluster.cluster_0.client_ssl_socket_factory.ssl_context_update_by_sds", 1); + + testRouterHeaderOnlyRequestAndResponse(true); +} + +} // namespace Ssl +} // namespace Envoy diff --git a/test/integration/sds_static_integration_test.cc b/test/integration/sds_static_integration_test.cc index e3a24c3c4298..cc259d31ffe7 100644 --- a/test/integration/sds_static_integration_test.cc +++ b/test/integration/sds_static_integration_test.cc @@ -15,6 +15,7 @@ #include "test/mocks/init/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/secret/mocks.h" +#include "test/mocks/server/mocks.h" #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" @@ -68,8 +69,7 @@ class SdsStaticDownstreamIntegrationTest registerTestServerPorts({"http"}); - client_ssl_ctx_ = - createClientSslTransportSocketFactory(false, false, context_manager_, secret_manager_); + client_ssl_ctx_ = createClientSslTransportSocketFactory(false, false, context_manager_); } void TearDown() override { @@ -88,7 +88,6 @@ class SdsStaticDownstreamIntegrationTest private: Runtime::MockLoader runtime_; Ssl::ContextManagerImpl context_manager_{runtime_}; - NiceMock secret_manager_; Network::TransportSocketFactoryPtr client_ssl_ctx_; }; @@ -144,41 +143,13 @@ class SdsStaticUpstreamIntegrationTest } void createUpstreams() override { - fake_upstreams_.emplace_back( - new FakeUpstream(createUpstreamSslContext(), 0, FakeHttpConnection::Type::HTTP1, version_)); - } - - Network::TransportSocketFactoryPtr createUpstreamSslContext() { - envoy::api::v2::auth::DownstreamTlsContext tls_context; - auto* common_tls_context = tls_context.mutable_common_tls_context(); - common_tls_context->add_alpn_protocols("h2"); - common_tls_context->add_alpn_protocols("http/1.1"); - common_tls_context->mutable_deprecated_v1()->set_alt_alpn_protocols("http/1.1"); - - auto* validation_context = common_tls_context->mutable_validation_context(); - validation_context->mutable_trusted_ca()->set_filename( - TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); - validation_context->add_verify_certificate_hash( - "E0:F3:C8:CE:5E:2E:A3:05:F0:70:1F:F5:12:E3:6E:2E:" - "97:92:82:84:A2:28:BC:F7:73:32:D3:39:30:A1:B6:FD"); - - auto* tls_certificate = common_tls_context->add_tls_certificates(); - tls_certificate->mutable_certificate_chain()->set_filename( - TestEnvironment::runfilesPath("/test/config/integration/certs/servercert.pem")); - tls_certificate->mutable_private_key()->set_filename( - TestEnvironment::runfilesPath("/test/config/integration/certs/serverkey.pem")); - - auto cfg = std::make_unique(tls_context, secret_manager_); - - static Stats::Scope* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); - return std::make_unique( - std::move(cfg), context_manager_, *upstream_stats_store, std::vector{}); + fake_upstreams_.emplace_back(new FakeUpstream(createUpstreamSslContext(context_manager_), 0, + FakeHttpConnection::Type::HTTP1, version_)); } private: Runtime::MockLoader runtime_; Ssl::ContextManagerImpl context_manager_{runtime_}; - NiceMock secret_manager_; }; INSTANTIATE_TEST_CASE_P(IpVersions, SdsStaticUpstreamIntegrationTest, diff --git a/test/integration/server.cc b/test/integration/server.cc index eb84cfe8ad91..956cb4381e42 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -102,9 +102,9 @@ void IntegrationTestServer::threadRoutine(const Network::Address::IpVersion vers } else { random_generator = std::make_unique(); } - server_.reset(new Server::InstanceImpl(options, Network::Utility::getLocalAddress(version), *this, - restarter, stats_store, lock, *this, - std::move(random_generator), tls)); + server_.reset(new Server::InstanceImpl( + options, test_time_.timeSource(), Network::Utility::getLocalAddress(version), *this, + restarter, stats_store, lock, *this, std::move(random_generator), tls)); pending_listeners_ = server_->listenerManager().listeners().size(); ENVOY_LOG(info, "waiting for {} test server listeners", pending_listeners_); server_set_.setReady(); diff --git a/test/integration/server.h b/test/integration/server.h index 3c1e65b9b1f6..0cc04041ceab 100644 --- a/test/integration/server.h +++ b/test/integration/server.h @@ -19,6 +19,7 @@ #include "server/test_hooks.h" #include "test/integration/server_stats.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" namespace Envoy { @@ -301,6 +302,7 @@ class IntegrationTestServer : Logger::Loggable, Thread::CondVar listeners_cv_; Thread::MutexBasicLockable listeners_mutex_; uint64_t pending_listeners_; + DangerousDeprecatedTestTime test_time_; ConditionalInitializer server_set_; std::unique_ptr server_; Server::TestDrainManager* drain_manager_{}; diff --git a/test/integration/ssl_integration_test.cc b/test/integration/ssl_integration_test.cc index 3ed603e9b37c..f72f0e7c518c 100644 --- a/test/integration/ssl_integration_test.cc +++ b/test/integration/ssl_integration_test.cc @@ -35,14 +35,10 @@ void SslIntegrationTest::initialize() { context_manager_.reset(new ContextManagerImpl(*runtime_)); registerTestServerPorts({"http"}); - client_ssl_ctx_plain_ = - createClientSslTransportSocketFactory(false, false, *context_manager_, secret_manager_); - client_ssl_ctx_alpn_ = - createClientSslTransportSocketFactory(true, false, *context_manager_, secret_manager_); - client_ssl_ctx_san_ = - createClientSslTransportSocketFactory(false, true, *context_manager_, secret_manager_); - client_ssl_ctx_alpn_san_ = - createClientSslTransportSocketFactory(true, true, *context_manager_, secret_manager_); + client_ssl_ctx_plain_ = createClientSslTransportSocketFactory(false, false, *context_manager_); + client_ssl_ctx_alpn_ = createClientSslTransportSocketFactory(true, false, *context_manager_); + client_ssl_ctx_san_ = createClientSslTransportSocketFactory(false, true, *context_manager_); + client_ssl_ctx_alpn_san_ = createClientSslTransportSocketFactory(true, true, *context_manager_); } void SslIntegrationTest::TearDown() { diff --git a/test/integration/ssl_integration_test.h b/test/integration/ssl_integration_test.h index 73f96c514a20..a883d3b9c3cb 100644 --- a/test/integration/ssl_integration_test.h +++ b/test/integration/ssl_integration_test.h @@ -32,7 +32,6 @@ class SslIntegrationTest : public HttpIntegrationTest, private: std::unique_ptr runtime_; std::unique_ptr context_manager_; - NiceMock secret_manager_; Network::TransportSocketFactoryPtr client_ssl_ctx_plain_; Network::TransportSocketFactoryPtr client_ssl_ctx_alpn_; diff --git a/test/integration/ssl_utility.cc b/test/integration/ssl_utility.cc index bb6ccb9f20b1..a5f7e71daa79 100644 --- a/test/integration/ssl_utility.cc +++ b/test/integration/ssl_utility.cc @@ -7,6 +7,7 @@ #include "common/ssl/ssl_socket.h" #include "test/integration/server.h" +#include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" @@ -14,8 +15,7 @@ namespace Envoy { namespace Ssl { Network::TransportSocketFactoryPtr -createClientSslTransportSocketFactory(bool alpn, bool san, ContextManager& context_manager, - Secret::SecretManager& secret_manager) { +createClientSslTransportSocketFactory(bool alpn, bool san, ContextManager& context_manager) { const std::string json_plain = R"EOF( { "ca_cert_file": "{{ test_rundir }}/test/config/integration/certs/cacert.pem", @@ -59,12 +59,41 @@ createClientSslTransportSocketFactory(bool alpn, bool san, ContextManager& conte target = san ? json_san : json_plain; } Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(target); - auto cfg = std::make_unique(*loader, secret_manager); + NiceMock mock_factory_ctx; + auto cfg = std::make_unique(*loader, mock_factory_ctx); static auto* client_stats_store = new Stats::TestIsolatedStoreImpl(); return Network::TransportSocketFactoryPtr{ new Ssl::ClientSslSocketFactory(std::move(cfg), context_manager, *client_stats_store)}; } +Network::TransportSocketFactoryPtr createUpstreamSslContext(ContextManager& context_manager) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + auto* common_tls_context = tls_context.mutable_common_tls_context(); + common_tls_context->add_alpn_protocols("h2"); + common_tls_context->add_alpn_protocols("http/1.1"); + common_tls_context->mutable_deprecated_v1()->set_alt_alpn_protocols("http/1.1"); + + auto* validation_context = common_tls_context->mutable_validation_context(); + validation_context->mutable_trusted_ca()->set_filename( + TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); + validation_context->add_verify_certificate_hash( + "E0:F3:C8:CE:5E:2E:A3:05:F0:70:1F:F5:12:E3:6E:2E:" + "97:92:82:84:A2:28:BC:F7:73:32:D3:39:30:A1:B6:FD"); + + auto* tls_certificate = common_tls_context->add_tls_certificates(); + tls_certificate->mutable_certificate_chain()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/servercert.pem")); + tls_certificate->mutable_private_key()->set_filename( + TestEnvironment::runfilesPath("/test/config/integration/certs/serverkey.pem")); + + NiceMock mock_factory_ctx; + auto cfg = std::make_unique(tls_context, mock_factory_ctx); + + static Stats::Scope* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); + return std::make_unique( + std::move(cfg), context_manager, *upstream_stats_store, std::vector{}); +} + Network::Address::InstanceConstSharedPtr getSslAddress(const Network::Address::IpVersion& version, int port) { std::string url = diff --git a/test/integration/ssl_utility.h b/test/integration/ssl_utility.h index d2ff42561bd4..268004dc756d 100644 --- a/test/integration/ssl_utility.h +++ b/test/integration/ssl_utility.h @@ -9,8 +9,8 @@ namespace Envoy { namespace Ssl { Network::TransportSocketFactoryPtr -createClientSslTransportSocketFactory(bool alpn, bool san, ContextManager& context_manager, - Secret::SecretManager& secret_manager); +createClientSslTransportSocketFactory(bool alpn, bool san, ContextManager& context_manager); +Network::TransportSocketFactoryPtr createUpstreamSslContext(ContextManager& context_manager); Network::Address::InstanceConstSharedPtr getSslAddress(const Network::Address::IpVersion& version, int port); diff --git a/test/integration/tcp_proxy_integration_test.cc b/test/integration/tcp_proxy_integration_test.cc index 31fc601e0cc6..c8ebe3c43921 100644 --- a/test/integration/tcp_proxy_integration_test.cc +++ b/test/integration/tcp_proxy_integration_test.cc @@ -383,8 +383,7 @@ void TcpProxySslIntegrationTest::setupConnections() { // Set up the SSl client. Network::Address::InstanceConstSharedPtr address = Ssl::getSslAddress(version_, lookupPort("tcp_proxy")); - context_ = - Ssl::createClientSslTransportSocketFactory(false, false, *context_manager_, secret_manager_); + context_ = Ssl::createClientSslTransportSocketFactory(false, false, *context_manager_); ssl_client_ = dispatcher_->createClientConnection(address, Network::Address::InstanceConstSharedPtr(), context_->createTransportSocket(), nullptr); diff --git a/test/integration/tcp_proxy_integration_test.h b/test/integration/tcp_proxy_integration_test.h index 9c75ee45fd92..b136c51c8bc5 100644 --- a/test/integration/tcp_proxy_integration_test.h +++ b/test/integration/tcp_proxy_integration_test.h @@ -41,7 +41,6 @@ class TcpProxySslIntegrationTest : public TcpProxyIntegrationTest { ConnectionStatusCallbacks connect_callbacks_; MockWatermarkBuffer* client_write_buffer_; std::shared_ptr payload_reader_; - testing::NiceMock secret_manager_; }; } // namespace diff --git a/test/integration/utility.cc b/test/integration/utility.cc index ffae1f670151..f2440ff0893e 100644 --- a/test/integration/utility.cc +++ b/test/integration/utility.cc @@ -53,13 +53,16 @@ void BufferingStreamDecoder::onComplete() { void BufferingStreamDecoder::onResetStream(Http::StreamResetReason) { ADD_FAILURE(); } +DangerousDeprecatedTestTime IntegrationUtil::evil_singleton_test_time_; + BufferingStreamDecoderPtr IntegrationUtil::makeSingleRequest(const Network::Address::InstanceConstSharedPtr& addr, const std::string& method, const std::string& url, const std::string& body, Http::CodecClient::Type type, const std::string& host, const std::string& content_type) { + Api::Impl api(std::chrono::milliseconds(9000)); - Event::DispatcherPtr dispatcher(api.allocateDispatcher()); + Event::DispatcherPtr dispatcher(api.allocateDispatcher(evil_singleton_test_time_.timeSource())); std::shared_ptr cluster{new NiceMock()}; Upstream::HostDescriptionConstSharedPtr host_description{ Upstream::makeTestHostDescription(cluster, "tcp://127.0.0.1:80")}; @@ -107,7 +110,7 @@ RawConnectionDriver::RawConnectionDriver(uint32_t port, Buffer::Instance& initia ReadCallback data_callback, Network::Address::IpVersion version) { api_.reset(new Api::Impl(std::chrono::milliseconds(10000))); - dispatcher_ = api_->allocateDispatcher(); + dispatcher_ = api_->allocateDispatcher(IntegrationUtil::evil_singleton_test_time_.timeSource()); client_ = dispatcher_->createClientConnection( Network::Utility::resolveUrl( fmt::format("tcp://{}:{}", Network::Test::getLoopbackAddressUrlString(version), port)), diff --git a/test/integration/utility.h b/test/integration/utility.h index a4b4c5d99c39..a1e1d7c9e449 100644 --- a/test/integration/utility.h +++ b/test/integration/utility.h @@ -11,9 +11,11 @@ #include "envoy/network/filter.h" #include "common/common/assert.h" +#include "common/common/utility.h" #include "common/http/codec_client.h" #include "test/test_common/printers.h" +#include "test/test_common/test_time.h" namespace Envoy { /** @@ -123,6 +125,9 @@ class IntegrationUtil { const std::string& body, Http::CodecClient::Type type, Network::Address::IpVersion ip_version, const std::string& host = "host", const std::string& content_type = ""); + + // TODO(jmarantz): this should be injectable. + static DangerousDeprecatedTestTime evil_singleton_test_time_; }; // A set of connection callbacks which tracks connection state. diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index e4d7c0903979..0d81e51bfa8d 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -61,7 +61,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createClientSslContext(b target = json_tls; } Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(target); - auto cfg = std::make_unique(*loader, secret_manager_); + auto cfg = std::make_unique(*loader, factory_context_); static auto* client_stats_store = new Stats::TestIsolatedStoreImpl(); return Network::TransportSocketFactoryPtr{ new Ssl::ClientSslSocketFactory(std::move(cfg), *context_manager_, *client_stats_store)}; @@ -76,7 +76,7 @@ Network::TransportSocketFactoryPtr XfccIntegrationTest::createUpstreamSslContext )EOF"; Json::ObjectSharedPtr loader = TestEnvironment::jsonLoadFromString(json); - auto cfg = std::make_unique(*loader, secret_manager_); + auto cfg = std::make_unique(*loader, factory_context_); static Stats::Scope* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); return std::make_unique( std::move(cfg), *context_manager_, *upstream_stats_store, std::vector{}); diff --git a/test/integration/xfcc_integration_test.h b/test/integration/xfcc_integration_test.h index e762b1720f41..d8302ea05816 100644 --- a/test/integration/xfcc_integration_test.h +++ b/test/integration/xfcc_integration_test.h @@ -6,7 +6,7 @@ #include "test/integration/http_integration.h" #include "test/integration/server.h" #include "test/mocks/runtime/mocks.h" -#include "test/mocks/secret/mocks.h" +#include "test/mocks/server/mocks.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -56,7 +56,7 @@ class XfccIntegrationTest : public HttpIntegrationTest, Network::TransportSocketFactoryPtr client_tls_ssl_ctx_; Network::TransportSocketFactoryPtr client_mtls_ssl_ctx_; Network::TransportSocketFactoryPtr upstream_ssl_ctx_; - testing::NiceMock secret_manager_; + testing::NiceMock factory_context_; }; } // namespace Xfcc } // namespace Envoy diff --git a/test/mocks/api/mocks.h b/test/mocks/api/mocks.h index 66898518b17a..82a2eaf7412a 100644 --- a/test/mocks/api/mocks.h +++ b/test/mocks/api/mocks.h @@ -23,11 +23,11 @@ class MockApi : public Api { ~MockApi(); // Api::Api - Event::DispatcherPtr allocateDispatcher() override { - return Event::DispatcherPtr{allocateDispatcher_()}; + Event::DispatcherPtr allocateDispatcher(TimeSource& time_source) override { + return Event::DispatcherPtr{allocateDispatcher_(time_source)}; } - MOCK_METHOD0(allocateDispatcher_, Event::Dispatcher*()); + MOCK_METHOD1(allocateDispatcher_, Event::Dispatcher*(TimeSource&)); MOCK_METHOD4(createFile, Filesystem::FileSharedPtr(const std::string& path, Event::Dispatcher& dispatcher, Thread::BasicLockable& lock, Stats::Store& stats_store)); diff --git a/test/mocks/event/BUILD b/test/mocks/event/BUILD index 448c025043bf..c807c41d0b6a 100644 --- a/test/mocks/event/BUILD +++ b/test/mocks/event/BUILD @@ -24,5 +24,6 @@ envoy_cc_mock( "//include/envoy/network:listener_interface", "//include/envoy/ssl:context_interface", "//test/mocks/buffer:buffer_mocks", + "//test/test_common:test_time_lib", ], ) diff --git a/test/mocks/event/mocks.cc b/test/mocks/event/mocks.cc index 59d724f85e4d..dd3273b076f1 100644 --- a/test/mocks/event/mocks.cc +++ b/test/mocks/event/mocks.cc @@ -13,7 +13,7 @@ using testing::SaveArg; namespace Envoy { namespace Event { -MockDispatcher::MockDispatcher() { +MockDispatcher::MockDispatcher() : time_source_(&test_time_.timeSource()) { ON_CALL(*this, clearDeferredDeleteList()).WillByDefault(Invoke([this]() -> void { to_delete_.clear(); })); diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index 643bb7120faf..55b7b29be56c 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -18,6 +18,7 @@ #include "envoy/ssl/context.h" #include "test/mocks/buffer/mocks.h" +#include "test/test_common/test_time.h" #include "gmock/gmock.h" @@ -29,6 +30,10 @@ class MockDispatcher : public Dispatcher { MockDispatcher(); ~MockDispatcher(); + void setTimeSource(TimeSource& time_source) { time_source_ = &time_source; } + + // Dispatcher + TimeSource& timeSource() override { return *time_source_; } Network::ConnectionPtr createServerConnection(Network::ConnectionSocketPtr&& socket, Network::TransportSocketPtr&& transport_socket) override { @@ -102,6 +107,10 @@ class MockDispatcher : public Dispatcher { MOCK_METHOD1(run, void(RunType type)); Buffer::WatermarkFactory& getWatermarkFactory() override { return buffer_factory_; } + // TODO(jmarantz): Switch these to using mock-time. + DangerousDeprecatedTestTime test_time_; + TimeSource* time_source_; + std::list to_delete_; MockBufferFactory buffer_factory_; }; diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 90e49e5d03d6..0474cf74a0b7 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -210,7 +210,7 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, encodeHeaders(std::move(headers), end_stream); }, [this](Buffer::Instance& data, bool end_stream) -> void { encodeData(data, end_stream); }, - stream_destroyed_, code, body); + stream_destroyed_, code, body, is_head_request_); } void encode100ContinueHeaders(HeaderMapPtr&& headers) override { encode100ContinueHeaders_(*headers); @@ -234,6 +234,7 @@ class MockStreamDecoderFilterCallbacks : public StreamDecoderFilterCallbacks, testing::NiceMock active_span_; testing::NiceMock tracing_config_; bool is_grpc_request_{}; + bool is_head_request_{false}; bool stream_destroyed_{}; }; diff --git a/test/mocks/secret/BUILD b/test/mocks/secret/BUILD index e01f07aaae59..43fc682bb7da 100644 --- a/test/mocks/secret/BUILD +++ b/test/mocks/secret/BUILD @@ -13,7 +13,9 @@ envoy_cc_mock( srcs = ["mocks.cc"], hdrs = ["mocks.h"], deps = [ + "//include/envoy/secret:secret_callbacks_interface", "//include/envoy/secret:secret_manager_interface", + "//include/envoy/server:transport_socket_config_interface", "//include/envoy/ssl:tls_certificate_config_interface", "//source/common/secret:secret_provider_impl_lib", ], diff --git a/test/mocks/secret/mocks.cc b/test/mocks/secret/mocks.cc index 3de2c5d039bb..7461830a5693 100644 --- a/test/mocks/secret/mocks.cc +++ b/test/mocks/secret/mocks.cc @@ -17,5 +17,9 @@ MockSecretManager::MockSecretManager() { MockSecretManager::~MockSecretManager() {} +MockSecretCallbacks::MockSecretCallbacks() {} + +MockSecretCallbacks::~MockSecretCallbacks() {} + } // namespace Secret } // namespace Envoy diff --git a/test/mocks/secret/mocks.h b/test/mocks/secret/mocks.h index 212cc8985cbb..9885c80f9b2e 100644 --- a/test/mocks/secret/mocks.h +++ b/test/mocks/secret/mocks.h @@ -1,6 +1,8 @@ #pragma once +#include "envoy/secret/secret_callbacks.h" #include "envoy/secret/secret_manager.h" +#include "envoy/server/transport_socket_config.h" #include "envoy/ssl/tls_certificate_config.h" #include "gmock/gmock.h" @@ -20,6 +22,17 @@ class MockSecretManager : public SecretManager { MOCK_METHOD1(createInlineTlsCertificateProvider, TlsCertificateConfigProviderSharedPtr( const envoy::api::v2::auth::TlsCertificate& tls_certificate)); + MOCK_METHOD3(findOrCreateDynamicSecretProvider, + TlsCertificateConfigProviderSharedPtr( + const envoy::api::v2::core::ConfigSource&, const std::string&, + Server::Configuration::TransportSocketFactoryContext&)); +}; + +class MockSecretCallbacks : public SecretCallbacks { +public: + MockSecretCallbacks(); + ~MockSecretCallbacks(); + MOCK_METHOD0(onAddOrUpdateSecret, void()); }; } // namespace Secret diff --git a/test/mocks/server/mocks.cc b/test/mocks/server/mocks.cc index b7f2db5efa74..7ea2f3cd4ac1 100644 --- a/test/mocks/server/mocks.cc +++ b/test/mocks/server/mocks.cc @@ -19,6 +19,7 @@ namespace Envoy { namespace Server { MockOptions::MockOptions(const std::string& config_path) : config_path_(config_path) { + ON_CALL(*this, concurrency()).WillByDefault(ReturnPointee(&concurrency_)); ON_CALL(*this, configPath()).WillByDefault(ReturnRef(config_path_)); ON_CALL(*this, configYaml()).WillByDefault(ReturnRef(config_yaml_)); ON_CALL(*this, v2ConfigOnly()).WillByDefault(Invoke([this] { return v2_config_only_; })); @@ -30,6 +31,7 @@ MockOptions::MockOptions(const std::string& config_path) : config_path_(config_p ON_CALL(*this, logPath()).WillByDefault(ReturnRef(log_path_)); ON_CALL(*this, maxStats()).WillByDefault(Return(1000)); ON_CALL(*this, statsOptions()).WillByDefault(ReturnRef(stats_options_)); + ON_CALL(*this, restartEpoch()).WillByDefault(ReturnPointee(&hot_restart_epoch_)); ON_CALL(*this, hotRestartDisabled()).WillByDefault(ReturnPointee(&hot_restart_disabled_)); } MockOptions::~MockOptions() {} @@ -88,6 +90,7 @@ MockListenerManager::MockListenerManager() {} MockListenerManager::~MockListenerManager() {} MockWorkerFactory::MockWorkerFactory() {} + MockWorkerFactory::~MockWorkerFactory() {} MockWorker::MockWorker() { @@ -130,6 +133,7 @@ MockInstance::MockInstance() ON_CALL(*this, listenerManager()).WillByDefault(ReturnRef(listener_manager_)); ON_CALL(*this, singletonManager()).WillByDefault(ReturnRef(*singleton_manager_)); ON_CALL(*this, overloadManager()).WillByDefault(ReturnRef(overload_manager_)); + ON_CALL(*this, timeSource()).WillByDefault(ReturnRef(test_time_.timeSource())); } MockInstance::~MockInstance() {} @@ -144,7 +148,9 @@ MockMain::MockMain(int wd_miss, int wd_megamiss, int wd_kill, int wd_multikill) ON_CALL(*this, wdMultiKillTimeout()).WillByDefault(Return(wd_multikill_)); } -MockFactoryContext::MockFactoryContext() : singleton_manager_(new Singleton::ManagerImpl()) { +MockFactoryContext::MockFactoryContext() + : singleton_manager_(new Singleton::ManagerImpl()), + time_source_(system_time_source_, monotonic_time_source_) { ON_CALL(*this, accessLogManager()).WillByDefault(ReturnRef(access_log_manager_)); ON_CALL(*this, clusterManager()).WillByDefault(ReturnRef(cluster_manager_)); ON_CALL(*this, dispatcher()).WillByDefault(ReturnRef(dispatcher_)); @@ -160,11 +166,14 @@ MockFactoryContext::MockFactoryContext() : singleton_manager_(new Singleton::Man ON_CALL(*this, admin()).WillByDefault(ReturnRef(admin_)); ON_CALL(*this, listenerScope()).WillByDefault(ReturnRef(listener_scope_)); ON_CALL(*this, systemTimeSource()).WillByDefault(ReturnRef(system_time_source_)); + ON_CALL(*this, timeSource()).WillByDefault(ReturnRef(time_source_)); } MockFactoryContext::~MockFactoryContext() {} -MockTransportSocketFactoryContext::MockTransportSocketFactoryContext() {} +MockTransportSocketFactoryContext::MockTransportSocketFactoryContext() + : secret_manager_(new Secret::SecretManagerImpl()) {} + MockTransportSocketFactoryContext::~MockTransportSocketFactoryContext() {} MockListenerFactoryContext::MockListenerFactoryContext() {} @@ -181,6 +190,9 @@ MockHealthCheckerFactoryContext::MockHealthCheckerFactoryContext() { MockHealthCheckerFactoryContext::~MockHealthCheckerFactoryContext() {} +MockAdminStream::MockAdminStream() {} +MockAdminStream::~MockAdminStream() {} + } // namespace Configuration } // namespace Server } // namespace Envoy diff --git a/test/mocks/server/mocks.h b/test/mocks/server/mocks.h index 3058a6cb0e2c..bd62535f854f 100644 --- a/test/mocks/server/mocks.h +++ b/test/mocks/server/mocks.h @@ -83,6 +83,8 @@ class MockOptions : public Options { spdlog::level::level_enum log_level_{spdlog::level::trace}; std::string log_path_; Stats::StatsOptionsImpl stats_options_; + uint32_t concurrency_{1}; + uint64_t hot_restart_epoch_{}; bool hot_restart_disabled_{}; }; @@ -325,6 +327,7 @@ class MockInstance : public Instance { MOCK_METHOD0(httpTracer, Tracing::HttpTracer&()); MOCK_METHOD0(threadLocal, ThreadLocal::Instance&()); MOCK_METHOD0(localInfo, const LocalInfo::LocalInfo&()); + MOCK_METHOD0(timeSource, TimeSource&()); MOCK_CONST_METHOD0(statsFlushInterval, std::chrono::milliseconds()); std::unique_ptr secret_manager_; @@ -349,6 +352,7 @@ class MockInstance : public Instance { testing::NiceMock init_manager_; testing::NiceMock listener_manager_; testing::NiceMock overload_manager_; + DangerousDeprecatedTestTime test_time_; Singleton::ManagerPtr singleton_manager_; }; @@ -402,6 +406,8 @@ class MockFactoryContext : public FactoryContext { MOCK_CONST_METHOD0(localInfo, const LocalInfo::LocalInfo&()); MOCK_CONST_METHOD0(listenerMetadata, const envoy::api::v2::core::Metadata&()); MOCK_METHOD0(systemTimeSource, SystemTimeSource&()); + MOCK_METHOD0(monotonicTimeSource, MonotonicTimeSource&()); + MOCK_METHOD0(timeSource, TimeSource&()); testing::NiceMock access_log_manager_; testing::NiceMock cluster_manager_; @@ -418,6 +424,8 @@ class MockFactoryContext : public FactoryContext { testing::NiceMock admin_; Stats::IsolatedStoreImpl listener_scope_; testing::NiceMock system_time_source_; + testing::NiceMock monotonic_time_source_; + TimeSource time_source_; }; class MockTransportSocketFactoryContext : public TransportSocketFactoryContext { @@ -425,14 +433,19 @@ class MockTransportSocketFactoryContext : public TransportSocketFactoryContext { MockTransportSocketFactoryContext(); ~MockTransportSocketFactoryContext(); + Secret::SecretManager& secretManager() override { return *(secret_manager_.get()); } + MOCK_METHOD0(sslContextManager, Ssl::ContextManager&()); MOCK_CONST_METHOD0(statsScope, Stats::Scope&()); MOCK_METHOD0(clusterManager, Upstream::ClusterManager&()); - MOCK_METHOD0(secretManager, Secret::SecretManager&()); MOCK_METHOD0(localInfo, const LocalInfo::LocalInfo&()); MOCK_METHOD0(dispatcher, Event::Dispatcher&()); MOCK_METHOD0(random, Envoy::Runtime::RandomGenerator&()); MOCK_METHOD0(stats, Stats::Store&()); + MOCK_METHOD1(setInitManager, void(Init::Manager&)); + MOCK_METHOD0(initManager, Init::Manager*()); + + std::unique_ptr secret_manager_; }; class MockListenerFactoryContext : public virtual MockFactoryContext, @@ -472,6 +485,18 @@ class MockHealthCheckerFactoryContext : public virtual HealthCheckerFactoryConte testing::NiceMock* event_logger_{}; }; +class MockAdminStream : public AdminStream { +public: + MockAdminStream(); + ~MockAdminStream(); + + MOCK_METHOD1(setEndStreamOnComplete, void(bool)); + MOCK_METHOD1(addOnDestroyCallback, void(std::function)); + MOCK_CONST_METHOD0(getRequestHeaders, Http::HeaderMap&()); + MOCK_CONST_METHOD0(getDecoderFilterCallbacks, + NiceMock&()); +}; + } // namespace Configuration } // namespace Server } // namespace Envoy diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index 5b9393d25a9a..befc62ecefeb 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -75,6 +75,7 @@ class MockHostDescription : public HostDescription { MOCK_CONST_METHOD0(address, Network::Address::InstanceConstSharedPtr()); MOCK_CONST_METHOD0(healthCheckAddress, Network::Address::InstanceConstSharedPtr()); + MOCK_METHOD1(setHealthCheckAddress, void(Network::Address::InstanceConstSharedPtr)); MOCK_CONST_METHOD0(canary, bool()); MOCK_METHOD1(canary, void(bool new_canary)); MOCK_CONST_METHOD0(metadata, const std::shared_ptr()); @@ -127,6 +128,7 @@ class MockHost : public Host { MOCK_CONST_METHOD0(address, Network::Address::InstanceConstSharedPtr()); MOCK_CONST_METHOD0(healthCheckAddress, Network::Address::InstanceConstSharedPtr()); + MOCK_METHOD1(setHealthCheckAddress, void(Network::Address::InstanceConstSharedPtr)); MOCK_CONST_METHOD0(canary, bool()); MOCK_METHOD1(canary, void(bool new_canary)); MOCK_CONST_METHOD0(metadata, const std::shared_ptr()); @@ -141,7 +143,9 @@ class MockHost : public Host { MOCK_CONST_METHOD0(healthChecker, HealthCheckHostMonitor&()); MOCK_METHOD1(healthFlagClear, void(HealthFlag flag)); MOCK_CONST_METHOD1(healthFlagGet, bool(HealthFlag flag)); + MOCK_CONST_METHOD0(getActiveHealthFailureType, ActiveHealthFailureType()); MOCK_METHOD1(healthFlagSet, void(HealthFlag flag)); + MOCK_METHOD1(setActiveHealthFailureType, void(ActiveHealthFailureType type)); MOCK_CONST_METHOD0(healthy, bool()); MOCK_CONST_METHOD0(hostname, const std::string&()); MOCK_CONST_METHOD0(outlierDetector, Outlier::DetectorHostMonitor&()); diff --git a/test/mocks/upstream/mocks.cc b/test/mocks/upstream/mocks.cc index 5be57cff244c..729af9809e75 100644 --- a/test/mocks/upstream/mocks.cc +++ b/test/mocks/upstream/mocks.cc @@ -87,7 +87,7 @@ MockThreadLocalCluster::MockThreadLocalCluster() { MockThreadLocalCluster::~MockThreadLocalCluster() {} -MockClusterManager::MockClusterManager() { +MockClusterManager::MockClusterManager() : time_source_(system_time_, monotonic_time_) { ON_CALL(*this, httpConnPoolForCluster(_, _, _, _)).WillByDefault(Return(&conn_pool_)); ON_CALL(*this, tcpConnPoolForCluster(_, _, _)).WillByDefault(Return(&tcp_conn_pool_)); ON_CALL(*this, httpAsyncClientForCluster(_)).WillByDefault(ReturnRef(async_client_)); diff --git a/test/mocks/upstream/mocks.h b/test/mocks/upstream/mocks.h index a19de4087863..6ba835f06560 100644 --- a/test/mocks/upstream/mocks.h +++ b/test/mocks/upstream/mocks.h @@ -120,6 +120,17 @@ class MockCluster : public Cluster { NiceMock priority_set_; }; +class MockLoadBalancerContext : public LoadBalancerContext { +public: + MOCK_METHOD0(computeHashKey, absl::optional()); + MOCK_METHOD0(metadataMatchCriteria, Router::MetadataMatchCriteria*()); + MOCK_CONST_METHOD0(downstreamConnection, const Network::Connection*()); + MOCK_CONST_METHOD0(downstreamHeaders, const Http::HeaderMap*()); + MOCK_METHOD2(determinePriorityLoad, const PriorityLoad&(const PrioritySet&, const PriorityLoad&)); + MOCK_METHOD1(shouldSelectAnotherHost, bool(const Host&)); + MOCK_CONST_METHOD0(hostSelectionRetryCount, uint32_t()); +}; + class MockLoadBalancer : public LoadBalancer { public: MockLoadBalancer(); @@ -196,6 +207,7 @@ class MockClusterManager : public ClusterManager { } ClusterManagerFactory& clusterManagerFactory() override { return cluster_manager_factory_; } + TimeSource& timeSource() override { return time_source_; } // Upstream::ClusterManager MOCK_METHOD2(addOrUpdateCluster, @@ -224,6 +236,11 @@ class MockClusterManager : public ClusterManager { MOCK_METHOD1(addThreadLocalClusterUpdateCallbacks, std::unique_ptr(ClusterUpdateCallbacks& callbacks)); + // TODO(jmarantz): Switch these to using mock-time. + ProdSystemTimeSource system_time_; + ProdMonotonicTimeSource monotonic_time_; + TimeSource time_source_; + NiceMock conn_pool_; NiceMock async_client_; NiceMock tcp_conn_pool_; diff --git a/test/proto/BUILD b/test/proto/BUILD index fa1674251719..3e339d903b55 100644 --- a/test/proto/BUILD +++ b/test/proto/BUILD @@ -19,11 +19,7 @@ envoy_proto_library( envoy_proto_library( name = "bookstore_proto", srcs = [":bookstore.proto"], - external_deps = [ - "api_httpbody_protos", - "http_api_protos", - "well_known_protos", - ], + external_deps = ["api_httpbody_protos"], ) envoy_proto_descriptor( diff --git a/test/server/BUILD b/test/server/BUILD index 064fe8fa72ef..4721ba2712b0 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -183,6 +183,7 @@ envoy_cc_fuzz_test( "//test/mocks/server:server_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:environment_lib", + "//test/test_common:test_time_lib", ] + envoy_all_extensions(), ) @@ -213,6 +214,7 @@ envoy_cc_test( "//test/integration:integration_lib", "//test/mocks/server:server_mocks", "//test/mocks/stats:stats_mocks", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) @@ -235,6 +237,7 @@ envoy_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", "//test/mocks/thread_local:thread_local_mocks", + "//test/test_common:test_time_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/server/config_validation/BUILD b/test/server/config_validation/BUILD index c57990c451c2..34bc8413b321 100644 --- a/test/server/config_validation/BUILD +++ b/test/server/config_validation/BUILD @@ -18,6 +18,7 @@ envoy_cc_test( "//source/server/config_validation:dns_lib", "//test/mocks/http:http_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_time_lib", ], ) @@ -74,6 +75,7 @@ envoy_cc_test( "//source/server/config_validation:dns_lib", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:test_time_lib", ], ) diff --git a/test/server/config_validation/async_client_test.cc b/test/server/config_validation/async_client_test.cc index 200e8afb6c20..53a172c3394e 100644 --- a/test/server/config_validation/async_client_test.cc +++ b/test/server/config_validation/async_client_test.cc @@ -6,6 +6,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/upstream/mocks.h" +#include "test/test_common/test_time.h" namespace Envoy { namespace Http { @@ -15,7 +16,8 @@ TEST(ValidationAsyncClientTest, MockedMethods) { MockAsyncClientCallbacks callbacks; MockAsyncClientStreamCallbacks stream_callbacks; - ValidationAsyncClient client; + DangerousDeprecatedTestTime test_time; + ValidationAsyncClient client(test_time.timeSource()); EXPECT_EQ(nullptr, client.send(std::move(message), callbacks, absl::optional())); EXPECT_EQ(nullptr, diff --git a/test/server/config_validation/config_fuzz_test.cc b/test/server/config_validation/config_fuzz_test.cc index 96f9e82ce107..e18d57c835dc 100644 --- a/test/server/config_validation/config_fuzz_test.cc +++ b/test/server/config_validation/config_fuzz_test.cc @@ -16,8 +16,9 @@ namespace Server { DEFINE_PROTO_FUZZER(const envoy::config::bootstrap::v2::Bootstrap& input) { testing::NiceMock options; TestComponentFactory component_factory; + Fuzz::PerTestEnvironment test_env; - const std::string bootstrap_path = TestEnvironment::temporaryPath("bootstrap.pb_text"); + const std::string bootstrap_path = test_env.temporaryPath("bootstrap.pb_text"); std::ofstream bootstrap_file(bootstrap_path); bootstrap_file << input.DebugString(); options.config_path_ = bootstrap_path; diff --git a/test/server/config_validation/dispatcher_test.cc b/test/server/config_validation/dispatcher_test.cc index aa38340100fc..3476ea525668 100644 --- a/test/server/config_validation/dispatcher_test.cc +++ b/test/server/config_validation/dispatcher_test.cc @@ -9,6 +9,7 @@ #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -22,9 +23,10 @@ class ConfigValidation : public ::testing::TestWithParam(std::chrono::milliseconds(1000)); - dispatcher_ = validation_->allocateDispatcher(); + dispatcher_ = validation_->allocateDispatcher(test_time_.timeSource()); } + DangerousDeprecatedTestTime test_time_; Event::DispatcherPtr dispatcher_; private: diff --git a/test/server/guarddog_impl_test.cc b/test/server/guarddog_impl_test.cc index 632caae9ab0d..84562189ab2d 100644 --- a/test/server/guarddog_impl_test.cc +++ b/test/server/guarddog_impl_test.cc @@ -21,6 +21,15 @@ using testing::NiceMock; namespace Envoy { namespace Server { +class GuardDogTestBase : public testing::Test { +protected: + GuardDogTestBase() : time_source_(system_time_source_, monotonic_time_source_) {} + + NiceMock monotonic_time_source_; + ProdSystemTimeSource system_time_source_; + TimeSource time_source_; +}; + /** * Death test caveat: Because of the way we die gcov doesn't receive coverage * information from the forked process that is checked for succesful death. @@ -28,12 +37,12 @@ namespace Server { * green in the coverage report. However, rest assured from the results of the * test: these lines are in fact covered. */ -class GuardDogDeathTest : public testing::Test { +class GuardDogDeathTest : public GuardDogTestBase { protected: GuardDogDeathTest() : config_kill_(1000, 1000, 100, 1000), config_multikill_(1000, 1000, 1000, 500), mock_time_(0) { - ON_CALL(time_source_, currentTime()).WillByDefault(testing::Invoke([this]() { + ON_CALL(monotonic_time_source_, currentTime()).WillByDefault(testing::Invoke([this]() { return std::chrono::steady_clock::time_point(std::chrono::milliseconds(mock_time_)); })); } @@ -67,7 +76,6 @@ class GuardDogDeathTest : public testing::Test { NiceMock config_kill_; NiceMock config_multikill_; NiceMock fakestats_; - NiceMock time_source_; std::atomic mock_time_; std::unique_ptr guard_dog_; WatchDogSharedPtr unpet_dog_; @@ -129,10 +137,10 @@ TEST_F(GuardDogAlmostDeadTest, NearDeathTest) { } } -class GuardDogMissTest : public testing::Test { +class GuardDogMissTest : public GuardDogTestBase { protected: GuardDogMissTest() : config_miss_(500, 1000, 0, 0), config_mega_(1000, 500, 0, 0), mock_time_(0) { - ON_CALL(time_source_, currentTime()).WillByDefault(testing::Invoke([this]() { + ON_CALL(monotonic_time_source_, currentTime()).WillByDefault(testing::Invoke([this]() { return std::chrono::steady_clock::time_point(std::chrono::milliseconds(mock_time_)); })); } @@ -140,7 +148,6 @@ class GuardDogMissTest : public testing::Test { NiceMock config_miss_; NiceMock config_mega_; Stats::IsolatedStoreImpl stats_store_; - NiceMock time_source_; std::atomic mock_time_; }; @@ -166,7 +173,7 @@ TEST_F(GuardDogMissTest, MissTest) { TEST_F(GuardDogMissTest, MegaMissTest) { // This test checks the actual collected statistics after doing some timer // advances that should and shouldn't increment the counters. - ON_CALL(time_source_, currentTime()).WillByDefault(testing::Invoke([this]() { + ON_CALL(monotonic_time_source_, currentTime()).WillByDefault(testing::Invoke([this]() { return std::chrono::steady_clock::time_point(std::chrono::milliseconds(mock_time_)); })); GuardDogImpl gd(stats_store_, config_mega_, time_source_); @@ -189,7 +196,7 @@ TEST_F(GuardDogMissTest, MissCountTest) { // This tests a flake discovered in the MissTest where real timeout or // spurious condition_variable wakeup causes the counter to get incremented // more than it should be. - ON_CALL(time_source_, currentTime()).WillByDefault(testing::Invoke([this]() { + ON_CALL(monotonic_time_source_, currentTime()).WillByDefault(testing::Invoke([this]() { return std::chrono::steady_clock::time_point(std::chrono::milliseconds(mock_time_)); })); GuardDogImpl gd(stats_store_, config_miss_, time_source_); @@ -229,34 +236,30 @@ TEST_F(GuardDogMissTest, MissCountTest) { sometimes_pet_dog = nullptr; } -TEST(GuardDogBasicTest, StartStopTest) { +TEST_F(GuardDogTestBase, StartStopTest) { NiceMock stats; NiceMock config(0, 0, 0, 0); - NiceMock time_source; - GuardDogImpl gd(stats, config, time_source); + GuardDogImpl gd(stats, config, time_source_); } -TEST(GuardDogBasicTest, LoopIntervalNoKillTest) { +TEST_F(GuardDogTestBase, LoopIntervalNoKillTest) { NiceMock stats; NiceMock config(40, 50, 0, 0); - NiceMock time_source; - GuardDogImpl gd(stats, config, time_source); + GuardDogImpl gd(stats, config, time_source_); EXPECT_EQ(gd.loopIntervalForTest(), 40); } -TEST(GuardDogBasicTest, LoopIntervalTest) { +TEST_F(GuardDogTestBase, LoopIntervalTest) { NiceMock stats; NiceMock config(100, 90, 1000, 500); - NiceMock time_source; - GuardDogImpl gd(stats, config, time_source); + GuardDogImpl gd(stats, config, time_source_); EXPECT_EQ(gd.loopIntervalForTest(), 90); } -TEST(WatchDogBasicTest, ThreadIdTest) { +TEST_F(GuardDogTestBase, WatchDogThreadIdTest) { NiceMock stats; NiceMock config(100, 90, 1000, 500); - NiceMock time_source; - GuardDogImpl gd(stats, config, time_source); + GuardDogImpl gd(stats, config, time_source_); auto watched_dog = gd.createWatchDog(123); EXPECT_EQ(watched_dog->threadId(), 123); gd.stopWatching(watched_dog); @@ -268,7 +271,7 @@ TEST(WatchDogBasicTest, ThreadIdTest) { // // The WatchDog/GuardDog relies on this being a lock free atomic for perf reasons so some workaround // will be required if this test starts failing. -TEST(WatchDogTimeTest, AtomicIsAtomicTest) { +TEST_F(GuardDogTestBase, AtomicIsAtomicTest) { ProdMonotonicTimeSource time_source; std::atomic atomic_time; ASSERT_EQ(atomic_time.is_lock_free(), true); diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index aba15f05284c..1aa358ad19d7 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -51,10 +51,10 @@ class ListenerHandle { class ListenerManagerImplTest : public testing::Test { public: - ListenerManagerImplTest() { + ListenerManagerImplTest() : time_source_(system_time_source_, monotonic_time_source_) { EXPECT_CALL(worker_factory_, createWorker_()).WillOnce(Return(worker_)); manager_.reset( - new ListenerManagerImpl(server_, listener_factory_, worker_factory_, system_time_source_)); + new ListenerManagerImpl(server_, listener_factory_, worker_factory_, time_source_)); } /** @@ -116,6 +116,8 @@ class ListenerManagerImplTest : public testing::Test { std::unique_ptr manager_; NiceMock guard_dog_; NiceMock system_time_source_; + ProdMonotonicTimeSource monotonic_time_source_; + TimeSource time_source_; }; class ListenerManagerImplWithRealFiltersTest : public ListenerManagerImplTest { diff --git a/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6313779791921152 b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6313779791921152 new file mode 100644 index 000000000000..6d31a95c8ebd --- /dev/null +++ b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-6313779791921152 @@ -0,0 +1,14 @@ +admin { + access_log_path: "W" + address { + pipe { + path: "W" + } + } +} +stats_config { + stats_tags { +tag_name: "W" + regex: "t\n \00000\\x000\00000\000\000a000\000 \000\\00\\D00\\n \000N000000\000\000\000\000L000\000\\B\\\n ^^^^^^^^^^^000N000000\000\000\000\000L000\000\\B\\\n ^^^^^^^^^^^000N0���00000\000(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((<((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII����00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII����00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII����00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII����00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII����00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII����00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\00\000\000IIIIIIIIII00\000\0^^^^^^^^^^^^^^^^^^^^^^000N000000\000((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\000\000\000L00000(((((((\000\000\000L00000\0\000\000\0000\\cCb\ + } +} diff --git a/test/server/server_fuzz_test.cc b/test/server/server_fuzz_test.cc index afc1bed58e8e..ff68f39a1814 100644 --- a/test/server/server_fuzz_test.cc +++ b/test/server/server_fuzz_test.cc @@ -12,10 +12,43 @@ #include "test/mocks/server/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" +#include "test/test_common/test_time.h" namespace Envoy { namespace Server { +void makePortHermetic(envoy::api::v2::core::Address& address) { + if (address.has_socket_address()) { + address.mutable_socket_address()->set_port_value(0); + } +} + +envoy::config::bootstrap::v2::Bootstrap +makeHermeticPathsAndPorts(Fuzz::PerTestEnvironment& test_env, + const envoy::config::bootstrap::v2::Bootstrap& input) { + envoy::config::bootstrap::v2::Bootstrap output(input); + // This is not a complete list of places where we need to zero out ports or sanitize paths, so we + // should adapt it as we go and encounter places that we need to stabilize server test flakes. + // config_validation_fuzz_test doesn't need to do this sanitization, so should pickup the coverage + // we lose here. If we don't sanitize here, we get flakes due to port bind conflicts, file + // conflicts, etc. + output.mutable_admin()->set_access_log_path(test_env.temporaryPath("admin.log")); + if (output.admin().has_address()) { + makePortHermetic(*output.mutable_admin()->mutable_address()); + } + for (auto& listener : *output.mutable_static_resources()->mutable_listeners()) { + if (listener.has_address()) { + makePortHermetic(*listener.mutable_address()); + } + } + for (auto& cluster : *output.mutable_static_resources()->mutable_clusters()) { + for (auto& host : *cluster.mutable_hosts()) { + makePortHermetic(host); + } + } + return output; +} + DEFINE_PROTO_FUZZER(const envoy::config::bootstrap::v2::Bootstrap& input) { testing::NiceMock options; DefaultTestHooks hooks; @@ -24,13 +57,15 @@ DEFINE_PROTO_FUZZER(const envoy::config::bootstrap::v2::Bootstrap& input) { Thread::MutexBasicLockable fakelock; TestComponentFactory component_factory; ThreadLocal::InstanceImpl thread_local_instance; + DangerousDeprecatedTestTime test_time; + Fuzz::PerTestEnvironment test_env; RELEASE_ASSERT(Envoy::Server::validateProtoDescriptors(), ""); { - const std::string bootstrap_path = TestEnvironment::temporaryPath("bootstrap.pb_text"); + const std::string bootstrap_path = test_env.temporaryPath("bootstrap.pb_text"); std::ofstream bootstrap_file(bootstrap_path); - bootstrap_file << input.DebugString(); + bootstrap_file << makeHermeticPathsAndPorts(test_env, input).DebugString(); options.config_path_ = bootstrap_path; options.v2_config_only_ = true; options.log_level_ = Fuzz::Runner::logLevel(); @@ -38,8 +73,9 @@ DEFINE_PROTO_FUZZER(const envoy::config::bootstrap::v2::Bootstrap& input) { try { auto server = std::make_unique( - options, std::make_shared("127.0.0.1"), hooks, restart, - stats_store, fakelock, component_factory, std::make_unique(), + options, test_time.timeSource(), + std::make_shared("127.0.0.1"), hooks, restart, stats_store, + fakelock, component_factory, std::make_unique(), thread_local_instance); } catch (const EnvoyException& ex) { ENVOY_LOG_MISC(debug, "Controlled EnvoyException exit: {}", ex.what()); diff --git a/test/server/server_test.cc b/test/server/server_test.cc index b6726ed75ca5..89607ff98edb 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -8,6 +8,7 @@ #include "test/mocks/server/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -112,7 +113,7 @@ class ServerInstanceImplTest : public testing::TestWithParam>(), thread_local_)); @@ -128,7 +129,7 @@ class ServerInstanceImplTest : public testing::TestWithParam>(), thread_local_)); @@ -144,6 +145,7 @@ class ServerInstanceImplTest : public testing::TestWithParam server_; }; @@ -173,9 +175,12 @@ TEST_P(ServerInstanceImplTest, V1ConfigFallback) { TEST_P(ServerInstanceImplTest, Stats) { options_.service_cluster_name_ = "some_cluster_name"; options_.service_node_name_ = "some_node_name"; + options_.concurrency_ = 2; + options_.hot_restart_epoch_ = 3; initialize(std::string()); EXPECT_NE(nullptr, TestUtility::findCounter(stats_store_, "server.watchdog_miss")); - EXPECT_NE(nullptr, TestUtility::findGauge(stats_store_, "server.hot_restart_epoch")); + EXPECT_EQ(2L, TestUtility::findGauge(stats_store_, "server.concurrency")->value()); + EXPECT_EQ(3L, TestUtility::findGauge(stats_store_, "server.hot_restart_epoch")->value()); } // Validate server localInfo() from bootstrap Node. @@ -293,7 +298,7 @@ TEST_P(ServerInstanceImplTest, LogToFileError) { TEST_P(ServerInstanceImplTest, NoOptionsPassed) { EXPECT_THROW_WITH_MESSAGE( server_.reset(new InstanceImpl( - options_, + options_, test_time_.timeSource(), Network::Address::InstanceConstSharedPtr(new Network::Address::Ipv4Instance("127.0.0.1")), hooks_, restart_, stats_store_, fakelock_, component_factory_, std::make_unique>(), thread_local_)), diff --git a/test/server/worker_impl_test.cc b/test/server/worker_impl_test.cc index 50a2d7fff083..af730229348f 100644 --- a/test/server/worker_impl_test.cc +++ b/test/server/worker_impl_test.cc @@ -5,6 +5,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" #include "test/mocks/thread_local/mocks.h" +#include "test/test_common/test_time.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -29,7 +30,8 @@ class WorkerImplTest : public testing::Test { } NiceMock tls_; - Event::DispatcherImpl* dispatcher_ = new Event::DispatcherImpl(); + DangerousDeprecatedTestTime test_time; + Event::DispatcherImpl* dispatcher_ = new Event::DispatcherImpl(test_time.timeSource()); Network::MockConnectionHandler* handler_ = new Network::MockConnectionHandler(); NiceMock guard_dog_; DefaultTestHooks hooks_; diff --git a/test/test_common/BUILD b/test/test_common/BUILD index d861e0b99ec7..0d7db72237ce 100644 --- a/test/test_common/BUILD +++ b/test/test_common/BUILD @@ -110,6 +110,15 @@ envoy_cc_library( ], ) +envoy_cc_test_library( + name = "test_time_lib", + srcs = ["test_time.cc"], + hdrs = ["test_time.h"], + deps = [ + "//source/common/common:utility_lib", + ], +) + envoy_cc_library( name = "tls_utility_lib", srcs = ["tls_utility.cc"], diff --git a/test/test_common/environment.cc b/test/test_common/environment.cc index a761afcf3849..e3bc95da7264 100644 --- a/test/test_common/environment.cc +++ b/test/test_common/environment.cc @@ -3,9 +3,11 @@ #include #include -#ifndef __APPLE__ +#ifdef __has_include +#if __has_include() #include #endif +#endif #include #include #include @@ -29,19 +31,6 @@ namespace Envoy { namespace { -// Create the parent directory of a given filesystem path. -void createParentPath(const std::string& path) { -#ifdef __APPLE__ - // No support in Clang OS X libc++ today for std::filesystem. - RELEASE_ASSERT(::system(("mkdir -p $(dirname " + path + ")").c_str()) == 0, ""); -#else - // We don't want to rely on mkdir etc. if we can avoid it, since it might not - // exist in some environments such as ClusterFuzz. - std::experimental::filesystem::create_directories( - std::experimental::filesystem::path(path).parent_path()); -#endif -} - std::string getOrCreateUnixDomainSocketDirectory() { const char* path = ::getenv("TEST_UDSDIR"); if (path != nullptr) { @@ -62,7 +51,10 @@ std::string getTemporaryDirectory() { if (::getenv("TMPDIR")) { return TestEnvironment::getCheckedEnvVar("TMPDIR"); } - return "/tmp"; + char test_tmpdir[] = "/tmp/envoy_test_tmp.XXXXXX"; + RELEASE_ASSERT(::mkdtemp(test_tmpdir) != nullptr, + fmt::format("Failed to create tmpdir {} {}", test_tmpdir, strerror(errno))); + return std::string(test_tmpdir); } // Allow initializeOptions() to remember CLI args for getOptions(). @@ -71,6 +63,28 @@ char** argv_; } // namespace +void TestEnvironment::createPath(const std::string& path) { +#ifdef __cpp_lib_experimental_filesystem + // We don't want to rely on mkdir etc. if we can avoid it, since it might not + // exist in some environments such as ClusterFuzz. + std::experimental::filesystem::create_directories(std::experimental::filesystem::path(path)); +#else + // No support on this system for std::experimental::filesystem. + RELEASE_ASSERT(::system(("mkdir -p " + path).c_str()) == 0, ""); +#endif +} + +void TestEnvironment::createParentPath(const std::string& path) { +#ifdef __cpp_lib_experimental_filesystem + // We don't want to rely on mkdir etc. if we can avoid it, since it might not + // exist in some environments such as ClusterFuzz. + std::experimental::filesystem::create_directories( + std::experimental::filesystem::path(path).parent_path()); +#else + // No support on this system for std::experimental::filesystem. + RELEASE_ASSERT(::system(("mkdir -p $(dirname " + path + ")").c_str()) == 0, ""); +#endif +} absl::optional TestEnvironment::getOptionalEnvVar(const std::string& var) { const char* path = ::getenv(var.c_str()); if (path == nullptr) { diff --git a/test/test_common/environment.h b/test/test_common/environment.h index 993b20cfe794..b0d9dea4e37e 100644 --- a/test/test_common/environment.h +++ b/test/test_common/environment.h @@ -175,5 +175,17 @@ class TestEnvironment { * @return string the contents of the file. */ static std::string readFileToStringForTest(const std::string& filename); + + /** + * Create a path on the filesystem (mkdir -p ... equivalent). + * @param path. + */ + static void createPath(const std::string& path); + + /** + * Create a parent path on the filesystem (mkdir -p $(dirname ...) equivalent). + * @param path. + */ + static void createParentPath(const std::string& path); }; } // namespace Envoy diff --git a/test/test_common/test_time.cc b/test/test_common/test_time.cc new file mode 100644 index 000000000000..266b070f2de9 --- /dev/null +++ b/test/test_common/test_time.cc @@ -0,0 +1,10 @@ +#include "test/test_common/test_time.h" + +#include "common/common/utility.h" + +namespace Envoy { + +DangerousDeprecatedTestTime::DangerousDeprecatedTestTime() + : time_source_(system_time_, monotonic_time_) {} + +} // namespace Envoy diff --git a/test/test_common/test_time.h b/test/test_common/test_time.h new file mode 100644 index 000000000000..d57fda876143 --- /dev/null +++ b/test/test_common/test_time.h @@ -0,0 +1,28 @@ +#pragma once + +#include "common/common/utility.h" + +namespace Envoy { + +// Instantiates real-time sources for testing purposes. In general, this is a +// bad idea, and tests should use simulated or mock time. +// +// TODO(#4160): change all references to this class to instantiate instead to +// some kind of mock or simulated-time source. +class DangerousDeprecatedTestTime { +public: + DangerousDeprecatedTestTime(); + + TimeSource& timeSource() { return time_source_; } + +private: + // TODO(#4160): Add a 'mode' enum arg to the constructor, which + // instantiates mock or perhaps fake time here rather than real-time, which is + // makes testing non-deterministic and hard to debug. It should be easy, on + // a test-by-test basis, to switch to mock time. + ProdSystemTimeSource system_time_; + ProdMonotonicTimeSource monotonic_time_; + TimeSource time_source_; +}; + +} // namespace Envoy diff --git a/test/tools/router_check/router.cc b/test/tools/router_check/router.cc index b52dd4948878..8f853276ca5b 100644 --- a/test/tools/router_check/router.cc +++ b/test/tools/router_check/router.cc @@ -6,6 +6,7 @@ #include #include "common/network/utility.h" +#include "common/protobuf/utility.h" #include "common/request_info/request_info_impl.h" #include "test/test_common/printers.h" @@ -40,18 +41,13 @@ ToolConfig::ToolConfig(std::unique_ptr headers, int ran : headers_(std::move(headers)), random_value_(random_value) {} // static -RouterCheckTool RouterCheckTool::create(const std::string& router_config_json) { +RouterCheckTool RouterCheckTool::create(const std::string& router_config_file) { // TODO(hennna): Allow users to load a full config and extract the route configuration from it. - Json::ObjectSharedPtr loader = Json::Factory::loadFromFile(router_config_json); envoy::api::v2::RouteConfiguration route_config; - // TODO(ambuc): Add a CLI option to allow for a maxStatNameLength constraint - Stats::StatsOptionsImpl stats_options; - Config::RdsJson::translateRouteConfiguration(*loader, route_config, stats_options); + MessageUtil::loadFromFile(router_config_file, route_config); - std::unique_ptr> factory_context( - std::make_unique>()); - std::unique_ptr config( - new Router::ConfigImpl(route_config, *factory_context, false)); + auto factory_context = std::make_unique>(); + auto config = std::make_unique(route_config, *factory_context, false); return RouterCheckTool(std::move(factory_context), std::move(config)); } @@ -76,42 +72,30 @@ bool RouterCheckTool::compareEntriesInJson(const std::string& expected_route_jso } Json::ObjectSharedPtr validate = check_config->getObject("validate"); - const std::unordered_map> - checkers = { - {"cluster_name", - [this](ToolConfig& tool_config, const std::string& expected) -> bool { - return compareCluster(tool_config, expected); - }}, - {"virtual_cluster_name", - [this](ToolConfig& tool_config, const std::string& expected) -> bool { - return compareVirtualCluster(tool_config, expected); - }}, - {"virtual_host_name", - [this](ToolConfig& tool_config, const std::string& expected) -> bool { - return compareVirtualHost(tool_config, expected); - }}, - {"path_rewrite", - [this](ToolConfig& tool_config, const std::string& expected) -> bool { - return compareRewritePath(tool_config, expected); - }}, - {"host_rewrite", - [this](ToolConfig& tool_config, const std::string& expected) -> bool { - return compareRewriteHost(tool_config, expected); - }}, - {"path_redirect", - [this](ToolConfig& tool_config, const std::string& expected) -> bool { - return compareRedirectPath(tool_config, expected); - }}, - }; - - // Call appropriate function for each match case - for (std::pair> test : checkers) { + using checkerFunc = std::function; + const std::unordered_map checkers = { + {"cluster_name", + [this](auto&... params) -> bool { return this->compareCluster(params...); }}, + {"virtual_cluster_name", + [this](auto&... params) -> bool { return this->compareVirtualCluster(params...); }}, + {"virtual_host_name", + [this](auto&... params) -> bool { return this->compareVirtualHost(params...); }}, + {"path_rewrite", + [this](auto&... params) -> bool { return this->compareRewritePath(params...); }}, + {"host_rewrite", + [this](auto&... params) -> bool { return this->compareRewriteHost(params...); }}, + {"path_redirect", + [this](auto&... params) -> bool { return this->compareRedirectPath(params...); }}, + }; + + // Call appropriate function for each match case. + for (const auto& test : checkers) { if (validate->hasObject(test.first)) { - std::string expected = validate->getString(test.first); + const std::string& expected = validate->getString(test.first); if (tool_config.route_ == nullptr) { compareResults("", expected, test.first); } else { - if (!test.second(tool_config, validate->getString(test.first))) { + if (!test.second(tool_config, expected)) { no_failures = false; } } @@ -137,6 +121,7 @@ bool RouterCheckTool::compareEntriesInJson(const std::string& expected_route_jso } } } + return no_failures; } diff --git a/test/tools/router_check/router.h b/test/tools/router_check/router.h index 09f1cdc85999..733fe37c7075 100644 --- a/test/tools/router_check/router.h +++ b/test/tools/router_check/router.h @@ -45,13 +45,15 @@ struct ToolConfig { class RouterCheckTool : Logger::Loggable { public: /** - * @param router_config_json router config json file. + * @param router_config_file v2 router config file. * @return RouterCheckTool a RouterCheckTool instance with member variables set by the router - * config json file. + * config file. * */ - static RouterCheckTool create(const std::string& router_config_json); + static RouterCheckTool create(const std::string& router_config_file); /** + * TODO(tonya11en): Use a YAML format for the expected routes. This will require a proto. + * * @param expected_route_json tool config json file. * @return bool if all routes match what is expected. */ diff --git a/test/tools/router_check/test/BUILD b/test/tools/router_check/test/BUILD index 5cb500c94d13..f99a5669b39e 100644 --- a/test/tools/router_check/test/BUILD +++ b/test/tools/router_check/test/BUILD @@ -19,5 +19,8 @@ envoy_sh_test( filegroup( name = "configs", - srcs = glob(["config/*.json"]), + srcs = glob([ + "config/*.yaml", + "config/*.json", + ]), ) diff --git a/test/tools/router_check/test/config/ClusterHeader.json b/test/tools/router_check/test/config/ClusterHeader.json deleted file mode 100644 index 0bef88213e88..000000000000 --- a/test/tools/router_check/test/config/ClusterHeader.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "virtual_hosts": [ - { - "name": "local_service", - "domains": ["*"], - "routes": [ - { - "prefix": "/foo", - "cluster_header": ":authority" - }, - { - "prefix": "/bar", - "cluster_header": "some_header", - "timeout_ms": 0 - } - ] - } - ] -} diff --git a/test/tools/router_check/test/config/ClusterHeader.yaml b/test/tools/router_check/test/config/ClusterHeader.yaml new file mode 100644 index 000000000000..493d279db7e6 --- /dev/null +++ b/test/tools/router_check/test/config/ClusterHeader.yaml @@ -0,0 +1,17 @@ +virtual_hosts: +- name: local_service + domains: + - '*' + routes: + - match: + prefix: "/foo" + headers: + - name: ":authority" + route: + cluster_header: ":authority" + - match: + prefix: "/bar" + route: + cluster_header: "some_header" + timeout: + nanos: 0 diff --git a/test/tools/router_check/test/config/ContentType.json b/test/tools/router_check/test/config/ContentType.json deleted file mode 100644 index 479b804333c6..000000000000 --- a/test/tools/router_check/test/config/ContentType.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "virtual_hosts": [ - { - "name": "local_service", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "local_service_grpc", - "headers" : [ - {"name": "content-type", "value": "application/grpc"} - ] - }, - { - "prefix": "/", - "cluster": "local_service" - } - ] - } - ] -} diff --git a/test/tools/router_check/test/config/ContentType.yaml b/test/tools/router_check/test/config/ContentType.yaml new file mode 100644 index 000000000000..819eade4d088 --- /dev/null +++ b/test/tools/router_check/test/config/ContentType.yaml @@ -0,0 +1,16 @@ +virtual_hosts: +- name: local_service + domains: + - '*' + routes: + - match: + prefix: "/" + headers: + - name: "content-type" + exact_match: "application/grpc" + route: + cluster: local_service_grpc + - match: + prefix: "/" + route: + cluster: local_service diff --git a/test/tools/router_check/test/config/HeaderMatchedRouting.json b/test/tools/router_check/test/config/HeaderMatchedRouting.json deleted file mode 100644 index 715da18a165b..000000000000 --- a/test/tools/router_check/test/config/HeaderMatchedRouting.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "virtual_hosts": [ - { - "name": "local_service", - "domains": ["*"], - "routes": [ - { - "prefix": "/", - "cluster": "local_service_with_headers", - "headers" : [ - {"name": "test_header", "value": "test"} - ] - }, - { - "prefix": "/", - "cluster": "local_service_with_multiple_headers", - "headers" : [ - {"name": "test_header_multiple1", "value": "test1"}, - {"name": "test_header_multiple2", "value": "test2"} - ] - }, - { - "prefix": "/", - "cluster": "local_service_with_empty_headers", - "headers" : [ - {"name": "test_header_presence"} - ] - }, - { - "prefix": "/", - "cluster": "local_service_with_header_pattern_set_regex", - "headers" : [ - {"name": "test_header_pattern", "value": "^user=test-\\d+$", "regex": true} - ] - }, - { - "prefix": "/", - "cluster": "local_service_with_header_pattern_unset_regex", - "headers" : [ - {"name": "test_header_pattern", "value": "^customer=test-\\d+$"} - ] - }, - { - "prefix": "/", - "cluster": "local_service_without_headers" - } - ] - } - ] -} diff --git a/test/tools/router_check/test/config/HeaderMatchedRouting.yaml b/test/tools/router_check/test/config/HeaderMatchedRouting.yaml new file mode 100644 index 000000000000..5b1e6309f8c4 --- /dev/null +++ b/test/tools/router_check/test/config/HeaderMatchedRouting.yaml @@ -0,0 +1,46 @@ +virtual_hosts: +- name: local_service + domains: + - '*' + routes: + - match: + prefix: / + headers: + - name: test_header + exact_match: test + route: + cluster: local_service_with_headers + - match: + prefix: / + headers: + - name: test_header_multiple1 + exact_match: test1 + - name: test_header_multiple2 + exact_match: test2 + route: + cluster: local_service_with_multiple_headers + + - match: + prefix: / + headers: + - name: test_header_presence + route: + cluster: local_service_with_empty_headers + - match: + prefix: / + headers: + - name: test_header_pattern + regex_match: ^user=test-\d+$ + route: + cluster: local_service_with_header_pattern_set_regex + - match: + prefix: / + headers: + - name: test_header_pattern + exact_match: ^customer=test-\d+$ + route: + cluster: local_service_with_header_pattern_unset_regex + - match: + prefix: / + route: + cluster: local_service_without_headers diff --git a/test/tools/router_check/test/config/Redirect.json b/test/tools/router_check/test/config/Redirect.json deleted file mode 100644 index 54e79a8d7f9d..000000000000 --- a/test/tools/router_check/test/config/Redirect.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "virtual_hosts": [ - { - "name": "www2", - "domains": ["www.lyft.com"], - "require_ssl": "all", - "routes": [ - { - "prefix": "/", - "cluster": "www2" - } - ] - }, - { - "name": "api", - "domains": ["api.lyft.com"], - "require_ssl": "external_only", - "routes": [ - { - "prefix": "/", - "cluster": "www2" - } - ] - }, - { - "name": "redirect", - "domains": ["redirect.lyft.com"], - "routes": [ - { - "prefix": "/foo", - "host_redirect": "new.lyft.com" - }, - { - "prefix": "/bar", - "path_redirect": "/new_bar" - }, - { - "prefix": "/baz", - "host_redirect": "new.lyft.com", - "path_redirect": "/new_baz" - } - ] - } - ] -} diff --git a/test/tools/router_check/test/config/Redirect.yaml b/test/tools/router_check/test/config/Redirect.yaml new file mode 100644 index 000000000000..c5b3b34b7c16 --- /dev/null +++ b/test/tools/router_check/test/config/Redirect.yaml @@ -0,0 +1,37 @@ +virtual_hosts: +- name: www2 + domains: + - www.lyft.com + require_tls: ALL + routes: + - match: + prefix: / + route: + cluster: www2 +- name: api + domains: + - api.lyft.com + require_tls: EXTERNAL_ONLY + routes: + - match: + prefix: / + route: + cluster: www2 +- name: redirect + domains: + - redirect.lyft.com + routes: + - match: + prefix: /foo + redirect: + host_redirect: new.lyft.com + + - match: + prefix: /bar + redirect: + path_redirect: /new_bar + - match: + prefix: /baz + redirect: + host_redirect: new.lyft.com + path_redirect: /new_baz diff --git a/test/tools/router_check/test/config/Redirect2.json b/test/tools/router_check/test/config/Redirect2.json deleted file mode 100644 index 65344263f501..000000000000 --- a/test/tools/router_check/test/config/Redirect2.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "virtual_hosts": [ - { - "name": "www2", - "domains": ["www.lyft.com"], - "routes": [ - { - "prefix": "/", - "cluster": "www2" - } - ] - }, - { - "name": "redirect", - "domains": ["redirect.lyft.com"], - "routes": [ - { - "prefix": "/foo", - "host_redirect": "new.lyft.com" - } - ] - } - ] -} diff --git a/test/tools/router_check/test/config/Redirect2.yaml b/test/tools/router_check/test/config/Redirect2.yaml new file mode 100644 index 000000000000..181265d34d37 --- /dev/null +++ b/test/tools/router_check/test/config/Redirect2.yaml @@ -0,0 +1,17 @@ +virtual_hosts: +- name: www2 + domains: + - www.lyft.com + routes: + - match: + prefix: / + route: + cluster: www2 +- name: redirect + domains: + - redirect.lyft.com + routes: + - match: + prefix: /foo + redirect: + host_redirect: new.lyft.com diff --git a/test/tools/router_check/test/config/Redirect3.json b/test/tools/router_check/test/config/Redirect3.json deleted file mode 100644 index 1e09088bc62f..000000000000 --- a/test/tools/router_check/test/config/Redirect3.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "virtual_hosts": [ - { - "name": "www2", - "domains": ["www.lyft.com"], - "routes": [ - { - "prefix": "/", - "weighted_clusters": { - "clusters" : [{ "name" : "www2", "weight" : 100 }] - } - } - ] - }, - { - "name": "redirect", - "domains": ["redirect.lyft.com"], - "routes": [ - { - "prefix": "/foo", - "host_redirect": "new.lyft.com" - } - ] - } - ] -} diff --git a/test/tools/router_check/test/config/Redirect3.yaml b/test/tools/router_check/test/config/Redirect3.yaml new file mode 100644 index 000000000000..44cceab82153 --- /dev/null +++ b/test/tools/router_check/test/config/Redirect3.yaml @@ -0,0 +1,21 @@ +virtual_hosts: +- name: www2 + domains: + - www.lyft.com + routes: + - match: + prefix: / + route: + weighted_clusters: + clusters: + - name: www2 + weight: 100 + total_weight: 100 +- name: redirect + domains: + - redirect.lyft.com + routes: + - match: + prefix: /foo + redirect: + host_redirect: new.lyft.com diff --git a/test/tools/router_check/test/config/TestRoutes.json b/test/tools/router_check/test/config/TestRoutes.json deleted file mode 100644 index 9cf8151929a4..000000000000 --- a/test/tools/router_check/test/config/TestRoutes.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "virtual_hosts": [ - { - "name": "www2", - "domains": ["lyft.com", "www.lyft.com", "w.lyft.com", "ww.lyft.com", "wwww.lyft.com"], - "routes": [ - { - "prefix": "/new_endpoint", - "prefix_rewrite": "/api/new_endpoint", - "cluster": "www2" - }, - { - "path": "/", - "cluster": "root_www2" - }, - { - "prefix": "/", - "cluster": "www2" - } - ] - }, - { - "name": "www2_staging", - "domains": ["www-staging.lyft.net", "www-staging-orca.lyft.com"], - "routes": [ - { - "prefix": "/", - "cluster": "www2_staging" - } - ] - }, - { - "name": "default", - "domains": ["*"], - "routes": [ - { - "prefix": "/api/application_data", - "cluster": "ats" - }, - { - "path": "/api/locations", - "cluster": "locations", - "prefix_rewrite": "/rewrote", - "case_sensitive": false - }, - { - "prefix": "/api/leads/me", - "cluster": "ats" - }, - { - "prefix": "/host/rewrite/me", - "cluster": "ats", - "host_rewrite": "new_host" - }, - { - "prefix": "/oldhost/rewrite/me", - "cluster": "ats", - "host_rewrite": "new_oldhost" - }, - { - "path": "/foo", - "prefix_rewrite": "/bar", - "cluster": "instant-server", - "case_sensitive": true - }, - { - "path": "/tar", - "prefix_rewrite": "/car", - "cluster": "instant-server", - "case_sensitive": false - }, - { - "prefix": "/newhost/rewrite/me", - "cluster": "ats", - "host_rewrite": "new_host", - "case_sensitive": false - }, - { - "path": "/FOOD", - "prefix_rewrite": "/cAndy", - "cluster": "ats", - "case_sensitive":false - }, - { - "path": "/ApplEs", - "prefix_rewrite": "/oranGES", - "cluster": "instant-server", - "case_sensitive": true - }, - { - "prefix": "/customheaders", - "cluster": "ats", - "host_rewrite": "new_host", - "request_headers_to_add":[ - {"key": "X-Client-IP", "value":"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"} - ] - }, - { - "prefix": "/", - "cluster": "instant-server", - "timeout_ms": 30000 - } - ], - "virtual_clusters": [ - {"pattern": "^/rides$", "method": "POST", "name": "ride_request"}, - {"pattern": "^/rides/\\d+$", "method": "PUT", "name": "update_ride"}, - {"pattern": "^/users/\\d+/chargeaccounts$", "method": "POST", "name": "cc_add"}, - {"pattern": "^/users/\\d+/chargeaccounts/(?!validate)\\w+$", "method": "PUT", - "name": "cc_add"}, - {"pattern": "^/users$", "method": "POST", "name": "create_user_login"}, - {"pattern": "^/users/\\d+$", "method": "PUT", "name": "update_user"}, - {"pattern": "^/users/\\d+/location$", "method": "POST", "name": "ulu"}] - } - ], - - "internal_only_headers": [ - "x-lyft-user-id" - ], - - "response_headers_to_add": [ - {"key": "x-envoy-upstream-canary", "value": "true"} - ], - - "response_headers_to_remove": [ - "x-envoy-upstream-canary", - "x-envoy-virtual-cluster" - ] -} diff --git a/test/tools/router_check/test/config/TestRoutes.yaml b/test/tools/router_check/test/config/TestRoutes.yaml new file mode 100644 index 000000000000..3037df2f1045 --- /dev/null +++ b/test/tools/router_check/test/config/TestRoutes.yaml @@ -0,0 +1,136 @@ +virtual_hosts: + - name: www2 + domains: + - lyft.com + - www.lyft.com + - w.lyft.com + - ww.lyft.com + - wwww.lyft.com + routes: + - match: + prefix: /new_endpoint + route: + cluster: www2 + prefix_rewrite: /api/new_endpoint + - match: + path: / + route: + cluster: root_www2 + - match: + prefix: / + route: + cluster: www2 + + - name: www2_staging + domains: + - www-staging.lyft.net + - www-staging-orca.lyft.com + routes: + - match: + prefix: / + route: + cluster: www2_staging + - name: default + domains: + - '*' + routes: + - match: + prefix: /api/application_data + route: + cluster: ats + - match: + path: /api/locations + case_sensitive: false + route: + cluster: locations + prefix_rewrite: /rewrote + - match: + prefix: /api/leads/me + route: + cluster: ats + - match: + prefix: /host/rewrite/me + route: + cluster: ats + host_rewrite: new_host + - match: + prefix: /oldhost/rewrite/me + route: + cluster: ats + host_rewrite: new_oldhost + - match: + path: /foo + case_sensitive: true + route: + prefix_rewrite: /bar + cluster: instant-server + - match: + path: /tar + case_sensitive: false + route: + prefix_rewrite: /car + cluster: instant-server + - match: + prefix: /newhost/rewrite/me + case_sensitive: false + route: + cluster: ats + host_rewrite: new_host + - match: + path: /FOOD + case_sensitive: false + route: + prefix_rewrite: /cAndy + cluster: ats + - match: + path: /ApplEs + case_sensitive: true + route: + prefix_rewrite: /oranGES + cluster: instant-server + - match: + prefix: /customheaders + route: + cluster: ats + host_rewrite: new_host + request_headers_to_add: + - header: + key: X-Client-IP + value: '%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%' + - match: + prefix: / + route: + cluster: instant-server + timeout: + seconds: 30 + virtual_clusters: + - pattern: ^/rides$ + method: POST + name: ride_request + - pattern: ^/rides/\d+$ + method: PUT + name: update_ride + - pattern: ^/users/\d+/chargeaccounts$ + method: POST + name: cc_add + - pattern: ^/users/\d+/chargeaccounts/(?!validate)\w+$ + method: PUT + name: cc_add + - pattern: ^/users$ + method: POST + name: create_user_login + - pattern: ^/users/\d+$ + method: PUT + name: update_user + - pattern: ^/users/\d+/location$ + method: POST + name: ulu +internal_only_headers: + - x-lyft-user-id +response_headers_to_add: + - header: + key: x-envoy-upstream-canary + value: "true" +response_headers_to_remove: + - x-envoy-upstream-canary + - x-envoy-virtual-cluster diff --git a/test/tools/router_check/test/config/Weighted.json b/test/tools/router_check/test/config/Weighted.json deleted file mode 100644 index 04bd3bbc2ca1..000000000000 --- a/test/tools/router_check/test/config/Weighted.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "virtual_hosts": [ - { - "name": "www1", - "domains": ["www1.lyft.com"], - "routes": [ - { - "prefix": "/", - "weighted_clusters": { - "clusters" : [ - { "name" : "cluster1", "weight" : 30 }, - { "name" : "cluster2", "weight" : 30 }, - { "name" : "cluster3", "weight" : 40 } - ] - } - } - ] - }, - { - "name": "www2", - "domains": ["www2.lyft.com"], - "routes": [ - { - "prefix": "/", - "weighted_clusters": { - "runtime_key_prefix" : "www2_weights", - "clusters" : [ - { "name" : "cluster1", "weight" : 30 }, - { "name" : "cluster2", "weight" : 30 }, - { "name" : "cluster3", "weight" : 40 } - ] - } - } - ] - } - ] -} diff --git a/test/tools/router_check/test/config/Weighted.yaml b/test/tools/router_check/test/config/Weighted.yaml new file mode 100644 index 000000000000..dd31345021a2 --- /dev/null +++ b/test/tools/router_check/test/config/Weighted.yaml @@ -0,0 +1,32 @@ +virtual_hosts: +- name: www1 + domains: + - www1.lyft.com + routes: + - match: + prefix: / + route: + weighted_clusters: + clusters: + - name: cluster1 + weight: 30 + - name: cluster2 + weight: 30 + - name: cluster3 + weight: 40 +- name: www2 + domains: + - www2.lyft.com + routes: + - match: + prefix: / + route: + weighted_clusters: + runtime_key_prefix: www2_weights + clusters: + - name: cluster1 + weight: 30 + - name: cluster2 + weight: 30 + - name: cluster3 + weight: 40 diff --git a/test/tools/router_check/test/route_tests.sh b/test/tools/router_check/test/route_tests.sh index bddc74e4e321..c42c1698440f 100755 --- a/test/tools/router_check/test/route_tests.sh +++ b/test/tools/router_check/test/route_tests.sh @@ -13,23 +13,21 @@ TESTS=("ContentType" "ClusterHeader" "HeaderMatchedRouting" "Redirect" "Redirect # Testing expected matches for t in "${TESTS[@]}" do - TEST_OUTPUT=$("${PATH_BIN}" "${PATH_CONFIG}/${t}.json" "${PATH_CONFIG}/${t}.golden.json" "--details") + TEST_OUTPUT=$("${PATH_BIN}" "${PATH_CONFIG}/${t}.yaml" "${PATH_CONFIG}/${t}.golden.json" "--details") done # Bad config file -BAD_CONFIG_OUTPUT=$(("${PATH_BIN}" "${PATH_CONFIG}/Redirect.golden.json" "${PATH_CONFIG}/TestRoutes.json") 2>&1) || - if [[ "${BAD_CONFIG_OUTPUT}" == *"does not conform to schema."* ]]; then - echo testing bad config output +echo testing bad config output +BAD_CONFIG_OUTPUT=$(("${PATH_BIN}" "${PATH_CONFIG}/Redirect.golden.json" "${PATH_CONFIG}/TestRoutes.yaml") 2>&1) || + if [[ "${BAD_CONFIG_OUTPUT}" != *"Unable to parse"* ]]; then echo ${BAD_CONFIG_OUTPUT} - else exit 1 fi # Failure test case -FAILURE_OUTPUT=$("${PATH_BIN}" "${PATH_CONFIG}/TestRoutes.json" "${PATH_CONFIG}/Weighted.golden.json" "--details") || - if [[ "${FAILURE_OUTPUT}" == *"cluster1 instant-server cluster_name"* ]]; then - echo testing failure test case - echo ${FAILURE_OUTPUT} - else +echo testing failure test case +FAILURE_OUTPUT=$("${PATH_BIN}" "${PATH_CONFIG}/TestRoutes.yaml" "${PATH_CONFIG}/Weighted.golden.json" "--details") || +echo ${FAILURE_OUTPUT} + if [[ "${FAILURE_OUTPUT}" != *"cluster1 instant-server cluster_name"* ]]; then exit 1 fi diff --git a/tools/bazel.rc b/tools/bazel.rc index e8bf79cc84b5..cc2e768623d0 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -30,6 +30,7 @@ build:clang-asan --test_tag_filters=-no_asan build:clang-asan --define signal_trace=disabled build:clang-asan --copt -DADDRESS_SANITIZER=1 build:clang-asan --test_env=ASAN_SYMBOLIZER_PATH +build:clang-asan --test_env=ASAN_OPTIONS=handle_abort=1 # Clang 5.0 TSAN build:clang-tsan --define ENVOY_CONFIG_TSAN=1