From 10bd762aca7d037fa222cffb9a442e3d12bc61d1 Mon Sep 17 00:00:00 2001 From: John Plevyak Date: Tue, 17 Sep 2019 08:26:38 -0700 Subject: [PATCH] Wasm sync (#195) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ext_authz: add metadata_context to ext_authz filter (#7818) This adds the ability to specify dynamic metadata (by namespace) to send with the ext_authz check request. This allows one filter to specify information that can be then used in evaluating an authorization decision. Risk Level: Medium. Optional feature/extension of existing filter Testing: Unit testing Docs Changes: Inline in attribute_context.proto and ext_authz.proto Fixes #7699 Signed-off-by: Ben Plotnick * fuzz: codec impl timeout fix + speed ups (#7963) Some speed-ups and validations for codec impl fuzz test: * validate actions aren't empty (another approach would be to scrub / clean these) * limit actions to 1024 * require oneofs Fixes OSS-Fuzz Issue: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=16481 Testing: local asan/libfuzzer exec/sec go from 25 to 50 Signed-off-by: Asra Ali * docs: more detail about tracking down deprecated features (#7972) Risk Level: n/a (docs only) Testing: n/a Docs Changes: yes Release Notes: no #7945 Signed-off-by: Alyssa Wilk * Fix the alignement in optval of setsockopt when compiled with libc++. (#7958) Description: libc++ std::string may inline the data which results the memory is not aligned to `void*`. Use vector instead to store the optval. Detected by UBSAN with libc++ config. Preparation for #4251 Risk Level: Low Testing: unittest locally Docs Changes: N/A Release Notes: N/A Fixes #7968 Signed-off-by: Lizan Zhou * security: some intra-entity and 3rd party embargo clarifications. (#7977) * security: some intra-entity and 3rd party embargo clarifications. These came up in the last set of CVEs. Signed-off-by: Harvey Tuch * protobuf: IWYU (#7989) Include What You Use fix for source/common/protobuf/message_validator_impl.h. Signed-off-by: Andres Guedez * api: add name into filter chain (#7966) Signed-off-by: Yuchen Dai * rds: validate config in depth before update config dump (#7956) Route config need deep validation for virtual host duplication check, regex check, per filter config validation etc, which PGV wasn't enough. Risk Level: Low Testing: regression test Docs Changes: N/A Release Notes: N/A Fixes #7939 Signed-off-by: Lizan Zhou * tls: maintain a free slot index set in TLS InstanceImpl to allocate in O(1… (#7979) Signed-off-by: Xin Zhuang * redis: handle invalid ip address from cluster slots and added tests (#7984) Signed-off-by: Henry Yang * protobuf: report field numbers for unknown fields. (#7978) Since binary proto won't have field names, report at least the field numbers, as per https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.unknown_field_set#UnknownField. Also fix minor typo encountered while doing this work. Risk level: Low Testing: Unit tests added/updated. Fixes #7937 Signed-off-by: Harvey Tuch * Content in envoy docs does not cover whole page (#7993) Signed-off-by: Manish Kumar * stats: Add option to switch between fake and real symbol-tables on the command-line. (#7882) * Add option to switch between fake and real symbol-tables on the command-line. Signed-off-by: Joshua Marantz * api config: add build rules for go protos (#7987) Some BUILD files are missing build rules to generate go protos. envoyproxy/go-control-plane depends on these protos, so they should be exposed publicly. Added build rules to generate *.pb.go files. Risk Level: Low Testing: These rules were copied to google3 and tested internally. Unfortunately, I am having a bit of trouble with bazel build directly on these targets ("Package is considered deleted due to --deleted_packages"). Please let me know if there is a better way to test this change. Signed-off-by: Teju Nareddy * test: don't use on macOS. (#8000) Xcode 11 requires at least macOS 10.15 (upcoming) in order to use either or C++17 . Signed-off-by: Piotr Sikora * event: adding the capability of creating an alarm with a given scope (#7920) Precursor to #7782 Adding scope tracking functionality to the basic alarm functions. Risk Level: Medium (should be a no-op but is a large enough refactor) Testing: new unit tests Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk * ext authz: add dns san support for ext authz service (#7948) Adds support for DNS SAN in ext authz peer validation Risk Level: Low Testing: Added Docs Changes: Added Release Notes: N/A Signed-off-by: Rama Chavali * accesslog: don't open log file with read flag (#7998) Description: File access log shouldn't need read access for a file. Risk Level: Low Testing: local in mac, CI Docs Changes: Release Notes: Fixes #7997 Signed-off-by: Lizan Zhou * protobuf: towards unifying PGV, deprecated and unknown field validation. (#8002) This is part of #7980; basically, we want to leverage the recursive pass that already exists for the deprecated check. This PR does not implement the recursive behavior yet for unknown fields though, because there is a ton of churn, so this PR just has the mechanical bits. We switch plumbing of validation visitor into places such as anyConvert() and instead pass this to MessageUtil::validate. There are a bunch of future followups planned in additional PRs: * Combine the recursive pass for unknown/deprecated check in MessageUtil::validate(). * Add mitigation for #5965 by copying to a temporary before recursive expansion. * [Future] consider moving deprecated reporting into a message validation visitor handler. Risk level: Low Testing: Some new //test/common/protobuf::utility_test unit test. Signed-off-by: Harvey Tuch * http: forwarding x-forwarded-proto from trusted proxies (#7995) Trusting the x-forwarded-proto header from trusted proxies. If Envoy is operating as an edge proxy but has a trusted hop in front, the trusted proxy should be allowed to set x-forwarded-proto and its x-forwarded-proto should be preserved. Guarded by envoy.reloadable_features.trusted_forwarded_proto, default on. Risk Level: Medium (L7 header changes) but guarded Testing: new unit tests Docs Changes: n/a Release Notes: inline Fixes #4496 Signed-off-by: Alyssa Wilk * build: adding an option to hard-fail when deprecated config is used. (#7962) Adding a build option to default all deprecated protos off, and using it on the debug build. Risk Level: Low Testing: new UT Docs Changes: inline Release Notes: n/a Fixes #7548 Signed-off-by: Alyssa Wilk * envoy_cc_library: add export of foo_with_external_headers (#8005) Add a parallel native.cc_library to envoy_cc_library for external projects that consume Envoy's libraries. This allows the consuming project to disambiguate overlapping include paths when repository overlaying is used, as it can now include envoy headers via external/envoy/... Risk Level: Low Testing: N/A Signed-off-by: Otto van der Schaaf * ci: add fuzz test targets to ci (#7949) Builds fuzz targets with asan+libfuzzer and runs them against their corpora. Our native bazel builds work, this PR integrates the asan+libfuzzer builds in to CI. The fuzz target binaries will be in your envoy docker build directory. Invoke with the following for all fuzz targets, or a specified one. ./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.fuzz' ./ci/run_envoy_docker.sh './ci/do_ci.sh bazel.fuzz //test/common/common:utility_fuzz_test' Risk level: low Signed-off-by: Asra Ali asraa@google.com Signed-off-by: Asra Ali * tls: support BoringSSL private key async functionality (#6326) This PR adds BoringSSL private key API abstraction, as discussed in #6248. All comments and discussion is welcomed to get the API sufficient for most private key API tasks. The PR contains the proposed API and the way how it can be used from ssl_socket.h. Also there is some code showing how the PrivateKeyMethodProvider is coming from TLS certificate config. Two example private key method providers are included in the tests. Description: tls: support BoringSSL private key async functionality Risk Level: medium Testing: two basic private key provider implementation Docs Changes: TLS arch doc, cert.proto doc Signed-off-by: Ismo Puustinen * use SymbolTableCreator rather than fakes in a few stray places. (#8006) stats: use SymbolTableCreator rather than fakes in a few stray places. (#8006) Signed-off-by: Joshua Marantz * [router] Add SRDS configUpdate impl (#7451) This PR contains changes on the xRDS side for SRDS impl, cribbed from http://go/gh/stevenzzzz/envoy/pull/8/files#diff-2071ab0887162eac1fd177e89d83175a * Add onConfigUpdate impl for SRDS subscription * Remove scoped_config_manager as it's not used now. * Move ScopedConfigInfo to scoped_config_impl.h/cc * Add a hash to scopeKey and scopeKeyFragment, so we can look up scopekey by hash value in constant time when SRDS has many scopes. * Add a initManager parameter to RDS createRdsRouteConfigProvider API interface, when creating RouteConfigProvider after listener/server warmed up, we need to specify a different initManager than the one from factoryContext to avoid an assertion failure. see related:#7617 This PR only latches a SRDS provider into the connection manager, the "conn manager using SRDS to make route decision" plus integration tests will be covered in a following PR. Risk Level: LOW [not fully implemented]. Testing: unit tests Signed-off-by: Xin Zhuang * Fix version history (#8021) Follow-up for #7995. Signed-off-by: Raul Gutierrez Segales * tools: sync tool for envoyproxy/assignable team. (#8015) Bulk update of team to match envoyproxy organization. While at it, cleaned up some venv stuff in shell_utils.sh. Risk level: Low Testing: Synced 157 members from envoyproxy to envoyproxy/assignable. Signed-off-by: Harvey Tuch * redis: fix onHostHealthUpdate got called before the cluster is resolved. (#8018) Signed-off-by: Henry Yang * api/build: migrate UDPA proto tree to external cncf/udpa repository. (#8017) This is a one-time movement of all UDPA content from envoyproxy/envoy to cncf/udpa. The permanent home of UDPA will be https://github.com/cncf/udpa. Risk level: Low Testing: Added UDPA service entry to build_test. Signed-off-by: Harvey Tuch * http: tracking active session under L7 timers (#7782) Signed-off-by: Alyssa Wilk * upstream: remove thread local cluster after triggering call backs (#8004) Signed-off-by: Rama Chavali * upstream: Introducing close_connections_on_host_set_change property (#7675) Signed-off-by: Kateryna Nezdolii * upstream: delete stale TODO (#8028) This was fixed in https://github.com/envoyproxy/envoy/pull/7877 Signed-off-by: Matt Klein * Enhance comment about MonotonicTime (#8011) Depending on the execution environment in which envoy is being run, it is possible that some of the assumption on the clock are maybe not holding as previously commented. With some sandboxing technologies the clock does not reference the machine boot time but the sandbox boot time. This invalidates the assumtpion that the first update in the cluster_manager will most likely fall out of the windows and ends up showing a non intuitive behavior difficult to catch. This PR simply adds a comment that will allow the reader to consider this option while reading to the code. Signed-off-by: Flavio Crisciani * build: some missing dep fixups for Google import. (#8026) Signed-off-by: Harvey Tuch * introduce safe regex matcher based on re2 engine (#7878) The libstdc++ std::regex implementation is not safe in all cases for user provided input. This change deprecates the used of std::regex in all user facing paths and introduces a new safe regex matcher with an explicitly configurable engine, right now limited to Google's re2 regex engine. This is not a drop in replacement for std::regex as all language features are not supported. As such we will go through a deprecation period for the old regex engine. Fixes https://github.com/envoyproxy/envoy/issues/7728 Signed-off-by: Matt Klein * docs: reorganize configuration tree (#8027) This is similar to what I did for the arch overview a while ago as this section is also getting out of control. Signed-off-by: Matt Klein * build: missing regex include. (#8032) Signed-off-by: Harvey Tuch * [headermap] speedup for appending data (#8029) For debug builds, performance testing and fuzzers reveal that when appending to a header, we scan both the existing value and the data to append for invalid characters. This PR moves the validation check to just the data that is appended, to avoid hangups on re-scanning long header values multiple times. Testing: Added corpus entry that reveals time spent in validHeaderString Signed-off-by: Asra Ali * eds: avoid send too many ClusterLoadAssignment requests (#7976) During initializing secondary clusters, for each initialized cluster, a ClusterLoadAssignment request is sent to istio pilot with the cluster's name appended into request's resource_names list. With a huge number of clusters(e.g 10k clusters), this behavior slows down Envoy's initialization and consumes ton of memory. This change pauses ADS mux for ClusterLoadAssignment to avoid that. Risk Level: Medium Testing: tiny change, no test case added Fixes #7955 Signed-off-by: lhuang8 * Set the bazel verison to 0.28.1 explicitly (#8037) In https://github.com/theopenlab/openlab-zuul-jobs/pull/622 , the OpenLab add the ability to set the bazel to specific version explicitly. This patch add the bazel role for the envoy job. Signed-off-by: Yikun Jiang * Read_policy is not set correctly. (#8034) Add more integration test and additional checks in the unit tests. Signed-off-by: Henry Yang * admin: fix /server_info hot restart version (#8022) Signed-off-by: Matt Klein * test: adding debug hints for integration test config failures (#8038) Risk Level: n/a (test only) Testing: manual Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk * udp_listener: refactor ActiveUdpListener creation (#7884) Signed-off-by: Dan Zhang * accesslog: implement TCP gRPC access logger (#7941) Description: Initial implementation for TCP gRPC access logger. Risk Level: Low (extension only) Testing: integration test Docs Changes: Added Release Notes: Added Signed-off-by: Lizan Zhou * tracing: add OpenCensus agent exporter support to OpenCensus driver. (#8023) Signed-off-by: Emil Mikulic * Exporting platform_impl_lib headers (#8045) This allows consuming projects using repository overlaying to disambiguate overlapping include paths when it comes to platform_impl.h by going through envoy/external/... Addendum to #8005 Risk Level: Low Testing: N/A Signed-off-by: Otto van der Schaaf * access_log: minimal log file error handling (#7938) Rather than ASSERT for a reasonably common error condition (e.g. disk full) record a stat that indicates log file writing failed. Also fixes a test race condition. Risk Level: low Testing: added stats checks Docs Changes: documented new stat Release Notes: updated Signed-off-by: Stephan Zuercher * tracing: add grpc-status and grpc-message to spans (#7996) Signed-off-by: Caleb Gilmour * fuzz: add bounds to statsh flush interval (#8043) Add PGV bounds to the stats flush interval (greater than 1ms and less than 5000ms) to prevent Envoy from hanging from too small of a flush time. Risk Level: Low Testing: Corpus Entry added Fixes OSS-Fuzz issue https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=16300 Signed-off-by: Asra Ali * Improve tools/stack_decode.py (#8041) Adjust tools/stack_decode.py to more obviously be Python 2 (not 3), and to work on stack traces that don't include the symbol names. Risk Level: Low Testing: Manually tested on a stack trace that one of our users sent us Signed-off-by: Luke Shumaker * build: tell googletest to use absl stacktrace (#8047) Description: https://github.com/google/googletest/blob/d7003576dd133856432e2e07340f45926242cc3a/BUILD.bazel#L42 Risk Level: Low (test only) Testing: CI Docs Changes: Release Notes: Signed-off-by: Lizan Zhou * Update references to local scripts to enable using build container for filter repos (#7907) Description: This change enables using run_envoy_docker.sh to build envoy-filter-example Risk Level: Low Testing: Manually tested building envoy-filter-example using: envoy/ci/run_envoy_docker.sh './ci/do_ci.sh build' Docs Changes: N/A Release Notes: N/A Signed-off-by: Santosh Kumar Cheler * bazel: patch gRPC to fix Envoy builds with glibc v2.30 (#7971) Description: the latest glibc (v2.30) declares its own `gettid()` function (see [0]) and this creates a naming conflict in gRPC which has a function with the same name. Apply to gRPC [a patch](https://github.com/grpc/grpc/pull/18950) which renames `gettid()` to `sys_gettid()`. [0] https://sourceware.org/git/?p=glibc.git;a=commit;h=1d0fc213824eaa2a8f8c4385daaa698ee8fb7c92 Risk Level: low Testing: unit tests Docs Changes: n/a Release Notes: n/a Signed-off-by: Dmitry Rozhkov * build: link C++ stdlib dynamically in sanitizer runs (#8019) Description: Sanitizers doesn't support static link, reverts #7929 and link lib(std)c++ dynamically in sanitizer runs. Addresses test issue for #4251. Added workaround in ASAN for #7647. Risk Level: Low (test only) Testing: CI, local libc++ runs Docs Changes: N/A Release Notes: N/A Fixes #7928 * test: cleaning up test runtime (#8012) Using the new runtime utility to clean up a bunch of test gorp. Yay utils! Risk Level: n/a (test only) Testing: tests pass Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk * test: improved coverage and handling of deprecated config (#8057) Making ENVOY_DISABLE_DEPRECATED_FEATURES work for unit tests without runtime configured. Fixing up a handful of unit tests to remove legacy code or use the handy DEPRECATED_FEATURE_TEST macro Adding back coverage of cors.enabled() and redis.catch_all_route() Risk Level: Low (test only) Testing: new unit tests Docs Changes: n/a Release Notes: n/a Fixes #8013 Fixes #7548 Signed-off-by: Alyssa Wilk * [Docs typo] Remote Executioon -> Remote Execution (#8061) Fixes mispelling of `Executioon` -> `Execution` Signed-off-by: Colin Schoen * api: Fix duplicate java_outer_classname declarations (#8059) The java_outer_classname is unintentionally duplicated in the new udp_listener_config and regex proto files. This changes them to unique names that match the predominant naming scheme. Signed-off-by: Bryce Anderson * http: making the behavior of the response Server header configurable (#8014) Default behavior remains unchanged, but now Envoy can override, override iff there's no server header from upstream, or always leave the server header (or lack thereof) unmodified. Risk Level: low (config guarded change) Testing: new unit tests Docs Changes: n/a Release Notes: inline Fixes #6716 Signed-off-by: Alyssa Wilk * use bazelversion for filter-example too (#8069) Signed-off-by: Lizan Zhou * grpc-httpjson-transcode: Update for RFC2045 support (#8065) RFC2045 (MIME) Base64 decoding support has been fixed upstream Description: The grpc transcoding filter has been updated to support RFC2045 (MIME) based inputs for protobuf type "Bytes". This is important since Base64 is often using the RFC2045 format for inputs. Also see: grpc-ecosystem/grpc-httpjson-transcoding#34 Risk Level: Low Testing: Integration / Manual Tests Docs Changes: N/A Release Notes: N/A Signed-off-by: Hans Viken Duedal * stats: Clean up all calls to Scope::counter() et al in production code. (#7842) * Convert a few more counter() references to use the StatName interface. Signed-off-by: Joshua Marantz * tls_inspector: inline the recv in the onAccept (#7951) Description: As discussed in #7864 this PR is the attempt to peek the socket at the invoke of onAccept. Usually client_hello packet should be in the buffer when tls_inspector is peeking, we could save a poll cycle for this connection. Once we agree on the solution I can apply to http_inspector as well. The expecting latency improvement especially when poll cycle is large. Benchmark: Env: hardware Intel(R) Xeon(R) CPU @ 2.20GHz envoy: concurrency = 1, tls_inspector as listener filter. One tls filter chain, and one plain text filter chain. load background: a [sniper](https://github.com/lubia/sniper) client with concurrency = 5 hitting the server with tls handshake, aiming to hit using the tls_filter chain. The qps is about 170/s Another load client hitting the plain text filter chain but would go through tls_inspector with concurrency = 1 This PR: TransactionTime: 10.3 - 11.0 ms(mean) Master TransactionTime: 12.3 - 12.8 ms(mean) Risk Level: Med (ActiveSocket code is affected to adopt the side effect of onAccept) Testing: Docs Changes: Release Notes: Fixes #7864 Signed-off-by: Yuchen Dai * Fixes gcc 8.3.1 build failure due to FilterChainBenchmarkFixture::SetUp hiding base-class virtual functions (#8071) Description: I'm seeing "bazel-out/k8-fastbuild/bin/external/com_github_google_benchmark/_virtual_includes/benchmark/benchmark/benchmark.h:1071:16: error: 'virtual void benchmark::Fixture::SetUp(benchmark::State&)' was hidden" when running tests. This resolves the issue with hiding of the base-class functions. Risk Level: low Testing: Docs Changes: Release Notes: Signed-off-by: Dmitri Dolguikh * test: fix ups for various deprecated fields (#8068) Takeaways: we've lost the ability to do empty regex (which was covered in router tests and is proto constraint validated on the new safe regex) as well as negative lookahead (also covered in tests) along with a host of other things conveniently documented as not supported here: https://github.com/google/re2/wiki/Syntax Otherwise split up a bunch of tests, duplicated and tagged a bunch of tests, and cleaning up after we finally can remove deprecated fields again will be an order of magnitude easier. Also fixing a dup relnote from #8014 Risk Level: n/a (test only) Testing: yes. yes there is. Docs Changes: no Release Notes: no Signed-off-by: Alyssa Wilk * include: add log dependency header to connection_handler.h (#8072) Signed-off-by: Teju Nareddy * quiche: Update QUICHE dep (#8044) Update QUICHE tar ball to 4abb566fbbc63df8fe7c1ac30b21632b9eb18d0c. Add some new impl's for newly added api. Risk Level: low Testing: using quiche build in tests. Part of #2557 Signed-off-by: Dan Zhang * tools: deprecated field check in Route Checker tool (#8058) We need a way to run the deprecated field check on the RouteConfiguration. Today the schema check tool validates the bootstrap config. This change will help achieve similar functionality on routes served from rds. Risk Level: Low Testing: Manual testing Docs Changes: included Release Notes: included Signed-off-by: Jyoti Mahapatra * tracing: Add support for sending data in Zipkin v2 format (#6985) Description: This patch supports sending a list of spans as JSON v2 and protobuf message over HTTP to Zipkin collector. [Sending protobuf](https://github.com/openzipkin/zipkin-api/blob/0.2.1/zipkin.proto) is considered to be more efficient than JSON, even compared to the v2's JSON (https://github.com/openzipkin/zipkin/pull/2589#issuecomment-491642768). This is an effort to rework https://github.com/envoyproxy/envoy/pull/6798. The approach is by serializing the v1 model to both v2 JSON and protobuf. Risk Level: Low, since the default is still HTTP-JSON v1 based on https://github.com/openzipkin/zipkin-api/blob/0.2.2/zipkin-api.yaml. Testing: Unit testing, manual integration test with real Zipkin collector server. Docs Changes: Added Release Notes: Added Fixes: #4839 Signed-off-by: Dhi Aurrahman Signed-off-by: José Carlos Chávez * Route Checker tool Fix code coverage bug in proto based schema (#8101) Signed-off-by: Jyoti Mahapatra * [hcm] Add scoped RDS routing into HCM (#7762) Description: add Scoped RDS routing logic into HCM. Changes include: * in ActiveStream constructor latch a ScopedConfig impl to the activeStream if SRDS is enabled * in the beginning of ActiveStream::decodeHeaders(headers, end_stream), get routeConfig from latched ScopedConfig impl. This PR is the 3rd in the srds impl PR chain: [#7704, #7451, this]. Risk Level: Medium Testing: unit test and integration tests. Release Notes: Add scoped RDS routing support into HCM. Signed-off-by: Xin Zhuang * owners: add @asraa and @lambdai to OWNERS. (#8110) * @asraa is joining Envoy OSS security team. * @lambdai is joining Friends of Envoy as v2 xDS point. Signed-off-by: Harvey Tuch * protobuf: recursively validate unknown fields. (#8094) This PR unifies the recursive traversal of deprecated fields with that of unknown fields. It doesn't deal with moving to a validator visitor model for deprecation; this would be a nice cleanup that we track at https://github.com/envoyproxy/envoy/issues/8092. Risk level: Low Testing: New nested unknown field test added. Fixes #7980 Signed-off-by: Harvey Tuch * Fuzz reuse (#8119) This PR allows the envoy_cc_fuzz_test rule to be used when pulling in envoy. which can be useful when you're writing filters for envoy, and want to reuse the fuzzing architecture envoy has already built. other rules already allow for this (see envoy_cc_test in this same file for example). Risk Level: Low Testing: Testing the Old Rule Still Works It is possible to test the old rules still work (even without specifying a repository), by simply choosing your favorite fuzz test, and choosing to run bazel test on it. For example: bazel test //test/common/router:header_parser_fuzz_test. Any envoy_cc_fuzz_test rule should do. Testing New Rules Work I've done testing inside my own repository, but if you want to create your own test rule you can probably do the following in envoy-filter-example: Checkout envoy-filter-example, and update the envoy submodule to this pr. Follow the directions in: test/fuzz/README.md to define a envoy_cc_fuzz_test rule. Make sure to add a line for: repository = "@envoy" which is the new argument being added. You should be able to run the fuzz test. Signed-off-by: Cynthia Coan * Set INCLUDE_DIRECTORIES so libcurl can find local urlapi.h (#8113) Fixes https://github.com/envoyproxy/envoy/issues/8112 Signed-off-by: John Millikin * cleanup: move test utility methods in ScopedRdsIntegrationTest to base class HttpIntegrationTest (#8108) Fixes #8050 Risk Level: LOW [refactor only] Signed-off-by: Xin Zhuang * upstream: fix invalid access of ClusterMap iterator during warming cluster modification (#8106) Risk Level: Medium Testing: New unit test added. Fix verified via --config=asan. Signed-off-by: Andres Guedez * api:Add a flag to disable overprovisioning in ClusterLoadAssignment (#8080) * api:Add a flag to disable overprovisioning in ClusterLoadAssignment Signed-off-by: Jie Chen * api:Add [#next-major-version and [#not-implemented-hide to the comment for field of disable_overprovisioning in ClusterLoadAssignment Signed-off-by: Jie Chen * api:Refine comments for the new added bool flag as suggested. Signed-off-by: Jie Chen * api: clone v2[alpha] to v3alpha. (#8125) This patch establishes a v3alpha baseline API, by doing a simple copy of v2[alpha] dirs and some sed-style heuristic fixups of BUILD dependencies and proto package namespaces. The objective is provide a baseline which we can compare the output from tooling described in #8083 in later PRs, providing smaller visual diffs. The core philosophy of the API migration is that every step will be captured in a script (at least until the last manual steps), api/migration/v3alpha.sh. This script will capture deterministic migration steps, allowing v2[alpha] to continue to be updated until we finalize v3. There is likely to be significant changes, e.g. in addition to the work scoped for v3, we might want to reduce the amount of API churn by referring back to v2 protos where it makes sense. This will be done via tooling in later PRs. Part of #8083. Risk level: Low Testing: build @envoy_api//... Signed-off-by: Harvey Tuch * dubbo: Fix heartbeat packet parsing error (#8103) Description: The heartbeat packet may carry data, and it is treated as null data when processing the heartbeat packet, causing some data to remain in the buffer. Risk Level: low Testing: Existing unit test Docs Changes: N/A Release Notes: N/A Fixes #7970 Signed-off-by: tianqian.zyf * stats: Shared cluster isolated stats (#8118) * shared the main symbol-table with the isolated stats used for cluster info. Signed-off-by: Joshua Marantz * protodoc: upgrade to Python 3. (#8129) Risk level: Low Testing: Rebuilt docs, manual inspection of some example generated files. Signed-off-by: Harvey Tuch * protodoc: single source-of-truth for doc protos. (#8132) This avoids having to list new docs protos in both docs/build.sh and api/docs/BUILD. This technical debt cleanup is helpful in v3 proto work to simplify collecting proto artifacts from a Bazel aspect. Risk level: Low Testing: docs/build.sh, visual inspection of docs. Signed-off-by: Harvey Tuch * api: organize go_proto_libraries (#8003) Fixes #7982 Defines a package level proto library and its associated internal go_proto_library. Deletes all existing api_go_proto_library, api_go_grpc_library, and go_package annotations in protos (they are not required and pollute the sources). I deliberately avoided touching anything under udpa since it's being moved to another repository. Risk Level: low Testing: build completes Signed-off-by: Kuat Yessenov * api: straggler v2alpha1 -> v3alpha clone. (#8133) These were missed in #8125. Signed-off-by: Harvey Tuch * docs: remove extraneous escape (#8150) Old versions of bash (e.g. on macOS) don't handle ${P/:/\/} the same way as modern versions. In particular, the expanded parameter on macOS includes a backslash, causing subsequent use of the string as a filename to include a slash (/) instead of treating the slash as a directory separator. Both versions of bash accept ${P/://} as a way to substitute : with /. Verified that this change does not alter the generated docs when running under Linux. Risk Level: low Testing: generated docs under linux & macOS Signed-off-by: Stephan Zuercher * Do not 503 on Upgrade: h2c instead remove the header and ignore. (#7981) Description: When a request comes in on http1 with "upgrade: h2c", the current behavior is to 503. Instead we should ignore the upgrade and remove the header and continue with the request as http1. Risk Level: Medium Testing: Unit test. Hand test with ithub.com/rdsubhas/java-istio client server locally. Docs Changes: N/A Release Notes: http1: ignore and remove Upgrade: h2c. Fixes istio/istio#16391 Signed-off-by: John Plevyak * docs: add line on installing xcode for macOS build flow (#8139) Because of rules_foreign_cc in bazelbuild, Envoy will not compile successfully when following the instructions in the build docs due to how the tools are referenced. One fix for this is installing Xcode from the App Store (see bazelbuild/rules_foreign_cc#185). Risk Level: Low Testing: N/A (docs change) Docs Changes: see Description Release Notes: N/A Signed-off-by: Lisa Lu * docs: note which header expressions cannot be used for request headers (#8138) As discussed in #8127, some custom header expressions evaluate as empty when used in request headers. Risk Level: low, docs only Testing: n/a Docs Changes: updated Release Notes: n/a Signed-off-by: Stephan Zuercher * api: use traffic_direction over operation_name if specified (#7999) Use the listener-level field for the tracing direction over the per-filter field. Unfortunately, the per filter did not provide an "unspecified" default, so this appears to be the right approach to deprecate the per-filter field with minimal impact. Risk Level: low (uses a newly introduce field traffic_direction) Testing: unit test Docs Changes: proto docs Signed-off-by: Kuat Yessenov * add more diagnostic logs (#8153) Istio sets listener filter timeout to 10ms by default but requests fail from time to tome. It's very difficult to debug. Even though downstream_pre_cx_timeout_ is exposed to track the number of timeouts, it would be better to have some debug logs. Description: add more diagnostic logs Risk Level: low Signed-off-by: crazyxy * http conn man: add tracing config for path length in tag (#8095) This PR adds a configuration option for controlling the length of the request path that is included in the HttpUrl span tag. Currently, this length is hard-coded to 256. With this PR, that length will be configurable (defaulting to the old value). Risk Level: Low Testing: Unit Docs Changes: Inline with the API proto. Documented new field. Release Notes: Added in the PR. Related issue: istio/istio#14563 Signed-off-by: Douglas Reid * cds: Add general-purpose LB policy configuration (#7744) This PR adds fields to CDS that allow for general-purpose LB policy configuration. Risk Level: Low Testing: None (but if anything is needed, please let me know) Docs Changes: Inline with API protos Release Notes: N/A Signed-off-by: Mark D. Roth * thrift_proxy: fix crash on invalid transport/protocol (#8143) Transport/protocol decoder errors that occur before the connection manager initializes an ActiveRPC to track the request caused a crash. Modifies the connection manager to handle this case, terminating the downstream the connection. Risk Level: low Testing: test case that triggers crash Docs Changes: n/a Release Notes: added Signed-off-by: Stephan Zuercher * api: strip gogoproto annotations (#8163) Remove gogoproto annotations. They can be replaced with a custom gogoproto compiler (e.g. something like https://github.com/gogo/googleapis/tree/master/protoc-gen-gogogoogleapis). I have an experimental version of it to validate that it's possible to re-apply important annotations in the compiler. Risk Level: low Testing: builds Signed-off-by: Kuat Yessenov * hotrestart: remove dynamic_resources from server config used by hotrestart_test (#8162) In the server config file `test/config/integration/server.yaml` used by //test/integration:hotrestart_test, `dynamic_resources` includes `lds_config` and `cds_config` definitions, which use HTTP API to fetch config, but CDS and LDS service do not exist, so the initial fetch will be failed with a connection failure, then Envoy server continue startup. Envoy server shouldn't continue startup because connection failure, see issue #8046. For this test, `dynamic_resources` is not needed, this change clean it up. Signed-off-by: lhuang8 * clang-tidy: misc-unused-using-decls (#8159) Description: clang-tidy check to flag unused using statements. There's a lot in test code that's just copy pasta, and it's hard to manually review whether it's being used, especially for things like using testing::_; Risk Level: low Testing: existing Docs Changes: N/A Release Notes: N/A Signed-off-by: Derek Argueta * build: curl with c-ares, nghttp2 and zlib (#8154) Build curl dependency with async DNS resolver c-ares avoiding potential crashes due to longjmp on modern kernels. Add zlib and nghttp2. Use Envoy's version of all of the above libraries. Signed-off-by: Taras Roshko * log: add upstream TLS info (#7911) Description: add upstream TLS info for logging purposes Refactor SSL connection info to be a shared pointer. Use read-only interface. Cache computed values in the SSL info object (this allows transition to remove the underlying SSL object if necessary). Risk Level: medium due to use of bssl::SSL to back ConnectionInfo Testing: unit Docs Changes: none Release Notes: add upstream TLS info Signed-off-by: Kuat Yessenov * fix windows implementation of PlatformImpl (#8169) Add missing destructor to class declaration. Fix copy/paste errors. These errors were apparently introduced in e1cd4cc. Risk Level: Low Testing: Passed Windows testing locally Docs Changes: n/a Release Notes: n/a Signed-off-by: William Rowe wrowe@pivotal.io Signed-off-by: Yechiel Kalmenson * Update Opencensus SHA (#8173) Signed-off-by: Pengyuan Bian * Outlier Detection: use gRPC status code for detecting failures (#7942) Signed-off-by: ZhouyihaiDing * fix build (#8177) Signed-off-by: Derek Argueta * docs: improving websocket docs (#8156) Making it clear H2 websockets don't work by default Risk Level: n/a Testing: n/a Docs Changes: yes Release Notes: no #8147 Signed-off-by: Alyssa Wilk * Upstream WebAssembly VM and Null VM from envoyproxy/envoy-wasm. (#8020) Description: Upstream from envoyproxy/envoy-wasm the WebAssembly VM support along with the Null VM support and tests. This is the first PR dealing with WebAssembly filter support in envoy. See https://github.com/envoyproxy/envoy-wasm/blob/master/WASM.md and https://github.com/envoyproxy/envoy-wasm/blob/master/docs/root/api-v2/config/wasm/wasm.rst for details. Risk Level: Medium Testing: Unit tests. Docs Changes: N/A Release Notes: N/A Part of #4272 Signed-off-by: John Plevyak * quiche: implement Envoy Quic stream and connection (#7721) Implement QuicStream|Session|Disptacher in Envoy. Weir up QUIC stream and connection with HCM callbacks. Risk Level: low, not in use Testing: Added unit tests for all new classes Part of #2557 Signed-off-by: Dan Zhang * protodoc/api_proto_plugin: generic API protoc plugin framework. (#8157) Split out the generic plugin and FileDescriptorProto traversal bits from protodoc. This is in aid of the work in #8082 ad #8083, where additional protoc plugins will be responsible for v2 -> v3alpha API migrations and translation code generation. This is only the start really of the api_proto_plugin framework. I anticipate additional bits of protodoc will move here later, including field type analysis and oneof handling. In some respects, this is a re-implementation of some of https://github.com/lyft/protoc-gen-star in Python. The advantage is that this is super lightweight, has few dependencies and can be easily hacked. We also embed various bits of API business logic, e.g. annotations, in the framework (for now). Risk level: Low Testing: diff -ru against previous protodoc.py RST output, identical modulo some trivial whitespace that doesn't appear in generated HTML. There are no real tests yet, I anticipate adding some golden proto style tests. Signed-off-by: Harvey Tuch * adaptive concurrency: Gradient algorithm implementation (#7908) Signed-off-by: Tony Allen * ext_authz: Check for cluster before sending HTTP request (#8144) Signed-off-by: Dhi Aurrahman * make getters const-ref (#8192) Description: Follow-up to #7911 to make cached values be exposed as const-references, saving on a copy of a string during retrieval. Risk Level: low Testing: updated mocks to return references Docs Changes: none Release Notes: none Signed-off-by: Kuat Yessenov * test: add curl features check (#8194) Add a test ensuring curl was built with the expected features. Description: Add a test ensuring curl was built with the expected features. Risk Level: Low. Testing: n/a. Docs Changes: n/a. Release Notes: n/a. Signed-off-by: Taras Roshko * subset lb: allow ring hash/maglev LB to work with subsets (#8030) * subset lb: allow ring hash/maglev LB to work with subsets Skip initializing the thread aware LB for a cluster when the subset load balancer is enabled. Also adds some extra checks for LB policies that are incompatible with the subset load balancer. Risk Level: low Testing: test additional checks Docs Changes: updated docs w.r.t subset lb compatibility Release Notes: n/a Fixes: #7651 Signed-off-by: Stephan Zuercher * redis: add a request time metric to redis upstream (#7890) Signed-off-by: Nicolas Flacco * bazel: update bazel to 0.29.1 (#8198) Description: Upgrade bazel to 0.29.1 and bazel-toolchains to corresponding version. Risk Level: Low Testing: CI Docs Changes: N/A Release Notes: N/A Signed-off-by: Lizan Zhou * upstream: Add ability to disable host selection during panic (#8024) Previously, when in a panic state, requests would be routed to all hosts. In some cases it is instead preferable to not route any requests. Add a configuration option for zone-aware load balancers which switches from routing to all hosts to no hosts. Closes #7550. Signed-off-by: James Forcier jforcier@grubhub.com Risk Level: Low Testing: 2 new unit tests written; manual testing Docs Changes: Note about new configuration option added Release Notes: added Signed-off-by: James Forcier * metrics service: flush histogram buckets (#8180) Signed-off-by: Rama Chavali * tracing: fix random sample fraction percent (#8205) Signed-off-by: Pengyuan Bian * stats: Add per-host memory usage test case to stats_integration_test (#8189) Signed-off-by: Antonio Vicente * router check tool: add flag for only printing failed tests (#8160) Signed-off-by: Lisa Lu * fix link to runtime docs (#8204) Description: Looks like the runtime docs moved under operations/. The PR fixes the link. Risk Level: low Testing: existing Docs Changes: this Release Notes: n/a Signed-off-by: Derek Argueta * config: make SlotImpl detachable from its owner, and add a new runOnAllThreads interface to Slot. (#8135) See the issue in #7902, this PR is to make the SlotImpl detachable from its owner, by introducing a Booker object wraps around a SlotImpl, which bookkeeps all the on-the-fly update callbacks. And on its destruction, if there are still on-the-fly callbacks, move the SlotImpl to an deferred-delete queue, instead of destructing the SlotImpl which may cause an SEGV error. More importantly, introduce a new runOnAllThreads(ThreadLocal::UpdateCb cb) API to Slot, which requests a Slot Owner to not assume that the Slot or its owner will out-live (in Main thread) the fired on-the-fly update callbacks, and should not capture the Slot or its owner in the update_cb. Picked RDS and config-providers-framework as examples to demonstrate that this change works. {i.e., changed from the runOnAllThreads(Event::PostCb) to the new runOnAllThreads(TLS::UpdateCb) interface. } Risk Level: Medium Testing: unit test Docs Changes: N/A Release Notes: N/A [Optional Fixes #Issue] #7902 Signed-off-by: Xin Zhuang * test: remove static config from subset lb integration test (#8203) Build the config programmatically to make future API changes less onerous. Risk Level: low (test change only) Testing: n/a Doc Changes: n/a Release Notes: n/a Signed-off-by: Stephan Zuercher * cleanup: clarify Cluster.filters and Dispatcher::createClientConnection (#8186) Signed-off-by: Fred Douglas * redis: health check is not sending the auth command on its connection (#8166) Signed-off-by: Henry Yang * redis: mirroring should work when default value is zero, not just greater than zero (#8089) Signed-off-by: Nicolas Flacco * tools: regularize pip/venv for format_python_tools.py. (#8176) As well as being a nice cleanup, this fixes some issues I had with local Docker use of fix_format as a non-root user. Signed-off-by: Harvey Tuch * absl: Absl hash hook in a couple of places rather than hash functors (#8179) Signed-off-by: Joshua Marantz * Update dependency: jwt_verify_lib (#8212) Signed-off-by: Daniel Grimm * upstream: add failure percentage-based outlier detection (#8130) Description: Add a new outlier detection mode which compares each host's rate of request failure to a configured fixed threshold. Risk Level: Low Testing: 2 new unit tests added. Docs Changes: New mode and config options described. Release Notes: white_check_mark Fixes #8105 Signed-off-by: James Forcier * Replace deprecated thread annotations macros. (#8237) Abseil thread annotation macros are now prefixed by ABSL_. There is no semantic change; this is just a rename. Signed-off-by: Yan Avlasov * Update protoc-gen-validate (PGV) (#8234) This picks up fixes for the Windows build and a C preprocessor defect Signed-off-by: Yechiel Kalmenson Signed-off-by: William Rowe * upstream: use named constants for outlier detection config defaults (#8221) Signed-off-by: James Forcier * server: add a post init lifecycle stage (#8217) Signed-off-by: Jose Nino * docs: document access control conditions and attributes (#8230) Signed-off-by: Kuat Yessenov * server: return processContext as optional reference (#8238) Signed-off-by: Elisha Ziskind * Update envoy.yaml in Redis proxy example (#8220) Description: Make Redis example use catch_all_route. Risk Level: Low. Testing: Done. docker-compose up --build brings up envoy proxy and I was able to run Redis commands using redis-cli. Signed-off-by: Raju Kadam * quiche: implement ActiveQuicListener (#7896) Signed-off-by: Dan Zhang * srds: allow SRDS pass on scope-not-found queries to filter-chain (issue #8236). (#8239) Description: Allow a no-scope request to pass through the filter chain, so that some special queries (e.g., data plane health-check ) can be processed by the customized filter-chain. By default, the behavior is the same (404). Risk Level: LOW Testing: unit test and integration test. Docs Changes: N/A Release Notes: N/A Fixes #8236 Signed-off-by: Xin Zhuang * Updated to new envoyproxy master branch. Signed-off-by: John Plevyak * Remove offending go proto option. Signed-off-by: John Plevyak * Fix format/tidy issues. Signed-off-by: John Plevyak --- .azure-pipelines/linux.yml | 9 + .bazelrc | 32 +- .bazelversion | 2 +- .circleci/config.yml | 1 + .clang-tidy | 2 + .zuul/playbooks/envoy-build/run.yaml | 2 + CODEOWNERS | 6 +- CONTRIBUTING.md | 6 +- OWNERS.md | 3 + SECURITY.md | 14 + api/CONTRIBUTING.md | 2 +- api/bazel/BUILD | 12 + api/bazel/api_build_system.bzl | 118 +- api/bazel/repositories.bzl | 85 +- api/bazel/repository_locations.bzl | 34 +- api/docs/BUILD | 38 +- api/envoy/admin/v2alpha/BUILD | 13 +- api/envoy/admin/v2alpha/config_dump.proto | 2 - api/envoy/admin/v2alpha/server_info.proto | 6 +- api/envoy/admin/v3alpha/BUILD | 87 + api/envoy/admin/v3alpha/certs.proto | 57 + api/envoy/admin/v3alpha/clusters.proto | 143 + api/envoy/admin/v3alpha/config_dump.proto | 264 + api/envoy/admin/v3alpha/listeners.proto | 28 + api/envoy/admin/v3alpha/memory.proto | 37 + api/envoy/admin/v3alpha/metrics.proto | 26 + api/envoy/admin/v3alpha/mutex_stats.proto | 28 + api/envoy/admin/v3alpha/server_info.proto | 137 + api/envoy/admin/v3alpha/tap.proto | 20 + api/envoy/api/v2/BUILD | 88 +- api/envoy/api/v2/auth/BUILD | 18 +- api/envoy/api/v2/auth/cert.proto | 31 +- api/envoy/api/v2/cds.proto | 82 +- api/envoy/api/v2/cluster/BUILD | 27 +- .../api/v2/cluster/circuit_breaker.proto | 5 - api/envoy/api/v2/cluster/filter.proto | 3 - .../api/v2/cluster/outlier_detection.proto | 33 +- api/envoy/api/v2/core/BUILD | 58 +- api/envoy/api/v2/core/address.proto | 4 - api/envoy/api/v2/core/base.proto | 7 - api/envoy/api/v2/core/config_source.proto | 8 +- api/envoy/api/v2/core/grpc_service.proto | 3 - api/envoy/api/v2/core/health_check.proto | 25 +- api/envoy/api/v2/core/http_uri.proto | 10 +- api/envoy/api/v2/core/protocol.proto | 5 +- api/envoy/api/v2/discovery.proto | 5 - api/envoy/api/v2/eds.proto | 15 +- api/envoy/api/v2/endpoint/BUILD | 31 +- api/envoy/api/v2/endpoint/endpoint.proto | 4 - api/envoy/api/v2/endpoint/load_report.proto | 1 - api/envoy/api/v2/lds.proto | 16 +- api/envoy/api/v2/listener/BUILD | 26 +- api/envoy/api/v2/listener/listener.proto | 9 +- api/envoy/api/v2/listener/quic_config.proto | 28 + .../api/v2/listener/udp_listener_config.proto | 30 + api/envoy/api/v2/ratelimit/BUILD | 9 +- api/envoy/api/v2/ratelimit/ratelimit.proto | 1 - api/envoy/api/v2/rds.proto | 3 - api/envoy/api/v2/route/BUILD | 22 +- api/envoy/api/v2/route/route.proto | 119 +- api/envoy/api/v2/srds.proto | 55 +- api/envoy/api/v3alpha/BUILD | 115 + api/envoy/api/v3alpha/README.md | 9 + api/envoy/api/v3alpha/auth/BUILD | 33 + api/envoy/api/v3alpha/auth/cert.proto | 403 + api/envoy/api/v3alpha/cds.proto | 653 + api/envoy/api/v3alpha/cluster/BUILD | 37 + .../api/v3alpha/cluster/circuit_breaker.proto | 65 + api/envoy/api/v3alpha/cluster/filter.proto | 26 + .../v3alpha/cluster/outlier_detection.proto | 114 + api/envoy/api/v3alpha/core/BUILD | 94 + api/envoy/api/v3alpha/core/address.proto | 117 + api/envoy/api/v3alpha/core/base.proto | 285 + .../api/v3alpha/core/config_source.proto | 122 + api/envoy/api/v3alpha/core/grpc_service.proto | 170 + api/envoy/api/v3alpha/core/health_check.proto | 262 + api/envoy/api/v3alpha/core/http_uri.proto | 48 + api/envoy/api/v3alpha/core/protocol.proto | 154 + api/envoy/api/v3alpha/discovery.proto | 225 + api/envoy/api/v3alpha/eds.proto | 131 + api/envoy/api/v3alpha/endpoint/BUILD | 34 + api/envoy/api/v3alpha/endpoint/endpoint.proto | 125 + .../api/v3alpha/endpoint/load_report.proto | 147 + api/envoy/api/v3alpha/lds.proto | 203 + api/envoy/api/v3alpha/listener/BUILD | 30 + api/envoy/api/v3alpha/listener/listener.proto | 206 + .../listener/udp_listener_config.proto | 30 + api/envoy/api/v3alpha/ratelimit/BUILD | 11 + .../api/v3alpha/ratelimit/ratelimit.proto | 65 + api/envoy/api/v3alpha/rds.proto | 132 + api/envoy/api/v3alpha/route/BUILD | 24 + api/envoy/api/v3alpha/route/route.proto | 1395 ++ api/envoy/api/v3alpha/srds.proto | 133 + api/envoy/config/accesslog/v2/BUILD | 6 +- api/envoy/config/accesslog/v2/als.proto | 2 - api/envoy/config/accesslog/v2/file.proto | 1 - api/envoy/config/accesslog/v2/wasm.proto | 1 - api/envoy/config/accesslog/v3alpha/BUILD | 20 + api/envoy/config/accesslog/v3alpha/als.proto | 63 + api/envoy/config/accesslog/v3alpha/file.proto | 31 + api/envoy/config/bootstrap/v2/BUILD | 33 +- api/envoy/config/bootstrap/v2/bootstrap.proto | 8 +- api/envoy/config/bootstrap/v3alpha/BUILD | 34 + .../config/bootstrap/v3alpha/bootstrap.proto | 313 + .../dynamic_forward_proxy/v2alpha/BUILD | 6 +- .../v2alpha/cluster.proto | 1 - .../dynamic_forward_proxy/v3alpha/BUILD | 15 + .../v3alpha/cluster.proto | 23 + api/envoy/config/cluster/redis/BUILD | 4 +- .../config/cluster/redis/redis_cluster.proto | 8 +- .../dynamic_forward_proxy/v2alpha/BUILD | 6 +- .../dynamic_forward_proxy/v3alpha/BUILD | 16 + .../v3alpha/dns_cache.proto | 69 + api/envoy/config/common/tap/v2alpha/BUILD | 9 +- .../config/common/tap/v2alpha/common.proto | 1 - api/envoy/config/common/tap/v3alpha/BUILD | 20 + .../config/common/tap/v3alpha/common.proto | 50 + api/envoy/config/filter/accesslog/v2/BUILD | 20 +- .../filter/accesslog/v2/accesslog.proto | 4 +- .../config/filter/accesslog/v3alpha/BUILD | 26 + .../filter/accesslog/v3alpha/accesslog.proto | 247 + .../config/filter/dubbo/router/v2alpha1/BUILD | 4 +- .../filter/dubbo/router/v2alpha1/router.proto | 1 - .../config/filter/dubbo/router/v3alpha/BUILD | 10 + .../filter/dubbo/router/v3alpha/router.proto | 13 + api/envoy/config/filter/fault/v2/BUILD | 6 +- api/envoy/config/filter/fault/v2/fault.proto | 5 +- api/envoy/config/filter/fault/v3alpha/BUILD | 17 + .../config/filter/fault/v3alpha/fault.proto | 81 + .../http/adaptive_concurrency/v2alpha/BUILD | 12 +- .../v2alpha/adaptive_concurrency.proto | 55 +- api/envoy/config/filter/http/buffer/v2/BUILD | 4 +- .../config/filter/http/buffer/v2/buffer.proto | 2 - .../config/filter/http/buffer/v3alpha/BUILD | 10 + .../filter/http/buffer/v3alpha/buffer.proto | 34 + api/envoy/config/filter/http/csrf/v2/BUILD | 9 +- .../config/filter/http/csrf/v2/csrf.proto | 2 - .../config/filter/http/csrf/v3alpha/BUILD | 19 + .../filter/http/csrf/v3alpha/csrf.proto | 49 + .../http/dynamic_forward_proxy/v2alpha/BUILD | 6 +- .../v2alpha/dynamic_forward_proxy.proto | 1 - .../http/dynamic_forward_proxy/v3alpha/BUILD | 15 + .../v3alpha/dynamic_forward_proxy.proto | 23 + .../config/filter/http/ext_authz/v2/BUILD | 10 +- .../filter/http/ext_authz/v2/ext_authz.proto | 18 +- .../filter/http/ext_authz/v3alpha/BUILD | 23 + .../http/ext_authz/v3alpha/ext_authz.proto | 205 + api/envoy/config/filter/http/fault/v2/BUILD | 10 +- .../config/filter/http/fault/v2/fault.proto | 1 - .../config/filter/http/fault/v3alpha/BUILD | 21 + .../filter/http/fault/v3alpha/fault.proto | 114 + .../grpc_http1_reverse_bridge/v2alpha1/BUILD | 4 +- .../v2alpha1/config.proto | 1 - .../grpc_http1_reverse_bridge/v3alpha/BUILD | 10 + .../v3alpha/config.proto | 26 + api/envoy/config/filter/http/gzip/v2/BUILD | 4 +- .../config/filter/http/gzip/v2/gzip.proto | 2 - .../config/filter/http/gzip/v3alpha/BUILD | 10 + .../filter/http/gzip/v3alpha/gzip.proto | 73 + .../filter/http/header_to_metadata/v2/BUILD | 5 +- .../v2/header_to_metadata.proto | 1 - .../http/header_to_metadata/v3alpha/BUILD | 10 + .../v3alpha/header_to_metadata.proto | 91 + .../config/filter/http/health_check/v2/BUILD | 18 +- .../http/health_check/v2/health_check.proto | 6 +- .../filter/http/health_check/v3alpha/BUILD | 19 + .../health_check/v3alpha/health_check.proto | 40 + .../config/filter/http/ip_tagging/v2/BUILD | 6 +- .../http/ip_tagging/v2/ip_tagging.proto | 1 - .../filter/http/ip_tagging/v3alpha/BUILD | 13 + .../http/ip_tagging/v3alpha/ip_tagging.proto | 52 + .../filter/http/jwt_authn/v2alpha/BUILD | 19 +- .../http/jwt_authn/v2alpha/config.proto | 3 - .../filter/http/jwt_authn/v3alpha/BUILD | 20 + .../filter/http/jwt_authn/v3alpha/README.md | 66 + .../http/jwt_authn/v3alpha/config.proto | 464 + api/envoy/config/filter/http/lua/v2/BUILD | 4 +- api/envoy/config/filter/http/lua/v2/lua.proto | 1 - .../config/filter/http/lua/v3alpha/BUILD | 10 + .../config/filter/http/lua/v3alpha/lua.proto | 20 + .../filter/http/original_src/v2alpha1/BUILD | 4 +- .../original_src/v2alpha1/original_src.proto | 2 - .../filter/http/original_src/v3alpha/BUILD | 10 + .../original_src/v3alpha/original_src.proto | 24 + .../config/filter/http/rate_limit/v2/BUILD | 6 +- .../http/rate_limit/v2/rate_limit.proto | 4 +- .../filter/http/rate_limit/v3alpha/BUILD | 15 + .../http/rate_limit/v3alpha/rate_limit.proto | 58 + api/envoy/config/filter/http/rbac/v2/BUILD | 6 +- .../config/filter/http/rbac/v2/rbac.proto | 2 - .../config/filter/http/rbac/v3alpha/BUILD | 13 + .../filter/http/rbac/v3alpha/rbac.proto | 36 + api/envoy/config/filter/http/router/v2/BUILD | 12 +- .../config/filter/http/router/v2/router.proto | 1 - .../config/filter/http/router/v3alpha/BUILD | 13 + .../filter/http/router/v3alpha/router.proto | 66 + api/envoy/config/filter/http/squash/v2/BUILD | 4 +- .../config/filter/http/squash/v2/squash.proto | 8 +- .../config/filter/http/squash/v3alpha/BUILD | 10 + .../filter/http/squash/v3alpha/squash.proto | 53 + .../config/filter/http/tap/v2alpha/BUILD | 6 +- .../config/filter/http/tap/v3alpha/BUILD | 15 + .../config/filter/http/tap/v3alpha/tap.proto | 21 + .../config/filter/http/transcoder/v2/BUILD | 9 +- .../http/transcoder/v2/transcoder.proto | 1 - .../filter/http/transcoder/v3alpha/BUILD | 10 + .../http/transcoder/v3alpha/transcoder.proto | 122 + api/envoy/config/filter/http/wasm/v2/BUILD | 4 +- .../config/filter/http/wasm/v2/wasm.proto | 1 - .../listener/original_src/v2alpha1/BUILD | 4 +- .../original_src/v2alpha1/original_src.proto | 2 - .../listener/original_src/v3alpha/BUILD | 10 + .../original_src/v3alpha/original_src.proto | 28 + .../filter/network/client_ssl_auth/v2/BUILD | 6 +- .../client_ssl_auth/v2/client_ssl_auth.proto | 4 +- .../network/client_ssl_auth/v3alpha/BUILD | 13 + .../v3alpha/client_ssl_auth.proto | 39 + .../filter/network/dubbo_proxy/v2alpha1/BUILD | 11 +- .../dubbo_proxy/v2alpha1/dubbo_proxy.proto | 4 +- .../network/dubbo_proxy/v2alpha1/route.proto | 4 - .../filter/network/dubbo_proxy/v3alpha/BUILD | 26 + .../network/dubbo_proxy/v3alpha/README.md | 1 + .../dubbo_proxy/v3alpha/dubbo_proxy.proto | 59 + .../network/dubbo_proxy/v3alpha/route.proto | 106 + .../config/filter/network/ext_authz/v2/BUILD | 6 +- .../network/ext_authz/v2/ext_authz.proto | 1 - .../filter/network/ext_authz/v3alpha/BUILD | 13 + .../network/ext_authz/v3alpha/ext_authz.proto | 34 + .../network/http_connection_manager/v2/BUILD | 25 +- .../v2/http_connection_manager.proto | 49 +- .../http_connection_manager/v3alpha/BUILD | 26 + .../v3alpha/http_connection_manager.proto | 593 + .../filter/network/mongo_proxy/v2/BUILD | 6 +- .../network/mongo_proxy/v2/mongo_proxy.proto | 1 - .../filter/network/mongo_proxy/v3alpha/BUILD | 13 + .../mongo_proxy/v3alpha/mongo_proxy.proto | 35 + .../filter/network/mysql_proxy/v1alpha1/BUILD | 4 +- .../mysql_proxy/v1alpha1/mysql_proxy.proto | 1 - .../config/filter/network/rate_limit/v2/BUILD | 9 +- .../network/rate_limit/v2/rate_limit.proto | 4 +- .../filter/network/rate_limit/v3alpha/BUILD | 19 + .../rate_limit/v3alpha/rate_limit.proto | 45 + api/envoy/config/filter/network/rbac/v2/BUILD | 6 +- .../config/filter/network/rbac/v2/rbac.proto | 2 - .../config/filter/network/rbac/v3alpha/BUILD | 13 + .../filter/network/rbac/v3alpha/rbac.proto | 50 + .../filter/network/redis_proxy/v2/BUILD | 9 +- .../network/redis_proxy/v2/redis_proxy.proto | 11 +- .../filter/network/redis_proxy/v3alpha/BUILD | 19 + .../redis_proxy/v3alpha/redis_proxy.proto | 233 + .../config/filter/network/tcp_proxy/v2/BUILD | 9 +- .../network/tcp_proxy/v2/tcp_proxy.proto | 5 +- .../filter/network/tcp_proxy/v3alpha/BUILD | 20 + .../network/tcp_proxy/v3alpha/tcp_proxy.proto | 143 + .../network/thrift_proxy/v2alpha1/BUILD | 9 +- .../network/thrift_proxy/v2alpha1/route.proto | 2 - .../thrift_proxy/v2alpha1/thrift_proxy.proto | 4 - .../filter/network/thrift_proxy/v3alpha/BUILD | 22 + .../network/thrift_proxy/v3alpha/README.md | 1 + .../network/thrift_proxy/v3alpha/route.proto | 128 + .../thrift_proxy/v3alpha/thrift_proxy.proto | 118 + .../network/zookeeper_proxy/v1alpha1/BUILD | 4 +- .../v1alpha1/zookeeper_proxy.proto | 1 - .../filter/thrift/rate_limit/v2alpha1/BUILD | 9 +- .../rate_limit/v2alpha1/rate_limit.proto | 4 +- .../filter/thrift/rate_limit/v3alpha/BUILD | 19 + .../rate_limit/v3alpha/rate_limit.proto | 49 + .../filter/thrift/router/v2alpha1/BUILD | 4 +- .../thrift/router/v2alpha1/router.proto | 1 - .../config/filter/thrift/router/v3alpha/BUILD | 10 + .../filter/thrift/router/v3alpha/router.proto | 13 + .../config/grpc_credential/v2alpha/BUILD | 19 +- .../grpc_credential/v2alpha/aws_iam.proto | 1 - .../v2alpha/file_based_metadata.proto | 1 - .../config/grpc_credential/v3alpha/BUILD | 18 + .../grpc_credential/v3alpha/aws_iam.proto | 28 + .../v3alpha/file_based_metadata.proto | 27 + .../config/health_checker/redis/v2/BUILD | 4 +- .../health_checker/redis/v2/redis.proto | 1 - .../config/health_checker/redis/v3alpha/BUILD | 10 + .../health_checker/redis/v3alpha/redis.proto | 18 + api/envoy/config/metrics/v2/BUILD | 26 +- api/envoy/config/metrics/v2/stats.proto | 1 - api/envoy/config/metrics/v3alpha/BUILD | 33 + .../metrics/v3alpha/metrics_service.proto | 21 + api/envoy/config/metrics/v3alpha/stats.proto | 330 + api/envoy/config/overload/v2alpha/BUILD | 9 +- .../config/overload/v2alpha/overload.proto | 1 - api/envoy/config/overload/v3alpha/BUILD | 11 + .../config/overload/v3alpha/overload.proto | 77 + api/envoy/config/ratelimit/v2/BUILD | 14 +- api/envoy/config/ratelimit/v2/rls.proto | 1 - api/envoy/config/ratelimit/v3alpha/BUILD | 16 + api/envoy/config/ratelimit/v3alpha/rls.proto | 25 + api/envoy/config/rbac/v2/BUILD | 23 +- api/envoy/config/rbac/v2/rbac.proto | 9 +- api/envoy/config/rbac/v3alpha/BUILD | 33 + api/envoy/config/rbac/v3alpha/rbac.proto | 211 + .../resource_monitor/fixed_heap/v2alpha/BUILD | 4 +- .../fixed_heap/v2alpha/fixed_heap.proto | 1 - .../resource_monitor/fixed_heap/v3alpha/BUILD | 11 + .../fixed_heap/v3alpha/fixed_heap.proto | 18 + .../injected_resource/v2alpha/BUILD | 4 +- .../v2alpha/injected_resource.proto | 1 - .../injected_resource/v3alpha/BUILD | 11 + .../v3alpha/injected_resource.proto | 19 + .../config/retry/previous_priorities/BUILD | 6 +- api/envoy/config/trace/v2/BUILD | 18 +- api/envoy/config/trace/v2/trace.proto | 37 +- api/envoy/config/trace/v3alpha/BUILD | 22 + api/envoy/config/trace/v3alpha/trace.proto | 203 + .../transport_socket/alts/v2alpha/BUILD | 6 +- .../transport_socket/alts/v2alpha/alts.proto | 1 - .../transport_socket/alts/v3alpha/BUILD | 15 + .../transport_socket/alts/v3alpha/alts.proto | 23 + .../config/transport_socket/tap/v2alpha/BUILD | 9 +- .../transport_socket/tap/v2alpha/tap.proto | 1 - .../config/transport_socket/tap/v3alpha/BUILD | 19 + .../transport_socket/tap/v3alpha/tap.proto | 25 + api/envoy/config/wasm/v2/BUILD | 9 +- api/envoy/data/accesslog/v2/BUILD | 15 +- api/envoy/data/accesslog/v2/accesslog.proto | 32 +- api/envoy/data/accesslog/v3alpha/BUILD | 19 + .../data/accesslog/v3alpha/accesslog.proto | 353 + api/envoy/data/cluster/v2alpha/BUILD | 4 +- .../v2alpha/outlier_detection_event.proto | 17 +- api/envoy/data/cluster/v3alpha/BUILD | 13 + .../v3alpha/outlier_detection_event.proto | 99 + api/envoy/data/core/v2alpha/BUILD | 6 +- .../core/v2alpha/health_check_event.proto | 5 +- api/envoy/data/core/v3alpha/BUILD | 19 + .../core/v3alpha/health_check_event.proto | 82 + api/envoy/data/tap/v2alpha/BUILD | 6 +- api/envoy/data/tap/v2alpha/transport.proto | 1 - api/envoy/data/tap/v3alpha/BUILD | 41 + api/envoy/data/tap/v3alpha/common.proto | 31 + api/envoy/data/tap/v3alpha/http.proto | 60 + api/envoy/data/tap/v3alpha/transport.proto | 96 + api/envoy/data/tap/v3alpha/wrapper.proto | 34 + api/envoy/service/accesslog/v2/BUILD | 19 +- api/envoy/service/accesslog/v2/als.proto | 3 - api/envoy/service/accesslog/v3alpha/BUILD | 22 + api/envoy/service/accesslog/v3alpha/als.proto | 70 + api/envoy/service/auth/v2/BUILD | 10 +- .../service/auth/v2/attribute_context.proto | 11 +- api/envoy/service/auth/v2/external_auth.proto | 1 - api/envoy/service/auth/v2alpha/BUILD | 9 +- .../service/auth/v2alpha/external_auth.proto | 2 - api/envoy/service/auth/v3alpha/BUILD | 36 + .../auth/v3alpha/attribute_context.proto | 151 + .../service/auth/v3alpha/external_auth.proto | 76 + api/envoy/service/discovery/v2/BUILD | 45 +- api/envoy/service/discovery/v2/ads.proto | 1 - api/envoy/service/discovery/v3alpha/BUILD | 50 + api/envoy/service/discovery/v3alpha/ads.proto | 37 + api/envoy/service/discovery/v3alpha/hds.proto | 127 + .../service/discovery/v3alpha/rtds.proto | 50 + api/envoy/service/discovery/v3alpha/sds.proto | 34 + api/envoy/service/load_stats/v2/BUILD | 19 +- api/envoy/service/load_stats/v2/lrs.proto | 1 - api/envoy/service/load_stats/v3alpha/BUILD | 21 + .../service/load_stats/v3alpha/lrs.proto | 81 + api/envoy/service/metrics/v2/BUILD | 19 +- .../service/metrics/v2/metrics_service.proto | 1 - api/envoy/service/metrics/v3alpha/BUILD | 23 + .../metrics/v3alpha/metrics_service.proto | 40 + api/envoy/service/ratelimit/v2/BUILD | 20 +- api/envoy/service/ratelimit/v2/rls.proto | 1 - api/envoy/service/ratelimit/v3alpha/BUILD | 22 + api/envoy/service/ratelimit/v3alpha/rls.proto | 94 + api/envoy/service/tap/v2alpha/BUILD | 12 +- api/envoy/service/tap/v3alpha/BUILD | 46 + api/envoy/service/tap/v3alpha/common.proto | 200 + api/envoy/service/tap/v3alpha/tap.proto | 50 + api/envoy/service/tap/v3alpha/tapds.proto | 44 + api/envoy/service/trace/v2/BUILD | 19 +- .../service/trace/v2/trace_service.proto | 1 - api/envoy/service/trace/v3alpha/BUILD | 22 + .../service/trace/v3alpha/trace_service.proto | 45 + api/envoy/type/BUILD | 21 +- api/envoy/type/matcher/BUILD | 42 +- api/envoy/type/matcher/metadata.proto | 1 - api/envoy/type/matcher/number.proto | 1 - api/envoy/type/matcher/regex.proto | 36 + api/envoy/type/matcher/string.proto | 12 +- api/envoy/type/matcher/value.proto | 1 - api/envoy/type/percent.proto | 3 - api/envoy/type/range.proto | 5 - api/migration/v3alpha.sh | 6 + api/test/build/BUILD | 22 +- api/test/build/build_test.cc | 1 + api/test/build/go_build_test.go | 21 +- api/udpa/data/orca/v1/BUILD | 16 - api/udpa/data/orca/v1/orca_load_report.proto | 36 - api/udpa/service/orca/v1/BUILD | 20 - api/udpa/service/orca/v1/orca.proto | 38 - bazel/BUILD | 36 +- bazel/README.md | 5 +- bazel/envoy_binary.bzl | 3 +- bazel/envoy_internal.bzl | 10 + bazel/envoy_library.bzl | 11 + bazel/envoy_test.bzl | 29 +- bazel/external/quiche.BUILD | 228 +- bazel/foreign_cc/BUILD | 34 +- bazel/grpc-rename-gettid.patch | 78 + bazel/io_opentracing_cpp.patch | 17 + bazel/repositories.bzl | 13 +- bazel/repository_locations.bzl | 45 +- .../{bazel_0.28.1 => bazel_0.29.1}/cc/BUILD | 6 +- .../cc/armeabi_cc_toolchain_config.bzl | 0 .../cc/builtin_include_directory_paths | 14 + .../cc/cc_toolchain_config.bzl | 155 +- .../cc/cc_wrapper.sh | 0 .../config/BUILD | 4 +- .../{bazel_0.28.1 => bazel_0.29.1}/cc/BUILD | 6 +- .../cc/armeabi_cc_toolchain_config.bzl | 0 .../cc/builtin_include_directory_paths | 12 + .../cc/cc_toolchain_config.bzl | 155 +- .../cc/cc_wrapper.sh | 0 .../config/BUILD | 4 +- .../{bazel_0.28.1 => bazel_0.29.1}/cc/BUILD | 6 +- .../cc/armeabi_cc_toolchain_config.bzl | 0 .../cc/builtin_include_directory_paths | 14 + .../cc/cc_toolchain_config.bzl | 155 +- .../cc/cc_wrapper.sh | 0 .../config/BUILD | 4 +- bazel/toolchains/configs/versions.bzl | 12 +- bazel/toolchains/rbe_toolchains_config.bzl | 8 +- ci/README.md | 5 +- ci/build_setup.sh | 1 + ci/do_ci.sh | 8 + ci/envoy_build_sha.sh | 2 +- ci/run_envoy_docker.sh | 2 +- docs/build.sh | 139 +- docs/root/_static/css/envoy.css | 9 +- docs/root/api-v2/listeners/listeners.rst | 2 + docs/root/api-v2/types/types.rst | 1 + docs/root/configuration/advanced/advanced.rst | 7 + .../well_known_dynamic_metadata.rst | 0 docs/root/configuration/configuration.rst | 25 +- docs/root/configuration/http/http.rst | 8 + .../http_conn_man/header_sanitizing.rst | 0 .../{ => http}/http_conn_man/headers.rst | 6 +- .../http_conn_man/http_conn_man.rst | 0 .../{ => http}/http_conn_man/overview.rst | 0 .../{ => http}/http_conn_man/rds.rst | 0 .../http_conn_man/route_matching.rst | 0 .../{ => http}/http_conn_man/runtime.rst | 0 .../{ => http}/http_conn_man/stats.rst | 0 .../http_conn_man/traffic_splitting.rst | 0 .../{ => http}/http_filters/buffer_filter.rst | 0 .../{ => http}/http_filters/cors_filter.rst | 0 .../{ => http}/http_filters/csrf_filter.rst | 0 .../dynamic_forward_proxy_filter.rst | 0 .../http_filters/dynamodb_filter.rst | 0 .../http_filters/ext_authz_filter.rst | 0 .../{ => http}/http_filters/fault_filter.rst | 0 .../http_filters/grpc_http1_bridge_filter.rst | 0 .../grpc_http1_reverse_bridge_filter.rst | 0 .../grpc_json_transcoder_filter.rst | 0 .../http_filters/grpc_web_filter.rst | 0 .../{ => http}/http_filters/gzip_filter.rst | 0 .../header_to_metadata_filter.rst | 0 .../http_filters/health_check_filter.rst | 0 .../{ => http}/http_filters/http_filters.rst | 0 .../http_filters/ip_tagging_filter.rst | 0 .../http_filters/jwt_authn_filter.rst | 0 .../{ => http}/http_filters/lua_filter.rst | 0 .../http_filters/original_src_filter.rst | 0 .../http_filters/rate_limit_filter.rst | 0 .../{ => http}/http_filters/rbac_filter.rst | 0 .../{ => http}/http_filters/router_filter.rst | 0 .../{ => http}/http_filters/squash_filter.rst | 0 .../{ => http}/http_filters/tap_filter.rst | 0 .../listener_filters/http_inspector.rst | 0 .../listener_filters/listener_filters.rst | 0 .../listener_filters/original_dst_filter.rst | 0 .../listener_filters/original_src_filter.rst | 0 .../listener_filters/proxy_protocol.rst | 0 .../listener_filters/tls_inspector.rst | 0 .../configuration/listeners/listeners.rst | 2 + .../client_ssl_auth_filter.rst | 0 .../network_filters/dubbo_proxy_filter.rst | 0 .../network_filters/echo_filter.rst | 0 .../network_filters/ext_authz_filter.rst | 0 .../network_filters/mongo_proxy_filter.rst | 0 .../network_filters/mysql_proxy_filter.rst | 0 .../network_filters/network_filters.rst | 0 .../network_filters/rate_limit_filter.rst | 0 .../network_filters/rbac_filter.rst | 0 .../network_filters/redis_proxy_filter.rst | 0 .../network_filters/sni_cluster_filter.rst | 0 .../network_filters/tcp_proxy_filter.rst | 0 .../network_filters/thrift_proxy_filter.rst | 0 .../zookeeper_proxy_filter.rst | 0 .../{ => observability}/access_log.rst | 0 .../observability/observability.rst | 8 + .../{ => observability}/statistics.rst | 7 +- .../configuration/operations/operations.rst | 9 + .../overload_manager/overload_manager.rst | 0 .../{ => operations}/runtime.rst | 2 +- .../{ => operations}/tools/router_check.rst | 0 .../other_features/other_features.rst | 7 + .../{ => other_features}/rate_limit.rst | 0 .../dubbo_filters/dubbo_filters.rst | 0 .../dubbo_filters/router_filter.rst | 0 .../other_protocols/other_protocols.rst | 8 + .../thrift_filters/rate_limit_filter.rst | 0 .../thrift_filters/router_filter.rst | 0 .../thrift_filters/thrift_filters.rst | 0 .../configuration/overview/v2_overview.rst | 24 + .../configuration/{ => security}/secret.rst | 0 docs/root/configuration/security/security.rst | 7 + .../{ => upstream}/cluster_manager/cds.rst | 0 .../cluster_circuit_breakers.rst | 0 .../cluster_manager/cluster_hc.rst | 0 .../cluster_manager/cluster_manager.rst | 0 .../cluster_manager/cluster_runtime.rst | 25 + .../cluster_manager/cluster_stats.rst | 4 + .../cluster_manager/overview.rst | 0 .../health_checkers/health_checkers.rst | 0 .../{ => upstream}/health_checkers/redis.rst | 0 docs/root/configuration/upstream/upstream.rst | 8 + .../configuration/xds_subscription_stats.rst | 23 - docs/root/extending/extending.rst | 1 + .../install/tools/route_table_check_tool.rst | 13 + .../intro/arch_overview/http/http_routing.rst | 29 + .../intro/arch_overview/http/websocket.rst | 16 +- .../arch_overview/observability/tracing.rst | 9 +- .../arch_overview/other_protocols/redis.rst | 16 + .../arch_overview/security/rbac_filter.rst | 64 + .../root/intro/arch_overview/security/ssl.rst | 4 + .../load_balancing/panic_threshold.rst | 19 +- .../upstream/load_balancing/subsets.rst | 20 +- .../intro/arch_overview/upstream/outlier.rst | 34 + docs/root/intro/deprecated.rst | 29 +- docs/root/intro/version_history.rst | 32 +- examples/redis/envoy.yaml | 4 +- include/envoy/api/io_error.h | 2 + include/envoy/common/BUILD | 13 + include/envoy/common/matchers.h | 28 + include/envoy/common/regex.h | 21 + include/envoy/event/dispatcher.h | 33 +- include/envoy/event/timer.h | 13 +- include/envoy/http/conn_pool.h | 4 +- include/envoy/network/connection.h | 2 +- include/envoy/network/connection_handler.h | 65 + include/envoy/network/listener.h | 9 + include/envoy/network/transport_socket.h | 2 +- include/envoy/router/BUILD | 1 + include/envoy/router/rds.h | 5 + .../router/route_config_provider_manager.h | 5 +- .../router/route_config_update_receiver.h | 1 + include/envoy/router/router.h | 10 +- include/envoy/server/BUILD | 7 + .../envoy/server/active_udp_listener_config.h | 33 + include/envoy/server/filter_config.h | 10 +- include/envoy/server/health_checker_config.h | 5 + include/envoy/server/instance.h | 2 +- include/envoy/server/lifecycle_notifier.h | 5 + include/envoy/server/options.h | 5 + .../envoy/server/resource_monitor_config.h | 7 + include/envoy/ssl/BUILD | 3 + include/envoy/ssl/connection.h | 14 +- include/envoy/ssl/context_manager.h | 7 + include/envoy/ssl/private_key/BUILD | 35 + include/envoy/ssl/private_key/private_key.h | 85 + .../ssl/private_key/private_key_callbacks.h | 25 + .../ssl/private_key/private_key_config.h | 22 + include/envoy/ssl/tls_certificate_config.h | 6 + include/envoy/stats/symbol_table.h | 2 +- include/envoy/stream_info/stream_info.h | 19 +- include/envoy/thread/thread.h | 8 +- include/envoy/thread_local/thread_local.h | 11 + include/envoy/tracing/http_tracer.h | 7 + include/envoy/upstream/retry.h | 6 +- .../common/access_log/access_log_formatter.cc | 1 + source/common/access_log/access_log_impl.cc | 38 +- source/common/access_log/access_log_impl.h | 14 +- .../access_log/access_log_manager_impl.cc | 14 +- .../access_log/access_log_manager_impl.h | 19 +- source/common/common/BUILD | 15 + source/common/common/cleanup.h | 10 +- source/common/common/lock_guard.h | 23 +- source/common/common/logger.h | 2 +- source/common/common/matchers.cc | 19 +- source/common/common/matchers.h | 19 +- source/common/common/non_copyable.h | 13 +- source/common/common/regex.cc | 78 + source/common/common/regex.h | 44 + source/common/common/thread.h | 14 +- source/common/common/utility.cc | 21 +- source/common/common/utility.h | 96 +- source/common/config/config_provider_impl.cc | 10 + source/common/config/config_provider_impl.h | 47 +- source/common/config/grpc_mux_impl.h | 2 +- source/common/event/BUILD | 1 + source/common/event/dispatcher_impl.cc | 8 +- source/common/event/dispatcher_impl.h | 6 +- source/common/event/libevent_scheduler.cc | 4 +- source/common/event/libevent_scheduler.h | 2 +- source/common/event/real_time_system.cc | 4 +- source/common/event/timer_impl.cc | 18 +- source/common/event/timer_impl.h | 10 +- source/common/http/BUILD | 3 + source/common/http/codec_client.cc | 1 + source/common/http/codec_client.h | 2 + source/common/http/codes.h | 2 +- source/common/http/conn_manager_config.h | 10 + source/common/http/conn_manager_impl.cc | 70 +- source/common/http/conn_manager_impl.h | 7 +- source/common/http/conn_manager_utility.cc | 24 +- source/common/http/header_map_impl.cc | 3 +- source/common/http/header_utility.cc | 15 +- source/common/http/header_utility.h | 35 +- source/common/http/headers.h | 4 + source/common/http/http1/codec_impl.cc | 33 +- source/common/http/http1/conn_pool.cc | 3 +- source/common/http/http2/conn_pool.cc | 3 +- source/common/network/connection_impl.h | 2 +- .../common/network/io_socket_handle_impl.cc | 6 +- source/common/network/raw_buffer_socket.h | 2 +- source/common/network/socket_option_impl.cc | 12 +- source/common/network/socket_option_impl.h | 12 +- .../common/protobuf/message_validator_impl.cc | 1 - .../common/protobuf/message_validator_impl.h | 2 + source/common/protobuf/utility.cc | 32 +- source/common/protobuf/utility.h | 43 +- source/common/router/BUILD | 20 +- source/common/router/config_impl.cc | 115 +- source/common/router/config_impl.h | 24 +- source/common/router/config_utility.cc | 48 +- source/common/router/config_utility.h | 15 +- source/common/router/rds_impl.cc | 37 +- source/common/router/rds_impl.h | 23 +- .../route_config_update_receiver_impl.cc | 5 +- source/common/router/router.cc | 39 +- source/common/router/router.h | 8 +- source/common/router/router_ratelimit.cc | 7 +- source/common/router/router_ratelimit.h | 2 +- source/common/router/scoped_config_impl.cc | 57 +- source/common/router/scoped_config_impl.h | 65 +- source/common/router/scoped_config_manager.cc | 22 - source/common/router/scoped_config_manager.h | 49 - source/common/router/scoped_rds.cc | 278 +- source/common/router/scoped_rds.h | 100 +- source/common/router/vhds.cc | 3 +- source/common/router/vhds.h | 5 +- source/common/runtime/runtime_features.cc | 2 + source/common/runtime/runtime_impl.cc | 10 +- source/common/runtime/runtime_impl.h | 6 +- source/common/secret/sds_api.cc | 5 +- source/common/secret/sds_api.h | 3 +- source/common/ssl/BUILD | 2 + .../common/ssl/tls_certificate_config_impl.cc | 19 +- .../common/ssl/tls_certificate_config_impl.h | 9 +- source/common/stats/BUILD | 13 + source/common/stats/isolated_store_impl.cc | 4 +- source/common/stats/isolated_store_impl.h | 2 +- source/common/stats/stats_matcher_impl.cc | 6 +- source/common/stats/stats_matcher_impl.h | 2 +- source/common/stats/symbol_table_creator.cc | 24 + source/common/stats/symbol_table_creator.h | 57 + source/common/stats/symbol_table_impl.h | 54 +- source/common/stats/tag_extractor_impl.cc | 5 +- source/common/stats/tag_producer_impl.h | 4 +- source/common/stream_info/stream_info_impl.h | 16 +- source/common/tcp/conn_pool.cc | 1 + source/common/tcp_proxy/tcp_proxy.cc | 1 + source/common/tcp_proxy/tcp_proxy.h | 4 +- .../common/thread_local/thread_local_impl.cc | 109 +- .../common/thread_local/thread_local_impl.h | 40 +- source/common/tracing/http_tracer_impl.cc | 34 +- source/common/tracing/http_tracer_impl.h | 4 + source/common/upstream/BUILD | 2 + source/common/upstream/cds_api_impl.cc | 11 +- source/common/upstream/cds_api_impl.h | 2 +- .../common/upstream/cluster_factory_impl.cc | 3 +- source/common/upstream/cluster_factory_impl.h | 3 +- .../common/upstream/cluster_manager_impl.cc | 105 +- source/common/upstream/cluster_manager_impl.h | 26 +- source/common/upstream/eds.cc | 6 +- source/common/upstream/eds.h | 4 +- source/common/upstream/health_checker_impl.cc | 21 +- source/common/upstream/health_checker_impl.h | 12 +- .../upstream/health_discovery_service.cc | 10 +- .../upstream/health_discovery_service.h | 3 +- source/common/upstream/load_balancer_impl.cc | 46 +- source/common/upstream/load_balancer_impl.h | 4 +- .../common/upstream/original_dst_cluster.cc | 4 - .../common/upstream/outlier_detection_impl.cc | 172 +- .../common/upstream/outlier_detection_impl.h | 38 +- source/common/upstream/thread_aware_lb_impl.h | 6 +- source/common/upstream/upstream_impl.cc | 22 +- source/common/upstream/upstream_impl.h | 6 +- source/exe/BUILD | 31 +- source/exe/main_common.cc | 5 +- source/exe/main_common.h | 2 +- source/exe/platform_impl.h | 20 + source/exe/posix/platform_impl.cc | 14 + source/exe/posix/platform_impl.h | 18 - source/exe/win32/platform_impl.cc | 24 + source/exe/win32/platform_impl.h | 32 - .../extensions/access_loggers/file/config.cc | 3 +- source/extensions/access_loggers/grpc/BUILD | 40 +- .../access_loggers/grpc/config_utils.cc | 25 + .../access_loggers/grpc/config_utils.h | 18 + .../grpc/grpc_access_log_impl.cc | 18 +- .../grpc/grpc_access_log_impl.h | 23 +- .../grpc/grpc_access_log_proto_descriptors.cc | 4 +- .../grpc/grpc_access_log_proto_descriptors.h | 4 +- .../grpc/grpc_access_log_utils.cc | 6 +- .../access_loggers/grpc/http_config.cc | 25 +- .../access_loggers/grpc/http_config.h | 2 - .../grpc/http_grpc_access_log_impl.cc | 4 +- .../access_loggers/grpc/tcp_config.cc | 51 + .../access_loggers/grpc/tcp_config.h | 29 + .../grpc/tcp_grpc_access_log_impl.cc | 48 + .../grpc/tcp_grpc_access_log_impl.h | 60 + .../extensions/access_loggers/wasm/config.cc | 4 +- .../access_loggers/well_known_names.h | 2 + .../clusters/redis/redis_cluster.cc | 41 +- .../extensions/clusters/redis/redis_cluster.h | 16 +- .../clusters/redis/redis_cluster_lb.cc | 5 + source/extensions/common/tap/tap_matcher.cc | 7 +- source/extensions/common/tap/tap_matcher.h | 2 +- source/extensions/common/wasm/BUILD | 1 + source/extensions/common/wasm/null/BUILD | 6 +- source/extensions/common/wasm/null/null.cc | 2 +- source/extensions/common/wasm/null/null.h | 2 +- .../common/wasm/null/null_plugin.cc | 91 +- source/extensions/common/wasm/null/null_vm.cc | 31 +- source/extensions/common/wasm/null/null_vm.h | 15 +- .../common/wasm/null/null_vm_plugin.h | 18 +- .../wasm/null/sample_plugin/plugin_wrapper.cc | 4 +- .../common/wasm/null/wasm_api_impl.h | 15 +- source/extensions/common/wasm/v8/v8.cc | 21 +- source/extensions/common/wasm/wasm.cc | 224 +- source/extensions/common/wasm/wasm.h | 56 +- source/extensions/common/wasm/wasm_vm.h | 283 +- source/extensions/common/wasm/wavm/wavm.cc | 27 +- .../extensions/common/wasm/well_known_names.h | 4 +- source/extensions/extensions_build_config.bzl | 1 + .../extensions/filters/common/expr/context.cc | 31 +- .../extensions/filters/common/expr/context.h | 15 +- .../filters/common/expr/evaluator.cc | 2 + .../common/ext_authz/check_request_utils.cc | 35 +- .../common/ext_authz/check_request_utils.h | 1 + .../common/ext_authz/ext_authz_http_impl.cc | 49 +- .../common/ext_authz/ext_authz_http_impl.h | 6 +- .../extensions/filters/common/lua/wrappers.h | 2 +- .../filters/common/rbac/matchers.cc | 2 +- .../extensions/filters/common/rbac/matchers.h | 8 +- .../adaptive_concurrency_filter.cc | 28 +- .../adaptive_concurrency_filter.h | 6 +- .../concurrency_controller/BUILD | 12 +- .../concurrency_controller.h | 13 +- .../gradient_controller.cc | 186 + .../gradient_controller.h | 205 + .../filters/http/common/factory_base.h | 9 +- .../filters/http/cors/cors_filter.cc | 35 +- .../filters/http/cors/cors_filter.h | 5 +- .../filters/http/csrf/csrf_filter.cc | 9 +- .../filters/http/csrf/csrf_filter.h | 31 +- .../filters/http/ext_authz/ext_authz.cc | 15 +- .../filters/http/ext_authz/ext_authz.h | 9 + .../filters/http/fault/fault_filter.cc | 41 +- .../filters/http/fault/fault_filter.h | 24 +- .../json_transcoder_filter.cc | 2 - .../filters/http/health_check/config.cc | 8 +- .../filters/http/health_check/health_check.h | 2 +- .../extensions/filters/http/ip_tagging/BUILD | 1 + .../http/ip_tagging/ip_tagging_filter.cc | 52 +- .../http/ip_tagging/ip_tagging_filter.h | 49 +- .../filters/http/jwt_authn/matcher.cc | 38 +- .../filters/http/squash/squash_filter.cc | 6 +- .../filters/http/squash/squash_filter.h | 2 + .../original_src_config_factory.cc | 5 +- .../listener/tls_inspector/tls_inspector.cc | 78 +- .../listener/tls_inspector/tls_inspector.h | 12 +- .../filters/network/common/factory_base.h | 12 +- .../filters/network/common/redis/BUILD | 18 + .../filters/network/common/redis/client.h | 19 +- .../network/common/redis/client_impl.cc | 84 +- .../network/common/redis/client_impl.h | 25 +- .../common/redis/redis_command_stats.cc | 110 + .../common/redis/redis_command_stats.h | 66 + .../filters/network/dubbo_proxy/decoder.cc | 8 +- .../dubbo_proxy/filters/factory_base.h | 5 +- .../dubbo_proxy/router/route_matcher.cc | 7 +- .../dubbo_proxy/router/route_matcher.h | 4 +- .../network/http_connection_manager/BUILD | 1 + .../network/http_connection_manager/config.cc | 58 +- .../network/http_connection_manager/config.h | 5 + .../filters/network/mongo_proxy/BUILD | 11 + .../filters/network/mongo_proxy/config.cc | 7 +- .../network/mongo_proxy/mongo_stats.cc | 47 + .../filters/network/mongo_proxy/mongo_stats.h | 56 + .../filters/network/mongo_proxy/proxy.cc | 96 +- .../filters/network/mongo_proxy/proxy.h | 17 +- .../filters/network/redis_proxy/BUILD | 2 + .../filters/network/redis_proxy/config.cc | 6 +- .../filters/network/redis_proxy/config.h | 13 + .../network/redis_proxy/conn_pool_impl.cc | 38 +- .../network/redis_proxy/conn_pool_impl.h | 4 +- .../network/redis_proxy/router_impl.cc | 8 +- .../filters/network/redis_proxy/router_impl.h | 2 +- .../network/thrift_proxy/conn_manager.cc | 10 +- .../thrift_proxy/filters/factory_base.h | 5 +- .../thrift_proxy/router/router_impl.cc | 8 +- .../network/thrift_proxy/router/router_impl.h | 2 +- .../router/router_ratelimit_impl.cc | 7 +- .../router/router_ratelimit_impl.h | 2 +- .../grpc_credentials/aws_iam/config.cc | 5 +- .../file_based_metadata/config.cc | 7 +- source/extensions/health_checkers/redis/BUILD | 1 + .../health_checkers/redis/config.cc | 2 +- .../extensions/health_checkers/redis/redis.cc | 16 +- .../extensions/health_checkers/redis/redis.h | 7 +- .../health_checkers/redis/utility.h | 2 +- source/extensions/quic_listeners/quiche/BUILD | 152 +- .../quiche/active_quic_listener.cc | 84 + .../quiche/active_quic_listener.h | 107 + .../quiche/active_quic_listener_config.cc | 25 + .../quiche/active_quic_listener_config.h | 27 + .../quic_listeners/quiche/codec_impl.cc | 23 + .../quic_listeners/quiche/codec_impl.h | 58 + .../quiche/envoy_quic_connection.cc | 59 + .../quiche/envoy_quic_connection.h | 71 + .../quiche/envoy_quic_connection_helper.h | 42 + .../quiche/envoy_quic_dispatcher.cc | 59 + .../quiche/envoy_quic_dispatcher.h | 78 + .../quiche/envoy_quic_fake_proof_source.h | 4 +- .../quiche/envoy_quic_fake_proof_verifier.h | 5 +- .../quiche/envoy_quic_server_session.cc | 190 + .../quiche/envoy_quic_server_session.h | 163 + .../quiche/envoy_quic_server_stream.cc | 132 + .../quiche/envoy_quic_server_stream.h | 52 + .../quic_listeners/quiche/envoy_quic_stream.h | 42 + .../quic_listeners/quiche/envoy_quic_utils.cc | 66 + .../quic_listeners/quiche/envoy_quic_utils.h | 27 +- .../quic_listeners/quiche/platform/BUILD | 9 + .../quiche/platform/flags_list.h | 408 +- .../platform/quic_mem_slice_storage_impl.h | 2 + .../quiche/platform/quic_pcc_sender_impl.h | 10 +- .../quiche/platform/quic_text_utils_impl.h | 4 + .../quiche/platform/spdy_containers_impl.h | 2 + .../quiche/platform/spdy_map_util_impl.h | 18 + .../quiche/quic_io_handle_wrapper.h | 68 + .../resource_monitors/common/factory_base.h | 5 +- .../priority/previous_priorities/config.cc | 10 +- .../priority/previous_priorities/config.h | 6 +- .../stat_sinks/common/statsd/statsd.cc | 6 +- .../stat_sinks/dog_statsd/config.cc | 3 +- .../extensions/stat_sinks/hystrix/config.cc | 3 +- .../stat_sinks/metrics_service/config.cc | 2 +- .../grpc_metrics_service_impl.cc | 44 +- .../grpc_metrics_service_impl.h | 2 +- source/extensions/stat_sinks/statsd/config.cc | 3 +- .../extensions/tracers/common/factory_base.h | 6 +- source/extensions/tracers/opencensus/BUILD | 2 + .../opencensus/opencensus_tracer_impl.cc | 6 + source/extensions/tracers/zipkin/BUILD | 1 + .../extensions/tracers/zipkin/span_buffer.cc | 217 +- .../extensions/tracers/zipkin/span_buffer.h | 97 +- source/extensions/tracers/zipkin/tracer.cc | 6 +- source/extensions/tracers/zipkin/tracer.h | 10 +- .../tracers/zipkin/tracer_interface.h | 21 + source/extensions/tracers/zipkin/util.cc | 14 +- source/extensions/tracers/zipkin/util.h | 24 + .../tracers/zipkin/zipkin_core_constants.h | 3 + .../tracers/zipkin/zipkin_core_types.cc | 3 + .../tracers/zipkin/zipkin_core_types.h | 28 +- .../tracers/zipkin/zipkin_tracer_impl.cc | 69 +- .../tracers/zipkin/zipkin_tracer_impl.h | 33 +- .../transport_sockets/alts/config.cc | 2 +- .../transport_sockets/alts/tsi_socket.h | 2 +- .../transport_sockets/tap/config.cc | 4 +- .../extensions/transport_sockets/tap/tap.cc | 2 +- source/extensions/transport_sockets/tap/tap.h | 2 +- source/extensions/transport_sockets/tls/BUILD | 5 + .../transport_sockets/tls/config.cc | 6 +- .../tls/context_config_impl.cc | 8 +- .../transport_sockets/tls/context_impl.cc | 155 +- .../transport_sockets/tls/context_impl.h | 17 +- .../tls/context_manager_impl.h | 7 + .../transport_sockets/tls/private_key/BUILD | 26 + .../private_key/private_key_manager_impl.cc | 30 + .../private_key/private_key_manager_impl.h | 23 + .../transport_sockets/tls/ssl_socket.cc | 221 +- .../transport_sockets/tls/ssl_socket.h | 73 +- .../transport_sockets/well_known_names.h | 1 + source/server/BUILD | 24 + .../server/active_raw_udp_listener_config.cc | 30 + .../server/active_raw_udp_listener_config.h | 33 + source/server/config_validation/server.h | 4 +- source/server/connection_handler_impl.cc | 119 +- source/server/connection_handler_impl.h | 114 +- source/server/guarddog_impl.cc | 9 +- source/server/guarddog_impl.h | 4 +- source/server/http/admin.cc | 1 + source/server/http/admin.h | 7 + source/server/lds_api.cc | 8 +- source/server/lds_api.h | 2 +- source/server/listener_manager_impl.cc | 11 + source/server/listener_manager_impl.h | 8 +- source/server/options_impl.cc | 8 +- source/server/options_impl.h | 5 + source/server/overload_manager_impl.cc | 26 +- source/server/resource_monitor_config_impl.h | 10 +- source/server/server.cc | 16 +- source/server/server.h | 4 +- source/server/ssl_context_manager.cc | 2 + source/server/well_known_names.h | 21 + .../access_log_formatter_fuzz_test.cc | 8 +- .../access_log/access_log_formatter_test.cc | 213 +- .../common/access_log/access_log_impl_test.cc | 2 +- .../access_log_manager_impl_test.cc | 143 +- test/common/common/BUILD | 9 + test/common/common/cleanup_test.cc | 12 + test/common/common/lock_guard_test.cc | 2 +- test/common/common/matchers_test.cc | 9 + test/common/common/regex_test.cc | 61 + test/common/common/utility_speed_test.cc | 22 + test/common/common/utility_test.cc | 38 +- .../config/config_provider_impl_test.cc | 3 + .../config/delta_subscription_test_harness.h | 2 +- .../filesystem_subscription_impl_test.cc | 3 +- .../filesystem_subscription_test_harness.h | 1 - test/common/config/grpc_mux_impl_test.cc | 7 +- .../config/grpc_subscription_impl_test.cc | 4 +- .../config/grpc_subscription_test_harness.h | 2 +- .../config/http_subscription_impl_test.cc | 4 +- .../config/http_subscription_test_harness.h | 4 +- test/common/config/rds_json_test.cc | 2 - test/common/config/utility_test.cc | 2 - test/common/event/dispatcher_impl_test.cc | 36 +- test/common/grpc/async_client_impl_test.cc | 1 - test/common/grpc/context_impl_test.cc | 4 +- test/common/grpc/grpc_client_integration.h | 26 +- .../grpc_client_integration_test_harness.h | 2 +- test/common/http/BUILD | 18 +- test/common/http/async_client_impl_test.cc | 70 +- test/common/http/codec_client_test.cc | 15 +- ...case-codec_impl_fuzz_test-5687788200001536 | 11962 ++++++++++++++++ test/common/http/codec_impl_fuzz.proto | 6 +- test/common/http/codec_impl_fuzz_test.cc | 16 +- test/common/http/codes_test.cc | 9 +- test/common/http/common.h | 4 +- test/common/http/conn_manager_impl_common.h | 1 + .../http/conn_manager_impl_fuzz_test.cc | 41 +- test/common/http/conn_manager_impl_test.cc | 466 +- test/common/http/conn_manager_utility_test.cc | 159 +- test/common/http/date_provider_impl_test.cc | 4 +- .../http/header_map_impl_corpus/appendheader | 5377 +++++++ test/common/http/header_utility_test.cc | 95 +- test/common/http/http1/BUILD | 1 + test/common/http/http1/codec_impl_test.cc | 83 +- test/common/http/http1/conn_pool_test.cc | 5 +- test/common/http/http2/BUILD | 1 + test/common/http/http2/codec_impl_test.cc | 30 +- test/common/http/http2/conn_pool_test.cc | 3 +- test/common/init/manager_impl_test.cc | 1 - test/common/json/config_schemas_test.cc | 2 - ...dr_family_aware_socket_option_impl_test.cc | 1 + test/common/network/connection_impl_test.cc | 10 +- test/common/network/dns_impl_test.cc | 3 +- .../network/socket_option_factory_test.cc | 17 +- .../common/network/socket_option_impl_test.cc | 3 +- test/common/network/socket_option_test.h | 1 - test/common/protobuf/utility_test.cc | 120 +- test/common/router/BUILD | 6 + test/common/router/config_impl_test.cc | 512 +- test/common/router/header_formatter_test.cc | 189 +- test/common/router/header_parser_fuzz_test.cc | 5 +- test/common/router/rds_impl_test.cc | 83 +- test/common/router/retry_state_impl_test.cc | 28 +- test/common/router/route_fuzz_test.cc | 2 +- test/common/router/router_ratelimit_test.cc | 4 +- test/common/router/router_test.cc | 385 +- .../common/router/router_upstream_log_test.cc | 14 +- test/common/router/scoped_config_impl_test.cc | 197 +- test/common/router/scoped_rds_test.cc | 442 +- test/common/router/vhds_test.cc | 7 - test/common/runtime/runtime_impl_test.cc | 11 +- test/common/secret/sds_api_test.cc | 5 +- .../common/secret/secret_manager_impl_test.cc | 6 +- test/common/stats/BUILD | 8 +- test/common/stats/allocator_impl_test.cc | 12 +- test/common/stats/metric_impl_test.cc | 10 +- test/common/stats/stat_merger_test.cc | 6 +- test/common/stats/stat_test_utility.h | 8 + test/common/stats/stats_matcher_impl_test.cc | 3 - test/common/stats/symbol_table_impl_test.cc | 21 + .../stats/thread_local_store_speed_test.cc | 10 +- test/common/stats/thread_local_store_test.cc | 150 +- test/common/stream_info/test_util.h | 16 +- test/common/tcp/conn_pool_test.cc | 11 +- test/common/tcp_proxy/tcp_proxy_test.cc | 57 +- .../thread_local/thread_local_impl_test.cc | 34 + test/common/tracing/http_tracer_impl_test.cc | 151 +- test/common/upstream/BUILD | 7 + test/common/upstream/cds_api_impl_test.cc | 3 - .../upstream/cluster_factory_impl_test.cc | 4 - .../upstream/cluster_manager_impl_test.cc | 461 +- .../upstream/conn_pool_map_impl_test.cc | 1 - test/common/upstream/eds_test.cc | 11 +- test/common/upstream/hds_test.cc | 16 +- .../upstream/health_checker_impl_test.cc | 504 +- .../upstream/load_balancer_impl_test.cc | 122 +- .../upstream/load_stats_reporter_test.cc | 18 +- .../upstream/logical_dns_cluster_test.cc | 10 +- .../upstream/original_dst_cluster_test.cc | 26 +- .../upstream/outlier_detection_impl_test.cc | 384 +- test/common/upstream/ring_hash_lb_test.cc | 1 - test/common/upstream/subset_lb_test.cc | 2 - test/common/upstream/upstream_impl_test.cc | 60 +- test/common/upstream/utility.h | 18 +- test/config/integration/server.yaml | 52 +- test/config/utility.cc | 4 +- test/config/utility.h | 9 + test/config_test/config_test.cc | 1 + test/config_test/example_configs_test.cc | 2 +- test/dependencies/BUILD | 17 + test/dependencies/curl_test.cc | 32 + test/exe/BUILD | 5 +- test/exe/main_common_test.cc | 8 +- test/extensions/access_loggers/file/BUILD | 1 + test/extensions/access_loggers/grpc/BUILD | 17 + .../grpc/grpc_access_log_impl_test.cc | 24 +- .../access_loggers/grpc/http_config_test.cc | 1 - .../grpc/http_grpc_access_log_impl_test.cc | 98 +- .../tcp_grpc_access_log_integration_test.cc | 189 + test/extensions/clusters/redis/mocks.cc | 3 - .../redis/redis_cluster_integration_test.cc | 53 +- .../clusters/redis/redis_cluster_lb_test.cc | 1 + .../clusters/redis/redis_cluster_test.cc | 67 +- .../dns_cache_impl_test.cc | 24 +- test/extensions/common/wasm/BUILD | 19 + test/extensions/common/wasm/wasm_vm_test.cc | 103 + .../filters/common/expr/context_test.cc | 40 +- .../ext_authz/check_request_utils_test.cc | 121 +- .../ext_authz/ext_authz_http_impl_test.cc | 17 +- .../filters/common/lua/wrappers_test.cc | 7 +- .../original_src_socket_option_test.cc | 1 - .../common/ratelimit/ratelimit_impl_test.cc | 2 - .../filters/common/rbac/engine_impl_test.cc | 2 - .../filters/common/rbac/matchers_test.cc | 40 +- .../adaptive_concurrency_filter_test.cc | 72 +- .../concurrency_controller/BUILD | 28 + .../gradient_controller_test.cc | 497 + test/extensions/filters/http/buffer/BUILD | 1 + .../filters/http/buffer/buffer_filter_test.cc | 30 +- .../filters/http/common/jwks_fetcher_test.cc | 1 - .../http/cors/cors_filter_integration_test.cc | 56 +- .../filters/http/cors/cors_filter_test.cc | 45 +- .../filters/http/csrf/csrf_filter_test.cc | 5 - .../proxy_filter_integration_test.cc | 12 +- .../filters/http/dynamo/dynamo_filter_test.cc | 1 - .../filters/http/dynamo/dynamo_stats_test.cc | 4 - .../filters/http/ext_authz/config_test.cc | 2 + .../filters/http/ext_authz/ext_authz_test.cc | 64 +- .../filters/http/fault/config_test.cc | 1 - .../filters/http/fault/fault_filter_test.cc | 24 +- .../http1_bridge_filter_test.cc | 4 +- .../reverse_bridge_test.cc | 1 - .../grpc_json_transcoder_integration_test.cc | 1 - .../json_transcoder_filter_test.cc | 4 - .../http/grpc_web/grpc_web_filter_test.cc | 2 +- .../http/health_check/health_check_test.cc | 13 +- .../http/ip_tagging/ip_tagging_filter_test.cc | 3 +- .../filters/http/jwt_authn/matcher_test.cc | 22 + .../filters/http/lua/lua_filter_test.cc | 6 +- .../filters/http/lua/wrappers_test.cc | 1 - .../original_src_config_factory_test.cc | 3 +- .../http/original_src/original_src_test.cc | 1 - .../filters/http/ratelimit/config_test.cc | 1 - .../filters/http/ratelimit/ratelimit_test.cc | 1 - .../filters/http/squash/squash_filter_test.cc | 21 +- .../http_inspector/http_inspector_test.cc | 2 - .../original_src_config_factory_test.cc | 3 +- .../original_src/original_src_test.cc | 1 - .../proxy_protocol/proxy_protocol_test.cc | 28 +- .../tls_inspector/tls_inspector_benchmark.cc | 7 - .../tls_inspector/tls_inspector_test.cc | 39 +- .../client_ssl_auth/client_ssl_auth_test.cc | 23 +- .../network/common/redis/client_impl_test.cc | 112 +- .../filters/network/common/redis/mocks.h | 1 + .../network/dubbo_proxy/conn_manager_test.cc | 13 +- .../network/dubbo_proxy/decoder_test.cc | 3 - .../dubbo_hessian2_serializer_impl_test.cc | 2 - .../dubbo_proxy/dubbo_protocol_impl_test.cc | 2 - .../filters/network/dubbo_proxy/mocks.h | 2 - .../network/dubbo_proxy/route_matcher_test.cc | 24 +- .../network/dubbo_proxy/router_test.cc | 2 - .../network/ext_authz/ext_authz_test.cc | 3 +- .../http_connection_manager/config_test.cc | 157 +- .../network/kafka/request_codec_unit_test.cc | 2 - .../network/kafka/response_codec_unit_test.cc | 2 - .../filters/network/mongo_proxy/proxy_test.cc | 19 +- .../filters/network/ratelimit/config_test.cc | 1 - .../network/redis_proxy/config_test.cc | 57 +- .../redis_proxy/conn_pool_impl_test.cc | 121 +- .../filters/network/redis_proxy/mocks.cc | 2 - .../redis_proxy_integration_test.cc | 11 +- .../network/redis_proxy/router_impl_test.cc | 20 +- .../network/sni_cluster/sni_cluster_test.cc | 1 - .../thrift_proxy/auto_protocol_impl_test.cc | 1 - .../network/thrift_proxy/conn_manager_test.cc | 49 +- .../network/thrift_proxy/decoder_test.cc | 1 - .../filters/ratelimit/config_test.cc | 1 - .../filters/ratelimit/ratelimit_test.cc | 1 - .../header_transport_impl_test.cc | 1 - .../network/thrift_proxy/integration_test.cc | 4 +- .../filters/network/thrift_proxy/mocks.cc | 3 +- .../thrift_proxy/route_matcher_test.cc | 8 +- .../network/thrift_proxy/router_test.cc | 1 - .../thrift_proxy/thrift_object_impl_test.cc | 2 - .../filters/network/thrift_proxy/utility.h | 4 +- test/extensions/health_checkers/redis/BUILD | 2 + .../health_checkers/redis/config_test.cc | 14 +- .../health_checkers/redis/redis_test.cc | 76 +- test/extensions/quic_listeners/quiche/BUILD | 118 + .../active_quic_listener_config_test.cc | 48 + .../quiche/active_quic_listener_test.cc | 192 + .../quiche/envoy_quic_dispatcher_test.cc | 263 + .../quiche/envoy_quic_proof_source_test.cc | 2 +- .../quiche/envoy_quic_server_session_test.cc | 401 + .../quiche/envoy_quic_server_stream_test.cc | 253 + .../quiche/envoy_quic_utils_test.cc | 65 + .../quic_listeners/quiche/platform/BUILD | 13 + .../quiche/quic_io_handle_wrapper_test.cc | 88 + .../fixed_heap/config_test.cc | 3 +- .../injected_resource/config_test.cc | 3 +- .../injected_resource_monitor_test.cc | 3 +- .../previous_priorities/config_test.cc | 3 +- .../stats_sinks/dog_statsd/config_test.cc | 3 - .../stats_sinks/hystrix/config_test.cc | 3 - .../stats_sinks/hystrix/hystrix_test.cc | 1 - .../grpc_metrics_service_impl_test.cc | 1 - .../metrics_service_integration_test.cc | 12 +- .../stats_sinks/statsd/config_test.cc | 3 - .../extensions/tracers/datadog/config_test.cc | 1 - .../datadog/datadog_tracer_impl_test.cc | 5 +- .../tracers/dynamic_ot/config_test.cc | 1 - .../tracers/lightstep/config_test.cc | 1 - .../lightstep/lightstep_tracer_impl_test.cc | 6 +- .../tracers/opencensus/config_test.cc | 2 + .../tracers/opencensus/tracer_test.cc | 1 - test/extensions/tracers/zipkin/BUILD | 1 + test/extensions/tracers/zipkin/config_test.cc | 3 +- .../tracers/zipkin/span_buffer_test.cc | 407 +- test/extensions/tracers/zipkin/tracer_test.cc | 2 +- .../tracers/zipkin/zipkin_tracer_impl_test.cc | 157 +- .../transport_sockets/alts/config_test.cc | 7 +- .../alts/tsi_frame_protector_test.cc | 5 - .../transport_sockets/alts/tsi_socket_test.cc | 1 - test/extensions/transport_sockets/tls/BUILD | 25 + .../tls/context_impl_test.cc | 121 +- .../tls/integration/ssl_integration_test.cc | 2 - .../tls/integration/ssl_integration_test.h | 2 - .../transport_sockets/tls/ssl_socket_test.cc | 587 +- .../tls/test_private_key_method_provider.cc | 377 + .../tls/test_private_key_method_provider.h | 96 + .../transport_sockets/tls/utility_test.cc | 9 +- test/fuzz/utility.h | 12 +- test/integration/BUILD | 18 + test/integration/ads_integration.cc | 37 +- test/integration/ads_integration.h | 4 + test/integration/ads_integration_test.cc | 43 +- test/integration/cds_integration_test.cc | 3 - test/integration/fake_upstream.cc | 42 +- test/integration/fake_upstream.h | 18 +- test/integration/filters/pause_filter.cc | 7 +- .../filters/process_context_filter.cc | 2 +- .../filters/random_pause_filter.cc | 2 +- test/integration/http2_integration_test.cc | 2 +- .../http2_upstream_integration_test.cc | 2 - test/integration/http_integration.cc | 42 +- test/integration/http_integration.h | 14 + .../http_subset_lb_integration_test.cc | 202 + test/integration/integration.cc | 7 +- test/integration/integration.h | 5 + test/integration/integration_admin_test.cc | 15 +- test/integration/integration_test.cc | 1 - test/integration/protocol_integration_test.cc | 4 - .../scoped_rds_integration_test.cc | 319 +- .../sds_dynamic_integration_test.cc | 3 - .../sds_static_integration_test.cc | 3 - test/integration/server.cc | 5 +- test/integration/stats_integration_test.cc | 128 +- test/integration/vhds_integration_test.cc | 3 - .../integration/websocket_integration_test.cc | 2 - test/mocks/access_log/mocks.cc | 1 - test/mocks/api/mocks.cc | 9 +- test/mocks/api/mocks.h | 2 + test/mocks/common.h | 3 +- test/mocks/event/mocks.cc | 7 +- test/mocks/event/mocks.h | 22 +- test/mocks/filesystem/mocks.cc | 4 +- test/mocks/filesystem/mocks.h | 3 +- test/mocks/http/mocks.cc | 5 - test/mocks/local_info/mocks.cc | 1 - test/mocks/network/connection.h | 6 +- test/mocks/network/mocks.h | 5 +- test/mocks/router/BUILD | 1 + test/mocks/router/mocks.cc | 16 +- test/mocks/router/mocks.h | 60 +- test/mocks/server/mocks.cc | 4 +- test/mocks/server/mocks.h | 9 +- test/mocks/ssl/mocks.cc | 6 + test/mocks/ssl/mocks.h | 36 +- test/mocks/stats/BUILD | 1 + test/mocks/stats/mocks.cc | 6 +- test/mocks/stats/mocks.h | 35 +- test/mocks/stream_info/mocks.cc | 9 +- test/mocks/stream_info/mocks.h | 9 +- test/mocks/thread_local/mocks.h | 8 + test/mocks/tracing/mocks.cc | 1 + test/mocks/tracing/mocks.h | 1 + test/mocks/upstream/host.h | 4 +- test/mocks/upstream/mocks.cc | 1 - test/mocks/upstream/mocks.h | 4 +- test/run_envoy_bazel_coverage.sh | 2 +- test/server/BUILD | 37 +- .../config_validation/cluster_manager_test.cc | 1 - test/server/configuration_impl_test.cc | 2 - test/server/connection_handler_test.cc | 87 +- test/server/drain_manager_impl_test.cc | 7 +- test/server/filter_chain_benchmark_test.cc | 1 + test/server/filter_chain_manager_impl_test.cc | 4 - test/server/guarddog_impl_test.cc | 2 +- test/server/hot_restart_impl_test.cc | 2 - test/server/hot_restarting_parent_test.cc | 1 - test/server/http/BUILD | 1 + test/server/http/admin_test.cc | 29 +- test/server/http/config_tracker_impl_test.cc | 2 - .../invalid_legacy_runtime_bootstrap.yaml | 4 + test/server/invalid_runtime_bootstrap.yaml | 10 +- test/server/lds_api_test.cc | 1 - .../listener_manager_impl_quic_only_test.cc | 46 + test/server/listener_manager_impl_test.cc | 190 +- test/server/listener_manager_impl_test.h | 119 + test/server/options_impl_test.cc | 12 +- ...testcase-server_fuzz_test-5734693923717120 | 18 + test/server/server_fuzz_test.cc | 2 + test/server/server_test.cc | 41 +- test/stress/stress_test_upstream.cc | 2 + test/stress/stress_test_upstream.h | 2 + test/test_common/BUILD | 16 + test/test_common/simulated_time_system.cc | 24 +- .../test_common/simulated_time_system_test.cc | 40 +- test/test_common/test_runtime.h | 52 + test/test_common/utility.cc | 7 +- test/test_common/utility.h | 34 +- test/test_common/utility_test.cc | 2 - test/test_runner.h | 2 + test/tools/router_check/router.cc | 90 +- test/tools/router_check/router.h | 33 +- test/tools/router_check/router_check.cc | 9 +- .../ComprehensiveRoutes.golden.proto.json | 63 + .../test/config/ComprehensiveRoutes.yaml | 9 +- .../test/config/HeaderMatchedRouting.yaml | 4 +- .../router_check/test/config/TestRoutes.yaml | 63 +- .../test/config/Weighted.golden.json | 7 +- .../test/config/Weighted.golden.proto.json | 4 +- .../test/config/Weighted.golden.proto.pb_text | 7 +- .../test/config/Weighted.golden.proto.yaml | 3 +- .../router_check/test/config/Weighted.yaml | 9 + test/tools/router_check/test/route_tests.sh | 27 +- test/tools/router_check/validation.proto | 13 +- test/tools/schema_validator/validator.cc | 4 +- tools/api/clone.sh | 65 + tools/api_proto_plugin/BUILD | 17 + tools/api_proto_plugin/__init__.py | 0 tools/api_proto_plugin/annotations.py | 79 + tools/api_proto_plugin/plugin.py | 57 + tools/api_proto_plugin/traverse.py | 72 + tools/api_proto_plugin/type_context.py | 195 + tools/api_proto_plugin/visitor.py | 45 + tools/check_format.py | 48 +- tools/check_format_test_helper.py | 3 + .../deprecate_features/deprecate_features.sh | 9 +- tools/deprecate_version/deprecate_version.sh | 9 +- tools/format_python_tools.py | 2 +- tools/format_python_tools.sh | 14 +- tools/github/requirements.txt | 1 + tools/github/sync_assignable.py | 53 + tools/github/sync_assignable.sh | 7 + tools/protodoc/BUILD | 3 +- tools/protodoc/protodoc.py | 564 +- tools/shell_utils.sh | 16 +- tools/spelling_dictionary.txt | 16 + tools/stack_decode.py | 20 +- .../check_format/api/go_package.proto | 5 + tools/testdata/check_format/regex.cc | 9 + 1296 files changed, 54634 insertions(+), 7356 deletions(-) create mode 100644 api/envoy/admin/v3alpha/BUILD create mode 100644 api/envoy/admin/v3alpha/certs.proto create mode 100644 api/envoy/admin/v3alpha/clusters.proto create mode 100644 api/envoy/admin/v3alpha/config_dump.proto create mode 100644 api/envoy/admin/v3alpha/listeners.proto create mode 100644 api/envoy/admin/v3alpha/memory.proto create mode 100644 api/envoy/admin/v3alpha/metrics.proto create mode 100644 api/envoy/admin/v3alpha/mutex_stats.proto create mode 100644 api/envoy/admin/v3alpha/server_info.proto create mode 100644 api/envoy/admin/v3alpha/tap.proto create mode 100644 api/envoy/api/v2/listener/quic_config.proto create mode 100644 api/envoy/api/v2/listener/udp_listener_config.proto create mode 100644 api/envoy/api/v3alpha/BUILD create mode 100644 api/envoy/api/v3alpha/README.md create mode 100644 api/envoy/api/v3alpha/auth/BUILD create mode 100644 api/envoy/api/v3alpha/auth/cert.proto create mode 100644 api/envoy/api/v3alpha/cds.proto create mode 100644 api/envoy/api/v3alpha/cluster/BUILD create mode 100644 api/envoy/api/v3alpha/cluster/circuit_breaker.proto create mode 100644 api/envoy/api/v3alpha/cluster/filter.proto create mode 100644 api/envoy/api/v3alpha/cluster/outlier_detection.proto create mode 100644 api/envoy/api/v3alpha/core/BUILD create mode 100644 api/envoy/api/v3alpha/core/address.proto create mode 100644 api/envoy/api/v3alpha/core/base.proto create mode 100644 api/envoy/api/v3alpha/core/config_source.proto create mode 100644 api/envoy/api/v3alpha/core/grpc_service.proto create mode 100644 api/envoy/api/v3alpha/core/health_check.proto create mode 100644 api/envoy/api/v3alpha/core/http_uri.proto create mode 100644 api/envoy/api/v3alpha/core/protocol.proto create mode 100644 api/envoy/api/v3alpha/discovery.proto create mode 100644 api/envoy/api/v3alpha/eds.proto create mode 100644 api/envoy/api/v3alpha/endpoint/BUILD create mode 100644 api/envoy/api/v3alpha/endpoint/endpoint.proto create mode 100644 api/envoy/api/v3alpha/endpoint/load_report.proto create mode 100644 api/envoy/api/v3alpha/lds.proto create mode 100644 api/envoy/api/v3alpha/listener/BUILD create mode 100644 api/envoy/api/v3alpha/listener/listener.proto create mode 100644 api/envoy/api/v3alpha/listener/udp_listener_config.proto create mode 100644 api/envoy/api/v3alpha/ratelimit/BUILD create mode 100644 api/envoy/api/v3alpha/ratelimit/ratelimit.proto create mode 100644 api/envoy/api/v3alpha/rds.proto create mode 100644 api/envoy/api/v3alpha/route/BUILD create mode 100644 api/envoy/api/v3alpha/route/route.proto create mode 100644 api/envoy/api/v3alpha/srds.proto create mode 100644 api/envoy/config/accesslog/v3alpha/BUILD create mode 100644 api/envoy/config/accesslog/v3alpha/als.proto create mode 100644 api/envoy/config/accesslog/v3alpha/file.proto create mode 100644 api/envoy/config/bootstrap/v3alpha/BUILD create mode 100644 api/envoy/config/bootstrap/v3alpha/bootstrap.proto create mode 100644 api/envoy/config/cluster/dynamic_forward_proxy/v3alpha/BUILD create mode 100644 api/envoy/config/cluster/dynamic_forward_proxy/v3alpha/cluster.proto create mode 100644 api/envoy/config/common/dynamic_forward_proxy/v3alpha/BUILD create mode 100644 api/envoy/config/common/dynamic_forward_proxy/v3alpha/dns_cache.proto create mode 100644 api/envoy/config/common/tap/v3alpha/BUILD create mode 100644 api/envoy/config/common/tap/v3alpha/common.proto create mode 100644 api/envoy/config/filter/accesslog/v3alpha/BUILD create mode 100644 api/envoy/config/filter/accesslog/v3alpha/accesslog.proto create mode 100644 api/envoy/config/filter/dubbo/router/v3alpha/BUILD create mode 100644 api/envoy/config/filter/dubbo/router/v3alpha/router.proto create mode 100644 api/envoy/config/filter/fault/v3alpha/BUILD create mode 100644 api/envoy/config/filter/fault/v3alpha/fault.proto create mode 100644 api/envoy/config/filter/http/buffer/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/buffer/v3alpha/buffer.proto create mode 100644 api/envoy/config/filter/http/csrf/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/csrf/v3alpha/csrf.proto create mode 100644 api/envoy/config/filter/http/dynamic_forward_proxy/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/dynamic_forward_proxy/v3alpha/dynamic_forward_proxy.proto create mode 100644 api/envoy/config/filter/http/ext_authz/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/ext_authz/v3alpha/ext_authz.proto create mode 100644 api/envoy/config/filter/http/fault/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/fault/v3alpha/fault.proto create mode 100644 api/envoy/config/filter/http/grpc_http1_reverse_bridge/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/grpc_http1_reverse_bridge/v3alpha/config.proto create mode 100644 api/envoy/config/filter/http/gzip/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/gzip/v3alpha/gzip.proto create mode 100644 api/envoy/config/filter/http/header_to_metadata/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/header_to_metadata/v3alpha/header_to_metadata.proto create mode 100644 api/envoy/config/filter/http/health_check/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/health_check/v3alpha/health_check.proto create mode 100644 api/envoy/config/filter/http/ip_tagging/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/ip_tagging/v3alpha/ip_tagging.proto create mode 100644 api/envoy/config/filter/http/jwt_authn/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/jwt_authn/v3alpha/README.md create mode 100644 api/envoy/config/filter/http/jwt_authn/v3alpha/config.proto create mode 100644 api/envoy/config/filter/http/lua/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/lua/v3alpha/lua.proto create mode 100644 api/envoy/config/filter/http/original_src/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/original_src/v3alpha/original_src.proto create mode 100644 api/envoy/config/filter/http/rate_limit/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/rate_limit/v3alpha/rate_limit.proto create mode 100644 api/envoy/config/filter/http/rbac/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/rbac/v3alpha/rbac.proto create mode 100644 api/envoy/config/filter/http/router/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/router/v3alpha/router.proto create mode 100644 api/envoy/config/filter/http/squash/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/squash/v3alpha/squash.proto create mode 100644 api/envoy/config/filter/http/tap/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/tap/v3alpha/tap.proto create mode 100644 api/envoy/config/filter/http/transcoder/v3alpha/BUILD create mode 100644 api/envoy/config/filter/http/transcoder/v3alpha/transcoder.proto create mode 100644 api/envoy/config/filter/listener/original_src/v3alpha/BUILD create mode 100644 api/envoy/config/filter/listener/original_src/v3alpha/original_src.proto create mode 100644 api/envoy/config/filter/network/client_ssl_auth/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/client_ssl_auth/v3alpha/client_ssl_auth.proto create mode 100644 api/envoy/config/filter/network/dubbo_proxy/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/dubbo_proxy/v3alpha/README.md create mode 100644 api/envoy/config/filter/network/dubbo_proxy/v3alpha/dubbo_proxy.proto create mode 100644 api/envoy/config/filter/network/dubbo_proxy/v3alpha/route.proto create mode 100644 api/envoy/config/filter/network/ext_authz/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/ext_authz/v3alpha/ext_authz.proto create mode 100644 api/envoy/config/filter/network/http_connection_manager/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/http_connection_manager/v3alpha/http_connection_manager.proto create mode 100644 api/envoy/config/filter/network/mongo_proxy/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/mongo_proxy/v3alpha/mongo_proxy.proto create mode 100644 api/envoy/config/filter/network/rate_limit/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/rate_limit/v3alpha/rate_limit.proto create mode 100644 api/envoy/config/filter/network/rbac/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/rbac/v3alpha/rbac.proto create mode 100644 api/envoy/config/filter/network/redis_proxy/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/redis_proxy/v3alpha/redis_proxy.proto create mode 100644 api/envoy/config/filter/network/tcp_proxy/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/tcp_proxy/v3alpha/tcp_proxy.proto create mode 100644 api/envoy/config/filter/network/thrift_proxy/v3alpha/BUILD create mode 100644 api/envoy/config/filter/network/thrift_proxy/v3alpha/README.md create mode 100644 api/envoy/config/filter/network/thrift_proxy/v3alpha/route.proto create mode 100644 api/envoy/config/filter/network/thrift_proxy/v3alpha/thrift_proxy.proto create mode 100644 api/envoy/config/filter/thrift/rate_limit/v3alpha/BUILD create mode 100644 api/envoy/config/filter/thrift/rate_limit/v3alpha/rate_limit.proto create mode 100644 api/envoy/config/filter/thrift/router/v3alpha/BUILD create mode 100644 api/envoy/config/filter/thrift/router/v3alpha/router.proto create mode 100644 api/envoy/config/grpc_credential/v3alpha/BUILD create mode 100644 api/envoy/config/grpc_credential/v3alpha/aws_iam.proto create mode 100644 api/envoy/config/grpc_credential/v3alpha/file_based_metadata.proto create mode 100644 api/envoy/config/health_checker/redis/v3alpha/BUILD create mode 100644 api/envoy/config/health_checker/redis/v3alpha/redis.proto create mode 100644 api/envoy/config/metrics/v3alpha/BUILD create mode 100644 api/envoy/config/metrics/v3alpha/metrics_service.proto create mode 100644 api/envoy/config/metrics/v3alpha/stats.proto create mode 100644 api/envoy/config/overload/v3alpha/BUILD create mode 100644 api/envoy/config/overload/v3alpha/overload.proto create mode 100644 api/envoy/config/ratelimit/v3alpha/BUILD create mode 100644 api/envoy/config/ratelimit/v3alpha/rls.proto create mode 100644 api/envoy/config/rbac/v3alpha/BUILD create mode 100644 api/envoy/config/rbac/v3alpha/rbac.proto create mode 100644 api/envoy/config/resource_monitor/fixed_heap/v3alpha/BUILD create mode 100644 api/envoy/config/resource_monitor/fixed_heap/v3alpha/fixed_heap.proto create mode 100644 api/envoy/config/resource_monitor/injected_resource/v3alpha/BUILD create mode 100644 api/envoy/config/resource_monitor/injected_resource/v3alpha/injected_resource.proto create mode 100644 api/envoy/config/trace/v3alpha/BUILD create mode 100644 api/envoy/config/trace/v3alpha/trace.proto create mode 100644 api/envoy/config/transport_socket/alts/v3alpha/BUILD create mode 100644 api/envoy/config/transport_socket/alts/v3alpha/alts.proto create mode 100644 api/envoy/config/transport_socket/tap/v3alpha/BUILD create mode 100644 api/envoy/config/transport_socket/tap/v3alpha/tap.proto create mode 100644 api/envoy/data/accesslog/v3alpha/BUILD create mode 100644 api/envoy/data/accesslog/v3alpha/accesslog.proto create mode 100644 api/envoy/data/cluster/v3alpha/BUILD create mode 100644 api/envoy/data/cluster/v3alpha/outlier_detection_event.proto create mode 100644 api/envoy/data/core/v3alpha/BUILD create mode 100644 api/envoy/data/core/v3alpha/health_check_event.proto create mode 100644 api/envoy/data/tap/v3alpha/BUILD create mode 100644 api/envoy/data/tap/v3alpha/common.proto create mode 100644 api/envoy/data/tap/v3alpha/http.proto create mode 100644 api/envoy/data/tap/v3alpha/transport.proto create mode 100644 api/envoy/data/tap/v3alpha/wrapper.proto create mode 100644 api/envoy/service/accesslog/v3alpha/BUILD create mode 100644 api/envoy/service/accesslog/v3alpha/als.proto create mode 100644 api/envoy/service/auth/v3alpha/BUILD create mode 100644 api/envoy/service/auth/v3alpha/attribute_context.proto create mode 100644 api/envoy/service/auth/v3alpha/external_auth.proto create mode 100644 api/envoy/service/discovery/v3alpha/BUILD create mode 100644 api/envoy/service/discovery/v3alpha/ads.proto create mode 100644 api/envoy/service/discovery/v3alpha/hds.proto create mode 100644 api/envoy/service/discovery/v3alpha/rtds.proto create mode 100644 api/envoy/service/discovery/v3alpha/sds.proto create mode 100644 api/envoy/service/load_stats/v3alpha/BUILD create mode 100644 api/envoy/service/load_stats/v3alpha/lrs.proto create mode 100644 api/envoy/service/metrics/v3alpha/BUILD create mode 100644 api/envoy/service/metrics/v3alpha/metrics_service.proto create mode 100644 api/envoy/service/ratelimit/v3alpha/BUILD create mode 100644 api/envoy/service/ratelimit/v3alpha/rls.proto create mode 100644 api/envoy/service/tap/v3alpha/BUILD create mode 100644 api/envoy/service/tap/v3alpha/common.proto create mode 100644 api/envoy/service/tap/v3alpha/tap.proto create mode 100644 api/envoy/service/tap/v3alpha/tapds.proto create mode 100644 api/envoy/service/trace/v3alpha/BUILD create mode 100644 api/envoy/service/trace/v3alpha/trace_service.proto create mode 100644 api/envoy/type/matcher/regex.proto create mode 100755 api/migration/v3alpha.sh delete mode 100644 api/udpa/data/orca/v1/BUILD delete mode 100644 api/udpa/data/orca/v1/orca_load_report.proto delete mode 100644 api/udpa/service/orca/v1/BUILD delete mode 100644 api/udpa/service/orca/v1/orca.proto mode change 100755 => 100644 bazel/BUILD create mode 100644 bazel/grpc-rename-gettid.patch create mode 100644 bazel/io_opentracing_cpp.patch rename bazel/toolchains/configs/clang/{bazel_0.28.1 => bazel_0.29.1}/cc/BUILD (95%) rename bazel/toolchains/configs/clang/{bazel_0.28.1 => bazel_0.29.1}/cc/armeabi_cc_toolchain_config.bzl (100%) create mode 100755 bazel/toolchains/configs/clang/bazel_0.29.1/cc/builtin_include_directory_paths rename bazel/toolchains/configs/clang/{bazel_0.28.1 => bazel_0.29.1}/cc/cc_toolchain_config.bzl (88%) rename bazel/toolchains/configs/clang/{bazel_0.28.1 => bazel_0.29.1}/cc/cc_wrapper.sh (100%) rename bazel/toolchains/configs/clang/{bazel_0.28.1 => bazel_0.29.1}/config/BUILD (89%) rename bazel/toolchains/configs/clang_libcxx/{bazel_0.28.1 => bazel_0.29.1}/cc/BUILD (95%) rename bazel/toolchains/configs/clang_libcxx/{bazel_0.28.1 => bazel_0.29.1}/cc/armeabi_cc_toolchain_config.bzl (100%) create mode 100755 bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/builtin_include_directory_paths rename bazel/toolchains/configs/clang_libcxx/{bazel_0.28.1 => bazel_0.29.1}/cc/cc_toolchain_config.bzl (88%) rename bazel/toolchains/configs/clang_libcxx/{bazel_0.28.1 => bazel_0.29.1}/cc/cc_wrapper.sh (100%) rename bazel/toolchains/configs/clang_libcxx/{bazel_0.28.1 => bazel_0.29.1}/config/BUILD (91%) rename bazel/toolchains/configs/gcc/{bazel_0.28.1 => bazel_0.29.1}/cc/BUILD (96%) rename bazel/toolchains/configs/gcc/{bazel_0.28.1 => bazel_0.29.1}/cc/armeabi_cc_toolchain_config.bzl (100%) create mode 100755 bazel/toolchains/configs/gcc/bazel_0.29.1/cc/builtin_include_directory_paths rename bazel/toolchains/configs/gcc/{bazel_0.28.1 => bazel_0.29.1}/cc/cc_toolchain_config.bzl (88%) rename bazel/toolchains/configs/gcc/{bazel_0.28.1 => bazel_0.29.1}/cc/cc_wrapper.sh (100%) rename bazel/toolchains/configs/gcc/{bazel_0.28.1 => bazel_0.29.1}/config/BUILD (89%) create mode 100644 docs/root/configuration/advanced/advanced.rst rename docs/root/configuration/{ => advanced}/well_known_dynamic_metadata.rst (100%) create mode 100644 docs/root/configuration/http/http.rst rename docs/root/configuration/{ => http}/http_conn_man/header_sanitizing.rst (100%) rename docs/root/configuration/{ => http}/http_conn_man/headers.rst (98%) rename docs/root/configuration/{ => http}/http_conn_man/http_conn_man.rst (100%) rename docs/root/configuration/{ => http}/http_conn_man/overview.rst (100%) rename docs/root/configuration/{ => http}/http_conn_man/rds.rst (100%) rename docs/root/configuration/{ => http}/http_conn_man/route_matching.rst (100%) rename docs/root/configuration/{ => http}/http_conn_man/runtime.rst (100%) rename docs/root/configuration/{ => http}/http_conn_man/stats.rst (100%) rename docs/root/configuration/{ => http}/http_conn_man/traffic_splitting.rst (100%) rename docs/root/configuration/{ => http}/http_filters/buffer_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/cors_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/csrf_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/dynamic_forward_proxy_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/dynamodb_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/ext_authz_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/fault_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/grpc_http1_bridge_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/grpc_http1_reverse_bridge_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/grpc_json_transcoder_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/grpc_web_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/gzip_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/header_to_metadata_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/health_check_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/http_filters.rst (100%) rename docs/root/configuration/{ => http}/http_filters/ip_tagging_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/jwt_authn_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/lua_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/original_src_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/rate_limit_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/rbac_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/router_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/squash_filter.rst (100%) rename docs/root/configuration/{ => http}/http_filters/tap_filter.rst (100%) rename docs/root/configuration/{ => listeners}/listener_filters/http_inspector.rst (100%) rename docs/root/configuration/{ => listeners}/listener_filters/listener_filters.rst (100%) rename docs/root/configuration/{ => listeners}/listener_filters/original_dst_filter.rst (100%) rename docs/root/configuration/{ => listeners}/listener_filters/original_src_filter.rst (100%) rename docs/root/configuration/{ => listeners}/listener_filters/proxy_protocol.rst (100%) rename docs/root/configuration/{ => listeners}/listener_filters/tls_inspector.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/client_ssl_auth_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/dubbo_proxy_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/echo_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/ext_authz_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/mongo_proxy_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/mysql_proxy_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/network_filters.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/rate_limit_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/rbac_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/redis_proxy_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/sni_cluster_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/tcp_proxy_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/thrift_proxy_filter.rst (100%) rename docs/root/configuration/{ => listeners}/network_filters/zookeeper_proxy_filter.rst (100%) rename docs/root/configuration/{ => observability}/access_log.rst (100%) create mode 100644 docs/root/configuration/observability/observability.rst rename docs/root/configuration/{ => observability}/statistics.rst (91%) create mode 100644 docs/root/configuration/operations/operations.rst rename docs/root/configuration/{ => operations}/overload_manager/overload_manager.rst (100%) rename docs/root/configuration/{ => operations}/runtime.rst (98%) rename docs/root/configuration/{ => operations}/tools/router_check.rst (100%) create mode 100644 docs/root/configuration/other_features/other_features.rst rename docs/root/configuration/{ => other_features}/rate_limit.rst (100%) rename docs/root/configuration/{ => other_protocols}/dubbo_filters/dubbo_filters.rst (100%) rename docs/root/configuration/{ => other_protocols}/dubbo_filters/router_filter.rst (100%) create mode 100644 docs/root/configuration/other_protocols/other_protocols.rst rename docs/root/configuration/{ => other_protocols}/thrift_filters/rate_limit_filter.rst (100%) rename docs/root/configuration/{ => other_protocols}/thrift_filters/router_filter.rst (100%) rename docs/root/configuration/{ => other_protocols}/thrift_filters/thrift_filters.rst (100%) rename docs/root/configuration/{ => security}/secret.rst (100%) create mode 100644 docs/root/configuration/security/security.rst rename docs/root/configuration/{ => upstream}/cluster_manager/cds.rst (100%) rename docs/root/configuration/{ => upstream}/cluster_manager/cluster_circuit_breakers.rst (100%) rename docs/root/configuration/{ => upstream}/cluster_manager/cluster_hc.rst (100%) rename docs/root/configuration/{ => upstream}/cluster_manager/cluster_manager.rst (100%) rename docs/root/configuration/{ => upstream}/cluster_manager/cluster_runtime.rst (84%) rename docs/root/configuration/{ => upstream}/cluster_manager/cluster_stats.rst (94%) rename docs/root/configuration/{ => upstream}/cluster_manager/overview.rst (100%) rename docs/root/configuration/{ => upstream}/health_checkers/health_checkers.rst (100%) rename docs/root/configuration/{ => upstream}/health_checkers/redis.rst (100%) create mode 100644 docs/root/configuration/upstream/upstream.rst delete mode 100644 docs/root/configuration/xds_subscription_stats.rst create mode 100644 include/envoy/common/matchers.h create mode 100644 include/envoy/common/regex.h create mode 100644 include/envoy/server/active_udp_listener_config.h create mode 100644 include/envoy/ssl/private_key/BUILD create mode 100644 include/envoy/ssl/private_key/private_key.h create mode 100644 include/envoy/ssl/private_key/private_key_callbacks.h create mode 100644 include/envoy/ssl/private_key/private_key_config.h create mode 100644 source/common/common/regex.cc create mode 100644 source/common/common/regex.h delete mode 100644 source/common/router/scoped_config_manager.cc delete mode 100644 source/common/router/scoped_config_manager.h create mode 100644 source/common/stats/symbol_table_creator.cc create mode 100644 source/common/stats/symbol_table_creator.h create mode 100644 source/exe/platform_impl.h create mode 100644 source/exe/posix/platform_impl.cc delete mode 100644 source/exe/posix/platform_impl.h create mode 100644 source/exe/win32/platform_impl.cc delete mode 100644 source/exe/win32/platform_impl.h create mode 100644 source/extensions/access_loggers/grpc/config_utils.cc create mode 100644 source/extensions/access_loggers/grpc/config_utils.h create mode 100644 source/extensions/access_loggers/grpc/tcp_config.cc create mode 100644 source/extensions/access_loggers/grpc/tcp_config.h create mode 100644 source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc create mode 100644 source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.h create mode 100644 source/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.cc create mode 100644 source/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.h create mode 100644 source/extensions/filters/network/common/redis/redis_command_stats.cc create mode 100644 source/extensions/filters/network/common/redis/redis_command_stats.h create mode 100644 source/extensions/filters/network/mongo_proxy/mongo_stats.cc create mode 100644 source/extensions/filters/network/mongo_proxy/mongo_stats.h create mode 100644 source/extensions/quic_listeners/quiche/active_quic_listener.cc create mode 100644 source/extensions/quic_listeners/quiche/active_quic_listener.h create mode 100644 source/extensions/quic_listeners/quiche/active_quic_listener_config.cc create mode 100644 source/extensions/quic_listeners/quiche/active_quic_listener_config.h create mode 100644 source/extensions/quic_listeners/quiche/codec_impl.cc create mode 100644 source/extensions/quic_listeners/quiche/codec_impl.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_connection.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_connection.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_connection_helper.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_server_session.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_stream.h create mode 100644 source/extensions/quic_listeners/quiche/envoy_quic_utils.cc create mode 100644 source/extensions/quic_listeners/quiche/platform/spdy_map_util_impl.h create mode 100644 source/extensions/quic_listeners/quiche/quic_io_handle_wrapper.h create mode 100644 source/extensions/transport_sockets/tls/private_key/BUILD create mode 100644 source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.cc create mode 100644 source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.h create mode 100644 source/server/active_raw_udp_listener_config.cc create mode 100644 source/server/active_raw_udp_listener_config.h create mode 100644 source/server/well_known_names.h create mode 100644 test/common/common/regex_test.cc create mode 100644 test/common/http/codec_impl_corpus/clusterfuzz-testcase-codec_impl_fuzz_test-5687788200001536 create mode 100644 test/common/http/header_map_impl_corpus/appendheader create mode 100644 test/dependencies/BUILD create mode 100644 test/dependencies/curl_test.cc create mode 100644 test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc create mode 100644 test/extensions/common/wasm/BUILD create mode 100644 test/extensions/common/wasm/wasm_vm_test.cc create mode 100644 test/extensions/filters/http/adaptive_concurrency/concurrency_controller/BUILD create mode 100644 test/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller_test.cc create mode 100644 test/extensions/quic_listeners/quiche/active_quic_listener_config_test.cc create mode 100644 test/extensions/quic_listeners/quiche/active_quic_listener_test.cc create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc create mode 100644 test/extensions/quic_listeners/quiche/envoy_quic_utils_test.cc create mode 100644 test/extensions/quic_listeners/quiche/quic_io_handle_wrapper_test.cc create mode 100644 test/extensions/transport_sockets/tls/test_private_key_method_provider.cc create mode 100644 test/extensions/transport_sockets/tls/test_private_key_method_provider.h create mode 100644 test/integration/http_subset_lb_integration_test.cc create mode 100644 test/server/invalid_legacy_runtime_bootstrap.yaml create mode 100644 test/server/listener_manager_impl_quic_only_test.cc create mode 100644 test/server/listener_manager_impl_test.h create mode 100644 test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5734693923717120 create mode 100644 test/test_common/test_runtime.h create mode 100644 test/tools/router_check/test/config/ComprehensiveRoutes.golden.proto.json create mode 100755 tools/api/clone.sh create mode 100644 tools/api_proto_plugin/BUILD create mode 100644 tools/api_proto_plugin/__init__.py create mode 100644 tools/api_proto_plugin/annotations.py create mode 100644 tools/api_proto_plugin/plugin.py create mode 100644 tools/api_proto_plugin/traverse.py create mode 100644 tools/api_proto_plugin/type_context.py create mode 100644 tools/api_proto_plugin/visitor.py create mode 100644 tools/github/requirements.txt create mode 100644 tools/github/sync_assignable.py create mode 100755 tools/github/sync_assignable.sh create mode 100644 tools/testdata/check_format/api/go_package.proto create mode 100644 tools/testdata/check_format/regex.cc diff --git a/.azure-pipelines/linux.yml b/.azure-pipelines/linux.yml index 842e1c992e..aa834255f3 100644 --- a/.azure-pipelines/linux.yml +++ b/.azure-pipelines/linux.yml @@ -10,6 +10,8 @@ jobs: CI_TARGET: 'bazel.gcc' compile_time_options: CI_TARGET: 'bazel.compile_time_options' + fuzz: + CI_TARGET: 'bazel.fuzz' dependsOn: [] # this removes the implicit dependency on previous stage and causes this to run in parallel. timeoutInMinutes: 360 pool: @@ -46,6 +48,13 @@ jobs: displayName: "Check disk space at end" condition: always() + - task: PublishTestResults@2 + inputs: + testResultsFiles: '**/bazel-out/**/testlogs/**/test.xml' + testRunTitle: '$(CI_TARGET)' + searchFolder: $(Build.StagingDirectory)/tmp + condition: always() + - task: PublishBuildArtifacts@1 inputs: pathtoPublish: "$(Build.StagingDirectory)/envoy" diff --git a/.bazelrc b/.bazelrc index 20dafa1ef1..d52af26f19 100644 --- a/.bazelrc +++ b/.bazelrc @@ -16,29 +16,36 @@ build --experimental_local_memory_estimate build --experimental_strict_action_env=true build --host_force_python=PY2 build --action_env=BAZEL_LINKLIBS=-l%:libstdc++.a -build --action_env=BAZEL_LINKOPTS=-lm:-static-libgcc +build --action_env=BAZEL_LINKOPTS=-lm build --host_javabase=@bazel_tools//tools/jdk:remote_jdk11 build --javabase=@bazel_tools//tools/jdk:remote_jdk11 +# We already have absl in the build, define absl=1 to tell googletest to use absl for backtrace. +build --define absl=1 + # Pass PATH, CC and CXX variables from the environment. build --action_env=CC build --action_env=CXX build --action_env=PATH +# Common flags for sanitizers +build:sanitizer --define tcmalloc=disabled +build:sanitizer --linkopt -ldl +build:sanitizer --build_tag_filters=-no_san +build:sanitizer --test_tag_filters=-no_san + # Basic ASAN/UBSAN that works for gcc build:asan --action_env=BAZEL_LINKLIBS= build:asan --action_env=BAZEL_LINKOPTS=-lstdc++:-lm build:asan --action_env=ENVOY_ASAN=1 +build:asan --config=sanitizer +# ASAN install its signal handler, disable ours so the stacktrace will be printed by ASAN +build:asan --define signal_trace=disabled build:asan --define ENVOY_CONFIG_ASAN=1 build:asan --copt -fsanitize=address,undefined build:asan --linkopt -fsanitize=address,undefined build:asan --copt -fno-sanitize=vptr build:asan --linkopt -fno-sanitize=vptr -build:asan --linkopt -ldl -build:asan --define tcmalloc=disabled -build:asan --build_tag_filters=-no_asan -build:asan --test_tag_filters=-no_asan -build:asan --define signal_trace=disabled build:asan --copt -DADDRESS_SANITIZER=1 build:asan --copt -D__SANITIZE_ADDRESS__ build:asan --test_env=ASAN_OPTIONS=handle_abort=1:allow_addr2line=true:check_initialization_order=true:strict_init_order=true:detect_odr_violation=1 @@ -61,22 +68,21 @@ build:macos-asan --dynamic_mode=off # Clang TSAN build:clang-tsan --action_env=ENVOY_TSAN=1 +build:clang-tsan --config=sanitizer build:clang-tsan --define ENVOY_CONFIG_TSAN=1 build:clang-tsan --copt -fsanitize=thread build:clang-tsan --linkopt -fsanitize=thread build:clang-tsan --linkopt -fuse-ld=lld -build:clang-tsan --linkopt -static-libsan -build:clang-tsan --define tcmalloc=disabled # Needed due to https://github.com/libevent/libevent/issues/777 build:clang-tsan --copt -DEVENT__DISABLE_DEBUG_MODE # Clang MSAN - broken today since we need to rebuild lib[std]c++ and external deps with MSAN # support (see https://github.com/envoyproxy/envoy/issues/443). build:clang-msan --action_env=ENVOY_MSAN=1 +build:clang-msan --config=sanitizer build:clang-msan --define ENVOY_CONFIG_MSAN=1 build:clang-msan --copt -fsanitize=memory build:clang-msan --linkopt -fsanitize=memory -build:clang-msan --define tcmalloc=disabled build:clang-msan --copt -fsanitize-memory-track-origins=2 # Clang with libc++ @@ -111,6 +117,7 @@ build:rbe-toolchain-clang-libc++ --extra_toolchains=@rbe_ubuntu_clang_libcxx//co build:rbe-toolchain-clang-libc++ --action_env=CC=clang --action_env=CXX=clang++ --action_env=PATH=/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/llvm-8/bin build:rbe-toolchain-clang-libc++ --action_env=CXXFLAGS=-stdlib=libc++ build:rbe-toolchain-clang-libc++ --action_env=LDFLAGS=-stdlib=libc++ +build:rbe-toolchain-clang-libc++ --define force_libcpp=enabled build:rbe-toolchain-gcc --config=rbe-toolchain build:rbe-toolchain-gcc --crosstool_top=@rbe_ubuntu_gcc//cc:toolchain @@ -125,13 +132,12 @@ build:remote --auth_enabled=true build:remote --experimental_inmemory_jdeps_files build:remote --experimental_inmemory_dotd_files build:remote --experimental_remote_download_outputs=toplevel -test:remote --experimental_remote_download_outputs=minimal build:remote-clang --config=remote build:remote-clang --config=rbe-toolchain-clang # Docker sandbox -build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build:8246167b9d238797cbc6c03dccc9e3921c37617d +build:docker-sandbox --experimental_docker_image=envoyproxy/envoy-build@sha256:9236915d10004a35f2439ce4a1c33c1dbb06f95f84c4a4497d4e4f95cdc9e07f build:docker-sandbox --spawn_strategy=docker build:docker-sandbox --strategy=Javac=docker build:docker-sandbox --strategy=Closure=docker @@ -151,4 +157,6 @@ build:remote-ci --remote_executor=grpcs://remotebuildexecution.googleapis.com build:asan-fuzzer --config=asan build:asan-fuzzer --define=FUZZING_ENGINE=libfuzzer build:asan-fuzzer --copt=-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -build:asan-fuzzer --copt=-fsanitize-coverage=trace-pc-guard \ No newline at end of file +build:asan-fuzzer --copt=-fsanitize-coverage=trace-pc-guard +# Remove UBSAN halt_on_error to avoid crashing on protobuf errors. +build:asan-fuzzer --test_env=UBSAN_OPTIONS=print_stacktrace=1 diff --git a/.bazelversion b/.bazelversion index 48f7a71df4..25939d35c7 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -0.28.1 +0.29.1 diff --git a/.circleci/config.yml b/.circleci/config.yml index 95d19d3dbf..1f26ad1b42 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,6 +4,7 @@ executors: ubuntu-build: description: "A regular build executor based on ubuntu image" docker: + # NOTE: Update bazel/toolchains/rbe_toolchains_config.bzl with sha256 digest to match the image here. - image: piotrsikora/envoy:d799827e285e2f4f42f4ecf284ff4cc999f48e35 resource_class: xlarge working_directory: /source diff --git a/.clang-tidy b/.clang-tidy index 248d7e6427..c5d817a7f0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,6 +2,7 @@ Checks: 'abseil-*, bugprone-*, clang-analyzer-*, clang-diagnostic-*, + misc-unused-using-decls, modernize-*, performance-*, readability-braces-around-statements, @@ -20,6 +21,7 @@ WarningsAsErrors: 'abseil-duration-*, bugprone-unused-raii, bugprone-use-after-move, clang-analyzer-core.DivideZero, + misc-unused-using-decls, modernize-deprecated-headers, modernize-loop-convert, modernize-make-shared, diff --git a/.zuul/playbooks/envoy-build/run.yaml b/.zuul/playbooks/envoy-build/run.yaml index 1ca8f832e0..0087029e4a 100644 --- a/.zuul/playbooks/envoy-build/run.yaml +++ b/.zuul/playbooks/envoy-build/run.yaml @@ -3,6 +3,8 @@ roles: - role: config-gcc gcc_version: 7 + - role: config-bazel + bazel_version: 0.28.1 tasks: - name: Build envoy shell: diff --git a/CODEOWNERS b/CODEOWNERS index 7d21fcf1e0..e8881a9e8c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -26,6 +26,8 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/filters/http/header_to_metadata @rgs1 @zuercher # alts transport socket extension /*/extensions/transport_sockets/alts @htuch @yangminzhu +# tls transport socket extension +/*/extensions/transport_sockets/tls @PiotrSikora @lizan # sni_cluster extension /*/extensions/filters/network/sni_cluster @rshriram @lizan # tracers.datadog extension @@ -53,7 +55,9 @@ extensions/filters/common/original_src @snowp @klarose /*/extensions/filters/listener/http_inspector @crazyxy @PiotrSikora @lizan # attribute context /*/extensions/filters/common/expr @kyessenov @yangminzhu -# WebAssembly extensions +# webassembly access logger extensions /*/extensions/access_loggers/wasm @jplevyak @PiotrSikora +# webassembly common extension /*/extensions/common/wasm @jplevyak @PiotrSikora +# webassembly http extensions /*/extensions/filters/http/wasm @jplevyak @PiotrSikora diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 345192d66d..23e9bd13a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,7 +72,11 @@ maximize the chances of your PR being merged. could convert from the earlier API to the new API. A field may be deprecated if this tool would be able to perform the conversion. For example, removing a field to describe HTTP/2 window settings is valid if a more comprehensive - HTTP/2 protocol options field is being introduced to replace it. + HTTP/2 protocol options field is being introduced to replace it. The PR author + deprecating the old configuration is responsible for updating all tests and + canonical configuration, or guarding them with the DEPRECATED_FEATURE_TEST() macro. + This will be validated by the bazel.compile_time_options target, which will hard-fail when + deprecated configuration is used. * For configuration deprecations that are not covered by the above semantic replacement policy, any deprecation will only take place after community consultation on mailing lists, Slack and GitHub, over the period of diff --git a/OWNERS.md b/OWNERS.md index 8f5898ff10..317dad3967 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -39,6 +39,7 @@ routing PRs, questions, etc. to the right place. * All maintainers * Piotr Sikora ([PiotrSikora](https://github.com/PiotrSikora)) (piotrsikora@google.com) * Yan Avlasov ([yanavlasov](https://github.com/yanavlasov)) (yavlasov@google.com) +* Asra Ali ([asraa](https://github.com/asraa)) (asraa@google.com) # Emeritus maintainers @@ -60,3 +61,5 @@ matter expert reviews. Feel free to loop them in as needed. * Bazel/build. * Daniel Hochman ([danielhochman](https://github.com/danielhochman)) (dhochman@lyft.com) * Redis, Python, configuration/operational questions. +* Yuchen Dai ([lambdai](https://github.com/lambdai)) (lambdai@google.com) + * v2 xDS, listeners, filter chain discovery service. diff --git a/SECURITY.md b/SECURITY.md index e24c641f88..883b3c3b06 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -217,11 +217,25 @@ issue fixed for your respective distribution's users. Before any information from the list is shared with respective members of your team required to fix said issue, they must agree to the same terms and only find out information on a need-to-know basis. +We typically expect a single point-of-contact (PoC) at any given legal entity. Within the +organization, it is the responsibility of the PoC to share CVE and related patches internally. This +should be performed on a strictly need-to-know basis with affected groups to the extent that this is +technically plausible. All teams should be aware of the embargo conditions and accept them. +Ultimately, if an organization breaks embargo transitively through such sharing, they will lose +the early disclosure privilege, so it's in their best interest to carefully share information internally, +following best practices and use their judgement in balancing the tradeoff between protecting users +and maintaining confidentiality. + The embargo applies to information shared, source code and binary images. **It is a violation of the embargo policy to share binary distributions of the security fixes before the public release date.** This includes, but is not limited to, Envoy binaries and Docker images. It is expected that distributors have a method to stage and validate new binaries without exposing them publicly. +If the information shared is under embargo from a third party, where Envoy is one of many projects +that a disclosure is shared with, it is critical to consider that the ramifications of any leak will +extend beyond the Envoy community and will leave us in a position in which we will be less likely to +receive embargoed reports in the future. + In the unfortunate event you share the information beyond what is allowed by this policy, you _must_ urgently inform the envoy-security@googlegroups.com mailing list of exactly what information leaked and to whom. A retrospective will take place after the leak so we can assess how to prevent making the diff --git a/api/CONTRIBUTING.md b/api/CONTRIBUTING.md index 02f8536906..d07e1820a0 100644 --- a/api/CONTRIBUTING.md +++ b/api/CONTRIBUTING.md @@ -8,7 +8,7 @@ API changes are regular PRs in https://github.com/envoyproxy/envoy for the API/c changes. They may be as part of a larger implementation PR. Please follow the standard Bazel and CI process for validating build/test sanity of `api/` before submitting a PR. -*Note: New .proto files should be also included to [build.sh](https://github.com/envoyproxy/envoy/blob/master/docs/build.sh) and +*Note: New .proto files should be added to [BUILD](https://github.com/envoyproxy/envoy/blob/master/api/docs/BUILD) in order to get the RSTs generated.* ## Documentation changes diff --git a/api/bazel/BUILD b/api/bazel/BUILD index e69de29bb2..4b582bb8be 100644 --- a/api/bazel/BUILD +++ b/api/bazel/BUILD @@ -0,0 +1,12 @@ +load("@io_bazel_rules_go//proto:compiler.bzl", "go_proto_compiler") + +licenses(["notice"]) # Apache 2 + +go_proto_compiler( + name = "pgv_plugin_go", + options = ["lang=go"], + plugin = "@com_envoyproxy_protoc_gen_validate//:protoc-gen-validate", + suffix = ".pb.validate.go", + valid_archive = False, + visibility = ["//visibility:public"], +) diff --git a/api/bazel/api_build_system.bzl b/api/bazel/api_build_system.bzl index d9a3e2d943..9e0d48ee17 100644 --- a/api/bazel/api_build_system.bzl +++ b/api/bazel/api_build_system.bzl @@ -5,10 +5,24 @@ load("@io_bazel_rules_go//go:def.bzl", "go_test") _PY_SUFFIX = "_py" _CC_SUFFIX = "_cc" +_CC_EXPORT_SUFFIX = "_export_cc" _GO_PROTO_SUFFIX = "_go_proto" -_GO_GRPC_SUFFIX = "_go_grpc" _GO_IMPORTPATH_PREFIX = "github.com/envoyproxy/data-plane-api/api/" +_COMMON_PROTO_DEPS = [ + "@com_google_protobuf//:any_proto", + "@com_google_protobuf//:descriptor_proto", + "@com_google_protobuf//:duration_proto", + "@com_google_protobuf//:empty_proto", + "@com_google_protobuf//:struct_proto", + "@com_google_protobuf//:timestamp_proto", + "@com_google_protobuf//:wrappers_proto", + "@com_google_googleapis//google/api:http_proto", + "@com_google_googleapis//google/api:annotations_proto", + "@com_google_googleapis//google/rpc:status_proto", + "@com_envoyproxy_protoc_gen_validate//validate:validate_proto", +] + def _Suffix(d, suffix): return d + suffix @@ -35,7 +49,6 @@ def api_py_proto_library(name, srcs = [], deps = [], external_py_proto_deps = [] "@com_google_googleapis//google/api:annotations_py_proto", "@com_google_googleapis//google/api:http_py_proto", "@com_google_googleapis//google/api:httpbody_py_proto", - "@com_github_gogo_protobuf//:gogo_proto_py", ], visibility = ["//visibility:public"], ) @@ -60,41 +73,6 @@ def py_proto_library(name, deps = []): visibility = ["//visibility:public"], ) -def api_go_proto_library(name, proto, deps = []): - go_proto_library( - name = _Suffix(name, _GO_PROTO_SUFFIX), - importpath = _Suffix(_GO_IMPORTPATH_PREFIX, name), - proto = proto, - visibility = ["//visibility:public"], - deps = deps + [ - "@com_github_gogo_protobuf//:gogo_proto_go", - "@io_bazel_rules_go//proto/wkt:any_go_proto", - "@io_bazel_rules_go//proto/wkt:duration_go_proto", - "@io_bazel_rules_go//proto/wkt:struct_go_proto", - "@io_bazel_rules_go//proto/wkt:timestamp_go_proto", - "@io_bazel_rules_go//proto/wkt:wrappers_go_proto", - "@com_envoyproxy_protoc_gen_validate//validate:go_default_library", - "@com_google_googleapis//google/rpc:status_go_proto", - ], - ) - -def api_go_grpc_library(name, proto, deps = []): - go_grpc_library( - name = _Suffix(name, _GO_GRPC_SUFFIX), - importpath = _Suffix(_GO_IMPORTPATH_PREFIX, name), - proto = proto, - visibility = ["//visibility:public"], - deps = deps + [ - "@com_github_gogo_protobuf//:gogo_proto_go", - "@io_bazel_rules_go//proto/wkt:any_go_proto", - "@io_bazel_rules_go//proto/wkt:duration_go_proto", - "@io_bazel_rules_go//proto/wkt:struct_go_proto", - "@io_bazel_rules_go//proto/wkt:wrappers_go_proto", - "@com_envoyproxy_protoc_gen_validate//validate:go_default_library", - "@com_google_googleapis//google/api:annotations_go_proto", - ], - ) - # This is api_proto_library plus some logic internal to //envoy/api. def api_proto_library_internal(visibility = ["//visibility:private"], **kwargs): # //envoy/docs/build.sh needs visibility in order to generate documents. @@ -107,8 +85,6 @@ def api_proto_library_internal(visibility = ["//visibility:private"], **kwargs): # TODO(htuch): has_services is currently ignored but will in future support # 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"], @@ -123,27 +99,13 @@ def api_proto_library( native.proto_library( name = name, srcs = srcs, - deps = deps + external_proto_deps + [ - "@com_google_protobuf//:any_proto", - "@com_google_protobuf//:descriptor_proto", - "@com_google_protobuf//:duration_proto", - "@com_google_protobuf//:empty_proto", - "@com_google_protobuf//:struct_proto", - "@com_google_protobuf//:timestamp_proto", - "@com_google_protobuf//:wrappers_proto", - "@com_google_googleapis//google/api:http_proto", - "@com_google_googleapis//google/api:annotations_proto", - "@com_google_googleapis//google/rpc:status_proto", - "@com_github_gogo_protobuf//:gogo_proto", - "@com_envoyproxy_protoc_gen_validate//validate:validate_proto", - ], + deps = deps + external_proto_deps + _COMMON_PROTO_DEPS, visibility = visibility, ) pgv_cc_proto_library( name = _Suffix(name, _CC_SUFFIX), linkstatic = linkstatic, cc_deps = [_LibrarySuffix(d, _CC_SUFFIX) for d in deps] + external_cc_proto_deps + [ - "@com_github_gogo_protobuf//:gogo_proto_cc", "@com_google_googleapis//google/api:http_cc_proto", "@com_google_googleapis//google/api:annotations_cc_proto", "@com_google_googleapis//google/rpc:status_cc_proto", @@ -169,7 +131,7 @@ def api_cc_test(name, srcs, proto_deps): native.cc_test( name = name, srcs = srcs, - deps = [_LibrarySuffix(d, _CC_SUFFIX) for d in proto_deps], + deps = [_LibrarySuffix(d, _CC_EXPORT_SUFFIX) for d in proto_deps], ) def api_go_test(name, size, importpath, srcs = [], deps = []): @@ -180,3 +142,49 @@ def api_go_test(name, size, importpath, srcs = [], deps = []): importpath = importpath, deps = deps, ) + +_GO_BAZEL_RULE_MAPPING = { + "@opencensus_proto//opencensus/proto/trace/v1:trace_proto": "@opencensus_proto//opencensus/proto/trace/v1:trace_proto_go", + "@opencensus_proto//opencensus/proto/trace/v1:trace_config_proto": "@opencensus_proto//opencensus/proto/trace/v1:trace_and_config_proto_go", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_proto": "@com_google_googleapis//google/api/expr/v1alpha1:cel_go_proto", +} + +def go_proto_mapping(dep): + mapped = _GO_BAZEL_RULE_MAPPING.get(dep) + if mapped == None: + return _Suffix("@" + Label(dep).workspace_name + "//" + Label(dep).package + ":" + Label(dep).name, _GO_PROTO_SUFFIX) + return mapped + +def api_proto_package(name = "pkg", srcs = [], deps = [], has_services = False, visibility = ["//visibility:public"]): + if srcs == []: + srcs = native.glob(["*.proto"]) + + native.proto_library( + name = name, + srcs = srcs, + deps = deps + _COMMON_PROTO_DEPS, + visibility = visibility, + ) + + compilers = ["@io_bazel_rules_go//proto:go_proto", "//bazel:pgv_plugin_go"] + if has_services: + compilers = ["@io_bazel_rules_go//proto:go_grpc", "//bazel:pgv_plugin_go"] + + go_proto_library( + name = _Suffix(name, _GO_PROTO_SUFFIX), + compilers = compilers, + importpath = _Suffix(_GO_IMPORTPATH_PREFIX, native.package_name()), + proto = name, + visibility = ["//visibility:public"], + deps = [go_proto_mapping(dep) for dep in deps] + [ + "@com_github_golang_protobuf//ptypes:go_default_library", + "@com_github_golang_protobuf//ptypes/any:go_default_library", + "@com_github_golang_protobuf//ptypes/duration:go_default_library", + "@com_github_golang_protobuf//ptypes/struct:go_default_library", + "@com_github_golang_protobuf//ptypes/timestamp:go_default_library", + "@com_github_golang_protobuf//ptypes/wrappers:go_default_library", + "@com_envoyproxy_protoc_gen_validate//validate:go_default_library", + "@com_google_googleapis//google/api:annotations_go_proto", + "@com_google_googleapis//google/rpc:status_go_proto", + ], + ) diff --git a/api/bazel/repositories.bzl b/api/bazel/repositories.bzl index 3185d0f740..1362c5671a 100644 --- a/api/bazel/repositories.bzl +++ b/api/bazel/repositories.bzl @@ -16,10 +16,10 @@ def api_dependencies(): locations = REPOSITORY_LOCATIONS, ) envoy_http_archive( - name = "com_github_gogo_protobuf", + name = "com_github_cncf_udpa", locations = REPOSITORY_LOCATIONS, - build_file_content = GOGOPROTO_BUILD_CONTENT, ) + envoy_http_archive( name = "prometheus_metrics_model", locations = REPOSITORY_LOCATIONS, @@ -34,61 +34,11 @@ def api_dependencies(): locations = REPOSITORY_LOCATIONS, build_file_content = KAFKASOURCE_BUILD_CONTENT, ) - -GOGOPROTO_BUILD_CONTENT = """ -load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library", "py_proto_library") -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") - -proto_library( - name = "gogo_proto", - srcs = [ - "gogoproto/gogo.proto", - ], - deps = [ - "@com_google_protobuf//:descriptor_proto", - ], - visibility = ["//visibility:public"], -) - -go_proto_library( - name = "descriptor_go_proto", - importpath = "github.com/golang/protobuf/protoc-gen-go/descriptor", - proto = "@com_google_protobuf//:descriptor_proto", - visibility = ["//visibility:public"], -) - -cc_proto_library( - name = "gogo_proto_cc", - srcs = [ - "gogoproto/gogo.proto", - ], - default_runtime = "@com_google_protobuf//:protobuf", - protoc = "@com_google_protobuf//:protoc", - deps = ["@com_google_protobuf//:cc_wkt_protos"], - visibility = ["//visibility:public"], -) - -go_proto_library( - name = "gogo_proto_go", - importpath = "gogoproto", - proto = ":gogo_proto", - visibility = ["//visibility:public"], - deps = [ - ":descriptor_go_proto", - ], -) - -py_proto_library( - name = "gogo_proto_py", - srcs = [ - "gogoproto/gogo.proto", - ], - default_runtime = "@com_google_protobuf//:protobuf_python", - protoc = "@com_google_protobuf//:protoc", - visibility = ["//visibility:public"], - deps = ["@com_google_protobuf//:protobuf_python"], -) -""" + envoy_http_archive( + name = "com_github_openzipkin_zipkinapi", + locations = REPOSITORY_LOCATIONS, + build_file_content = ZIPKINAPI_BUILD_CONTENT, + ) PROMETHEUSMETRICS_BUILD_CONTENT = """ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library") @@ -149,3 +99,24 @@ filegroup( ) """ + +ZIPKINAPI_BUILD_CONTENT = """ + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") + +api_proto_library( + name = "zipkin", + srcs = [ + "zipkin-jsonv2.proto", + "zipkin.proto", + ], + visibility = ["//visibility:public"], +) + +go_proto_library( + name = "zipkin_go_proto", + proto = ":zipkin", + visibility = ["//visibility:public"], +) +""" diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 0d0600a984..07a9fb8334 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -1,14 +1,11 @@ BAZEL_SKYLIB_RELEASE = "0.8.0" BAZEL_SKYLIB_SHA256 = "2ef429f5d7ce7111263289644d233707dba35e39696377ebab8b0bc701f7818e" -GOGOPROTO_RELEASE = "1.2.1" -GOGOPROTO_SHA256 = "99e423905ba8921e86817607a5294ffeedb66fdd4a85efce5eb2848f715fdb3a" +OPENCENSUS_PROTO_GIT_SHA = "5cec5ea58c3efa81fa808f2bd38ce182da9ee731" # Jul 25, 2019 +OPENCENSUS_PROTO_SHA256 = "faeb93f293ff715b0cb530d273901c0e2e99277b9ed1c0a0326bca9ec5774ad2" -OPENCENSUS_PROTO_RELEASE = "0.2.1" -OPENCENSUS_PROTO_SHA256 = "bfcefa6093fc2ecdf5c9effea86e6982d0d6f9a5178b17fcf73a62e0f3fb43d0" - -PGV_GIT_SHA = "2feaabb13a5d697b80fcb938c0ce37b24c9381ee" # Jul 26, 2018 -PGV_SHA256 = "ddefe3dcbb25d68a2e5dfea67d19c060959c2aecc782802bd4c1a5811d44dd45" +PGV_GIT_SHA = "fd7de029969b7c0ef8b754660b997399b6fd812a" # Sep 4, 2019 +PGV_SHA256 = "55c6ad4a1b405938524ab55b18349c824d3fc6eaef580e1ef2a9dfe39f737b9e" GOOGLEAPIS_GIT_SHA = "be480e391cc88a75cf2a81960ef79c80d5012068" # Jul 24, 2019 GOOGLEAPIS_SHA = "c1969e5b72eab6d9b6cfcff748e45ba57294aeea1d96fd04cd081995de0605c2" @@ -18,6 +15,12 @@ PROMETHEUS_SHA = "783bdaf8ee0464b35ec0c8704871e1e72afa0005c3f3587f65d9d6694bf391 KAFKA_SOURCE_SHA = "ae7a1696c0a0302b43c5b21e515c37e6ecd365941f68a510a7e442eebddf39a1" # 2.2.0-rc2 +UDPA_GIT_SHA = "4cbdcb9931ca743a915a7c5fda51b2ee793ed157" # Aug 22, 2019 +UDPA_SHA256 = "6291d0c0e3a4d5f08057ea7a00ed0b0ec3dd4e5a3b1cf20f803774680b5a806f" + +ZIPKINAPI_RELEASE = "0.2.2" # Aug 23, 2019 +ZIPKINAPI_SHA256 = "688c4fe170821dd589f36ec45aaadc03a618a40283bc1f97da8fa11686fc816b" + REPOSITORY_LOCATIONS = dict( bazel_skylib = dict( sha256 = BAZEL_SKYLIB_SHA256, @@ -34,10 +37,10 @@ REPOSITORY_LOCATIONS = dict( strip_prefix = "googleapis-" + GOOGLEAPIS_GIT_SHA, urls = ["https://github.com/googleapis/googleapis/archive/" + GOOGLEAPIS_GIT_SHA + ".tar.gz"], ), - com_github_gogo_protobuf = dict( - sha256 = GOGOPROTO_SHA256, - strip_prefix = "protobuf-" + GOGOPROTO_RELEASE, - urls = ["https://github.com/gogo/protobuf/archive/v" + GOGOPROTO_RELEASE + ".tar.gz"], + com_github_cncf_udpa = dict( + sha256 = UDPA_SHA256, + strip_prefix = "udpa-" + UDPA_GIT_SHA, + urls = ["https://github.com/cncf/udpa/archive/" + UDPA_GIT_SHA + ".tar.gz"], ), prometheus_metrics_model = dict( sha256 = PROMETHEUS_SHA, @@ -46,12 +49,17 @@ REPOSITORY_LOCATIONS = dict( ), opencensus_proto = dict( sha256 = OPENCENSUS_PROTO_SHA256, - strip_prefix = "opencensus-proto-" + OPENCENSUS_PROTO_RELEASE + "/src", - urls = ["https://github.com/census-instrumentation/opencensus-proto/archive/v" + OPENCENSUS_PROTO_RELEASE + ".tar.gz"], + strip_prefix = "opencensus-proto-" + OPENCENSUS_PROTO_GIT_SHA + "/src", + urls = ["https://github.com/census-instrumentation/opencensus-proto/archive/" + OPENCENSUS_PROTO_GIT_SHA + ".tar.gz"], ), kafka_source = dict( sha256 = KAFKA_SOURCE_SHA, strip_prefix = "kafka-2.2.0-rc2/clients/src/main/resources/common/message", urls = ["https://github.com/apache/kafka/archive/2.2.0-rc2.zip"], ), + com_github_openzipkin_zipkinapi = dict( + sha256 = ZIPKINAPI_SHA256, + strip_prefix = "zipkin-api-" + ZIPKINAPI_RELEASE, + urls = ["https://github.com/openzipkin/zipkin-api/archive/" + ZIPKINAPI_RELEASE + ".tar.gz"], + ), ) diff --git a/api/docs/BUILD b/api/docs/BUILD index 8b2fb7ffd3..93fccd13fa 100644 --- a/api/docs/BUILD +++ b/api/docs/BUILD @@ -7,28 +7,17 @@ package_group( ], ) -# TODO(htuch): Grow this to cover everything we want to generate docs for, so we can just invoke -# bazel build //docs:protos --aspects tools/protodoc/protodoc.bzl%proto_doc_aspect --output_groups=rst +# This is where you add protos that will participate in docs RST generation. proto_library( name = "protos", deps = [ - "//envoy/admin/v2alpha:certs", - "//envoy/admin/v2alpha:clusters", - "//envoy/admin/v2alpha:config_dump", - "//envoy/admin/v2alpha:listeners", - "//envoy/admin/v2alpha:memory", - "//envoy/admin/v2alpha:mutex_stats", - "//envoy/admin/v2alpha:server_info", - "//envoy/admin/v2alpha:tap", - "//envoy/api/v2:cds", - "//envoy/api/v2:discovery", - "//envoy/api/v2:eds", - "//envoy/api/v2:lds", - "//envoy/api/v2:rds", - "//envoy/api/v2/cluster:circuit_breaker", - "//envoy/api/v2/cluster:outlier_detection", - "//envoy/api/v2/core:protocol", - "//envoy/api/v2/listener", + "//envoy/admin/v2alpha:pkg", + "//envoy/api/v2", + "//envoy/api/v2/auth", + "//envoy/api/v2/cluster", + "//envoy/api/v2/core", + "//envoy/api/v2/endpoint", + "//envoy/api/v2/listener:pkg", "//envoy/api/v2/ratelimit", "//envoy/api/v2/route", "//envoy/config/accesslog/v2:als", @@ -40,6 +29,7 @@ proto_library( "//envoy/config/common/tap/v2alpha:common", "//envoy/config/filter/accesslog/v2:accesslog", "//envoy/config/filter/dubbo/router/v2alpha1:router", + "//envoy/config/filter/fault/v2:fault", "//envoy/config/filter/http/buffer/v2:buffer", "//envoy/config/filter/http/csrf/v2:csrf", "//envoy/config/filter/http/dynamic_forward_proxy/v2alpha:dynamic_forward_proxy", @@ -76,6 +66,7 @@ proto_library( "//envoy/config/health_checker/redis/v2:redis", "//envoy/config/metrics/v2:metrics_service", "//envoy/config/metrics/v2:stats", + "//envoy/config/overload/v2alpha:overload", "//envoy/config/ratelimit/v2:rls", "//envoy/config/rbac/v2:rbac", "//envoy/config/resource_monitor/fixed_heap/v2alpha:fixed_heap", @@ -95,14 +86,9 @@ proto_library( "//envoy/service/auth/v2:external_auth", "//envoy/service/discovery/v2:ads", "//envoy/service/discovery/v2:rtds", - "//envoy/service/load_stats/v2:lrs", - "//envoy/service/metrics/v2:metrics_service", "//envoy/service/ratelimit/v2:rls", "//envoy/service/tap/v2alpha:common", - "//envoy/type:percent", - "//envoy/type:range", - "//envoy/type/matcher:metadata", - "//envoy/type/matcher:number", - "//envoy/type/matcher:string", + "//envoy/type", + "//envoy/type/matcher", ], ) diff --git a/api/envoy/admin/v2alpha/BUILD b/api/envoy/admin/v2alpha/BUILD index aa35aa7c8d..850eb05158 100644 --- a/api/envoy/admin/v2alpha/BUILD +++ b/api/envoy/admin/v2alpha/BUILD @@ -1,7 +1,18 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2", + "//envoy/api/v2/auth", + "//envoy/api/v2/core", + "//envoy/config/bootstrap/v2:pkg", + "//envoy/service/tap/v2alpha:pkg", + "//envoy/type", + ], +) + api_proto_library_internal( name = "config_dump", srcs = ["config_dump.proto"], diff --git a/api/envoy/admin/v2alpha/config_dump.proto b/api/envoy/admin/v2alpha/config_dump.proto index 1025b17fb1..10c57f06f1 100644 --- a/api/envoy/admin/v2alpha/config_dump.proto +++ b/api/envoy/admin/v2alpha/config_dump.proto @@ -16,8 +16,6 @@ import "envoy/config/bootstrap/v2/bootstrap.proto"; import "google/protobuf/any.proto"; import "google/protobuf/timestamp.proto"; -import "gogoproto/gogo.proto"; - // [#protodoc-title: ConfigDump] // The :ref:`/config_dump ` admin endpoint uses this wrapper diff --git a/api/envoy/admin/v2alpha/server_info.proto b/api/envoy/admin/v2alpha/server_info.proto index 7389af5b08..78cc6fa702 100644 --- a/api/envoy/admin/v2alpha/server_info.proto +++ b/api/envoy/admin/v2alpha/server_info.proto @@ -36,6 +36,9 @@ message ServerInfo { // Uptime since the start of the first epoch. google.protobuf.Duration uptime_all_epochs = 4; + // Hot restart version. + string hot_restart_version = 5; + // Command line options the server is currently running with. CommandLineOptions command_line_options = 6; } @@ -82,8 +85,7 @@ message CommandLineOptions { // See :option:`--log-path` for details. string log_path = 11; - // See :option:`--hot-restart-version` for details. - bool hot_restart_version = 12; + reserved 12; // See :option:`--service-cluster` for details. string service_cluster = 13; diff --git a/api/envoy/admin/v3alpha/BUILD b/api/envoy/admin/v3alpha/BUILD new file mode 100644 index 0000000000..9849282084 --- /dev/null +++ b/api/envoy/admin/v3alpha/BUILD @@ -0,0 +1,87 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha", + "//envoy/api/v3alpha/auth", + "//envoy/api/v3alpha/core", + "//envoy/config/bootstrap/v3alpha:pkg", + "//envoy/service/tap/v3alpha:pkg", + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "config_dump", + srcs = ["config_dump.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha:cds", + "//envoy/api/v3alpha:lds", + "//envoy/api/v3alpha:rds", + "//envoy/api/v3alpha:srds", + "//envoy/api/v3alpha/auth:cert", + "//envoy/config/bootstrap/v3alpha:bootstrap", + ], +) + +api_proto_library_internal( + name = "clusters", + srcs = ["clusters.proto"], + visibility = ["//visibility:public"], + deps = [ + ":metrics", + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:health_check", + "//envoy/type:percent", + ], +) + +api_proto_library_internal( + name = "listeners", + srcs = ["listeners.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha/core:address", + ], +) + +api_proto_library_internal( + name = "metrics", + srcs = ["metrics.proto"], + visibility = ["//visibility:public"], +) + +api_proto_library_internal( + name = "memory", + srcs = ["memory.proto"], + visibility = ["//visibility:public"], +) + +api_proto_library_internal( + name = "mutex_stats", + srcs = ["mutex_stats.proto"], + visibility = ["//visibility:public"], +) + +api_proto_library_internal( + name = "certs", + srcs = ["certs.proto"], + visibility = ["//visibility:public"], +) + +api_proto_library_internal( + name = "server_info", + srcs = ["server_info.proto"], + visibility = ["//visibility:public"], +) + +api_proto_library_internal( + name = "tap", + srcs = ["tap.proto"], + deps = [ + "//envoy/service/tap/v3alpha:common", + ], +) diff --git a/api/envoy/admin/v3alpha/certs.proto b/api/envoy/admin/v3alpha/certs.proto new file mode 100644 index 0000000000..e34fd36d99 --- /dev/null +++ b/api/envoy/admin/v3alpha/certs.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "CertsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +import "google/protobuf/timestamp.proto"; + +// [#protodoc-title: Certificates] + +// Proto representation of certificate details. Admin endpoint uses this wrapper for `/certs` to +// display certificate information. See :ref:`/certs ` for more +// information. +message Certificates { + // List of certificates known to an Envoy. + repeated Certificate certificates = 1; +} + +message Certificate { + + // Details of CA certificate. + repeated CertificateDetails ca_cert = 1; + + // Details of Certificate Chain + repeated CertificateDetails cert_chain = 2; +} + +message CertificateDetails { + // Path of the certificate. + string path = 1; + + // Certificate Serial Number. + string serial_number = 2; + + // List of Subject Alternate names. + repeated SubjectAlternateName subject_alt_names = 3; + + // Minimum of days until expiration of certificate and it's chain. + uint64 days_until_expiration = 4; + + // Indicates the time from which the certificate is valid. + google.protobuf.Timestamp valid_from = 5; + + // Indicates the time at which the certificate expires. + google.protobuf.Timestamp expiration_time = 6; +} + +message SubjectAlternateName { + + // Subject Alternate Name. + oneof name { + string dns = 1; + string uri = 2; + } +} diff --git a/api/envoy/admin/v3alpha/clusters.proto b/api/envoy/admin/v3alpha/clusters.proto new file mode 100644 index 0000000000..093448d9f8 --- /dev/null +++ b/api/envoy/admin/v3alpha/clusters.proto @@ -0,0 +1,143 @@ +syntax = "proto3"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "ClustersProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +import "envoy/admin/v3alpha/metrics.proto"; +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/core/health_check.proto"; +import "envoy/type/percent.proto"; + +// [#protodoc-title: Clusters] + +// Admin endpoint uses this wrapper for `/clusters` to display cluster status information. +// See :ref:`/clusters ` for more information. +message Clusters { + // Mapping from cluster name to each cluster's status. + repeated ClusterStatus cluster_statuses = 1; +} + +// Details an individual cluster's current status. +message ClusterStatus { + // Name of the cluster. + string name = 1; + + // Denotes whether this cluster was added via API or configured statically. + bool added_via_api = 2; + + // The success rate threshold used in the last interval. + // If + // :ref:`outlier_detection.split_external_local_origin_errors` + // is *false*, all errors: externally and locally generated were used to calculate the threshold. + // If + // :ref:`outlier_detection.split_external_local_origin_errors` + // is *true*, only externally generated errors were used to calculate the threshold. + // The threshold is used to eject hosts based on their success rate. See + // :ref:`Cluster outlier detection ` documentation for details. + // + // Note: this field may be omitted in any of the three following cases: + // + // 1. There were not enough hosts with enough request volume to proceed with success rate based + // outlier ejection. + // 2. The threshold is computed to be < 0 because a negative value implies that there was no + // threshold for that interval. + // 3. Outlier detection is not enabled for this cluster. + envoy.type.Percent success_rate_ejection_threshold = 3; + + // Mapping from host address to the host's current status. + repeated HostStatus host_statuses = 4; + + // The success rate threshold used in the last interval when only locally originated failures were + // taken into account and externally originated errors were treated as success. + // This field should be interpretted only when + // :ref:`outlier_detection.split_external_local_origin_errors` + // is *true*. The threshold is used to eject hosts based on their success rate. + // See :ref:`Cluster outlier detection ` documentation for + // details. + // + // Note: this field may be omitted in any of the three following cases: + // + // 1. There were not enough hosts with enough request volume to proceed with success rate based + // outlier ejection. + // 2. The threshold is computed to be < 0 because a negative value implies that there was no + // threshold for that interval. + // 3. Outlier detection is not enabled for this cluster. + envoy.type.Percent local_origin_success_rate_ejection_threshold = 5; +} + +// Current state of a particular host. +message HostStatus { + // Address of this host. + envoy.api.v3alpha.core.Address address = 1; + + // List of stats specific to this host. + repeated SimpleMetric stats = 2; + + // The host's current health status. + HostHealthStatus health_status = 3; + + // Request success rate for this host over the last calculated interval. + // If + // :ref:`outlier_detection.split_external_local_origin_errors` + // is *false*, all errors: externally and locally generated were used in success rate + // calculation. If + // :ref:`outlier_detection.split_external_local_origin_errors` + // is *true*, only externally generated errors were used in success rate calculation. + // See :ref:`Cluster outlier detection ` documentation for + // details. + // + // Note: the message will not be present if host did not have enough request volume to calculate + // success rate or the cluster did not have enough hosts to run through success rate outlier + // ejection. + envoy.type.Percent success_rate = 4; + + // The host's weight. If not configured, the value defaults to 1. + uint32 weight = 5; + + // The hostname of the host, if applicable. + string hostname = 6; + + // The host's priority. If not configured, the value defaults to 0 (highest priority). + uint32 priority = 7; + + // Request success rate for this host over the last calculated + // interval when only locally originated errors are taken into account and externally originated + // errors were treated as success. + // This field should be interpretted only when + // :ref:`outlier_detection.split_external_local_origin_errors` + // is *true*. + // See :ref:`Cluster outlier detection ` documentation for + // details. + // + // Note: the message will not be present if host did not have enough request volume to calculate + // success rate or the cluster did not have enough hosts to run through success rate outlier + // ejection. + envoy.type.Percent local_origin_success_rate = 8; +} + +// Health status for a host. +message HostHealthStatus { + // The host is currently failing active health checks. + bool failed_active_health_check = 1; + + // The host is currently considered an outlier and has been ejected. + bool failed_outlier_check = 2; + + // The host is currently being marked as degraded through active health checking. + bool failed_active_degraded_check = 4; + + // The host has been removed from service discovery, but is being stabilized due to active + // health checking. + bool pending_dynamic_removal = 5; + + // The host has not yet been health checked. + bool pending_active_hc = 6; + + // Health status as reported by EDS. Note: only HEALTHY and UNHEALTHY are currently supported + // here. + // TODO(mrice32): pipe through remaining EDS health status possibilities. + envoy.api.v3alpha.core.HealthStatus eds_health_status = 3; +} diff --git a/api/envoy/admin/v3alpha/config_dump.proto b/api/envoy/admin/v3alpha/config_dump.proto new file mode 100644 index 0000000000..cc6afbdbad --- /dev/null +++ b/api/envoy/admin/v3alpha/config_dump.proto @@ -0,0 +1,264 @@ +syntax = "proto3"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "ConfigDumpProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +import "envoy/api/v3alpha/auth/cert.proto"; +import "envoy/api/v3alpha/cds.proto"; +import "envoy/api/v3alpha/lds.proto"; +import "envoy/api/v3alpha/rds.proto"; +import "envoy/api/v3alpha/srds.proto"; +import "envoy/config/bootstrap/v3alpha/bootstrap.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + +// [#protodoc-title: ConfigDump] + +// The :ref:`/config_dump ` admin endpoint uses this wrapper +// message to maintain and serve arbitrary configuration information from any component in Envoy. +message ConfigDump { + // This list is serialized and dumped in its entirety at the + // :ref:`/config_dump ` endpoint. + // + // The following configurations are currently supported and will be dumped in the order given + // below: + // + // * *bootstrap*: :ref:`BootstrapConfigDump ` + // * *clusters*: :ref:`ClustersConfigDump ` + // * *listeners*: :ref:`ListenersConfigDump ` + // * *routes*: :ref:`RoutesConfigDump ` + repeated google.protobuf.Any configs = 1; +} + +// This message describes the bootstrap configuration that Envoy was started with. This includes +// any CLI overrides that were merged. Bootstrap configuration information can be used to recreate +// the static portions of an Envoy configuration by reusing the output as the bootstrap +// configuration for another Envoy. +message BootstrapConfigDump { + envoy.config.bootstrap.v3alpha.Bootstrap bootstrap = 1; + + // The timestamp when the BootstrapConfig was last updated. + google.protobuf.Timestamp last_updated = 2; +} + +// Envoy's listener manager fills this message with all currently known listeners. Listener +// configuration information can be used to recreate an Envoy configuration by populating all +// listeners as static listeners or by returning them in a LDS response. +message ListenersConfigDump { + // This is the :ref:`version_info ` in the + // last processed LDS discovery response. If there are only static bootstrap listeners, this field + // will be "". + string version_info = 1; + + // Describes a statically loaded listener. + message StaticListener { + // The listener config. + envoy.api.v3alpha.Listener listener = 1; + + // The timestamp when the Listener was last updated. + google.protobuf.Timestamp last_updated = 2; + } + + // Describes a dynamically loaded cluster via the LDS API. + message DynamicListener { + // This is the per-resource version information. This version is currently taken from the + // :ref:`version_info ` field at the time + // that the listener was loaded. In the future, discrete per-listener versions may be supported + // by the API. + string version_info = 1; + + // The listener config. + envoy.api.v3alpha.Listener listener = 2; + + // The timestamp when the Listener was last updated. + google.protobuf.Timestamp last_updated = 3; + } + + // The statically loaded listener configs. + repeated StaticListener static_listeners = 2; + + // The dynamically loaded active listeners. These are listeners that are available to service + // data plane traffic. + repeated DynamicListener dynamic_active_listeners = 3; + + // The dynamically loaded warming listeners. These are listeners that are currently undergoing + // warming in preparation to service data plane traffic. Note that if attempting to recreate an + // Envoy configuration from a configuration dump, the warming listeners should generally be + // discarded. + repeated DynamicListener dynamic_warming_listeners = 4; + + // The dynamically loaded draining listeners. These are listeners that are currently undergoing + // draining in preparation to stop servicing data plane traffic. Note that if attempting to + // recreate an Envoy configuration from a configuration dump, the draining listeners should + // generally be discarded. + repeated DynamicListener dynamic_draining_listeners = 5; +} + +// Envoy's cluster manager fills this message with all currently known clusters. Cluster +// configuration information can be used to recreate an Envoy configuration by populating all +// clusters as static clusters or by returning them in a CDS response. +message ClustersConfigDump { + // This is the :ref:`version_info ` in the + // last processed CDS discovery response. If there are only static bootstrap clusters, this field + // will be "". + string version_info = 1; + + // Describes a statically loaded cluster. + message StaticCluster { + // The cluster config. + envoy.api.v3alpha.Cluster cluster = 1; + + // The timestamp when the Cluster was last updated. + google.protobuf.Timestamp last_updated = 2; + } + + // Describes a dynamically loaded cluster via the CDS API. + message DynamicCluster { + // This is the per-resource version information. This version is currently taken from the + // :ref:`version_info ` field at the time + // that the cluster was loaded. In the future, discrete per-cluster versions may be supported by + // the API. + string version_info = 1; + + // The cluster config. + envoy.api.v3alpha.Cluster cluster = 2; + + // The timestamp when the Cluster was last updated. + google.protobuf.Timestamp last_updated = 3; + } + + // The statically loaded cluster configs. + repeated StaticCluster static_clusters = 2; + + // The dynamically loaded active clusters. These are clusters that are available to service + // data plane traffic. + repeated DynamicCluster dynamic_active_clusters = 3; + + // The dynamically loaded warming clusters. These are clusters that are currently undergoing + // warming in preparation to service data plane traffic. Note that if attempting to recreate an + // Envoy configuration from a configuration dump, the warming clusters should generally be + // discarded. + repeated DynamicCluster dynamic_warming_clusters = 4; +} + +// Envoy's RDS implementation fills this message with all currently loaded routes, as described by +// their RouteConfiguration objects. Static routes configured in the bootstrap configuration are +// separated from those configured dynamically via RDS. Route configuration information can be used +// to recreate an Envoy configuration by populating all routes as static routes or by returning them +// in RDS responses. +message RoutesConfigDump { + message StaticRouteConfig { + // The route config. + envoy.api.v3alpha.RouteConfiguration route_config = 1; + + // The timestamp when the Route was last updated. + google.protobuf.Timestamp last_updated = 2; + } + + message DynamicRouteConfig { + // This is the per-resource version information. This version is currently taken from the + // :ref:`version_info ` field at the time that + // the route configuration was loaded. + string version_info = 1; + + // The route config. + envoy.api.v3alpha.RouteConfiguration route_config = 2; + + // The timestamp when the Route was last updated. + google.protobuf.Timestamp last_updated = 3; + } + + // The statically loaded route configs. + repeated StaticRouteConfig static_route_configs = 2; + + // The dynamically loaded route configs. + repeated DynamicRouteConfig dynamic_route_configs = 3; +} + +// Envoy's scoped RDS implementation fills this message with all currently loaded route +// configuration scopes (defined via ScopedRouteConfigurationsSet protos). This message lists both +// the scopes defined inline with the higher order object (i.e., the HttpConnectionManager) and the +// dynamically obtained scopes via the SRDS API. +message ScopedRoutesConfigDump { + message InlineScopedRouteConfigs { + // The name assigned to the scoped route configurations. + string name = 1; + + // The scoped route configurations. + repeated envoy.api.v3alpha.ScopedRouteConfiguration scoped_route_configs = 2; + + // The timestamp when the scoped route config set was last updated. + google.protobuf.Timestamp last_updated = 3; + } + + message DynamicScopedRouteConfigs { + // The name assigned to the scoped route configurations. + string name = 1; + + // This is the per-resource version information. This version is currently taken from the + // :ref:`version_info ` field at the time that + // the scoped routes configuration was loaded. + string version_info = 2; + + // The scoped route configurations. + repeated envoy.api.v3alpha.ScopedRouteConfiguration scoped_route_configs = 3; + + // The timestamp when the scoped route config set was last updated. + google.protobuf.Timestamp last_updated = 4; + } + + // The statically loaded scoped route configs. + repeated InlineScopedRouteConfigs inline_scoped_route_configs = 1; + + // The dynamically loaded scoped route configs. + repeated DynamicScopedRouteConfigs dynamic_scoped_route_configs = 2; +} + +// Envoys SDS implementation fills this message with all secrets fetched dynamically via SDS. +message SecretsConfigDump { + // DynamicSecret contains secret information fetched via SDS. + message DynamicSecret { + // The name assigned to the secret. + string name = 1; + + // This is the per-resource version information. + string version_info = 2; + + // The timestamp when the secret was last updated. + google.protobuf.Timestamp last_updated = 3; + + // The actual secret information. + // Security sensitive information is redacted (replaced with "[redacted]") for + // private keys and passwords in TLS certificates. + envoy.api.v3alpha.auth.Secret secret = 4; + } + + // StaticSecret specifies statically loaded secret in bootstrap. + message StaticSecret { + // The name assigned to the secret. + string name = 1; + + // The timestamp when the secret was last updated. + google.protobuf.Timestamp last_updated = 2; + + // The actual secret information. + // Security sensitive information is redacted (replaced with "[redacted]") for + // private keys and passwords in TLS certificates. + envoy.api.v3alpha.auth.Secret secret = 3; + } + + // The statically loaded secrets. + repeated StaticSecret static_secrets = 1; + + // The dynamically loaded active secrets. These are secrets that are available to service + // clusters or listeners. + repeated DynamicSecret dynamic_active_secrets = 2; + + // The dynamically loaded warming secrets. These are secrets that are currently undergoing + // warming in preparation to service clusters or listeners. + repeated DynamicSecret dynamic_warming_secrets = 3; +} diff --git a/api/envoy/admin/v3alpha/listeners.proto b/api/envoy/admin/v3alpha/listeners.proto new file mode 100644 index 0000000000..5e4d121b1f --- /dev/null +++ b/api/envoy/admin/v3alpha/listeners.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "ListenersProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; + +// [#protodoc-title: Listeners] + +// Admin endpoint uses this wrapper for `/listeners` to display listener status information. +// See :ref:`/listeners ` for more information. +message Listeners { + // List of listener statuses. + repeated ListenerStatus listener_statuses = 1; +} + +// Details an individual listener's current status. +message ListenerStatus { + // Name of the listener + string name = 1; + + // The actual local address that the listener is listening on. If a listener was configured + // to listen on port 0, then this address has the port that was allocated by the OS. + envoy.api.v3alpha.core.Address local_address = 2; +} diff --git a/api/envoy/admin/v3alpha/memory.proto b/api/envoy/admin/v3alpha/memory.proto new file mode 100644 index 0000000000..4c17be034e --- /dev/null +++ b/api/envoy/admin/v3alpha/memory.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "MemoryProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +// [#protodoc-title: Memory] + +// Proto representation of the internal memory consumption of an Envoy instance. These represent +// values extracted from an internal TCMalloc instance. For more information, see the section of the +// docs entitled ["Generic Tcmalloc Status"](https://gperftools.github.io/gperftools/tcmalloc.html). +message Memory { + + // The number of bytes allocated by the heap for Envoy. This is an alias for + // `generic.current_allocated_bytes`. + uint64 allocated = 1; + + // The number of bytes reserved by the heap but not necessarily allocated. This is an alias for + // `generic.heap_size`. + uint64 heap_size = 2; + + // The number of bytes in free, unmapped pages in the page heap. These bytes always count towards + // virtual memory usage, and depending on the OS, typically do not count towards physical memory + // usage. This is an alias for `tcmalloc.pageheap_unmapped_bytes`. + uint64 pageheap_unmapped = 3; + + // The number of bytes in free, mapped pages in the page heap. These bytes always count towards + // virtual memory usage, and unless the underlying memory is swapped out by the OS, they also + // count towards physical memory usage. This is an alias for `tcmalloc.pageheap_free_bytes`. + uint64 pageheap_free = 4; + + // The amount of memory used by the TCMalloc thread caches (for small objects). This is an alias + // for `tcmalloc.current_total_thread_cache_bytes`. + uint64 total_thread_cache = 5; +} diff --git a/api/envoy/admin/v3alpha/metrics.proto b/api/envoy/admin/v3alpha/metrics.proto new file mode 100644 index 0000000000..5a52ff2648 --- /dev/null +++ b/api/envoy/admin/v3alpha/metrics.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "MetricsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +// [#protodoc-title: Metrics] + +// Proto representation of an Envoy Counter or Gauge value. +message SimpleMetric { + enum Type { + COUNTER = 0; + GAUGE = 1; + } + + // Type of the metric represented. + Type type = 1; + + // Current metric value. + uint64 value = 2; + + // Name of the metric. + string name = 3; +} diff --git a/api/envoy/admin/v3alpha/mutex_stats.proto b/api/envoy/admin/v3alpha/mutex_stats.proto new file mode 100644 index 0000000000..72350dea8d --- /dev/null +++ b/api/envoy/admin/v3alpha/mutex_stats.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "MutexStatsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +// [#protodoc-title: MutexStats] + +// Proto representation of the statistics collected upon absl::Mutex contention, if Envoy is run +// under :option:`--enable-mutex-tracing`. For more information, see the `absl::Mutex` +// [docs](https://abseil.io/about/design/mutex#extra-features). +// +// *NB*: The wait cycles below are measured by `absl::base_internal::CycleClock`, and may not +// correspond to core clock frequency. For more information, see the `CycleClock` +// [docs](https://github.com/abseil/abseil-cpp/blob/master/absl/base/internal/cycleclock.h). +message MutexStats { + + // The number of individual mutex contentions which have occurred since startup. + uint64 num_contentions = 1; + + // The length of the current contention wait cycle. + uint64 current_wait_cycles = 2; + + // The lifetime total of all contention wait cycles. + uint64 lifetime_wait_cycles = 3; +} diff --git a/api/envoy/admin/v3alpha/server_info.proto b/api/envoy/admin/v3alpha/server_info.proto new file mode 100644 index 0000000000..a259a87f16 --- /dev/null +++ b/api/envoy/admin/v3alpha/server_info.proto @@ -0,0 +1,137 @@ +syntax = "proto3"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "ServerInfoProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +import "google/protobuf/duration.proto"; + +// [#protodoc-title: Server State] + +// Proto representation of the value returned by /server_info, containing +// server version/server status information. +message ServerInfo { + // Server version. + string version = 1; + + enum State { + // Server is live and serving traffic. + LIVE = 0; + // Server is draining listeners in response to external health checks failing. + DRAINING = 1; + // Server has not yet completed cluster manager initialization. + PRE_INITIALIZING = 2; + // Server is running the cluster manager initialization callbacks (e.g., RDS). + INITIALIZING = 3; + } + + // State of the server. + State state = 2; + + // Uptime since current epoch was started. + google.protobuf.Duration uptime_current_epoch = 3; + + // Uptime since the start of the first epoch. + google.protobuf.Duration uptime_all_epochs = 4; + + // Hot restart version. + string hot_restart_version = 5; + + // Command line options the server is currently running with. + CommandLineOptions command_line_options = 6; +} + +message CommandLineOptions { + // See :option:`--base-id` for details. + uint64 base_id = 1; + + // See :option:`--concurrency` for details. + uint32 concurrency = 2; + + // See :option:`--config-path` for details. + string config_path = 3; + + // See :option:`--config-yaml` for details. + string config_yaml = 4; + + // See :option:`--allow-unknown-static-fields` for details. + bool allow_unknown_static_fields = 5; + + // See :option:`--reject-unknown-dynamic-fields` for details. + bool reject_unknown_dynamic_fields = 26; + + // See :option:`--admin-address-path` for details. + string admin_address_path = 6; + + enum IpVersion { + v4 = 0; + v6 = 1; + } + + // See :option:`--local-address-ip-version` for details. + IpVersion local_address_ip_version = 7; + + // See :option:`--log-level` for details. + string log_level = 8; + + // See :option:`--component-log-level` for details. + string component_log_level = 9; + + // See :option:`--log-format` for details. + string log_format = 10; + + // See :option:`--log-path` for details. + string log_path = 11; + + reserved 12; + + // See :option:`--service-cluster` for details. + string service_cluster = 13; + + // See :option:`--service-node` for details. + string service_node = 14; + + // See :option:`--service-zone` for details. + string service_zone = 15; + + // See :option:`--file-flush-interval-msec` for details. + google.protobuf.Duration file_flush_interval = 16; + + // See :option:`--drain-time-s` for details. + google.protobuf.Duration drain_time = 17; + + // See :option:`--parent-shutdown-time-s` for details. + google.protobuf.Duration parent_shutdown_time = 18; + + enum Mode { + // Validate configs and then serve traffic normally. + Serve = 0; + + // Validate configs and exit. + Validate = 1; + + // Completely load and initialize the config, and then exit without running the listener loop. + InitOnly = 2; + } + + // See :option:`--mode` for details. + Mode mode = 19; + + // max_stats and max_obj_name_len are now unused and have no effect. + uint64 max_stats = 20 [deprecated = true]; + uint64 max_obj_name_len = 21 [deprecated = true]; + + // See :option:`--disable-hot-restart` for details. + bool disable_hot_restart = 22; + + // See :option:`--enable-mutex-tracing` for details. + bool enable_mutex_tracing = 23; + + // See :option:`--restart-epoch` for details. + uint32 restart_epoch = 24; + + // See :option:`--cpuset-threads` for details. + bool cpuset_threads = 25; +} diff --git a/api/envoy/admin/v3alpha/tap.proto b/api/envoy/admin/v3alpha/tap.proto new file mode 100644 index 0000000000..b6fd6a85f5 --- /dev/null +++ b/api/envoy/admin/v3alpha/tap.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +import "envoy/service/tap/v3alpha/common.proto"; +import "validate/validate.proto"; + +package envoy.admin.v3alpha; + +option java_outer_classname = "TapProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.admin.v3alpha"; + +// The /tap admin request body that is used to configure an active tap session. +message TapRequest { + // The opaque configuration ID used to match the configuration to a loaded extension. + // A tap extension configures a similar opaque ID that is used to match. + string config_id = 1 [(validate.rules).string.min_bytes = 1]; + + // The tap configuration to load. + service.tap.v3alpha.TapConfig tap_config = 2 [(validate.rules).message.required = true]; +} diff --git a/api/envoy/api/v2/BUILD b/api/envoy/api/v2/BUILD index 48ddb2e130..72fcb5f562 100644 --- a/api/envoy/api/v2/BUILD +++ b/api/envoy/api/v2/BUILD @@ -1,4 +1,4 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 @@ -16,6 +16,21 @@ package_group( ], ) +api_proto_package( + name = "v2", + has_services = True, + deps = [ + "//envoy/api/v2/auth", + "//envoy/api/v2/cluster", + "//envoy/api/v2/core", + "//envoy/api/v2/endpoint:pkg", + "//envoy/api/v2/listener:pkg", + "//envoy/api/v2/ratelimit:pkg", + "//envoy/api/v2/route:pkg", + "//envoy/type", + ], +) + api_proto_library_internal( name = "discovery", srcs = ["discovery.proto"], @@ -23,12 +38,6 @@ api_proto_library_internal( deps = ["//envoy/api/v2/core:base"], ) -api_go_proto_library( - name = "discovery", - proto = ":discovery", - deps = ["//envoy/api/v2/core:base_go_proto"], -) - api_proto_library_internal( name = "eds", srcs = ["eds.proto"], @@ -44,19 +53,6 @@ api_proto_library_internal( ], ) -api_go_grpc_library( - name = "eds", - proto = ":eds", - deps = [ - ":discovery_go_proto", - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:health_check_go_proto", - "//envoy/api/v2/endpoint:endpoint_go_proto", - "//envoy/type:percent_go_proto", - ], -) - api_proto_library_internal( name = "cds", srcs = ["cds.proto"], @@ -79,26 +75,6 @@ api_proto_library_internal( ], ) -api_go_grpc_library( - name = "cds", - proto = ":cds", - deps = [ - ":discovery_go_proto", - ":eds_go_grpc", - "//envoy/api/v2/auth:cert_go_proto", - "//envoy/api/v2/cluster:circuit_breaker_go_proto", - "//envoy/api/v2/cluster:filter_go_proto", - "//envoy/api/v2/cluster:outlier_detection_go_proto", - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:config_source_go_proto", - "//envoy/api/v2/core:health_check_go_proto", - "//envoy/api/v2/core:protocol_go_proto", - "//envoy/api/v2/endpoint:endpoint_go_proto", - "//envoy/type:percent_go_proto", - ], -) - api_proto_library_internal( name = "lds", srcs = ["lds.proto"], @@ -109,17 +85,7 @@ api_proto_library_internal( "//envoy/api/v2/core:address", "//envoy/api/v2/core:base", "//envoy/api/v2/listener", - ], -) - -api_go_grpc_library( - name = "lds", - proto = ":lds", - deps = [ - ":discovery_go_proto", - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/listener:listener_go_proto", + "//envoy/api/v2/listener:udp_listener_config", ], ) @@ -136,17 +102,6 @@ api_proto_library_internal( ], ) -api_go_grpc_library( - name = "rds", - proto = ":rds", - deps = [ - ":discovery_go_proto", - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:config_source_go_proto", - "//envoy/api/v2/route:route_go_proto", - ], -) - api_proto_library_internal( name = "srds", srcs = ["srds.proto"], @@ -158,12 +113,3 @@ api_proto_library_internal( "//envoy/api/v2/route", ], ) - -api_go_grpc_library( - name = "srds", - proto = ":srds", - deps = [ - ":discovery_go_proto", - "//envoy/api/v2/core:base_go_proto", - ], -) diff --git a/api/envoy/api/v2/auth/BUILD b/api/envoy/api/v2/auth/BUILD index acc28aacff..bb3951fb95 100644 --- a/api/envoy/api/v2/auth/BUILD +++ b/api/envoy/api/v2/auth/BUILD @@ -1,4 +1,4 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 @@ -15,6 +15,13 @@ package_group( ], ) +api_proto_package( + name = "auth", + deps = [ + "//envoy/api/v2/core", + ], +) + api_proto_library_internal( name = "cert", srcs = ["cert.proto"], @@ -24,12 +31,3 @@ api_proto_library_internal( "//envoy/api/v2/core:config_source", ], ) - -api_go_proto_library( - name = "cert", - proto = ":cert", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:config_source_go_proto", - ], -) diff --git a/api/envoy/api/v2/auth/cert.proto b/api/envoy/api/v2/auth/cert.proto index 526caf2928..0f33126820 100644 --- a/api/envoy/api/v2/auth/cert.proto +++ b/api/envoy/api/v2/auth/cert.proto @@ -5,17 +5,15 @@ package envoy.api.v2.auth; option java_outer_classname = "CertProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.auth"; -option go_package = "auth"; import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/core/config_source.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Common TLS configuration] @@ -102,6 +100,22 @@ message TlsParameters { repeated string ecdh_curves = 4; } +// BoringSSL private key method configuration. The private key methods are used for external +// (potentially asynchronous) signing and decryption operations. Some use cases for private key +// methods would be TPM support and TLS acceleration. +message PrivateKeyProvider { + // Private key method provider name. The name must match a + // supported private key method provider type. + string provider_name = 1 [(validate.rules).string.min_bytes = 1]; + + // Private key method provider specific configuration. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} + message TlsCertificate { // The TLS certificate chain. core.DataSource certificate_chain = 1; @@ -109,6 +123,15 @@ message TlsCertificate { // The TLS private key. core.DataSource private_key = 2; + // BoringSSL private key method provider. This is an alternative to :ref:`private_key + // ` field. This can't be + // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key + // ` and + // :ref:`private_key_provider + // ` fields will result in an + // error. + PrivateKeyProvider private_key_provider = 6; + // The password to decrypt the TLS private key. If this field is not set, it is assumed that the // TLS private key is not password encrypted. core.DataSource password = 3; diff --git a/api/envoy/api/v2/cds.proto b/api/envoy/api/v2/cds.proto index 647a501506..85e9a3827c 100644 --- a/api/envoy/api/v2/cds.proto +++ b/api/envoy/api/v2/cds.proto @@ -28,10 +28,6 @@ import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; -option (gogoproto.stable_marshaler_all) = true; // Return list of all clusters this proxy will load balance to. service ClusterDiscoveryService { @@ -52,7 +48,7 @@ service ClusterDiscoveryService { // [#protodoc-title: Clusters] // Configuration for a single upstream cluster. -// [#comment:next free field: 41] +// [#comment:next free field: 42] message Cluster { // Supplies the name of the cluster which must be unique across all clusters. // The cluster name is used when emitting @@ -127,8 +123,7 @@ message Cluster { EdsClusterConfig eds_cluster_config = 3; // The timeout for new network connections to hosts in the cluster. - google.protobuf.Duration connect_timeout = 4 - [(validate.rules).duration.gt = {}, (gogoproto.stdduration) = true]; + google.protobuf.Duration connect_timeout = 4 [(validate.rules).duration.gt = {}]; // Soft limit on size of the cluster’s connections read and write buffers. If // unspecified, an implementation defined default is applied (1MiB). @@ -175,6 +170,13 @@ message Cluster { // specific load balancer. Consult the configured cluster's documentation for whether to set // this option or not. CLUSTER_PROVIDED = 6; + + // [#not-implemented-hide:] Use the new :ref:`load_balancing_policy + // ` field to determine the LB policy. + // [#next-major-version: In the v3 API, we should consider deprecating the lb_policy field + // and instead using the new load_balancing_policy field as the one and only mechanism for + // configuring this.] + LOAD_BALANCING_POLICY_CONFIG = 7; } // The :ref:`load balancer type ` to use // when picking a host in the cluster. @@ -274,8 +276,7 @@ message Cluster { // :ref:`STRICT_DNS` // and :ref:`LOGICAL_DNS` // this setting is ignored. - google.protobuf.Duration dns_refresh_rate = 16 - [(validate.rules).duration.gt = {}, (gogoproto.stdduration) = true]; + google.protobuf.Duration dns_refresh_rate = 16 [(validate.rules).duration.gt = {}]; // Optional configuration for setting cluster's DNS refresh rate. If the value is set to true, // cluster's DNS refresh rate will be set to resource record's TTL which comes from DNS @@ -333,8 +334,7 @@ message Cluster { // value defaults to 5000ms. For cluster types other than // :ref:`ORIGINAL_DST` // this setting is ignored. - google.protobuf.Duration cleanup_interval = 20 - [(validate.rules).duration.gt = {}, (gogoproto.stdduration) = true]; + google.protobuf.Duration cleanup_interval = 20 [(validate.rules).duration.gt = {}]; // Optional configuration used to bind newly established upstream connections. // This overrides any bind_config specified in the bootstrap proto. @@ -539,6 +539,12 @@ message Cluster { // * :ref:`runtime values `. // * :ref:`Zone aware routing support `. google.protobuf.UInt64Value min_cluster_size = 2; + + // If set to true, Envoy will not consider any hosts when the cluster is in :ref:`panic + // mode`. Instead, the cluster will fail all + // requests as if all hosts are unhealthy. This can help avoid potentially overwhelming a + // failing service. + bool fail_traffic_on_panic = 3; } // Configuration for :ref:`locality weighted load balancing // ` @@ -553,7 +559,8 @@ message Cluster { // the first update happens. This is useful for big clusters, with potentially noisy deploys // that might trigger excessive CPU usage due to a constant stream of healthcheck state changes // or metadata updates. The first set of updates to be seen apply immediately (e.g.: a new - // cluster). + // cluster). Please always keep in mind that the use of sandbox technologies may change this + // behavior. // // If this is not set, we default to a merge window of 1000ms. To disable it, set the merge // window to 0. @@ -583,6 +590,10 @@ message Cluster { // If panic mode is triggered, new hosts are still eligible for traffic; they simply do not // contribute to the calculation when deciding whether panic mode is enabled or not. bool ignore_new_hosts_until_first_hc = 5; + + // If set to `true`, the cluster manager will drain all existing + // connections to upstream hosts whenever hosts are added or removed from the cluster. + bool close_connections_on_host_set_change = 6; } // Common configuration for all load balancer implementations. @@ -636,10 +647,51 @@ message Cluster { // checking before removing it from the cluster. bool drain_connections_on_host_removal = 32; - // An optional list of network filters that make up the filter chain for - // outgoing connections made by the cluster. Order matters as the filters are - // processed sequentially as connection events happen. + // An (optional) network filter chain, listed in the order the filters should be applied. + // The chain will be applied to all outgoing connections that Envoy makes to the upstream + // servers of this cluster. repeated cluster.Filter filters = 40; + + // [#not-implemented-hide:] New mechanism for LB policy configuration. Used only if the + // :ref:`lb_policy` field has the value + // :ref:`LOAD_BALANCING_POLICY_CONFIG`. + LoadBalancingPolicy load_balancing_policy = 41; +} + +// [#not-implemented-hide:] Extensible load balancing policy configuration. +// +// Every LB policy defined via this mechanism will be identified via a unique name using reverse +// DNS notation. If the policy needs configuration parameters, it must define a message for its +// own configuration, which will be stored in the config field. The name of the policy will tell +// clients which type of message they should expect to see in the config field. +// +// Note that there are cases where it is useful to be able to independently select LB policies +// for choosing a locality and for choosing an endpoint within that locality. For example, a +// given deployment may always use the same policy to choose the locality, but for choosing the +// endpoint within the locality, some clusters may use weighted-round-robin, while others may +// use some sort of session-based balancing. +// +// This can be accomplished via hierarchical LB policies, where the parent LB policy creates a +// child LB policy for each locality. For each request, the parent chooses the locality and then +// delegates to the child policy for that locality to choose the endpoint within the locality. +// +// To facilitate this, the config message for the top-level LB policy may include a field of +// type LoadBalancingPolicy that specifies the child policy. +// +// [#proto-status: experimental] +message LoadBalancingPolicy { + message Policy { + // Required. The name of the LB policy. + string name = 1; + // Optional config for the LB policy. + // No more than one of these two fields may be populated. + google.protobuf.Struct config = 2; + google.protobuf.Any typed_config = 3; + } + // Each client will iterate over the list in order and stop at the first policy that it + // supports. This provides a mechanism for starting to use new LB policies that are not yet + // supported by all clients. + repeated Policy policies = 1; } // An extensible structure containing the address Envoy should bind to when diff --git a/api/envoy/api/v2/cluster/BUILD b/api/envoy/api/v2/cluster/BUILD index 5589905d85..baf9a4bfde 100644 --- a/api/envoy/api/v2/cluster/BUILD +++ b/api/envoy/api/v2/cluster/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + name = "cluster", + deps = [ + "//envoy/api/v2/core", + ], +) + api_proto_library_internal( name = "circuit_breaker", srcs = ["circuit_breaker.proto"], @@ -13,14 +20,6 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "circuit_breaker", - proto = ":circuit_breaker", - deps = [ - "//envoy/api/v2/core:base_go_proto", - ], -) - api_proto_library_internal( name = "outlier_detection", srcs = ["outlier_detection.proto"], @@ -29,11 +28,6 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "outlier_detection", - proto = ":outlier_detection", -) - api_proto_library_internal( name = "filter", srcs = ["filter.proto"], @@ -41,8 +35,3 @@ api_proto_library_internal( "//envoy/api/v2:__pkg__", ], ) - -api_go_proto_library( - name = "filter", - proto = ":filter", -) diff --git a/api/envoy/api/v2/cluster/circuit_breaker.proto b/api/envoy/api/v2/cluster/circuit_breaker.proto index bc2bcf2548..e36677c89b 100644 --- a/api/envoy/api/v2/cluster/circuit_breaker.proto +++ b/api/envoy/api/v2/cluster/circuit_breaker.proto @@ -5,7 +5,6 @@ package envoy.api.v2.cluster; option java_outer_classname = "CircuitBreakerProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.cluster"; -option go_package = "cluster"; option csharp_namespace = "Envoy.Api.V2.ClusterNS"; option ruby_package = "Envoy.Api.V2.ClusterNS"; @@ -13,10 +12,6 @@ import "envoy/api/v2/core/base.proto"; import "google/protobuf/wrappers.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; - // [#protodoc-title: Circuit breakers] // :ref:`Circuit breaking` settings can be diff --git a/api/envoy/api/v2/cluster/filter.proto b/api/envoy/api/v2/cluster/filter.proto index 8a287b3997..94c6839139 100644 --- a/api/envoy/api/v2/cluster/filter.proto +++ b/api/envoy/api/v2/cluster/filter.proto @@ -11,9 +11,6 @@ option ruby_package = "Envoy.Api.V2.ClusterNS"; import "google/protobuf/any.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Upstream filters] // diff --git a/api/envoy/api/v2/cluster/outlier_detection.proto b/api/envoy/api/v2/cluster/outlier_detection.proto index 69feb60e23..d457c8165f 100644 --- a/api/envoy/api/v2/cluster/outlier_detection.proto +++ b/api/envoy/api/v2/cluster/outlier_detection.proto @@ -12,9 +12,6 @@ import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Outlier detection] @@ -114,4 +111,34 @@ message OutlierDetection { // is set to true. google.protobuf.UInt32Value enforcing_local_origin_success_rate = 15 [(validate.rules).uint32.lte = 100]; + + // The failure percentage to use when determining failure percentage-based outlier detection. If + // the failure percentage of a given host is greater than or equal to this value, it will be + // ejected. Defaults to 85. + google.protobuf.UInt32Value failure_percentage_threshold = 16 [(validate.rules).uint32.lte = 100]; + + // The % chance that a host will be actually ejected when an outlier status is detected through + // failure percentage statistics. This setting can be used to disable ejection or to ramp it up + // slowly. Defaults to 0. + // + // [#next-major-version: setting this without setting failure_percentage_threshold should be + // invalid in v4.] + google.protobuf.UInt32Value enforcing_failure_percentage = 17 [(validate.rules).uint32.lte = 100]; + + // The % chance that a host will be actually ejected when an outlier status is detected through + // local-origin failure percentage statistics. This setting can be used to disable ejection or to + // ramp it up slowly. Defaults to 0. + google.protobuf.UInt32Value enforcing_failure_percentage_local_origin = 18 + [(validate.rules).uint32.lte = 100]; + + // The minimum number of hosts in a cluster in order to perform failure percentage-based ejection. + // If the total number of hosts in the cluster is less than this value, failure percentage-based + // ejection will not be performed. Defaults to 5. + google.protobuf.UInt32Value failure_percentage_minimum_hosts = 19; + + // The minimum number of total requests that must be collected in one interval (as defined by the + // interval duration above) to perform failure percentage-based ejection for this host. If the + // volume is lower than this setting, failure percentage-based ejection will not be performed for + // this host. Defaults to 50. + google.protobuf.UInt32Value failure_percentage_request_volume = 20; } diff --git a/api/envoy/api/v2/core/BUILD b/api/envoy/api/v2/core/BUILD index b3d2be2301..01234d07b1 100644 --- a/api/envoy/api/v2/core/BUILD +++ b/api/envoy/api/v2/core/BUILD @@ -1,4 +1,4 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 @@ -16,6 +16,13 @@ package_group( ], ) +api_proto_package( + name = "core", + deps = [ + "//envoy/type", + ], +) + api_proto_library_internal( name = "address", srcs = ["address.proto"], @@ -25,12 +32,6 @@ api_proto_library_internal( deps = [":base"], ) -api_go_proto_library( - name = "address", - proto = ":address", - deps = [":base_go_proto"], -) - api_proto_library_internal( name = "base", srcs = ["base.proto"], @@ -43,15 +44,6 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "base", - proto = ":base", - deps = [ - ":http_uri_go_proto", - "//envoy/type:percent_go_proto", - ], -) - api_proto_library_internal( name = "health_check", srcs = ["health_check.proto"], @@ -64,15 +56,6 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "health_check", - proto = ":health_check", - deps = [ - ":base_go_proto", - "//envoy/type:range_go_proto", - ], -) - api_proto_library_internal( name = "config_source", srcs = ["config_source.proto"], @@ -85,20 +68,6 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "config_source", - proto = ":config_source", - deps = [ - ":base_go_proto", - ":grpc_service_go_proto", - ], -) - -api_go_proto_library( - name = "http_uri", - proto = ":http_uri", -) - api_proto_library_internal( name = "http_uri", srcs = ["http_uri.proto"], @@ -116,12 +85,6 @@ api_proto_library_internal( deps = [":base"], ) -api_go_proto_library( - name = "grpc_service", - proto = ":grpc_service", - deps = [":base_go_proto"], -) - api_proto_library_internal( name = "protocol", srcs = ["protocol.proto"], @@ -129,8 +92,3 @@ api_proto_library_internal( ":friends", ], ) - -api_go_proto_library( - name = "protocol", - proto = ":protocol", -) diff --git a/api/envoy/api/v2/core/address.proto b/api/envoy/api/v2/core/address.proto index 88689e2648..362395577f 100644 --- a/api/envoy/api/v2/core/address.proto +++ b/api/envoy/api/v2/core/address.proto @@ -11,9 +11,6 @@ import "envoy/api/v2/core/base.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Network addresses] @@ -27,7 +24,6 @@ message Pipe { message SocketAddress { enum Protocol { - option (gogoproto.goproto_enum_prefix) = false; TCP = 0; // [#not-implemented-hide:] UDP = 1; diff --git a/api/envoy/api/v2/core/base.proto b/api/envoy/api/v2/core/base.proto index 2553fe04de..2a778f19af 100644 --- a/api/envoy/api/v2/core/base.proto +++ b/api/envoy/api/v2/core/base.proto @@ -5,7 +5,6 @@ package envoy.api.v2.core; option java_outer_classname = "BaseProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.core"; -option go_package = "core"; import "envoy/api/v2/core/http_uri.proto"; @@ -14,13 +13,9 @@ import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; import "envoy/type/percent.proto"; -option (gogoproto.equal_all) = true; -option (gogoproto.stable_marshaler_all) = true; - // [#protodoc-title: Common types] // Identifies location of where either Envoy runs or where upstream hosts run. @@ -132,7 +127,6 @@ enum RoutingPriority { // HTTP request method. enum RequestMethod { - option (gogoproto.goproto_enum_prefix) = false; METHOD_UNSPECIFIED = 0; GET = 1; HEAD = 2; @@ -248,7 +242,6 @@ message SocketOption { bytes buf_value = 5; } enum SocketState { - option (gogoproto.goproto_enum_prefix) = false; // Socket options are applied after socket creation but before binding the socket to a port STATE_PREBIND = 0; // Socket options are applied after binding the socket to a port but before calling listen() diff --git a/api/envoy/api/v2/core/config_source.proto b/api/envoy/api/v2/core/config_source.proto index 9b77f12f5b..d86a2104f7 100644 --- a/api/envoy/api/v2/core/config_source.proto +++ b/api/envoy/api/v2/core/config_source.proto @@ -12,9 +12,6 @@ import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Configuration sources] @@ -56,11 +53,10 @@ message ApiConfigSource { repeated GrpcService grpc_services = 4; // For REST APIs, the delay between successive polls. - google.protobuf.Duration refresh_delay = 3 [(gogoproto.stdduration) = true]; + google.protobuf.Duration refresh_delay = 3; // For REST APIs, the request timeout. If not set, a default value of 1s will be used. - google.protobuf.Duration request_timeout = 5 - [(validate.rules).duration.gt.seconds = 0, (gogoproto.stdduration) = true]; + google.protobuf.Duration request_timeout = 5 [(validate.rules).duration.gt.seconds = 0]; // For GRPC APIs, the rate limit settings. If present, discovery requests made by Envoy will be // rate limited. diff --git a/api/envoy/api/v2/core/grpc_service.proto b/api/envoy/api/v2/core/grpc_service.proto index 404791e1b3..705c61f5a1 100644 --- a/api/envoy/api/v2/core/grpc_service.proto +++ b/api/envoy/api/v2/core/grpc_service.proto @@ -14,9 +14,6 @@ import "google/protobuf/struct.proto"; import "google/protobuf/empty.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: gRPC services] diff --git a/api/envoy/api/v2/core/health_check.proto b/api/envoy/api/v2/core/health_check.proto index edbcef7b52..64396f8380 100644 --- a/api/envoy/api/v2/core/health_check.proto +++ b/api/envoy/api/v2/core/health_check.proto @@ -15,9 +15,6 @@ import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Health check] // * Health checking :ref:`architecture overview `. @@ -27,22 +24,16 @@ option (gogoproto.equal_all) = true; message HealthCheck { // The time to wait for a health check response. If the timeout is reached the // health check attempt will be considered a failure. - google.protobuf.Duration timeout = 1 [ - (validate.rules).duration = { - required: true, - gt: {seconds: 0} - }, - (gogoproto.stdduration) = true - ]; + google.protobuf.Duration timeout = 1 [(validate.rules).duration = { + required: true, + gt: {seconds: 0} + }]; // The interval between health checks. - google.protobuf.Duration interval = 2 [ - (validate.rules).duration = { - required: true, - gt: {seconds: 0} - }, - (gogoproto.stdduration) = true - ]; + google.protobuf.Duration interval = 2 [(validate.rules).duration = { + required: true, + gt: {seconds: 0} + }]; // An optional jitter amount in milliseconds. If specified, Envoy will start health // checking after for a random time in ms between 0 and initial_jitter. This only diff --git a/api/envoy/api/v2/core/http_uri.proto b/api/envoy/api/v2/core/http_uri.proto index 4c7a594f06..debaa41556 100644 --- a/api/envoy/api/v2/core/http_uri.proto +++ b/api/envoy/api/v2/core/http_uri.proto @@ -7,12 +7,9 @@ option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.core"; import "google/protobuf/duration.proto"; -import "gogoproto/gogo.proto"; import "validate/validate.proto"; -option (gogoproto.equal_all) = true; - // [#protodoc-title: HTTP Service URI ] // Envoy external URI descriptor @@ -46,9 +43,6 @@ message HttpUri { } // Sets the maximum duration in milliseconds that a response can take to arrive upon request. - google.protobuf.Duration timeout = 3 [ - (validate.rules).duration.gte = {}, - (validate.rules).duration.required = true, - (gogoproto.stdduration) = true - ]; + google.protobuf.Duration timeout = 3 + [(validate.rules).duration.gte = {}, (validate.rules).duration.required = true]; } diff --git a/api/envoy/api/v2/core/protocol.proto b/api/envoy/api/v2/core/protocol.proto index 88a82077a4..a318ba698b 100644 --- a/api/envoy/api/v2/core/protocol.proto +++ b/api/envoy/api/v2/core/protocol.proto @@ -12,9 +12,6 @@ import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Protocol options] @@ -27,7 +24,7 @@ message HttpProtocolOptions { // period in which there are no active requests. If not set, there is no idle timeout. When the // idle timeout is reached the connection will be closed. Note that request based timeouts mean // that HTTP/2 PINGs will not keep the connection alive. - google.protobuf.Duration idle_timeout = 1 [(gogoproto.stdduration) = true]; + google.protobuf.Duration idle_timeout = 1; } message Http1ProtocolOptions { diff --git a/api/envoy/api/v2/discovery.proto b/api/envoy/api/v2/discovery.proto index a3072a817a..2d04a24817 100644 --- a/api/envoy/api/v2/discovery.proto +++ b/api/envoy/api/v2/discovery.proto @@ -5,16 +5,11 @@ package envoy.api.v2; option java_outer_classname = "DiscoveryProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2"; -option go_package = "v2"; import "envoy/api/v2/core/base.proto"; import "google/protobuf/any.proto"; import "google/rpc/status.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; -option (gogoproto.stable_marshaler_all) = true; // [#protodoc-title: Common discovery API components] diff --git a/api/envoy/api/v2/eds.proto b/api/envoy/api/v2/eds.proto index d680ef7ea5..01982fbf6f 100644 --- a/api/envoy/api/v2/eds.proto +++ b/api/envoy/api/v2/eds.proto @@ -15,13 +15,9 @@ import "envoy/type/percent.proto"; import "google/api/annotations.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; import "google/protobuf/wrappers.proto"; import "google/protobuf/duration.proto"; -option (gogoproto.equal_all) = true; -option (gogoproto.stable_marshaler_all) = true; - // [#protodoc-title: EDS] // Endpoint discovery :ref:`architecture overview ` @@ -117,6 +113,17 @@ message ClusterLoadAssignment { // are considered stale and should be marked unhealthy. // Defaults to 0 which means endpoints never go stale. google.protobuf.Duration endpoint_stale_after = 4 [(validate.rules).duration.gt.seconds = 0]; + + // The flag to disable overprovisioning. If it is set to true, + // :ref:`overprovisioning factor + // ` will be ignored + // and Envoy will not perform graceful failover between priority levels or + // localities as endpoints become unhealthy. Otherwise Envoy will perform + // graceful failover as :ref:`overprovisioning factor + // ` suggests. + // [#next-major-version: Unify with overprovisioning config as a single message.] + // [#not-implemented-hide:] + bool disable_overprovisioning = 5; } // Load balancing policy settings. diff --git a/api/envoy/api/v2/endpoint/BUILD b/api/envoy/api/v2/endpoint/BUILD index 0dead0f570..a12db37309 100644 --- a/api/envoy/api/v2/endpoint/BUILD +++ b/api/envoy/api/v2/endpoint/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/auth", + "//envoy/api/v2/core", + ], +) + api_proto_library_internal( name = "endpoint", srcs = ["endpoint.proto"], @@ -16,19 +23,6 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "endpoint", - proto = ":endpoint", - deps = [ - "//envoy/api/v2/auth:cert_go_proto", - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:config_source_go_proto", - "//envoy/api/v2/core:health_check_go_proto", - "//envoy/api/v2/core:protocol_go_proto", - ], -) - api_proto_library_internal( name = "load_report", srcs = ["load_report.proto"], @@ -38,12 +32,3 @@ api_proto_library_internal( "//envoy/api/v2/core:base", ], ) - -api_go_proto_library( - name = "load_report", - proto = ":load_report", - deps = [ - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/core:base_go_proto", - ], -) diff --git a/api/envoy/api/v2/endpoint/endpoint.proto b/api/envoy/api/v2/endpoint/endpoint.proto index 7abb7ea5f3..7d614a26bb 100644 --- a/api/envoy/api/v2/endpoint/endpoint.proto +++ b/api/envoy/api/v2/endpoint/endpoint.proto @@ -5,7 +5,6 @@ package envoy.api.v2.endpoint; option java_outer_classname = "EndpointProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.endpoint"; -option go_package = "endpoint"; import "envoy/api/v2/core/address.proto"; import "envoy/api/v2/core/base.proto"; @@ -14,9 +13,6 @@ import "envoy/api/v2/core/health_check.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Endpoints] diff --git a/api/envoy/api/v2/endpoint/load_report.proto b/api/envoy/api/v2/endpoint/load_report.proto index df3fd6071e..b44313ba4e 100644 --- a/api/envoy/api/v2/endpoint/load_report.proto +++ b/api/envoy/api/v2/endpoint/load_report.proto @@ -13,7 +13,6 @@ import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // These are stats Envoy reports to GLB every so often. Report frequency is // defined by diff --git a/api/envoy/api/v2/lds.proto b/api/envoy/api/v2/lds.proto index 195401341c..aec4ad85ad 100644 --- a/api/envoy/api/v2/lds.proto +++ b/api/envoy/api/v2/lds.proto @@ -12,15 +12,13 @@ import "envoy/api/v2/core/address.proto"; import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/discovery.proto"; import "envoy/api/v2/listener/listener.proto"; +import "envoy/api/v2/listener/udp_listener_config.proto"; import "google/api/annotations.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Listener] // Listener :ref:`configuration overview ` @@ -44,7 +42,7 @@ service ListenerDiscoveryService { } } -// [#comment:next free field: 18] +// [#comment:next free field: 19] message Listener { // The unique name by which this listener is known. If no name is provided, // Envoy will allocate an internal UUID for the listener. If the listener is to be dynamically @@ -135,7 +133,7 @@ message Listener { // the accepted socket is closed without a connection being created unless // `continue_on_listener_filters_timeout` is set to true. Specify 0 to disable the // timeout. If not specified, a default timeout of 15s is used. - google.protobuf.Duration listener_filters_timeout = 15 [(gogoproto.stdduration) = true]; + google.protobuf.Duration listener_filters_timeout = 15; // Whether a connection should be created when listener filters timeout. Default is false. // @@ -194,4 +192,12 @@ message Listener { // Specifies the intended direction of the traffic relative to the local Envoy. core.TrafficDirection traffic_direction = 16; + + // If the protocol in the listener socket address in :ref:`protocol + // ` is :ref:'UDP + // `, this field specifies the actual udp listener to create, + // i.e. :ref:`udp_listener_name + // ` = "raw_udp_listener" for + // creating a packet-oriented UDP listener. If not present, treat it as "raw_udp_listener". + listener.UdpListenerConfig udp_listener_config = 18; } diff --git a/api/envoy/api/v2/listener/BUILD b/api/envoy/api/v2/listener/BUILD index 9eb0c0ec98..99a82254d1 100644 --- a/api/envoy/api/v2/listener/BUILD +++ b/api/envoy/api/v2/listener/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/auth", + "//envoy/api/v2/core", + ], +) + api_proto_library_internal( name = "listener", srcs = ["listener.proto"], @@ -13,12 +20,17 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "listener", - proto = ":listener", +api_proto_library_internal( + name = "udp_listener_config", + srcs = ["udp_listener_config.proto"], + visibility = ["//envoy/api/v2:friends"], deps = [ - "//envoy/api/v2/auth:cert_go_proto", - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/core:base_go_proto", + "//envoy/api/v2/core:base", ], ) + +api_proto_library_internal( + name = "quic_config", + srcs = ["quic_config.proto"], + visibility = ["//envoy/api/v2:friends"], +) diff --git a/api/envoy/api/v2/listener/listener.proto b/api/envoy/api/v2/listener/listener.proto index 77f753501f..f6dcecc708 100644 --- a/api/envoy/api/v2/listener/listener.proto +++ b/api/envoy/api/v2/listener/listener.proto @@ -5,7 +5,6 @@ package envoy.api.v2.listener; option java_outer_classname = "ListenerProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.listener"; -option go_package = "listener"; option csharp_namespace = "Envoy.Api.V2.ListenerNS"; option ruby_package = "Envoy::Api::V2::ListenerNS"; @@ -18,9 +17,6 @@ import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Listener components] // Listener :ref:`configuration overview ` @@ -188,6 +184,11 @@ message FilterChain { // See :ref:`base.TransportSocket` description. core.TransportSocket transport_socket = 6; + + // [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no + // name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter + // chain is to be dynamically updated or removed via FCDS a unique name must be provided. + string name = 7; } message ListenerFilter { diff --git a/api/envoy/api/v2/listener/quic_config.proto b/api/envoy/api/v2/listener/quic_config.proto new file mode 100644 index 0000000000..95ffc3cdf3 --- /dev/null +++ b/api/envoy/api/v2/listener/quic_config.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package envoy.api.v2.listener; + +option java_outer_classname = "ListenerProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v2.listener"; +option csharp_namespace = "Envoy.Api.V2.ListenerNS"; +option ruby_package = "Envoy::Api::V2::ListenerNS"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +// Configuration specific to the QUIC protocol. +// Next id: 4 +message QuicProtocolOptions { + // Maximum number of streams that the client can negotiate per connection. 100 + // if not specified. + google.protobuf.UInt32Value max_concurrent_streams = 1; + + // Maximum number of milliseconds that connection will be alive when there is + // no network activity. 300000ms if not specified. + google.protobuf.Duration idle_timeout = 2; + + // Connection timeout in milliseconds before the crypto handshake is finished. + // 20000ms if not specified. + google.protobuf.Duration crypto_handshake_timeout = 3; +} diff --git a/api/envoy/api/v2/listener/udp_listener_config.proto b/api/envoy/api/v2/listener/udp_listener_config.proto new file mode 100644 index 0000000000..28d8233f5f --- /dev/null +++ b/api/envoy/api/v2/listener/udp_listener_config.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package envoy.api.v2.listener; + +option java_outer_classname = "UdpListenerConfigProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v2.listener"; +option csharp_namespace = "Envoy.Api.V2.ListenerNS"; +option ruby_package = "Envoy::Api::V2::ListenerNS"; + +import "google/protobuf/struct.proto"; +import "google/protobuf/any.proto"; + +// [#protodoc-title: Udp Listener Config] +// Listener :ref:`configuration overview ` + +message UdpListenerConfig { + // Used to look up UDP listener factory, matches "raw_udp_listener" or + // "quic_listener" to create a specific udp listener. + // If not specified, treat as "raw_udp_listener". + string udp_listener_name = 1; + + // Used to create a specific listener factory. To some factory, e.g. + // "raw_udp_listener", config is not needed. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} diff --git a/api/envoy/api/v2/ratelimit/BUILD b/api/envoy/api/v2/ratelimit/BUILD index 5f2a920146..234a3b20f1 100644 --- a/api/envoy/api/v2/ratelimit/BUILD +++ b/api/envoy/api/v2/ratelimit/BUILD @@ -1,14 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "ratelimit", srcs = ["ratelimit.proto"], visibility = ["//envoy/api/v2:friends"], ) - -api_go_proto_library( - name = "ratelimit", - proto = ":ratelimit", -) diff --git a/api/envoy/api/v2/ratelimit/ratelimit.proto b/api/envoy/api/v2/ratelimit/ratelimit.proto index 8ebec71822..6f4cd62582 100644 --- a/api/envoy/api/v2/ratelimit/ratelimit.proto +++ b/api/envoy/api/v2/ratelimit/ratelimit.proto @@ -5,7 +5,6 @@ package envoy.api.v2.ratelimit; option java_outer_classname = "RatelimitProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.ratelimit"; -option go_package = "ratelimit"; import "validate/validate.proto"; diff --git a/api/envoy/api/v2/rds.proto b/api/envoy/api/v2/rds.proto index 2d82e4ad00..9fabaf28af 100644 --- a/api/envoy/api/v2/rds.proto +++ b/api/envoy/api/v2/rds.proto @@ -17,9 +17,6 @@ import "google/api/annotations.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: HTTP route configuration] // * Routing :ref:`architecture overview ` diff --git a/api/envoy/api/v2/route/BUILD b/api/envoy/api/v2/route/BUILD index 2fec56ae38..163281ca35 100644 --- a/api/envoy/api/v2/route/BUILD +++ b/api/envoy/api/v2/route/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/type", + "//envoy/type/matcher", + ], +) + api_proto_library_internal( name = "route", srcs = ["route.proto"], @@ -10,15 +18,7 @@ api_proto_library_internal( "//envoy/api/v2/core:base", "//envoy/type:percent", "//envoy/type:range", - ], -) - -api_go_proto_library( - name = "route", - proto = ":route", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "//envoy/type:percent_go_proto", - "//envoy/type:range_go_proto", + "//envoy/type/matcher:regex", + "//envoy/type/matcher:string", ], ) diff --git a/api/envoy/api/v2/route/route.proto b/api/envoy/api/v2/route/route.proto index 93d021ddb2..a967cdef7b 100644 --- a/api/envoy/api/v2/route/route.proto +++ b/api/envoy/api/v2/route/route.proto @@ -5,10 +5,11 @@ package envoy.api.v2.route; option java_outer_classname = "RouteProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.api.v2.route"; -option go_package = "route"; option java_generic_services = true; import "envoy/api/v2/core/base.proto"; +import "envoy/type/matcher/regex.proto"; +import "envoy/type/matcher/string.proto"; import "envoy/type/percent.proto"; import "envoy/type/range.proto"; @@ -18,10 +19,6 @@ import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; -option (gogoproto.stable_marshaler_all) = true; // [#protodoc-title: HTTP route] // * Routing :ref:`architecture overview ` @@ -349,7 +346,25 @@ 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 [(validate.rules).string.max_bytes = 1024]; + // + // .. attention:: + // This field has been deprecated in favor of `safe_regex` as it is not safe for use with + // untrusted input in all cases. + string regex = 3 [(validate.rules).string.max_bytes = 1024, deprecated = true]; + + // If specified, the route is a regular expression rule meaning that the + // regex must match the *:path* header once the query string is removed. The entire path + // (without the query string) must match the regex. The rule will not match if only a + // subsequence of the *:path* header matches the regex. + // + // [#next-major-version: In the v3 API we should redo how path specification works such + // that we utilize StringMatcher, and additionally have consistent options around whether we + // strip query strings, do a case sensitive match, etc. In the interim it will be too disruptive + // to deprecate the existing options. We should even consider whether we want to do away with + // path_specifier entirely and just rely on a set of header matchers which can already match + // on :path, etc. The issue with that is it is unclear how to generically deal with query string + // stripping. This needs more thought.] + type.matcher.RegexMatcher safe_regex = 10 [(validate.rules).message.required = true]; } // Indicates that prefix/path matching should be case insensitive. The default @@ -404,12 +419,24 @@ message CorsPolicy { // Specifies the origins that will be allowed to do CORS requests. // // An origin is allowed if either allow_origin or allow_origin_regex match. - repeated string allow_origin = 1; + // + // .. attention:: + // This field has been deprecated in favor of `allow_origin_string_match`. + repeated string allow_origin = 1 [deprecated = true]; // 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 [(validate.rules).repeated .items.string.max_bytes = 1024]; + // + // .. attention:: + // This field has been deprecated in favor of `allow_origin_string_match` as it is not safe for + // use with untrusted input in all cases. + repeated string allow_origin_regex = 8 + [(validate.rules).repeated .items.string.max_bytes = 1024, deprecated = true]; + + // Specifies string patterns that match allowed origins. An origin is allowed if any of the + // string matchers match. + repeated type.matcher.StringMatcher allow_origin_string_match = 11; // Specifies the content for the *access-control-allow-methods* header. string allow_methods = 2; @@ -571,7 +598,7 @@ message RouteAction { // :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, // :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the // :ref:`retry overview `. - google.protobuf.Duration timeout = 8 [(gogoproto.stdduration) = true]; + google.protobuf.Duration timeout = 8; // Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, // although the connection manager wide :ref:`stream_idle_timeout @@ -591,7 +618,7 @@ message RouteAction { // fires, the stream is terminated with a 408 Request Timeout error code if no // upstream response header has been received, otherwise a stream reset // occurs. - google.protobuf.Duration idle_timeout = 24 [(gogoproto.stdduration) = true]; + google.protobuf.Duration idle_timeout = 24; // Indicates that the route has a retry policy. Note that if this is set, // it'll take precedence over the virtual host level retry policy entirely @@ -701,7 +728,7 @@ message RouteAction { // If specified, a cookie with the TTL will be generated if the cookie is // not present. If the TTL is present and zero, the generated cookie will // be a session cookie. - google.protobuf.Duration ttl = 2 [(gogoproto.stdduration) = true]; + google.protobuf.Duration ttl = 2; // The name of the path for the cookie. If no path is specified here, no path // will be set for the cookie. @@ -780,7 +807,7 @@ message RouteAction { // :ref:`timeout ` or its default. // This can be used to prevent unexpected upstream request timeouts due to potentially long // time gaps between gRPC request and response in gRPC streaming mode. - google.protobuf.Duration max_grpc_timeout = 23 [(gogoproto.stdduration) = true]; + google.protobuf.Duration max_grpc_timeout = 23; // If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting // the provided duration from the header. This is useful in allowing Envoy to set its global @@ -789,7 +816,7 @@ message RouteAction { // The offset will only be applied if the provided grpc_timeout is greater than the offset. This // ensures that the offset will only ever decrease the timeout and never set it to 0 (meaning // infinity). - google.protobuf.Duration grpc_timeout_offset = 28 [(gogoproto.stdduration) = true]; + google.protobuf.Duration grpc_timeout_offset = 28; // Allows enabling and disabling upgrades on a per-route basis. // This overrides any enabled/disabled upgrade filter chain specified in the @@ -844,7 +871,7 @@ message RetryPolicy { // Consequently, when using a :ref:`5xx ` based // retry policy, a request that times out will not be retried as the total timeout budget // would have been exhausted. - google.protobuf.Duration per_try_timeout = 3 [(gogoproto.stdduration) = true]; + google.protobuf.Duration per_try_timeout = 3; message RetryPriority { string name = 1 [(validate.rules).string.min_bytes = 1]; @@ -888,20 +915,16 @@ message RetryPolicy { // than zero. Values less than 1 ms are rounded up to 1 ms. // See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's // back-off algorithm. - google.protobuf.Duration base_interval = 1 [ - (validate.rules).duration = { - required: true, - gt: {seconds: 0} - }, - (gogoproto.stdduration) = true - ]; + google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { + required: true, + gt: {seconds: 0} + }]; // Specifies the maximum interval between retries. This parameter is optional, but must be // greater than or equal to the `base_interval` if set. The default is 10 times the // `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion // of Envoy's back-off algorithm. - google.protobuf.Duration max_interval = 2 - [(validate.rules).duration.gt = {seconds: 0}, (gogoproto.stdduration) = true]; + google.protobuf.Duration max_interval = 2 [(validate.rules).duration.gt = {seconds: 0}]; } // Specifies parameters that control retry back off. This parameter is optional, in which case the @@ -1077,18 +1100,28 @@ 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, max_bytes: 1024}]; + // + // .. attention:: + // This field has been deprecated in favor of `headers` as it is not safe for use with + // untrusted input in all cases. + string pattern = 1 [(validate.rules).string.max_bytes = 1024, deprecated = true]; - // Specifies the name of the virtual cluster. The virtual cluster name as well + // Specifies a list of header matchers to use for matching requests. Each specified header must + // match. The pseudo-headers `:path` and `:method` can be used to match the request path and + // method, respectively. + repeated HeaderMatcher headers = 4; + + // 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 // router filter and are documented :ref:`here `. string name = 2 [(validate.rules).string.min_bytes = 1]; // Optionally specifies the HTTP method to match on. For example GET, PUT, // etc. - // [#comment:TODO(htuch): add (validate.rules).enum.defined_only = true once - // https://github.com/lyft/protoc-gen-validate/issues/42 is resolved.] - core.RequestMethod method = 3; + // + // .. attention:: + // This field has been deprecated in favor of `headers`. + core.RequestMethod method = 3 [deprecated = true]; } // Global rate limiting :ref:`architecture overview `. @@ -1248,6 +1281,7 @@ message RateLimit { // ` header will match, regardless of the header's // value. // +// [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] message HeaderMatcher { // Specifies the name of the header in the request. string name = 1 [(validate.rules).string.min_bytes = 1]; @@ -1273,7 +1307,16 @@ 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 [(validate.rules).string.max_bytes = 1024]; + // + // .. attention:: + // This field has been deprecated in favor of `safe_regex_match` as it is not safe for use + // with untrusted input in all cases. + string regex_match = 5 [(validate.rules).string.max_bytes = 1024, deprecated = true]; + + // If specified, this regex string is a regular expression rule which implies the entire request + // header value must match the regex. The rule will not match if only a subsequence of the + // request header value matches the regex. + type.matcher.RegexMatcher safe_regex_match = 11; // If specified, header match will be performed based on range. // The rule will match if the request header value is within this range. @@ -1328,11 +1371,25 @@ message QueryParameterMatcher { // 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 // key appears with a value (e.g., "?debug=true") or not (e.g., "?debug") - string value = 3; + // + // ..attention:: + // This field is deprecated. Use an `exact` match inside the `string_match` field. + string value = 3 [deprecated = true]; // Specifies whether the query parameter value is a regular expression. // Defaults to false. The entire query parameter value (i.e., the part to // the right of the equals sign in "key=value") must match the regex. // E.g., the regex "\d+$" will match "123" but not "a123" or "123a". - google.protobuf.BoolValue regex = 4; + // + // ..attention:: + // This field is deprecated. Use a `safe_regex` match inside the `string_match` field. + google.protobuf.BoolValue regex = 4 [deprecated = true]; + + oneof query_parameter_match_specifier { + // Specifies whether a query parameter value should match against a string. + type.matcher.StringMatcher string_match = 5 [(validate.rules).message.required = true]; + + // Specifies whether a query parameter should be present. + bool present_match = 6; + } } diff --git a/api/envoy/api/v2/srds.proto b/api/envoy/api/v2/srds.proto index 9038cb1e32..a51426af01 100644 --- a/api/envoy/api/v2/srds.proto +++ b/api/envoy/api/v2/srds.proto @@ -2,36 +2,25 @@ syntax = "proto3"; package envoy.api.v2; -option java_outer_classname = "SrdsProto"; -option java_package = "io.envoyproxy.envoy.api.v2"; -option java_multiple_files = true; -option java_generic_services = true; - import "envoy/api/v2/discovery.proto"; - import "google/api/annotations.proto"; - import "validate/validate.proto"; -import "gogoproto/gogo.proto"; -option (gogoproto.equal_all) = true; +option java_outer_classname = "SrdsProto"; +option java_package = "io.envoyproxy.envoy.api.v2"; +option java_multiple_files = true; +option java_generic_services = true; // [#protodoc-title: HTTP scoped routing configuration] // * Routing :ref:`architecture overview ` // -// .. attention:: -// -// The Scoped RDS API is not yet fully implemented and *should not* be enabled in -// :ref:`envoy_api_msg_config.filter.network.http_connection_manager.v2.HttpConnectionManager`. -// -// TODO(AndresGuedez): Update :ref:`arch_overview_http_routing` with scoped routing overview and -// configuration details. - // The Scoped Routes Discovery Service (SRDS) API distributes -// :ref:`ScopedRouteConfiguration` resources. Each -// ScopedRouteConfiguration resource represents a "routing scope" containing a mapping that allows -// the HTTP connection manager to dynamically assign a routing table (specified via -// a :ref:`RouteConfiguration` message) to each HTTP request. +// :ref:`ScopedRouteConfiguration` +// resources. Each ScopedRouteConfiguration resource represents a "routing +// scope" containing a mapping that allows the HTTP connection manager to +// dynamically assign a routing table (specified via a +// :ref:`RouteConfiguration` message) to each +// HTTP request. // [#proto-status: experimental] service ScopedRoutesDiscoveryService { rpc StreamScopedRoutes(stream DiscoveryRequest) returns (stream DiscoveryResponse) { @@ -52,9 +41,9 @@ service ScopedRoutesDiscoveryService { // :ref:`Key` to a // :ref:`envoy_api_msg_RouteConfiguration` (identified by its resource name). // -// The HTTP connection manager builds up a table consisting of these Key to RouteConfiguration -// mappings, and looks up the RouteConfiguration to use per request according to the algorithm -// specified in the +// The HTTP connection manager builds up a table consisting of these Key to +// RouteConfiguration mappings, and looks up the RouteConfiguration to use per +// request according to the algorithm specified in the // :ref:`scope_key_builder` // assigned to the HttpConnectionManager. // @@ -104,8 +93,8 @@ service ScopedRoutesDiscoveryService { // Host: foo.com // X-Route-Selector: vip=172.10.10.20 // -// would result in the routing table defined by the `route-config1` RouteConfiguration being -// assigned to the HTTP request/stream. +// would result in the routing table defined by the `route-config1` +// RouteConfiguration being assigned to the HTTP request/stream. // // [#comment:next free field: 4] // [#proto-status: experimental] @@ -115,8 +104,9 @@ message ScopedRouteConfiguration { // Specifies a key which is matched against the output of the // :ref:`scope_key_builder` - // specified in the HttpConnectionManager. The matching is done per HTTP request and is dependent - // on the order of the fragments contained in the Key. + // specified in the HttpConnectionManager. The matching is done per HTTP + // request and is dependent on the order of the fragments contained in the + // Key. message Key { message Fragment { oneof type { @@ -127,14 +117,15 @@ message ScopedRouteConfiguration { } } - // The ordered set of fragments to match against. The order must match the fragments in the - // corresponding + // The ordered set of fragments to match against. The order must match the + // fragments in the corresponding // :ref:`scope_key_builder`. repeated Fragment fragments = 1 [(validate.rules).repeated .min_items = 1]; } - // The resource name to use for a :ref:`envoy_api_msg_DiscoveryRequest` to an RDS server to - // fetch the :ref:`envoy_api_msg_RouteConfiguration` associated with this scope. + // The resource name to use for a :ref:`envoy_api_msg_DiscoveryRequest` to an + // RDS server to fetch the :ref:`envoy_api_msg_RouteConfiguration` associated + // with this scope. string route_configuration_name = 2 [(validate.rules).string.min_bytes = 1]; // The key to match against. diff --git a/api/envoy/api/v3alpha/BUILD b/api/envoy/api/v3alpha/BUILD new file mode 100644 index 0000000000..e61a715ab9 --- /dev/null +++ b/api/envoy/api/v3alpha/BUILD @@ -0,0 +1,115 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +# Friends of core API packages - filters, services, service configs. +# Package //envoy/api/v3alpha contains xDS and discovery definitions that should +# be in //envoy/service/discovery, but remain here for backwards compatibility. +package_group( + name = "friends", + packages = [ + "//envoy/admin/...", + "//envoy/api/v3alpha", + "//envoy/config/...", + "//envoy/data/...", + "//envoy/service/...", + ], +) + +api_proto_package( + name = "v3alpha", + has_services = True, + deps = [ + "//envoy/api/v3alpha/auth", + "//envoy/api/v3alpha/cluster", + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/endpoint:pkg", + "//envoy/api/v3alpha/listener:pkg", + "//envoy/api/v3alpha/ratelimit:pkg", + "//envoy/api/v3alpha/route:pkg", + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "discovery", + srcs = ["discovery.proto"], + visibility = [":friends"], + deps = ["//envoy/api/v3alpha/core:base"], +) + +api_proto_library_internal( + name = "eds", + srcs = ["eds.proto"], + has_services = 1, + visibility = [":friends"], + deps = [ + ":discovery", + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:health_check", + "//envoy/api/v3alpha/endpoint", + "//envoy/type:percent", + ], +) + +api_proto_library_internal( + name = "cds", + srcs = ["cds.proto"], + has_services = 1, + visibility = [":friends"], + deps = [ + ":discovery", + ":eds", + "//envoy/api/v3alpha/auth:cert", + "//envoy/api/v3alpha/cluster:circuit_breaker", + "//envoy/api/v3alpha/cluster:filter", + "//envoy/api/v3alpha/cluster:outlier_detection", + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:config_source", + "//envoy/api/v3alpha/core:health_check", + "//envoy/api/v3alpha/core:protocol", + "//envoy/api/v3alpha/endpoint", + "//envoy/type:percent", + ], +) + +api_proto_library_internal( + name = "lds", + srcs = ["lds.proto"], + has_services = 1, + visibility = [":friends"], + deps = [ + ":discovery", + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/listener", + "//envoy/api/v3alpha/listener:udp_listener_config", + ], +) + +api_proto_library_internal( + name = "rds", + srcs = ["rds.proto"], + has_services = 1, + visibility = [":friends"], + deps = [ + ":discovery", + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:config_source", + "//envoy/api/v3alpha/route", + ], +) + +api_proto_library_internal( + name = "srds", + srcs = ["srds.proto"], + has_services = 1, + visibility = [":friends"], + deps = [ + ":discovery", + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/route", + ], +) diff --git a/api/envoy/api/v3alpha/README.md b/api/envoy/api/v3alpha/README.md new file mode 100644 index 0000000000..984be690a1 --- /dev/null +++ b/api/envoy/api/v3alpha/README.md @@ -0,0 +1,9 @@ +Protocol buffer definitions for xDS and top-level resource API messages. + +Package group `//envoy/api/v2:friends` enumerates all consumers of the shared +API messages. That includes package envoy.api.v2 itself, which contains several +xDS definitions. Default visibility for all shared definitions should be set to +`//envoy/api/v2:friends`. + +Additionally, packages envoy.api.v2.core and envoy.api.v2.auth are also +consumed throughout the subpackages of `//envoy/api/v2`. diff --git a/api/envoy/api/v3alpha/auth/BUILD b/api/envoy/api/v3alpha/auth/BUILD new file mode 100644 index 0000000000..6c47aff6e2 --- /dev/null +++ b/api/envoy/api/v3alpha/auth/BUILD @@ -0,0 +1,33 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +package_group( + name = "friends", + includes = [ + "//envoy/api/v3alpha:friends", + ], + packages = [ + "//envoy/api/v3alpha/cluster", + "//envoy/api/v3alpha/endpoint", + "//envoy/api/v3alpha/listener", + "//envoy/api/v3alpha/route", + ], +) + +api_proto_package( + name = "auth", + deps = [ + "//envoy/api/v3alpha/core", + ], +) + +api_proto_library_internal( + name = "cert", + srcs = ["cert.proto"], + visibility = [":friends"], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:config_source", + ], +) diff --git a/api/envoy/api/v3alpha/auth/cert.proto b/api/envoy/api/v3alpha/auth/cert.proto new file mode 100644 index 0000000000..83897b2683 --- /dev/null +++ b/api/envoy/api/v3alpha/auth/cert.proto @@ -0,0 +1,403 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.auth; + +option java_outer_classname = "CertProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.auth"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/core/config_source.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Common TLS configuration] + +message TlsParameters { + enum TlsProtocol { + // Envoy will choose the optimal TLS version. + TLS_AUTO = 0; + + // TLS 1.0 + TLSv1_0 = 1; + + // TLS 1.1 + TLSv1_1 = 2; + + // TLS 1.2 + TLSv1_2 = 3; + + // TLS 1.3 + TLSv1_3 = 4; + } + + // Minimum TLS protocol version. By default, it's ``TLSv1_0``. + TlsProtocol tls_minimum_protocol_version = 1 [(validate.rules).enum.defined_only = true]; + + // Maximum TLS protocol version. By default, it's ``TLSv1_3`` for servers in non-FIPS builds, and + // ``TLSv1_2`` for clients and for servers using :ref:`BoringSSL FIPS `. + TlsProtocol tls_maximum_protocol_version = 2 [(validate.rules).enum.defined_only = true]; + + // If specified, the TLS listener will only support the specified `cipher list + // `_ + // when negotiating TLS 1.0-1.2 (this setting has no effect when negotiating TLS 1.3). If not + // specified, the default list will be used. + // + // In non-FIPS builds, the default cipher list is: + // + // .. code-block:: none + // + // [ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-ECDSA-CHACHA20-POLY1305] + // [ECDHE-RSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305] + // ECDHE-ECDSA-AES128-SHA + // ECDHE-RSA-AES128-SHA + // AES128-GCM-SHA256 + // AES128-SHA + // ECDHE-ECDSA-AES256-GCM-SHA384 + // ECDHE-RSA-AES256-GCM-SHA384 + // ECDHE-ECDSA-AES256-SHA + // ECDHE-RSA-AES256-SHA + // AES256-GCM-SHA384 + // AES256-SHA + // + // In builds using :ref:`BoringSSL FIPS `, the default cipher list is: + // + // .. code-block:: none + // + // ECDHE-ECDSA-AES128-GCM-SHA256 + // ECDHE-RSA-AES128-GCM-SHA256 + // ECDHE-ECDSA-AES128-SHA + // ECDHE-RSA-AES128-SHA + // AES128-GCM-SHA256 + // AES128-SHA + // ECDHE-ECDSA-AES256-GCM-SHA384 + // ECDHE-RSA-AES256-GCM-SHA384 + // ECDHE-ECDSA-AES256-SHA + // ECDHE-RSA-AES256-SHA + // AES256-GCM-SHA384 + // AES256-SHA + repeated string cipher_suites = 3; + + // If specified, the TLS connection will only support the specified ECDH + // curves. If not specified, the default curves will be used. + // + // In non-FIPS builds, the default curves are: + // + // .. code-block:: none + // + // X25519 + // P-256 + // + // In builds using :ref:`BoringSSL FIPS `, the default curve is: + // + // .. code-block:: none + // + // P-256 + repeated string ecdh_curves = 4; +} + +// BoringSSL private key method configuration. The private key methods are used for external +// (potentially asynchronous) signing and decryption operations. Some use cases for private key +// methods would be TPM support and TLS acceleration. +message PrivateKeyProvider { + // Private key method provider name. The name must match a + // supported private key method provider type. + string provider_name = 1 [(validate.rules).string.min_bytes = 1]; + + // Private key method provider specific configuration. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} + +message TlsCertificate { + // The TLS certificate chain. + core.DataSource certificate_chain = 1; + + // The TLS private key. + core.DataSource private_key = 2; + + // BoringSSL private key method provider. This is an alternative to :ref:`private_key + // ` field. This can't be + // marked as ``oneof`` due to API compatibility reasons. Setting both :ref:`private_key + // ` and + // :ref:`private_key_provider + // ` fields will result in an + // error. + PrivateKeyProvider private_key_provider = 6; + + // The password to decrypt the TLS private key. If this field is not set, it is assumed that the + // TLS private key is not password encrypted. + core.DataSource password = 3; + + // [#not-implemented-hide:] + core.DataSource ocsp_staple = 4; + + // [#not-implemented-hide:] + repeated core.DataSource signed_certificate_timestamp = 5; +} + +message TlsSessionTicketKeys { + // Keys for encrypting and decrypting TLS session tickets. The + // first key in the array contains the key to encrypt all new sessions created by this context. + // All keys are candidates for decrypting received tickets. This allows for easy rotation of keys + // by, for example, putting the new key first, and the previous key second. + // + // If :ref:`session_ticket_keys ` + // is not specified, the TLS library will still support resuming sessions via tickets, but it will + // use an internally-generated and managed key, so sessions cannot be resumed across hot restarts + // or on different hosts. + // + // Each key must contain exactly 80 bytes of cryptographically-secure random data. For + // example, the output of ``openssl rand 80``. + // + // .. attention:: + // + // Using this feature has serious security considerations and risks. Improper handling of keys + // may result in loss of secrecy in connections, even if ciphers supporting perfect forward + // secrecy are used. See https://www.imperialviolet.org/2013/06/27/botchingpfs.html for some + // discussion. To minimize the risk, you must: + // + // * Keep the session ticket keys at least as secure as your TLS certificate private keys + // * Rotate session ticket keys at least daily, and preferably hourly + // * Always generate keys using a cryptographically-secure random data source + repeated core.DataSource keys = 1 [(validate.rules).repeated .min_items = 1]; +} + +message CertificateValidationContext { + // TLS certificate data containing certificate authority certificates to use in verifying + // a presented peer certificate (e.g. server certificate for clusters or client certificate + // for listeners). If not specified and a peer certificate is presented it will not be + // verified. By default, a client certificate is optional, unless one of the additional + // options (:ref:`require_client_certificate + // `, + // :ref:`verify_certificate_spki + // `, + // :ref:`verify_certificate_hash + // `, or + // :ref:`verify_subject_alt_name + // `) is also + // specified. + // + // It can optionally contain certificate revocation lists, in which case Envoy will verify + // that the presented peer certificate has not been revoked by one of the included CRLs. + // + // See :ref:`the TLS overview ` for a list of common + // system CA locations. + core.DataSource trusted_ca = 1; + + // An optional list of base64-encoded SHA-256 hashes. If specified, Envoy will verify that the + // SHA-256 of the DER-encoded Subject Public Key Information (SPKI) of the presented certificate + // matches one of the specified values. + // + // A base64-encoded SHA-256 of the Subject Public Key Information (SPKI) of the certificate + // can be generated with the following command: + // + // .. code-block:: bash + // + // $ openssl x509 -in path/to/client.crt -noout -pubkey \ + // | openssl pkey -pubin -outform DER \ + // | openssl dgst -sha256 -binary \ + // | openssl enc -base64 + // NvqYIYSbgK2vCJpQhObf77vv+bQWtc5ek5RIOwPiC9A= + // + // This is the format used in HTTP Public Key Pinning. + // + // When both: + // :ref:`verify_certificate_hash + // ` and + // :ref:`verify_certificate_spki + // ` are specified, + // a hash matching value from either of the lists will result in the certificate being accepted. + // + // .. attention:: + // + // This option is preferred over :ref:`verify_certificate_hash + // `, + // because SPKI is tied to a private key, so it doesn't change when the certificate + // is renewed using the same private key. + repeated string verify_certificate_spki = 3 + [(validate.rules).repeated .items.string = {min_bytes: 44, max_bytes: 44}]; + + // An optional list of hex-encoded SHA-256 hashes. If specified, Envoy will verify that + // the SHA-256 of the DER-encoded presented certificate matches one of the specified values. + // + // A hex-encoded SHA-256 of the certificate can be generated with the following command: + // + // .. code-block:: bash + // + // $ openssl x509 -in path/to/client.crt -outform DER | openssl dgst -sha256 | cut -d" " -f2 + // df6ff72fe9116521268f6f2dd4966f51df479883fe7037b39f75916ac3049d1a + // + // A long hex-encoded and colon-separated SHA-256 (a.k.a. "fingerprint") of the certificate + // can be generated with the following command: + // + // .. code-block:: bash + // + // $ openssl x509 -in path/to/client.crt -noout -fingerprint -sha256 | cut -d"=" -f2 + // DF:6F:F7:2F:E9:11:65:21:26:8F:6F:2D:D4:96:6F:51:DF:47:98:83:FE:70:37:B3:9F:75:91:6A:C3:04:9D:1A + // + // Both of those formats are acceptable. + // + // When both: + // :ref:`verify_certificate_hash + // ` and + // :ref:`verify_certificate_spki + // ` are specified, + // a hash matching value from either of the lists will result in the certificate being accepted. + repeated string verify_certificate_hash = 2 + [(validate.rules).repeated .items.string = {min_bytes: 64, max_bytes: 95}]; + + // An optional list of Subject Alternative Names. If specified, Envoy will verify that the + // Subject Alternative Name of the presented certificate matches one of the specified values. + // + // .. attention:: + // + // Subject Alternative Names are easily spoofable and verifying only them is insecure, + // therefore this option must be used together with :ref:`trusted_ca + // `. + repeated string verify_subject_alt_name = 4; + + // [#not-implemented-hide:] Must present a signed time-stamped OCSP response. + google.protobuf.BoolValue require_ocsp_staple = 5; + + // [#not-implemented-hide:] Must present signed certificate time-stamp. + google.protobuf.BoolValue require_signed_certificate_timestamp = 6; + + // An optional `certificate revocation list + // `_ + // (in PEM format). If specified, Envoy will verify that the presented peer + // certificate has not been revoked by this CRL. If this DataSource contains + // multiple CRLs, all of them will be used. + core.DataSource crl = 7; + + // If specified, Envoy will not reject expired certificates. + bool allow_expired_certificate = 8; +} + +// TLS context shared by both client and server TLS contexts. +message CommonTlsContext { + // TLS protocol versions, cipher suites etc. + TlsParameters tls_params = 1; + + // :ref:`Multiple TLS certificates ` can be associated with the + // same context to allow both RSA and ECDSA certificates. + // + // Only a single TLS certificate is supported in client contexts. In server contexts, the first + // RSA certificate is used for clients that only support RSA and the first ECDSA certificate is + // used for clients that support ECDSA. + repeated TlsCertificate tls_certificates = 2; + + // Configs for fetching TLS certificates via SDS API. + repeated SdsSecretConfig tls_certificate_sds_secret_configs = 6 + [(validate.rules).repeated .max_items = 1]; + + message CombinedCertificateValidationContext { + // How to validate peer certificates. + CertificateValidationContext default_validation_context = 1 + [(validate.rules).message.required = true]; + + // Config for fetching validation context via SDS API. + SdsSecretConfig validation_context_sds_secret_config = 2 + [(validate.rules).message.required = true]; + }; + + oneof validation_context_type { + // How to validate peer certificates. + CertificateValidationContext validation_context = 3; + + // Config for fetching validation context via SDS API. + SdsSecretConfig validation_context_sds_secret_config = 7; + + // Combined certificate validation context holds a default CertificateValidationContext + // and SDS config. When SDS server returns dynamic CertificateValidationContext, both dynamic + // and default CertificateValidationContext are merged into a new CertificateValidationContext + // for validation. This merge is done by Message::MergeFrom(), so dynamic + // CertificateValidationContext overwrites singular fields in default + // CertificateValidationContext, and concatenates repeated fields to default + // CertificateValidationContext, and logical OR is applied to boolean fields. + CombinedCertificateValidationContext combined_validation_context = 8; + } + + // Supplies the list of ALPN protocols that the listener should expose. In + // practice this is likely to be set to one of two values (see the + // :ref:`codec_type + // ` + // parameter in the HTTP connection manager for more information): + // + // * "h2,http/1.1" If the listener is going to support both HTTP/2 and HTTP/1.1. + // * "http/1.1" If the listener is only going to support HTTP/1.1. + // + // There is no default for this parameter. If empty, Envoy will not expose ALPN. + repeated string alpn_protocols = 4; + + reserved 5; +} + +message UpstreamTlsContext { + // Common TLS context settings. + CommonTlsContext common_tls_context = 1; + + // SNI string to use when creating TLS backend connections. + string sni = 2 [(validate.rules).string.max_bytes = 255]; + + // If true, server-initiated TLS renegotiation will be allowed. + // + // .. attention:: + // + // TLS renegotiation is considered insecure and shouldn't be used unless absolutely necessary. + bool allow_renegotiation = 3; + + // Maximum number of session keys (Pre-Shared Keys for TLSv1.3+, Session IDs and Session Tickets + // for TLSv1.2 and older) to store for the purpose of session resumption. + // + // Defaults to 1, setting this to 0 disables session resumption. + google.protobuf.UInt32Value max_session_keys = 4; +} + +message DownstreamTlsContext { + // Common TLS context settings. + CommonTlsContext common_tls_context = 1; + + // If specified, Envoy will reject connections without a valid client + // certificate. + google.protobuf.BoolValue require_client_certificate = 2; + + // If specified, Envoy will reject connections without a valid and matching SNI. + // [#not-implemented-hide:] + google.protobuf.BoolValue require_sni = 3; + + oneof session_ticket_keys_type { + // TLS session ticket key settings. + TlsSessionTicketKeys session_ticket_keys = 4; + + // [#not-implemented-hide:] + SdsSecretConfig session_ticket_keys_sds_secret_config = 5; + } +} + +// [#proto-status: experimental] +message SdsSecretConfig { + // Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. + // When both name and config are specified, then secret can be fetched and/or reloaded via SDS. + // When only name is specified, then secret will be loaded from static resources [V2-API-DIFF]. + string name = 1; + core.ConfigSource sds_config = 2; +} + +// [#proto-status: experimental] +message Secret { + // Name (FQDN, UUID, SPKI, SHA256, etc.) by which the secret can be uniquely referred to. + string name = 1; + oneof type { + TlsCertificate tls_certificate = 2; + TlsSessionTicketKeys session_ticket_keys = 3; + CertificateValidationContext validation_context = 4; + } +} diff --git a/api/envoy/api/v3alpha/cds.proto b/api/envoy/api/v3alpha/cds.proto new file mode 100644 index 0000000000..506224a7ba --- /dev/null +++ b/api/envoy/api/v3alpha/cds.proto @@ -0,0 +1,653 @@ +syntax = "proto3"; + +package envoy.api.v3alpha; + +option java_outer_classname = "CdsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha"; + +option java_generic_services = true; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/auth/cert.proto"; +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/core/config_source.proto"; +import "envoy/api/v3alpha/discovery.proto"; +import "envoy/api/v3alpha/core/health_check.proto"; +import "envoy/api/v3alpha/core/protocol.proto"; +import "envoy/api/v3alpha/cluster/circuit_breaker.proto"; +import "envoy/api/v3alpha/cluster/filter.proto"; +import "envoy/api/v3alpha/cluster/outlier_detection.proto"; +import "envoy/api/v3alpha/eds.proto"; +import "envoy/type/percent.proto"; + +import "google/api/annotations.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// Return list of all clusters this proxy will load balance to. +service ClusterDiscoveryService { + rpc StreamClusters(stream DiscoveryRequest) returns (stream DiscoveryResponse) { + } + + rpc DeltaClusters(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) { + } + + rpc FetchClusters(DiscoveryRequest) returns (DiscoveryResponse) { + option (google.api.http) = { + post: "/v3alpha/discovery:clusters" + body: "*" + }; + } +} + +// [#protodoc-title: Clusters] + +// Configuration for a single upstream cluster. +// [#comment:next free field: 41] +message Cluster { + // Supplies the name of the cluster which must be unique across all clusters. + // The cluster name is used when emitting + // :ref:`statistics ` if :ref:`alt_stat_name + // ` is not provided. + // Any ``:`` in the cluster name will be converted to ``_`` when emitting statistics. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // An optional alternative to the cluster name to be used while emitting stats. + // Any ``:`` in the name will be converted to ``_`` when emitting statistics. This should not be + // confused with :ref:`Router Filter Header + // `. + string alt_stat_name = 28; + + // Refer to :ref:`service discovery type ` + // for an explanation on each type. + enum DiscoveryType { + // Refer to the :ref:`static discovery type` + // for an explanation. + STATIC = 0; + + // Refer to the :ref:`strict DNS discovery + // type` + // for an explanation. + STRICT_DNS = 1; + + // Refer to the :ref:`logical DNS discovery + // type` + // for an explanation. + LOGICAL_DNS = 2; + + // Refer to the :ref:`service discovery type` + // for an explanation. + EDS = 3; + + // Refer to the :ref:`original destination discovery + // type` + // for an explanation. + ORIGINAL_DST = 4; + } + + // Extended cluster type. + message CustomClusterType { + // The type of the cluster to instantiate. The name must match a supported cluster type. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Cluster specific configuration which depends on the cluster being instantiated. + // See the supported cluster for further documentation. + google.protobuf.Any typed_config = 2; + } + + oneof cluster_discovery_type { + // The :ref:`service discovery type ` + // to use for resolving the cluster. + DiscoveryType type = 2 [(validate.rules).enum.defined_only = true]; + + // The custom cluster type. + CustomClusterType cluster_type = 38; + } + + // Only valid when discovery type is EDS. + message EdsClusterConfig { + // Configuration for the source of EDS updates for this Cluster. + core.ConfigSource eds_config = 1; + + // Optional alternative to cluster name to present to EDS. This does not + // have the same restrictions as cluster name, i.e. it may be arbitrary + // length. + string service_name = 2; + } + // Configuration to use for EDS updates for the Cluster. + EdsClusterConfig eds_cluster_config = 3; + + // The timeout for new network connections to hosts in the cluster. + google.protobuf.Duration connect_timeout = 4 [(validate.rules).duration.gt = {}]; + + // Soft limit on size of the cluster’s connections read and write buffers. If + // unspecified, an implementation defined default is applied (1MiB). + google.protobuf.UInt32Value per_connection_buffer_limit_bytes = 5; + + // Refer to :ref:`load balancer type ` architecture + // overview section for information on each type. + enum LbPolicy { + // Refer to the :ref:`round robin load balancing + // policy` + // for an explanation. + ROUND_ROBIN = 0; + + // Refer to the :ref:`least request load balancing + // policy` + // for an explanation. + LEAST_REQUEST = 1; + + // Refer to the :ref:`ring hash load balancing + // policy` + // for an explanation. + RING_HASH = 2; + + // Refer to the :ref:`random load balancing + // policy` + // for an explanation. + RANDOM = 3; + + // Refer to the :ref:`original destination load balancing + // policy` + // for an explanation. + // + // .. attention:: + // + // **This load balancing policy is deprecated**. Use CLUSTER_PROVIDED instead. + // + ORIGINAL_DST_LB = 4 [deprecated = true]; + + // Refer to the :ref:`Maglev load balancing policy` + // for an explanation. + MAGLEV = 5; + + // This load balancer type must be specified if the configured cluster provides a cluster + // specific load balancer. Consult the configured cluster's documentation for whether to set + // this option or not. + CLUSTER_PROVIDED = 6; + } + // The :ref:`load balancer type ` to use + // when picking a host in the cluster. + LbPolicy lb_policy = 6 [(validate.rules).enum.defined_only = true]; + + // If the service discovery type is + // :ref:`STATIC`, + // :ref:`STRICT_DNS` + // or :ref:`LOGICAL_DNS`, + // then hosts is required. + // + // .. attention:: + // + // **This field is deprecated**. Set the + // :ref:`load_assignment` field instead. + // + repeated core.Address hosts = 7; + + // Setting this is required for specifying members of + // :ref:`STATIC`, + // :ref:`STRICT_DNS` + // or :ref:`LOGICAL_DNS` clusters. + // This field supersedes :ref:`hosts` field. + // [#comment:TODO(dio): Deprecate the hosts field and add it to :ref:`deprecated log` + // once load_assignment is implemented.] + // + // .. attention:: + // + // Setting this allows non-EDS cluster types to contain embedded EDS equivalent + // :ref:`endpoint assignments`. + // Setting this overrides :ref:`hosts` values. + // + ClusterLoadAssignment load_assignment = 33; + + // Optional :ref:`active health checking ` + // configuration for the cluster. If no + // configuration is specified no health checking will be done and all cluster + // members will be considered healthy at all times. + repeated core.HealthCheck health_checks = 8; + + // Optional maximum requests for a single upstream connection. This parameter + // is respected by both the HTTP/1.1 and HTTP/2 connection pool + // implementations. If not specified, there is no limit. Setting this + // parameter to 1 will effectively disable keep alive. + google.protobuf.UInt32Value max_requests_per_connection = 9; + + // Optional :ref:`circuit breaking ` for the cluster. + cluster.CircuitBreakers circuit_breakers = 10; + + // The TLS configuration for connections to the upstream cluster. If no TLS + // configuration is specified, TLS will not be used for new connections. + // + // .. attention:: + // + // Server certificate verification is not enabled by default. Configure + // :ref:`trusted_ca` to enable + // verification. + auth.UpstreamTlsContext tls_context = 11; + + reserved 12; + + // Additional options when handling HTTP requests. These options will be applicable to both + // HTTP1 and HTTP2 requests. + core.HttpProtocolOptions common_http_protocol_options = 29; + + // Additional options when handling HTTP1 requests. + core.Http1ProtocolOptions http_protocol_options = 13; + + // Even if default HTTP2 protocol options are desired, this field must be + // set so that Envoy will assume that the upstream supports HTTP/2 when + // making new HTTP connection pool connections. Currently, Envoy only + // supports prior knowledge for upstream connections. Even if TLS is used + // with ALPN, `http2_protocol_options` must be specified. As an aside this allows HTTP/2 + // connections to happen over plain text. + core.Http2ProtocolOptions http2_protocol_options = 14; + + // The extension_protocol_options field is used to provide extension-specific protocol options + // for upstream connections. The key should match the extension filter name, such as + // "envoy.filters.network.thrift_proxy". See the extension's documentation for details on + // specific options. + map extension_protocol_options = 35; + + // The extension_protocol_options field is used to provide extension-specific protocol options + // for upstream connections. The key should match the extension filter name, such as + // "envoy.filters.network.thrift_proxy". See the extension's documentation for details on + // specific options. + map typed_extension_protocol_options = 36; + + reserved 15; + + // If the DNS refresh rate is specified and the cluster type is either + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, + // this value is used as the cluster’s DNS refresh + // rate. If this setting is not specified, the value defaults to 5000ms. For + // cluster types other than + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` + // this setting is ignored. + google.protobuf.Duration dns_refresh_rate = 16 [(validate.rules).duration.gt = {}]; + + // Optional configuration for setting cluster's DNS refresh rate. If the value is set to true, + // cluster's DNS refresh rate will be set to resource record's TTL which comes from DNS + // resolution. + bool respect_dns_ttl = 39; + + // When V4_ONLY is selected, the DNS resolver will only perform a lookup for + // addresses in the IPv4 family. If V6_ONLY is selected, the DNS resolver will + // only perform a lookup for addresses in the IPv6 family. If AUTO is + // specified, the DNS resolver will first perform a lookup for addresses in + // the IPv6 family and fallback to a lookup for addresses in the IPv4 family. + // For cluster types other than + // :ref:`STRICT_DNS` and + // :ref:`LOGICAL_DNS`, + // this setting is + // ignored. + enum DnsLookupFamily { + AUTO = 0; + V4_ONLY = 1; + V6_ONLY = 2; + } + + // The DNS IP address resolution policy. If this setting is not specified, the + // value defaults to + // :ref:`AUTO`. + DnsLookupFamily dns_lookup_family = 17 [(validate.rules).enum.defined_only = true]; + + // If DNS resolvers are specified and the cluster type is either + // :ref:`STRICT_DNS`, + // or :ref:`LOGICAL_DNS`, + // this value is used to specify the cluster’s dns resolvers. + // If this setting is not specified, the value defaults to the default + // resolver, which uses /etc/resolv.conf for configuration. For cluster types + // other than + // :ref:`STRICT_DNS` + // and :ref:`LOGICAL_DNS` + // this setting is ignored. + repeated core.Address dns_resolvers = 18; + + // If specified, outlier detection will be enabled for this upstream cluster. + // Each of the configuration values can be overridden via + // :ref:`runtime values `. + cluster.OutlierDetection outlier_detection = 19; + + // The interval for removing stale hosts from a cluster type + // :ref:`ORIGINAL_DST`. + // Hosts are considered stale if they have not been used + // as upstream destinations during this interval. New hosts are added + // to original destination clusters on demand as new connections are + // redirected to Envoy, causing the number of hosts in the cluster to + // grow over time. Hosts that are not stale (they are actively used as + // destinations) are kept in the cluster, which allows connections to + // them remain open, saving the latency that would otherwise be spent + // on opening new connections. If this setting is not specified, the + // value defaults to 5000ms. For cluster types other than + // :ref:`ORIGINAL_DST` + // this setting is ignored. + google.protobuf.Duration cleanup_interval = 20 [(validate.rules).duration.gt = {}]; + + // Optional configuration used to bind newly established upstream connections. + // This overrides any bind_config specified in the bootstrap proto. + // If the address and port are empty, no bind will be performed. + core.BindConfig upstream_bind_config = 21; + + // Optionally divide the endpoints in this cluster into subsets defined by + // endpoint metadata and selected by route and weighted cluster metadata. + message LbSubsetConfig { + + // If NO_FALLBACK is selected, a result + // equivalent to no healthy hosts is reported. If ANY_ENDPOINT is selected, + // any cluster endpoint may be returned (subject to policy, health checks, + // etc). If DEFAULT_SUBSET is selected, load balancing is performed over the + // endpoints matching the values from the default_subset field. + enum LbSubsetFallbackPolicy { + NO_FALLBACK = 0; + ANY_ENDPOINT = 1; + DEFAULT_SUBSET = 2; + } + + // The behavior used when no endpoint subset matches the selected route's + // metadata. The value defaults to + // :ref:`NO_FALLBACK`. + LbSubsetFallbackPolicy fallback_policy = 1 [(validate.rules).enum.defined_only = true]; + + // Specifies the default subset of endpoints used during fallback if + // fallback_policy is + // :ref:`DEFAULT_SUBSET`. + // Each field in default_subset is + // compared to the matching LbEndpoint.Metadata under the *envoy.lb* + // namespace. It is valid for no hosts to match, in which case the behavior + // is the same as a fallback_policy of + // :ref:`NO_FALLBACK`. + google.protobuf.Struct default_subset = 2; + + // Specifications for subsets. + message LbSubsetSelector { + // List of keys to match with the weighted cluster metadata. + repeated string keys = 1; + // The behavior used when no endpoint subset matches the selected route's + // metadata. + LbSubsetSelectorFallbackPolicy fallback_policy = 2 + [(validate.rules).enum.defined_only = true]; + + // Allows to override top level fallback policy per selector. + enum LbSubsetSelectorFallbackPolicy { + // If NOT_DEFINED top level config fallback policy is used instead. + NOT_DEFINED = 0; + // If NO_FALLBACK is selected, a result equivalent to no healthy hosts is reported. + NO_FALLBACK = 1; + // If ANY_ENDPOINT is selected, any cluster endpoint may be returned + // (subject to policy, health checks, etc). + ANY_ENDPOINT = 2; + // If DEFAULT_SUBSET is selected, load balancing is performed over the + // endpoints matching the values from the default_subset field. + DEFAULT_SUBSET = 3; + } + } + + // For each entry, LbEndpoint.Metadata's + // *envoy.lb* namespace is traversed and a subset is created for each unique + // combination of key and value. For example: + // + // .. code-block:: json + // + // { "subset_selectors": [ + // { "keys": [ "version" ] }, + // { "keys": [ "stage", "hardware_type" ] } + // ]} + // + // A subset is matched when the metadata from the selected route and + // weighted cluster contains the same keys and values as the subset's + // metadata. The same host may appear in multiple subsets. + repeated LbSubsetSelector subset_selectors = 3; + + // If true, routing to subsets will take into account the localities and locality weights of the + // endpoints when making the routing decision. + // + // There are some potential pitfalls associated with enabling this feature, as the resulting + // traffic split after applying both a subset match and locality weights might be undesirable. + // + // Consider for example a situation in which you have 50/50 split across two localities X/Y + // which have 100 hosts each without subsetting. If the subset LB results in X having only 1 + // host selected but Y having 100, then a lot more load is being dumped on the single host in X + // than originally anticipated in the load balancing assignment delivered via EDS. + bool locality_weight_aware = 4; + + // When used with locality_weight_aware, scales the weight of each locality by the ratio + // of hosts in the subset vs hosts in the original subset. This aims to even out the load + // going to an individual locality if said locality is disproportionally affected by the + // subset predicate. + bool scale_locality_weight = 5; + + // If true, when a fallback policy is configured and its corresponding subset fails to find + // a host this will cause any host to be selected instead. + // + // This is useful when using the default subset as the fallback policy, given the default + // subset might become empty. With this option enabled, if that happens the LB will attempt + // to select a host from the entire cluster. + bool panic_mode_any = 6; + + // If true, metadata specified for a metadata key will be matched against the corresponding + // endpoint metadata if the endpoint metadata matches the value exactly OR it is a list value + // and any of the elements in the list matches the criteria. + bool list_as_any = 7; + } + + // Configuration for load balancing subsetting. + LbSubsetConfig lb_subset_config = 22; + + // Specific configuration for the LeastRequest load balancing policy. + message LeastRequestLbConfig { + // The number of random healthy hosts from which the host with the fewest active requests will + // be chosen. Defaults to 2 so that we perform two-choice selection if the field is not set. + google.protobuf.UInt32Value choice_count = 1 [(validate.rules).uint32.gte = 2]; + } + + // Specific configuration for the :ref:`RingHash` + // load balancing policy. + message RingHashLbConfig { + // Minimum hash ring size. The larger the ring is (that is, the more hashes there are for each + // provided host) the better the request distribution will reflect the desired weights. Defaults + // to 1024 entries, and limited to 8M entries. See also + // :ref:`maximum_ring_size`. + google.protobuf.UInt64Value minimum_ring_size = 1 [(validate.rules).uint64.lte = 8388608]; + + reserved 2; + + // The hash function used to hash hosts onto the ketama ring. + enum HashFunction { + // Use `xxHash `_, this is the default hash function. + XX_HASH = 0; + // Use `MurmurHash2 `_, this is compatible with + // std:hash in GNU libstdc++ 3.4.20 or above. This is typically the case when compiled + // on Linux and not macOS. + MURMUR_HASH_2 = 1; + } + + // The hash function used to hash hosts onto the ketama ring. The value defaults to + // :ref:`XX_HASH`. + HashFunction hash_function = 3 [(validate.rules).enum.defined_only = true]; + + // Maximum hash ring size. Defaults to 8M entries, and limited to 8M entries, but can be lowered + // to further constrain resource use. See also + // :ref:`minimum_ring_size`. + google.protobuf.UInt64Value maximum_ring_size = 4 [(validate.rules).uint64.lte = 8388608]; + } + + // Specific configuration for the + // :ref:`Original Destination ` + // load balancing policy. + message OriginalDstLbConfig { + // When true, :ref:`x-envoy-original-dst-host + // ` can be used to override destination + // address. + // + // .. attention:: + // + // This header isn't sanitized by default, so enabling this feature allows HTTP clients to + // route traffic to arbitrary hosts and/or ports, which may have serious security + // consequences. + bool use_http_header = 1; + } + + // Optional configuration for the load balancing algorithm selected by + // LbPolicy. Currently only + // :ref:`RING_HASH` and + // :ref:`LEAST_REQUEST` + // has additional configuration options. + // Specifying ring_hash_lb_config or least_request_lb_config without setting the corresponding + // LbPolicy will generate an error at runtime. + oneof lb_config { + // Optional configuration for the Ring Hash load balancing policy. + RingHashLbConfig ring_hash_lb_config = 23; + // Optional configuration for the Original Destination load balancing policy. + OriginalDstLbConfig original_dst_lb_config = 34; + // Optional configuration for the LeastRequest load balancing policy. + LeastRequestLbConfig least_request_lb_config = 37; + } + + // Common configuration for all load balancer implementations. + message CommonLbConfig { + // Configures the :ref:`healthy panic threshold `. + // If not specified, the default is 50%. + // To disable panic mode, set to 0%. + // + // .. note:: + // The specified percent will be truncated to the nearest 1%. + envoy.type.Percent healthy_panic_threshold = 1; + // Configuration for :ref:`zone aware routing + // `. + message ZoneAwareLbConfig { + // Configures percentage of requests that will be considered for zone aware routing + // if zone aware routing is configured. If not specified, the default is 100%. + // * :ref:`runtime values `. + // * :ref:`Zone aware routing support `. + envoy.type.Percent routing_enabled = 1; + // Configures minimum upstream cluster size required for zone aware routing + // If upstream cluster size is less than specified, zone aware routing is not performed + // even if zone aware routing is configured. If not specified, the default is 6. + // * :ref:`runtime values `. + // * :ref:`Zone aware routing support `. + google.protobuf.UInt64Value min_cluster_size = 2; + } + // Configuration for :ref:`locality weighted load balancing + // ` + message LocalityWeightedLbConfig { + } + oneof locality_config_specifier { + ZoneAwareLbConfig zone_aware_lb_config = 2; + LocalityWeightedLbConfig locality_weighted_lb_config = 3; + } + // If set, all health check/weight/metadata updates that happen within this duration will be + // merged and delivered in one shot when the duration expires. The start of the duration is when + // the first update happens. This is useful for big clusters, with potentially noisy deploys + // that might trigger excessive CPU usage due to a constant stream of healthcheck state changes + // or metadata updates. The first set of updates to be seen apply immediately (e.g.: a new + // cluster). Please always keep in mind that the use of sandbox technologies may change this + // behavior. + // + // If this is not set, we default to a merge window of 1000ms. To disable it, set the merge + // window to 0. + // + // Note: merging does not apply to cluster membership changes (e.g.: adds/removes); this is + // because merging those updates isn't currently safe. See + // https://github.com/envoyproxy/envoy/pull/3941. + google.protobuf.Duration update_merge_window = 4; + + // If set to true, Envoy will not consider new hosts when computing load balancing weights until + // they have been health checked for the first time. This will have no effect unless + // active health checking is also configured. + // + // Ignoring a host means that for any load balancing calculations that adjust weights based + // on the ratio of eligible hosts and total hosts (priority spillover, locality weighting and + // panic mode) Envoy will exclude these hosts in the denominator. + // + // For example, with hosts in two priorities P0 and P1, where P0 looks like + // {healthy, unhealthy (new), unhealthy (new)} + // and where P1 looks like + // {healthy, healthy} + // all traffic will still hit P0, as 1 / (3 - 2) = 1. + // + // Enabling this will allow scaling up the number of hosts for a given cluster without entering + // panic mode or triggering priority spillover, assuming the hosts pass the first health check. + // + // If panic mode is triggered, new hosts are still eligible for traffic; they simply do not + // contribute to the calculation when deciding whether panic mode is enabled or not. + bool ignore_new_hosts_until_first_hc = 5; + + // If set to `true`, the cluster manager will drain all existing + // connections to upstream hosts whenever hosts are added or removed from the cluster. + bool close_connections_on_host_set_change = 6; + } + + // Common configuration for all load balancer implementations. + CommonLbConfig common_lb_config = 27; + + // Optional custom transport socket implementation to use for upstream connections. + core.TransportSocket transport_socket = 24; + + // The Metadata field can be used to provide additional information about the + // cluster. It can be used for stats, logging, and varying filter behavior. + // Fields should use reverse DNS notation to denote which entity within Envoy + // will need the information. For instance, if the metadata is intended for + // the Router filter, the filter name should be specified as *envoy.router*. + core.Metadata metadata = 25; + + enum ClusterProtocolSelection { + // Cluster can only operate on one of the possible upstream protocols (HTTP1.1, HTTP2). + // If :ref:`http2_protocol_options ` are + // present, HTTP2 will be used, otherwise HTTP1.1 will be used. + USE_CONFIGURED_PROTOCOL = 0; + // Use HTTP1.1 or HTTP2, depending on which one is used on the downstream connection. + USE_DOWNSTREAM_PROTOCOL = 1; + } + + // Determines how Envoy selects the protocol used to speak to upstream hosts. + ClusterProtocolSelection protocol_selection = 26; + + // Optional options for upstream connections. + envoy.api.v3alpha.UpstreamConnectionOptions upstream_connection_options = 30; + + // If an upstream host becomes unhealthy (as determined by the configured health checks + // or outlier detection), immediately close all connections to the failed host. + // + // .. note:: + // + // This is currently only supported for connections created by tcp_proxy. + // + // .. note:: + // + // The current implementation of this feature closes all connections immediately when + // the unhealthy status is detected. If there are a large number of connections open + // to an upstream host that becomes unhealthy, Envoy may spend a substantial amount of + // time exclusively closing these connections, and not processing any other traffic. + bool close_connections_on_host_health_failure = 31; + + // If this cluster uses EDS or STRICT_DNS to configure its hosts, immediately drain + // connections from any hosts that are removed from service discovery. + // + // This only affects behavior for hosts that are being actively health checked. + // If this flag is not set to true, Envoy will wait until the hosts fail active health + // checking before removing it from the cluster. + bool drain_connections_on_host_removal = 32; + + // An (optional) network filter chain, listed in the order the filters should be applied. + // The chain will be applied to all outgoing connections that Envoy makes to the upstream + // servers of this cluster. + repeated cluster.Filter filters = 40; +} + +// An extensible structure containing the address Envoy should bind to when +// establishing upstream connections. +message UpstreamBindConfig { + // The address Envoy should bind to when establishing upstream connections. + core.Address source_address = 1; +} + +message UpstreamConnectionOptions { + // If set then set SO_KEEPALIVE on the socket to enable TCP Keepalives. + core.TcpKeepalive tcp_keepalive = 1; +} diff --git a/api/envoy/api/v3alpha/cluster/BUILD b/api/envoy/api/v3alpha/cluster/BUILD new file mode 100644 index 0000000000..ef01624057 --- /dev/null +++ b/api/envoy/api/v3alpha/cluster/BUILD @@ -0,0 +1,37 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + name = "cluster", + deps = [ + "//envoy/api/v3alpha/core", + ], +) + +api_proto_library_internal( + name = "circuit_breaker", + srcs = ["circuit_breaker.proto"], + visibility = [ + "//envoy/api/v3alpha:__pkg__", + ], + deps = [ + "//envoy/api/v3alpha/core:base", + ], +) + +api_proto_library_internal( + name = "outlier_detection", + srcs = ["outlier_detection.proto"], + visibility = [ + "//envoy/api/v3alpha:__pkg__", + ], +) + +api_proto_library_internal( + name = "filter", + srcs = ["filter.proto"], + visibility = [ + "//envoy/api/v3alpha:__pkg__", + ], +) diff --git a/api/envoy/api/v3alpha/cluster/circuit_breaker.proto b/api/envoy/api/v3alpha/cluster/circuit_breaker.proto new file mode 100644 index 0000000000..0b6d2fe992 --- /dev/null +++ b/api/envoy/api/v3alpha/cluster/circuit_breaker.proto @@ -0,0 +1,65 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.cluster; + +option java_outer_classname = "CircuitBreakerProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.cluster"; +option csharp_namespace = "Envoy.Api.V2.ClusterNS"; +option ruby_package = "Envoy.Api.V2.ClusterNS"; + +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/wrappers.proto"; + +// [#protodoc-title: Circuit breakers] + +// :ref:`Circuit breaking` settings can be +// specified individually for each defined priority. +message CircuitBreakers { + + // A Thresholds defines CircuitBreaker settings for a + // :ref:`RoutingPriority`. + message Thresholds { + // The :ref:`RoutingPriority` + // the specified CircuitBreaker settings apply to. + // [#comment:TODO(htuch): add (validate.rules).enum.defined_only = true once + // https://github.com/lyft/protoc-gen-validate/issues/42 is resolved.] + core.RoutingPriority priority = 1; + + // The maximum number of connections that Envoy will make to the upstream + // cluster. If not specified, the default is 1024. + google.protobuf.UInt32Value max_connections = 2; + + // The maximum number of pending requests that Envoy will allow to the + // upstream cluster. If not specified, the default is 1024. + google.protobuf.UInt32Value max_pending_requests = 3; + + // The maximum number of parallel requests that Envoy will make to the + // upstream cluster. If not specified, the default is 1024. + google.protobuf.UInt32Value max_requests = 4; + + // The maximum number of parallel retries that Envoy will allow to the + // upstream cluster. If not specified, the default is 3. + google.protobuf.UInt32Value max_retries = 5; + + // If track_remaining is true, then stats will be published that expose + // the number of resources remaining until the circuit breakers open. If + // not specified, the default is false. + bool track_remaining = 6; + + // The maximum number of connection pools per cluster that Envoy will concurrently support at + // once. If not specified, the default is unlimited. Set this for clusters which create a + // large number of connection pools. See + // :ref:`Circuit Breaking ` for + // more details. + google.protobuf.UInt32Value max_connection_pools = 7; + } + + // If multiple :ref:`Thresholds` + // are defined with the same :ref:`RoutingPriority`, + // the first one in the list is used. If no Thresholds is defined for a given + // :ref:`RoutingPriority`, the default values + // are used. + repeated Thresholds thresholds = 1; +} diff --git a/api/envoy/api/v3alpha/cluster/filter.proto b/api/envoy/api/v3alpha/cluster/filter.proto new file mode 100644 index 0000000000..1bf3433ad2 --- /dev/null +++ b/api/envoy/api/v3alpha/cluster/filter.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.cluster; + +option java_outer_classname = "FilterProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.cluster"; +option csharp_namespace = "Envoy.Api.V2.ClusterNS"; +option ruby_package = "Envoy.Api.V2.ClusterNS"; + +import "google/protobuf/any.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Upstream filters] +// +// Upstream filters apply to the connections to the upstream cluster hosts. +message Filter { + // The name of the filter to instantiate. The name must match a + // :ref:`supported filter `. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Filter specific configuration which depends on the filter being + // instantiated. See the supported filters for further documentation. + google.protobuf.Any typed_config = 2; +} diff --git a/api/envoy/api/v3alpha/cluster/outlier_detection.proto b/api/envoy/api/v3alpha/cluster/outlier_detection.proto new file mode 100644 index 0000000000..0954b85f2c --- /dev/null +++ b/api/envoy/api/v3alpha/cluster/outlier_detection.proto @@ -0,0 +1,114 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.cluster; + +option java_outer_classname = "OutlierDetectionProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.cluster"; +option csharp_namespace = "Envoy.Api.V2.ClusterNS"; +option ruby_package = "Envoy.Api.V2.ClusterNS"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Outlier detection] + +// See the :ref:`architecture overview ` for +// more information on outlier detection. +message OutlierDetection { + // The number of consecutive 5xx responses or local origin errors that are mapped + // to 5xx error codes before a consecutive 5xx ejection + // occurs. Defaults to 5. + google.protobuf.UInt32Value consecutive_5xx = 1; + + // The time interval between ejection analysis sweeps. This can result in + // both new ejections as well as hosts being returned to service. Defaults + // to 10000ms or 10s. + google.protobuf.Duration interval = 2 [(validate.rules).duration.gt = {}]; + + // The base time that a host is ejected for. The real time is equal to the + // base time multiplied by the number of times the host has been ejected. + // Defaults to 30000ms or 30s. + google.protobuf.Duration base_ejection_time = 3 [(validate.rules).duration.gt = {}]; + + // The maximum % of an upstream cluster that can be ejected due to outlier + // detection. Defaults to 10% but will eject at least one host regardless of the value. + google.protobuf.UInt32Value max_ejection_percent = 4 [(validate.rules).uint32.lte = 100]; + + // The % chance that a host will be actually ejected when an outlier status + // is detected through consecutive 5xx. This setting can be used to disable + // ejection or to ramp it up slowly. Defaults to 100. + google.protobuf.UInt32Value enforcing_consecutive_5xx = 5 [(validate.rules).uint32.lte = 100]; + + // The % chance that a host will be actually ejected when an outlier status + // is detected through success rate statistics. This setting can be used to + // disable ejection or to ramp it up slowly. Defaults to 100. + google.protobuf.UInt32Value enforcing_success_rate = 6 [(validate.rules).uint32.lte = 100]; + + // The number of hosts in a cluster that must have enough request volume to + // detect success rate outliers. If the number of hosts is less than this + // setting, outlier detection via success rate statistics is not performed + // for any host in the cluster. Defaults to 5. + google.protobuf.UInt32Value success_rate_minimum_hosts = 7; + + // The minimum number of total requests that must be collected in one + // interval (as defined by the interval duration above) to include this host + // in success rate based outlier detection. If the volume is lower than this + // setting, outlier detection via success rate statistics is not performed + // for that host. Defaults to 100. + google.protobuf.UInt32Value success_rate_request_volume = 8; + + // This factor is used to determine the ejection threshold for success rate + // outlier ejection. The ejection threshold is the difference between the + // mean success rate, and the product of this factor and the standard + // deviation of the mean success rate: mean - (stdev * + // success_rate_stdev_factor). This factor is divided by a thousand to get a + // double. That is, if the desired factor is 1.9, the runtime value should + // be 1900. Defaults to 1900. + google.protobuf.UInt32Value success_rate_stdev_factor = 9; + + // The number of consecutive gateway failures (502, 503, 504 status codes) + // before a consecutive gateway failure ejection occurs. Defaults to 5. + google.protobuf.UInt32Value consecutive_gateway_failure = 10; + + // The % chance that a host will be actually ejected when an outlier status + // is detected through consecutive gateway failures. This setting can be + // used to disable ejection or to ramp it up slowly. Defaults to 0. + google.protobuf.UInt32Value enforcing_consecutive_gateway_failure = 11 + [(validate.rules).uint32.lte = 100]; + + // Determines whether to distinguish local origin failures from external errors. If set to true + // the following configuration parameters are taken into account: + // :ref:`consecutive_local_origin_failure`, + // :ref:`enforcing_consecutive_local_origin_failure` + // and + // :ref:`enforcing_local_origin_success_rate`. + // Defaults to false. + bool split_external_local_origin_errors = 12; + + // The number of consecutive locally originated failures before ejection + // occurs. Defaults to 5. Parameter takes effect only when + // :ref:`split_external_local_origin_errors` + // is set to true. + google.protobuf.UInt32Value consecutive_local_origin_failure = 13; + + // The % chance that a host will be actually ejected when an outlier status + // is detected through consecutive locally originated failures. This setting can be + // used to disable ejection or to ramp it up slowly. Defaults to 100. + // Parameter takes effect only when + // :ref:`split_external_local_origin_errors` + // is set to true. + google.protobuf.UInt32Value enforcing_consecutive_local_origin_failure = 14 + [(validate.rules).uint32.lte = 100]; + + // The % chance that a host will be actually ejected when an outlier status + // is detected through success rate statistics for locally originated errors. + // This setting can be used to disable ejection or to ramp it up slowly. Defaults to 100. + // Parameter takes effect only when + // :ref:`split_external_local_origin_errors` + // is set to true. + google.protobuf.UInt32Value enforcing_local_origin_success_rate = 15 + [(validate.rules).uint32.lte = 100]; +} diff --git a/api/envoy/api/v3alpha/core/BUILD b/api/envoy/api/v3alpha/core/BUILD new file mode 100644 index 0000000000..871c9fe0e8 --- /dev/null +++ b/api/envoy/api/v3alpha/core/BUILD @@ -0,0 +1,94 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +package_group( + name = "friends", + includes = [ + "//envoy/api/v3alpha:friends", + ], + packages = [ + "//envoy/api/v3alpha/auth", + "//envoy/api/v3alpha/cluster", + "//envoy/api/v3alpha/endpoint", + "//envoy/api/v3alpha/listener", + "//envoy/api/v3alpha/route", + ], +) + +api_proto_package( + name = "core", + deps = [ + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "address", + srcs = ["address.proto"], + visibility = [ + ":friends", + ], + deps = [":base"], +) + +api_proto_library_internal( + name = "base", + srcs = ["base.proto"], + visibility = [ + ":friends", + ], + deps = [ + ":http_uri", + "//envoy/type:percent", + ], +) + +api_proto_library_internal( + name = "health_check", + srcs = ["health_check.proto"], + visibility = [ + ":friends", + ], + deps = [ + ":base", + "//envoy/type:range", + ], +) + +api_proto_library_internal( + name = "config_source", + srcs = ["config_source.proto"], + visibility = [ + ":friends", + ], + deps = [ + ":base", + ":grpc_service", + ], +) + +api_proto_library_internal( + name = "http_uri", + srcs = ["http_uri.proto"], + visibility = [ + ":friends", + ], +) + +api_proto_library_internal( + name = "grpc_service", + srcs = ["grpc_service.proto"], + visibility = [ + ":friends", + ], + deps = [":base"], +) + +api_proto_library_internal( + name = "protocol", + srcs = ["protocol.proto"], + visibility = [ + ":friends", + ], +) diff --git a/api/envoy/api/v3alpha/core/address.proto b/api/envoy/api/v3alpha/core/address.proto new file mode 100644 index 0000000000..80ab295b2b --- /dev/null +++ b/api/envoy/api/v3alpha/core/address.proto @@ -0,0 +1,117 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.core; + +option java_outer_classname = "AddressProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.core"; + +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Network addresses] + +message Pipe { + // Unix Domain Socket path. On Linux, paths starting with '@' will use the + // abstract namespace. The starting '@' is replaced by a null byte by Envoy. + // Paths starting with '@' will result in an error in environments other than + // Linux. + string path = 1 [(validate.rules).string.min_bytes = 1]; +} + +message SocketAddress { + enum Protocol { + TCP = 0; + // [#not-implemented-hide:] + UDP = 1; + } + Protocol protocol = 1 [(validate.rules).enum.defined_only = true]; + // The address for this socket. :ref:`Listeners ` will bind + // to the address. An empty address is not allowed. Specify ``0.0.0.0`` or ``::`` + // to bind to any address. [#comment:TODO(zuercher) reinstate when implemented: + // It is possible to distinguish a Listener address via the prefix/suffix matching + // in :ref:`FilterChainMatch `.] When used + // within an upstream :ref:`BindConfig `, the address + // controls the source address of outbound connections. For :ref:`clusters + // `, the cluster type determines whether the + // address must be an IP (*STATIC* or *EDS* clusters) or a hostname resolved by DNS + // (*STRICT_DNS* or *LOGICAL_DNS* clusters). Address resolution can be customized + // via :ref:`resolver_name `. + string address = 2 [(validate.rules).string.min_bytes = 1]; + oneof port_specifier { + option (validate.required) = true; + uint32 port_value = 3 [(validate.rules).uint32.lte = 65535]; + // This is only valid if :ref:`resolver_name + // ` is specified below and the + // named resolver is capable of named port resolution. + string named_port = 4; + } + // The name of the custom resolver. This must have been registered with Envoy. If + // this is empty, a context dependent default applies. If the address is a concrete + // IP address, no resolution will occur. If address is a hostname this + // should be set for resolution other than DNS. Specifying a custom resolver with + // *STRICT_DNS* or *LOGICAL_DNS* will generate an error at runtime. + string resolver_name = 5; + + // When binding to an IPv6 address above, this enables `IPv4 compatibility + // `_. Binding to ``::`` will + // allow both IPv4 and IPv6 connections, with peer IPv4 addresses mapped into + // IPv6 space as ``::FFFF:``. + bool ipv4_compat = 6; +} + +message TcpKeepalive { + // Maximum number of keepalive probes to send without response before deciding + // the connection is dead. Default is to use the OS level configuration (unless + // overridden, Linux defaults to 9.) + google.protobuf.UInt32Value keepalive_probes = 1; + // The number of seconds a connection needs to be idle before keep-alive probes + // start being sent. Default is to use the OS level configuration (unless + // overridden, Linux defaults to 7200s (ie 2 hours.) + google.protobuf.UInt32Value keepalive_time = 2; + // The number of seconds between keep-alive probes. Default is to use the OS + // level configuration (unless overridden, Linux defaults to 75s.) + google.protobuf.UInt32Value keepalive_interval = 3; +} + +message BindConfig { + // The address to bind to when creating a socket. + SocketAddress source_address = 1 [(validate.rules).message.required = true]; + + // Whether to set the *IP_FREEBIND* option when creating the socket. When this + // flag is set to true, allows the :ref:`source_address + // ` to be an IP address + // that is not configured on the system running Envoy. When this flag is set + // to false, the option *IP_FREEBIND* is disabled on the socket. When this + // flag is not set (default), the socket is not modified, i.e. the option is + // neither enabled nor disabled. + google.protobuf.BoolValue freebind = 2; + + // Additional socket options that may not be present in Envoy source code or + // precompiled binaries. + repeated SocketOption socket_options = 3; +} + +// Addresses specify either a logical or physical address and port, which are +// used to tell Envoy where to bind/listen, connect to upstream and find +// management servers. +message Address { + oneof address { + option (validate.required) = true; + + SocketAddress socket_address = 1; + Pipe pipe = 2; + } +} + +// CidrRange specifies an IP Address and a prefix length to construct +// the subnet mask for a `CIDR `_ range. +message CidrRange { + // IPv4 or IPv6 address, e.g. ``192.0.0.0`` or ``2001:db8::``. + string address_prefix = 1 [(validate.rules).string.min_bytes = 1]; + // Length of prefix, e.g. 0, 32. + google.protobuf.UInt32Value prefix_len = 2 [(validate.rules).uint32.lte = 128]; +} diff --git a/api/envoy/api/v3alpha/core/base.proto b/api/envoy/api/v3alpha/core/base.proto new file mode 100644 index 0000000000..a7b2f54d69 --- /dev/null +++ b/api/envoy/api/v3alpha/core/base.proto @@ -0,0 +1,285 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.core; + +option java_outer_classname = "BaseProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.core"; + +import "envoy/api/v3alpha/core/http_uri.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +import "envoy/type/percent.proto"; + +// [#protodoc-title: Common types] + +// Identifies location of where either Envoy runs or where upstream hosts run. +message Locality { + // Region this :ref:`zone ` belongs to. + string region = 1; + + // Defines the local service zone where Envoy is running. Though optional, it + // should be set if discovery service routing is used and the discovery + // service exposes :ref:`zone data `, + // either in this message or via :option:`--service-zone`. The meaning of zone + // is context dependent, e.g. `Availability Zone (AZ) + // `_ + // on AWS, `Zone `_ on + // GCP, etc. + string zone = 2; + + // When used for locality of upstream hosts, this field further splits zone + // into smaller chunks of sub-zones so they can be load balanced + // independently. + string sub_zone = 3; +} + +// Identifies a specific Envoy instance. The node identifier is presented to the +// management server, which may use this identifier to distinguish per Envoy +// configuration for serving. +message Node { + // An opaque node identifier for the Envoy node. This also provides the local + // service node name. It should be set if any of the following features are + // used: :ref:`statsd `, :ref:`CDS + // `, and :ref:`HTTP tracing + // `, either in this message or via + // :option:`--service-node`. + string id = 1; + + // Defines the local service cluster name where Envoy is running. Though + // optional, it should be set if any of the following features are used: + // :ref:`statsd `, :ref:`health check cluster + // verification `, + // :ref:`runtime override directory `, + // :ref:`user agent addition + // `, + // :ref:`HTTP global rate limiting `, + // :ref:`CDS `, and :ref:`HTTP tracing + // `, either in this message or via + // :option:`--service-cluster`. + string cluster = 2; + + // Opaque metadata extending the node identifier. Envoy will pass this + // directly to the management server. + google.protobuf.Struct metadata = 3; + + // Locality specifying where the Envoy instance is running. + Locality locality = 4; + + // This is motivated by informing a management server during canary which + // version of Envoy is being tested in a heterogeneous fleet. This will be set + // by Envoy in management server RPCs. + string build_version = 5; +} + +// Metadata provides additional inputs to filters based on matched listeners, +// filter chains, routes and endpoints. It is structured as a map, usually from +// filter name (in reverse DNS format) to metadata specific to the filter. Metadata +// key-values for a filter are merged as connection and request handling occurs, +// with later values for the same key overriding earlier values. +// +// An example use of metadata is providing additional values to +// http_connection_manager in the envoy.http_connection_manager.access_log +// namespace. +// +// Another example use of metadata is to per service config info in cluster metadata, which may get +// consumed by multiple filters. +// +// For load balancing, Metadata provides a means to subset cluster endpoints. +// Endpoints have a Metadata object associated and routes contain a Metadata +// object to match against. There are some well defined metadata used today for +// this purpose: +// +// * ``{"envoy.lb": {"canary": }}`` This indicates the canary status of an +// endpoint and is also used during header processing +// (x-envoy-upstream-canary) and for stats purposes. +message Metadata { + // Key is the reverse DNS filter name, e.g. com.acme.widget. The envoy.* + // namespace is reserved for Envoy's built-in filters. + map filter_metadata = 1; +} + +// Runtime derived uint32 with a default when not specified. +message RuntimeUInt32 { + // Default value if runtime value is not available. + uint32 default_value = 2; + + // Runtime key to get value for comparison. This value is used if defined. + string runtime_key = 3 [(validate.rules).string.min_bytes = 1]; +} + +// Envoy supports :ref:`upstream priority routing +// ` both at the route and the virtual +// cluster level. The current priority implementation uses different connection +// pool and circuit breaking settings for each priority level. This means that +// even for HTTP/2 requests, two physical connections will be used to an +// upstream host. In the future Envoy will likely support true HTTP/2 priority +// over a single upstream connection. +enum RoutingPriority { + DEFAULT = 0; + HIGH = 1; +} + +// HTTP request method. +enum RequestMethod { + METHOD_UNSPECIFIED = 0; + GET = 1; + HEAD = 2; + POST = 3; + PUT = 4; + DELETE = 5; + CONNECT = 6; + OPTIONS = 7; + TRACE = 8; + PATCH = 9; +} + +// Header name/value pair. +message HeaderValue { + // Header name. + string key = 1 [(validate.rules).string = {min_bytes: 1, max_bytes: 16384}]; + + // Header value. + // + // The same :ref:`format specifier ` as used for + // :ref:`HTTP access logging ` applies here, however + // unknown header values are replaced with the empty string instead of `-`. + string value = 2 [(validate.rules).string.max_bytes = 16384]; +} + +// Header name/value pair plus option to control append behavior. +message HeaderValueOption { + // Header name/value pair that this option applies to. + HeaderValue header = 1 [(validate.rules).message.required = true]; + + // Should the value be appended? If true (default), the value is appended to + // existing values. + google.protobuf.BoolValue append = 2; +} + +// Wrapper for a set of headers. +message HeaderMap { + repeated HeaderValue headers = 1; +} + +// Data source consisting of either a file or an inline value. +message DataSource { + oneof specifier { + option (validate.required) = true; + + // Local filesystem data source. + string filename = 1 [(validate.rules).string.min_bytes = 1]; + + // Bytes inlined in the configuration. + bytes inline_bytes = 2 [(validate.rules).bytes.min_len = 1]; + + // String inlined in the configuration. + string inline_string = 3 [(validate.rules).string.min_bytes = 1]; + } +} + +// The message specifies how to fetch data from remote and how to verify it. +message RemoteDataSource { + // The HTTP URI to fetch the remote data. + HttpUri http_uri = 1 [(validate.rules).message.required = true]; + + // SHA256 string for verifying data. + string sha256 = 2 [(validate.rules).string.min_bytes = 1]; +} + +// Async data source which support async data fetch. +message AsyncDataSource { + oneof specifier { + option (validate.required) = true; + + // Local async data source. + DataSource local = 1; + + // Remote async data source. + RemoteDataSource remote = 2; + } +} + +// Configuration for transport socket in :ref:`listeners ` and +// :ref:`clusters `. If the configuration is +// empty, a default transport socket implementation and configuration will be +// chosen based on the platform and existence of tls_context. +message TransportSocket { + // The name of the transport socket to instantiate. The name must match a supported transport + // socket implementation. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Implementation specific configuration which depends on the implementation being instantiated. + // See the supported transport socket implementations for further documentation. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} + +// Generic socket option message. This would be used to set socket options that +// might not exist in upstream kernels or precompiled Envoy binaries. +message SocketOption { + // An optional name to give this socket option for debugging, etc. + // Uniqueness is not required and no special meaning is assumed. + string description = 1; + // Corresponding to the level value passed to setsockopt, such as IPPROTO_TCP + int64 level = 2; + // The numeric name as passed to setsockopt + int64 name = 3; + oneof value { + option (validate.required) = true; + + // Because many sockopts take an int value. + int64 int_value = 4; + // Otherwise it's a byte buffer. + bytes buf_value = 5; + } + enum SocketState { + // Socket options are applied after socket creation but before binding the socket to a port + STATE_PREBIND = 0; + // Socket options are applied after binding the socket to a port but before calling listen() + STATE_BOUND = 1; + // Socket options are applied after calling listen() + STATE_LISTENING = 2; + } + // The state in which the option will be applied. When used in BindConfig + // STATE_PREBIND is currently the only valid value. + SocketState state = 6 [(validate.rules).enum.defined_only = true]; +} + +// Runtime derived FractionalPercent with defaults for when the numerator or denominator is not +// specified via a runtime key. +message RuntimeFractionalPercent { + // Default value if the runtime value's for the numerator/denominator keys are not available. + envoy.type.FractionalPercent default_value = 1 [(validate.rules).message.required = true]; + + // Runtime key for a YAML representation of a FractionalPercent. + string runtime_key = 2; +} + +// Identifies a specific ControlPlane instance that Envoy is connected to. +message ControlPlane { + // An opaque control plane identifier that uniquely identifies an instance + // of control plane. This can be used to identify which control plane instance, + // the Envoy is connected to. + string identifier = 1; +} + +// Identifies the direction of the traffic relative to the local Envoy. +enum TrafficDirection { + // Default option is unspecified. + UNSPECIFIED = 0; + + // The transport is used for incoming traffic. + INBOUND = 1; + + // The transport is used for outgoing traffic. + OUTBOUND = 2; +} diff --git a/api/envoy/api/v3alpha/core/config_source.proto b/api/envoy/api/v3alpha/core/config_source.proto new file mode 100644 index 0000000000..1c4510322e --- /dev/null +++ b/api/envoy/api/v3alpha/core/config_source.proto @@ -0,0 +1,122 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.core; + +option java_outer_classname = "ConfigSourceProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.core"; + +import "envoy/api/v3alpha/core/grpc_service.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Configuration sources] + +// API configuration source. This identifies the API type and cluster that Envoy +// will use to fetch an xDS API. +message ApiConfigSource { + // APIs may be fetched via either REST or gRPC. + enum ApiType { + // Ideally this would be 'reserved 0' but one can't reserve the default + // value. Instead we throw an exception if this is ever used. + UNSUPPORTED_REST_LEGACY = 0 [deprecated = true]; + // REST-JSON v2 API. The `canonical JSON encoding + // `_ for + // the v2 protos is used. + REST = 1; + // gRPC v2 API. + GRPC = 2; + // Using the delta xDS gRPC service, i.e. DeltaDiscovery{Request,Response} + // rather than Discovery{Request,Response}. Rather than sending Envoy the entire state + // with every update, the xDS server only sends what has changed since the last update. + // + // DELTA_GRPC is not yet entirely implemented! Initially, only CDS is available. + // Do not use for other xDSes. TODO(fredlas) update/remove this warning when appropriate. + DELTA_GRPC = 3; + } + ApiType api_type = 1 [(validate.rules).enum.defined_only = true]; + // Cluster names should be used only with REST. If > 1 + // cluster is defined, clusters will be cycled through if any kind of failure + // occurs. + // + // .. note:: + // + // The cluster with name ``cluster_name`` must be statically defined and its + // type must not be ``EDS``. + repeated string cluster_names = 2; + + // Multiple gRPC services be provided for GRPC. If > 1 cluster is defined, + // services will be cycled through if any kind of failure occurs. + repeated GrpcService grpc_services = 4; + + // For REST APIs, the delay between successive polls. + google.protobuf.Duration refresh_delay = 3; + + // For REST APIs, the request timeout. If not set, a default value of 1s will be used. + google.protobuf.Duration request_timeout = 5 [(validate.rules).duration.gt.seconds = 0]; + + // For GRPC APIs, the rate limit settings. If present, discovery requests made by Envoy will be + // rate limited. + RateLimitSettings rate_limit_settings = 6; + + // Skip the node identifier in subsequent discovery requests for streaming gRPC config types. + bool set_node_on_first_message_only = 7; +} + +// Aggregated Discovery Service (ADS) options. This is currently empty, but when +// set in :ref:`ConfigSource ` can be used to +// specify that ADS is to be used. +message AggregatedConfigSource { +} + +// Rate Limit settings to be applied for discovery requests made by Envoy. +message RateLimitSettings { + // Maximum number of tokens to be used for rate limiting discovery request calls. If not set, a + // default value of 100 will be used. + google.protobuf.UInt32Value max_tokens = 1; + + // Rate at which tokens will be filled per second. If not set, a default fill rate of 10 tokens + // per second will be used. + google.protobuf.DoubleValue fill_rate = 2 [(validate.rules).double.gt = 0.0]; +} + +// Configuration for :ref:`listeners `, :ref:`clusters +// `, :ref:`routes +// `, :ref:`endpoints +// ` etc. may either be sourced from the +// filesystem or from an xDS API source. Filesystem configs are watched with +// inotify for updates. +message ConfigSource { + oneof config_source_specifier { + option (validate.required) = true; + // Path on the filesystem to source and watch for configuration updates. + // + // .. note:: + // + // The path to the source must exist at config load time. + // + // .. note:: + // + // Envoy will only watch the file path for *moves.* This is because in general only moves + // are atomic. The same method of swapping files as is demonstrated in the + // :ref:`runtime documentation ` can be used here also. + string path = 1; + // API configuration source. + ApiConfigSource api_config_source = 2; + // When set, ADS will be used to fetch resources. The ADS API configuration + // source in the bootstrap configuration is used. + AggregatedConfigSource ads = 3; + } + + // When this timeout is specified, Envoy will wait no longer than the specified time for first + // config response on this xDS subscription during the :ref:`initialization process + // `. After reaching the timeout, Envoy will move to the next + // initialization phase, even if the first config is not delivered yet. The timer is activated + // when the xDS API subscription starts, and is disarmed on first config update or on error. 0 + // means no timeout - Envoy will wait indefinitely for the first xDS config (unless another + // timeout applies). The default is 15s. + google.protobuf.Duration initial_fetch_timeout = 4; +} diff --git a/api/envoy/api/v3alpha/core/grpc_service.proto b/api/envoy/api/v3alpha/core/grpc_service.proto new file mode 100644 index 0000000000..dd8b90d72c --- /dev/null +++ b/api/envoy/api/v3alpha/core/grpc_service.proto @@ -0,0 +1,170 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.core; + +option java_outer_classname = "GrpcServiceProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.core"; + +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/empty.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: gRPC services] + +// gRPC service configuration. This is used by :ref:`ApiConfigSource +// ` and filter configurations. +message GrpcService { + message EnvoyGrpc { + // The name of the upstream gRPC cluster. SSL credentials will be supplied + // in the :ref:`Cluster ` :ref:`tls_context + // `. + string cluster_name = 1 [(validate.rules).string.min_bytes = 1]; + } + + // [#proto-status: draft] + message GoogleGrpc { + // The target URI when using the `Google C++ gRPC client + // `_. SSL credentials will be supplied in + // :ref:`channel_credentials `. + string target_uri = 1 [(validate.rules).string.min_bytes = 1]; + + // See https://grpc.io/grpc/cpp/structgrpc_1_1_ssl_credentials_options.html. + message SslCredentials { + // PEM encoded server root certificates. + DataSource root_certs = 1; + + // PEM encoded client private key. + DataSource private_key = 2; + + // PEM encoded client certificate chain. + DataSource cert_chain = 3; + } + + // Local channel credentials. Only UDS is supported for now. + // See https://github.com/grpc/grpc/pull/15909. + message GoogleLocalCredentials { + } + + // See https://grpc.io/docs/guides/auth.html#credential-types to understand Channel and Call + // credential types. + message ChannelCredentials { + oneof credential_specifier { + option (validate.required) = true; + SslCredentials ssl_credentials = 1; + + // https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 + google.protobuf.Empty google_default = 2; + + GoogleLocalCredentials local_credentials = 3; + } + } + + ChannelCredentials channel_credentials = 2; + + message CallCredentials { + message ServiceAccountJWTAccessCredentials { + string json_key = 1; + uint64 token_lifetime_seconds = 2; + } + + message GoogleIAMCredentials { + string authorization_token = 1; + string authority_selector = 2; + } + + message MetadataCredentialsFromPlugin { + string name = 1; + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } + } + + oneof credential_specifier { + option (validate.required) = true; + + // Access token credentials. + // https://grpc.io/grpc/cpp/namespacegrpc.html#ad3a80da696ffdaea943f0f858d7a360d. + string access_token = 1; + + // Google Compute Engine credentials. + // https://grpc.io/grpc/cpp/namespacegrpc.html#a6beb3ac70ff94bd2ebbd89b8f21d1f61 + google.protobuf.Empty google_compute_engine = 2; + + // Google refresh token credentials. + // https://grpc.io/grpc/cpp/namespacegrpc.html#a96901c997b91bc6513b08491e0dca37c. + string google_refresh_token = 3; + + // Service Account JWT Access credentials. + // https://grpc.io/grpc/cpp/namespacegrpc.html#a92a9f959d6102461f66ee973d8e9d3aa. + ServiceAccountJWTAccessCredentials service_account_jwt_access = 4; + + // Google IAM credentials. + // https://grpc.io/grpc/cpp/namespacegrpc.html#a9fc1fc101b41e680d47028166e76f9d0. + GoogleIAMCredentials google_iam = 5; + + // Custom authenticator credentials. + // https://grpc.io/grpc/cpp/namespacegrpc.html#a823c6a4b19ffc71fb33e90154ee2ad07. + // https://grpc.io/docs/guides/auth.html#extending-grpc-to-support-other-authentication-mechanisms. + MetadataCredentialsFromPlugin from_plugin = 6; + } + } + + // A set of call credentials that can be composed with `channel credentials + // `_. + repeated CallCredentials call_credentials = 3; + + // The human readable prefix to use when emitting statistics for the gRPC + // service. + // + // .. csv-table:: + // :header: Name, Type, Description + // :widths: 1, 1, 2 + // + // streams_total, Counter, Total number of streams opened + // streams_closed_, Counter, Total streams closed with + string stat_prefix = 4 [(validate.rules).string.min_bytes = 1]; + + // The name of the Google gRPC credentials factory to use. This must have been registered with + // Envoy. If this is empty, a default credentials factory will be used that sets up channel + // credentials based on other configuration parameters. + string credentials_factory_name = 5; + + // Additional configuration for site-specific customizations of the Google + // gRPC library. + google.protobuf.Struct config = 6; + } + + oneof target_specifier { + option (validate.required) = true; + + // Envoy's in-built gRPC client. + // See the :ref:`gRPC services overview ` + // documentation for discussion on gRPC client selection. + EnvoyGrpc envoy_grpc = 1; + + // `Google C++ gRPC client `_ + // See the :ref:`gRPC services overview ` + // documentation for discussion on gRPC client selection. + GoogleGrpc google_grpc = 2; + } + + // The timeout for the gRPC request. This is the timeout for a specific + // request. + google.protobuf.Duration timeout = 3; + + // Field 4 reserved due to moving credentials inside the GoogleGrpc message + reserved 4; + + // Additional metadata to include in streams initiated to the GrpcService. + // This can be used for scenarios in which additional ad hoc authorization + // headers (e.g. `x-foo-bar: baz-key`) are to be injected. + repeated HeaderValue initial_metadata = 5; +} diff --git a/api/envoy/api/v3alpha/core/health_check.proto b/api/envoy/api/v3alpha/core/health_check.proto new file mode 100644 index 0000000000..5000918a40 --- /dev/null +++ b/api/envoy/api/v3alpha/core/health_check.proto @@ -0,0 +1,262 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.core; + +option java_outer_classname = "HealthCheckProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.core"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/type/range.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Health check] +// * Health checking :ref:`architecture overview `. +// * If health checking is configured for a cluster, additional statistics are emitted. They are +// documented :ref:`here `. + +message HealthCheck { + // The time to wait for a health check response. If the timeout is reached the + // health check attempt will be considered a failure. + google.protobuf.Duration timeout = 1 [(validate.rules).duration = { + required: true, + gt: {seconds: 0} + }]; + + // The interval between health checks. + google.protobuf.Duration interval = 2 [(validate.rules).duration = { + required: true, + gt: {seconds: 0} + }]; + + // An optional jitter amount in milliseconds. If specified, Envoy will start health + // checking after for a random time in ms between 0 and initial_jitter. This only + // applies to the first health check. + google.protobuf.Duration initial_jitter = 20; + + // An optional jitter amount in milliseconds. If specified, during every + // interval Envoy will add interval_jitter to the wait time. + google.protobuf.Duration interval_jitter = 3; + + // An optional jitter amount as a percentage of interval_ms. If specified, + // during every interval Envoy will add interval_ms * + // interval_jitter_percent / 100 to the wait time. + // + // If interval_jitter_ms and interval_jitter_percent are both set, both of + // them will be used to increase the wait time. + uint32 interval_jitter_percent = 18; + + // The number of unhealthy health checks required before a host is marked + // unhealthy. Note that for *http* health checking if a host responds with 503 + // this threshold is ignored and the host is considered unhealthy immediately. + google.protobuf.UInt32Value unhealthy_threshold = 4; + + // The number of healthy health checks required before a host is marked + // healthy. Note that during startup, only a single successful health check is + // required to mark a host healthy. + google.protobuf.UInt32Value healthy_threshold = 5; + + // [#not-implemented-hide:] Non-serving port for health checking. + google.protobuf.UInt32Value alt_port = 6; + + // Reuse health check connection between health checks. Default is true. + google.protobuf.BoolValue reuse_connection = 7; + + // Describes the encoding of the payload bytes in the payload. + message Payload { + oneof payload { + option (validate.required) = true; + + // Hex encoded payload. E.g., "000000FF". + string text = 1 [(validate.rules).string.min_bytes = 1]; + + // [#not-implemented-hide:] Binary payload. + bytes binary = 2; + } + } + + // [#comment:next free field: 10] + message HttpHealthCheck { + // The value of the host header in the HTTP health check request. If + // left empty (default value), the name of the cluster this health check is associated + // with will be used. + string host = 1; + + // Specifies the HTTP path that will be requested during health checking. For example + // */healthcheck*. + string path = 2 [(validate.rules).string.min_bytes = 1]; + + // [#not-implemented-hide:] HTTP specific payload. + Payload send = 3; + + // [#not-implemented-hide:] HTTP specific response. + Payload receive = 4; + + // An optional service name parameter which is used to validate the identity of + // the health checked cluster. See the :ref:`architecture overview + // ` for more information. + string service_name = 5; + + // Specifies a list of HTTP headers that should be added to each request that is sent to the + // health checked cluster. For more information, including details on header value syntax, see + // the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption request_headers_to_add = 6 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of HTTP headers that should be removed from each request that is sent to the + // health checked cluster. + repeated string request_headers_to_remove = 8; + + // If set, health checks will be made using http/2. + bool use_http2 = 7; + + // Specifies a list of HTTP response statuses considered healthy. If provided, replaces default + // 200-only policy - 200 must be included explicitly as needed. Ranges follow half-open + // semantics of :ref:`Int64Range `. + repeated envoy.type.Int64Range expected_statuses = 9; + } + + message TcpHealthCheck { + // Empty payloads imply a connect-only health check. + Payload send = 1; + + // When checking the response, “fuzzy” matching is performed such that each + // binary block must be found, and in the order specified, but not + // necessarily contiguous. + repeated Payload receive = 2; + } + + message RedisHealthCheck { + // If set, optionally perform ``EXISTS `` instead of ``PING``. A return value + // from Redis of 0 (does not exist) is considered a passing healthcheck. A return value other + // than 0 is considered a failure. This allows the user to mark a Redis instance for maintenance + // by setting the specified key to any value and waiting for traffic to drain. + string key = 1; + } + + // `grpc.health.v1.Health + // `_-based + // healthcheck. See `gRPC doc `_ + // for details. + message GrpcHealthCheck { + // An optional service name parameter which will be sent to gRPC service in + // `grpc.health.v1.HealthCheckRequest + // `_. + // message. See `gRPC health-checking overview + // `_ for more information. + string service_name = 1; + + // The value of the :authority header in the gRPC health check request. If + // left empty (default value), the name of the cluster this health check is associated + // with will be used. + string authority = 2; + } + + // Custom health check. + message CustomHealthCheck { + // The registered name of the custom health checker. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // A custom health checker specific configuration which depends on the custom health checker + // being instantiated. See :api:`envoy/config/health_checker` for reference. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } + } + + oneof health_checker { + option (validate.required) = true; + + // HTTP health check. + HttpHealthCheck http_health_check = 8; + + // TCP health check. + TcpHealthCheck tcp_health_check = 9; + + // gRPC health check. + GrpcHealthCheck grpc_health_check = 11; + + // Custom health check. + CustomHealthCheck custom_health_check = 13; + } + + reserved 10; // redis_health_check is deprecated by :ref:`custom_health_check + // ` + reserved "redis_health_check"; + + // The "no traffic interval" is a special health check interval that is used when a cluster has + // never had traffic routed to it. This lower interval allows cluster information to be kept up to + // date, without sending a potentially large amount of active health checking traffic for no + // reason. Once a cluster has been used for traffic routing, Envoy will shift back to using the + // standard health check interval that is defined. Note that this interval takes precedence over + // any other. + // + // The default value for "no traffic interval" is 60 seconds. + google.protobuf.Duration no_traffic_interval = 12 [(validate.rules).duration.gt = {}]; + + // The "unhealthy interval" is a health check interval that is used for hosts that are marked as + // unhealthy. As soon as the host is marked as healthy, Envoy will shift back to using the + // standard health check interval that is defined. + // + // The default value for "unhealthy interval" is the same as "interval". + google.protobuf.Duration unhealthy_interval = 14 [(validate.rules).duration.gt = {}]; + + // The "unhealthy edge interval" is a special health check interval that is used for the first + // health check right after a host is marked as unhealthy. For subsequent health checks + // Envoy will shift back to using either "unhealthy interval" if present or the standard health + // check interval that is defined. + // + // The default value for "unhealthy edge interval" is the same as "unhealthy interval". + google.protobuf.Duration unhealthy_edge_interval = 15 [(validate.rules).duration.gt = {}]; + + // The "healthy edge interval" is a special health check interval that is used for the first + // health check right after a host is marked as healthy. For subsequent health checks + // Envoy will shift back to using the standard health check interval that is defined. + // + // The default value for "healthy edge interval" is the same as the default interval. + google.protobuf.Duration healthy_edge_interval = 16 [(validate.rules).duration.gt = {}]; + + // Specifies the path to the :ref:`health check event log `. + // If empty, no event log will be written. + string event_log_path = 17; + + // If set to true, health check failure events will always be logged. If set to false, only the + // initial health check failure event will be logged. + // The default value is false. + bool always_log_health_check_failures = 19; +} + +// Endpoint health status. +enum HealthStatus { + // The health status is not known. This is interpreted by Envoy as *HEALTHY*. + UNKNOWN = 0; + + // Healthy. + HEALTHY = 1; + + // Unhealthy. + UNHEALTHY = 2; + + // Connection draining in progress. E.g., + // ``_ + // or + // ``_. + // This is interpreted by Envoy as *UNHEALTHY*. + DRAINING = 3; + + // Health check timed out. This is part of HDS and is interpreted by Envoy as + // *UNHEALTHY*. + TIMEOUT = 4; + + // Degraded. + DEGRADED = 5; +} diff --git a/api/envoy/api/v3alpha/core/http_uri.proto b/api/envoy/api/v3alpha/core/http_uri.proto new file mode 100644 index 0000000000..9c8e85b441 --- /dev/null +++ b/api/envoy/api/v3alpha/core/http_uri.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.core; + +option java_outer_classname = "HttpUriProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.core"; + +import "google/protobuf/duration.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: HTTP Service URI ] + +// Envoy external URI descriptor +message HttpUri { + // The HTTP server URI. It should be a full FQDN with protocol, host and path. + // + // Example: + // + // .. code-block:: yaml + // + // uri: https://www.googleapis.com/oauth2/v1/certs + // + string uri = 1 [(validate.rules).string.min_bytes = 1]; + + // Specify how `uri` is to be fetched. Today, this requires an explicit + // cluster, but in the future we may support dynamic cluster creation or + // inline DNS resolution. See `issue + // `_. + oneof http_upstream_type { + option (validate.required) = true; + // A cluster is created in the Envoy "cluster_manager" config + // section. This field specifies the cluster name. + // + // Example: + // + // .. code-block:: yaml + // + // cluster: jwks_cluster + // + string cluster = 2 [(validate.rules).string.min_bytes = 1]; + } + + // Sets the maximum duration in milliseconds that a response can take to arrive upon request. + google.protobuf.Duration timeout = 3 + [(validate.rules).duration.gte = {}, (validate.rules).duration.required = true]; +} diff --git a/api/envoy/api/v3alpha/core/protocol.proto b/api/envoy/api/v3alpha/core/protocol.proto new file mode 100644 index 0000000000..b0b29e630e --- /dev/null +++ b/api/envoy/api/v3alpha/core/protocol.proto @@ -0,0 +1,154 @@ +// [#protodoc-title: Protocol options] + +syntax = "proto3"; + +package envoy.api.v3alpha.core; + +option java_outer_classname = "ProtocolProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.core"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Protocol options] + +// [#not-implemented-hide:] +message TcpProtocolOptions { +} + +message HttpProtocolOptions { + // The idle timeout for upstream connection pool connections. The idle timeout is defined as the + // period in which there are no active requests. If not set, there is no idle timeout. When the + // idle timeout is reached the connection will be closed. Note that request based timeouts mean + // that HTTP/2 PINGs will not keep the connection alive. + google.protobuf.Duration idle_timeout = 1; +} + +message Http1ProtocolOptions { + // Handle HTTP requests with absolute URLs in the requests. These requests + // are generally sent by clients to forward/explicit proxies. This allows clients to configure + // envoy as their HTTP proxy. In Unix, for example, this is typically done by setting the + // *http_proxy* environment variable. + google.protobuf.BoolValue allow_absolute_url = 1; + + // Handle incoming HTTP/1.0 and HTTP 0.9 requests. + // This is off by default, and not fully standards compliant. There is support for pre-HTTP/1.1 + // style connect logic, dechunking, and handling lack of client host iff + // *default_host_for_http_10* is configured. + bool accept_http_10 = 2; + + // A default host for HTTP/1.0 requests. This is highly suggested if *accept_http_10* is true as + // Envoy does not otherwise support HTTP/1.0 without a Host header. + // This is a no-op if *accept_http_10* is not true. + string default_host_for_http_10 = 3; +} + +// [#comment:next free field: 13] +message Http2ProtocolOptions { + // `Maximum table size `_ + // (in octets) that the encoder is permitted to use for the dynamic HPACK table. Valid values + // range from 0 to 4294967295 (2^32 - 1) and defaults to 4096. 0 effectively disables header + // compression. + google.protobuf.UInt32Value hpack_table_size = 1; + + // `Maximum concurrent streams `_ + // allowed for peer on one HTTP/2 connection. Valid values range from 1 to 2147483647 (2^31 - 1) + // and defaults to 2147483647. + google.protobuf.UInt32Value max_concurrent_streams = 2 + [(validate.rules).uint32 = {gte: 1, lte: 2147483647}]; + + // `Initial stream-level flow-control window + // `_ size. Valid values range from 65535 + // (2^16 - 1, HTTP/2 default) to 2147483647 (2^31 - 1, HTTP/2 maximum) and defaults to 268435456 + // (256 * 1024 * 1024). + // + // NOTE: 65535 is the initial window size from HTTP/2 spec. We only support increasing the default + // window size now, so it's also the minimum. + + // This field also acts as a soft limit on the number of bytes Envoy will buffer per-stream in the + // HTTP/2 codec buffers. Once the buffer reaches this pointer, watermark callbacks will fire to + // stop the flow of data to the codec buffers. + google.protobuf.UInt32Value initial_stream_window_size = 3 + [(validate.rules).uint32 = {gte: 65535, lte: 2147483647}]; + + // Similar to *initial_stream_window_size*, but for connection-level flow-control + // window. Currently, this has the same minimum/maximum/default as *initial_stream_window_size*. + google.protobuf.UInt32Value initial_connection_window_size = 4 + [(validate.rules).uint32 = {gte: 65535, lte: 2147483647}]; + + // Allows proxying Websocket and other upgrades over H2 connect. + bool allow_connect = 5; + + // [#not-implemented-hide:] Hiding until envoy has full metadata support. + // Still under implementation. DO NOT USE. + // + // Allows metadata. See [metadata + // docs](https://github.com/envoyproxy/envoy/blob/master/source/docs/h2_metadata.md) for more + // information. + bool allow_metadata = 6; + + // Limit the number of pending outbound downstream frames of all types (frames that are waiting to + // be written into the socket). Exceeding this limit triggers flood mitigation and connection is + // terminated. The ``http2.outbound_flood`` stat tracks the number of terminated connections due + // to flood mitigation. The default limit is 10000. + // [#comment:TODO: implement same limits for upstream outbound frames as well.] + google.protobuf.UInt32Value max_outbound_frames = 7 [(validate.rules).uint32 = {gte: 1}]; + + // Limit the number of pending outbound downstream frames of types PING, SETTINGS and RST_STREAM, + // preventing high memory utilization when receiving continuous stream of these frames. Exceeding + // this limit triggers flood mitigation and connection is terminated. The + // ``http2.outbound_control_flood`` stat tracks the number of terminated connections due to flood + // mitigation. The default limit is 1000. + // [#comment:TODO: implement same limits for upstream outbound frames as well.] + google.protobuf.UInt32Value max_outbound_control_frames = 8 [(validate.rules).uint32 = {gte: 1}]; + + // Limit the number of consecutive inbound frames of types HEADERS, CONTINUATION and DATA with an + // empty payload and no end stream flag. Those frames have no legitimate use and are abusive, but + // might be a result of a broken HTTP/2 implementation. The `http2.inbound_empty_frames_flood`` + // stat tracks the number of connections terminated due to flood mitigation. + // Setting this to 0 will terminate connection upon receiving first frame with an empty payload + // and no end stream flag. The default limit is 1. + // [#comment:TODO: implement same limits for upstream inbound frames as well.] + google.protobuf.UInt32Value max_consecutive_inbound_frames_with_empty_payload = 9; + + // Limit the number of inbound PRIORITY frames allowed per each opened stream. If the number + // of PRIORITY frames received over the lifetime of connection exceeds the value calculated + // using this formula:: + // + // max_inbound_priority_frames_per_stream * (1 + inbound_streams) + // + // the connection is terminated. The ``http2.inbound_priority_frames_flood`` stat tracks + // the number of connections terminated due to flood mitigation. The default limit is 100. + // [#comment:TODO: implement same limits for upstream inbound frames as well.] + google.protobuf.UInt32Value max_inbound_priority_frames_per_stream = 10; + + // Limit the number of inbound WINDOW_UPDATE frames allowed per DATA frame sent. If the number + // of WINDOW_UPDATE frames received over the lifetime of connection exceeds the value calculated + // using this formula:: + // + // 1 + 2 * (inbound_streams + + // max_inbound_window_update_frames_per_data_frame_sent * outbound_data_frames) + // + // the connection is terminated. The ``http2.inbound_priority_frames_flood`` stat tracks + // the number of connections terminated due to flood mitigation. The default limit is 10. + // Setting this to 1 should be enough to support HTTP/2 implementations with basic flow control, + // but more complex implementations that try to estimate available bandwidth require at least 2. + // [#comment:TODO: implement same limits for upstream inbound frames as well.] + google.protobuf.UInt32Value max_inbound_window_update_frames_per_data_frame_sent = 11 + [(validate.rules).uint32 = {gte: 1}]; + + // Allows invalid HTTP messaging and headers. When this option is disabled (default), then + // the whole HTTP/2 connection is terminated upon receiving invalid HEADERS frame. However, + // when this option is enabled, only the offending stream is terminated. + // + // See [RFC7540, sec. 8.1](https://tools.ietf.org/html/rfc7540#section-8.1) for details. + bool stream_error_on_invalid_http_messaging = 12; +} + +// [#not-implemented-hide:] +message GrpcProtocolOptions { + Http2ProtocolOptions http2_protocol_options = 1; +} diff --git a/api/envoy/api/v3alpha/discovery.proto b/api/envoy/api/v3alpha/discovery.proto new file mode 100644 index 0000000000..1547b03006 --- /dev/null +++ b/api/envoy/api/v3alpha/discovery.proto @@ -0,0 +1,225 @@ +syntax = "proto3"; + +package envoy.api.v3alpha; + +option java_outer_classname = "DiscoveryProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/any.proto"; +import "google/rpc/status.proto"; + +// [#protodoc-title: Common discovery API components] + +// A DiscoveryRequest requests a set of versioned resources of the same type for +// a given Envoy node on some API. +message DiscoveryRequest { + // The version_info provided in the request messages will be the version_info + // received with the most recent successfully processed response or empty on + // the first request. It is expected that no new request is sent after a + // response is received until the Envoy instance is ready to ACK/NACK the new + // configuration. ACK/NACK takes place by returning the new API config version + // as applied or the previous API config version respectively. Each type_url + // (see below) has an independent version associated with it. + string version_info = 1; + + // The node making the request. + core.Node node = 2; + + // List of resources to subscribe to, e.g. list of cluster names or a route + // configuration name. If this is empty, all resources for the API are + // returned. LDS/CDS may have empty resource_names, which will cause all + // resources for the Envoy instance to be returned. The LDS and CDS responses + // will then imply a number of resources that need to be fetched via EDS/RDS, + // which will be explicitly enumerated in resource_names. + repeated string resource_names = 3; + + // Type of the resource that is being requested, e.g. + // "type.googleapis.com/envoy.api.v3alpha.ClusterLoadAssignment". This is implicit + // in requests made via singleton xDS APIs such as CDS, LDS, etc. but is + // required for ADS. + string type_url = 4; + + // nonce corresponding to DiscoveryResponse being ACK/NACKed. See above + // discussion on version_info and the DiscoveryResponse nonce comment. This + // may be empty if no nonce is available, e.g. at startup or for non-stream + // xDS implementations. + string response_nonce = 5; + + // This is populated when the previous :ref:`DiscoveryResponse ` + // failed to update configuration. The *message* field in *error_details* provides the Envoy + // internal exception related to the failure. It is only intended for consumption during manual + // debugging, the string provided is not guaranteed to be stable across Envoy versions. + google.rpc.Status error_detail = 6; +} + +message DiscoveryResponse { + // The version of the response data. + string version_info = 1; + + // The response resources. These resources are typed and depend on the API being called. + repeated google.protobuf.Any resources = 2; + + // [#not-implemented-hide:] + // Canary is used to support two Envoy command line flags: + // + // * --terminate-on-canary-transition-failure. When set, Envoy is able to + // terminate if it detects that configuration is stuck at canary. Consider + // this example sequence of updates: + // - Management server applies a canary config successfully. + // - Management server rolls back to a production config. + // - Envoy rejects the new production config. + // Since there is no sensible way to continue receiving configuration + // updates, Envoy will then terminate and apply production config from a + // clean slate. + // * --dry-run-canary. When set, a canary response will never be applied, only + // validated via a dry run. + bool canary = 3; + + // Type URL for resources. Identifies the xDS API when muxing over ADS. + // Must be consistent with the type_url in the 'resources' repeated Any (if non-empty). + string type_url = 4; + + // For gRPC based subscriptions, the nonce provides a way to explicitly ack a + // specific DiscoveryResponse in a following DiscoveryRequest. Additional + // messages may have been sent by Envoy to the management server for the + // previous version on the stream prior to this DiscoveryResponse, that were + // unprocessed at response send time. The nonce allows the management server + // to ignore any further DiscoveryRequests for the previous version until a + // DiscoveryRequest bearing the nonce. The nonce is optional and is not + // required for non-stream based xDS implementations. + string nonce = 5; + + // [#not-implemented-hide:] + // The control plane instance that sent the response. + core.ControlPlane control_plane = 6; +} + +// DeltaDiscoveryRequest and DeltaDiscoveryResponse are used in a new gRPC +// endpoint for Delta xDS. +// +// With Delta xDS, the DeltaDiscoveryResponses do not need to include a full +// snapshot of the tracked resources. Instead, DeltaDiscoveryResponses are a +// diff to the state of a xDS client. +// In Delta XDS there are per-resource versions, which allow tracking state at +// the resource granularity. +// An xDS Delta session is always in the context of a gRPC bidirectional +// stream. This allows the xDS server to keep track of the state of xDS clients +// connected to it. +// +// In Delta xDS the nonce field is required and used to pair +// DeltaDiscoveryResponse to a DeltaDiscoveryRequest ACK or NACK. +// Optionally, a response message level system_version_info is present for +// debugging purposes only. +// +// DeltaDiscoveryRequest plays two independent roles. Any DeltaDiscoveryRequest +// can be either or both of: [1] informing the server of what resources the +// client has gained/lost interest in (using resource_names_subscribe and +// resource_names_unsubscribe), or [2] (N)ACKing an earlier resource update from +// the server (using response_nonce, with presence of error_detail making it a NACK). +// Additionally, the first message (for a given type_url) of a reconnected gRPC stream +// has a third role: informing the server of the resources (and their versions) +// that the client already possesses, using the initial_resource_versions field. +// +// As with state-of-the-world, when multiple resource types are multiplexed (ADS), +// all requests/acknowledgments/updates are logically walled off by type_url: +// a Cluster ACK exists in a completely separate world from a prior Route NACK. +// In particular, initial_resource_versions being sent at the "start" of every +// gRPC stream actually entails a message for each type_url, each with its own +// initial_resource_versions. +message DeltaDiscoveryRequest { + // The node making the request. + core.Node node = 1; + + // Type of the resource that is being requested, e.g. + // "type.googleapis.com/envoy.api.v3alpha.ClusterLoadAssignment". + string type_url = 2; + + // DeltaDiscoveryRequests allow the client to add or remove individual + // resources to the set of tracked resources in the context of a stream. + // All resource names in the resource_names_subscribe list are added to the + // set of tracked resources and all resource names in the resource_names_unsubscribe + // list are removed from the set of tracked resources. + // + // *Unlike* state-of-the-world xDS, an empty resource_names_subscribe or + // resource_names_unsubscribe list simply means that no resources are to be + // added or removed to the resource list. + // *Like* state-of-the-world xDS, the server must send updates for all tracked + // resources, but can also send updates for resources the client has not subscribed to. + // + // NOTE: the server must respond with all resources listed in resource_names_subscribe, + // even if it believes the client has the most recent version of them. The reason: + // the client may have dropped them, but then regained interest before it had a chance + // to send the unsubscribe message. See DeltaSubscriptionStateTest.RemoveThenAdd. + // + // These two fields can be set in any DeltaDiscoveryRequest, including ACKs + // and initial_resource_versions. + // + // A list of Resource names to add to the list of tracked resources. + repeated string resource_names_subscribe = 3; + + // A list of Resource names to remove from the list of tracked resources. + repeated string resource_names_unsubscribe = 4; + + // Informs the server of the versions of the resources the xDS client knows of, to enable the + // client to continue the same logical xDS session even in the face of gRPC stream reconnection. + // It will not be populated: [1] in the very first stream of a session, since the client will + // not yet have any resources, [2] in any message after the first in a stream (for a given + // type_url), since the server will already be correctly tracking the client's state. + // (In ADS, the first message *of each type_url* of a reconnected stream populates this map.) + // The map's keys are names of xDS resources known to the xDS client. + // The map's values are opaque resource versions. + map initial_resource_versions = 5; + + // When the DeltaDiscoveryRequest is a ACK or NACK message in response + // to a previous DeltaDiscoveryResponse, the response_nonce must be the + // nonce in the DeltaDiscoveryResponse. + // Otherwise response_nonce must be omitted. + string response_nonce = 6; + + // This is populated when the previous :ref:`DiscoveryResponse ` + // failed to update configuration. The *message* field in *error_details* + // provides the Envoy internal exception related to the failure. + google.rpc.Status error_detail = 7; +} + +message DeltaDiscoveryResponse { + // The version of the response data (used for debugging). + string system_version_info = 1; + + // The response resources. These are typed resources, whose types must match + // the type_url field. + repeated Resource resources = 2; + + // field id 3 IS available! + + // Type URL for resources. Identifies the xDS API when muxing over ADS. + // Must be consistent with the type_url in the Any within 'resources' if 'resources' is non-empty. + string type_url = 4; + + // Resources names of resources that have be deleted and to be removed from the xDS Client. + // Removed resources for missing resources can be ignored. + repeated string removed_resources = 6; + + // The nonce provides a way for DeltaDiscoveryRequests to uniquely + // reference a DeltaDiscoveryResponse when (N)ACKing. The nonce is required. + string nonce = 5; +} + +message Resource { + // The resource's name, to distinguish it from others of the same type of resource. + string name = 3; + + // [#not-implemented-hide:] + // The aliases are a list of other names that this resource can go by. + repeated string aliases = 4; + + // The resource level version. It allows xDS to track the state of individual + // resources. + string version = 1; + + // The resource being tracked. + google.protobuf.Any resource = 2; +} diff --git a/api/envoy/api/v3alpha/eds.proto b/api/envoy/api/v3alpha/eds.proto new file mode 100644 index 0000000000..da149db20b --- /dev/null +++ b/api/envoy/api/v3alpha/eds.proto @@ -0,0 +1,131 @@ +syntax = "proto3"; + +package envoy.api.v3alpha; + +option java_outer_classname = "EdsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha"; + +option java_generic_services = true; + +import "envoy/api/v3alpha/discovery.proto"; +import "envoy/api/v3alpha/endpoint/endpoint.proto"; +import "envoy/type/percent.proto"; + +import "google/api/annotations.proto"; + +import "validate/validate.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; + +// [#protodoc-title: EDS] +// Endpoint discovery :ref:`architecture overview ` + +service EndpointDiscoveryService { + // The resource_names field in DiscoveryRequest specifies a list of clusters + // to subscribe to updates for. + rpc StreamEndpoints(stream DiscoveryRequest) returns (stream DiscoveryResponse) { + } + + rpc DeltaEndpoints(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) { + } + + rpc FetchEndpoints(DiscoveryRequest) returns (DiscoveryResponse) { + option (google.api.http) = { + post: "/v3alpha/discovery:endpoints" + body: "*" + }; + } +} + +// Each route from RDS will map to a single cluster or traffic split across +// clusters using weights expressed in the RDS WeightedCluster. +// +// With EDS, each cluster is treated independently from a LB perspective, with +// LB taking place between the Localities within a cluster and at a finer +// granularity between the hosts within a locality. The percentage of traffic +// for each endpoint is determined by both its load_balancing_weight, and the +// load_balancing_weight of its locality. First, a locality will be selected, +// then an endpoint within that locality will be chose based on its weight. +message ClusterLoadAssignment { + // Name of the cluster. This will be the :ref:`service_name + // ` value if specified + // in the cluster :ref:`EdsClusterConfig + // `. + string cluster_name = 1 [(validate.rules).string.min_bytes = 1]; + + // List of endpoints to load balance to. + repeated endpoint.LocalityLbEndpoints endpoints = 2; + + // Map of named endpoints that can be referenced in LocalityLbEndpoints. + map named_endpoints = 5; + + // Load balancing policy settings. + message Policy { + reserved 1; + + message DropOverload { + // Identifier for the policy specifying the drop. + string category = 1 [(validate.rules).string.min_bytes = 1]; + + // Percentage of traffic that should be dropped for the category. + envoy.type.FractionalPercent drop_percentage = 2; + } + // Action to trim the overall incoming traffic to protect the upstream + // hosts. This action allows protection in case the hosts are unable to + // recover from an outage, or unable to autoscale or unable to handle + // incoming traffic volume for any reason. + // + // At the client each category is applied one after the other to generate + // the 'actual' drop percentage on all outgoing traffic. For example: + // + // .. code-block:: json + // + // { "drop_overloads": [ + // { "category": "throttle", "drop_percentage": 60 } + // { "category": "lb", "drop_percentage": 50 } + // ]} + // + // The actual drop percentages applied to the traffic at the clients will be + // "throttle"_drop = 60% + // "lb"_drop = 20% // 50% of the remaining 'actual' load, which is 40%. + // actual_outgoing_load = 20% // remaining after applying all categories. + repeated DropOverload drop_overloads = 2; + + // Priority levels and localities are considered overprovisioned with this + // factor (in percentage). This means that we don't consider a priority + // level or locality unhealthy until the percentage of healthy hosts + // multiplied by the overprovisioning factor drops below 100. + // With the default value 140(1.4), Envoy doesn't consider a priority level + // or a locality unhealthy until their percentage of healthy hosts drops + // below 72%. For example: + // + // .. code-block:: json + // + // { "overprovisioning_factor": 100 } + // + // Read more at :ref:`priority levels ` and + // :ref:`localities `. + google.protobuf.UInt32Value overprovisioning_factor = 3 [(validate.rules).uint32.gt = 0]; + + // The max time until which the endpoints from this assignment can be used. + // If no new assignments are received before this time expires the endpoints + // are considered stale and should be marked unhealthy. + // Defaults to 0 which means endpoints never go stale. + google.protobuf.Duration endpoint_stale_after = 4 [(validate.rules).duration.gt.seconds = 0]; + + // The flag to disable overprovisioning. If it is set to true, + // :ref:`overprovisioning factor + // ` will be ignored + // and Envoy will not perform graceful failover between priority levels or + // localities as endpoints become unhealthy. Otherwise Envoy will perform + // graceful failover as :ref:`overprovisioning factor + // ` suggests. + // [#next-major-version: Unify with overprovisioning config as a single message.] + // [#not-implemented-hide:] + bool disable_overprovisioning = 5; + } + + // Load balancing policy settings. + Policy policy = 4; +} diff --git a/api/envoy/api/v3alpha/endpoint/BUILD b/api/envoy/api/v3alpha/endpoint/BUILD new file mode 100644 index 0000000000..733560514d --- /dev/null +++ b/api/envoy/api/v3alpha/endpoint/BUILD @@ -0,0 +1,34 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/auth", + "//envoy/api/v3alpha/core", + ], +) + +api_proto_library_internal( + name = "endpoint", + srcs = ["endpoint.proto"], + visibility = ["//envoy/api/v3alpha:friends"], + deps = [ + "//envoy/api/v3alpha/auth:cert", + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:config_source", + "//envoy/api/v3alpha/core:health_check", + "//envoy/api/v3alpha/core:protocol", + ], +) + +api_proto_library_internal( + name = "load_report", + srcs = ["load_report.proto"], + visibility = ["//envoy/api/v3alpha:friends"], + deps = [ + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + ], +) diff --git a/api/envoy/api/v3alpha/endpoint/endpoint.proto b/api/envoy/api/v3alpha/endpoint/endpoint.proto new file mode 100644 index 0000000000..62c3060dee --- /dev/null +++ b/api/envoy/api/v3alpha/endpoint/endpoint.proto @@ -0,0 +1,125 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.endpoint; + +option java_outer_classname = "EndpointProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.endpoint"; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/core/health_check.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Endpoints] + +// Upstream host identifier. +message Endpoint { + // The upstream host address. + // + // .. attention:: + // + // The form of host address depends on the given cluster type. For STATIC or EDS, + // it is expected to be a direct IP address (or something resolvable by the + // specified :ref:`resolver ` + // in the Address). For LOGICAL or STRICT DNS, it is expected to be hostname, + // and will be resolved via DNS. + core.Address address = 1; + + // The optional health check configuration. + message HealthCheckConfig { + // Optional alternative health check port value. + // + // By default the health check address port of an upstream host is the same + // as the host's serving address port. This provides an alternative health + // check port. Setting this with a non-zero value allows an upstream host + // to have different health check address port. + uint32 port_value = 1 [(validate.rules).uint32.lte = 65535]; + } + + // The optional health check configuration is used as configuration for the + // health checker to contact the health checked host. + // + // .. attention:: + // + // This takes into effect only for upstream clusters with + // :ref:`active health checking ` enabled. + HealthCheckConfig health_check_config = 2; +} + +// An Endpoint that Envoy can route traffic to. +message LbEndpoint { + // Upstream host identifier or a named reference. + oneof host_identifier { + Endpoint endpoint = 1; + string endpoint_name = 5; + } + + // Optional health status when known and supplied by EDS server. + core.HealthStatus health_status = 2; + + // The endpoint metadata specifies values that may be used by the load + // balancer to select endpoints in a cluster for a given request. The filter + // name should be specified as *envoy.lb*. An example boolean key-value pair + // is *canary*, providing the optional canary status of the upstream host. + // This may be matched against in a route's + // :ref:`RouteAction ` metadata_match field + // to subset the endpoints considered in cluster load balancing. + core.Metadata metadata = 3; + + // The optional load balancing weight of the upstream host; at least 1. + // Envoy uses the load balancing weight in some of the built in load + // balancers. The load balancing weight for an endpoint is divided by the sum + // of the weights of all endpoints in the endpoint's locality to produce a + // percentage of traffic for the endpoint. This percentage is then further + // weighted by the endpoint's locality's load balancing weight from + // LocalityLbEndpoints. If unspecified, each host is presumed to have equal + // weight in a locality. + google.protobuf.UInt32Value load_balancing_weight = 4 [(validate.rules).uint32 = {gte: 1}]; +} + +// A group of endpoints belonging to a Locality. +// One can have multiple LocalityLbEndpoints for a locality, but this is +// generally only done if the different groups need to have different load +// balancing weights or different priorities. +message LocalityLbEndpoints { + // Identifies location of where the upstream hosts run. + core.Locality locality = 1; + + // The group of endpoints belonging to the locality specified. + repeated LbEndpoint lb_endpoints = 2; + + // Optional: Per priority/region/zone/sub_zone weight; at least 1. The load + // balancing weight for a locality is divided by the sum of the weights of all + // localities at the same priority level to produce the effective percentage + // of traffic for the locality. + // + // Locality weights are only considered when :ref:`locality weighted load + // balancing ` is + // configured. These weights are ignored otherwise. If no weights are + // specified when locality weighted load balancing is enabled, the locality is + // assigned no load. + google.protobuf.UInt32Value load_balancing_weight = 3 [(validate.rules).uint32 = {gte: 1}]; + + // Optional: the priority for this LocalityLbEndpoints. If unspecified this will + // default to the highest priority (0). + // + // Under usual circumstances, Envoy will only select endpoints for the highest + // priority (0). In the event all endpoints for a particular priority are + // unavailable/unhealthy, Envoy will fail over to selecting endpoints for the + // next highest priority group. + // + // Priorities should range from 0 (highest) to N (lowest) without skipping. + uint32 priority = 5 [(validate.rules).uint32 = {lte: 128}]; + + // Optional: Per locality proximity value which indicates how close this + // locality is from the source locality. This value only provides ordering + // information (lower the value, closer it is to the source locality). + // This will be consumed by load balancing schemes that need proximity order + // to determine where to route the requests. + // [#not-implemented-hide:] + google.protobuf.UInt32Value proximity = 6; +} diff --git a/api/envoy/api/v3alpha/endpoint/load_report.proto b/api/envoy/api/v3alpha/endpoint/load_report.proto new file mode 100644 index 0000000000..9ab814d30d --- /dev/null +++ b/api/envoy/api/v3alpha/endpoint/load_report.proto @@ -0,0 +1,147 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.endpoint; + +option java_outer_classname = "LoadReportProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.endpoint"; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; + +import "validate/validate.proto"; + +// These are stats Envoy reports to GLB every so often. Report frequency is +// defined by +// :ref:`LoadStatsResponse.load_reporting_interval`. +// Stats per upstream region/zone and optionally per subzone. +// [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. +message UpstreamLocalityStats { + // Name of zone, region and optionally endpoint group these metrics were + // collected from. Zone and region names could be empty if unknown. + core.Locality locality = 1; + + // The total number of requests successfully completed by the endpoints in the + // locality. + uint64 total_successful_requests = 2; + + // The total number of unfinished requests + uint64 total_requests_in_progress = 3; + + // The total number of requests that failed due to errors at the endpoint, + // aggregated over all endpoints in the locality. + uint64 total_error_requests = 4; + + // The total number of requests that were issued by this Envoy since + // the last report. This information is aggregated over all the + // upstream endpoints in the locality. + uint64 total_issued_requests = 8; + + // Stats for multi-dimensional load balancing. + repeated EndpointLoadMetricStats load_metric_stats = 5; + + // Endpoint granularity stats information for this locality. This information + // is populated if the Server requests it by setting + // :ref:`LoadStatsResponse.report_endpoint_granularity`. + repeated UpstreamEndpointStats upstream_endpoint_stats = 7; + + // [#not-implemented-hide:] The priority of the endpoint group these metrics + // were collected from. + uint32 priority = 6; +} + +message UpstreamEndpointStats { + // Upstream host address. + core.Address address = 1; + + // Opaque and implementation dependent metadata of the + // endpoint. Envoy will pass this directly to the management server. + google.protobuf.Struct metadata = 6; + + // The total number of requests successfully completed by the endpoints in the + // locality. These include non-5xx responses for HTTP, where errors + // originate at the client and the endpoint responded successfully. For gRPC, + // the grpc-status values are those not covered by total_error_requests below. + uint64 total_successful_requests = 2; + + // The total number of unfinished requests for this endpoint. + uint64 total_requests_in_progress = 3; + + // The total number of requests that failed due to errors at the endpoint. + // For HTTP these are responses with 5xx status codes and for gRPC the + // grpc-status values: + // + // - DeadlineExceeded + // - Unimplemented + // - Internal + // - Unavailable + // - Unknown + // - DataLoss + uint64 total_error_requests = 4; + + // The total number of requests that were issued to this endpoint + // since the last report. A single TCP connection, HTTP or gRPC + // request or stream is counted as one request. + uint64 total_issued_requests = 7; + + // Stats for multi-dimensional load balancing. + repeated EndpointLoadMetricStats load_metric_stats = 5; +} + +// [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. +message EndpointLoadMetricStats { + // Name of the metric; may be empty. + string metric_name = 1; + + // Number of calls that finished and included this metric. + uint64 num_requests_finished_with_metric = 2; + + // Sum of metric values across all calls that finished with this metric for + // load_reporting_interval. + double total_metric_value = 3; +} + +// Per cluster load stats. Envoy reports these stats a management server in a +// :ref:`LoadStatsRequest` +// [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. +// Next ID: 7 +message ClusterStats { + // The name of the cluster. + string cluster_name = 1 [(validate.rules).string.min_bytes = 1]; + + // The eds_cluster_config service_name of the cluster. + // It's possible that two clusters send the same service_name to EDS, + // in that case, the management server is supposed to do aggregation on the load reports. + string cluster_service_name = 6; + + // Need at least one. + repeated UpstreamLocalityStats upstream_locality_stats = 2 + [(validate.rules).repeated .min_items = 1]; + + // Cluster-level stats such as total_successful_requests may be computed by + // summing upstream_locality_stats. In addition, below there are additional + // cluster-wide stats. + // + // The total number of dropped requests. This covers requests + // deliberately dropped by the drop_overload policy and circuit breaking. + uint64 total_dropped_requests = 3; + + message DroppedRequests { + // Identifier for the policy specifying the drop. + string category = 1 [(validate.rules).string.min_bytes = 1]; + // Total number of deliberately dropped requests for the category. + uint64 dropped_count = 2; + } + // Information about deliberately dropped requests for each category specified + // in the DropOverload policy. + repeated DroppedRequests dropped_requests = 5; + + // Period over which the actual load report occurred. This will be guaranteed to include every + // request reported. Due to system load and delays between the *LoadStatsRequest* sent from Envoy + // and the *LoadStatsResponse* message sent from the management server, this may be longer than + // the requested load reporting interval in the *LoadStatsResponse*. + google.protobuf.Duration load_report_interval = 4; +} diff --git a/api/envoy/api/v3alpha/lds.proto b/api/envoy/api/v3alpha/lds.proto new file mode 100644 index 0000000000..794b25a4a3 --- /dev/null +++ b/api/envoy/api/v3alpha/lds.proto @@ -0,0 +1,203 @@ +syntax = "proto3"; + +package envoy.api.v3alpha; + +option java_outer_classname = "LdsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha"; + +option java_generic_services = true; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/discovery.proto"; +import "envoy/api/v3alpha/listener/listener.proto"; +import "envoy/api/v3alpha/listener/udp_listener_config.proto"; + +import "google/api/annotations.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Listener] +// Listener :ref:`configuration overview ` + +// The Envoy instance initiates an RPC at startup to discover a list of +// listeners. Updates are delivered via streaming from the LDS server and +// consist of a complete update of all listeners. Existing connections will be +// allowed to drain from listeners that are no longer present. +service ListenerDiscoveryService { + rpc DeltaListeners(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) { + } + + rpc StreamListeners(stream DiscoveryRequest) returns (stream DiscoveryResponse) { + } + + rpc FetchListeners(DiscoveryRequest) returns (DiscoveryResponse) { + option (google.api.http) = { + post: "/v3alpha/discovery:listeners" + body: "*" + }; + } +} + +// [#comment:next free field: 19] +message Listener { + // The unique name by which this listener is known. If no name is provided, + // Envoy will allocate an internal UUID for the listener. If the listener is to be dynamically + // updated or removed via :ref:`LDS ` a unique name must be provided. + string name = 1; + + // The address that the listener should listen on. In general, the address must be unique, though + // that is governed by the bind rules of the OS. E.g., multiple listeners can listen on port 0 on + // Linux as the actual port will be allocated by the OS. + core.Address address = 2 [(validate.rules).message.required = true]; + + // A list of filter chains to consider for this listener. The + // :ref:`FilterChain ` with the most specific + // :ref:`FilterChainMatch ` criteria is used on a + // connection. + // + // Example using SNI for filter chain selection can be found in the + // :ref:`FAQ entry `. + repeated listener.FilterChain filter_chains = 3; + + // If a connection is redirected using *iptables*, the port on which the proxy + // receives it might be different from the original destination address. When this flag is set to + // true, the listener hands off redirected connections to the listener associated with the + // original destination address. If there is no listener associated with the original destination + // address, the connection is handled by the listener that receives it. Defaults to false. + // + // .. attention:: + // + // This field is deprecated. Use :ref:`an original_dst ` + // :ref:`listener filter ` instead. + // + // Note that hand off to another listener is *NOT* performed without this flag. Once + // :ref:`FilterChainMatch ` is implemented this flag + // will be removed, as filter chain matching can be used to select a filter chain based on the + // restored destination address. + google.protobuf.BoolValue use_original_dst = 4 [deprecated = true]; + + // Soft limit on size of the listener’s new connection read and write buffers. + // If unspecified, an implementation defined default is applied (1MiB). + google.protobuf.UInt32Value per_connection_buffer_limit_bytes = 5; + + // Listener metadata. + core.Metadata metadata = 6; + + // [#not-implemented-hide:] + message DeprecatedV1 { + // Whether the listener should bind to the port. A listener that doesn't + // bind can only receive connections redirected from other listeners that + // set use_original_dst parameter to true. Default is true. + // + // [V2-API-DIFF] This is deprecated in v2, all Listeners will bind to their + // port. An additional filter chain must be created for every original + // destination port this listener may redirect to in v2, with the original + // port specified in the FilterChainMatch destination_port field. + // + // [#comment:TODO(PiotrSikora): Remove this once verified that we no longer need it.] + google.protobuf.BoolValue bind_to_port = 1; + } + + // [#not-implemented-hide:] + DeprecatedV1 deprecated_v1 = 7; + + enum DrainType { + // Drain in response to calling /healthcheck/fail admin endpoint (along with the health check + // filter), listener removal/modification, and hot restart. + DEFAULT = 0; + // Drain in response to listener removal/modification and hot restart. This setting does not + // include /healthcheck/fail. This setting may be desirable if Envoy is hosting both ingress + // and egress listeners. + MODIFY_ONLY = 1; + } + + // The type of draining to perform at a listener-wide level. + DrainType drain_type = 8; + + // Listener filters have the opportunity to manipulate and augment the connection metadata that + // is used in connection filter chain matching, for example. These filters are run before any in + // :ref:`filter_chains `. Order matters as the + // filters are processed sequentially right after a socket has been accepted by the listener, and + // before a connection is created. + // UDP Listener filters can be specified when the protocol in the listener socket address in + // :ref:`protocol ` is :ref:'UDP + // `. + // UDP listeners currently support a single filter. + repeated listener.ListenerFilter listener_filters = 9; + + // The timeout to wait for all listener filters to complete operation. If the timeout is reached, + // the accepted socket is closed without a connection being created unless + // `continue_on_listener_filters_timeout` is set to true. Specify 0 to disable the + // timeout. If not specified, a default timeout of 15s is used. + google.protobuf.Duration listener_filters_timeout = 15; + + // Whether a connection should be created when listener filters timeout. Default is false. + // + // .. attention:: + // + // Some listener filters, such as :ref:`Proxy Protocol filter + // `, should not be used with this option. It will cause + // unexpected behavior when a connection is created. + bool continue_on_listener_filters_timeout = 17; + + // Whether the listener should be set as a transparent socket. + // When this flag is set to true, connections can be redirected to the listener using an + // *iptables* *TPROXY* target, in which case the original source and destination addresses and + // ports are preserved on accepted connections. This flag should be used in combination with + // :ref:`an original_dst ` :ref:`listener filter + // ` to mark the connections' local addresses as + // "restored." This can be used to hand off each redirected connection to another listener + // associated with the connection's destination address. Direct connections to the socket without + // using *TPROXY* cannot be distinguished from connections redirected using *TPROXY* and are + // therefore treated as if they were redirected. + // When this flag is set to false, the listener's socket is explicitly reset as non-transparent. + // Setting this flag requires Envoy to run with the *CAP_NET_ADMIN* capability. + // When this flag is not set (default), the socket is not modified, i.e. the transparent option + // is neither set nor reset. + google.protobuf.BoolValue transparent = 10; + + // Whether the listener should set the *IP_FREEBIND* socket option. When this + // flag is set to true, listeners can be bound to an IP address that is not + // configured on the system running Envoy. When this flag is set to false, the + // option *IP_FREEBIND* is disabled on the socket. When this flag is not set + // (default), the socket is not modified, i.e. the option is neither enabled + // nor disabled. + google.protobuf.BoolValue freebind = 11; + + // Additional socket options that may not be present in Envoy source code or + // precompiled binaries. + repeated core.SocketOption socket_options = 13; + + // Whether the listener should accept TCP Fast Open (TFO) connections. + // When this flag is set to a value greater than 0, the option TCP_FASTOPEN is enabled on + // the socket, with a queue length of the specified size + // (see `details in RFC7413 `_). + // When this flag is set to 0, the option TCP_FASTOPEN is disabled on the socket. + // When this flag is not set (default), the socket is not modified, + // i.e. the option is neither enabled nor disabled. + // + // On Linux, the net.ipv4.tcp_fastopen kernel parameter must include flag 0x2 to enable + // TCP_FASTOPEN. + // See `ip-sysctl.txt `_. + // + // On macOS, only values of 0, 1, and unset are valid; other values may result in an error. + // To set the queue length on macOS, set the net.inet.tcp.fastopen_backlog kernel parameter. + google.protobuf.UInt32Value tcp_fast_open_queue_length = 12; + + reserved 14; + + // Specifies the intended direction of the traffic relative to the local Envoy. + core.TrafficDirection traffic_direction = 16; + + // If the protocol in the listener socket address in :ref:`protocol + // ` is :ref:'UDP + // `, this field specifies the actual udp listener to create, + // i.e. :ref:`udp_listener_name + // ` = "raw_udp_listener" for + // creating a packet-oriented UDP listener. If not present, treat it as "raw_udp_listener". + listener.UdpListenerConfig udp_listener_config = 18; +} diff --git a/api/envoy/api/v3alpha/listener/BUILD b/api/envoy/api/v3alpha/listener/BUILD new file mode 100644 index 0000000000..3ee071ca5c --- /dev/null +++ b/api/envoy/api/v3alpha/listener/BUILD @@ -0,0 +1,30 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/auth", + "//envoy/api/v3alpha/core", + ], +) + +api_proto_library_internal( + name = "listener", + srcs = ["listener.proto"], + visibility = ["//envoy/api/v3alpha:friends"], + deps = [ + "//envoy/api/v3alpha/auth:cert", + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + ], +) + +api_proto_library_internal( + name = "udp_listener_config", + srcs = ["udp_listener_config.proto"], + visibility = ["//envoy/api/v3alpha:friends"], + deps = [ + "//envoy/api/v3alpha/core:base", + ], +) diff --git a/api/envoy/api/v3alpha/listener/listener.proto b/api/envoy/api/v3alpha/listener/listener.proto new file mode 100644 index 0000000000..ef50639694 --- /dev/null +++ b/api/envoy/api/v3alpha/listener/listener.proto @@ -0,0 +1,206 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.listener; + +option java_outer_classname = "ListenerProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.listener"; +option csharp_namespace = "Envoy.Api.V2.ListenerNS"; +option ruby_package = "Envoy::Api::V2::ListenerNS"; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/auth/cert.proto"; +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Listener components] +// Listener :ref:`configuration overview ` + +message Filter { + // The name of the filter to instantiate. The name must match a + // :ref:`supported filter `. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Filter specific configuration which depends on the filter being + // instantiated. See the supported filters for further documentation. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 4; + } + + reserved 3; +} + +// Specifies the match criteria for selecting a specific filter chain for a +// listener. +// +// In order for a filter chain to be selected, *ALL* of its criteria must be +// fulfilled by the incoming connection, properties of which are set by the +// networking stack and/or listener filters. +// +// The following order applies: +// +// 1. Destination port. +// 2. Destination IP address. +// 3. Server name (e.g. SNI for TLS protocol), +// 4. Transport protocol. +// 5. Application protocols (e.g. ALPN for TLS protocol). +// 6. Source type (e.g. any, local or external network). +// 7. Source IP address. +// 8. Source port. +// +// For criteria that allow ranges or wildcards, the most specific value in any +// of the configured filter chains that matches the incoming connection is going +// to be used (e.g. for SNI ``www.example.com`` the most specific match would be +// ``www.example.com``, then ``*.example.com``, then ``*.com``, then any filter +// chain without ``server_names`` requirements). +// +// [#comment: Implemented rules are kept in the preference order, with deprecated fields +// listed at the end, because that's how we want to list them in the docs. +// +// [#comment:TODO(PiotrSikora): Add support for configurable precedence of the rules] +message FilterChainMatch { + // Optional destination port to consider when use_original_dst is set on the + // listener in determining a filter chain match. + google.protobuf.UInt32Value destination_port = 8 [(validate.rules).uint32 = {gte: 1, lte: 65535}]; + + // If non-empty, an IP address and prefix length to match addresses when the + // listener is bound to 0.0.0.0/:: or when use_original_dst is specified. + repeated core.CidrRange prefix_ranges = 3; + + // If non-empty, an IP address and suffix length to match addresses when the + // listener is bound to 0.0.0.0/:: or when use_original_dst is specified. + // [#not-implemented-hide:] + string address_suffix = 4; + + // [#not-implemented-hide:] + google.protobuf.UInt32Value suffix_len = 5; + + enum ConnectionSourceType { + // Any connection source matches. + ANY = 0; + // Match a connection originating from the same host. + LOCAL = 1; + // Match a connection originating from a different host. + EXTERNAL = 2; + } + + // Specifies the connection source IP match type. Can be any, local or external network. + ConnectionSourceType source_type = 12 [(validate.rules).enum.defined_only = true]; + + // The criteria is satisfied if the source IP address of the downstream + // connection is contained in at least one of the specified subnets. If the + // parameter is not specified or the list is empty, the source IP address is + // ignored. + repeated core.CidrRange source_prefix_ranges = 6; + + // The criteria is satisfied if the source port of the downstream connection + // is contained in at least one of the specified ports. If the parameter is + // not specified, the source port is ignored. + repeated uint32 source_ports = 7 [(validate.rules).repeated .items.uint32 = {gte: 1, lte: 65535}]; + + // If non-empty, a list of server names (e.g. SNI for TLS protocol) to consider when determining + // a filter chain match. Those values will be compared against the server names of a new + // connection, when detected by one of the listener filters. + // + // The server name will be matched against all wildcard domains, i.e. ``www.example.com`` + // will be first matched against ``www.example.com``, then ``*.example.com``, then ``*.com``. + // + // Note that partial wildcards are not supported, and values like ``*w.example.com`` are invalid. + // + // .. attention:: + // + // See the :ref:`FAQ entry ` on how to configure SNI for more + // information. + repeated string server_names = 11; + + // If non-empty, a transport protocol to consider when determining a filter chain match. + // This value will be compared against the transport protocol of a new connection, when + // it's detected by one of the listener filters. + // + // Suggested values include: + // + // * ``raw_buffer`` - default, used when no transport protocol is detected, + // * ``tls`` - set by :ref:`envoy.listener.tls_inspector ` + // when TLS protocol is detected. + string transport_protocol = 9; + + // If non-empty, a list of application protocols (e.g. ALPN for TLS protocol) to consider when + // determining a filter chain match. Those values will be compared against the application + // protocols of a new connection, when detected by one of the listener filters. + // + // Suggested values include: + // + // * ``http/1.1`` - set by :ref:`envoy.listener.tls_inspector + // `, + // * ``h2`` - set by :ref:`envoy.listener.tls_inspector ` + // + // .. attention:: + // + // Currently, only :ref:`TLS Inspector ` provides + // application protocol detection based on the requested + // `ALPN `_ values. + // + // However, the use of ALPN is pretty much limited to the HTTP/2 traffic on the Internet, + // and matching on values other than ``h2`` is going to lead to a lot of false negatives, + // unless all connecting clients are known to use ALPN. + repeated string application_protocols = 10; + + reserved 1; + reserved "sni_domains"; +} + +// A filter chain wraps a set of match criteria, an option TLS context, a set of filters, and +// various other parameters. +message FilterChain { + // The criteria to use when matching a connection to this filter chain. + FilterChainMatch filter_chain_match = 1; + + // The TLS context for this filter chain. + auth.DownstreamTlsContext tls_context = 2; + + // A list of individual network filters that make up the filter chain for + // connections established with the listener. Order matters as the filters are + // processed sequentially as connection events happen. Note: If the filter + // list is empty, the connection will close by default. + repeated Filter filters = 3; + + // Whether the listener should expect a PROXY protocol V1 header on new + // connections. If this option is enabled, the listener will assume that that + // remote address of the connection is the one specified in the header. Some + // load balancers including the AWS ELB support this option. If the option is + // absent or set to false, Envoy will use the physical peer address of the + // connection as the remote address. + google.protobuf.BoolValue use_proxy_proto = 4; + + // [#not-implemented-hide:] filter chain metadata. + core.Metadata metadata = 5; + + // See :ref:`base.TransportSocket` description. + core.TransportSocket transport_socket = 6; + + // [#not-implemented-hide:] The unique name (or empty) by which this filter chain is known. If no + // name is provided, Envoy will allocate an internal UUID for the filter chain. If the filter + // chain is to be dynamically updated or removed via FCDS a unique name must be provided. + string name = 7; +} + +message ListenerFilter { + // The name of the filter to instantiate. The name must match a + // :ref:`supported filter `. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Filter specific configuration which depends on the filter being instantiated. + // See the supported filters for further documentation. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} diff --git a/api/envoy/api/v3alpha/listener/udp_listener_config.proto b/api/envoy/api/v3alpha/listener/udp_listener_config.proto new file mode 100644 index 0000000000..532028da9f --- /dev/null +++ b/api/envoy/api/v3alpha/listener/udp_listener_config.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.listener; + +option java_outer_classname = "UdpListenerConfigProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.listener"; +option csharp_namespace = "Envoy.Api.V2.ListenerNS"; +option ruby_package = "Envoy::Api::V2::ListenerNS"; + +import "google/protobuf/struct.proto"; +import "google/protobuf/any.proto"; + +// [#protodoc-title: Udp Listener Config] +// Listener :ref:`configuration overview ` + +message UdpListenerConfig { + // Used to look up UDP listener factory, matches "raw_udp_listener" or + // "quic_listener" to create a specific udp listener. + // If not specified, treat as "raw_udp_listener". + string udp_listener_name = 1; + + // Used to create a specific listener factory. To some factory, e.g. + // "raw_udp_listener", config is not needed. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} diff --git a/api/envoy/api/v3alpha/ratelimit/BUILD b/api/envoy/api/v3alpha/ratelimit/BUILD new file mode 100644 index 0000000000..a99624b1c4 --- /dev/null +++ b/api/envoy/api/v3alpha/ratelimit/BUILD @@ -0,0 +1,11 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "ratelimit", + srcs = ["ratelimit.proto"], + visibility = ["//envoy/api/v3alpha:friends"], +) diff --git a/api/envoy/api/v3alpha/ratelimit/ratelimit.proto b/api/envoy/api/v3alpha/ratelimit/ratelimit.proto new file mode 100644 index 0000000000..9f2b67818a --- /dev/null +++ b/api/envoy/api/v3alpha/ratelimit/ratelimit.proto @@ -0,0 +1,65 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.ratelimit; + +option java_outer_classname = "RatelimitProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.ratelimit"; + +import "validate/validate.proto"; + +// [#protodoc-title: Common rate limit components] + +// A RateLimitDescriptor is a list of hierarchical entries that are used by the service to +// determine the final rate limit key and overall allowed limit. Here are some examples of how +// they might be used for the domain "envoy". +// +// .. code-block:: cpp +// +// ["authenticated": "false"], ["remote_address": "10.0.0.1"] +// +// What it does: Limits all unauthenticated traffic for the IP address 10.0.0.1. The +// configuration supplies a default limit for the *remote_address* key. If there is a desire to +// raise the limit for 10.0.0.1 or block it entirely it can be specified directly in the +// configuration. +// +// .. code-block:: cpp +// +// ["authenticated": "false"], ["path": "/foo/bar"] +// +// What it does: Limits all unauthenticated traffic globally for a specific path (or prefix if +// configured that way in the service). +// +// .. code-block:: cpp +// +// ["authenticated": "false"], ["path": "/foo/bar"], ["remote_address": "10.0.0.1"] +// +// What it does: Limits unauthenticated traffic to a specific path for a specific IP address. +// Like (1) we can raise/block specific IP addresses if we want with an override configuration. +// +// .. code-block:: cpp +// +// ["authenticated": "true"], ["client_id": "foo"] +// +// What it does: Limits all traffic for an authenticated client "foo" +// +// .. code-block:: cpp +// +// ["authenticated": "true"], ["client_id": "foo"], ["path": "/foo/bar"] +// +// What it does: Limits traffic to a specific path for an authenticated client "foo" +// +// The idea behind the API is that (1)/(2)/(3) and (4)/(5) can be sent in 1 request if desired. +// This enables building complex application scenarios with a generic backend. +message RateLimitDescriptor { + message Entry { + // Descriptor key. + string key = 1 [(validate.rules).string.min_bytes = 1]; + + // Descriptor value. + string value = 2 [(validate.rules).string.min_bytes = 1]; + } + + // Descriptor entries. + repeated Entry entries = 1 [(validate.rules).repeated .min_items = 1]; +} diff --git a/api/envoy/api/v3alpha/rds.proto b/api/envoy/api/v3alpha/rds.proto new file mode 100644 index 0000000000..36dfbc4a0f --- /dev/null +++ b/api/envoy/api/v3alpha/rds.proto @@ -0,0 +1,132 @@ +syntax = "proto3"; + +package envoy.api.v3alpha; + +option java_outer_classname = "RdsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha"; + +option java_generic_services = true; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/core/config_source.proto"; +import "envoy/api/v3alpha/discovery.proto"; +import "envoy/api/v3alpha/route/route.proto"; + +import "google/api/annotations.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: HTTP route configuration] +// * Routing :ref:`architecture overview ` +// * HTTP :ref:`router filter ` + +// The resource_names field in DiscoveryRequest specifies a route configuration. +// This allows an Envoy configuration with multiple HTTP listeners (and +// associated HTTP connection manager filters) to use different route +// configurations. Each listener will bind its HTTP connection manager filter to +// a route table via this identifier. +service RouteDiscoveryService { + rpc StreamRoutes(stream DiscoveryRequest) returns (stream DiscoveryResponse) { + } + + rpc DeltaRoutes(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) { + } + + rpc FetchRoutes(DiscoveryRequest) returns (DiscoveryResponse) { + option (google.api.http) = { + post: "/v3alpha/discovery:routes" + body: "*" + }; + } +} + +// Virtual Host Discovery Service (VHDS) is used to dynamically update the list of virtual hosts for +// a given RouteConfiguration. If VHDS is configured a virtual host list update will be triggered +// during the processing of an HTTP request if a route for the request cannot be resolved. The +// :ref:`resource_names_subscribe ` +// field contains a list of virtual host names or aliases to track. The contents of an alias would +// be the contents of a *host* or *authority* header used to make an http request. An xDS server +// will match an alias to a virtual host based on the content of :ref:`domains' +// ` field. The *resource_names_unsubscribe* field contains +// a list of virtual host names that have been :ref:`unsubscribed ` +// from the routing table associated with the RouteConfiguration. +service VirtualHostDiscoveryService { + rpc DeltaVirtualHosts(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) { + } +} + +// [#comment:next free field: 10] +message RouteConfiguration { + // The name of the route configuration. For example, it might match + // :ref:`route_config_name + // ` + // in :ref:`envoy_api_msg_config.filter.network.http_connection_manager.v3alpha.Rds`. + string name = 1; + + // An array of virtual hosts that make up the route table. + repeated route.VirtualHost virtual_hosts = 2; + + // An array of virtual hosts will be dynamically loaded via the VHDS API. + // Both *virtual_hosts* and *vhds* fields will be used when present. *virtual_hosts* can be used + // for a base routing table or for infrequently changing virtual hosts. *vhds* is used for + // on-demand discovery of virtual hosts. The contents of these two fields will be merged to + // generate a routing table for a given RouteConfiguration, with *vhds* derived configuration + // taking precedence. + // [#not-implemented-hide:] + Vhds vhds = 9; + + // Optionally specifies a list of HTTP headers that the connection manager + // will consider to be internal only. If they are found on external requests they will be cleaned + // prior to filter invocation. See :ref:`config_http_conn_man_headers_x-envoy-internal` for more + // information. + repeated string internal_only_headers = 3; + + // Specifies a list of HTTP headers that should be added to each response that + // the connection manager encodes. Headers specified at this level are applied + // after headers from any enclosed :ref:`envoy_api_msg_route.VirtualHost` or + // :ref:`envoy_api_msg_route.RouteAction`. For more information, including details on + // header value syntax, see the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption response_headers_to_add = 4 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of HTTP headers that should be removed from each response + // that the connection manager encodes. + repeated string response_headers_to_remove = 5; + + // Specifies a list of HTTP headers that should be added to each request + // routed by the HTTP connection manager. Headers specified at this level are + // applied after headers from any enclosed :ref:`envoy_api_msg_route.VirtualHost` or + // :ref:`envoy_api_msg_route.RouteAction`. For more information, including details on + // header value syntax, see the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption request_headers_to_add = 6 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of HTTP headers that should be removed from each request + // routed by the HTTP connection manager. + repeated string request_headers_to_remove = 8; + + // An optional boolean that specifies whether the clusters that the route + // table refers to will be validated by the cluster manager. If set to true + // and a route refers to a non-existent cluster, the route table will not + // load. If set to false and a route refers to a non-existent cluster, the + // route table will load and the router filter will return a 404 if the route + // is selected at runtime. This setting defaults to true if the route table + // is statically defined via the :ref:`route_config + // ` + // option. This setting default to false if the route table is loaded dynamically via the + // :ref:`rds + // ` + // option. Users may wish to override the default behavior in certain cases (for example when + // using CDS with a static route table). + google.protobuf.BoolValue validate_clusters = 7; +} + +// [#not-implemented-hide:] +message Vhds { + // Configuration source specifier for VHDS. + envoy.api.v3alpha.core.ConfigSource config_source = 1 [(validate.rules).message.required = true]; +} diff --git a/api/envoy/api/v3alpha/route/BUILD b/api/envoy/api/v3alpha/route/BUILD new file mode 100644 index 0000000000..cbed3ec01f --- /dev/null +++ b/api/envoy/api/v3alpha/route/BUILD @@ -0,0 +1,24 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/type", + "//envoy/type/matcher", + ], +) + +api_proto_library_internal( + name = "route", + srcs = ["route.proto"], + visibility = ["//envoy/api/v3alpha:friends"], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/type:percent", + "//envoy/type:range", + "//envoy/type/matcher:regex", + "//envoy/type/matcher:string", + ], +) diff --git a/api/envoy/api/v3alpha/route/route.proto b/api/envoy/api/v3alpha/route/route.proto new file mode 100644 index 0000000000..d4d60e9290 --- /dev/null +++ b/api/envoy/api/v3alpha/route/route.proto @@ -0,0 +1,1395 @@ +syntax = "proto3"; + +package envoy.api.v3alpha.route; + +option java_outer_classname = "RouteProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.api.v3alpha.route"; +option java_generic_services = true; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/type/matcher/regex.proto"; +import "envoy/type/matcher/string.proto"; +import "envoy/type/percent.proto"; +import "envoy/type/range.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: HTTP route] +// * Routing :ref:`architecture overview ` +// * HTTP :ref:`router filter ` + +// The top level element in the routing configuration is a virtual host. Each virtual host has +// a logical name as well as a set of domains that get routed to it based on the incoming request's +// host header. This allows a single listener to service multiple top level domain path trees. Once +// a virtual host is selected based on the domain, the routes are processed in order to see which +// upstream cluster to route to or whether to perform a redirect. +// [#comment:next free field: 17] +message VirtualHost { + // The logical name of the virtual host. This is used when emitting certain + // statistics but is not relevant for routing. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // A list of domains (host/authority header) that will be matched to this + // virtual host. Wildcard hosts are supported in the suffix or prefix form. + // + // Domain search order: + // 1. Exact domain names: ``www.foo.com``. + // 2. Suffix domain wildcards: ``*.foo.com`` or ``*-bar.foo.com``. + // 3. Prefix domain wildcards: ``foo.*`` or ``foo-*``. + // 4. Special wildcard ``*`` matching any domain. + // + // .. note:: + // + // The wildcard will not match the empty string. + // e.g. ``*-bar.foo.com`` will match ``baz-bar.foo.com`` but not ``-bar.foo.com``. + // The longest wildcards match first. + // Only a single virtual host in the entire route configuration can match on ``*``. A domain + // must be unique across all virtual hosts or the config will fail to load. + repeated string domains = 2 [(validate.rules).repeated .min_items = 1]; + + // The list of routes that will be matched, in order, for incoming requests. + // The first route that matches will be used. + repeated Route routes = 3; + + enum TlsRequirementType { + // No TLS requirement for the virtual host. + NONE = 0; + + // External requests must use TLS. If a request is external and it is not + // using TLS, a 301 redirect will be sent telling the client to use HTTPS. + EXTERNAL_ONLY = 1; + + // All requests must use TLS. If a request is not using TLS, a 301 redirect + // will be sent telling the client to use HTTPS. + ALL = 2; + } + + // Specifies the type of TLS enforcement the virtual host expects. If this option is not + // specified, there is no TLS requirement for the virtual host. + TlsRequirementType require_tls = 4; + + // A list of virtual clusters defined for this virtual host. Virtual clusters + // are used for additional statistics gathering. + repeated VirtualCluster virtual_clusters = 5; + + // Specifies a set of rate limit configurations that will be applied to the + // virtual host. + repeated RateLimit rate_limits = 6; + + // Specifies a list of HTTP headers that should be added to each request + // handled by this virtual host. Headers specified at this level are applied + // after headers from enclosed :ref:`envoy_api_msg_route.Route` and before headers from the + // enclosing :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + // details on header value syntax, see the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption request_headers_to_add = 7 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of HTTP headers that should be removed from each request + // handled by this virtual host. + repeated string request_headers_to_remove = 13; + + // Specifies a list of HTTP headers that should be added to each response + // handled by this virtual host. Headers specified at this level are applied + // after headers from enclosed :ref:`envoy_api_msg_route.Route` and before headers from the + // enclosing :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + // details on header value syntax, see the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption response_headers_to_add = 10 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of HTTP headers that should be removed from each response + // handled by this virtual host. + repeated string response_headers_to_remove = 11; + + // Indicates that the virtual host has a CORS policy. + CorsPolicy cors = 8; + + reserved 9; + + // The per_filter_config field can be used to provide virtual host-specific + // configurations for filters. The key should match the filter name, such as + // *envoy.buffer* for the HTTP buffer filter. Use of this field is filter + // specific; see the :ref:`HTTP filter documentation ` + // for if and how it is utilized. + map per_filter_config = 12; + + // The per_filter_config field can be used to provide virtual host-specific + // configurations for filters. The key should match the filter name, such as + // *envoy.buffer* for the HTTP buffer filter. Use of this field is filter + // specific; see the :ref:`HTTP filter documentation ` + // for if and how it is utilized. + map typed_per_filter_config = 15; + + // Decides whether the :ref:`x-envoy-attempt-count + // ` header should be included + // in the upstream request. Setting this option will cause it to override any existing header + // value, so in the case of two Envoys on the request path with this option enabled, the upstream + // will see the attempt count as perceived by the second Envoy. Defaults to false. + // This header is unaffected by the + // :ref:`suppress_envoy_headers + // ` flag. + bool include_request_attempt_count = 14; + + // Indicates the retry policy for all routes in this virtual host. Note that setting a + // route level entry will take precedence over this config and it'll be treated + // independently (e.g.: values are not inherited). + RetryPolicy retry_policy = 16; + + // Indicates the hedge policy for all routes in this virtual host. Note that setting a + // route level entry will take precedence over this config and it'll be treated + // independently (e.g.: values are not inherited). + HedgePolicy hedge_policy = 17; +} + +// A route is both a specification of how to match a request as well as an indication of what to do +// next (e.g., redirect, forward, rewrite, etc.). +// +// .. attention:: +// +// Envoy supports routing on HTTP method via :ref:`header matching +// `. +// [#comment:next free field: 15] +message Route { + // Name for the route. + string name = 14; + + // Route matching parameters. + RouteMatch match = 1 [(validate.rules).message.required = true]; + + oneof action { + option (validate.required) = true; + + // Route request to some upstream cluster. + RouteAction route = 2; + + // Return a redirect. + RedirectAction redirect = 3; + + // Return an arbitrary HTTP response directly, without proxying. + DirectResponseAction direct_response = 7; + } + + // The Metadata field can be used to provide additional information + // about the route. It can be used for configuration, stats, and logging. + // The metadata should go under the filter namespace that will need it. + // For instance, if the metadata is intended for the Router filter, + // the filter name should be specified as *envoy.router*. + core.Metadata metadata = 4; + + // Decorator for the matched route. + Decorator decorator = 5; + + reserved 6; + + // The per_filter_config field can be used to provide route-specific + // configurations for filters. The key should match the filter name, such as + // *envoy.buffer* for the HTTP buffer filter. Use of this field is filter + // specific; see the :ref:`HTTP filter documentation ` for + // if and how it is utilized. + map per_filter_config = 8; + + // The per_filter_config field can be used to provide route-specific + // configurations for filters. The key should match the filter name, such as + // *envoy.buffer* for the HTTP buffer filter. Use of this field is filter + // specific; see the :ref:`HTTP filter documentation ` for + // if and how it is utilized. + map typed_per_filter_config = 13; + + // Specifies a set of headers that will be added to requests matching this + // route. Headers specified at this level are applied before headers from the + // enclosing :ref:`envoy_api_msg_route.VirtualHost` and + // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + // header value syntax, see the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption request_headers_to_add = 9 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of HTTP headers that should be removed from each request + // matching this route. + repeated string request_headers_to_remove = 12; + + // Specifies a set of headers that will be added to responses to requests + // matching this route. Headers specified at this level are applied before + // headers from the enclosing :ref:`envoy_api_msg_route.VirtualHost` and + // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including + // details on header value syntax, see the documentation on + // :ref:`custom request headers `. + repeated core.HeaderValueOption response_headers_to_add = 10 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of HTTP headers that should be removed from each response + // to requests matching this route. + repeated string response_headers_to_remove = 11; + + // Presence of the object defines whether the connection manager's tracing configuration + // is overridden by this route specific instance. + Tracing tracing = 15; +} + +// Compared to the :ref:`cluster ` field that specifies a +// single upstream cluster as the target of a request, the :ref:`weighted_clusters +// ` option allows for specification of +// multiple upstream clusters along with weights that indicate the percentage of +// traffic to be forwarded to each cluster. The router selects an upstream cluster based on the +// weights. +// [#comment:next free field: 11] +message WeightedCluster { + message ClusterWeight { + // Name of the upstream cluster. The cluster must exist in the + // :ref:`cluster manager configuration `. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // An integer between 0 and :ref:`total_weight + // `. When a request matches the route, + // the choice of an upstream cluster is determined by its weight. The sum of weights across all + // entries in the clusters array must add up to the total_weight, which defaults to 100. + google.protobuf.UInt32Value weight = 2; + + // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in + // the upstream cluster with metadata matching what is set in this field will be considered for + // load balancing. Note that this will be merged with what's provided in :ref: + // `RouteAction.MetadataMatch `, with values + // here taking precedence. The filter name should be specified as *envoy.lb*. + core.Metadata metadata_match = 3; + + // Specifies a list of headers to be added to requests when this cluster is selected + // through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + // Headers specified at this level are applied before headers from the enclosing + // :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_route.VirtualHost`, and + // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + // header value syntax, see the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption request_headers_to_add = 4 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of HTTP headers that should be removed from each request when + // this cluster is selected through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + repeated string request_headers_to_remove = 9; + + // Specifies a list of headers to be added to responses when this cluster is selected + // through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + // Headers specified at this level are applied before headers from the enclosing + // :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_route.VirtualHost`, and + // :ref:`envoy_api_msg_RouteConfiguration`. For more information, including details on + // header value syntax, see the documentation on :ref:`custom request headers + // `. + repeated core.HeaderValueOption response_headers_to_add = 5 + [(validate.rules).repeated .max_items = 1000]; + + // Specifies a list of headers to be removed from responses when this cluster is selected + // through the enclosing :ref:`envoy_api_msg_route.RouteAction`. + repeated string response_headers_to_remove = 6; + + reserved 7; + + // The per_filter_config field can be used to provide weighted cluster-specific + // configurations for filters. The key should match the filter name, such as + // *envoy.buffer* for the HTTP buffer filter. Use of this field is filter + // specific; see the :ref:`HTTP filter documentation ` + // for if and how it is utilized. + map per_filter_config = 8; + + // The per_filter_config field can be used to provide weighted cluster-specific + // configurations for filters. The key should match the filter name, such as + // *envoy.buffer* for the HTTP buffer filter. Use of this field is filter + // specific; see the :ref:`HTTP filter documentation ` + // for if and how it is utilized. + map typed_per_filter_config = 10; + } + + // Specifies one or more upstream clusters associated with the route. + repeated ClusterWeight clusters = 1 [(validate.rules).repeated .min_items = 1]; + + // Specifies the total weight across all clusters. The sum of all cluster weights must equal this + // value, which must be greater than 0. Defaults to 100. + google.protobuf.UInt32Value total_weight = 3 [(validate.rules).uint32.gte = 1]; + + // Specifies the runtime key prefix that should be used to construct the + // runtime keys associated with each cluster. When the *runtime_key_prefix* is + // specified, the router will look for weights associated with each upstream + // cluster under the key *runtime_key_prefix* + "." + *cluster[i].name* where + // *cluster[i]* denotes an entry in the clusters array field. If the runtime + // key for the cluster does not exist, the value specified in the + // configuration file will be used as the default weight. See the :ref:`runtime documentation + // ` for how key names map to the underlying implementation. + string runtime_key_prefix = 2; +} + +message RouteMatch { + oneof path_specifier { + option (validate.required) = true; + + // If specified, the route is a prefix rule meaning that the prefix must + // match the beginning of the *:path* header. + string prefix = 1; + + // If specified, the route is an exact path rule meaning that the path must + // exactly match the *:path* header once the query string is removed. + string path = 2; + + // If specified, the route is a regular expression rule meaning that the + // regex must match the *:path* header once the query string is removed. The entire path + // (without the query string) must match the regex. The rule will not match if only a + // subsequence of the *:path* header matches the regex. The regex grammar is defined `here + // `_. + // + // Examples: + // + // * The regex */b[io]t* matches the path */bit* + // * 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* + // + // .. attention:: + // This field has been deprecated in favor of `safe_regex` as it is not safe for use with + // untrusted input in all cases. + string regex = 3 [(validate.rules).string.max_bytes = 1024, deprecated = true]; + + // If specified, the route is a regular expression rule meaning that the + // regex must match the *:path* header once the query string is removed. The entire path + // (without the query string) must match the regex. The rule will not match if only a + // subsequence of the *:path* header matches the regex. + // + // [#next-major-version: In the v3 API we should redo how path specification works such + // that we utilize StringMatcher, and additionally have consistent options around whether we + // strip query strings, do a case sensitive match, etc. In the interim it will be too disruptive + // to deprecate the existing options. We should even consider whether we want to do away with + // path_specifier entirely and just rely on a set of header matchers which can already match + // on :path, etc. The issue with that is it is unclear how to generically deal with query string + // stripping. This needs more thought.] + type.matcher.RegexMatcher safe_regex = 10 [(validate.rules).message.required = true]; + } + + // Indicates that prefix/path matching should be case insensitive. The default + // is true. + google.protobuf.BoolValue case_sensitive = 4; + + reserved 5; + + // Indicates that the route should additionally match on a runtime key. Every time the route + // is considered for a match, it must also fall under the percentage of matches indicated by + // this field. For some fraction N/D, a random number in the range [0,D) is selected. If the + // number is <= the value of the numerator N, or if the key is not present, the default + // value, the router continues to evaluate the remaining match criteria. A runtime_fraction + // route configuration can be used to roll out route changes in a gradual manner without full + // code/config deploys. Refer to the :ref:`traffic shifting + // ` docs for additional documentation. + // + // .. note:: + // + // Parsing this field is implemented such that the runtime key's data may be represented + // as a FractionalPercent proto represented as JSON/YAML and may also be represented as an + // integer with the assumption that the value is an integral percentage out of 100. For + // instance, a runtime key lookup returning the value "42" would parse as a FractionalPercent + // whose numerator is 42 and denominator is HUNDRED. This preserves legacy semantics. + core.RuntimeFractionalPercent runtime_fraction = 9; + + // Specifies a set of headers that the route should match on. The router will + // check the request’s headers against all the specified headers in the route + // config. A match will happen if all the headers in the route are present in + // the request with the same values (or based on presence if the value field + // is not in the config). + repeated HeaderMatcher headers = 6; + + // Specifies a set of URL query parameters on which the route should + // match. The router will check the query string from the *path* header + // against all the specified query parameters. If the number of specified + // query parameters is nonzero, they all must match the *path* header's + // query string for a match to occur. + repeated QueryParameterMatcher query_parameters = 7; + + message GrpcRouteMatchOptions { + } + + // If specified, only gRPC requests will be matched. The router will check + // that the content-type header has a application/grpc or one of the various + // application/grpc+ values. + GrpcRouteMatchOptions grpc = 8; +} + +// [#comment:next free field: 11] +message CorsPolicy { + // Specifies the origins that will be allowed to do CORS requests. + // + // An origin is allowed if either allow_origin or allow_origin_regex match. + // + // .. attention:: + // This field has been deprecated in favor of `allow_origin_string_match`. + repeated string allow_origin = 1 [deprecated = true]; + + // Specifies regex patterns that match allowed origins. + // + // An origin is allowed if either allow_origin or allow_origin_regex match. + // + // .. attention:: + // This field has been deprecated in favor of `allow_origin_string_match` as it is not safe for + // use with untrusted input in all cases. + repeated string allow_origin_regex = 8 + [(validate.rules).repeated .items.string.max_bytes = 1024, deprecated = true]; + + // Specifies string patterns that match allowed origins. An origin is allowed if any of the + // string matchers match. + repeated type.matcher.StringMatcher allow_origin_string_match = 11; + + // Specifies the content for the *access-control-allow-methods* header. + string allow_methods = 2; + + // Specifies the content for the *access-control-allow-headers* header. + string allow_headers = 3; + + // Specifies the content for the *access-control-expose-headers* header. + string expose_headers = 4; + + // Specifies the content for the *access-control-max-age* header. + string max_age = 5; + + // Specifies whether the resource allows credentials. + google.protobuf.BoolValue allow_credentials = 6; + + oneof enabled_specifier { + // Specifies if CORS is enabled. Defaults to true. Only effective on route. + // + // .. attention:: + // + // **This field is deprecated**. Set the + // :ref:`filter_enabled` field instead. + google.protobuf.BoolValue enabled = 7 [deprecated = true]; + + // Specifies if CORS is enabled. + // + // More information on how this can be controlled via runtime can be found + // :ref:`here `. + // + // .. note:: + // + // This field defaults to 100/:ref:`HUNDRED + // `. + core.RuntimeFractionalPercent filter_enabled = 9; + } + + // Specifies if CORS policies are evaluated and tracked when filter is off but + // does not enforce any policies. + // + // More information on how this can be controlled via runtime can be found + // :ref:`here `. + // + // .. note:: + // + // This field defaults to 100/:ref:`HUNDRED + // `. + core.RuntimeFractionalPercent shadow_enabled = 10; +} + +// [#comment:next free field: 30] +message RouteAction { + oneof cluster_specifier { + option (validate.required) = true; + + // Indicates the upstream cluster to which the request should be routed + // to. + 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 + // header is not found or the referenced cluster does not exist, Envoy will + // return a 404 response. + // + // .. attention:: + // + // 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 [(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 + // assigned to each cluster. See + // :ref:`traffic splitting ` + // for additional documentation. + WeightedCluster weighted_clusters = 3; + } + + enum ClusterNotFoundResponseCode { + // HTTP status code - 503 Service Unavailable. + SERVICE_UNAVAILABLE = 0; + + // HTTP status code - 404 Not Found. + NOT_FOUND = 1; + } + + // The HTTP status code to use when configured cluster is not found. + // The default response code is 503 Service Unavailable. + ClusterNotFoundResponseCode cluster_not_found_response_code = 20 + [(validate.rules).enum.defined_only = true]; + + // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints + // in the upstream cluster with metadata matching what's set in this field will be considered + // for load balancing. If using :ref:`weighted_clusters + // `, metadata will be merged, with values + // provided there taking precedence. The filter name should be specified as *envoy.lb*. + core.Metadata metadata_match = 4; + + // Indicates that during forwarding, the matched prefix (or path) should be + // swapped with this value. This option allows application URLs to be rooted + // at a different path from those exposed at the reverse proxy layer. The router filter will + // place the original path before rewrite into the :ref:`x-envoy-original-path + // ` header. + // + // .. attention:: + // + // Pay careful attention to the use of trailing slashes in the + // :ref:`route's match ` prefix value. + // Stripping a prefix from a path requires multiple Routes to handle all cases. For example, + // rewriting */prefix* to */* and */prefix/etc* to */etc* cannot be done in a single + // :ref:`Route `, as shown by the below config entries: + // + // .. code-block:: yaml + // + // - match: + // prefix: "/prefix/" + // route: + // prefix_rewrite: "/" + // - match: + // prefix: "/prefix" + // route: + // prefix_rewrite: "/" + // + // Having above entries in the config, requests to */prefix* will be stripped to */*, while + // requests to */prefix/etc* will be stripped to */etc*. + string prefix_rewrite = 5; + + oneof host_rewrite_specifier { + // Indicates that during forwarding, the host header will be swapped with + // this value. + string host_rewrite = 6; + + // Indicates that during forwarding, the host header will be swapped with + // the hostname of the upstream host chosen by the cluster manager. This + // option is applicable only when the destination cluster for a route is of + // type *strict_dns* or *logical_dns*. Setting this to true with other cluster + // types has no effect. + google.protobuf.BoolValue auto_host_rewrite = 7; + + // Indicates that during forwarding, the host header will be swapped with the content of given + // downstream or :ref:`custom ` header. + // If header value is empty, host header is left intact. + // + // .. attention:: + // + // Pay attention to the potential security implications of using this option. Provided header + // must come from trusted source. + string auto_host_rewrite_header = 29; + } + + // Specifies the upstream timeout for the route. If not specified, the default is 15s. This + // spans between the point at which the entire downstream request (i.e. end-of-stream) has been + // processed and when the upstream response has been completely processed. A value of 0 will + // disable the route's timeout. + // + // .. note:: + // + // This timeout includes all retries. See also + // :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms`, + // :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms`, and the + // :ref:`retry overview `. + google.protobuf.Duration timeout = 8; + + // Specifies the idle timeout for the route. If not specified, there is no per-route idle timeout, + // although the connection manager wide :ref:`stream_idle_timeout + // ` + // will still apply. A value of 0 will completely disable the route's idle timeout, even if a + // connection manager stream idle timeout is configured. + // + // The idle timeout is distinct to :ref:`timeout + // `, which provides an upper bound + // on the upstream response time; :ref:`idle_timeout + // ` instead bounds the amount + // of time the request's stream may be idle. + // + // After header decoding, the idle timeout will apply on downstream and + // upstream request events. Each time an encode/decode event for headers or + // data is processed for the stream, the timer will be reset. If the timeout + // fires, the stream is terminated with a 408 Request Timeout error code if no + // upstream response header has been received, otherwise a stream reset + // occurs. + google.protobuf.Duration idle_timeout = 24; + + // Indicates that the route has a retry policy. Note that if this is set, + // it'll take precedence over the virtual host level retry policy entirely + // (e.g.: policies are not merged, most internal one becomes the enforced policy). + RetryPolicy retry_policy = 9; + + // The router is capable of shadowing traffic from one cluster to another. The current + // implementation is "fire and forget," meaning Envoy will not wait for the shadow cluster to + // respond before returning the response from the primary cluster. All normal statistics are + // collected for the shadow cluster making this feature useful for testing. + // + // During shadowing, the host/authority header is altered such that *-shadow* is appended. This is + // useful for logging. For example, *cluster1* becomes *cluster1-shadow*. + message RequestMirrorPolicy { + // Specifies the cluster that requests will be mirrored to. The cluster must + // exist in the cluster manager configuration. + string cluster = 1 [(validate.rules).string.min_bytes = 1]; + + // If not specified, all requests to the target cluster will be mirrored. If + // specified, Envoy will lookup the runtime key to get the % of requests to + // mirror. Valid values are from 0 to 10000, allowing for increments of + // 0.01% of requests to be mirrored. If the runtime key is specified in the + // configuration but not present in runtime, 0 is the default and thus 0% of + // requests will be mirrored. + // + // .. attention:: + // + // **This field is deprecated**. Set the + // :ref:`runtime_fraction + // ` field instead. + string runtime_key = 2 [deprecated = true]; + + // If both :ref:`runtime_key + // ` and this field are not + // specified, all requests to the target cluster will be mirrored. + // + // If specified, this field takes precedence over the `runtime_key` field and requests must also + // fall under the percentage of matches indicated by this field. + // + // For some fraction N/D, a random number in the range [0,D) is selected. If the + // number is <= the value of the numerator N, or if the key is not present, the default + // value, the request will be mirrored. + // + // .. note:: + // + // Parsing this field is implemented such that the runtime key's data may be represented + // as a :ref:`FractionalPercent ` proto represented + // as JSON/YAML and may also be represented as an integer with the assumption that the value + // is an integral percentage out of 100. For instance, a runtime key lookup returning the + // value "42" would parse as a `FractionalPercent` whose numerator is 42 and denominator is + // HUNDRED. This is behaviour is different to that of the deprecated `runtime_key` field, + // where the implicit denominator is 10000. + core.RuntimeFractionalPercent runtime_fraction = 3; + } + + // Indicates that the route has a request mirroring policy. + RequestMirrorPolicy request_mirror_policy = 10; + + // Optionally specifies the :ref:`routing priority `. + // [#comment:TODO(htuch): add (validate.rules).enum.defined_only = true once + // https://github.com/lyft/protoc-gen-validate/issues/42 is resolved.] + core.RoutingPriority priority = 11; + + reserved 12; + reserved 18; + reserved 19; + + // Specifies a set of rate limit configurations that could be applied to the + // route. + repeated RateLimit rate_limits = 13; + + // Specifies if the rate limit filter should include the virtual host rate + // limits. By default, if the route configured rate limits, the virtual host + // :ref:`rate_limits ` are not applied to the + // request. + google.protobuf.BoolValue include_vh_rate_limits = 14; + + // Specifies the route's hashing policy if the upstream cluster uses a hashing :ref:`load balancer + // `. + message HashPolicy { + message Header { + // The name of the request header that will be used to obtain the hash + // key. If the request header is not present, no hash will be produced. + string header_name = 1 [(validate.rules).string.min_bytes = 1]; + } + + // Envoy supports two types of cookie affinity: + // + // 1. Passive. Envoy takes a cookie that's present in the cookies header and + // hashes on its value. + // + // 2. Generated. Envoy generates and sets a cookie with an expiration (TTL) + // on the first request from the client in its response to the client, + // based on the endpoint the request gets sent to. The client then + // presents this on the next and all subsequent requests. The hash of + // this is sufficient to ensure these requests get sent to the same + // endpoint. The cookie is generated by hashing the source and + // destination ports and addresses so that multiple independent HTTP2 + // streams on the same connection will independently receive the same + // cookie, even if they arrive at the Envoy simultaneously. + message Cookie { + // The name of the cookie that will be used to obtain the hash key. If the + // cookie is not present and ttl below is not set, no hash will be + // produced. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // If specified, a cookie with the TTL will be generated if the cookie is + // not present. If the TTL is present and zero, the generated cookie will + // be a session cookie. + google.protobuf.Duration ttl = 2; + + // The name of the path for the cookie. If no path is specified here, no path + // will be set for the cookie. + string path = 3; + } + + message ConnectionProperties { + // Hash on source IP address. + bool source_ip = 1; + } + + oneof policy_specifier { + option (validate.required) = true; + + // Header hash policy. + Header header = 1; + + // Cookie hash policy. + Cookie cookie = 2; + + // Connection properties hash policy. + ConnectionProperties connection_properties = 3; + } + + // The flag that shortcircuits the hash computing. This field provides a + // 'fallback' style of configuration: "if a terminal policy doesn't work, + // fallback to rest of the policy list", it saves time when the terminal + // policy works. + // + // If true, and there is already a hash computed, ignore rest of the + // list of hash polices. + // For example, if the following hash methods are configured: + // + // ========= ======== + // specifier terminal + // ========= ======== + // Header A true + // Header B false + // Header C false + // ========= ======== + // + // The generateHash process ends if policy "header A" generates a hash, as + // it's a terminal policy. + bool terminal = 4; + } + + // Specifies a list of hash policies to use for ring hash load balancing. Each + // hash policy is evaluated individually and the combined result is used to + // route the request. The method of combination is deterministic such that + // identical lists of hash policies will produce the same hash. Since a hash + // policy examines specific parts of a request, it can fail to produce a hash + // (i.e. if the hashed header is not present). If (and only if) all configured + // hash policies fail to generate a hash, no hash will be produced for + // the route. In this case, the behavior is the same as if no hash policies + // were specified (i.e. the ring hash load balancer will choose a random + // backend). If a hash policy has the "terminal" attribute set to true, and + // there is already a hash generated, the hash is returned immediately, + // ignoring the rest of the hash policy list. + repeated HashPolicy hash_policy = 15; + + reserved 16; + reserved 22; + + // Indicates that the route has a CORS policy. + CorsPolicy cors = 17; + + reserved 21; + + // If present, and the request is a gRPC request, use the + // `grpc-timeout header `_, + // or its default value (infinity) instead of + // :ref:`timeout `, but limit the applied timeout + // to the maximum value specified here. If configured as 0, the maximum allowed timeout for + // gRPC requests is infinity. If not configured at all, the `grpc-timeout` header is not used + // and gRPC requests time out like any other requests using + // :ref:`timeout ` or its default. + // This can be used to prevent unexpected upstream request timeouts due to potentially long + // time gaps between gRPC request and response in gRPC streaming mode. + google.protobuf.Duration max_grpc_timeout = 23; + + // If present, Envoy will adjust the timeout provided by the `grpc-timeout` header by subtracting + // the provided duration from the header. This is useful in allowing Envoy to set its global + // timeout to be less than that of the deadline imposed by the calling client, which makes it more + // likely that Envoy will handle the timeout instead of having the call canceled by the client. + // The offset will only be applied if the provided grpc_timeout is greater than the offset. This + // ensures that the offset will only ever decrease the timeout and never set it to 0 (meaning + // infinity). + google.protobuf.Duration grpc_timeout_offset = 28; + + // Allows enabling and disabling upgrades on a per-route basis. + // This overrides any enabled/disabled upgrade filter chain specified in the + // HttpConnectionManager + // :ref:upgrade_configs` + // ` + // but does not affect any custom filter chain specified there. + message UpgradeConfig { + // The case-insensitive name of this upgrade, e.g. "websocket". + // For each upgrade type present in upgrade_configs, requests with + // Upgrade: [upgrade_type] will be proxied upstream. + string upgrade_type = 1; + // Determines if upgrades are available on this route. Defaults to true. + google.protobuf.BoolValue enabled = 2; + }; + repeated UpgradeConfig upgrade_configs = 25; + + // Configures :ref:`internal redirect ` behavior. + enum InternalRedirectAction { + PASS_THROUGH_INTERNAL_REDIRECT = 0; + HANDLE_INTERNAL_REDIRECT = 1; + } + InternalRedirectAction internal_redirect_action = 26; + + // Indicates that the route has a hedge policy. Note that if this is set, + // it'll take precedence over the virtual host level hedge policy entirely + // (e.g.: policies are not merged, most internal one becomes the enforced policy). + HedgePolicy hedge_policy = 27; +} + +// HTTP retry :ref:`architecture overview `. +// [#comment:next free field: 9] +message RetryPolicy { + // Specifies the conditions under which retry takes place. These are the same + // conditions documented for :ref:`config_http_filters_router_x-envoy-retry-on` and + // :ref:`config_http_filters_router_x-envoy-retry-grpc-on`. + string retry_on = 1; + + // Specifies the allowed number of retries. This parameter is optional and + // defaults to 1. These are the same conditions documented for + // :ref:`config_http_filters_router_x-envoy-max-retries`. + google.protobuf.UInt32Value num_retries = 2; + + // Specifies a non-zero upstream timeout per retry attempt. This parameter is optional. The + // same conditions documented for + // :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` apply. + // + // .. note:: + // + // If left unspecified, Envoy will use the global + // :ref:`route timeout ` for the request. + // Consequently, when using a :ref:`5xx ` based + // retry policy, a request that times out will not be retried as the total timeout budget + // would have been exhausted. + google.protobuf.Duration per_try_timeout = 3; + + message RetryPriority { + string name = 1 [(validate.rules).string.min_bytes = 1]; + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } + } + + // Specifies an implementation of a RetryPriority which is used to determine the + // distribution of load across priorities used for retries. Refer to + // :ref:`retry plugin configuration ` for more details. + RetryPriority retry_priority = 4; + + message RetryHostPredicate { + string name = 1 [(validate.rules).string.min_bytes = 1]; + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } + } + + // Specifies a collection of RetryHostPredicates that will be consulted when selecting a host + // for retries. If any of the predicates reject the host, host selection will be reattempted. + // Refer to :ref:`retry plugin configuration ` for more + // details. + repeated RetryHostPredicate retry_host_predicate = 5; + + // The maximum number of times host selection will be reattempted before giving up, at which + // point the host that was last selected will be routed to. If unspecified, this will default to + // retrying once. + int64 host_selection_retry_max_attempts = 6; + + // HTTP status codes that should trigger a retry in addition to those specified by retry_on. + repeated uint32 retriable_status_codes = 7; + + message RetryBackOff { + // Specifies the base interval between retries. This parameter is required and must be greater + // than zero. Values less than 1 ms are rounded up to 1 ms. + // See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion of Envoy's + // back-off algorithm. + google.protobuf.Duration base_interval = 1 [(validate.rules).duration = { + required: true, + gt: {seconds: 0} + }]; + + // Specifies the maximum interval between retries. This parameter is optional, but must be + // greater than or equal to the `base_interval` if set. The default is 10 times the + // `base_interval`. See :ref:`config_http_filters_router_x-envoy-max-retries` for a discussion + // of Envoy's back-off algorithm. + google.protobuf.Duration max_interval = 2 [(validate.rules).duration.gt = {seconds: 0}]; + } + + // Specifies parameters that control retry back off. This parameter is optional, in which case the + // default base interval is 25 milliseconds or, if set, the current value of the + // `upstream.base_retry_backoff_ms` runtime parameter. The default maximum interval is 10 times + // the base interval. The documentation for :ref:`config_http_filters_router_x-envoy-max-retries` + // describes Envoy's back-off algorithm. + RetryBackOff retry_back_off = 8; +} + +// HTTP request hedging :ref:`architecture overview `. +message HedgePolicy { + // Specifies the number of initial requests that should be sent upstream. + // Must be at least 1. + // Defaults to 1. + // [#not-implemented-hide:] + google.protobuf.UInt32Value initial_requests = 1 [(validate.rules).uint32.gte = 1]; + + // Specifies a probability that an additional upstream request should be sent + // on top of what is specified by initial_requests. + // Defaults to 0. + // [#not-implemented-hide:] + envoy.type.FractionalPercent additional_request_chance = 2; + + // Indicates that a hedged request should be sent when the per-try timeout + // is hit. This will only occur if the retry policy also indicates that a + // timed out request should be retried. + // Once a timed out request is retried due to per try timeout, the router + // filter will ensure that it is not retried again even if the returned + // response headers would otherwise be retried according the specified + // :ref:`RetryPolicy `. + // Defaults to false. + bool hedge_on_per_try_timeout = 3; +} + +message RedirectAction { + // When the scheme redirection take place, the following rules apply: + // 1. If the source URI scheme is `http` and the port is explicitly + // set to `:80`, the port will be removed after the redirection + // 2. If the source URI scheme is `https` and the port is explicitly + // set to `:443`, the port will be removed after the redirection + oneof scheme_rewrite_specifier { + // The scheme portion of the URL will be swapped with "https". + bool https_redirect = 4; + // The scheme portion of the URL will be swapped with this value. + string scheme_redirect = 7; + } + // The host portion of the URL will be swapped with this value. + string host_redirect = 1; + // The port value of the URL will be swapped with this value. + uint32 port_redirect = 8; + + oneof path_rewrite_specifier { + // The path portion of the URL will be swapped with this value. + string path_redirect = 2; + + // Indicates that during redirection, the matched prefix (or path) + // should be swapped with this value. This option allows redirect URLs be dynamically created + // based on the request. + // + // .. attention:: + // + // Pay attention to the use of trailing slashes as mentioned in + // :ref:`RouteAction's prefix_rewrite `. + string prefix_rewrite = 5; + } + + enum RedirectResponseCode { + // Moved Permanently HTTP Status Code - 301. + MOVED_PERMANENTLY = 0; + + // Found HTTP Status Code - 302. + FOUND = 1; + + // See Other HTTP Status Code - 303. + SEE_OTHER = 2; + + // Temporary Redirect HTTP Status Code - 307. + TEMPORARY_REDIRECT = 3; + + // Permanent Redirect HTTP Status Code - 308. + PERMANENT_REDIRECT = 4; + } + + // The HTTP status code to use in the redirect response. The default response + // code is MOVED_PERMANENTLY (301). + RedirectResponseCode response_code = 3 [(validate.rules).enum.defined_only = true]; + + // Indicates that during redirection, the query portion of the URL will + // be removed. Default value is false. + bool strip_query = 6; +} + +message DirectResponseAction { + // Specifies the HTTP response status to be returned. + uint32 status = 1 [(validate.rules).uint32 = {gte: 100, lt: 600}]; + + // Specifies the content of the response body. If this setting is omitted, + // no body is included in the generated response. + // + // .. note:: + // + // Headers can be specified using *response_headers_to_add* in the enclosing + // :ref:`envoy_api_msg_route.Route`, :ref:`envoy_api_msg_RouteConfiguration` or + // :ref:`envoy_api_msg_route.VirtualHost`. + core.DataSource body = 2; +} + +message Decorator { + // The operation name associated with the request matched to this route. If tracing is + // enabled, this information will be used as the span name reported for this request. + // + // .. note:: + // + // For ingress (inbound) requests, or egress (outbound) responses, this value may be overridden + // by the :ref:`x-envoy-decorator-operation + // ` header. + string operation = 1 [(validate.rules).string.min_bytes = 1]; +} + +message Tracing { + + // Target percentage of requests managed by this HTTP connection manager that will be force + // traced if the :ref:`x-client-trace-id ` + // header is set. This field is a direct analog for the runtime variable + // 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + // `. + // Default: 100% + envoy.type.FractionalPercent client_sampling = 1; + + // Target percentage of requests managed by this HTTP connection manager that will be randomly + // selected for trace generation, if not requested by the client or not forced. This field is + // a direct analog for the runtime variable 'tracing.random_sampling' in the + // :ref:`HTTP Connection Manager `. + // Default: 100% + envoy.type.FractionalPercent random_sampling = 2; + + // Target percentage of requests managed by this HTTP connection manager that will be traced + // after all other sampling checks have been applied (client-directed, force tracing, random + // sampling). This field functions as an upper limit on the total configured sampling rate. For + // instance, setting client_sampling to 100% but overall_sampling to 1% will result in only 1% + // of client requests with the appropriate headers to be force traced. This field is a direct + // analog for the runtime variable 'tracing.global_enabled' in the + // :ref:`HTTP Connection Manager `. + // Default: 100% + envoy.type.FractionalPercent overall_sampling = 3; +} + +// A virtual cluster is a way of specifying a regex matching rule against +// certain important endpoints such that statistics are generated explicitly for +// the matched requests. The reason this is useful is that when doing +// prefix/path matching Envoy does not always know what the application +// considers to be an endpoint. Thus, it’s impossible for Envoy to generically +// emit per endpoint statistics. However, often systems have highly critical +// endpoints that they wish to get “perfect” statistics on. Virtual cluster +// statistics are perfect in the sense that they are emitted on the downstream +// side such that they include network level failures. +// +// Documentation for :ref:`virtual cluster statistics `. +// +// .. note:: +// +// Virtual clusters are a useful tool, but we do not recommend setting up a virtual cluster for +// every application endpoint. This is both not easily maintainable and as well the matching and +// statistics output are not free. +message VirtualCluster { + // Specifies a regex pattern to use for matching requests. The entire path of the request + // must match the regex. The regex grammar used is defined `here + // `_. + // + // Examples: + // + // * 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* + // + // .. attention:: + // This field has been deprecated in favor of `headers` as it is not safe for use with + // untrusted input in all cases. + string pattern = 1 [(validate.rules).string.max_bytes = 1024, deprecated = true]; + + // Specifies a list of header matchers to use for matching requests. Each specified header must + // match. The pseudo-headers `:path` and `:method` can be used to match the request path and + // method, respectively. + repeated HeaderMatcher headers = 4; + + // 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 + // router filter and are documented :ref:`here `. + string name = 2 [(validate.rules).string.min_bytes = 1]; + + // Optionally specifies the HTTP method to match on. For example GET, PUT, + // etc. + // + // .. attention:: + // This field has been deprecated in favor of `headers`. + core.RequestMethod method = 3 [deprecated = true]; +} + +// Global rate limiting :ref:`architecture overview `. +message RateLimit { + // Refers to the stage set in the filter. The rate limit configuration only + // applies to filters with the same stage number. The default stage number is + // 0. + // + // .. note:: + // + // The filter supports a range of 0 - 10 inclusively for stage numbers. + google.protobuf.UInt32Value stage = 1 [(validate.rules).uint32.lte = 10]; + + // The key to be set in runtime to disable this rate limit configuration. + string disable_key = 2; + + message Action { + // The following descriptor entry is appended to the descriptor: + // + // .. code-block:: cpp + // + // ("source_cluster", "") + // + // is derived from the :option:`--service-cluster` option. + message SourceCluster { + } + + // The following descriptor entry is appended to the descriptor: + // + // .. code-block:: cpp + // + // ("destination_cluster", "") + // + // Once a request matches against a route table rule, a routed cluster is determined by one of + // the following :ref:`route table configuration ` + // settings: + // + // * :ref:`cluster ` indicates the upstream cluster + // to route to. + // * :ref:`weighted_clusters ` + // chooses a cluster randomly from a set of clusters with attributed weight. + // * :ref:`cluster_header ` indicates which + // header in the request contains the target cluster. + message DestinationCluster { + } + + // The following descriptor entry is appended when a header contains a key that matches the + // *header_name*: + // + // .. code-block:: cpp + // + // ("", "") + message RequestHeaders { + // The header name to be queried from the request headers. The header’s + // value is used to populate the value of the descriptor entry for the + // descriptor_key. + string header_name = 1 [(validate.rules).string.min_bytes = 1]; + + // The key to use in the descriptor entry. + string descriptor_key = 2 [(validate.rules).string.min_bytes = 1]; + } + + // The following descriptor entry is appended to the descriptor and is populated using the + // trusted address from :ref:`x-forwarded-for `: + // + // .. code-block:: cpp + // + // ("remote_address", "") + message RemoteAddress { + } + + // The following descriptor entry is appended to the descriptor: + // + // .. code-block:: cpp + // + // ("generic_key", "") + message GenericKey { + // The value to use in the descriptor entry. + string descriptor_value = 1 [(validate.rules).string.min_bytes = 1]; + } + + // The following descriptor entry is appended to the descriptor: + // + // .. code-block:: cpp + // + // ("header_match", "") + message HeaderValueMatch { + // The value to use in the descriptor entry. + string descriptor_value = 1 [(validate.rules).string.min_bytes = 1]; + + // If set to true, the action will append a descriptor entry when the + // request matches the headers. If set to false, the action will append a + // descriptor entry when the request does not match the headers. The + // default value is true. + google.protobuf.BoolValue expect_match = 2; + + // Specifies a set of headers that the rate limit action should match + // on. The action will check the request’s headers against all the + // specified headers in the config. A match will happen if all the + // headers in the config are present in the request with the same values + // (or based on presence if the value field is not in the config). + repeated HeaderMatcher headers = 3 [(validate.rules).repeated .min_items = 1]; + } + + oneof action_specifier { + option (validate.required) = true; + + // Rate limit on source cluster. + SourceCluster source_cluster = 1; + + // Rate limit on destination cluster. + DestinationCluster destination_cluster = 2; + + // Rate limit on request headers. + RequestHeaders request_headers = 3; + + // Rate limit on remote address. + RemoteAddress remote_address = 4; + + // Rate limit on a generic key. + GenericKey generic_key = 5; + + // Rate limit on the existence of request headers. + HeaderValueMatch header_value_match = 6; + } + } + + // A list of actions that are to be applied for this rate limit configuration. + // Order matters as the actions are processed sequentially and the descriptor + // is composed by appending descriptor entries in that sequence. If an action + // cannot append a descriptor entry, no descriptor is generated for the + // configuration. See :ref:`composing actions + // ` for additional documentation. + repeated Action actions = 3 [(validate.rules).repeated .min_items = 1]; +} + +// .. attention:: +// +// 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. +// +// .. attention:: +// +// To route on HTTP method, use the special HTTP/2 *:method* header. This works for both +// HTTP/1 and HTTP/2 as Envoy normalizes headers. E.g., +// +// .. code-block:: json +// +// { +// "name": ":method", +// "exact_match": "POST" +// } +// +// .. attention:: +// In the absence of any header match specifier, match will default to :ref:`present_match +// `. i.e, a request that has the :ref:`name +// ` header will match, regardless of the header's +// value. +// +// [#next-major-version: HeaderMatcher should be refactored to use StringMatcher.] +message HeaderMatcher { + // Specifies the name of the header in the request. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + reserved 2; // value deprecated by :ref:`exact_match + // ` + + reserved 3; // regex deprecated by :ref:`regex_match + // ` + + // Specifies how the header match will be performed to route the request. + oneof header_match_specifier { + // If specified, header match will be performed based on the value of the header. + string exact_match = 4; + + // If specified, this regex string is a regular expression rule which implies the entire request + // header value must match the regex. The rule will not match if only a subsequence of the + // request header value matches the regex. The regex grammar used in the value field is defined + // `here `_. + // + // Examples: + // + // * 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* + // + // .. attention:: + // This field has been deprecated in favor of `safe_regex_match` as it is not safe for use + // with untrusted input in all cases. + string regex_match = 5 [(validate.rules).string.max_bytes = 1024, deprecated = true]; + + // If specified, this regex string is a regular expression rule which implies the entire request + // header value must match the regex. The rule will not match if only a subsequence of the + // request header value matches the regex. + type.matcher.RegexMatcher safe_regex_match = 11; + + // If specified, header match will be performed based on range. + // The rule will match if the request header value is within this range. + // The entire request header value must represent an integer in base 10 notation: consisting of + // an optional plus or minus sign followed by a sequence of digits. The rule will not match if + // the header value does not represent an integer. Match will fail for empty values, floating + // point numbers or if only a subsequence of the header value is an integer. + // + // Examples: + // + // * For range [-10,0), route will match for header value -1, but not for 0, "somestring", 10.9, + // "-1somestring" + envoy.type.Int64Range range_match = 6; + + // If specified, header match will be performed based on whether the header is in the + // request. + bool present_match = 7; + + // If specified, header match will be performed based on the prefix of the header value. + // Note: empty prefix is not allowed, please use present_match instead. + // + // Examples: + // + // * The prefix *abcd* matches the value *abcdxyz*, but not for *abcxyz*. + string prefix_match = 9 [(validate.rules).string.min_bytes = 1]; + + // If specified, header match will be performed based on the suffix of the header value. + // Note: empty suffix is not allowed, please use present_match instead. + // + // Examples: + // + // * The suffix *abcd* matches the value *xyzabcd*, but not for *xyzbcd*. + string suffix_match = 10 [(validate.rules).string.min_bytes = 1]; + } + + // If specified, the match result will be inverted before checking. Defaults to false. + // + // Examples: + // + // * The regex *\d{3}* does not match the value *1234*, so it will match when inverted. + // * The range [-10,0) will match the value -1, so it will not match when inverted. + bool invert_match = 8; +} + +// Query parameter matching treats the query string of a request's :path header +// as an ampersand-separated list of keys and/or key=value elements. +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, 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 + // key appears with a value (e.g., "?debug=true") or not (e.g., "?debug") + // + // ..attention:: + // This field is deprecated. Use an `exact` match inside the `string_match` field. + string value = 3 [deprecated = true]; + + // Specifies whether the query parameter value is a regular expression. + // Defaults to false. The entire query parameter value (i.e., the part to + // the right of the equals sign in "key=value") must match the regex. + // E.g., the regex "\d+$" will match "123" but not "a123" or "123a". + // + // ..attention:: + // This field is deprecated. Use a `safe_regex` match inside the `string_match` field. + google.protobuf.BoolValue regex = 4 [deprecated = true]; + + oneof query_parameter_match_specifier { + // Specifies whether a query parameter value should match against a string. + type.matcher.StringMatcher string_match = 5 [(validate.rules).message.required = true]; + + // Specifies whether a query parameter should be present. + bool present_match = 6; + } +} diff --git a/api/envoy/api/v3alpha/srds.proto b/api/envoy/api/v3alpha/srds.proto new file mode 100644 index 0000000000..636ba3917e --- /dev/null +++ b/api/envoy/api/v3alpha/srds.proto @@ -0,0 +1,133 @@ +syntax = "proto3"; + +package envoy.api.v3alpha; + +import "envoy/api/v3alpha/discovery.proto"; +import "google/api/annotations.proto"; +import "validate/validate.proto"; + +option java_outer_classname = "SrdsProto"; +option java_package = "io.envoyproxy.envoy.api.v3alpha"; +option java_multiple_files = true; +option java_generic_services = true; + +// [#protodoc-title: HTTP scoped routing configuration] +// * Routing :ref:`architecture overview ` +// +// The Scoped Routes Discovery Service (SRDS) API distributes +// :ref:`ScopedRouteConfiguration` +// resources. Each ScopedRouteConfiguration resource represents a "routing +// scope" containing a mapping that allows the HTTP connection manager to +// dynamically assign a routing table (specified via a +// :ref:`RouteConfiguration` message) to each +// HTTP request. +// [#proto-status: experimental] +service ScopedRoutesDiscoveryService { + rpc StreamScopedRoutes(stream DiscoveryRequest) returns (stream DiscoveryResponse) { + } + + rpc DeltaScopedRoutes(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) { + } + + rpc FetchScopedRoutes(DiscoveryRequest) returns (DiscoveryResponse) { + option (google.api.http) = { + post: "/v3alpha/discovery:scoped-routes" + body: "*" + }; + } +} + +// Specifies a routing scope, which associates a +// :ref:`Key` to a +// :ref:`envoy_api_msg_RouteConfiguration` (identified by its resource name). +// +// The HTTP connection manager builds up a table consisting of these Key to +// RouteConfiguration mappings, and looks up the RouteConfiguration to use per +// request according to the algorithm specified in the +// :ref:`scope_key_builder` +// assigned to the HttpConnectionManager. +// +// For example, with the following configurations (in YAML): +// +// HttpConnectionManager config: +// +// .. code:: +// +// ... +// scoped_routes: +// name: foo-scoped-routes +// scope_key_builder: +// fragments: +// - header_value_extractor: +// name: X-Route-Selector +// element_separator: , +// element: +// separator: = +// key: vip +// +// ScopedRouteConfiguration resources (specified statically via +// :ref:`scoped_route_configurations_list` +// or obtained dynamically via SRDS): +// +// .. code:: +// +// (1) +// name: route-scope1 +// route_configuration_name: route-config1 +// key: +// fragments: +// - string_key: 172.10.10.20 +// +// (2) +// name: route-scope2 +// route_configuration_name: route-config2 +// key: +// fragments: +// - string_key: 172.20.20.30 +// +// A request from a client such as: +// +// .. code:: +// +// GET / HTTP/1.1 +// Host: foo.com +// X-Route-Selector: vip=172.10.10.20 +// +// would result in the routing table defined by the `route-config1` +// RouteConfiguration being assigned to the HTTP request/stream. +// +// [#comment:next free field: 4] +// [#proto-status: experimental] +message ScopedRouteConfiguration { + // The name assigned to the routing scope. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Specifies a key which is matched against the output of the + // :ref:`scope_key_builder` + // specified in the HttpConnectionManager. The matching is done per HTTP + // request and is dependent on the order of the fragments contained in the + // Key. + message Key { + message Fragment { + oneof type { + option (validate.required) = true; + + // A string to match against. + string string_key = 1; + } + } + + // The ordered set of fragments to match against. The order must match the + // fragments in the corresponding + // :ref:`scope_key_builder`. + repeated Fragment fragments = 1 [(validate.rules).repeated .min_items = 1]; + } + + // The resource name to use for a :ref:`envoy_api_msg_DiscoveryRequest` to an + // RDS server to fetch the :ref:`envoy_api_msg_RouteConfiguration` associated + // with this scope. + string route_configuration_name = 2 [(validate.rules).string.min_bytes = 1]; + + // The key to match against. + Key key = 3 [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/accesslog/v2/BUILD b/api/envoy/config/accesslog/v2/BUILD index d152681ec7..04f4cdc9e0 100644 --- a/api/envoy/config/accesslog/v2/BUILD +++ b/api/envoy/config/accesslog/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library_internal( name = "als", srcs = ["als.proto"], diff --git a/api/envoy/config/accesslog/v2/als.proto b/api/envoy/config/accesslog/v2/als.proto index a7291e4e97..c02835dbbc 100644 --- a/api/envoy/config/accesslog/v2/als.proto +++ b/api/envoy/config/accesslog/v2/als.proto @@ -5,7 +5,6 @@ package envoy.config.accesslog.v2; option java_outer_classname = "AlsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.accesslog.v2"; -option go_package = "v2"; import "envoy/api/v2/core/grpc_service.proto"; @@ -38,7 +37,6 @@ message HttpGrpcAccessLogConfig { // Configuration for the built-in *envoy.tcp_grpc_access_log* type. This configuration will // populate *StreamAccessLogsMessage.tcp_logs*. -// [#not-implemented-hide:] message TcpGrpcAccessLogConfig { CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message.required = true]; } diff --git a/api/envoy/config/accesslog/v2/file.proto b/api/envoy/config/accesslog/v2/file.proto index 48a1841a96..b88529a325 100644 --- a/api/envoy/config/accesslog/v2/file.proto +++ b/api/envoy/config/accesslog/v2/file.proto @@ -5,7 +5,6 @@ package envoy.config.accesslog.v2; option java_outer_classname = "FileProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.accesslog.v2"; -option go_package = "v2"; import "validate/validate.proto"; import "google/protobuf/struct.proto"; diff --git a/api/envoy/config/accesslog/v2/wasm.proto b/api/envoy/config/accesslog/v2/wasm.proto index 81bf1c8b34..f4150d3461 100644 --- a/api/envoy/config/accesslog/v2/wasm.proto +++ b/api/envoy/config/accesslog/v2/wasm.proto @@ -5,7 +5,6 @@ package envoy.config.accesslog.v2; option java_outer_classname = "WasmProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.accesslog.v2"; -option go_package = "v2"; import "validate/validate.proto"; import "google/protobuf/struct.proto"; diff --git a/api/envoy/config/accesslog/v3alpha/BUILD b/api/envoy/config/accesslog/v3alpha/BUILD new file mode 100644 index 0000000000..8409598da6 --- /dev/null +++ b/api/envoy/config/accesslog/v3alpha/BUILD @@ -0,0 +1,20 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library_internal( + name = "als", + srcs = ["als.proto"], + deps = [ + "//envoy/api/v3alpha/core:grpc_service", + ], +) + +api_proto_library_internal( + name = "file", + srcs = ["file.proto"], +) diff --git a/api/envoy/config/accesslog/v3alpha/als.proto b/api/envoy/config/accesslog/v3alpha/als.proto new file mode 100644 index 0000000000..07ec724d10 --- /dev/null +++ b/api/envoy/config/accesslog/v3alpha/als.proto @@ -0,0 +1,63 @@ +syntax = "proto3"; + +package envoy.config.accesslog.v3alpha; + +option java_outer_classname = "AlsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.accesslog.v3alpha"; + +import "envoy/api/v3alpha/core/grpc_service.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: gRPC Access Log Service (ALS)] + +// Configuration for the built-in *envoy.http_grpc_access_log* +// :ref:`AccessLog `. This configuration +// will populate :ref:`StreamAccessLogsMessage.http_logs +// `. +message HttpGrpcAccessLogConfig { + CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message.required = true]; + + // Additional request headers to log in :ref:`HTTPRequestProperties.request_headers + // `. + repeated string additional_request_headers_to_log = 2; + + // Additional response headers to log in :ref:`HTTPResponseProperties.response_headers + // `. + repeated string additional_response_headers_to_log = 3; + + // Additional response trailers to log in :ref:`HTTPResponseProperties.response_trailers + // `. + repeated string additional_response_trailers_to_log = 4; +} + +// Configuration for the built-in *envoy.tcp_grpc_access_log* type. This configuration will +// populate *StreamAccessLogsMessage.tcp_logs*. +message TcpGrpcAccessLogConfig { + CommonGrpcAccessLogConfig common_config = 1 [(validate.rules).message.required = true]; +} + +// Common configuration for gRPC access logs. +message CommonGrpcAccessLogConfig { + // The friendly name of the access log to be returned in :ref:`StreamAccessLogsMessage.Identifier + // `. This allows the + // access log server to differentiate between different access logs coming from the same Envoy. + string log_name = 1 [(validate.rules).string.min_bytes = 1]; + + // The gRPC service for the access log service. + envoy.api.v3alpha.core.GrpcService grpc_service = 2 [(validate.rules).message.required = true]; + + // Interval for flushing access logs to the gRPC stream. Logger will flush requests every time + // this interval is elapsed, or when batch size limit is hit, whichever comes first. Defaults to + // 1 second. + google.protobuf.Duration buffer_flush_interval = 3 [(validate.rules).duration.gt = {}]; + + // Soft size limit in bytes for access log entries buffer. Logger will buffer requests until + // this limit it hit, or every time flush interval is elapsed, whichever comes first. Setting it + // to zero effectively disables the batching. Defaults to 16384. + google.protobuf.UInt32Value buffer_size_bytes = 4; +} diff --git a/api/envoy/config/accesslog/v3alpha/file.proto b/api/envoy/config/accesslog/v3alpha/file.proto new file mode 100644 index 0000000000..2f32da7bb6 --- /dev/null +++ b/api/envoy/config/accesslog/v3alpha/file.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package envoy.config.accesslog.v3alpha; + +option java_outer_classname = "FileProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.accesslog.v3alpha"; + +import "validate/validate.proto"; +import "google/protobuf/struct.proto"; + +// [#protodoc-title: File access log] + +// Custom configuration for an :ref:`AccessLog +// ` that writes log entries directly to a +// file. Configures the built-in *envoy.file_access_log* AccessLog. +message FileAccessLog { + // A path to a local file to which to write the access log entries. + string path = 1 [(validate.rules).string.min_bytes = 1]; + + // Access log format. Envoy supports :ref:`custom access log formats + // ` as well as a :ref:`default format + // `. + oneof access_log_format { + // Access log :ref:`format string` + string format = 2; + + // Access log :ref:`format dictionary` + google.protobuf.Struct json_format = 3; + } +} diff --git a/api/envoy/config/bootstrap/v2/BUILD b/api/envoy/config/bootstrap/v2/BUILD index b6b8c84779..0c8e84f89f 100644 --- a/api/envoy/config/bootstrap/v2/BUILD +++ b/api/envoy/config/bootstrap/v2/BUILD @@ -1,7 +1,19 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2", + "//envoy/api/v2/auth", + "//envoy/api/v2/core", + "//envoy/config/metrics/v2:pkg", + "//envoy/config/overload/v2alpha:pkg", + "//envoy/config/ratelimit/v2:pkg", + "//envoy/config/trace/v2:pkg", + ], +) + api_proto_library_internal( name = "bootstrap", srcs = ["bootstrap.proto"], @@ -21,22 +33,3 @@ api_proto_library_internal( "//envoy/config/wasm/v2:wasm", ], ) - -api_go_proto_library( - name = "bootstrap", - proto = ":bootstrap", - deps = [ - "//envoy/api/v2:cds_go_grpc", - "//envoy/api/v2:lds_go_grpc", - "//envoy/api/v2/auth:cert_go_proto", - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:config_source_go_proto", - "//envoy/config/metrics/v2:metrics_service_go_proto", - "//envoy/config/metrics/v2:stats_go_proto", - "//envoy/config/overload/v2alpha:overload_go_proto", - "//envoy/config/ratelimit/v2:rls_go_grpc", - "//envoy/config/trace/v2:trace_go_proto", - "//envoy/config/wasm/v2:wasm_go_proto", - ], -) diff --git a/api/envoy/config/bootstrap/v2/bootstrap.proto b/api/envoy/config/bootstrap/v2/bootstrap.proto index 194d978221..fdb4c5a47a 100644 --- a/api/envoy/config/bootstrap/v2/bootstrap.proto +++ b/api/envoy/config/bootstrap/v2/bootstrap.proto @@ -10,7 +10,6 @@ package envoy.config.bootstrap.v2; option java_outer_classname = "BootstrapProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.bootstrap.v2"; -option go_package = "v2"; import "envoy/api/v2/core/address.proto"; import "envoy/api/v2/core/base.proto"; @@ -27,7 +26,6 @@ import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // Bootstrap :ref:`configuration overview `. message Bootstrap { @@ -100,7 +98,11 @@ message Bootstrap { // performance reasons Envoy latches counters and only flushes counters and // gauges at a periodic interval. If not specified the default is 5000ms (5 // seconds). - google.protobuf.Duration stats_flush_interval = 7 [(gogoproto.stdduration) = true]; + // Duration must be at least 1ms and at most 5 min. + google.protobuf.Duration stats_flush_interval = 7 [(validate.rules).duration = { + lt: {seconds: 300}, + gte: {nanos: 1000000} + }]; // Optional watchdog configuration. Watchdog watchdog = 8; diff --git a/api/envoy/config/bootstrap/v3alpha/BUILD b/api/envoy/config/bootstrap/v3alpha/BUILD new file mode 100644 index 0000000000..c88b982492 --- /dev/null +++ b/api/envoy/config/bootstrap/v3alpha/BUILD @@ -0,0 +1,34 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha", + "//envoy/api/v3alpha/auth", + "//envoy/api/v3alpha/core", + "//envoy/config/metrics/v3alpha:pkg", + "//envoy/config/overload/v3alpha:pkg", + "//envoy/config/ratelimit/v3alpha:pkg", + "//envoy/config/trace/v3alpha:pkg", + ], +) + +api_proto_library_internal( + name = "bootstrap", + srcs = ["bootstrap.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha:cds", + "//envoy/api/v3alpha:lds", + "//envoy/api/v3alpha/auth:cert", + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:config_source", + "//envoy/config/metrics/v3alpha:metrics_service", + "//envoy/config/metrics/v3alpha:stats", + "//envoy/config/overload/v3alpha:overload", + "//envoy/config/ratelimit/v3alpha:rls", + "//envoy/config/trace/v3alpha:trace", + ], +) diff --git a/api/envoy/config/bootstrap/v3alpha/bootstrap.proto b/api/envoy/config/bootstrap/v3alpha/bootstrap.proto new file mode 100644 index 0000000000..f3ac2e8342 --- /dev/null +++ b/api/envoy/config/bootstrap/v3alpha/bootstrap.proto @@ -0,0 +1,313 @@ +// [#protodoc-title: Bootstrap] +// This proto is supplied via the :option:`-c` CLI flag and acts as the root +// of the Envoy v2 configuration. See the :ref:`v2 configuration overview +// ` for more detail. + +syntax = "proto3"; + +package envoy.config.bootstrap.v3alpha; + +option java_outer_classname = "BootstrapProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.bootstrap.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/auth/cert.proto"; +import "envoy/api/v3alpha/core/config_source.proto"; +import "envoy/api/v3alpha/cds.proto"; +import "envoy/api/v3alpha/lds.proto"; +import "envoy/config/trace/v3alpha/trace.proto"; +import "envoy/config/metrics/v3alpha/stats.proto"; +import "envoy/config/overload/v3alpha/overload.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; + +import "validate/validate.proto"; + +// Bootstrap :ref:`configuration overview `. +message Bootstrap { + // Node identity to present to the management server and for instance + // identification purposes (e.g. in generated headers). + envoy.api.v3alpha.core.Node node = 1; + + message StaticResources { + // Static :ref:`Listeners `. These listeners are + // available regardless of LDS configuration. + repeated envoy.api.v3alpha.Listener listeners = 1; + + // If a network based configuration source is specified for :ref:`cds_config + // `, it's + // necessary to have some initial cluster definitions available to allow Envoy to know how to + // speak to the management server. These cluster definitions may not use :ref:`EDS + // ` (i.e. they should be static IP or DNS-based). + repeated envoy.api.v3alpha.Cluster clusters = 2; + + // These static secrets can be used by :ref:`SdsSecretConfig + // ` + repeated envoy.api.v3alpha.auth.Secret secrets = 3; + } + // Statically specified resources. + StaticResources static_resources = 2; + + message DynamicResources { + // All :ref:`Listeners ` are provided by a single + // :ref:`LDS ` configuration source. + envoy.api.v3alpha.core.ConfigSource lds_config = 1; + + // All post-bootstrap :ref:`Cluster ` definitions are + // provided by a single :ref:`CDS ` + // configuration source. + envoy.api.v3alpha.core.ConfigSource cds_config = 2; + + // A single :ref:`ADS ` source may be optionally + // specified. This must have :ref:`api_type + // ` :ref:`GRPC + // `. Only + // :ref:`ConfigSources ` that have + // the :ref:`ads ` field set will be + // streamed on the ADS channel. + envoy.api.v3alpha.core.ApiConfigSource ads_config = 3; + + reserved 4; + } + // xDS configuration sources. + DynamicResources dynamic_resources = 3; + + // Configuration for the cluster manager which owns all upstream clusters + // within the server. + ClusterManager cluster_manager = 4; + + // Health discovery service config option. + // (:ref:`core.ApiConfigSource `) + envoy.api.v3alpha.core.ApiConfigSource hds_config = 14; + + // Optional file system path to search for startup flag files. + string flags_path = 5; + + // Optional set of stats sinks. + repeated envoy.config.metrics.v3alpha.StatsSink stats_sinks = 6; + + // Configuration for internal processing of stats. + envoy.config.metrics.v3alpha.StatsConfig stats_config = 13; + + // Optional duration between flushes to configured stats sinks. For + // performance reasons Envoy latches counters and only flushes counters and + // gauges at a periodic interval. If not specified the default is 5000ms (5 + // seconds). + // Duration must be at least 1ms and at most 5 min. + google.protobuf.Duration stats_flush_interval = 7 [(validate.rules).duration = { + lt: {seconds: 300}, + gte: {nanos: 1000000} + }]; + + // Optional watchdog configuration. + Watchdog watchdog = 8; + + // Configuration for an external tracing provider. If not specified, no + // tracing will be performed. + envoy.config.trace.v3alpha.Tracing tracing = 9; + + reserved 10; + + // Configuration for the runtime configuration provider (deprecated). If not + // specified, a “null” provider will be used which will result in all defaults + // being used. + Runtime runtime = 11 [deprecated = true]; + + // Configuration for the runtime configuration provider. If not + // specified, a “null” provider will be used which will result in all defaults + // being used. + LayeredRuntime layered_runtime = 17; + + // Configuration for the local administration HTTP server. + Admin admin = 12; + + // Optional overload manager configuration. + envoy.config.overload.v3alpha.OverloadManager overload_manager = 15; + + // Enable :ref:`stats for event dispatcher `, defaults to false. + // Note that this records a value for each iteration of the event loop on every thread. This + // should normally be minimal overhead, but when using + // :ref:`statsd `, it will send each observed + // value over the wire individually because the statsd protocol doesn't have any way to represent + // a histogram summary. Be aware that this can be a very large volume of data. + bool enable_dispatcher_stats = 16; + + // Optional string which will be used in lieu of x-envoy in prefixing headers. + // + // For example, if this string is present and set to X-Foo, then x-envoy-retry-on will be + // transformed into x-foo-retry-on etc. + // + // Note this applies to the headers Envoy will generate, the headers Envoy will sanitize, and the + // headers Envoy will trust for core code and core extensions only. Be VERY careful making + // changes to this string, especially in multi-layer Envoy deployments or deployments using + // extensions which are not upstream. + string header_prefix = 18; +} + +// Administration interface :ref:`operations documentation +// `. +message Admin { + // The path to write the access log for the administration server. If no + // access log is desired specify ‘/dev/null’. This is only required if + // :ref:`address ` is set. + string access_log_path = 1; + + // The cpu profiler output path for the administration server. If no profile + // path is specified, the default is ‘/var/log/envoy/envoy.prof’. + string profile_path = 2; + + // The TCP address that the administration server will listen on. + // If not specified, Envoy will not start an administration server. + envoy.api.v3alpha.core.Address address = 3; + + // Additional socket options that may not be present in Envoy source code or + // precompiled binaries. + repeated envoy.api.v3alpha.core.SocketOption socket_options = 4; +} + +// Cluster manager :ref:`architecture overview `. +message ClusterManager { + // Name of the local cluster (i.e., the cluster that owns the Envoy running + // this configuration). In order to enable :ref:`zone aware routing + // ` this option must be set. + // If *local_cluster_name* is defined then :ref:`clusters + // ` must be defined in the :ref:`Bootstrap + // static cluster resources + // `. This is + // unrelated to the :option:`--service-cluster` option which does not `affect zone aware routing + // `_. + string local_cluster_name = 1; + + message OutlierDetection { + // Specifies the path to the outlier event log. + string event_log_path = 1; + } + // Optional global configuration for outlier detection. + OutlierDetection outlier_detection = 2; + + // Optional configuration used to bind newly established upstream connections. + // This may be overridden on a per-cluster basis by upstream_bind_config in the cds_config. + envoy.api.v3alpha.core.BindConfig upstream_bind_config = 3; + + // A management server endpoint to stream load stats to via + // *StreamLoadStats*. This must have :ref:`api_type + // ` :ref:`GRPC + // `. + envoy.api.v3alpha.core.ApiConfigSource load_stats_config = 4; +} + +// Envoy process watchdog configuration. When configured, this monitors for +// nonresponsive threads and kills the process after the configured thresholds. +message Watchdog { + // The duration after which Envoy counts a nonresponsive thread in the + // *server.watchdog_miss* statistic. If not specified the default is 200ms. + google.protobuf.Duration miss_timeout = 1; + + // The duration after which Envoy counts a nonresponsive thread in the + // *server.watchdog_mega_miss* statistic. If not specified the default is + // 1000ms. + google.protobuf.Duration megamiss_timeout = 2; + + // If a watched thread has been nonresponsive for this duration, assume a + // programming error and kill the entire Envoy process. Set to 0 to disable + // kill behavior. If not specified the default is 0 (disabled). + google.protobuf.Duration kill_timeout = 3; + + // If at least two watched threads have been nonresponsive for at least this + // duration assume a true deadlock and kill the entire Envoy process. Set to 0 + // to disable this behavior. If not specified the default is 0 (disabled). + google.protobuf.Duration multikill_timeout = 4; +} + +// Runtime :ref:`configuration overview ` (deprecated). +message Runtime { + // The implementation assumes that the file system tree is accessed via a + // symbolic link. An atomic link swap is used when a new tree should be + // switched to. This parameter specifies the path to the symbolic link. Envoy + // will watch the location for changes and reload the file system tree when + // they happen. If this parameter is not set, there will be no disk based + // runtime. + string symlink_root = 1; + + // Specifies the subdirectory to load within the root directory. This is + // useful if multiple systems share the same delivery mechanism. Envoy + // configuration elements can be contained in a dedicated subdirectory. + string subdirectory = 2; + + // Specifies an optional subdirectory to load within the root directory. If + // specified and the directory exists, configuration values within this + // directory will override those found in the primary subdirectory. This is + // useful when Envoy is deployed across many different types of servers. + // Sometimes it is useful to have a per service cluster directory for runtime + // configuration. See below for exactly how the override directory is used. + string override_subdirectory = 3; + + // Static base runtime. This will be :ref:`overridden + // ` by other runtime layers, e.g. + // disk or admin. This follows the :ref:`runtime protobuf JSON representation + // encoding `. + google.protobuf.Struct base = 4; +} + +message RuntimeLayer { + // :ref:`Disk runtime ` layer. + message DiskLayer { + // The implementation assumes that the file system tree is accessed via a + // symbolic link. An atomic link swap is used when a new tree should be + // switched to. This parameter specifies the path to the symbolic link. + // Envoy will watch the location for changes and reload the file system tree + // when they happen. See documentation on runtime :ref:`atomicity + // ` for further details on how reloads are + // treated. + string symlink_root = 1; + + // Specifies the subdirectory to load within the root directory. This is + // useful if multiple systems share the same delivery mechanism. Envoy + // configuration elements can be contained in a dedicated subdirectory. + string subdirectory = 3; + + // :ref:`Append ` the + // service cluster to the path under symlink root. + bool append_service_cluster = 2; + } + + // :ref:`Admin console runtime ` layer. + message AdminLayer { + } + + // :ref:`Runtime Discovery Service (RTDS) ` layer. + message RtdsLayer { + // Resource to subscribe to at *rtds_config* for the RTDS layer. + string name = 1; + + // RTDS configuration source. + envoy.api.v3alpha.core.ConfigSource rtds_config = 2; + } + + // Descriptive name for the runtime layer. This is only used for the runtime + // :http:get:`/runtime` output. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + oneof layer_specifier { + // :ref:`Static runtime ` layer. + // This follows the :ref:`runtime protobuf JSON representation encoding + // `. Unlike static xDS resources, this static + // layer is overridable by later layers in the runtime virtual filesystem. + option (validate.required) = true; + + google.protobuf.Struct static_layer = 2; + DiskLayer disk_layer = 3; + AdminLayer admin_layer = 4; + RtdsLayer rtds_layer = 5; + } +} + +// Runtime :ref:`configuration overview `. +message LayeredRuntime { + // The :ref:`layers ` of the runtime. This is ordered + // such that later layers in the list overlay earlier entries. + repeated RuntimeLayer layers = 1; +} diff --git a/api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/BUILD b/api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/BUILD index b09f5c858b..669b6745ab 100644 --- a/api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/BUILD +++ b/api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/config/common/dynamic_forward_proxy/v2alpha:pkg"], +) + api_proto_library_internal( name = "cluster", srcs = ["cluster.proto"], diff --git a/api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster.proto b/api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster.proto index d9ae859032..c6d47807ce 100644 --- a/api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster.proto +++ b/api/envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster.proto @@ -5,7 +5,6 @@ package envoy.config.cluster.dynamic_forward_proxy.v2alpha; option java_outer_classname = "DynamicForwardProxyClusterProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.cluster.dynamic_forward_proxy.v2alpha"; -option go_package = "v2alpha"; import "envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache.proto"; diff --git a/api/envoy/config/cluster/dynamic_forward_proxy/v3alpha/BUILD b/api/envoy/config/cluster/dynamic_forward_proxy/v3alpha/BUILD new file mode 100644 index 0000000000..3c1d737802 --- /dev/null +++ b/api/envoy/config/cluster/dynamic_forward_proxy/v3alpha/BUILD @@ -0,0 +1,15 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/config/common/dynamic_forward_proxy/v3alpha:pkg"], +) + +api_proto_library_internal( + name = "cluster", + srcs = ["cluster.proto"], + deps = [ + "//envoy/config/common/dynamic_forward_proxy/v3alpha:dns_cache", + ], +) diff --git a/api/envoy/config/cluster/dynamic_forward_proxy/v3alpha/cluster.proto b/api/envoy/config/cluster/dynamic_forward_proxy/v3alpha/cluster.proto new file mode 100644 index 0000000000..6bc7bdd4c5 --- /dev/null +++ b/api/envoy/config/cluster/dynamic_forward_proxy/v3alpha/cluster.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package envoy.config.cluster.dynamic_forward_proxy.v3alpha; + +option java_outer_classname = "DynamicForwardProxyClusterProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.cluster.dynamic_forward_proxy.v3alpha"; + +import "envoy/config/common/dynamic_forward_proxy/v3alpha/dns_cache.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Dynamic forward proxy cluster configuration] + +// Configuration for the dynamic forward proxy cluster. See the :ref:`architecture overview +// ` for more information. +message ClusterConfig { + // The DNS cache configuration that the cluster will attach to. Note this configuration must + // match that of associated :ref:`dynamic forward proxy HTTP filter configuration + // `. + common.dynamic_forward_proxy.v3alpha.DnsCacheConfig dns_cache_config = 1 + [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/cluster/redis/BUILD b/api/envoy/config/cluster/redis/BUILD index 42e2d408e3..760ae606c0 100644 --- a/api/envoy/config/cluster/redis/BUILD +++ b/api/envoy/config/cluster/redis/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "redis_cluster", srcs = ["redis_cluster.proto"], diff --git a/api/envoy/config/cluster/redis/redis_cluster.proto b/api/envoy/config/cluster/redis/redis_cluster.proto index 2644288c40..fabaa0274f 100644 --- a/api/envoy/config/cluster/redis/redis_cluster.proto +++ b/api/envoy/config/cluster/redis/redis_cluster.proto @@ -5,12 +5,10 @@ package envoy.config.cluster.redis; option java_outer_classname = "RedisClusterProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.cluster.redis"; -option go_package = "v2"; import "google/protobuf/duration.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Redis Cluster Configuration] // This cluster adds support for `Redis Cluster `_, as part @@ -45,10 +43,8 @@ import "gogoproto/gogo.proto"; message RedisClusterConfig { // Interval between successive topology refresh requests. If not set, this defaults to 5s. - google.protobuf.Duration cluster_refresh_rate = 1 - [(validate.rules).duration.gt = {}, (gogoproto.stdduration) = true]; + google.protobuf.Duration cluster_refresh_rate = 1 [(validate.rules).duration.gt = {}]; // Timeout for topology refresh request. If not set, this defaults to 3s. - google.protobuf.Duration cluster_refresh_timeout = 2 - [(validate.rules).duration.gt = {}, (gogoproto.stdduration) = true]; + google.protobuf.Duration cluster_refresh_timeout = 2 [(validate.rules).duration.gt = {}]; } diff --git a/api/envoy/config/common/dynamic_forward_proxy/v2alpha/BUILD b/api/envoy/config/common/dynamic_forward_proxy/v2alpha/BUILD index 5309582645..312ae36b37 100644 --- a/api/envoy/config/common/dynamic_forward_proxy/v2alpha/BUILD +++ b/api/envoy/config/common/dynamic_forward_proxy/v2alpha/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2"], +) + api_proto_library_internal( name = "dns_cache", srcs = ["dns_cache.proto"], diff --git a/api/envoy/config/common/dynamic_forward_proxy/v3alpha/BUILD b/api/envoy/config/common/dynamic_forward_proxy/v3alpha/BUILD new file mode 100644 index 0000000000..e1853725da --- /dev/null +++ b/api/envoy/config/common/dynamic_forward_proxy/v3alpha/BUILD @@ -0,0 +1,16 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha"], +) + +api_proto_library_internal( + name = "dns_cache", + srcs = ["dns_cache.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha:cds", + ], +) diff --git a/api/envoy/config/common/dynamic_forward_proxy/v3alpha/dns_cache.proto b/api/envoy/config/common/dynamic_forward_proxy/v3alpha/dns_cache.proto new file mode 100644 index 0000000000..7b8a67be43 --- /dev/null +++ b/api/envoy/config/common/dynamic_forward_proxy/v3alpha/dns_cache.proto @@ -0,0 +1,69 @@ +syntax = "proto3"; + +package envoy.config.common.dynamic_forward_proxy.v3alpha; + +option java_outer_classname = "DnsCacheProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.common.dynamic_forward_proxy.v3alpha"; + +import "envoy/api/v3alpha/cds.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Dynamic forward proxy common configuration] + +// Configuration for the dynamic forward proxy DNS cache. See the :ref:`architecture overview +// ` for more information. +message DnsCacheConfig { + // The name of the cache. Multiple named caches allow independent dynamic forward proxy + // configurations to operate within a single Envoy process using different configurations. All + // configurations with the same name *must* otherwise have the same settings when referenced + // from different configuration components. Configuration will fail to load if this is not + // the case. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // The DNS lookup family to use during resolution. + // + // [#comment:TODO(mattklein123): Figure out how to support IPv4/IPv6 "happy eyeballs" mode. The + // way this might work is a new lookup family which returns both IPv4 and IPv6 addresses, and + // then configures a host to have a primary and fall back address. With this, we could very + // likely build a "happy eyeballs" connection pool which would race the primary / fall back + // address and return the one that wins. This same method could potentially also be used for + // QUIC to TCP fall back.] + api.v3alpha.Cluster.DnsLookupFamily dns_lookup_family = 2 + [(validate.rules).enum.defined_only = true]; + + // The DNS refresh rate for currently cached DNS hosts. If not specified defaults to 60s. + // + // .. note: + // + // The returned DNS TTL is not currently used to alter the refresh rate. This feature will be + // added in a future change. + google.protobuf.Duration dns_refresh_rate = 3 [(validate.rules).duration.gt = {}]; + + // The TTL for hosts that are unused. Hosts that have not been used in the configured time + // interval will be purged. If not specified defaults to 5m. + // + // .. note: + // + // The TTL is only checked at the time of DNS refresh, as specified by *dns_refresh_rate*. This + // means that if the configured TTL is shorter than the refresh rate the host may not be removed + // immediately. + // + // .. note: + // + // The TTL has no relation to DNS TTL and is only used to control Envoy's resource usage. + google.protobuf.Duration host_ttl = 4 [(validate.rules).duration.gt = {}]; + + // The maximum number of hosts that the cache will hold. If not specified defaults to 1024. + // + // .. note: + // + // The implementation is approximate and enforced independently on each worker thread, thus + // it is possible for the maximum hosts in the cache to go slightly above the configured + // value depending on timing. This is similar to how other circuit breakers work. + google.protobuf.UInt32Value max_hosts = 5 [(validate.rules).uint32.gt = 0]; +} diff --git a/api/envoy/config/common/tap/v2alpha/BUILD b/api/envoy/config/common/tap/v2alpha/BUILD index 863ba519d1..898773297b 100644 --- a/api/envoy/config/common/tap/v2alpha/BUILD +++ b/api/envoy/config/common/tap/v2alpha/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/service/tap/v2alpha:pkg", + ], +) + api_proto_library_internal( name = "common", srcs = ["common.proto"], diff --git a/api/envoy/config/common/tap/v2alpha/common.proto b/api/envoy/config/common/tap/v2alpha/common.proto index 6d3c869baf..ac640b83e4 100644 --- a/api/envoy/config/common/tap/v2alpha/common.proto +++ b/api/envoy/config/common/tap/v2alpha/common.proto @@ -4,7 +4,6 @@ import "envoy/service/tap/v2alpha/common.proto"; import "envoy/api/v2/core/config_source.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; package envoy.config.common.tap.v2alpha; diff --git a/api/envoy/config/common/tap/v3alpha/BUILD b/api/envoy/config/common/tap/v3alpha/BUILD new file mode 100644 index 0000000000..55147b12ba --- /dev/null +++ b/api/envoy/config/common/tap/v3alpha/BUILD @@ -0,0 +1,20 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/service/tap/v3alpha:pkg", + ], +) + +api_proto_library_internal( + name = "common", + srcs = ["common.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha/core:config_source", + "//envoy/service/tap/v3alpha:common", + ], +) diff --git a/api/envoy/config/common/tap/v3alpha/common.proto b/api/envoy/config/common/tap/v3alpha/common.proto new file mode 100644 index 0000000000..c260d04afa --- /dev/null +++ b/api/envoy/config/common/tap/v3alpha/common.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +import "envoy/service/tap/v3alpha/common.proto"; +import "envoy/api/v3alpha/core/config_source.proto"; + +import "validate/validate.proto"; + +package envoy.config.common.tap.v3alpha; + +option java_outer_classname = "CommonProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.common.tap.v3alpha"; + +// [#protodoc-title: Common tap extension configuration] + +// Common configuration for all tap extensions. +message CommonExtensionConfig { + + // [#not-implemented-hide:] + message TapDSConfig { + // Configuration for the source of TapDS updates for this Cluster. + envoy.api.v3alpha.core.ConfigSource config_source = 1 + [(validate.rules).message.required = true]; + + // Tap config to request from XDS server. + string name = 2 [(validate.rules).string.min_bytes = 1]; + } + + oneof config_type { + option (validate.required) = true; + + // If specified, the tap filter will be configured via an admin handler. + AdminConfig admin_config = 1; + + // If specified, the tap filter will be configured via a static configuration that cannot be + // changed. + service.tap.v3alpha.TapConfig static_config = 2; + + // [#not-implemented-hide:] Configuration to use for TapDS updates for the filter. + TapDSConfig tapds_config = 3; + } +} + +// Configuration for the admin handler. See :ref:`here ` for +// more information. +message AdminConfig { + // Opaque configuration ID. When requests are made to the admin handler, the passed opaque ID is + // matched to the configured filter opaque ID to determine which filter to configure. + string config_id = 1 [(validate.rules).string.min_bytes = 1]; +} diff --git a/api/envoy/config/filter/accesslog/v2/BUILD b/api/envoy/config/filter/accesslog/v2/BUILD index fdbf376af1..d9b7409213 100644 --- a/api/envoy/config/filter/accesslog/v2/BUILD +++ b/api/envoy/config/filter/accesslog/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/api/v2/route:pkg", + "//envoy/type", + ], +) + api_proto_library_internal( name = "accesslog", srcs = ["accesslog.proto"], @@ -16,13 +24,3 @@ api_proto_library_internal( "//envoy/type:percent", ], ) - -api_go_proto_library( - name = "accesslog", - proto = ":accesslog", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/route:route_go_proto", - "//envoy/type:percent_go_proto", - ], -) diff --git a/api/envoy/config/filter/accesslog/v2/accesslog.proto b/api/envoy/config/filter/accesslog/v2/accesslog.proto index 66d901c2d4..d777708175 100644 --- a/api/envoy/config/filter/accesslog/v2/accesslog.proto +++ b/api/envoy/config/filter/accesslog/v2/accesslog.proto @@ -5,7 +5,6 @@ package envoy.config.filter.accesslog.v2; option java_outer_classname = "AccesslogProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.accesslog.v2"; -option go_package = "v2"; import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/route/route.proto"; @@ -24,6 +23,7 @@ message AccessLog { // // #. "envoy.file_access_log" // #. "envoy.http_grpc_access_log" + // #. "envoy.tcp_grpc_access_log" string name = 1; // Filter which is used to determine if the access log needs to be written. @@ -36,6 +36,8 @@ message AccessLog { // ` // #. "envoy.http_grpc_access_log": :ref:`HttpGrpcAccessLogConfig // ` + // #. "envoy.tcp_grpc_access_log": :ref:`TcpGrpcAccessLogConfig + // ` oneof config_type { google.protobuf.Struct config = 3; diff --git a/api/envoy/config/filter/accesslog/v3alpha/BUILD b/api/envoy/config/filter/accesslog/v3alpha/BUILD new file mode 100644 index 0000000000..454a1ab4a1 --- /dev/null +++ b/api/envoy/config/filter/accesslog/v3alpha/BUILD @@ -0,0 +1,26 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/route:pkg", + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "accesslog", + srcs = ["accesslog.proto"], + visibility = [ + "//envoy/config/filter/http/router/v3alpha:__pkg__", + "//envoy/config/filter/network/http_connection_manager/v3alpha:__pkg__", + "//envoy/config/filter/network/tcp_proxy/v3alpha:__pkg__", + ], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/route", + "//envoy/type:percent", + ], +) diff --git a/api/envoy/config/filter/accesslog/v3alpha/accesslog.proto b/api/envoy/config/filter/accesslog/v3alpha/accesslog.proto new file mode 100644 index 0000000000..b7beef0bd9 --- /dev/null +++ b/api/envoy/config/filter/accesslog/v3alpha/accesslog.proto @@ -0,0 +1,247 @@ +syntax = "proto3"; + +package envoy.config.filter.accesslog.v3alpha; + +option java_outer_classname = "AccesslogProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.accesslog.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/route/route.proto"; +import "envoy/type/percent.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Common access log types] + +message AccessLog { + // The name of the access log implementation to instantiate. The name must + // match a statically registered access log. Current built-in loggers include: + // + // #. "envoy.file_access_log" + // #. "envoy.http_grpc_access_log" + // #. "envoy.tcp_grpc_access_log" + string name = 1; + + // Filter which is used to determine if the access log needs to be written. + AccessLogFilter filter = 2; + + // Custom configuration that depends on the access log being instantiated. Built-in + // configurations include: + // + // #. "envoy.file_access_log": :ref:`FileAccessLog + // ` + // #. "envoy.http_grpc_access_log": :ref:`HttpGrpcAccessLogConfig + // ` + // #. "envoy.tcp_grpc_access_log": :ref:`TcpGrpcAccessLogConfig + // ` + oneof config_type { + google.protobuf.Struct config = 3; + + google.protobuf.Any typed_config = 4; + } +} + +message AccessLogFilter { + oneof filter_specifier { + option (validate.required) = true; + + // Status code filter. + StatusCodeFilter status_code_filter = 1; + + // Duration filter. + DurationFilter duration_filter = 2; + + // Not health check filter. + NotHealthCheckFilter not_health_check_filter = 3; + + // Traceable filter. + TraceableFilter traceable_filter = 4; + + // Runtime filter. + RuntimeFilter runtime_filter = 5; + + // And filter. + AndFilter and_filter = 6; + + // Or filter. + OrFilter or_filter = 7; + + // Header filter. + HeaderFilter header_filter = 8; + + // Response flag filter. + ResponseFlagFilter response_flag_filter = 9; + + // gRPC status filter. + GrpcStatusFilter grpc_status_filter = 10; + + // Extension filter. + ExtensionFilter extension_filter = 11; + } +} + +// Filter on an integer comparison. +message ComparisonFilter { + enum Op { + // = + EQ = 0; + + // >= + GE = 1; + + // <= + LE = 2; + } + + // Comparison operator. + Op op = 1 [(validate.rules).enum.defined_only = true]; + + // Value to compare against. + envoy.api.v3alpha.core.RuntimeUInt32 value = 2; +} + +// Filters on HTTP response/status code. +message StatusCodeFilter { + // Comparison. + ComparisonFilter comparison = 1 [(validate.rules).message.required = true]; +} + +// Filters on total request duration in milliseconds. +message DurationFilter { + // Comparison. + ComparisonFilter comparison = 1 [(validate.rules).message.required = true]; +} + +// Filters for requests that are not health check requests. A health check +// request is marked by the health check filter. +message NotHealthCheckFilter { +} + +// Filters for requests that are traceable. See the tracing overview for more +// information on how a request becomes traceable. +message TraceableFilter { +} + +// Filters for random sampling of requests. +message RuntimeFilter { + // Runtime key to get an optional overridden numerator for use in the *percent_sampled* field. + // If found in runtime, this value will replace the default numerator. + string runtime_key = 1 [(validate.rules).string.min_bytes = 1]; + + // The default sampling percentage. If not specified, defaults to 0% with denominator of 100. + envoy.type.FractionalPercent percent_sampled = 2; + + // By default, sampling pivots on the header + // :ref:`x-request-id` being present. If + // :ref:`x-request-id` is present, the filter will + // consistently sample across multiple hosts based on the runtime key value and the value + // extracted from :ref:`x-request-id`. If it is + // missing, or *use_independent_randomness* is set to true, the filter will randomly sample based + // on the runtime key value alone. *use_independent_randomness* can be used for logging kill + // switches within complex nested :ref:`AndFilter + // ` and :ref:`OrFilter + // ` blocks that are easier to reason + // about from a probability perspective (i.e., setting to true will cause the filter to behave + // like an independent random variable when composed within logical operator filters). + bool use_independent_randomness = 3; +} + +// Performs a logical “and” operation on the result of each filter in filters. +// Filters are evaluated sequentially and if one of them returns false, the +// filter returns false immediately. +message AndFilter { + repeated AccessLogFilter filters = 1 [(validate.rules).repeated .min_items = 2]; +} + +// Performs a logical “or” operation on the result of each individual filter. +// Filters are evaluated sequentially and if one of them returns true, the +// filter returns true immediately. +message OrFilter { + repeated AccessLogFilter filters = 2 [(validate.rules).repeated .min_items = 2]; +} + +// Filters requests based on the presence or value of a request header. +message HeaderFilter { + // Only requests with a header which matches the specified HeaderMatcher will pass the filter + // check. + envoy.api.v3alpha.route.HeaderMatcher header = 1 [(validate.rules).message.required = true]; +} + +// Filters requests that received responses with an Envoy response flag set. +// A list of the response flags can be found +// in the access log formatter :ref:`documentation`. +message ResponseFlagFilter { + // Only responses with the any of the flags listed in this field will be logged. + // 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", + "RLSE", + "DC", + "URX", + "SI", + "IH" + ] + }]; +} + +// Filters gRPC requests based on their response status. If a gRPC status is not provided, the +// filter will infer the status from the HTTP status code. +message GrpcStatusFilter { + enum Status { + OK = 0; + CANCELED = 1; + UNKNOWN = 2; + INVALID_ARGUMENT = 3; + DEADLINE_EXCEEDED = 4; + NOT_FOUND = 5; + ALREADY_EXISTS = 6; + PERMISSION_DENIED = 7; + RESOURCE_EXHAUSTED = 8; + FAILED_PRECONDITION = 9; + ABORTED = 10; + OUT_OF_RANGE = 11; + UNIMPLEMENTED = 12; + INTERNAL = 13; + UNAVAILABLE = 14; + DATA_LOSS = 15; + UNAUTHENTICATED = 16; + } + + // Logs only responses that have any one of the gRPC statuses in this field. + repeated Status statuses = 1 [(validate.rules).repeated .items.enum.defined_only = true]; + + // If included and set to true, the filter will instead block all responses with a gRPC status or + // inferred gRPC status enumerated in statuses, and allow all other responses. + bool exclude = 2; +} + +// Extension filter is statically registered at runtime. +message ExtensionFilter { + // The name of the filter implementation to instantiate. The name must + // match a statically registered filter. + string name = 1; + + // Custom configuration that depends on the filter being instantiated. + oneof config_type { + google.protobuf.Struct config = 2; + google.protobuf.Any typed_config = 3; + } +} diff --git a/api/envoy/config/filter/dubbo/router/v2alpha1/BUILD b/api/envoy/config/filter/dubbo/router/v2alpha1/BUILD index 51c69c0d5b..68bd8c126b 100644 --- a/api/envoy/config/filter/dubbo/router/v2alpha1/BUILD +++ b/api/envoy/config/filter/dubbo/router/v2alpha1/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "router", srcs = ["router.proto"], diff --git a/api/envoy/config/filter/dubbo/router/v2alpha1/router.proto b/api/envoy/config/filter/dubbo/router/v2alpha1/router.proto index 37a5542a17..4e65f14e0e 100644 --- a/api/envoy/config/filter/dubbo/router/v2alpha1/router.proto +++ b/api/envoy/config/filter/dubbo/router/v2alpha1/router.proto @@ -5,7 +5,6 @@ package envoy.config.filter.dubbo.router.v2alpha1; option java_outer_classname = "RouterProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.dubbo.router.v2alpha1"; -option go_package = "v2alpha1"; // [#protodoc-title: Router] // Dubbo router :ref:`configuration overview `. diff --git a/api/envoy/config/filter/dubbo/router/v3alpha/BUILD b/api/envoy/config/filter/dubbo/router/v3alpha/BUILD new file mode 100644 index 0000000000..68bd8c126b --- /dev/null +++ b/api/envoy/config/filter/dubbo/router/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "router", + srcs = ["router.proto"], +) diff --git a/api/envoy/config/filter/dubbo/router/v3alpha/router.proto b/api/envoy/config/filter/dubbo/router/v3alpha/router.proto new file mode 100644 index 0000000000..46b6609d1c --- /dev/null +++ b/api/envoy/config/filter/dubbo/router/v3alpha/router.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package envoy.config.filter.dubbo.router.v3alpha; + +option java_outer_classname = "RouterProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.dubbo.router.v3alpha"; + +// [#protodoc-title: Router] +// Dubbo router :ref:`configuration overview `. + +message Router { +} diff --git a/api/envoy/config/filter/fault/v2/BUILD b/api/envoy/config/filter/fault/v2/BUILD index 35419a9902..78687f4e4d 100644 --- a/api/envoy/config/filter/fault/v2/BUILD +++ b/api/envoy/config/filter/fault/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/type"], +) + api_proto_library_internal( name = "fault", srcs = ["fault.proto"], diff --git a/api/envoy/config/filter/fault/v2/fault.proto b/api/envoy/config/filter/fault/v2/fault.proto index f27f9d4462..15164172dc 100644 --- a/api/envoy/config/filter/fault/v2/fault.proto +++ b/api/envoy/config/filter/fault/v2/fault.proto @@ -5,14 +5,12 @@ package envoy.config.filter.fault.v2; option java_outer_classname = "FaultProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.fault.v2"; -option go_package = "v2"; import "envoy/type/percent.proto"; import "google/protobuf/duration.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Common fault injection types] @@ -44,8 +42,7 @@ message FaultDelay { // delay will be injected before a new request/operation. For TCP // connections, the proxying of the connection upstream will be delayed // for the specified period. This is required if type is FIXED. - google.protobuf.Duration fixed_delay = 3 - [(validate.rules).duration.gt = {}, (gogoproto.stdduration) = true]; + google.protobuf.Duration fixed_delay = 3 [(validate.rules).duration.gt = {}]; // Fault delays are controlled via an HTTP header (if applicable). HeaderDelay header_delay = 5; diff --git a/api/envoy/config/filter/fault/v3alpha/BUILD b/api/envoy/config/filter/fault/v3alpha/BUILD new file mode 100644 index 0000000000..61bc8dc6bc --- /dev/null +++ b/api/envoy/config/filter/fault/v3alpha/BUILD @@ -0,0 +1,17 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/type"], +) + +api_proto_library_internal( + name = "fault", + srcs = ["fault.proto"], + visibility = [ + "//envoy/config/filter/http/fault/v3alpha:__pkg__", + "//envoy/config/filter/network/mongo_proxy/v3alpha:__pkg__", + ], + deps = ["//envoy/type:percent"], +) diff --git a/api/envoy/config/filter/fault/v3alpha/fault.proto b/api/envoy/config/filter/fault/v3alpha/fault.proto new file mode 100644 index 0000000000..21e0f9e12e --- /dev/null +++ b/api/envoy/config/filter/fault/v3alpha/fault.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +package envoy.config.filter.fault.v3alpha; + +option java_outer_classname = "FaultProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.fault.v3alpha"; + +import "envoy/type/percent.proto"; + +import "google/protobuf/duration.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Common fault injection types] + +// Delay specification is used to inject latency into the +// HTTP/gRPC/Mongo/Redis operation or delay proxying of TCP connections. +message FaultDelay { + // Fault delays are controlled via an HTTP header (if applicable). See the + // :ref:`http fault filter ` documentation for + // more information. + message HeaderDelay { + } + + enum FaultDelayType { + // Unused and deprecated. + FIXED = 0; + } + + // Unused and deprecated. Will be removed in the next release. + FaultDelayType type = 1 [deprecated = true]; + + reserved 2; + + oneof fault_delay_secifier { + option (validate.required) = true; + + // Add a fixed delay before forwarding the operation upstream. See + // https://developers.google.com/protocol-buffers/docs/proto3#json for + // the JSON/YAML Duration mapping. For HTTP/Mongo/Redis, the specified + // delay will be injected before a new request/operation. For TCP + // connections, the proxying of the connection upstream will be delayed + // for the specified period. This is required if type is FIXED. + google.protobuf.Duration fixed_delay = 3 [(validate.rules).duration.gt = {}]; + + // Fault delays are controlled via an HTTP header (if applicable). + HeaderDelay header_delay = 5; + } + + // The percentage of operations/connections/requests on which the delay will be injected. + type.FractionalPercent percentage = 4; +} + +// Describes a rate limit to be applied. +message FaultRateLimit { + // Describes a fixed/constant rate limit. + message FixedLimit { + // The limit supplied in KiB/s. + uint64 limit_kbps = 1 [(validate.rules).uint64.gte = 1]; + } + + // Rate limits are controlled via an HTTP header (if applicable). See the + // :ref:`http fault filter ` documentation for + // more information. + message HeaderLimit { + } + + oneof limit_type { + option (validate.required) = true; + + // A fixed rate limit. + FixedLimit fixed_limit = 1; + + // Rate limits are controlled via an HTTP header (if applicable). + HeaderLimit header_limit = 3; + } + + // The percentage of operations/connections/requests on which the rate limit will be injected. + type.FractionalPercent percentage = 2; +} diff --git a/api/envoy/config/filter/http/adaptive_concurrency/v2alpha/BUILD b/api/envoy/config/filter/http/adaptive_concurrency/v2alpha/BUILD index 948ceec222..a02fc54275 100644 --- a/api/envoy/config/filter/http/adaptive_concurrency/v2alpha/BUILD +++ b/api/envoy/config/filter/http/adaptive_concurrency/v2alpha/BUILD @@ -1,11 +1,19 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/type", + ], +) + api_proto_library_internal( name = "adaptive_concurrency", srcs = ["adaptive_concurrency.proto"], deps = [ - "//envoy/api/v2/core:base", + "//envoy/api/v3alpha/core:base", + "//envoy/type:percent", ], ) diff --git a/api/envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.proto b/api/envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.proto index ff19657260..9b03169f7d 100644 --- a/api/envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.proto +++ b/api/envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.proto @@ -5,7 +5,60 @@ package envoy.config.filter.http.adaptive_concurrency.v2alpha; option java_package = "io.envoyproxy.envoy.config.filter.http.adaptive_concurrency.v2alpha"; option java_outer_classname = "AdaptiveConcurrencyProto"; option java_multiple_files = true; -option go_package = "v2alpha"; + +import "envoy/type/percent.proto"; + +import "google/protobuf/duration.proto"; +import "google/api/annotations.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// Configuration parameters for the gradient controller. +message GradientControllerConfig { + // The percentile to use when summarizing aggregated samples. Defaults to p50. + envoy.type.Percent sample_aggregate_percentile = 1; + + // Parameters controlling the periodic recalculation of the concurrency limit from sampled request + // latencies. + message ConcurrencyLimitCalculationParams { + // The maximum value the gradient is allowed to take. This influences how aggressively the + // concurrency limit can increase. Defaults to 2.0. + google.protobuf.DoubleValue max_gradient = 1 [(validate.rules).double.gt = 1.0]; + + // The allowed upper-bound on the calculated concurrency limit. Defaults to 1000. + google.protobuf.UInt32Value max_concurrency_limit = 2 [(validate.rules).uint32.gt = 0]; + + // The period of time samples are taken to recalculate the concurrency limit. + google.protobuf.Duration concurrency_update_interval = 3 [(validate.rules).duration = { + required: true, + gt: {seconds: 0} + }]; + } + ConcurrencyLimitCalculationParams concurrency_limit_params = 2 + [(validate.rules).message.required = true]; + + // Parameters controlling the periodic minRTT recalculation. + message MinimumRTTCalculationParams { + // The time interval between recalculating the minimum request round-trip time. + google.protobuf.Duration interval = 1 [(validate.rules).duration = { + required: true, + gt: {seconds: 0} + }]; + + // The number of requests to aggregate/sample during the minRTT recalculation window before + // updating. Defaults to 50. + google.protobuf.UInt32Value request_count = 2 [(validate.rules).uint32.gt = 0]; + }; + MinimumRTTCalculationParams min_rtt_calc_params = 3 [(validate.rules).message.required = true]; +} message AdaptiveConcurrency { + oneof concurrency_controller_config { + option (validate.required) = true; + + // Gradient concurrency control will be used. + GradientControllerConfig gradient_controller_config = 1 + [(validate.rules).message.required = true]; + } } diff --git a/api/envoy/config/filter/http/buffer/v2/BUILD b/api/envoy/config/filter/http/buffer/v2/BUILD index e59429af9a..039ebb63e6 100644 --- a/api/envoy/config/filter/http/buffer/v2/BUILD +++ b/api/envoy/config/filter/http/buffer/v2/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "buffer", srcs = ["buffer.proto"], diff --git a/api/envoy/config/filter/http/buffer/v2/buffer.proto b/api/envoy/config/filter/http/buffer/v2/buffer.proto index a203d9d98c..ce6c0d6d14 100644 --- a/api/envoy/config/filter/http/buffer/v2/buffer.proto +++ b/api/envoy/config/filter/http/buffer/v2/buffer.proto @@ -5,12 +5,10 @@ package envoy.config.filter.http.buffer.v2; option java_outer_classname = "BufferProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.buffer.v2"; -option go_package = "v2"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Buffer] // Buffer :ref:`configuration overview `. diff --git a/api/envoy/config/filter/http/buffer/v3alpha/BUILD b/api/envoy/config/filter/http/buffer/v3alpha/BUILD new file mode 100644 index 0000000000..039ebb63e6 --- /dev/null +++ b/api/envoy/config/filter/http/buffer/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "buffer", + srcs = ["buffer.proto"], +) diff --git a/api/envoy/config/filter/http/buffer/v3alpha/buffer.proto b/api/envoy/config/filter/http/buffer/v3alpha/buffer.proto new file mode 100644 index 0000000000..9d44f35032 --- /dev/null +++ b/api/envoy/config/filter/http/buffer/v3alpha/buffer.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package envoy.config.filter.http.buffer.v3alpha; + +option java_outer_classname = "BufferProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.buffer.v3alpha"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Buffer] +// Buffer :ref:`configuration overview `. + +message Buffer { + reserved 2; // formerly max_request_time + + // The maximum request size that the filter will buffer before the connection + // manager will stop buffering and return a 413 response. + google.protobuf.UInt32Value max_request_bytes = 1 [(validate.rules).uint32.gt = 0]; +} + +message BufferPerRoute { + oneof override { + option (validate.required) = true; + + // Disable the buffer filter for this particular vhost or route. + bool disabled = 1 [(validate.rules).bool.const = true]; + + // Override the global configuration of the filter with this new config. + Buffer buffer = 2 [(validate.rules).message.required = true]; + } +} diff --git a/api/envoy/config/filter/http/csrf/v2/BUILD b/api/envoy/config/filter/http/csrf/v2/BUILD index 0d58b1ef6d..af3a87b07c 100644 --- a/api/envoy/config/filter/http/csrf/v2/BUILD +++ b/api/envoy/config/filter/http/csrf/v2/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/type/matcher", + ], +) + api_proto_library_internal( name = "csrf", srcs = ["csrf.proto"], diff --git a/api/envoy/config/filter/http/csrf/v2/csrf.proto b/api/envoy/config/filter/http/csrf/v2/csrf.proto index 525ed118a7..b5c78db544 100644 --- a/api/envoy/config/filter/http/csrf/v2/csrf.proto +++ b/api/envoy/config/filter/http/csrf/v2/csrf.proto @@ -5,13 +5,11 @@ package envoy.config.filter.http.csrf.v2; option java_outer_classname = "CsrfPolicyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.csrf.v2"; -option go_package = "v2"; import "envoy/api/v2/core/base.proto"; import "envoy/type/matcher/string.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: CSRF] // Cross-Site Request Forgery :ref:`configuration overview `. diff --git a/api/envoy/config/filter/http/csrf/v3alpha/BUILD b/api/envoy/config/filter/http/csrf/v3alpha/BUILD new file mode 100644 index 0000000000..676559830c --- /dev/null +++ b/api/envoy/config/filter/http/csrf/v3alpha/BUILD @@ -0,0 +1,19 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/type/matcher", + ], +) + +api_proto_library_internal( + name = "csrf", + srcs = ["csrf.proto"], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/type/matcher:string", + ], +) diff --git a/api/envoy/config/filter/http/csrf/v3alpha/csrf.proto b/api/envoy/config/filter/http/csrf/v3alpha/csrf.proto new file mode 100644 index 0000000000..7c8416878a --- /dev/null +++ b/api/envoy/config/filter/http/csrf/v3alpha/csrf.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package envoy.config.filter.http.csrf.v3alpha; + +option java_outer_classname = "CsrfPolicyProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.csrf.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/type/matcher/string.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: CSRF] +// Cross-Site Request Forgery :ref:`configuration overview `. + +// CSRF filter config. +message CsrfPolicy { + // Specifies if CSRF is enabled. + // + // More information on how this can be controlled via runtime can be found + // :ref:`here `. + // + // .. note:: + // + // This field defaults to 100/:ref:`HUNDRED + // `. + envoy.api.v3alpha.core.RuntimeFractionalPercent filter_enabled = 1 + [(validate.rules).message.required = true]; + + // Specifies that CSRF policies will be evaluated and tracked, but not enforced. + // This is intended to be used when filter_enabled is off. + // + // More information on how this can be controlled via runtime can be found + // :ref:`here `. + // + // .. note:: + // + // This field defaults to 100/:ref:`HUNDRED + // `. + envoy.api.v3alpha.core.RuntimeFractionalPercent shadow_enabled = 2; + + // Specifies additional source origins that will be allowed in addition to + // the destination origin. + // + // More information on how this can be configured via runtime can be found + // :ref:`here `. + repeated envoy.type.matcher.StringMatcher additional_origins = 3; +} diff --git a/api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/BUILD b/api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/BUILD index 4fd1d84399..15d184377e 100644 --- a/api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/BUILD +++ b/api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/config/common/dynamic_forward_proxy/v2alpha:pkg"], +) + api_proto_library_internal( name = "dynamic_forward_proxy", srcs = ["dynamic_forward_proxy.proto"], diff --git a/api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy.proto b/api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy.proto index 631363a6d9..c315ddb465 100644 --- a/api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy.proto +++ b/api/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.dynamic_forward_proxy.v2alpha; option java_outer_classname = "DynamicForwardProxyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.dynamic_forward_proxy.v2alpha"; -option go_package = "v2alpha"; import "envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache.proto"; diff --git a/api/envoy/config/filter/http/dynamic_forward_proxy/v3alpha/BUILD b/api/envoy/config/filter/http/dynamic_forward_proxy/v3alpha/BUILD new file mode 100644 index 0000000000..c06227674a --- /dev/null +++ b/api/envoy/config/filter/http/dynamic_forward_proxy/v3alpha/BUILD @@ -0,0 +1,15 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/config/common/dynamic_forward_proxy/v3alpha:pkg"], +) + +api_proto_library_internal( + name = "dynamic_forward_proxy", + srcs = ["dynamic_forward_proxy.proto"], + deps = [ + "//envoy/config/common/dynamic_forward_proxy/v3alpha:dns_cache", + ], +) diff --git a/api/envoy/config/filter/http/dynamic_forward_proxy/v3alpha/dynamic_forward_proxy.proto b/api/envoy/config/filter/http/dynamic_forward_proxy/v3alpha/dynamic_forward_proxy.proto new file mode 100644 index 0000000000..f60aaae89e --- /dev/null +++ b/api/envoy/config/filter/http/dynamic_forward_proxy/v3alpha/dynamic_forward_proxy.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package envoy.config.filter.http.dynamic_forward_proxy.v3alpha; + +option java_outer_classname = "DynamicForwardProxyProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.dynamic_forward_proxy.v3alpha"; + +import "envoy/config/common/dynamic_forward_proxy/v3alpha/dns_cache.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Dynamic forward proxy] + +// Configuration for the dynamic forward proxy HTTP filter. See the :ref:`architecture overview +// ` for more information. +message FilterConfig { + // The DNS cache configuration that the filter will attach to. Note this configuration must + // match that of associated :ref:`dynamic forward proxy cluster configuration + // `. + common.dynamic_forward_proxy.v3alpha.DnsCacheConfig dns_cache_config = 1 + [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/filter/http/ext_authz/v2/BUILD b/api/envoy/config/filter/http/ext_authz/v2/BUILD index b1d02437df..10187f48bd 100644 --- a/api/envoy/config/filter/http/ext_authz/v2/BUILD +++ b/api/envoy/config/filter/http/ext_authz/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/type", + "//envoy/type/matcher", + ], +) + api_proto_library_internal( name = "ext_authz", srcs = ["ext_authz.proto"], diff --git a/api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto b/api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto index 8e2ea7661e..84d4ab1949 100644 --- a/api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto +++ b/api/envoy/config/filter/http/ext_authz/v2/ext_authz.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.ext_authz.v2; option java_outer_classname = "ExtAuthzProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.ext_authz.v2"; -option go_package = "v2"; import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/core/grpc_service.proto"; @@ -15,9 +14,6 @@ import "envoy/type/http_status.proto"; import "envoy/type/matcher/string.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.stable_marshaler_all) = true; // [#protodoc-title: External Authorization] // External Authorization :ref:`configuration overview `. @@ -72,6 +68,20 @@ message ExtAuthz { // Sets the HTTP status that is returned to the client when there is a network error between the // filter and the authorization server. The default status is HTTP 403 Forbidden. envoy.type.HttpStatus status_on_error = 7; + + // Specifies a list of metadata namespaces whose values, if present, will be passed to the + // ext_authz service as an opaque *protobuf::Struct*. + // + // For example, if the *jwt_authn* filter is used and :ref:`payload_in_metadata + // ` is set, + // then the following will pass the jwt payload to the authorization server. + // + // .. code-block:: yaml + // + // metadata_context_namespaces: + // - envoy.filters.http.jwt_authn + // + repeated string metadata_context_namespaces = 8; } // Configuration for buffering the request data. diff --git a/api/envoy/config/filter/http/ext_authz/v3alpha/BUILD b/api/envoy/config/filter/http/ext_authz/v3alpha/BUILD new file mode 100644 index 0000000000..cb0d25a3ee --- /dev/null +++ b/api/envoy/config/filter/http/ext_authz/v3alpha/BUILD @@ -0,0 +1,23 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/type", + "//envoy/type/matcher", + ], +) + +api_proto_library_internal( + name = "ext_authz", + srcs = ["ext_authz.proto"], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:grpc_service", + "//envoy/api/v3alpha/core:http_uri", + "//envoy/type:http_status", + "//envoy/type/matcher:string", + ], +) diff --git a/api/envoy/config/filter/http/ext_authz/v3alpha/ext_authz.proto b/api/envoy/config/filter/http/ext_authz/v3alpha/ext_authz.proto new file mode 100644 index 0000000000..113be0256b --- /dev/null +++ b/api/envoy/config/filter/http/ext_authz/v3alpha/ext_authz.proto @@ -0,0 +1,205 @@ +syntax = "proto3"; + +package envoy.config.filter.http.ext_authz.v3alpha; + +option java_outer_classname = "ExtAuthzProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.ext_authz.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/core/grpc_service.proto"; +import "envoy/api/v3alpha/core/http_uri.proto"; + +import "envoy/type/http_status.proto"; +import "envoy/type/matcher/string.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: External Authorization] +// External Authorization :ref:`configuration overview `. + +message ExtAuthz { + // External authorization service configuration. + oneof services { + // gRPC service configuration (default timeout: 200ms). + envoy.api.v3alpha.core.GrpcService grpc_service = 1; + + // HTTP service configuration (default timeout: 200ms). + HttpService http_service = 3; + } + + // Changes filter's behaviour on errors: + // + // 1. When set to true, the filter will *accept* client request even if the communication with + // the authorization service has failed, or if the authorization service has returned a HTTP 5xx + // error. + // + // 2. When set to false, ext-authz will *reject* client requests and return a *Forbidden* + // response if the communication with the authorization service has failed, or if the + // authorization service has returned a HTTP 5xx error. + // + // Note that errors can be *always* tracked in the :ref:`stats + // `. + bool failure_mode_allow = 2; + + // Sets the package version the gRPC service should use. This is particularly + // useful when transitioning from alpha to release versions assuming that both definitions are + // semantically compatible. Deprecation note: This field is deprecated and should only be used for + // version upgrade. See release notes for more details. + bool use_alpha = 4 [deprecated = true]; + + // Enables filter to buffer the client request body and send it within the authorization request. + // A ``x-envoy-auth-partial-body: false|true`` metadata header will be added to the authorization + // request message indicating if the body data is partial. + BufferSettings with_request_body = 5; + + // Clears route cache in order to allow the external authorization service to correctly affect + // routing decisions. Filter clears all cached routes when: + // + // 1. The field is set to *true*. + // + // 2. The status returned from the authorization service is a HTTP 200 or gRPC 0. + // + // 3. At least one *authorization response header* is added to the client request, or is used for + // altering another client request header. + // + bool clear_route_cache = 6; + + // Sets the HTTP status that is returned to the client when there is a network error between the + // filter and the authorization server. The default status is HTTP 403 Forbidden. + envoy.type.HttpStatus status_on_error = 7; + + // Specifies a list of metadata namespaces whose values, if present, will be passed to the + // ext_authz service as an opaque *protobuf::Struct*. + // + // For example, if the *jwt_authn* filter is used and :ref:`payload_in_metadata + // ` is set, + // then the following will pass the jwt payload to the authorization server. + // + // .. code-block:: yaml + // + // metadata_context_namespaces: + // - envoy.filters.http.jwt_authn + // + repeated string metadata_context_namespaces = 8; +} + +// Configuration for buffering the request data. +message BufferSettings { + // Sets the maximum size of a message body that the filter will hold in memory. Envoy will return + // *HTTP 413* and will *not* initiate the authorization process when buffer reaches the number + // set in this field. Note that this setting will have precedence over :ref:`failure_mode_allow + // `. + uint32 max_request_bytes = 1 [(validate.rules).uint32.gt = 0]; + + // When this field is true, Envoy will buffer the message until *max_request_bytes* is reached. + // The authorization request will be dispatched and no 413 HTTP error will be returned by the + // filter. + bool allow_partial_message = 2; +} + +// HttpService is used for raw HTTP communication between the filter and the authorization service. +// When configured, the filter will parse the client request and use these attributes to call the +// authorization server. Depending on the response, the filter may reject or accept the client +// request. Note that in any of these events, metadata can be added, removed or overridden by the +// filter: +// +// *On authorization request*, a list of allowed request headers may be supplied. See +// :ref:`allowed_headers +// ` +// for details. Additional headers metadata may be added to the authorization request. See +// :ref:`headers_to_add +// ` for +// details. +// +// On authorization response status HTTP 200 OK, the filter will allow traffic to the upstream and +// additional headers metadata may be added to the original client request. See +// :ref:`allowed_upstream_headers +// ` +// for details. +// +// On other authorization response statuses, the filter will not allow traffic. Additional headers +// metadata as well as body may be added to the client's response. See :ref:`allowed_client_headers +// ` +// for details. +message HttpService { + // Sets the HTTP server URI which the authorization requests must be sent to. + envoy.api.v3alpha.core.HttpUri server_uri = 1; + + // Sets a prefix to the value of authorization request header *Path*. + string path_prefix = 2; + + reserved 3; + reserved 4; + reserved 5; + reserved 6; + + // Settings used for controlling authorization request metadata. + AuthorizationRequest authorization_request = 7; + + // Settings used for controlling authorization response metadata. + AuthorizationResponse authorization_response = 8; +} + +message AuthorizationRequest { + // Authorization request will include the client request headers that have a correspondent match + // in the :ref:`list `. Note that in addition to the + // user's supplied matchers: + // + // 1. *Host*, *Method*, *Path* and *Content-Length* are automatically included to the list. + // + // 2. *Content-Length* will be set to 0 and the request to the authorization service will not have + // a message body. + // + envoy.type.matcher.ListStringMatcher allowed_headers = 1; + + // Sets a list of headers that will be included to the request to authorization service. Note that + // client request of the same key will be overridden. + repeated envoy.api.v3alpha.core.HeaderValue headers_to_add = 2; +} + +message AuthorizationResponse { + // When this :ref:`list ` is set, authorization + // response headers that have a correspondent match will be added to the original client request. + // Note that coexistent headers will be overridden. + envoy.type.matcher.ListStringMatcher allowed_upstream_headers = 1; + + // When this :ref:`list `. is set, authorization + // response headers that have a correspondent match will be added to the client's response. Note + // that when this list is *not* set, all the authorization response headers, except *Authority + // (Host)* will be in the response to the client. When a header is included in this list, *Path*, + // *Status*, *Content-Length*, *WWWAuthenticate* and *Location* are automatically added. + envoy.type.matcher.ListStringMatcher allowed_client_headers = 2; +} + +// Extra settings on a per virtualhost/route/weighted-cluster level. +message ExtAuthzPerRoute { + oneof override { + option (validate.required) = true; + + // Disable the ext auth filter for this particular vhost or route. + // If disabled is specified in multiple per-filter-configs, the most specific one will be used. + bool disabled = 1 [(validate.rules).bool.const = true]; + + // Check request settings for this route. + CheckSettings check_settings = 2 [(validate.rules).message.required = true]; + } +} + +// Extra settings for the check request. You can use this to provide extra context for the +// external authorization server on specific virtual hosts \ routes. For example, adding a context +// extension on the virtual host level can give the ext-authz server information on what virtual +// host is used without needing to parse the host header. If CheckSettings is specified in multiple +// per-filter-configs, they will be merged in order, and the result will be used. +message CheckSettings { + // Context extensions to set on the CheckRequest's + // :ref:`AttributeContext.context_extensions` + // + // Merge semantics for this field are such that keys from more specific configs override. + // + // .. note:: + // + // These settings are only applied to a filter configured with a + // :ref:`grpc_service`. + map context_extensions = 1; +} diff --git a/api/envoy/config/filter/http/fault/v2/BUILD b/api/envoy/config/filter/http/fault/v2/BUILD index e561e88196..b169a09048 100644 --- a/api/envoy/config/filter/http/fault/v2/BUILD +++ b/api/envoy/config/filter/http/fault/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/route:pkg", + "//envoy/config/filter/fault/v2:pkg", + "//envoy/type", + ], +) + api_proto_library_internal( name = "fault", srcs = ["fault.proto"], diff --git a/api/envoy/config/filter/http/fault/v2/fault.proto b/api/envoy/config/filter/http/fault/v2/fault.proto index 51ee24ac91..8256690837 100644 --- a/api/envoy/config/filter/http/fault/v2/fault.proto +++ b/api/envoy/config/filter/http/fault/v2/fault.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.fault.v2; option java_outer_classname = "FaultProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.fault.v2"; -option go_package = "v2"; import "envoy/api/v2/route/route.proto"; import "envoy/config/filter/fault/v2/fault.proto"; diff --git a/api/envoy/config/filter/http/fault/v3alpha/BUILD b/api/envoy/config/filter/http/fault/v3alpha/BUILD new file mode 100644 index 0000000000..508e2d3c92 --- /dev/null +++ b/api/envoy/config/filter/http/fault/v3alpha/BUILD @@ -0,0 +1,21 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/route:pkg", + "//envoy/config/filter/fault/v3alpha:pkg", + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "fault", + srcs = ["fault.proto"], + deps = [ + "//envoy/api/v3alpha/route", + "//envoy/config/filter/fault/v3alpha:fault", + "//envoy/type:percent", + ], +) diff --git a/api/envoy/config/filter/http/fault/v3alpha/fault.proto b/api/envoy/config/filter/http/fault/v3alpha/fault.proto new file mode 100644 index 0000000000..2189e4a4c1 --- /dev/null +++ b/api/envoy/config/filter/http/fault/v3alpha/fault.proto @@ -0,0 +1,114 @@ +syntax = "proto3"; + +package envoy.config.filter.http.fault.v3alpha; + +option java_outer_classname = "FaultProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.fault.v3alpha"; + +import "envoy/api/v3alpha/route/route.proto"; +import "envoy/config/filter/fault/v3alpha/fault.proto"; +import "envoy/type/percent.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Fault Injection] +// Fault Injection :ref:`configuration overview `. + +message FaultAbort { + reserved 1; + + oneof error_type { + option (validate.required) = true; + + // HTTP status code to use to abort the HTTP request. + uint32 http_status = 2 [(validate.rules).uint32 = {gte: 200, lt: 600}]; + } + + // The percentage of requests/operations/connections that will be aborted with the error code + // provided. + type.FractionalPercent percentage = 3; +} + +message HTTPFault { + // If specified, the filter will inject delays based on the values in the + // object. + filter.fault.v3alpha.FaultDelay delay = 1; + + // If specified, the filter will abort requests based on the values in + // the object. At least *abort* or *delay* must be specified. + FaultAbort abort = 2; + + // Specifies the name of the (destination) upstream cluster that the + // filter should match on. Fault injection will be restricted to requests + // bound to the specific upstream cluster. + string upstream_cluster = 3; + + // Specifies a set of headers that the filter should match on. The fault + // injection filter can be applied selectively to requests that match a set of + // headers specified in the fault filter config. The chances of actual fault + // injection further depend on the value of the :ref:`percentage + // ` field. + // The filter will check the request's headers against all the specified + // headers in the filter config. A match will happen if all the headers in the + // config are present in the request with the same values (or based on + // presence if the *value* field is not in the config). + repeated envoy.api.v3alpha.route.HeaderMatcher headers = 4; + + // Faults are injected for the specified list of downstream hosts. If this + // setting is not set, faults are injected for all downstream nodes. + // Downstream node name is taken from :ref:`the HTTP + // x-envoy-downstream-service-node + // ` header and compared + // against downstream_nodes list. + repeated string downstream_nodes = 5; + + // The maximum number of faults that can be active at a single time via the configured fault + // filter. Note that because this setting can be overridden at the route level, it's possible + // for the number of active faults to be greater than this value (if injected via a different + // route). If not specified, defaults to unlimited. This setting can be overridden via + // `runtime ` and any faults that are not injected + // due to overflow will be indicated via the `faults_overflow + // ` stat. + // + // .. attention:: + // Like other :ref:`circuit breakers ` in Envoy, this is a fuzzy + // limit. It's possible for the number of active faults to rise slightly above the configured + // amount due to the implementation details. + google.protobuf.UInt32Value max_active_faults = 6; + + // The response rate limit to be applied to the response body of the stream. When configured, + // the percentage can be overridden by the :ref:`fault.http.rate_limit.response_percent + // ` runtime key. + // + // .. attention:: + // This is a per-stream limit versus a connection level limit. This means that concurrent streams + // will each get an independent limit. + filter.fault.v3alpha.FaultRateLimit response_rate_limit = 7; + + // The runtime key to override the :ref:`default ` + // runtime. The default is: fault.http.delay.fixed_delay_percent + string delay_percent_runtime = 8; + + // The runtime key to override the :ref:`default ` + // runtime. The default is: fault.http.abort.abort_percent + string abort_percent_runtime = 9; + + // The runtime key to override the :ref:`default ` + // runtime. The default is: fault.http.delay.fixed_duration_ms + string delay_duration_runtime = 10; + + // The runtime key to override the :ref:`default ` + // runtime. The default is: fault.http.abort.http_status + string abort_http_status_runtime = 11; + + // The runtime key to override the :ref:`default ` + // runtime. The default is: fault.http.max_active_faults + string max_active_faults_runtime = 12; + + // The runtime key to override the :ref:`default ` + // runtime. The default is: fault.http.rate_limit.response_percent + string response_rate_limit_percent_runtime = 13; +} diff --git a/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/BUILD b/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/BUILD index 7c1deb713c..a88ba2443c 100644 --- a/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/BUILD +++ b/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library( name = "config", srcs = ["config.proto"], diff --git a/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/config.proto b/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/config.proto index 0c33b6d077..b3b1fde5e1 100644 --- a/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/config.proto +++ b/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v2alpha1/config.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.grpc_http1_reverse_bridge.v2alpha1; option java_outer_classname = "ConfigProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.grpc_http1_reverse_bridge.v2alpha1"; -option go_package = "v2"; import "validate/validate.proto"; diff --git a/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v3alpha/BUILD b/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v3alpha/BUILD new file mode 100644 index 0000000000..a88ba2443c --- /dev/null +++ b/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library( + name = "config", + srcs = ["config.proto"], +) diff --git a/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v3alpha/config.proto b/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v3alpha/config.proto new file mode 100644 index 0000000000..2883701d33 --- /dev/null +++ b/api/envoy/config/filter/http/grpc_http1_reverse_bridge/v3alpha/config.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package envoy.config.filter.http.grpc_http1_reverse_bridge.v3alpha; + +option java_outer_classname = "ConfigProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.grpc_http1_reverse_bridge.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: gRPC HTTP/1.1 Reverse Bridge] +// gRPC HTTP/1.1 Reverse Bridge :ref:`configuration overview +// `. + +// gRPC reverse bridge filter configuration +message FilterConfig { + // The content-type to pass to the upstream when the gRPC bridge filter is applied. + // The filter will also validate that the upstream responds with the same content type. + string content_type = 1 [(validate.rules).string.min_bytes = 1]; + + // If true, Envoy will assume that the upstream doesn't understand gRPC frames and + // strip the gRPC frame from the request, and add it back in to the response. This will + // hide the gRPC semantics from the upstream, allowing it to receive and respond with a + // simple binary encoded protobuf. + bool withhold_grpc_frames = 2; +} diff --git a/api/envoy/config/filter/http/gzip/v2/BUILD b/api/envoy/config/filter/http/gzip/v2/BUILD index e34d73c51c..a3f4b0af2a 100644 --- a/api/envoy/config/filter/http/gzip/v2/BUILD +++ b/api/envoy/config/filter/http/gzip/v2/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "gzip", srcs = ["gzip.proto"], diff --git a/api/envoy/config/filter/http/gzip/v2/gzip.proto b/api/envoy/config/filter/http/gzip/v2/gzip.proto index fb6b8878e6..02041b87fd 100644 --- a/api/envoy/config/filter/http/gzip/v2/gzip.proto +++ b/api/envoy/config/filter/http/gzip/v2/gzip.proto @@ -5,12 +5,10 @@ package envoy.config.filter.http.gzip.v2; option java_outer_classname = "GzipProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.gzip.v2"; -option go_package = "v2"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Gzip] // Gzip :ref:`configuration overview `. diff --git a/api/envoy/config/filter/http/gzip/v3alpha/BUILD b/api/envoy/config/filter/http/gzip/v3alpha/BUILD new file mode 100644 index 0000000000..a3f4b0af2a --- /dev/null +++ b/api/envoy/config/filter/http/gzip/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "gzip", + srcs = ["gzip.proto"], +) diff --git a/api/envoy/config/filter/http/gzip/v3alpha/gzip.proto b/api/envoy/config/filter/http/gzip/v3alpha/gzip.proto new file mode 100644 index 0000000000..26e437d48c --- /dev/null +++ b/api/envoy/config/filter/http/gzip/v3alpha/gzip.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; + +package envoy.config.filter.http.gzip.v3alpha; + +option java_outer_classname = "GzipProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.gzip.v3alpha"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Gzip] +// Gzip :ref:`configuration overview `. + +message Gzip { + // Value from 1 to 9 that controls the amount of internal memory used by zlib. Higher values + // use more memory, but are faster and produce better compression results. The default value is 5. + google.protobuf.UInt32Value memory_level = 1 [(validate.rules).uint32 = {gte: 1, lte: 9}]; + + // Minimum response length, in bytes, which will trigger compression. The default value is 30. + google.protobuf.UInt32Value content_length = 2 [(validate.rules).uint32.gte = 30]; + + message CompressionLevel { + enum Enum { + DEFAULT = 0; + BEST = 1; + SPEED = 2; + } + } + + // A value used for selecting the zlib compression level. This setting will affect speed and + // amount of compression applied to the content. "BEST" provides higher compression at the cost of + // higher latency, "SPEED" provides lower compression with minimum impact on response time. + // "DEFAULT" provides an optimal result between speed and compression. This field will be set to + // "DEFAULT" if not specified. + CompressionLevel.Enum compression_level = 3 [(validate.rules).enum.defined_only = true]; + + enum CompressionStrategy { + DEFAULT = 0; + FILTERED = 1; + HUFFMAN = 2; + RLE = 3; + } + + // A value used for selecting the zlib compression strategy which is directly related to the + // characteristics of the content. Most of the time "DEFAULT" will be the best choice, though + // there are situations which changing this parameter might produce better results. For example, + // run-length encoding (RLE) is typically used when the content is known for having sequences + // which same data occurs many consecutive times. For more information about each strategy, please + // refer to zlib manual. + CompressionStrategy compression_strategy = 4 [(validate.rules).enum.defined_only = true]; + + // Set of strings that allows specifying which mime-types yield compression; e.g., + // application/json, text/html, etc. When this field is not defined, compression will be applied + // to the following mime-types: "application/javascript", "application/json", + // "application/xhtml+xml", "image/svg+xml", "text/css", "text/html", "text/plain", "text/xml". + repeated string content_type = 6 [(validate.rules).repeated = {max_items: 50}]; + + // If true, disables compression when the response contains an etag header. When it is false, the + // filter will preserve weak etags and remove the ones that require strong validation. + bool disable_on_etag_header = 7; + + // If true, removes accept-encoding from the request headers before dispatching it to the upstream + // so that responses do not get compressed before reaching the filter. + bool remove_accept_encoding_header = 8; + + // Value from 9 to 15 that represents the base two logarithmic of the compressor's window size. + // Larger window results in better compression at the expense of memory usage. The default is 12 + // which will produce a 4096 bytes window. For more details about this parameter, please refer to + // zlib manual > deflateInit2. + google.protobuf.UInt32Value window_bits = 9 [(validate.rules).uint32 = {gte: 9, lte: 15}]; +} diff --git a/api/envoy/config/filter/http/header_to_metadata/v2/BUILD b/api/envoy/config/filter/http/header_to_metadata/v2/BUILD index 3f8503acbe..cfd34fcf2b 100644 --- a/api/envoy/config/filter/http/header_to_metadata/v2/BUILD +++ b/api/envoy/config/filter/http/header_to_metadata/v2/BUILD @@ -1,9 +1,10 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "header_to_metadata", srcs = ["header_to_metadata.proto"], - deps = [], ) diff --git a/api/envoy/config/filter/http/header_to_metadata/v2/header_to_metadata.proto b/api/envoy/config/filter/http/header_to_metadata/v2/header_to_metadata.proto index 5e70bbfce4..345c5225ed 100644 --- a/api/envoy/config/filter/http/header_to_metadata/v2/header_to_metadata.proto +++ b/api/envoy/config/filter/http/header_to_metadata/v2/header_to_metadata.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.header_to_metadata.v2; option java_outer_classname = "HeaderToMetadataProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.header_to_metadata.v2"; -option go_package = "v2"; import "validate/validate.proto"; diff --git a/api/envoy/config/filter/http/header_to_metadata/v3alpha/BUILD b/api/envoy/config/filter/http/header_to_metadata/v3alpha/BUILD new file mode 100644 index 0000000000..cfd34fcf2b --- /dev/null +++ b/api/envoy/config/filter/http/header_to_metadata/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "header_to_metadata", + srcs = ["header_to_metadata.proto"], +) diff --git a/api/envoy/config/filter/http/header_to_metadata/v3alpha/header_to_metadata.proto b/api/envoy/config/filter/http/header_to_metadata/v3alpha/header_to_metadata.proto new file mode 100644 index 0000000000..c3811a0057 --- /dev/null +++ b/api/envoy/config/filter/http/header_to_metadata/v3alpha/header_to_metadata.proto @@ -0,0 +1,91 @@ +syntax = "proto3"; + +package envoy.config.filter.http.header_to_metadata.v3alpha; + +option java_outer_classname = "HeaderToMetadataProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.header_to_metadata.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: Header-To-Metadata Filter] +// +// The configuration for transforming headers into metadata. This is useful +// for matching load balancer subsets, logging, etc. +// +// Header to Metadata :ref:`configuration overview `. + +message Config { + enum ValueType { + STRING = 0; + NUMBER = 1; + + // The value is a serialized `protobuf.Value + // `_. + PROTOBUF_VALUE = 2; + } + + // ValueEncode defines the encoding algorithm. + enum ValueEncode { + // The value is not encoded. + NONE = 0; + + // The value is encoded in `Base64 `_. + // Note: this is mostly used for STRING and PROTOBUF_VALUE to escape the + // non-ASCII characters in the header. + BASE64 = 1; + } + + message KeyValuePair { + // The namespace — if this is empty, the filter's namespace will be used. + string metadata_namespace = 1; + + // The key to use within the namespace. + string key = 2 [(validate.rules).string.min_bytes = 1]; + + // The value to pair with the given key. + // + // When used for a `on_header_present` case, if value is non-empty it'll be used + // instead of the header value. If both are empty, no metadata is added. + // + // When used for a `on_header_missing` case, a non-empty value must be provided + // otherwise no metadata is added. + string value = 3; + + // The value's type — defaults to string. + ValueType type = 4; + + // How is the value encoded, default is NONE (not encoded). + // The value will be decoded accordingly before storing to metadata. + ValueEncode encode = 5; + } + + // A Rule defines what metadata to apply when a header is present or missing. + message Rule { + // The header that triggers this rule — required. + string header = 1 [(validate.rules).string.min_bytes = 1]; + + // If the header is present, apply this metadata KeyValuePair. + // + // If the value in the KeyValuePair is non-empty, it'll be used instead + // of the header value. + KeyValuePair on_header_present = 2; + + // If the header is not present, apply this metadata KeyValuePair. + // + // The value in the KeyValuePair must be set, since it'll be used in lieu + // of the missing header value. + KeyValuePair on_header_missing = 3; + + // Whether or not to remove the header after a rule is applied. + // + // This prevents headers from leaking. + bool remove = 4; + } + + // The list of rules to apply to requests. + repeated Rule request_rules = 1; + + // The list of rules to apply to responses. + repeated Rule response_rules = 2; +} diff --git a/api/envoy/config/filter/http/health_check/v2/BUILD b/api/envoy/config/filter/http/health_check/v2/BUILD index 9dc0af2df1..8a995f1694 100644 --- a/api/envoy/config/filter/http/health_check/v2/BUILD +++ b/api/envoy/config/filter/http/health_check/v2/BUILD @@ -1,21 +1,19 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 -api_proto_library_internal( - name = "health_check", - srcs = ["health_check.proto"], +api_proto_package( deps = [ - "//envoy/api/v2/route", - "//envoy/type:percent", + "//envoy/api/v2/route:pkg", + "//envoy/type", ], ) -api_go_proto_library( +api_proto_library_internal( name = "health_check", - proto = ":health_check", + srcs = ["health_check.proto"], deps = [ - "//envoy/api/v2/route:route_go_proto", - "//envoy/type:percent_go_proto", + "//envoy/api/v2/route", + "//envoy/type:percent", ], ) diff --git a/api/envoy/config/filter/http/health_check/v2/health_check.proto b/api/envoy/config/filter/http/health_check/v2/health_check.proto index bc8433732d..9cd572b437 100644 --- a/api/envoy/config/filter/http/health_check/v2/health_check.proto +++ b/api/envoy/config/filter/http/health_check/v2/health_check.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.health_check.v2; option java_outer_classname = "HealthCheckProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.health_check.v2"; -option go_package = "v2"; import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; @@ -14,9 +13,6 @@ import "envoy/api/v2/route/route.proto"; import "envoy/type/percent.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.stable_marshaler_all) = true; // [#protodoc-title: Health check] // Health check :ref:`configuration overview `. @@ -30,7 +26,7 @@ message HealthCheck { // If operating in pass through mode, the amount of time in milliseconds // that the filter should cache the upstream response. - google.protobuf.Duration cache_time = 3 [(gogoproto.stdduration) = true]; + google.protobuf.Duration cache_time = 3; // If operating in non-pass-through mode, specifies a set of upstream cluster // names and the minimum percentage of servers in each of those clusters that diff --git a/api/envoy/config/filter/http/health_check/v3alpha/BUILD b/api/envoy/config/filter/http/health_check/v3alpha/BUILD new file mode 100644 index 0000000000..b583685750 --- /dev/null +++ b/api/envoy/config/filter/http/health_check/v3alpha/BUILD @@ -0,0 +1,19 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/route:pkg", + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "health_check", + srcs = ["health_check.proto"], + deps = [ + "//envoy/api/v3alpha/route", + "//envoy/type:percent", + ], +) diff --git a/api/envoy/config/filter/http/health_check/v3alpha/health_check.proto b/api/envoy/config/filter/http/health_check/v3alpha/health_check.proto new file mode 100644 index 0000000000..c5e91e703d --- /dev/null +++ b/api/envoy/config/filter/http/health_check/v3alpha/health_check.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package envoy.config.filter.http.health_check.v3alpha; + +option java_outer_classname = "HealthCheckProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.health_check.v3alpha"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "envoy/api/v3alpha/route/route.proto"; +import "envoy/type/percent.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Health check] +// Health check :ref:`configuration overview `. + +message HealthCheck { + // Specifies whether the filter operates in pass through mode or not. + google.protobuf.BoolValue pass_through_mode = 1 [(validate.rules).message.required = true]; + + reserved 2; + reserved "endpoint"; + + // If operating in pass through mode, the amount of time in milliseconds + // that the filter should cache the upstream response. + google.protobuf.Duration cache_time = 3; + + // If operating in non-pass-through mode, specifies a set of upstream cluster + // names and the minimum percentage of servers in each of those clusters that + // must be healthy or degraded in order for the filter to return a 200. + map cluster_min_healthy_percentages = 4; + + // Specifies a set of health check request headers to match on. The health check filter will + // check a request’s headers against all the specified headers. To specify the health check + // endpoint, set the ``:path`` header to match on. + repeated envoy.api.v3alpha.route.HeaderMatcher headers = 5; +} diff --git a/api/envoy/config/filter/http/ip_tagging/v2/BUILD b/api/envoy/config/filter/http/ip_tagging/v2/BUILD index 4c7001972e..b318ae58f3 100644 --- a/api/envoy/config/filter/http/ip_tagging/v2/BUILD +++ b/api/envoy/config/filter/http/ip_tagging/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library_internal( name = "ip_tagging", srcs = ["ip_tagging.proto"], diff --git a/api/envoy/config/filter/http/ip_tagging/v2/ip_tagging.proto b/api/envoy/config/filter/http/ip_tagging/v2/ip_tagging.proto index 4f5da60150..92ec469c62 100644 --- a/api/envoy/config/filter/http/ip_tagging/v2/ip_tagging.proto +++ b/api/envoy/config/filter/http/ip_tagging/v2/ip_tagging.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.ip_tagging.v2; option java_outer_classname = "IpTaggingProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.ip_tagging.v2"; -option go_package = "v2"; import "envoy/api/v2/core/address.proto"; diff --git a/api/envoy/config/filter/http/ip_tagging/v3alpha/BUILD b/api/envoy/config/filter/http/ip_tagging/v3alpha/BUILD new file mode 100644 index 0000000000..a05f0fd96b --- /dev/null +++ b/api/envoy/config/filter/http/ip_tagging/v3alpha/BUILD @@ -0,0 +1,13 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library_internal( + name = "ip_tagging", + srcs = ["ip_tagging.proto"], + deps = ["//envoy/api/v3alpha/core:address"], +) diff --git a/api/envoy/config/filter/http/ip_tagging/v3alpha/ip_tagging.proto b/api/envoy/config/filter/http/ip_tagging/v3alpha/ip_tagging.proto new file mode 100644 index 0000000000..de7871d9e7 --- /dev/null +++ b/api/envoy/config/filter/http/ip_tagging/v3alpha/ip_tagging.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +package envoy.config.filter.http.ip_tagging.v3alpha; + +option java_outer_classname = "IpTaggingProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.ip_tagging.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: IP tagging] +// IP tagging :ref:`configuration overview `. + +message IPTagging { + + // The type of requests the filter should apply to. The supported types + // are internal, external or both. The + // :ref:`x-forwarded-for` header is + // used to determine if a request is internal and will result in + // :ref:`x-envoy-internal` + // being set. The filter defaults to both, and it will apply to all request types. + enum RequestType { + // Both external and internal requests will be tagged. This is the default value. + BOTH = 0; + + // Only internal requests will be tagged. + INTERNAL = 1; + + // Only external requests will be tagged. + EXTERNAL = 2; + } + + // The type of request the filter should apply to. + RequestType request_type = 1 [(validate.rules).enum.defined_only = true]; + + // Supplies the IP tag name and the IP address subnets. + message IPTag { + // Specifies the IP tag name to apply. + string ip_tag_name = 1; + + // A list of IP address subnets that will be tagged with + // ip_tag_name. Both IPv4 and IPv6 are supported. + repeated envoy.api.v3alpha.core.CidrRange ip_list = 2; + } + + // [#comment:TODO(ccaraman): Extend functionality to load IP tags from file system. + // Tracked by issue https://github.com/envoyproxy/envoy/issues/2695] + // The set of IP tags for the filter. + repeated IPTag ip_tags = 4 [(validate.rules).repeated .min_items = 1]; +} diff --git a/api/envoy/config/filter/http/jwt_authn/v2alpha/BUILD b/api/envoy/config/filter/http/jwt_authn/v2alpha/BUILD index e48aa58267..80b4345f61 100644 --- a/api/envoy/config/filter/http/jwt_authn/v2alpha/BUILD +++ b/api/envoy/config/filter/http/jwt_authn/v2alpha/BUILD @@ -1,6 +1,13 @@ licenses(["notice"]) # Apache 2 -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/api/v2/route:pkg", + ], +) api_proto_library_internal( name = "jwt_authn", @@ -11,13 +18,3 @@ api_proto_library_internal( "//envoy/api/v2/route", ], ) - -api_go_proto_library( - name = "jwt_authn", - proto = ":jwt_authn", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:http_uri_go_proto", - "//envoy/api/v2/route:route_go_proto", - ], -) diff --git a/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto b/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto index 2f8a0ec29c..c07b780b96 100644 --- a/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto +++ b/api/envoy/config/filter/http/jwt_authn/v2alpha/config.proto @@ -13,9 +13,6 @@ import "envoy/api/v2/route/route.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/empty.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.stable_marshaler_all) = true; // [#protodoc-title: JWT Authentication] // JWT Authentication :ref:`configuration overview `. diff --git a/api/envoy/config/filter/http/jwt_authn/v3alpha/BUILD b/api/envoy/config/filter/http/jwt_authn/v3alpha/BUILD new file mode 100644 index 0000000000..ea5d0d17b1 --- /dev/null +++ b/api/envoy/config/filter/http/jwt_authn/v3alpha/BUILD @@ -0,0 +1,20 @@ +licenses(["notice"]) # Apache 2 + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/route:pkg", + ], +) + +api_proto_library_internal( + name = "jwt_authn", + srcs = ["config.proto"], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:http_uri", + "//envoy/api/v3alpha/route", + ], +) diff --git a/api/envoy/config/filter/http/jwt_authn/v3alpha/README.md b/api/envoy/config/filter/http/jwt_authn/v3alpha/README.md new file mode 100644 index 0000000000..c390a4d5ce --- /dev/null +++ b/api/envoy/config/filter/http/jwt_authn/v3alpha/README.md @@ -0,0 +1,66 @@ +# JWT Authentication HTTP filter config + +## Overview + +1. The proto file in this folder defines an HTTP filter config for "jwt_authn" filter. + +2. This filter will verify the JWT in the HTTP request as: + - The signature should be valid + - JWT should not be expired + - Issuer and audiences are valid and specified in the filter config. + +3. [JWK](https://tools.ietf.org/html/rfc7517#appendix-A) is needed to verify JWT signature. It can be fetched from a remote server or read from a local file. If the JWKS is fetched remotely, it will be cached by the filter. + +3. If a JWT is valid, the user is authenticated and the request will be forwarded to the backend server. If a JWT is not valid, the request will be rejected with an error message. + +## The locations to extract JWT + +JWT will be extracted from the HTTP headers or query parameters. The default location is the HTTP header: +``` +Authorization: Bearer +``` +The next default location is in the query parameter as: +``` +?access_token= +``` + +If a custom location is desired, `from_headers` or `from_params` can be used to specify custom locations to extract JWT. + +## HTTP header to pass successfully verified JWT + +If a JWT is valid, its payload will be passed to the backend in a new HTTP header specified in `forward_payload_header` field. Its value is base64 encoded JWT payload in JSON. + + +## Further header options + +In addition to the `name` field, which specifies the HTTP header name, +the `from_headers` section can specify an optional `value_prefix` value, as in: + +```yaml + from_headers: + - name: bespoke + value_prefix: jwt_value +``` + +The above will cause the jwt_authn filter to look for the JWT in the `bespoke` header, following the tag `jwt_value`. + +Any non-JWT characters (i.e., anything _other than_ alphanumerics, `_`, `-`, and `.`) will be skipped, +and all following, contiguous, JWT-legal chars will be taken as the JWT. + +This means all of the following will return a JWT of `eyJFbnZveSI6ICJyb2NrcyJ9.e30.c2lnbmVk`: + +```text +bespoke: jwt_value=eyJFbnZveSI6ICJyb2NrcyJ9.e30.c2lnbmVk + +bespoke: {"jwt_value": "eyJFbnZveSI6ICJyb2NrcyJ9.e30.c2lnbmVk"} + +bespoke: beta:true,jwt_value:"eyJFbnZveSI6ICJyb2NrcyJ9.e30.c2lnbmVk",trace=1234 +``` + +The header `name` may be `Authorization`. + +The `value_prefix` must match exactly, i.e., case-sensitively. +If the `value_prefix` is not found, the header is skipped: not considered as a source for a JWT token. + +If there are no JWT-legal characters after the `value_prefix`, the entire string after it +is taken to be the JWT token. This is unlikely to succeed; the error will reported by the JWT parser. \ No newline at end of file diff --git a/api/envoy/config/filter/http/jwt_authn/v3alpha/config.proto b/api/envoy/config/filter/http/jwt_authn/v3alpha/config.proto new file mode 100644 index 0000000000..bc4785e64e --- /dev/null +++ b/api/envoy/config/filter/http/jwt_authn/v3alpha/config.proto @@ -0,0 +1,464 @@ + +syntax = "proto3"; + +package envoy.config.filter.http.jwt_authn.v3alpha; + +option java_outer_classname = "ConfigProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.jwt_authn.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/core/http_uri.proto"; +import "envoy/api/v3alpha/route/route.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; +import "validate/validate.proto"; + +// [#protodoc-title: JWT Authentication] +// JWT Authentication :ref:`configuration overview `. + +// Please see following for JWT authentication flow: +// +// * `JSON Web Token (JWT) `_ +// * `The OAuth 2.0 Authorization Framework `_ +// * `OpenID Connect `_ +// +// A JwtProvider message specifies how a JSON Web Token (JWT) can be verified. It specifies: +// +// * issuer: the principal that issues the JWT. It has to match the one from the token. +// * allowed audiences: the ones in the token have to be listed here. +// * how to fetch public key JWKS to verify the token signature. +// * how to extract JWT token in the request. +// * how to pass successfully verified token payload. +// +// Example: +// +// .. code-block:: yaml +// +// issuer: https://example.com +// audiences: +// - bookstore_android.apps.googleusercontent.com +// - bookstore_web.apps.googleusercontent.com +// remote_jwks: +// http_uri: +// uri: https://example.com/.well-known/jwks.json +// cluster: example_jwks_cluster +// cache_duration: +// seconds: 300 +// +message JwtProvider { + // Specify the `principal `_ that issued + // the JWT, usually a URL or an email address. + // + // Example: https://securetoken.google.com + // Example: 1234567-compute@developer.gserviceaccount.com + // + string issuer = 1 [(validate.rules).string.min_bytes = 1]; + + // The list of JWT `audiences `_ are + // allowed to access. A JWT containing any of these audiences will be accepted. If not specified, + // will not check audiences in the token. + // + // Example: + // + // .. code-block:: yaml + // + // audiences: + // - bookstore_android.apps.googleusercontent.com + // - bookstore_web.apps.googleusercontent.com + // + repeated string audiences = 2; + + // `JSON Web Key Set (JWKS) `_ is needed to + // validate signature of a JWT. This field specifies where to fetch JWKS. + oneof jwks_source_specifier { + option (validate.required) = true; + + // JWKS can be fetched from remote server via HTTP/HTTPS. This field specifies the remote HTTP + // URI and how the fetched JWKS should be cached. + // + // Example: + // + // .. code-block:: yaml + // + // remote_jwks: + // http_uri: + // uri: https://www.googleapis.com/oauth2/v1/certs + // cluster: jwt.www.googleapis.com|443 + // cache_duration: + // seconds: 300 + // + RemoteJwks remote_jwks = 3; + + // JWKS is in local data source. It could be either in a local file or embedded in the + // inline_string. + // + // Example: local file + // + // .. code-block:: yaml + // + // local_jwks: + // filename: /etc/envoy/jwks/jwks1.txt + // + // Example: inline_string + // + // .. code-block:: yaml + // + // local_jwks: + // inline_string: ACADADADADA + // + envoy.api.v3alpha.core.DataSource local_jwks = 4; + } + + // If false, the JWT is removed in the request after a success verification. If true, the JWT is + // not removed in the request. Default value is false. + bool forward = 5; + + // Two fields below define where to extract the JWT from an HTTP request. + // + // If no explicit location is specified, the following default locations are tried in order: + // + // 1. The Authorization header using the `Bearer schema + // `_. Example:: + // + // Authorization: Bearer . + // + // 2. `access_token `_ query parameter. + // + // Multiple JWTs can be verified for a request. Each JWT has to be extracted from the locations + // its provider specified or from the default locations. + // + // Specify the HTTP headers to extract JWT token. For examples, following config: + // + // .. code-block:: yaml + // + // from_headers: + // - name: x-goog-iap-jwt-assertion + // + // can be used to extract token from header:: + // + // x-goog-iap-jwt-assertion: . + // + repeated JwtHeader from_headers = 6; + + // JWT is sent in a query parameter. `jwt_params` represents the query parameter names. + // + // For example, if config is: + // + // .. code-block:: yaml + // + // from_params: + // - jwt_token + // + // The JWT format in query parameter is:: + // + // /path?jwt_token= + // + repeated string from_params = 7; + + // This field specifies the header name to forward a successfully verified JWT payload to the + // backend. The forwarded data is:: + // + // base64_encoded(jwt_payload_in_JSON) + // + // If it is not specified, the payload will not be forwarded. + string forward_payload_header = 8; + + // If non empty, successfully verified JWT payloads will be written to StreamInfo DynamicMetadata + // in the format as: *namespace* is the jwt_authn filter name as **envoy.filters.http.jwt_authn** + // The value is the *protobuf::Struct*. The value of this field will be the key for its *fields* + // and the value is the *protobuf::Struct* converted from JWT JSON payload. + // + // For example, if payload_in_metadata is *my_payload*: + // + // .. code-block:: yaml + // + // envoy.filters.http.jwt_authn: + // my_payload: + // iss: https://example.com + // sub: test@example.com + // aud: https://example.com + // exp: 1501281058 + // + string payload_in_metadata = 9; +} + +// This message specifies how to fetch JWKS from remote and how to cache it. +message RemoteJwks { + // The HTTP URI to fetch the JWKS. For example: + // + // .. code-block:: yaml + // + // http_uri: + // uri: https://www.googleapis.com/oauth2/v1/certs + // cluster: jwt.www.googleapis.com|443 + // + envoy.api.v3alpha.core.HttpUri http_uri = 1; + + // Duration after which the cached JWKS should be expired. If not specified, default cache + // duration is 5 minutes. + google.protobuf.Duration cache_duration = 2; +} + +// This message specifies a header location to extract JWT token. +message JwtHeader { + // The HTTP header name. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // The value prefix. The value format is "value_prefix" + // For example, for "Authorization: Bearer ", value_prefix="Bearer " with a space at the + // end. + string value_prefix = 2; +} + +// Specify a required provider with audiences. +message ProviderWithAudiences { + // Specify a required provider name. + string provider_name = 1; + + // This field overrides the one specified in the JwtProvider. + repeated string audiences = 2; +} + +// This message specifies a Jwt requirement. An empty message means JWT verification is not +// required. Here are some config examples: +// +// .. code-block:: yaml +// +// # Example 1: not required with an empty message +// +// # Example 2: require A +// provider_name: provider-A +// +// # Example 3: require A or B +// requires_any: +// requirements: +// - provider_name: provider-A +// - provider_name: provider-B +// +// # Example 4: require A and B +// requires_all: +// requirements: +// - provider_name: provider-A +// - provider_name: provider-B +// +// # Example 5: require A and (B or C) +// requires_all: +// requirements: +// - provider_name: provider-A +// - requires_any: +// requirements: +// - provider_name: provider-B +// - provider_name: provider-C +// +// # Example 6: require A or (B and C) +// requires_any: +// requirements: +// - provider_name: provider-A +// - requires_all: +// requirements: +// - provider_name: provider-B +// - provider_name: provider-C +// +message JwtRequirement { + oneof requires_type { + // Specify a required provider name. + string provider_name = 1; + + // Specify a required provider with audiences. + ProviderWithAudiences provider_and_audiences = 2; + + // Specify list of JwtRequirement. Their results are OR-ed. + // If any one of them passes, the result is passed. + JwtRequirementOrList requires_any = 3; + + // Specify list of JwtRequirement. Their results are AND-ed. + // All of them must pass, if one of them fails or missing, it fails. + JwtRequirementAndList requires_all = 4; + + // The requirement is always satisfied even if JWT is missing or the JWT + // verification fails. A typical usage is: this filter is used to only verify + // JWTs and pass the verified JWT payloads to another filter, the other filter + // will make decision. In this mode, all JWT tokens will be verified. + google.protobuf.Empty allow_missing_or_failed = 5; + } +} + +// This message specifies a list of RequiredProvider. +// Their results are OR-ed; if any one of them passes, the result is passed +message JwtRequirementOrList { + // Specify a list of JwtRequirement. + repeated JwtRequirement requirements = 1 [(validate.rules).repeated .min_items = 2]; +} + +// This message specifies a list of RequiredProvider. +// Their results are AND-ed; all of them must pass, if one of them fails or missing, it fails. +message JwtRequirementAndList { + // Specify a list of JwtRequirement. + repeated JwtRequirement requirements = 1 [(validate.rules).repeated .min_items = 2]; +} + +// This message specifies a Jwt requirement for a specific Route condition. +// Example 1: +// +// .. code-block:: yaml +// +// - match: +// prefix: /healthz +// +// In above example, "requires" field is empty for /healthz prefix match, +// it means that requests matching the path prefix don't require JWT authentication. +// +// Example 2: +// +// .. code-block:: yaml +// +// - match: +// prefix: / +// requires: { provider_name: provider-A } +// +// In above example, all requests matched the path prefix require jwt authentication +// from "provider-A". +message RequirementRule { + // The route matching parameter. Only when the match is satisfied, the "requires" field will + // apply. + // + // For example: following match will match all requests. + // + // .. code-block:: yaml + // + // match: + // prefix: / + // + envoy.api.v3alpha.route.RouteMatch match = 1 [(validate.rules).message.required = true]; + + // Specify a Jwt Requirement. Please detail comment in message JwtRequirement. + JwtRequirement requires = 2; +} + +// This message specifies Jwt requirements based on stream_info.filterState. +// This FilterState should use `Router::StringAccessor` object to set a string value. +// Other HTTP filters can use it to specify Jwt requirements dynamically. +// +// Example: +// +// .. code-block:: yaml +// +// name: jwt_selector +// requires: +// issuer_1: +// provider_name: issuer1 +// issuer_2: +// provider_name: issuer2 +// +// If a filter set "jwt_selector" with "issuer_1" to FilterState for a request, +// jwt_authn filter will use JwtRequirement{"provider_name": "issuer1"} to verify. +message FilterStateRule { + // The filter state name to retrieve the `Router::StringAccessor` object. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // A map of string keys to requirements. The string key is the string value + // in the FilterState with the name specified in the *name* field above. + map requires = 3; +} + +// This is the Envoy HTTP filter config for JWT authentication. +// +// For example: +// +// .. code-block:: yaml +// +// providers: +// provider1: +// issuer: issuer1 +// audiences: +// - audience1 +// - audience2 +// remote_jwks: +// http_uri: +// uri: https://example.com/.well-known/jwks.json +// cluster: example_jwks_cluster +// provider2: +// issuer: issuer2 +// local_jwks: +// inline_string: jwks_string +// +// rules: +// # Not jwt verification is required for /health path +// - match: +// prefix: /health +// +// # Jwt verification for provider1 is required for path prefixed with "prefix" +// - match: +// prefix: /prefix +// requires: +// provider_name: provider1 +// +// # Jwt verification for either provider1 or provider2 is required for all other requests. +// - match: +// prefix: / +// requires: +// requires_any: +// requirements: +// - provider_name: provider1 +// - provider_name: provider2 +// +message JwtAuthentication { + // Map of provider names to JwtProviders. + // + // .. code-block:: yaml + // + // providers: + // provider1: + // issuer: issuer1 + // audiences: + // - audience1 + // - audience2 + // remote_jwks: + // http_uri: + // uri: https://example.com/.well-known/jwks.json + // cluster: example_jwks_cluster + // provider2: + // issuer: provider2 + // local_jwks: + // inline_string: jwks_string + // + map providers = 1; + + // Specifies requirements based on the route matches. The first matched requirement will be + // applied. If there are overlapped match conditions, please put the most specific match first. + // + // Examples + // + // .. code-block:: yaml + // + // rules: + // - match: + // prefix: /healthz + // - match: + // prefix: /baz + // requires: + // provider_name: provider1 + // - match: + // prefix: /foo + // requires: + // requires_any: + // requirements: + // - provider_name: provider1 + // - provider_name: provider2 + // - match: + // prefix: /bar + // requires: + // requires_all: + // requirements: + // - provider_name: provider1 + // - provider_name: provider2 + // + repeated RequirementRule rules = 2; + + // This message specifies Jwt requirements based on stream_info.filterState. + // Other HTTP filters can use it to specify Jwt requirements dynamically. + // The *rules* field above is checked first, if it could not find any matches, + // check this one. + FilterStateRule filter_state_rules = 3; +} diff --git a/api/envoy/config/filter/http/lua/v2/BUILD b/api/envoy/config/filter/http/lua/v2/BUILD index 6daf0c82f1..7aaf74617c 100644 --- a/api/envoy/config/filter/http/lua/v2/BUILD +++ b/api/envoy/config/filter/http/lua/v2/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "lua", srcs = ["lua.proto"], diff --git a/api/envoy/config/filter/http/lua/v2/lua.proto b/api/envoy/config/filter/http/lua/v2/lua.proto index f29bcdbe89..6fc7fabc6b 100644 --- a/api/envoy/config/filter/http/lua/v2/lua.proto +++ b/api/envoy/config/filter/http/lua/v2/lua.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.lua.v2; option java_outer_classname = "LuaProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.lua.v2"; -option go_package = "v2"; import "validate/validate.proto"; diff --git a/api/envoy/config/filter/http/lua/v3alpha/BUILD b/api/envoy/config/filter/http/lua/v3alpha/BUILD new file mode 100644 index 0000000000..7aaf74617c --- /dev/null +++ b/api/envoy/config/filter/http/lua/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "lua", + srcs = ["lua.proto"], +) diff --git a/api/envoy/config/filter/http/lua/v3alpha/lua.proto b/api/envoy/config/filter/http/lua/v3alpha/lua.proto new file mode 100644 index 0000000000..934a592678 --- /dev/null +++ b/api/envoy/config/filter/http/lua/v3alpha/lua.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package envoy.config.filter.http.lua.v3alpha; + +option java_outer_classname = "LuaProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.lua.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: Lua] +// Lua :ref:`configuration overview `. + +message Lua { + // The Lua code that Envoy will execute. This can be a very small script that + // further loads code from disk if desired. Note that if JSON configuration is used, the code must + // be properly escaped. YAML configuration may be easier to read since YAML supports multi-line + // strings so complex scripts can be easily expressed inline in the configuration. + string inline_code = 1 [(validate.rules).string.min_bytes = 1]; +} diff --git a/api/envoy/config/filter/http/original_src/v2alpha1/BUILD b/api/envoy/config/filter/http/original_src/v2alpha1/BUILD index e064545b21..a7435bb55c 100644 --- a/api/envoy/config/filter/http/original_src/v2alpha1/BUILD +++ b/api/envoy/config/filter/http/original_src/v2alpha1/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "original_src", srcs = ["original_src.proto"], diff --git a/api/envoy/config/filter/http/original_src/v2alpha1/original_src.proto b/api/envoy/config/filter/http/original_src/v2alpha1/original_src.proto index 32f37a8c48..5c09b860fc 100644 --- a/api/envoy/config/filter/http/original_src/v2alpha1/original_src.proto +++ b/api/envoy/config/filter/http/original_src/v2alpha1/original_src.proto @@ -6,8 +6,6 @@ option java_outer_classname = "OriginalSrcProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.original_src.v2alpha1"; -option go_package = "v2alpha1"; - import "validate/validate.proto"; // [#protodoc-title: Original Src Filter] diff --git a/api/envoy/config/filter/http/original_src/v3alpha/BUILD b/api/envoy/config/filter/http/original_src/v3alpha/BUILD new file mode 100644 index 0000000000..a7435bb55c --- /dev/null +++ b/api/envoy/config/filter/http/original_src/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "original_src", + srcs = ["original_src.proto"], +) diff --git a/api/envoy/config/filter/http/original_src/v3alpha/original_src.proto b/api/envoy/config/filter/http/original_src/v3alpha/original_src.proto new file mode 100644 index 0000000000..20bd0e920e --- /dev/null +++ b/api/envoy/config/filter/http/original_src/v3alpha/original_src.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package envoy.config.filter.http.original_src.v3alpha; + +option java_outer_classname = "OriginalSrcProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.original_src.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: Original Src Filter] +// Use the Original source address on upstream connections. + +// The Original Src filter binds upstream connections to the original source address determined +// for the request. This address could come from something like the Proxy Protocol filter, or it +// could come from trusted http headers. +message OriginalSrc { + + // Sets the SO_MARK option on the upstream connection's socket to the provided value. Used to + // ensure that non-local addresses may be routed back through envoy when binding to the original + // source address. The option will not be applied if the mark is 0. + // [#proto-status: experimental] + uint32 mark = 1; +} diff --git a/api/envoy/config/filter/http/rate_limit/v2/BUILD b/api/envoy/config/filter/http/rate_limit/v2/BUILD index d8fb8e72ff..4a6d451da9 100644 --- a/api/envoy/config/filter/http/rate_limit/v2/BUILD +++ b/api/envoy/config/filter/http/rate_limit/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/config/ratelimit/v2:pkg"], +) + api_proto_library_internal( name = "rate_limit", srcs = ["rate_limit.proto"], 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 9d93e4a255..08189be1df 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 @@ -5,14 +5,12 @@ package envoy.config.filter.http.rate_limit.v2; option java_outer_classname = "RateLimitProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.rate_limit.v2"; -option go_package = "v2"; import "envoy/config/ratelimit/v2/rls.proto"; import "google/protobuf/duration.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Rate limit] // Rate limit :ref:`configuration overview `. @@ -39,7 +37,7 @@ 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]; + google.protobuf.Duration timeout = 4; // 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 diff --git a/api/envoy/config/filter/http/rate_limit/v3alpha/BUILD b/api/envoy/config/filter/http/rate_limit/v3alpha/BUILD new file mode 100644 index 0000000000..7060f7e9ce --- /dev/null +++ b/api/envoy/config/filter/http/rate_limit/v3alpha/BUILD @@ -0,0 +1,15 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/config/ratelimit/v3alpha:pkg"], +) + +api_proto_library_internal( + name = "rate_limit", + srcs = ["rate_limit.proto"], + deps = [ + "//envoy/config/ratelimit/v3alpha:rls", + ], +) diff --git a/api/envoy/config/filter/http/rate_limit/v3alpha/rate_limit.proto b/api/envoy/config/filter/http/rate_limit/v3alpha/rate_limit.proto new file mode 100644 index 0000000000..091c5d3d33 --- /dev/null +++ b/api/envoy/config/filter/http/rate_limit/v3alpha/rate_limit.proto @@ -0,0 +1,58 @@ +syntax = "proto3"; + +package envoy.config.filter.http.rate_limit.v3alpha; + +option java_outer_classname = "RateLimitProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.rate_limit.v3alpha"; + +import "envoy/config/ratelimit/v3alpha/rls.proto"; + +import "google/protobuf/duration.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Rate limit] +// Rate limit :ref:`configuration overview `. + +message RateLimit { + // The rate limit domain to use when calling the rate limit service. + string domain = 1 [(validate.rules).string.min_bytes = 1]; + + // Specifies the rate limit configurations to be applied with the same + // stage number. If not set, the default stage number is 0. + // + // .. note:: + // + // The filter supports a range of 0 - 10 inclusively for stage numbers. + uint32 stage = 2 [(validate.rules).uint32.lte = 10]; + + // The type of requests the filter should apply to. The supported + // types are *internal*, *external* or *both*. A request is considered internal if + // :ref:`x-envoy-internal` is set to true. If + // :ref:`x-envoy-internal` is not set or false, a + // request is considered external. The filter defaults to *both*, and it will apply to all request + // types. + string request_type = 3; + + // The timeout in milliseconds for the rate limit service RPC. If not + // set, this defaults to 20ms. + google.protobuf.Duration timeout = 4; + + // 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; + + // Specifies whether a `RESOURCE_EXHAUSTED` gRPC code must be returned instead + // of the default `UNAVAILABLE` gRPC code for a rate limited gRPC call. The + // HTTP code will be 200 for a gRPC response. + bool rate_limited_as_resource_exhausted = 6; + + // Configuration for an external rate limit service provider. If not + // specified, any calls to the rate limit service will immediately return + // success. + envoy.config.ratelimit.v3alpha.RateLimitServiceConfig rate_limit_service = 7 + [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/filter/http/rbac/v2/BUILD b/api/envoy/config/filter/http/rbac/v2/BUILD index 6182fe2674..ca9aa2ca41 100644 --- a/api/envoy/config/filter/http/rbac/v2/BUILD +++ b/api/envoy/config/filter/http/rbac/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/config/rbac/v2:pkg"], +) + api_proto_library_internal( name = "rbac", srcs = ["rbac.proto"], diff --git a/api/envoy/config/filter/http/rbac/v2/rbac.proto b/api/envoy/config/filter/http/rbac/v2/rbac.proto index 0a75d9590f..7c9a3c24d0 100644 --- a/api/envoy/config/filter/http/rbac/v2/rbac.proto +++ b/api/envoy/config/filter/http/rbac/v2/rbac.proto @@ -5,12 +5,10 @@ package envoy.config.filter.http.rbac.v2; option java_outer_classname = "RbacProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.rbac.v2"; -option go_package = "v2"; import "envoy/config/rbac/v2/rbac.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: RBAC] // Role-Based Access Control :ref:`configuration overview `. diff --git a/api/envoy/config/filter/http/rbac/v3alpha/BUILD b/api/envoy/config/filter/http/rbac/v3alpha/BUILD new file mode 100644 index 0000000000..1e4d51b504 --- /dev/null +++ b/api/envoy/config/filter/http/rbac/v3alpha/BUILD @@ -0,0 +1,13 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/config/rbac/v3alpha:pkg"], +) + +api_proto_library_internal( + name = "rbac", + srcs = ["rbac.proto"], + deps = ["//envoy/config/rbac/v3alpha:rbac"], +) diff --git a/api/envoy/config/filter/http/rbac/v3alpha/rbac.proto b/api/envoy/config/filter/http/rbac/v3alpha/rbac.proto new file mode 100644 index 0000000000..3ffe04ec3a --- /dev/null +++ b/api/envoy/config/filter/http/rbac/v3alpha/rbac.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package envoy.config.filter.http.rbac.v3alpha; + +option java_outer_classname = "RbacProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.rbac.v3alpha"; + +import "envoy/config/rbac/v3alpha/rbac.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: RBAC] +// Role-Based Access Control :ref:`configuration overview `. + +// RBAC filter config. +message RBAC { + // Specify the RBAC rules to be applied globally. + // If absent, no enforcing RBAC policy will be applied. + config.rbac.v3alpha.RBAC rules = 1; + + // Shadow rules are not enforced by the filter (i.e., returning a 403) + // but will emit stats and logs and can be used for rule testing. + // If absent, no shadow RBAC policy will be applied. + config.rbac.v3alpha.RBAC shadow_rules = 2; +} + +message RBACPerRoute { + reserved 1; + + reserved "disabled"; + + // Override the global configuration of the filter with this new config. + // If absent, the global RBAC policy will be disabled for this route. + RBAC rbac = 2; +} diff --git a/api/envoy/config/filter/http/router/v2/BUILD b/api/envoy/config/filter/http/router/v2/BUILD index 7a80299a2c..9ddaf54b28 100644 --- a/api/envoy/config/filter/http/router/v2/BUILD +++ b/api/envoy/config/filter/http/router/v2/BUILD @@ -1,15 +1,13 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/config/filter/accesslog/v2:pkg"], +) + api_proto_library_internal( name = "router", srcs = ["router.proto"], deps = ["//envoy/config/filter/accesslog/v2:accesslog"], ) - -api_go_proto_library( - name = "router", - proto = ":router", - deps = ["//envoy/config/filter/accesslog/v2:accesslog_go_proto"], -) diff --git a/api/envoy/config/filter/http/router/v2/router.proto b/api/envoy/config/filter/http/router/v2/router.proto index e776756733..fd0cadec96 100644 --- a/api/envoy/config/filter/http/router/v2/router.proto +++ b/api/envoy/config/filter/http/router/v2/router.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.router.v2; option java_outer_classname = "RouterProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.router.v2"; -option go_package = "v2"; import "envoy/config/filter/accesslog/v2/accesslog.proto"; diff --git a/api/envoy/config/filter/http/router/v3alpha/BUILD b/api/envoy/config/filter/http/router/v3alpha/BUILD new file mode 100644 index 0000000000..d68a0ac2c2 --- /dev/null +++ b/api/envoy/config/filter/http/router/v3alpha/BUILD @@ -0,0 +1,13 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/config/filter/accesslog/v3alpha:pkg"], +) + +api_proto_library_internal( + name = "router", + srcs = ["router.proto"], + deps = ["//envoy/config/filter/accesslog/v3alpha:accesslog"], +) diff --git a/api/envoy/config/filter/http/router/v3alpha/router.proto b/api/envoy/config/filter/http/router/v3alpha/router.proto new file mode 100644 index 0000000000..a4ceae7dc1 --- /dev/null +++ b/api/envoy/config/filter/http/router/v3alpha/router.proto @@ -0,0 +1,66 @@ +syntax = "proto3"; + +package envoy.config.filter.http.router.v3alpha; + +option java_outer_classname = "RouterProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.router.v3alpha"; + +import "envoy/config/filter/accesslog/v3alpha/accesslog.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Router] +// Router :ref:`configuration overview `. + +message Router { + // Whether the router generates dynamic cluster statistics. Defaults to + // true. Can be disabled in high performance scenarios. + google.protobuf.BoolValue dynamic_stats = 1; + + // Whether to start a child span for egress routed calls. This can be + // useful in scenarios where other filters (auth, ratelimit, etc.) make + // outbound calls and have child spans rooted at the same ingress + // parent. Defaults to false. + bool start_child_span = 2; + + // Configuration for HTTP upstream logs emitted by the router. Upstream logs + // are configured in the same way as access logs, but each log entry represents + // an upstream request. Presuming retries are configured, multiple upstream + // requests may be made for each downstream (inbound) request. + repeated envoy.config.filter.accesslog.v3alpha.AccessLog upstream_log = 3; + + // Do not add any additional *x-envoy-* headers to requests or responses. This + // only affects the :ref:`router filter generated *x-envoy-* headers + // `, other Envoy filters and the HTTP + // connection manager may continue to set *x-envoy-* headers. + bool suppress_envoy_headers = 4; + + // Specifies a list of HTTP headers to strictly validate. Envoy will reject a + // request and respond with HTTP status 400 if the request contains an invalid + // value for any of the headers listed in this field. Strict header checking + // is only supported for the following headers: + // + // Value must be a ','-delimited list (i.e. no spaces) of supported retry + // policy values: + // + // * :ref:`config_http_filters_router_x-envoy-retry-grpc-on` + // * :ref:`config_http_filters_router_x-envoy-retry-on` + // + // Value must be an integer: + // + // * :ref:`config_http_filters_router_x-envoy-max-retries` + // * :ref:`config_http_filters_router_x-envoy-upstream-rq-timeout-ms` + // * :ref:`config_http_filters_router_x-envoy-upstream-rq-per-try-timeout-ms` + repeated string strict_check_headers = 5 [(validate.rules).repeated .items.string = { + in: [ + "x-envoy-upstream-rq-timeout-ms", + "x-envoy-upstream-rq-per-try-timeout-ms", + "x-envoy-max-retries", + "x-envoy-retry-grpc-on", + "x-envoy-retry-on" + ] + }]; +} diff --git a/api/envoy/config/filter/http/squash/v2/BUILD b/api/envoy/config/filter/http/squash/v2/BUILD index 86bd4e8cfb..2a0c1c8e30 100644 --- a/api/envoy/config/filter/http/squash/v2/BUILD +++ b/api/envoy/config/filter/http/squash/v2/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "squash", srcs = ["squash.proto"], diff --git a/api/envoy/config/filter/http/squash/v2/squash.proto b/api/envoy/config/filter/http/squash/v2/squash.proto index 006af4380d..54a67ceddf 100644 --- a/api/envoy/config/filter/http/squash/v2/squash.proto +++ b/api/envoy/config/filter/http/squash/v2/squash.proto @@ -5,13 +5,11 @@ package envoy.config.filter.http.squash.v2; option java_outer_classname = "SquashProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.squash.v2"; -option go_package = "v2"; import "google/protobuf/duration.proto"; import "google/protobuf/struct.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Squash] // Squash :ref:`configuration overview `. @@ -43,13 +41,13 @@ message Squash { google.protobuf.Struct attachment_template = 2; // The timeout for individual requests sent to the Squash cluster. Defaults to 1 second. - google.protobuf.Duration request_timeout = 3 [(gogoproto.stdduration) = true]; + google.protobuf.Duration request_timeout = 3; // The total timeout Squash will delay a request and wait for it to be attached. Defaults to 60 // seconds. - google.protobuf.Duration attachment_timeout = 4 [(gogoproto.stdduration) = true]; + google.protobuf.Duration attachment_timeout = 4; // Amount of time to poll for the status of the attachment object in the Squash server // (to check if has been attached). Defaults to 1 second. - google.protobuf.Duration attachment_poll_period = 5 [(gogoproto.stdduration) = true]; + google.protobuf.Duration attachment_poll_period = 5; } diff --git a/api/envoy/config/filter/http/squash/v3alpha/BUILD b/api/envoy/config/filter/http/squash/v3alpha/BUILD new file mode 100644 index 0000000000..2a0c1c8e30 --- /dev/null +++ b/api/envoy/config/filter/http/squash/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "squash", + srcs = ["squash.proto"], +) diff --git a/api/envoy/config/filter/http/squash/v3alpha/squash.proto b/api/envoy/config/filter/http/squash/v3alpha/squash.proto new file mode 100644 index 0000000000..a1b355e67c --- /dev/null +++ b/api/envoy/config/filter/http/squash/v3alpha/squash.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +package envoy.config.filter.http.squash.v3alpha; + +option java_outer_classname = "SquashProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.squash.v3alpha"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Squash] +// Squash :ref:`configuration overview `. + +// [#proto-status: experimental] +message Squash { + // The name of the cluster that hosts the Squash server. + string cluster = 1 [(validate.rules).string.min_bytes = 1]; + + // When the filter requests the Squash server to create a DebugAttachment, it will use this + // structure as template for the body of the request. It can contain reference to environment + // variables in the form of '{{ ENV_VAR_NAME }}'. These can be used to provide the Squash server + // with more information to find the process to attach the debugger to. For example, in a + // Istio/k8s environment, this will contain information on the pod: + // + // .. code-block:: json + // + // { + // "spec": { + // "attachment": { + // "pod": "{{ POD_NAME }}", + // "namespace": "{{ POD_NAMESPACE }}" + // }, + // "match_request": true + // } + // } + // + // (where POD_NAME, POD_NAMESPACE are configured in the pod via the Downward API) + google.protobuf.Struct attachment_template = 2; + + // The timeout for individual requests sent to the Squash cluster. Defaults to 1 second. + google.protobuf.Duration request_timeout = 3; + + // The total timeout Squash will delay a request and wait for it to be attached. Defaults to 60 + // seconds. + google.protobuf.Duration attachment_timeout = 4; + + // Amount of time to poll for the status of the attachment object in the Squash server + // (to check if has been attached). Defaults to 1 second. + google.protobuf.Duration attachment_poll_period = 5; +} diff --git a/api/envoy/config/filter/http/tap/v2alpha/BUILD b/api/envoy/config/filter/http/tap/v2alpha/BUILD index f84625a7da..0949dad0c6 100644 --- a/api/envoy/config/filter/http/tap/v2alpha/BUILD +++ b/api/envoy/config/filter/http/tap/v2alpha/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/config/common/tap/v2alpha:pkg"], +) + api_proto_library_internal( name = "tap", srcs = ["tap.proto"], diff --git a/api/envoy/config/filter/http/tap/v3alpha/BUILD b/api/envoy/config/filter/http/tap/v3alpha/BUILD new file mode 100644 index 0000000000..0535cfbc21 --- /dev/null +++ b/api/envoy/config/filter/http/tap/v3alpha/BUILD @@ -0,0 +1,15 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/config/common/tap/v3alpha:pkg"], +) + +api_proto_library_internal( + name = "tap", + srcs = ["tap.proto"], + deps = [ + "//envoy/config/common/tap/v3alpha:common", + ], +) diff --git a/api/envoy/config/filter/http/tap/v3alpha/tap.proto b/api/envoy/config/filter/http/tap/v3alpha/tap.proto new file mode 100644 index 0000000000..e92c7d229f --- /dev/null +++ b/api/envoy/config/filter/http/tap/v3alpha/tap.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +import "envoy/config/common/tap/v3alpha/common.proto"; + +import "validate/validate.proto"; + +package envoy.config.filter.http.tap.v3alpha; + +option java_outer_classname = "TapProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.tap.v3alpha"; + +// [#protodoc-title: Tap] +// Tap :ref:`configuration overview `. + +// Top level configuration for the tap filter. +message Tap { + // Common configuration for the HTTP tap filter. + common.tap.v3alpha.CommonExtensionConfig common_config = 1 + [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/filter/http/transcoder/v2/BUILD b/api/envoy/config/filter/http/transcoder/v2/BUILD index c1a845bcd9..33a99a23a0 100644 --- a/api/envoy/config/filter/http/transcoder/v2/BUILD +++ b/api/envoy/config/filter/http/transcoder/v2/BUILD @@ -1,13 +1,10 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "transcoder", srcs = ["transcoder.proto"], ) - -api_go_proto_library( - name = "transcoder", - proto = ":transcoder", -) diff --git a/api/envoy/config/filter/http/transcoder/v2/transcoder.proto b/api/envoy/config/filter/http/transcoder/v2/transcoder.proto index 14f5412450..85f837fa79 100644 --- a/api/envoy/config/filter/http/transcoder/v2/transcoder.proto +++ b/api/envoy/config/filter/http/transcoder/v2/transcoder.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.transcoder.v2; option java_outer_classname = "TranscoderProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.transcoder.v2"; -option go_package = "v2"; import "validate/validate.proto"; diff --git a/api/envoy/config/filter/http/transcoder/v3alpha/BUILD b/api/envoy/config/filter/http/transcoder/v3alpha/BUILD new file mode 100644 index 0000000000..33a99a23a0 --- /dev/null +++ b/api/envoy/config/filter/http/transcoder/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "transcoder", + srcs = ["transcoder.proto"], +) diff --git a/api/envoy/config/filter/http/transcoder/v3alpha/transcoder.proto b/api/envoy/config/filter/http/transcoder/v3alpha/transcoder.proto new file mode 100644 index 0000000000..630ad245a8 --- /dev/null +++ b/api/envoy/config/filter/http/transcoder/v3alpha/transcoder.proto @@ -0,0 +1,122 @@ +syntax = "proto3"; + +package envoy.config.filter.http.transcoder.v3alpha; + +option java_outer_classname = "TranscoderProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.http.transcoder.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: gRPC-JSON transcoder] +// gRPC-JSON transcoder :ref:`configuration overview `. + +message GrpcJsonTranscoder { + oneof descriptor_set { + option (validate.required) = true; + + // Supplies the filename of + // :ref:`the proto descriptor set ` for the gRPC + // services. + string proto_descriptor = 1; + + // Supplies the binary content of + // :ref:`the proto descriptor set ` for the gRPC + // services. + bytes proto_descriptor_bin = 4; + } + + // A list of strings that + // supplies the fully qualified service names (i.e. "package_name.service_name") that + // the transcoder will translate. If the service name doesn't exist in ``proto_descriptor``, + // Envoy will fail at startup. The ``proto_descriptor`` may contain more services than + // the service names specified here, but they won't be translated. + repeated string services = 2 [(validate.rules).repeated .min_items = 1]; + + message PrintOptions { + // Whether to add spaces, line breaks and indentation to make the JSON + // output easy to read. Defaults to false. + bool add_whitespace = 1; + + // Whether to always print primitive fields. By default primitive + // fields with default values will be omitted in JSON output. For + // example, an int32 field set to 0 will be omitted. Setting this flag to + // true will override the default behavior and print primitive fields + // regardless of their values. Defaults to false. + bool always_print_primitive_fields = 2; + + // Whether to always print enums as ints. By default they are rendered + // as strings. Defaults to false. + bool always_print_enums_as_ints = 3; + + // Whether to preserve proto field names. By default protobuf will + // generate JSON field names using the ``json_name`` option, or lower camel case, + // in that order. Setting this flag will preserve the original field names. Defaults to false. + bool preserve_proto_field_names = 4; + }; + + // Control options for response JSON. These options are passed directly to + // `JsonPrintOptions `_. + PrintOptions print_options = 3; + + // Whether to keep the incoming request route after the outgoing headers have been transformed to + // the match the upstream gRPC service. Note: This means that routes for gRPC services that are + // not transcoded cannot be used in combination with *match_incoming_request_route*. + bool match_incoming_request_route = 5; + + // A list of query parameters to be ignored for transcoding method mapping. + // By default, the transcoder filter will not transcode a request if there are any + // unknown/invalid query parameters. + // + // Example : + // + // .. code-block:: proto + // + // service Bookstore { + // rpc GetShelf(GetShelfRequest) returns (Shelf) { + // option (google.api.http) = { + // get: "/shelves/{shelf}" + // }; + // } + // } + // + // message GetShelfRequest { + // int64 shelf = 1; + // } + // + // message Shelf {} + // + // The request ``/shelves/100?foo=bar`` will not be mapped to ``GetShelf``` because variable + // binding for ``foo`` is not defined. Adding ``foo`` to ``ignored_query_parameters`` will allow + // the same request to be mapped to ``GetShelf``. + repeated string ignored_query_parameters = 6; + + // Whether to route methods without the ``google.api.http`` option. + // + // Example : + // + // .. code-block:: proto + // + // package bookstore; + // + // service Bookstore { + // rpc GetShelf(GetShelfRequest) returns (Shelf) {} + // } + // + // message GetShelfRequest { + // int64 shelf = 1; + // } + // + // message Shelf {} + // + // The client could ``post`` a json body ``{"shelf": 1234}`` with the path of + // ``/bookstore.Bookstore/GetShelfRequest`` to call ``GetShelfRequest``. + bool auto_mapping = 7; + + // Whether to ignore query parameters that cannot be mapped to a corresponding + // protobuf field. Use this if you cannot control the query parameters and do + // not know them beforehand. Otherwise use ``ignored_query_parameters``. + // Defaults to false. + bool ignore_unknown_query_parameters = 8; +} diff --git a/api/envoy/config/filter/http/wasm/v2/BUILD b/api/envoy/config/filter/http/wasm/v2/BUILD index e014b24532..70569fb5a6 100644 --- a/api/envoy/config/filter/http/wasm/v2/BUILD +++ b/api/envoy/config/filter/http/wasm/v2/BUILD @@ -1,7 +1,9 @@ -load("//bazel:api_build_system.bzl", "api_proto_library_internal") +load("//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "wasm", srcs = ["wasm.proto"], diff --git a/api/envoy/config/filter/http/wasm/v2/wasm.proto b/api/envoy/config/filter/http/wasm/v2/wasm.proto index 313201dbb0..ec7a2e0ff4 100644 --- a/api/envoy/config/filter/http/wasm/v2/wasm.proto +++ b/api/envoy/config/filter/http/wasm/v2/wasm.proto @@ -5,7 +5,6 @@ package envoy.config.filter.http.wasm.v2; option java_outer_classname = "WasmProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.http.wasm.v2"; -option go_package = "v2"; import "validate/validate.proto"; import "envoy/config/wasm/v2/wasm.proto"; diff --git a/api/envoy/config/filter/listener/original_src/v2alpha1/BUILD b/api/envoy/config/filter/listener/original_src/v2alpha1/BUILD index e064545b21..a7435bb55c 100644 --- a/api/envoy/config/filter/listener/original_src/v2alpha1/BUILD +++ b/api/envoy/config/filter/listener/original_src/v2alpha1/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "original_src", srcs = ["original_src.proto"], diff --git a/api/envoy/config/filter/listener/original_src/v2alpha1/original_src.proto b/api/envoy/config/filter/listener/original_src/v2alpha1/original_src.proto index aa38e1d3df..11f55a787f 100644 --- a/api/envoy/config/filter/listener/original_src/v2alpha1/original_src.proto +++ b/api/envoy/config/filter/listener/original_src/v2alpha1/original_src.proto @@ -6,8 +6,6 @@ option java_outer_classname = "OriginalSrcProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.listener.original_src.v2alpha1"; -option go_package = "v2alpha1"; - import "validate/validate.proto"; // [#protodoc-title: Original Src Filter] diff --git a/api/envoy/config/filter/listener/original_src/v3alpha/BUILD b/api/envoy/config/filter/listener/original_src/v3alpha/BUILD new file mode 100644 index 0000000000..a7435bb55c --- /dev/null +++ b/api/envoy/config/filter/listener/original_src/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "original_src", + srcs = ["original_src.proto"], +) diff --git a/api/envoy/config/filter/listener/original_src/v3alpha/original_src.proto b/api/envoy/config/filter/listener/original_src/v3alpha/original_src.proto new file mode 100644 index 0000000000..3c5fee9505 --- /dev/null +++ b/api/envoy/config/filter/listener/original_src/v3alpha/original_src.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package envoy.config.filter.listener.original_src.v3alpha; + +option java_outer_classname = "OriginalSrcProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.listener.original_src.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: Original Src Filter] +// Use the Original source address on upstream connections. + +// The Original Src filter binds upstream connections to the original source address determined +// for the connection. This address could come from something like the Proxy Protocol filter, or it +// could come from trusted http headers. +message OriginalSrc { + + // Whether to bind the port to the one used in the original downstream connection. + // [#not-implemented-warn:] + bool bind_port = 1; + + // Sets the SO_MARK option on the upstream connection's socket to the provided value. Used to + // ensure that non-local addresses may be routed back through envoy when binding to the original + // source address. The option will not be applied if the mark is 0. + // [#proto-status: experimental] + uint32 mark = 2; +} diff --git a/api/envoy/config/filter/network/client_ssl_auth/v2/BUILD b/api/envoy/config/filter/network/client_ssl_auth/v2/BUILD index dad2d7fea2..96b5e9d0d4 100644 --- a/api/envoy/config/filter/network/client_ssl_auth/v2/BUILD +++ b/api/envoy/config/filter/network/client_ssl_auth/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library_internal( name = "client_ssl_auth", srcs = ["client_ssl_auth.proto"], diff --git a/api/envoy/config/filter/network/client_ssl_auth/v2/client_ssl_auth.proto b/api/envoy/config/filter/network/client_ssl_auth/v2/client_ssl_auth.proto index fe0a6a3800..bfd59dd5e8 100644 --- a/api/envoy/config/filter/network/client_ssl_auth/v2/client_ssl_auth.proto +++ b/api/envoy/config/filter/network/client_ssl_auth/v2/client_ssl_auth.proto @@ -5,13 +5,11 @@ package envoy.config.filter.network.client_ssl_auth.v2; option java_outer_classname = "ClientSslAuthProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.client_ssl_auth.v2"; -option go_package = "v2"; import "envoy/api/v2/core/address.proto"; import "google/protobuf/duration.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Client TLS authentication] // Client TLS authentication @@ -32,7 +30,7 @@ message ClientSSLAuth { // authentication service. Default is 60000 (60s). The actual fetch time // will be this value plus a random jittered value between // 0-refresh_delay_ms milliseconds. - google.protobuf.Duration refresh_delay = 3 [(gogoproto.stdduration) = true]; + google.protobuf.Duration refresh_delay = 3; // An optional list of IP address and subnet masks that should be white // listed for access by the filter. If no list is provided, there is no diff --git a/api/envoy/config/filter/network/client_ssl_auth/v3alpha/BUILD b/api/envoy/config/filter/network/client_ssl_auth/v3alpha/BUILD new file mode 100644 index 0000000000..540d8b4aa1 --- /dev/null +++ b/api/envoy/config/filter/network/client_ssl_auth/v3alpha/BUILD @@ -0,0 +1,13 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library_internal( + name = "client_ssl_auth", + srcs = ["client_ssl_auth.proto"], + deps = ["//envoy/api/v3alpha/core:address"], +) diff --git a/api/envoy/config/filter/network/client_ssl_auth/v3alpha/client_ssl_auth.proto b/api/envoy/config/filter/network/client_ssl_auth/v3alpha/client_ssl_auth.proto new file mode 100644 index 0000000000..e9a27d151e --- /dev/null +++ b/api/envoy/config/filter/network/client_ssl_auth/v3alpha/client_ssl_auth.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package envoy.config.filter.network.client_ssl_auth.v3alpha; + +option java_outer_classname = "ClientSslAuthProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.client_ssl_auth.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; +import "google/protobuf/duration.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Client TLS authentication] +// Client TLS authentication +// :ref:`configuration overview `. + +message ClientSSLAuth { + // The :ref:`cluster manager ` cluster that runs + // the authentication service. The filter will connect to the service every 60s to fetch the list + // of principals. The service must support the expected :ref:`REST API + // `. + string auth_api_cluster = 1 [(validate.rules).string.min_bytes = 1]; + + // The prefix to use when emitting :ref:`statistics + // `. + string stat_prefix = 2 [(validate.rules).string.min_bytes = 1]; + + // Time in milliseconds between principal refreshes from the + // authentication service. Default is 60000 (60s). The actual fetch time + // will be this value plus a random jittered value between + // 0-refresh_delay_ms milliseconds. + google.protobuf.Duration refresh_delay = 3; + + // An optional list of IP address and subnet masks that should be white + // listed for access by the filter. If no list is provided, there is no + // IP white list. + repeated envoy.api.v3alpha.core.CidrRange ip_white_list = 4; +} diff --git a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/BUILD b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/BUILD index e3e83a7046..c6cee209c6 100644 --- a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/BUILD +++ b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/BUILD @@ -1,7 +1,16 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/api/v2/route:pkg", + "//envoy/type", + "//envoy/type/matcher", + ], +) + api_proto_library_internal( name = "dubbo_proxy", srcs = [ diff --git a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto index 5b0995ba00..a27d7001cb 100644 --- a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto +++ b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto @@ -5,14 +5,12 @@ package envoy.config.filter.network.dubbo_proxy.v2alpha1; option java_outer_classname = "DubboProxyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.dubbo_proxy.v2alpha1"; -option go_package = "v2"; import "envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto"; import "google/protobuf/any.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Dubbo Proxy] // Dubbo Proxy :ref:`configuration overview `. @@ -58,4 +56,4 @@ message DubboFilter { // Filter specific configuration which depends on the filter being // instantiated. See the supported filters for further documentation. google.protobuf.Any config = 2; -} \ No newline at end of file +} diff --git a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto index 39f16e1a93..02d86443a6 100644 --- a/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto +++ b/api/envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.dubbo_proxy.v2alpha1; option java_outer_classname = "RouteProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.dubbo_proxy.v2alpha1"; -option go_package = "v2"; import "envoy/api/v2/route/route.proto"; import "envoy/type/matcher/string.proto"; @@ -14,9 +13,6 @@ import "envoy/type/range.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.stable_marshaler_all) = true; // [#protodoc-title: Dubbo Proxy Route Configuration] // Dubbo Proxy :ref:`configuration overview `. diff --git a/api/envoy/config/filter/network/dubbo_proxy/v3alpha/BUILD b/api/envoy/config/filter/network/dubbo_proxy/v3alpha/BUILD new file mode 100644 index 0000000000..db73dfbd08 --- /dev/null +++ b/api/envoy/config/filter/network/dubbo_proxy/v3alpha/BUILD @@ -0,0 +1,26 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/route:pkg", + "//envoy/type", + "//envoy/type/matcher", + ], +) + +api_proto_library_internal( + name = "dubbo_proxy", + srcs = [ + "dubbo_proxy.proto", + "route.proto", + ], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/route", + "//envoy/type:range", + "//envoy/type/matcher:string", + ], +) diff --git a/api/envoy/config/filter/network/dubbo_proxy/v3alpha/README.md b/api/envoy/config/filter/network/dubbo_proxy/v3alpha/README.md new file mode 100644 index 0000000000..c83caca1f8 --- /dev/null +++ b/api/envoy/config/filter/network/dubbo_proxy/v3alpha/README.md @@ -0,0 +1 @@ +Protocol buffer definitions for the Dubbo proxy. diff --git a/api/envoy/config/filter/network/dubbo_proxy/v3alpha/dubbo_proxy.proto b/api/envoy/config/filter/network/dubbo_proxy/v3alpha/dubbo_proxy.proto new file mode 100644 index 0000000000..211c4cfed1 --- /dev/null +++ b/api/envoy/config/filter/network/dubbo_proxy/v3alpha/dubbo_proxy.proto @@ -0,0 +1,59 @@ +syntax = "proto3"; + +package envoy.config.filter.network.dubbo_proxy.v3alpha; + +option java_outer_classname = "DubboProxyProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.dubbo_proxy.v3alpha"; + +import "envoy/config/filter/network/dubbo_proxy/v3alpha/route.proto"; + +import "google/protobuf/any.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Dubbo Proxy] +// Dubbo Proxy :ref:`configuration overview `. + +// [#comment:next free field: 6] +message DubboProxy { + // The human readable prefix to use when emitting statistics. + string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; + + // Configure the protocol used. + ProtocolType protocol_type = 2 [(validate.rules).enum.defined_only = true]; + + // Configure the serialization protocol used. + SerializationType serialization_type = 3 [(validate.rules).enum.defined_only = true]; + + // The route table for the connection manager is static and is specified in this property. + repeated RouteConfiguration route_config = 4; + + // A list of individual Dubbo filters that make up the filter chain for requests made to the + // Dubbo proxy. Order matters as the filters are processed sequentially. For backwards + // compatibility, if no dubbo_filters are specified, a default Dubbo router filter + // (`envoy.filters.dubbo.router`) is used. + repeated DubboFilter dubbo_filters = 5; +} + +// Dubbo Protocol types supported by Envoy. +enum ProtocolType { + Dubbo = 0; // the default protocol. +} + +// Dubbo Serialization types supported by Envoy. +enum SerializationType { + Hessian2 = 0; // the default serialization protocol. +} + +// DubboFilter configures a Dubbo filter. +// [#comment:next free field: 3] +message DubboFilter { + // The name of the filter to instantiate. The name must match a supported + // filter. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Filter specific configuration which depends on the filter being + // instantiated. See the supported filters for further documentation. + google.protobuf.Any config = 2; +} diff --git a/api/envoy/config/filter/network/dubbo_proxy/v3alpha/route.proto b/api/envoy/config/filter/network/dubbo_proxy/v3alpha/route.proto new file mode 100644 index 0000000000..6d357b8c13 --- /dev/null +++ b/api/envoy/config/filter/network/dubbo_proxy/v3alpha/route.proto @@ -0,0 +1,106 @@ +syntax = "proto3"; + +package envoy.config.filter.network.dubbo_proxy.v3alpha; + +option java_outer_classname = "RouteProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.dubbo_proxy.v3alpha"; + +import "envoy/api/v3alpha/route/route.proto"; +import "envoy/type/matcher/string.proto"; +import "envoy/type/range.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Dubbo Proxy Route Configuration] +// Dubbo Proxy :ref:`configuration overview `. + +// [#comment:next free field: 6] +message RouteConfiguration { + // The name of the route configuration. Reserved for future use in asynchronous route discovery. + string name = 1; + + // The interface name of the service. + string interface = 2; + + // Which group does the interface belong to. + string group = 3; + + // The version number of the interface. + string version = 4; + + // The list of routes that will be matched, in order, against incoming requests. The first route + // that matches will be used. + repeated Route routes = 5; +} + +// [#comment:next free field: 3] +message Route { + // Route matching parameters. + RouteMatch match = 1 [(validate.rules).message.required = true]; + + // Route request to some upstream cluster. + RouteAction route = 2 [(validate.rules).message.required = true]; +} + +// [#comment:next free field: 3] +message RouteMatch { + // Method level routing matching. + MethodMatch method = 1; + + // Specifies a set of headers that the route should match on. The router will check the request’s + // headers against all the specified headers in the route config. A match will happen if all the + // headers in the route are present in the request with the same values (or based on presence if + // the value field is not in the config). + repeated envoy.api.v3alpha.route.HeaderMatcher headers = 2; +} + +// [#comment:next free field: 3] +message RouteAction { + oneof cluster_specifier { + option (validate.required) = true; + + // Indicates the upstream cluster to which the request should be routed. + string cluster = 1; + + // Multiple upstream clusters can be specified for a given route. The + // request is routed to one of the upstream clusters based on weights + // assigned to each cluster. + // Currently ClusterWeight only supports the name and weight fields. + envoy.api.v3alpha.route.WeightedCluster weighted_clusters = 2; + } +} + +// [#comment:next free field: 5] +message MethodMatch { + // The name of the method. + envoy.type.matcher.StringMatcher name = 1; + + // The parameter matching type. + message ParameterMatchSpecifier { + oneof parameter_match_specifier { + // If specified, header match will be performed based on the value of the header. + string exact_match = 3; + + // If specified, header match will be performed based on range. + // The rule will match if the request header value is within this range. + // The entire request header value must represent an integer in base 10 notation: consisting + // of an optional plus or minus sign followed by a sequence of digits. The rule will not match + // if the header value does not represent an integer. Match will fail for empty values, + // floating point numbers or if only a subsequence of the header value is an integer. + // + // Examples: + // + // * For range [-10,0), route will match for header value -1, but not for 0, + // "somestring", 10.9, "-1somestring" + envoy.type.Int64Range range_match = 4; + } + } + + // Method parameter definition. + // The key is the parameter index, starting from 0. + // The value is the parameter matching type. + map params_match = 2; +} diff --git a/api/envoy/config/filter/network/ext_authz/v2/BUILD b/api/envoy/config/filter/network/ext_authz/v2/BUILD index 96184437fa..3bdae60659 100644 --- a/api/envoy/config/filter/network/ext_authz/v2/BUILD +++ b/api/envoy/config/filter/network/ext_authz/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library_internal( name = "ext_authz", srcs = ["ext_authz.proto"], diff --git a/api/envoy/config/filter/network/ext_authz/v2/ext_authz.proto b/api/envoy/config/filter/network/ext_authz/v2/ext_authz.proto index f9a2f351f7..8d0a6c6ca2 100644 --- a/api/envoy/config/filter/network/ext_authz/v2/ext_authz.proto +++ b/api/envoy/config/filter/network/ext_authz/v2/ext_authz.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.ext_authz.v2; option java_outer_classname = "ExtAuthzProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.ext_authz.v2"; -option go_package = "v2"; import "envoy/api/v2/core/grpc_service.proto"; diff --git a/api/envoy/config/filter/network/ext_authz/v3alpha/BUILD b/api/envoy/config/filter/network/ext_authz/v3alpha/BUILD new file mode 100644 index 0000000000..58aa289063 --- /dev/null +++ b/api/envoy/config/filter/network/ext_authz/v3alpha/BUILD @@ -0,0 +1,13 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library_internal( + name = "ext_authz", + srcs = ["ext_authz.proto"], + deps = ["//envoy/api/v3alpha/core:grpc_service"], +) diff --git a/api/envoy/config/filter/network/ext_authz/v3alpha/ext_authz.proto b/api/envoy/config/filter/network/ext_authz/v3alpha/ext_authz.proto new file mode 100644 index 0000000000..c53b509fee --- /dev/null +++ b/api/envoy/config/filter/network/ext_authz/v3alpha/ext_authz.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package envoy.config.filter.network.ext_authz.v3alpha; + +option java_outer_classname = "ExtAuthzProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.ext_authz.v3alpha"; + +import "envoy/api/v3alpha/core/grpc_service.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Network External Authorization ] +// The network layer external authorization service configuration +// :ref:`configuration overview `. + +// External Authorization filter calls out to an external service over the +// gRPC Authorization API defined by +// :ref:`CheckRequest `. +// A failed check will cause this filter to close the TCP connection. +message ExtAuthz { + // The prefix to use when emitting statistics. + string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; + + // The external authorization gRPC service configuration. + // The default timeout is set to 200ms by this filter. + envoy.api.v3alpha.core.GrpcService grpc_service = 2; + + // The filter's behaviour in case the external authorization service does + // not respond back. When it is set to true, Envoy will also allow traffic in case of + // communication failure between authorization service and the proxy. + // Defaults to false. + bool failure_mode_allow = 3; +} diff --git a/api/envoy/config/filter/network/http_connection_manager/v2/BUILD b/api/envoy/config/filter/network/http_connection_manager/v2/BUILD index 95d3811f42..6a090f3a11 100644 --- a/api/envoy/config/filter/network/http_connection_manager/v2/BUILD +++ b/api/envoy/config/filter/network/http_connection_manager/v2/BUILD @@ -1,7 +1,16 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2", + "//envoy/api/v2/core", + "//envoy/config/filter/accesslog/v2:pkg", + "//envoy/type", + ], +) + api_proto_library_internal( name = "http_connection_manager", srcs = ["http_connection_manager.proto"], @@ -15,17 +24,3 @@ api_proto_library_internal( "//envoy/type:percent", ], ) - -api_go_proto_library( - name = "http_connection_manager", - proto = ":http_connection_manager", - deps = [ - "//envoy/api/v2:rds_go_grpc", - "//envoy/api/v2:srds_go_grpc", - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:config_source_go_proto", - "//envoy/api/v2/core:protocol_go_proto", - "//envoy/config/filter/accesslog/v2:accesslog_go_proto", - "//envoy/type:percent_go_proto", - ], -) diff --git a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto index 4d662fc2c5..05c351eb14 100644 --- a/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto +++ b/api/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.http_connection_manager.v2; option java_outer_classname = "HttpConnectionManagerProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.http_connection_manager.v2"; -option go_package = "v2"; import "envoy/api/v2/core/config_source.proto"; import "envoy/api/v2/core/protocol.proto"; @@ -20,15 +19,13 @@ import "google/protobuf/struct.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: HTTP connection manager] // HTTP connection manager :ref:`configuration overview `. -// [#comment:next free field: 34] +// [#comment:next free field: 35] message HttpConnectionManager { enum CodecType { - option (gogoproto.goproto_enum_prefix) = false; // For every new connection, the connection manager will determine which // codec to use. This mode supports both ALPN for TLS listeners as well as @@ -80,9 +77,7 @@ message HttpConnectionManager { google.protobuf.BoolValue add_user_agent = 6; message Tracing { - // [#comment:TODO(kyessenov): Align this field with listener traffic direction field.] enum OperationName { - option (gogoproto.goproto_enum_prefix) = false; // The HTTP listener is used for ingress/incoming requests. INGRESS = 0; @@ -91,8 +86,13 @@ message HttpConnectionManager { EGRESS = 1; } - // The span name will be derived from this field. - OperationName operation_name = 1 [(validate.rules).enum.defined_only = true]; + // The span name will be derived from this field. If + // :ref:`traffic_direction ` is + // specified on the parent listener, then it is used instead of this field. + // + // .. attention:: + // This field has been deprecated in favor of `traffic_direction`. + OperationName operation_name = 1 [(validate.rules).enum.defined_only = true, deprecated = true]; // A list of header names used to create tags for the active span. The header name is used to // populate the tag name, and the header value is used to populate the tag value. The tag is @@ -127,6 +127,11 @@ message HttpConnectionManager { // Whether to annotate spans with additional data. If true, spans will include logs for stream // events. bool verbose = 6; + + // Maximum length of the request path to extract and include in the HttpUrl tag. Used to + // truncate lengthy request paths to meet the needs of a tracing backend. + // Default: 256 + google.protobuf.UInt32Value max_path_tag_length = 7; } // Presence of the object defines whether the connection manager @@ -144,6 +149,23 @@ message HttpConnectionManager { // header in responses. If not set, the default is *envoy*. string server_name = 10; + enum ServerHeaderTransformation { + + // Overwrite any Server header with the contents of server_name. + OVERWRITE = 0; + // If no Server header is present, append Server server_name + // If a Server header is present, pass it through. + APPEND_IF_ABSENT = 1; + // Pass through the value of the server header, and do not append a header + // if none is present. + PASS_THROUGH = 2; + } + // Defines the action to be applied to the Server header on the response path. + // By default, Envoy will overwrite the header with the value specified in + // server_name. + ServerHeaderTransformation server_header_transformation = 34 + [(validate.rules).enum.defined_only = true]; + // The maximum request headers size for incoming connections. // If unconfigured, the default max request headers allowed is 60 KiB. // Requests that exceed this limit will receive a 431 response. @@ -159,7 +181,7 @@ message HttpConnectionManager { // connection a drain sequence will occur prior to closing the connection. See // :ref:`drain_timeout // `. - google.protobuf.Duration idle_timeout = 11 [(gogoproto.stdduration) = true]; + google.protobuf.Duration idle_timeout = 11; // The stream idle timeout for connections managed by the connection manager. // If not specified, this defaults to 5 minutes. The default value was selected @@ -186,13 +208,13 @@ message HttpConnectionManager { // // A value of 0 will completely disable the connection manager stream idle // timeout, although per-route idle timeout overrides will continue to apply. - google.protobuf.Duration stream_idle_timeout = 24 [(gogoproto.stdduration) = true]; + google.protobuf.Duration stream_idle_timeout = 24; // A timeout for idle requests managed by the connection manager. // The timer is activated when the request is initiated, and is disarmed when the last byte of the // request is sent upstream (i.e. all decoding filters have processed the request), OR when the // response is initiated. If not specified or set to 0, this timeout is disabled. - google.protobuf.Duration request_timeout = 28 [(gogoproto.stdduration) = true]; + google.protobuf.Duration request_timeout = 28; // The time that Envoy will wait between sending an HTTP/2 “shutdown // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. @@ -203,7 +225,7 @@ message HttpConnectionManager { // both when a connection hits the idle timeout or during general server // draining. The default grace period is 5000 milliseconds (5 seconds) if this // option is not specified. - google.protobuf.Duration drain_timeout = 12 [(gogoproto.stdduration) = true]; + google.protobuf.Duration drain_timeout = 12; // The delayed close timeout is for downstream connections managed by the HTTP connection manager. // It is defined as a grace period after connection close processing has been locally initiated @@ -235,7 +257,7 @@ message HttpConnectionManager { // A value of 0 will completely disable delayed close processing. When disabled, the downstream // connection's socket will be closed immediately after the write flush is completed or will // never close if the write flush does not complete. - google.protobuf.Duration delayed_close_timeout = 26 [(gogoproto.stdduration) = true]; + google.protobuf.Duration delayed_close_timeout = 26; // Configuration for :ref:`HTTP access logs ` // emitted by the connection manager. @@ -297,7 +319,6 @@ message HttpConnectionManager { // How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP // header. enum ForwardClientCertDetails { - option (gogoproto.goproto_enum_prefix) = false; // Do not send the XFCC header to the next hop. This is the default value. SANITIZE = 0; diff --git a/api/envoy/config/filter/network/http_connection_manager/v3alpha/BUILD b/api/envoy/config/filter/network/http_connection_manager/v3alpha/BUILD new file mode 100644 index 0000000000..57e0528c2e --- /dev/null +++ b/api/envoy/config/filter/network/http_connection_manager/v3alpha/BUILD @@ -0,0 +1,26 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha", + "//envoy/api/v3alpha/core", + "//envoy/config/filter/accesslog/v3alpha:pkg", + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "http_connection_manager", + srcs = ["http_connection_manager.proto"], + deps = [ + "//envoy/api/v3alpha:rds", + "//envoy/api/v3alpha:srds", + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:config_source", + "//envoy/api/v3alpha/core:protocol", + "//envoy/config/filter/accesslog/v3alpha:accesslog", + "//envoy/type:percent", + ], +) diff --git a/api/envoy/config/filter/network/http_connection_manager/v3alpha/http_connection_manager.proto b/api/envoy/config/filter/network/http_connection_manager/v3alpha/http_connection_manager.proto new file mode 100644 index 0000000000..24e56507cd --- /dev/null +++ b/api/envoy/config/filter/network/http_connection_manager/v3alpha/http_connection_manager.proto @@ -0,0 +1,593 @@ +syntax = "proto3"; + +package envoy.config.filter.network.http_connection_manager.v3alpha; + +option java_outer_classname = "HttpConnectionManagerProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.http_connection_manager.v3alpha"; + +import "envoy/api/v3alpha/core/config_source.proto"; +import "envoy/api/v3alpha/core/protocol.proto"; +import "envoy/api/v3alpha/rds.proto"; +import "envoy/api/v3alpha/srds.proto"; +import "envoy/config/filter/accesslog/v3alpha/accesslog.proto"; +import "envoy/type/percent.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: HTTP connection manager] +// HTTP connection manager :ref:`configuration overview `. + +// [#comment:next free field: 35] +message HttpConnectionManager { + enum CodecType { + + // For every new connection, the connection manager will determine which + // codec to use. This mode supports both ALPN for TLS listeners as well as + // protocol inference for plaintext listeners. If ALPN data is available, it + // is preferred, otherwise protocol inference is used. In almost all cases, + // this is the right option to choose for this setting. + AUTO = 0; + + // The connection manager will assume that the client is speaking HTTP/1.1. + HTTP1 = 1; + + // The connection manager will assume that the client is speaking HTTP/2 + // (Envoy does not require HTTP/2 to take place over TLS or to use ALPN. + // Prior knowledge is allowed). + HTTP2 = 2; + } + + // Supplies the type of codec that the connection manager should use. + CodecType codec_type = 1 [(validate.rules).enum.defined_only = true]; + + // The human readable prefix to use when emitting statistics for the + // connection manager. See the :ref:`statistics documentation ` for + // more information. + string stat_prefix = 2 [(validate.rules).string.min_bytes = 1]; + + oneof route_specifier { + option (validate.required) = true; + + // The connection manager’s route table will be dynamically loaded via the RDS API. + Rds rds = 3; + + // The route table for the connection manager is static and is specified in this property. + envoy.api.v3alpha.RouteConfiguration route_config = 4; + + // A route table will be dynamically assigned to each request based on request attributes + // (e.g., the value of a header). The "routing scopes" (i.e., route tables) and "scope keys" are + // specified in this message. + ScopedRoutes scoped_routes = 31; + } + + // A list of individual HTTP filters that make up the filter chain for + // requests made to the connection manager. Order matters as the filters are + // processed sequentially as request events happen. + repeated HttpFilter http_filters = 5; + + // Whether the connection manager manipulates the :ref:`config_http_conn_man_headers_user-agent` + // and :ref:`config_http_conn_man_headers_downstream-service-cluster` headers. See the linked + // documentation for more information. Defaults to false. + google.protobuf.BoolValue add_user_agent = 6; + + message Tracing { + // [#comment:TODO(kyessenov): Align this field with listener traffic direction field.] + enum OperationName { + + // The HTTP listener is used for ingress/incoming requests. + INGRESS = 0; + + // The HTTP listener is used for egress/outgoing requests. + EGRESS = 1; + } + + // The span name will be derived from this field. + OperationName operation_name = 1 [(validate.rules).enum.defined_only = true]; + + // A list of header names used to create tags for the active span. The header name is used to + // populate the tag name, and the header value is used to populate the tag value. The tag is + // created if the specified header name is present in the request's headers. + repeated string request_headers_for_tags = 2; + + // Target percentage of requests managed by this HTTP connection manager that will be force + // traced if the :ref:`x-client-trace-id ` + // header is set. This field is a direct analog for the runtime variable + // 'tracing.client_sampling' in the :ref:`HTTP Connection Manager + // `. + // Default: 100% + envoy.type.Percent client_sampling = 3; + + // Target percentage of requests managed by this HTTP connection manager that will be randomly + // selected for trace generation, if not requested by the client or not forced. This field is + // a direct analog for the runtime variable 'tracing.random_sampling' in the + // :ref:`HTTP Connection Manager `. + // Default: 100% + envoy.type.Percent random_sampling = 4; + + // Target percentage of requests managed by this HTTP connection manager that will be traced + // after all other sampling checks have been applied (client-directed, force tracing, random + // sampling). This field functions as an upper limit on the total configured sampling rate. For + // instance, setting client_sampling to 100% but overall_sampling to 1% will result in only 1% + // of client requests with the appropriate headers to be force traced. This field is a direct + // analog for the runtime variable 'tracing.global_enabled' in the + // :ref:`HTTP Connection Manager `. + // Default: 100% + envoy.type.Percent overall_sampling = 5; + + // Whether to annotate spans with additional data. If true, spans will include logs for stream + // events. + bool verbose = 6; + } + + // Presence of the object defines whether the connection manager + // emits :ref:`tracing ` data to the :ref:`configured tracing provider + // `. + Tracing tracing = 7; + + // Additional HTTP/1 settings that are passed to the HTTP/1 codec. + envoy.api.v3alpha.core.Http1ProtocolOptions http_protocol_options = 8; + + // Additional HTTP/2 settings that are passed directly to the HTTP/2 codec. + envoy.api.v3alpha.core.Http2ProtocolOptions http2_protocol_options = 9; + + // An optional override that the connection manager will write to the server + // header in responses. If not set, the default is *envoy*. + string server_name = 10; + + enum ServerHeaderTransformation { + + // Overwrite any Server header with the contents of server_name. + OVERWRITE = 0; + // If no Server header is present, append Server server_name + // If a Server header is present, pass it through. + APPEND_IF_ABSENT = 1; + // Pass through the value of the server header, and do not append a header + // if none is present. + PASS_THROUGH = 2; + } + // Defines the action to be applied to the Server header on the response path. + // By default, Envoy will overwrite the header with the value specified in + // server_name. + ServerHeaderTransformation server_header_transformation = 34 + [(validate.rules).enum.defined_only = true]; + + // The maximum request headers size for incoming connections. + // If unconfigured, the default max request headers allowed is 60 KiB. + // Requests that exceed this limit will receive a 431 response. + // The max configurable limit is 96 KiB, based on current implementation + // constraints. + google.protobuf.UInt32Value max_request_headers_kb = 29 + [(validate.rules).uint32.gt = 0, (validate.rules).uint32.lte = 96]; + + // The idle timeout for connections managed by the connection manager. The + // idle timeout is defined as the period in which there are no active + // requests. If not set, there is no idle timeout. When the idle timeout is + // reached the connection will be closed. If the connection is an HTTP/2 + // connection a drain sequence will occur prior to closing the connection. See + // :ref:`drain_timeout + // `. + google.protobuf.Duration idle_timeout = 11; + + // The stream idle timeout for connections managed by the connection manager. + // If not specified, this defaults to 5 minutes. The default value was selected + // so as not to interfere with any smaller configured timeouts that may have + // existed in configurations prior to the introduction of this feature, while + // introducing robustness to TCP connections that terminate without a FIN. + // + // This idle timeout applies to new streams and is overridable by the + // :ref:`route-level idle_timeout + // `. Even on a stream in + // which the override applies, prior to receipt of the initial request + // headers, the :ref:`stream_idle_timeout + // ` + // applies. Each time an encode/decode event for headers or data is processed + // for the stream, the timer will be reset. If the timeout fires, the stream + // is terminated with a 408 Request Timeout error code if no upstream response + // header has been received, otherwise a stream reset occurs. + // + // Note that it is possible to idle timeout even if the wire traffic for a stream is non-idle, due + // to the granularity of events presented to the connection manager. For example, while receiving + // very large request headers, it may be the case that there is traffic regularly arriving on the + // wire while the connection manage is only able to observe the end-of-headers event, hence the + // stream may still idle timeout. + // + // A value of 0 will completely disable the connection manager stream idle + // timeout, although per-route idle timeout overrides will continue to apply. + google.protobuf.Duration stream_idle_timeout = 24; + + // A timeout for idle requests managed by the connection manager. + // The timer is activated when the request is initiated, and is disarmed when the last byte of the + // request is sent upstream (i.e. all decoding filters have processed the request), OR when the + // response is initiated. If not specified or set to 0, this timeout is disabled. + google.protobuf.Duration request_timeout = 28; + + // The time that Envoy will wait between sending an HTTP/2 “shutdown + // notification” (GOAWAY frame with max stream ID) and a final GOAWAY frame. + // This is used so that Envoy provides a grace period for new streams that + // race with the final GOAWAY frame. During this grace period, Envoy will + // continue to accept new streams. After the grace period, a final GOAWAY + // frame is sent and Envoy will start refusing new streams. Draining occurs + // both when a connection hits the idle timeout or during general server + // draining. The default grace period is 5000 milliseconds (5 seconds) if this + // option is not specified. + google.protobuf.Duration drain_timeout = 12; + + // The delayed close timeout is for downstream connections managed by the HTTP connection manager. + // It is defined as a grace period after connection close processing has been locally initiated + // during which Envoy will wait for the peer to close (i.e., a TCP FIN/RST is received by Envoy + // from the downstream connection) prior to Envoy closing the socket associated with that + // connection. + // NOTE: This timeout is enforced even when the socket associated with the downstream connection + // is pending a flush of the write buffer. However, any progress made writing data to the socket + // will restart the timer associated with this timeout. This means that the total grace period for + // a socket in this state will be + // +. + // + // Delaying Envoy's connection close and giving the peer the opportunity to initiate the close + // sequence mitigates a race condition that exists when downstream clients do not drain/process + // data in a connection's receive buffer after a remote close has been detected via a socket + // write(). This race leads to such clients failing to process the response code sent by Envoy, + // which could result in erroneous downstream processing. + // + // If the timeout triggers, Envoy will close the connection's socket. + // + // The default timeout is 1000 ms if this option is not specified. + // + // .. NOTE:: + // To be useful in avoiding the race condition described above, this timeout must be set + // to *at least* +<100ms to account for + // a reasonsable "worst" case processing time for a full iteration of Envoy's event loop>. + // + // .. WARNING:: + // A value of 0 will completely disable delayed close processing. When disabled, the downstream + // connection's socket will be closed immediately after the write flush is completed or will + // never close if the write flush does not complete. + google.protobuf.Duration delayed_close_timeout = 26; + + // Configuration for :ref:`HTTP access logs ` + // emitted by the connection manager. + repeated envoy.config.filter.accesslog.v3alpha.AccessLog access_log = 13; + + // If set to true, the connection manager will use the real remote address + // of the client connection when determining internal versus external origin and manipulating + // various headers. If set to false or absent, the connection manager will use the + // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. See the documentation for + // :ref:`config_http_conn_man_headers_x-forwarded-for`, + // :ref:`config_http_conn_man_headers_x-envoy-internal`, and + // :ref:`config_http_conn_man_headers_x-envoy-external-address` for more information. + google.protobuf.BoolValue use_remote_address = 14; + + // The number of additional ingress proxy hops from the right side of the + // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header to trust when + // determining the origin client's IP address. The default is zero if this option + // is not specified. See the documentation for + // :ref:`config_http_conn_man_headers_x-forwarded-for` for more information. + uint32 xff_num_trusted_hops = 19; + + message InternalAddressConfig { + // Whether unix socket addresses should be considered internal. + bool unix_sockets = 1; + } + + // Configures what network addresses are considered internal for stats and header sanitation + // purposes. If unspecified, only RFC1918 IP addresses will be considered internal. + // See the documentation for :ref:`config_http_conn_man_headers_x-envoy-internal` for more + // information about internal/external addresses. + InternalAddressConfig internal_address_config = 25; + + // If set, Envoy will not append the remote address to the + // :ref:`config_http_conn_man_headers_x-forwarded-for` HTTP header. This may be used in + // conjunction with HTTP filters that explicitly manipulate XFF after the HTTP connection manager + // has mutated the request headers. While :ref:`use_remote_address + // ` + // will also suppress XFF addition, it has consequences for logging and other + // Envoy uses of the remote address, so *skip_xff_append* should be used + // when only an elision of XFF addition is intended. + bool skip_xff_append = 21; + + // Via header value to append to request and response headers. If this is + // empty, no via header will be appended. + string via = 22; + + // Whether the connection manager will generate the :ref:`x-request-id + // ` header if it does not exist. This defaults to + // true. Generating a random UUID4 is expensive so in high throughput scenarios where this feature + // is not desired it can be disabled. + google.protobuf.BoolValue generate_request_id = 15; + + // Whether the connection manager will keep the :ref:`x-request-id + // ` header if passed for a request that is edge + // (Edge request is the request from external clients to front Envoy) and not reset it, which + // is the current Envoy behaviour. This defaults to false. + bool preserve_external_request_id = 32; + + // How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP + // header. + enum ForwardClientCertDetails { + + // Do not send the XFCC header to the next hop. This is the default value. + SANITIZE = 0; + + // When the client connection is mTLS (Mutual TLS), forward the XFCC header + // in the request. + FORWARD_ONLY = 1; + + // When the client connection is mTLS, append the client certificate + // information to the request’s XFCC header and forward it. + APPEND_FORWARD = 2; + + // When the client connection is mTLS, reset the XFCC header with the client + // certificate information and send it to the next hop. + SANITIZE_SET = 3; + + // Always forward the XFCC header in the request, regardless of whether the + // client connection is mTLS. + ALWAYS_FORWARD_ONLY = 4; + }; + + // How to handle the :ref:`config_http_conn_man_headers_x-forwarded-client-cert` (XFCC) HTTP + // header. + ForwardClientCertDetails forward_client_cert_details = 16 + [(validate.rules).enum.defined_only = true]; + + // [#comment:next free field: 7] + message SetCurrentClientCertDetails { + // Whether to forward the subject of the client cert. Defaults to false. + google.protobuf.BoolValue subject = 1; + + reserved 2; // san deprecated by uri + + // Whether to forward the entire client cert in URL encoded PEM format. This will appear in the + // XFCC header comma separated from other values with the value Cert="PEM". + // Defaults to false. + bool cert = 3; + + // Whether to forward the entire client cert chain (including the leaf cert) in URL encoded PEM + // format. This will appear in the XFCC header comma separated from other values with the value + // Chain="PEM". + // Defaults to false. + bool chain = 6; + + // Whether to forward the DNS type Subject Alternative Names of the client cert. + // Defaults to false. + bool dns = 4; + + // Whether to forward the URI type Subject Alternative Name of the client cert. Defaults to + // false. + bool uri = 5; + }; + + // This field is valid only when :ref:`forward_client_cert_details + // ` + // is APPEND_FORWARD or SANITIZE_SET and the client connection is mTLS. It specifies the fields in + // the client certificate to be forwarded. Note that in the + // :ref:`config_http_conn_man_headers_x-forwarded-client-cert` header, *Hash* is always set, and + // *By* is always set when the client certificate presents the URI type Subject Alternative Name + // value. + SetCurrentClientCertDetails set_current_client_cert_details = 17; + + // If proxy_100_continue is true, Envoy will proxy incoming "Expect: + // 100-continue" headers upstream, and forward "100 Continue" responses + // downstream. If this is false or not set, Envoy will instead strip the + // "Expect: 100-continue" header, and send a "100 Continue" response itself. + bool proxy_100_continue = 18; + + // If + // :ref:`use_remote_address + // ` + // is true and represent_ipv4_remote_address_as_ipv4_mapped_ipv6 is true and the remote address is + // an IPv4 address, the address will be mapped to IPv6 before it is appended to *x-forwarded-for*. + // This is useful for testing compatibility of upstream services that parse the header value. For + // example, 50.0.0.1 is represented as ::FFFF:50.0.0.1. See `IPv4-Mapped IPv6 Addresses + // `_ for details. This will also affect the + // :ref:`config_http_conn_man_headers_x-envoy-external-address` header. See + // :ref:`http_connection_manager.represent_ipv4_remote_address_as_ipv4_mapped_ipv6 + // ` for runtime + // control. + // [#not-implemented-hide:] + bool represent_ipv4_remote_address_as_ipv4_mapped_ipv6 = 20; + + // The configuration for HTTP upgrades. + // For each upgrade type desired, an UpgradeConfig must be added. + // + // .. warning:: + // + // The current implementation of upgrade headers does not handle + // multi-valued upgrade headers. Support for multi-valued headers may be + // added in the future if needed. + // + // .. warning:: + // The current implementation of upgrade headers does not work with HTTP/2 + // upstreams. + message UpgradeConfig { + // The case-insensitive name of this upgrade, e.g. "websocket". + // For each upgrade type present in upgrade_configs, requests with + // Upgrade: [upgrade_type] + // will be proxied upstream. + string upgrade_type = 1; + // If present, this represents the filter chain which will be created for + // this type of upgrade. If no filters are present, the filter chain for + // HTTP connections will be used for this upgrade type. + repeated HttpFilter filters = 2; + // Determines if upgrades are enabled or disabled by default. Defaults to true. + // This can be overridden on a per-route basis with :ref:`cluster + // ` as documented in the + // :ref:`upgrade documentation `. + google.protobuf.BoolValue enabled = 3; + }; + repeated UpgradeConfig upgrade_configs = 23; + + reserved 27; + + // Should paths be normalized according to RFC 3986 before any processing of + // requests by HTTP filters or routing? This affects the upstream *:path* header + // as well. For paths that fail this check, Envoy will respond with 400 to + // paths that are malformed. This defaults to false currently but will default + // true in the future. When not specified, this value may be overridden by the + // runtime variable + // :ref:`http_connection_manager.normalize_path`. + // See `Normalization and Comparison ` + // for details of normalization. + // Note that Envoy does not perform + // `case normalization ` + google.protobuf.BoolValue normalize_path = 30; + + // Determines if adjacent slashes in the path are merged into one before any processing of + // requests by HTTP filters or routing. This affects the upstream *:path* header as well. Without + // setting this option, incoming requests with path `//dir///file` will not match against route + // with `prefix` match set to `/dir`. Defaults to `false`. Note that slash merging is not part of + // `HTTP spec ` and is provided for convenience. + bool merge_slashes = 33; +} + +message Rds { + // Configuration source specifier for RDS. + envoy.api.v3alpha.core.ConfigSource config_source = 1 [(validate.rules).message.required = true]; + + // The name of the route configuration. This name will be passed to the RDS + // API. This allows an Envoy configuration with multiple HTTP listeners (and + // associated HTTP connection manager filters) to use different route + // configurations. + string route_config_name = 2 [(validate.rules).string.min_bytes = 1]; +} + +// This message is used to work around the limitations with 'oneof' and repeated fields. +message ScopedRouteConfigurationsList { + repeated envoy.api.v3alpha.ScopedRouteConfiguration scoped_route_configurations = 1 + [(validate.rules).repeated .min_items = 1]; +} + +message ScopedRoutes { + // The name assigned to the scoped routing configuration. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Specifies the mechanism for constructing "scope keys" based on HTTP request attributes. These + // keys are matched against a set of :ref:`Key` + // objects assembled from :ref:`ScopedRouteConfiguration` + // messages distributed via SRDS (the Scoped Route Discovery Service) or assigned statically via + // :ref:`scoped_route_configurations_list`. + // + // Upon receiving a request's headers, the Router will build a key using the algorithm specified + // by this message. This key will be used to look up the routing table (i.e., the + // :ref:`RouteConfiguration`) to use for the request. + message ScopeKeyBuilder { + // Specifies the mechanism for constructing key fragments which are composed into scope keys. + message FragmentBuilder { + // Specifies how the value of a header should be extracted. + // The following example maps the structure of a header to the fields in this message. + // + // .. code:: + // + // <0> <1> <-- index + // X-Header: a=b;c=d + // | || | + // | || \----> + // | || + // | |\----> + // | | + // | \----> + // | + // \----> + // + // Each 'a=b' key-value pair constitutes an 'element' of the header field. + message HeaderValueExtractor { + // The name of the header field to extract the value from. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // The element separator (e.g., ';' separates 'a;b;c;d'). + // Default: empty string. This causes the entirety of the header field to be extracted. + // If this field is set to an empty string and 'index' is used in the oneof below, 'index' + // must be set to 0. + string element_separator = 2; + + // Specifies a header field's key value pair to match on. + message KvElement { + // The separator between key and value (e.g., '=' separates 'k=v;...'). + // If an element is an empty string, the element is ignored. + // If an element contains no separator, the whole element is parsed as key and the + // fragment value is an empty string. + // If there are multiple values for a matched key, the first value is returned. + string separator = 1 [(validate.rules).string.min_bytes = 1]; + + // The key to match on. + string key = 2 [(validate.rules).string.min_bytes = 1]; + } + + oneof extract_type { + // Specifies the zero based index of the element to extract. + // Note Envoy concatenates multiple values of the same header key into a comma separated + // string, the splitting always happens after the concatenation. + uint32 index = 3; + + // Specifies the key value pair to extract the value from. + KvElement element = 4; + } + } + + oneof type { + option (validate.required) = true; + + // Specifies how a header field's value should be extracted. + HeaderValueExtractor header_value_extractor = 1; + } + } + + // The final scope key consists of the ordered union of these fragments. + repeated FragmentBuilder fragments = 1 [(validate.rules).repeated .min_items = 1]; + } + + // The algorithm to use for constructing a scope key for each request. + ScopeKeyBuilder scope_key_builder = 2 [(validate.rules).message.required = true]; + + // Configuration source specifier for RDS. + // This config source is used to subscribe to RouteConfiguration resources specified in + // ScopedRouteConfiguration messages. + envoy.api.v3alpha.core.ConfigSource rds_config_source = 3 + [(validate.rules).message.required = true]; + + oneof config_specifier { + option (validate.required) = true; + + // The set of routing scopes corresponding to the HCM. A scope is assigned to a request by + // matching a key constructed from the request's attributes according to the algorithm specified + // by the + // :ref:`ScopeKeyBuilder` + // in this message. + ScopedRouteConfigurationsList scoped_route_configurations_list = 4; + + // The set of routing scopes associated with the HCM will be dynamically loaded via the SRDS + // API. A scope is assigned to a request by matching a key constructed from the request's + // attributes according to the algorithm specified by the + // :ref:`ScopeKeyBuilder` + // in this message. + ScopedRds scoped_rds = 5; + } +} + +message ScopedRds { + // Configuration source specifier for scoped RDS. + envoy.api.v3alpha.core.ConfigSource scoped_rds_config_source = 1 + [(validate.rules).message.required = true]; +} + +message HttpFilter { + // The name of the filter to instantiate. The name must match a + // :ref:`supported filter `. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Filter specific configuration which depends on the filter being instantiated. See the supported + // filters for further documentation. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 4; + } + + reserved 3; +} diff --git a/api/envoy/config/filter/network/mongo_proxy/v2/BUILD b/api/envoy/config/filter/network/mongo_proxy/v2/BUILD index 5535f01017..59bad30ed9 100644 --- a/api/envoy/config/filter/network/mongo_proxy/v2/BUILD +++ b/api/envoy/config/filter/network/mongo_proxy/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/config/filter/fault/v2:pkg"], +) + api_proto_library_internal( name = "mongo_proxy", srcs = ["mongo_proxy.proto"], diff --git a/api/envoy/config/filter/network/mongo_proxy/v2/mongo_proxy.proto b/api/envoy/config/filter/network/mongo_proxy/v2/mongo_proxy.proto index 0d3d67bf66..46ef44c96b 100644 --- a/api/envoy/config/filter/network/mongo_proxy/v2/mongo_proxy.proto +++ b/api/envoy/config/filter/network/mongo_proxy/v2/mongo_proxy.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.mongo_proxy.v2; option java_outer_classname = "MongoProxyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.mongo_proxy.v2"; -option go_package = "v2"; import "envoy/config/filter/fault/v2/fault.proto"; diff --git a/api/envoy/config/filter/network/mongo_proxy/v3alpha/BUILD b/api/envoy/config/filter/network/mongo_proxy/v3alpha/BUILD new file mode 100644 index 0000000000..67dca3bb13 --- /dev/null +++ b/api/envoy/config/filter/network/mongo_proxy/v3alpha/BUILD @@ -0,0 +1,13 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/config/filter/fault/v3alpha:pkg"], +) + +api_proto_library_internal( + name = "mongo_proxy", + srcs = ["mongo_proxy.proto"], + deps = ["//envoy/config/filter/fault/v3alpha:fault"], +) diff --git a/api/envoy/config/filter/network/mongo_proxy/v3alpha/mongo_proxy.proto b/api/envoy/config/filter/network/mongo_proxy/v3alpha/mongo_proxy.proto new file mode 100644 index 0000000000..780483ccb4 --- /dev/null +++ b/api/envoy/config/filter/network/mongo_proxy/v3alpha/mongo_proxy.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package envoy.config.filter.network.mongo_proxy.v3alpha; + +option java_outer_classname = "MongoProxyProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.mongo_proxy.v3alpha"; + +import "envoy/config/filter/fault/v3alpha/fault.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Mongo proxy] +// MongoDB :ref:`configuration overview `. + +message MongoProxy { + // The human readable prefix to use when emitting :ref:`statistics + // `. + string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; + + // The optional path to use for writing Mongo access logs. If not access log + // path is specified no access logs will be written. Note that access log is + // also gated :ref:`runtime `. + string access_log = 2; + + // Inject a fixed delay before proxying a Mongo operation. Delays are + // applied to the following MongoDB operations: Query, Insert, GetMore, + // and KillCursors. Once an active delay is in progress, all incoming + // data up until the timer event fires will be a part of the delay. + envoy.config.filter.fault.v3alpha.FaultDelay delay = 3; + + // Flag to specify whether :ref:`dynamic metadata + // ` should be emitted. Defaults to false. + bool emit_dynamic_metadata = 4; +} diff --git a/api/envoy/config/filter/network/mysql_proxy/v1alpha1/BUILD b/api/envoy/config/filter/network/mysql_proxy/v1alpha1/BUILD index fde664838c..7f7da3af92 100644 --- a/api/envoy/config/filter/network/mysql_proxy/v1alpha1/BUILD +++ b/api/envoy/config/filter/network/mysql_proxy/v1alpha1/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "mysql_proxy", srcs = ["mysql_proxy.proto"], diff --git a/api/envoy/config/filter/network/mysql_proxy/v1alpha1/mysql_proxy.proto b/api/envoy/config/filter/network/mysql_proxy/v1alpha1/mysql_proxy.proto index e4246c9314..dee0145563 100644 --- a/api/envoy/config/filter/network/mysql_proxy/v1alpha1/mysql_proxy.proto +++ b/api/envoy/config/filter/network/mysql_proxy/v1alpha1/mysql_proxy.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.mysql_proxy.v1alpha1; option java_outer_classname = "MysqlProxyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.mysql_proxy.v1alpha1"; -option go_package = "v1alpha1"; import "validate/validate.proto"; diff --git a/api/envoy/config/filter/network/rate_limit/v2/BUILD b/api/envoy/config/filter/network/rate_limit/v2/BUILD index 08d5db95b1..fcdcd0dfa5 100644 --- a/api/envoy/config/filter/network/rate_limit/v2/BUILD +++ b/api/envoy/config/filter/network/rate_limit/v2/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/ratelimit:pkg", + "//envoy/config/ratelimit/v2:pkg", + ], +) + api_proto_library_internal( name = "rate_limit", srcs = ["rate_limit.proto"], 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 6a1b795580..f5d484fac1 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 @@ -5,7 +5,6 @@ package envoy.config.filter.network.rate_limit.v2; option java_outer_classname = "RateLimitProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.rate_limit.v2"; -option go_package = "v2"; import "envoy/api/v2/ratelimit/ratelimit.proto"; import "envoy/config/ratelimit/v2/rls.proto"; @@ -13,7 +12,6 @@ import "envoy/config/ratelimit/v2/rls.proto"; import "google/protobuf/duration.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Rate limit] // Rate limit :ref:`configuration overview `. @@ -31,7 +29,7 @@ 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]; + google.protobuf.Duration timeout = 4; // 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 diff --git a/api/envoy/config/filter/network/rate_limit/v3alpha/BUILD b/api/envoy/config/filter/network/rate_limit/v3alpha/BUILD new file mode 100644 index 0000000000..a13183b9eb --- /dev/null +++ b/api/envoy/config/filter/network/rate_limit/v3alpha/BUILD @@ -0,0 +1,19 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/ratelimit:pkg", + "//envoy/config/ratelimit/v3alpha:pkg", + ], +) + +api_proto_library_internal( + name = "rate_limit", + srcs = ["rate_limit.proto"], + deps = [ + "//envoy/api/v3alpha/ratelimit", + "//envoy/config/ratelimit/v3alpha:rls", + ], +) diff --git a/api/envoy/config/filter/network/rate_limit/v3alpha/rate_limit.proto b/api/envoy/config/filter/network/rate_limit/v3alpha/rate_limit.proto new file mode 100644 index 0000000000..522fe145a7 --- /dev/null +++ b/api/envoy/config/filter/network/rate_limit/v3alpha/rate_limit.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +package envoy.config.filter.network.rate_limit.v3alpha; + +option java_outer_classname = "RateLimitProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.rate_limit.v3alpha"; + +import "envoy/api/v3alpha/ratelimit/ratelimit.proto"; +import "envoy/config/ratelimit/v3alpha/rls.proto"; + +import "google/protobuf/duration.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Rate limit] +// Rate limit :ref:`configuration overview `. + +message RateLimit { + // The prefix to use when emitting :ref:`statistics `. + string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; + + // The rate limit domain to use in the rate limit service request. + string domain = 2 [(validate.rules).string.min_bytes = 1]; + + // The rate limit descriptor list to use in the rate limit service request. + repeated envoy.api.v3alpha.ratelimit.RateLimitDescriptor descriptors = 3 + [(validate.rules).repeated .min_items = 1]; + + // The timeout in milliseconds for the rate limit service RPC. If not + // set, this defaults to 20ms. + google.protobuf.Duration timeout = 4; + + // 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; + + // Configuration for an external rate limit service provider. If not + // specified, any calls to the rate limit service will immediately return + // success. + envoy.config.ratelimit.v3alpha.RateLimitServiceConfig rate_limit_service = 6 + [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/filter/network/rbac/v2/BUILD b/api/envoy/config/filter/network/rbac/v2/BUILD index 6182fe2674..ca9aa2ca41 100644 --- a/api/envoy/config/filter/network/rbac/v2/BUILD +++ b/api/envoy/config/filter/network/rbac/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/config/rbac/v2:pkg"], +) + api_proto_library_internal( name = "rbac", srcs = ["rbac.proto"], diff --git a/api/envoy/config/filter/network/rbac/v2/rbac.proto b/api/envoy/config/filter/network/rbac/v2/rbac.proto index aea17f725f..c192b888e5 100644 --- a/api/envoy/config/filter/network/rbac/v2/rbac.proto +++ b/api/envoy/config/filter/network/rbac/v2/rbac.proto @@ -5,12 +5,10 @@ package envoy.config.filter.network.rbac.v2; option java_outer_classname = "RbacProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.rbac.v2"; -option go_package = "v2"; import "envoy/config/rbac/v2/rbac.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: RBAC] // Role-Based Access Control :ref:`configuration overview `. diff --git a/api/envoy/config/filter/network/rbac/v3alpha/BUILD b/api/envoy/config/filter/network/rbac/v3alpha/BUILD new file mode 100644 index 0000000000..1e4d51b504 --- /dev/null +++ b/api/envoy/config/filter/network/rbac/v3alpha/BUILD @@ -0,0 +1,13 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/config/rbac/v3alpha:pkg"], +) + +api_proto_library_internal( + name = "rbac", + srcs = ["rbac.proto"], + deps = ["//envoy/config/rbac/v3alpha:rbac"], +) diff --git a/api/envoy/config/filter/network/rbac/v3alpha/rbac.proto b/api/envoy/config/filter/network/rbac/v3alpha/rbac.proto new file mode 100644 index 0000000000..c1dcd65682 --- /dev/null +++ b/api/envoy/config/filter/network/rbac/v3alpha/rbac.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +package envoy.config.filter.network.rbac.v3alpha; + +option java_outer_classname = "RbacProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.rbac.v3alpha"; + +import "envoy/config/rbac/v3alpha/rbac.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: RBAC] +// Role-Based Access Control :ref:`configuration overview `. + +// RBAC network filter config. +// +// Header should not be used in rules/shadow_rules in RBAC network filter as +// this information is only available in :ref:`RBAC http filter `. +message RBAC { + // Specify the RBAC rules to be applied globally. + // If absent, no enforcing RBAC policy will be applied. + config.rbac.v3alpha.RBAC rules = 1; + + // Shadow rules are not enforced by the filter but will emit stats and logs + // and can be used for rule testing. + // If absent, no shadow RBAC policy will be applied. + config.rbac.v3alpha.RBAC shadow_rules = 2; + + // The prefix to use when emitting statistics. + string stat_prefix = 3 [(validate.rules).string.min_bytes = 1]; + + enum EnforcementType { + // Apply RBAC policies when the first byte of data arrives on the connection. + ONE_TIME_ON_FIRST_BYTE = 0; + + // Continuously apply RBAC policies as data arrives. Use this mode when + // using RBAC with message oriented protocols such as Mongo, MySQL, Kafka, + // etc. when the protocol decoders emit dynamic metadata such as the + // resources being accessed and the operations on the resources. + CONTINUOUS = 1; + }; + + // RBAC enforcement strategy. By default RBAC will be enforced only once + // when the first byte of data arrives from the downstream. When used in + // conjunction with filters that emit dynamic metadata after decoding + // every payload (e.g., Mongo, MySQL, Kafka) set the enforcement type to + // CONTINUOUS to enforce RBAC policies on every message boundary. + EnforcementType enforcement_type = 4; +} diff --git a/api/envoy/config/filter/network/redis_proxy/v2/BUILD b/api/envoy/config/filter/network/redis_proxy/v2/BUILD index 16cff613b3..d23450a55d 100644 --- a/api/envoy/config/filter/network/redis_proxy/v2/BUILD +++ b/api/envoy/config/filter/network/redis_proxy/v2/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/type", + ], +) + api_proto_library_internal( name = "redis_proxy", srcs = ["redis_proxy.proto"], diff --git a/api/envoy/config/filter/network/redis_proxy/v2/redis_proxy.proto b/api/envoy/config/filter/network/redis_proxy/v2/redis_proxy.proto index 175e564dec..78c56bb2ef 100644 --- a/api/envoy/config/filter/network/redis_proxy/v2/redis_proxy.proto +++ b/api/envoy/config/filter/network/redis_proxy/v2/redis_proxy.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.redis_proxy.v2; option java_outer_classname = "RedisProxyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.redis_proxy.v2"; -option go_package = "v2"; import "envoy/api/v2/core/base.proto"; @@ -13,7 +12,6 @@ import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Redis Proxy] // Redis Proxy :ref:`configuration overview `. @@ -41,8 +39,7 @@ message RedisProxy { // The only exception to this behavior is when a connection to a backend is not yet established. // In that case, the connect timeout on the cluster will govern the timeout until the connection // is ready. - google.protobuf.Duration op_timeout = 1 - [(validate.rules).duration.required = true, (gogoproto.stdduration) = true]; + google.protobuf.Duration op_timeout = 1 [(validate.rules).duration.required = true]; // Use hash tagging on every redis key to guarantee that keys with the same hash tag will be // forwarded to the same upstream. The hash key used for determining the upstream in a @@ -82,7 +79,7 @@ message RedisProxy { // before the timer fires. // If `max_buffer_size_before_flush` is set, but `buffer_flush_timeout` is not, the latter // defaults to 3ms. - google.protobuf.Duration buffer_flush_timeout = 5 [(gogoproto.stdduration) = true]; + google.protobuf.Duration buffer_flush_timeout = 5; // `max_upstream_unknown_connections` controls how many upstream connections to unknown hosts // can be created at any given time by any given worker thread (see `enable_redirection` for @@ -91,6 +88,10 @@ message RedisProxy { // downstream unchanged. This limit defaults to 100. google.protobuf.UInt32Value max_upstream_unknown_connections = 6; + // Enable per-command statistics per upstream cluster, in addition to the filter level aggregate + // count. + bool enable_command_stats = 8; + // ReadPolicy controls how Envoy routes read commands to Redis nodes. This is currently // supported for Redis Cluster. All ReadPolicy settings except MASTER may return stale data // because replication is asynchronous and requires some delay. You need to ensure that your diff --git a/api/envoy/config/filter/network/redis_proxy/v3alpha/BUILD b/api/envoy/config/filter/network/redis_proxy/v3alpha/BUILD new file mode 100644 index 0000000000..4db47e3bb6 --- /dev/null +++ b/api/envoy/config/filter/network/redis_proxy/v3alpha/BUILD @@ -0,0 +1,19 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "redis_proxy", + srcs = ["redis_proxy.proto"], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/type:percent", + ], +) diff --git a/api/envoy/config/filter/network/redis_proxy/v3alpha/redis_proxy.proto b/api/envoy/config/filter/network/redis_proxy/v3alpha/redis_proxy.proto new file mode 100644 index 0000000000..bb9307cb32 --- /dev/null +++ b/api/envoy/config/filter/network/redis_proxy/v3alpha/redis_proxy.proto @@ -0,0 +1,233 @@ +syntax = "proto3"; + +package envoy.config.filter.network.redis_proxy.v3alpha; + +option java_outer_classname = "RedisProxyProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.redis_proxy.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Redis Proxy] +// Redis Proxy :ref:`configuration overview `. + +message RedisProxy { + // The prefix to use when emitting :ref:`statistics `. + string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; + + // Name of cluster from cluster manager. See the :ref:`configuration section + // ` of the architecture overview for recommendations on + // configuring the backing cluster. + // + // .. attention:: + // + // This field is deprecated. Use a :ref:`catch_all + // route` + // instead. + string cluster = 2 [deprecated = true]; + + // Redis connection pool settings. + message ConnPoolSettings { + // Per-operation timeout in milliseconds. The timer starts when the first + // command of a pipeline is written to the backend connection. Each response received from Redis + // resets the timer since it signifies that the next command is being processed by the backend. + // The only exception to this behavior is when a connection to a backend is not yet established. + // In that case, the connect timeout on the cluster will govern the timeout until the connection + // is ready. + google.protobuf.Duration op_timeout = 1 [(validate.rules).duration.required = true]; + + // Use hash tagging on every redis key to guarantee that keys with the same hash tag will be + // forwarded to the same upstream. The hash key used for determining the upstream in a + // consistent hash ring configuration will be computed from the hash tagged key instead of the + // whole key. The algorithm used to compute the hash tag is identical to the `redis-cluster + // implementation `_. + // + // Examples: + // + // * '{user1000}.following' and '{user1000}.followers' **will** be sent to the same upstream + // * '{user1000}.following' and '{user1001}.following' **might** be sent to the same upstream + bool enable_hashtagging = 2; + + // Accept `moved and ask redirection + // `_ errors from upstream + // redis servers, and retry commands to the specified target server. The target server does not + // need to be known to the cluster manager. If the command cannot be redirected, then the + // original error is passed downstream unchanged. By default, this support is not enabled. + bool enable_redirection = 3; + + // Maximum size of encoded request buffer before flush is triggered and encoded requests + // are sent upstream. If this is unset, the buffer flushes whenever it receives data + // and performs no batching. + // This feature makes it possible for multiple clients to send requests to Envoy and have + // them batched- for example if one is running several worker processes, each with its own + // Redis connection. There is no benefit to using this with a single downstream process. + // Recommended size (if enabled) is 1024 bytes. + uint32 max_buffer_size_before_flush = 4; + + // The encoded request buffer is flushed N milliseconds after the first request has been + // encoded, unless the buffer size has already exceeded `max_buffer_size_before_flush`. + // If `max_buffer_size_before_flush` is not set, this flush timer is not used. Otherwise, + // the timer should be set according to the number of clients, overall request rate and + // desired maximum latency for a single command. For example, if there are many requests + // being batched together at a high rate, the buffer will likely be filled before the timer + // fires. Alternatively, if the request rate is lower the buffer will not be filled as often + // before the timer fires. + // If `max_buffer_size_before_flush` is set, but `buffer_flush_timeout` is not, the latter + // defaults to 3ms. + google.protobuf.Duration buffer_flush_timeout = 5; + + // `max_upstream_unknown_connections` controls how many upstream connections to unknown hosts + // can be created at any given time by any given worker thread (see `enable_redirection` for + // more details). If the host is unknown and a connection cannot be created due to enforcing + // this limit, then redirection will fail and the original redirection error will be passed + // downstream unchanged. This limit defaults to 100. + google.protobuf.UInt32Value max_upstream_unknown_connections = 6; + + // ReadPolicy controls how Envoy routes read commands to Redis nodes. This is currently + // supported for Redis Cluster. All ReadPolicy settings except MASTER may return stale data + // because replication is asynchronous and requires some delay. You need to ensure that your + // application can tolerate stale data. + enum ReadPolicy { + // Default mode. Read from the current master node. + MASTER = 0; + // Read from the master, but if it is unavailable, read from replica nodes. + PREFER_MASTER = 1; + // Read from replica nodes. If multiple replica nodes are present within a shard, a random + // node is selected. Healthy nodes have precedent over unhealthy nodes. + REPLICA = 2; + // Read from the replica nodes (similar to REPLICA), but if all replicas are unavailable (not + // present or unhealthy), read from the master. + PREFER_REPLICA = 3; + // Read from any node of the cluster. A random node is selected among the master and replicas, + // healthy nodes have precedent over unhealthy nodes. + ANY = 4; + } + + // Read policy. The default is to read from the master. + ReadPolicy read_policy = 7 [(validate.rules).enum.defined_only = true]; + } + + // Network settings for the connection pool to the upstream clusters. + ConnPoolSettings settings = 3 [(validate.rules).message.required = true]; + + // Indicates that latency stat should be computed in microseconds. By default it is computed in + // milliseconds. + bool latency_in_micros = 4; + + message PrefixRoutes { + message Route { + // String prefix that must match the beginning of the keys. Envoy will always favor the + // longest match. + string prefix = 1; + + // Indicates if the prefix needs to be removed from the key when forwarded. + bool remove_prefix = 2; + + // Upstream cluster to forward the command to. + string cluster = 3 [(validate.rules).string.min_bytes = 1]; + + // The router is capable of shadowing traffic from one cluster to another. The current + // implementation is "fire and forget," meaning Envoy will not wait for the shadow cluster to + // respond before returning the response from the primary cluster. All normal statistics are + // collected for the shadow cluster making this feature useful for testing. + message RequestMirrorPolicy { + // Specifies the cluster that requests will be mirrored to. The cluster must + // exist in the cluster manager configuration. + string cluster = 1 [(validate.rules).string.min_bytes = 1]; + + // If not specified or the runtime key is not present, all requests to the target cluster + // will be mirrored. + // + // If specified, Envoy will lookup the runtime key to get the percentage of requests to the + // mirror. + // + // Parsing this field is implemented such that the runtime key's data may be represented + // as a :ref:`FractionalPercent ` proto represented + // as JSON/YAML and may also be represented as an integer with the assumption that the value + // is an integral percentage out of 100. For instance, a runtime key lookup returning the + // value "42" would parse as a `FractionalPercent` whose numerator is 42 and denominator is + // HUNDRED. + envoy.api.v3alpha.core.RuntimeFractionalPercent runtime_fraction = 2; + + // Set this to TRUE to only mirror write commands, this is effectively replicating the + // writes in a "fire and forget" manner. + bool exclude_read_commands = 3; + } + + // Indicates that the route has a request mirroring policy. + repeated RequestMirrorPolicy request_mirror_policy = 4; + } + + // List of prefix routes. + repeated Route routes = 1; + + // Indicates that prefix matching should be case insensitive. + bool case_insensitive = 2; + + // Optional catch-all route to forward commands that doesn't match any of the routes. The + // catch-all route becomes required when no routes are specified. + // .. attention:: + // + // This field is deprecated. Use a :ref:`catch_all + // route` + // instead. + string catch_all_cluster = 3 [deprecated = true]; + + // Optional catch-all route to forward commands that doesn't match any of the routes. The + // catch-all route becomes required when no routes are specified. + Route catch_all_route = 4; + } + + // List of **unique** prefixes used to separate keys from different workloads to different + // clusters. Envoy will always favor the longest match first in case of overlap. A catch-all + // cluster can be used to forward commands when there is no match. Time complexity of the + // lookups are in O(min(longest key prefix, key length)). + // + // Example: + // + // .. code-block:: yaml + // + // prefix_routes: + // routes: + // - prefix: "ab" + // cluster: "cluster_a" + // - prefix: "abc" + // cluster: "cluster_b" + // + // When using the above routes, the following prefixes would be sent to: + // + // * 'get abc:users' would retrive the key 'abc:users' from cluster_b. + // * 'get ab:users' would retrive the key 'ab:users' from cluster_a. + // * 'get z:users' would return a NoUpstreamHost error. A :ref:`catch-all + // route` + // would have retrieved the key from that cluster instead. + // + // See the :ref:`configuration section + // ` of the architecture overview for recommendations on + // configuring the backing clusters. + PrefixRoutes prefix_routes = 5; + + // Authenticate Redis client connections locally by forcing downstream clients to issue a 'Redis + // AUTH command `_ with this password before enabling any other + // command. If an AUTH command's password matches this password, an "OK" response will be returned + // to the client. If the AUTH command password does not match this password, then an "ERR invalid + // password" error will be returned. If any other command is received before AUTH when this + // password is set, then a "NOAUTH Authentication required." error response will be sent to the + // client. If an AUTH command is received when the password is not set, then an "ERR Client sent + // AUTH, but no password is set" error will be returned. + envoy.api.v3alpha.core.DataSource downstream_auth_password = 6; +} + +// RedisProtocolOptions specifies Redis upstream protocol options. This object is used in +// :ref:`extension_protocol_options`, keyed +// by the name `envoy.redis_proxy`. +message RedisProtocolOptions { + // Upstream server password as defined by the `requirepass directive + // `_ in the server's configuration file. + envoy.api.v3alpha.core.DataSource auth_password = 1; +} diff --git a/api/envoy/config/filter/network/tcp_proxy/v2/BUILD b/api/envoy/config/filter/network/tcp_proxy/v2/BUILD index d77d910ace..a0cc067086 100644 --- a/api/envoy/config/filter/network/tcp_proxy/v2/BUILD +++ b/api/envoy/config/filter/network/tcp_proxy/v2/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/config/filter/accesslog/v2:pkg", + ], +) + api_proto_library_internal( name = "tcp_proxy", srcs = ["tcp_proxy.proto"], diff --git a/api/envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.proto b/api/envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.proto index 62874fe1d4..8e4453dd9f 100644 --- a/api/envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.proto +++ b/api/envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.tcp_proxy.v2; option java_outer_classname = "TcpProxyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.tcp_proxy.v2"; -option go_package = "v2"; import "envoy/config/filter/accesslog/v2/accesslog.proto"; import "envoy/api/v2/core/address.proto"; @@ -15,7 +14,6 @@ import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: TCP Proxy] // TCP Proxy :ref:`configuration overview `. @@ -47,8 +45,7 @@ message TcpProxy { // is defined as the period in which there are no bytes sent or received on either // the upstream or downstream connection. If not set, connections will never be closed // by the TCP proxy due to being idle. - google.protobuf.Duration idle_timeout = 8 - [(validate.rules).duration.gt = {}, (gogoproto.stdduration) = true]; + google.protobuf.Duration idle_timeout = 8 [(validate.rules).duration.gt = {}]; // [#not-implemented-hide:] The idle timeout for connections managed by the TCP proxy // filter. The idle timeout is defined as the period in which there is no diff --git a/api/envoy/config/filter/network/tcp_proxy/v3alpha/BUILD b/api/envoy/config/filter/network/tcp_proxy/v3alpha/BUILD new file mode 100644 index 0000000000..305e06bc8b --- /dev/null +++ b/api/envoy/config/filter/network/tcp_proxy/v3alpha/BUILD @@ -0,0 +1,20 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/config/filter/accesslog/v3alpha:pkg", + ], +) + +api_proto_library_internal( + name = "tcp_proxy", + srcs = ["tcp_proxy.proto"], + deps = [ + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + "//envoy/config/filter/accesslog/v3alpha:accesslog", + ], +) diff --git a/api/envoy/config/filter/network/tcp_proxy/v3alpha/tcp_proxy.proto b/api/envoy/config/filter/network/tcp_proxy/v3alpha/tcp_proxy.proto new file mode 100644 index 0000000000..ff74cc06cd --- /dev/null +++ b/api/envoy/config/filter/network/tcp_proxy/v3alpha/tcp_proxy.proto @@ -0,0 +1,143 @@ +syntax = "proto3"; + +package envoy.config.filter.network.tcp_proxy.v3alpha; + +option java_outer_classname = "TcpProxyProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.tcp_proxy.v3alpha"; + +import "envoy/config/filter/accesslog/v3alpha/accesslog.proto"; +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: TCP Proxy] +// TCP Proxy :ref:`configuration overview `. + +message TcpProxy { + // The prefix to use when emitting :ref:`statistics + // `. + string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; + + oneof cluster_specifier { + option (validate.required) = true; + + // The upstream cluster to connect to. + // + string cluster = 2; + + // Multiple upstream clusters can be specified for a given route. The + // request is routed to one of the upstream clusters based on weights + // assigned to each cluster. + WeightedCluster weighted_clusters = 10; + } + + // Optional endpoint metadata match criteria. Only endpoints in the upstream + // cluster with metadata matching that set in metadata_match will be + // considered. The filter name should be specified as *envoy.lb*. + envoy.api.v3alpha.core.Metadata metadata_match = 9; + + // The idle timeout for connections managed by the TCP proxy filter. The idle timeout + // is defined as the period in which there are no bytes sent or received on either + // the upstream or downstream connection. If not set, connections will never be closed + // by the TCP proxy due to being idle. + google.protobuf.Duration idle_timeout = 8 [(validate.rules).duration.gt = {}]; + + // [#not-implemented-hide:] The idle timeout for connections managed by the TCP proxy + // filter. The idle timeout is defined as the period in which there is no + // active traffic. If not set, there is no idle timeout. When the idle timeout + // is reached the connection will be closed. The distinction between + // downstream_idle_timeout/upstream_idle_timeout provides a means to set + // timeout based on the last byte sent on the downstream/upstream connection. + google.protobuf.Duration downstream_idle_timeout = 3; + + // [#not-implemented-hide:] + google.protobuf.Duration upstream_idle_timeout = 4; + + // Configuration for :ref:`access logs ` + // emitted by the this tcp_proxy. + repeated envoy.config.filter.accesslog.v3alpha.AccessLog access_log = 5; + + // [#not-implemented-hide:] Deprecated. + // TCP Proxy filter configuration using V1 format. + message DeprecatedV1 { + // A TCP proxy route consists of a set of optional L4 criteria and the + // name of a cluster. If a downstream connection matches all the + // specified criteria, the cluster in the route is used for the + // corresponding upstream connection. Routes are tried in the order + // specified until a match is found. If no match is found, the connection + // is closed. A route with no criteria is valid and always produces a + // match. + message TCPRoute { + // The cluster to connect to when a the downstream network connection + // matches the specified criteria. + string cluster = 1 [(validate.rules).string.min_bytes = 1]; + + // An optional list of IP address subnets in the form + // “ip_address/xx”. The criteria is satisfied if the destination IP + // address of the downstream connection is contained in at least one of + // the specified subnets. If the parameter is not specified or the list + // is empty, the destination IP address is ignored. The destination IP + // address of the downstream connection might be different from the + // addresses on which the proxy is listening if the connection has been + // redirected. + repeated envoy.api.v3alpha.core.CidrRange destination_ip_list = 2; + + // An optional string containing a comma-separated list of port numbers + // or ranges. The criteria is satisfied if the destination port of the + // downstream connection is contained in at least one of the specified + // ranges. If the parameter is not specified, the destination port is + // ignored. The destination port address of the downstream connection + // might be different from the port on which the proxy is listening if + // the connection has been redirected. + string destination_ports = 3; + + // An optional list of IP address subnets in the form + // “ip_address/xx”. The criteria is satisfied if the source IP address + // of the downstream connection is contained in at least one of the + // specified subnets. If the parameter is not specified or the list is + // empty, the source IP address is ignored. + repeated envoy.api.v3alpha.core.CidrRange source_ip_list = 4; + + // An optional string containing a comma-separated list of port numbers + // or ranges. The criteria is satisfied if the source port of the + // downstream connection is contained in at least one of the specified + // ranges. If the parameter is not specified, the source port is + // ignored. + string source_ports = 5; + } + + // The route table for the filter. All filter instances must have a route + // table, even if it is empty. + repeated TCPRoute routes = 1 [(validate.rules).repeated .min_items = 1]; + } + + // [#not-implemented-hide:] Deprecated. + DeprecatedV1 deprecated_v1 = 6 [deprecated = true]; + + // The maximum number of unsuccessful connection attempts that will be made before + // giving up. If the parameter is not specified, 1 connection attempt will be made. + google.protobuf.UInt32Value max_connect_attempts = 7 [(validate.rules).uint32.gte = 1]; + + // Allows for specification of multiple upstream clusters along with weights + // that indicate the percentage of traffic to be forwarded to each cluster. + // The router selects an upstream cluster based on these weights. + message WeightedCluster { + message ClusterWeight { + // Name of the upstream cluster. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // When a request matches the route, the choice of an upstream cluster is + // determined by its weight. The sum of weights across all entries in the + // clusters array determines the total weight. + uint32 weight = 2 [(validate.rules).uint32.gte = 1]; + } + + // Specifies one or more upstream clusters associated with the route. + repeated ClusterWeight clusters = 1 [(validate.rules).repeated .min_items = 1]; + } +} diff --git a/api/envoy/config/filter/network/thrift_proxy/v2alpha1/BUILD b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/BUILD index f758f7f580..28a64a0a32 100644 --- a/api/envoy/config/filter/network/thrift_proxy/v2alpha1/BUILD +++ b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/api/v2/route:pkg", + ], +) + api_proto_library_internal( name = "thrift_proxy", srcs = [ diff --git a/api/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto index dcd83f2f1a..33d1200471 100644 --- a/api/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto +++ b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.thrift_proxy.v2alpha1; option java_outer_classname = "RouteProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.thrift_proxy.v2alpha1"; -option go_package = "v2"; import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/route/route.proto"; @@ -13,7 +12,6 @@ import "envoy/api/v2/route/route.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Thrift Proxy Route Configuration] // Thrift Proxy :ref:`configuration overview `. diff --git a/api/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto index 0be6c33703..823a174752 100644 --- a/api/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto +++ b/api/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.thrift_proxy.v2alpha1; option java_outer_classname = "ThriftProxyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.thrift_proxy.v2alpha1"; -option go_package = "v2"; import "envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto"; @@ -13,7 +12,6 @@ import "google/protobuf/any.proto"; import "google/protobuf/struct.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Thrift Proxy] // Thrift Proxy :ref:`configuration overview `. @@ -43,7 +41,6 @@ message ThriftProxy { // Thrift transport types supported by Envoy. enum TransportType { - option (gogoproto.goproto_enum_prefix) = false; // For downstream connections, the Thrift proxy will attempt to determine which transport to use. // For upstream connections, the Thrift proxy will use same transport as the downstream @@ -62,7 +59,6 @@ enum TransportType { // Thrift Protocol types supported by Envoy. enum ProtocolType { - option (gogoproto.goproto_enum_prefix) = false; // For downstream connections, the Thrift proxy will attempt to determine which protocol to use. // Note that the older, non-strict (or lax) binary protocol is not included in automatic protocol diff --git a/api/envoy/config/filter/network/thrift_proxy/v3alpha/BUILD b/api/envoy/config/filter/network/thrift_proxy/v3alpha/BUILD new file mode 100644 index 0000000000..34a2c397cc --- /dev/null +++ b/api/envoy/config/filter/network/thrift_proxy/v3alpha/BUILD @@ -0,0 +1,22 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/route:pkg", + ], +) + +api_proto_library_internal( + name = "thrift_proxy", + srcs = [ + "route.proto", + "thrift_proxy.proto", + ], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/route", + ], +) diff --git a/api/envoy/config/filter/network/thrift_proxy/v3alpha/README.md b/api/envoy/config/filter/network/thrift_proxy/v3alpha/README.md new file mode 100644 index 0000000000..a7d95c0d47 --- /dev/null +++ b/api/envoy/config/filter/network/thrift_proxy/v3alpha/README.md @@ -0,0 +1 @@ +Protocol buffer definitions for the Thrift proxy. diff --git a/api/envoy/config/filter/network/thrift_proxy/v3alpha/route.proto b/api/envoy/config/filter/network/thrift_proxy/v3alpha/route.proto new file mode 100644 index 0000000000..3a351b1449 --- /dev/null +++ b/api/envoy/config/filter/network/thrift_proxy/v3alpha/route.proto @@ -0,0 +1,128 @@ +syntax = "proto3"; + +package envoy.config.filter.network.thrift_proxy.v3alpha; + +option java_outer_classname = "RouteProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.thrift_proxy.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/route/route.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Thrift Proxy Route Configuration] +// Thrift Proxy :ref:`configuration overview `. + +// [#comment:next free field: 3] +message RouteConfiguration { + // The name of the route configuration. Reserved for future use in asynchronous route discovery. + string name = 1; + + // The list of routes that will be matched, in order, against incoming requests. The first route + // that matches will be used. + repeated Route routes = 2; +} + +// [#comment:next free field: 3] +message Route { + // Route matching parameters. + RouteMatch match = 1 [(validate.rules).message.required = true]; + + // Route request to some upstream cluster. + RouteAction route = 2 [(validate.rules).message.required = true]; +} + +// [#comment:next free field: 5] +message RouteMatch { + oneof match_specifier { + option (validate.required) = true; + + // If specified, the route must exactly match the request method name. As a special case, an + // empty string matches any request method name. + string method_name = 1; + + // If specified, the route must have the service name as the request method name prefix. As a + // special case, an empty string matches any service name. Only relevant when service + // multiplexing. + string service_name = 2; + } + + // Inverts whatever matching is done in the :ref:`method_name + // ` or + // :ref:`service_name + // ` fields. + // Cannot be combined with wildcard matching as that would result in routes never being matched. + // + // .. note:: + // + // This does not invert matching done as part of the :ref:`headers field + // ` field. To + // invert header matching, see :ref:`invert_match + // `. + bool invert = 3; + + // Specifies a set of headers that the route should match on. The router will check the request’s + // headers against all the specified headers in the route config. A match will happen if all the + // headers in the route are present in the request with the same values (or based on presence if + // the value field is not in the config). Note that this only applies for Thrift transports and/or + // protocols that support headers. + repeated envoy.api.v3alpha.route.HeaderMatcher headers = 4; +} + +// [#comment:next free field: 5] +message RouteAction { + oneof cluster_specifier { + option (validate.required) = true; + + // Indicates a single upstream cluster to which the request should be routed + // to. + string cluster = 1 [(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 + // assigned to each cluster. + WeightedCluster weighted_clusters = 2; + } + + // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in + // the upstream cluster with metadata matching what is set in this field will be considered. + // Note that this will be merged with what's provided in :ref: `WeightedCluster.MetadataMatch + // `, + // with values there taking precedence. Keys and values should be provided under the "envoy.lb" + // metadata key. + envoy.api.v3alpha.core.Metadata metadata_match = 3; + + // Specifies a set of rate limit configurations that could be applied to the route. + // N.B. Thrift service or method name matching can be achieved by specifying a RequestHeaders + // action with the header name ":method-name". + repeated envoy.api.v3alpha.route.RateLimit rate_limits = 4; +} + +// Allows for specification of multiple upstream clusters along with weights that indicate the +// percentage of traffic to be forwarded to each cluster. The router selects an upstream cluster +// based on these weights. +message WeightedCluster { + message ClusterWeight { + // Name of the upstream cluster. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // When a request matches the route, the choice of an upstream cluster is determined by its + // weight. The sum of weights across all entries in the clusters array determines the total + // weight. + google.protobuf.UInt32Value weight = 2 [(validate.rules).uint32.gte = 1]; + + // Optional endpoint metadata match criteria used by the subset load balancer. Only endpoints in + // the upstream cluster with metadata matching what is set in this field, combined with what's + // provided in :ref: `RouteAction's metadata_match + // `, + // will be considered. Values here will take precedence. Keys and values should be provided + // under the "envoy.lb" metadata key. + envoy.api.v3alpha.core.Metadata metadata_match = 3; + } + + // Specifies one or more upstream clusters associated with the route. + repeated ClusterWeight clusters = 1 [(validate.rules).repeated .min_items = 1]; +} diff --git a/api/envoy/config/filter/network/thrift_proxy/v3alpha/thrift_proxy.proto b/api/envoy/config/filter/network/thrift_proxy/v3alpha/thrift_proxy.proto new file mode 100644 index 0000000000..83f44bbf72 --- /dev/null +++ b/api/envoy/config/filter/network/thrift_proxy/v3alpha/thrift_proxy.proto @@ -0,0 +1,118 @@ +syntax = "proto3"; + +package envoy.config.filter.network.thrift_proxy.v3alpha; + +option java_outer_classname = "ThriftProxyProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.network.thrift_proxy.v3alpha"; + +import "envoy/config/filter/network/thrift_proxy/v3alpha/route.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Thrift Proxy] +// Thrift Proxy :ref:`configuration overview `. + +// [#comment:next free field: 6] +message ThriftProxy { + // Supplies the type of transport that the Thrift proxy should use. Defaults to + // :ref:`AUTO_TRANSPORT`. + TransportType transport = 2 [(validate.rules).enum.defined_only = true]; + + // Supplies the type of protocol that the Thrift proxy should use. Defaults to + // :ref:`AUTO_PROTOCOL`. + ProtocolType protocol = 3 [(validate.rules).enum.defined_only = true]; + + // The human readable prefix to use when emitting statistics. + string stat_prefix = 1 [(validate.rules).string.min_bytes = 1]; + + // The route table for the connection manager is static and is specified in this property. + RouteConfiguration route_config = 4; + + // A list of individual Thrift filters that make up the filter chain for requests made to the + // Thrift proxy. Order matters as the filters are processed sequentially. For backwards + // compatibility, if no thrift_filters are specified, a default Thrift router filter + // (`envoy.filters.thrift.router`) is used. + repeated ThriftFilter thrift_filters = 5; +} + +// Thrift transport types supported by Envoy. +enum TransportType { + + // For downstream connections, the Thrift proxy will attempt to determine which transport to use. + // For upstream connections, the Thrift proxy will use same transport as the downstream + // connection. + AUTO_TRANSPORT = 0; + + // The Thrift proxy will use the Thrift framed transport. + FRAMED = 1; + + // The Thrift proxy will use the Thrift unframed transport. + UNFRAMED = 2; + + // The Thrift proxy will assume the client is using the Thrift header transport. + HEADER = 3; +} + +// Thrift Protocol types supported by Envoy. +enum ProtocolType { + + // For downstream connections, the Thrift proxy will attempt to determine which protocol to use. + // Note that the older, non-strict (or lax) binary protocol is not included in automatic protocol + // detection. For upstream connections, the Thrift proxy will use the same protocol as the + // downstream connection. + AUTO_PROTOCOL = 0; + + // The Thrift proxy will use the Thrift binary protocol. + BINARY = 1; + + // The Thrift proxy will use Thrift non-strict binary protocol. + LAX_BINARY = 2; + + // The Thrift proxy will use the Thrift compact protocol. + COMPACT = 3; + + // The Thrift proxy will use the Thrift "Twitter" protocol implemented by the finagle library. + TWITTER = 4; +} + +// ThriftFilter configures a Thrift filter. +// [#comment:next free field: 3] +message ThriftFilter { + // The name of the filter to instantiate. The name must match a supported + // filter. The built-in filters are: + // + // [#comment:TODO(zuercher): Auto generate the following list] + // * :ref:`envoy.filters.thrift.router ` + // * :ref:`envoy.filters.thrift.rate_limit ` + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Filter specific configuration which depends on the filter being instantiated. See the supported + // filters for further documentation. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} + +// ThriftProtocolOptions specifies Thrift upstream protocol options. This object is used in +// in :ref:`extension_protocol_options`, keyed +// by the name `envoy.filters.network.thrift_proxy`. +// [#comment:next free field: 3] +message ThriftProtocolOptions { + // Supplies the type of transport that the Thrift proxy should use for upstream connections. + // Selecting + // :ref:`AUTO_TRANSPORT`, + // which is the default, causes the proxy to use the same transport as the downstream connection. + TransportType transport = 1 [(validate.rules).enum.defined_only = true]; + + // Supplies the type of protocol that the Thrift proxy should use for upstream connections. + // Selecting + // :ref:`AUTO_PROTOCOL`, + // which is the default, causes the proxy to use the same protocol as the downstream connection. + ProtocolType protocol = 2 [(validate.rules).enum.defined_only = true]; +} diff --git a/api/envoy/config/filter/network/zookeeper_proxy/v1alpha1/BUILD b/api/envoy/config/filter/network/zookeeper_proxy/v1alpha1/BUILD index 8719f5083f..02594c24b8 100644 --- a/api/envoy/config/filter/network/zookeeper_proxy/v1alpha1/BUILD +++ b/api/envoy/config/filter/network/zookeeper_proxy/v1alpha1/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "zookeeper_proxy", srcs = ["zookeeper_proxy.proto"], diff --git a/api/envoy/config/filter/network/zookeeper_proxy/v1alpha1/zookeeper_proxy.proto b/api/envoy/config/filter/network/zookeeper_proxy/v1alpha1/zookeeper_proxy.proto index 6a8afdd12e..72d09810ff 100644 --- a/api/envoy/config/filter/network/zookeeper_proxy/v1alpha1/zookeeper_proxy.proto +++ b/api/envoy/config/filter/network/zookeeper_proxy/v1alpha1/zookeeper_proxy.proto @@ -5,7 +5,6 @@ package envoy.config.filter.network.zookeeper_proxy.v1alpha1; option java_outer_classname = "ZookeeperProxyProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.network.zookeeper_proxy.v1alpha1"; -option go_package = "v1alpha1"; import "validate/validate.proto"; import "google/protobuf/wrappers.proto"; diff --git a/api/envoy/config/filter/thrift/rate_limit/v2alpha1/BUILD b/api/envoy/config/filter/thrift/rate_limit/v2alpha1/BUILD index 08d5db95b1..fcdcd0dfa5 100644 --- a/api/envoy/config/filter/thrift/rate_limit/v2alpha1/BUILD +++ b/api/envoy/config/filter/thrift/rate_limit/v2alpha1/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/ratelimit:pkg", + "//envoy/config/ratelimit/v2:pkg", + ], +) + api_proto_library_internal( name = "rate_limit", srcs = ["rate_limit.proto"], diff --git a/api/envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit.proto b/api/envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit.proto index 15a50d553f..ff2463b26c 100644 --- a/api/envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit.proto +++ b/api/envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit.proto @@ -5,14 +5,12 @@ package envoy.config.filter.thrift.rate_limit.v2alpha1; option java_outer_classname = "RateLimitProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.thrift.rate_limit.v2alpha1"; -option go_package = "v2alpha1"; import "envoy/config/ratelimit/v2/rls.proto"; import "google/protobuf/duration.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; // [#protodoc-title: Rate limit] // Rate limit :ref:`configuration overview `. @@ -35,7 +33,7 @@ message RateLimit { // The timeout in milliseconds for the rate limit service RPC. If not // set, this defaults to 20ms. - google.protobuf.Duration timeout = 3 [(gogoproto.stdduration) = true]; + google.protobuf.Duration timeout = 3; // 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 diff --git a/api/envoy/config/filter/thrift/rate_limit/v3alpha/BUILD b/api/envoy/config/filter/thrift/rate_limit/v3alpha/BUILD new file mode 100644 index 0000000000..a13183b9eb --- /dev/null +++ b/api/envoy/config/filter/thrift/rate_limit/v3alpha/BUILD @@ -0,0 +1,19 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/ratelimit:pkg", + "//envoy/config/ratelimit/v3alpha:pkg", + ], +) + +api_proto_library_internal( + name = "rate_limit", + srcs = ["rate_limit.proto"], + deps = [ + "//envoy/api/v3alpha/ratelimit", + "//envoy/config/ratelimit/v3alpha:rls", + ], +) diff --git a/api/envoy/config/filter/thrift/rate_limit/v3alpha/rate_limit.proto b/api/envoy/config/filter/thrift/rate_limit/v3alpha/rate_limit.proto new file mode 100644 index 0000000000..017d9546a9 --- /dev/null +++ b/api/envoy/config/filter/thrift/rate_limit/v3alpha/rate_limit.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package envoy.config.filter.thrift.rate_limit.v3alpha; + +option java_outer_classname = "RateLimitProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.thrift.rate_limit.v3alpha"; + +import "envoy/config/ratelimit/v3alpha/rls.proto"; + +import "google/protobuf/duration.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Rate limit] +// Rate limit :ref:`configuration overview `. + +// [#comment:next free field: 5] +message RateLimit { + // The rate limit domain to use in the rate limit service request. + string domain = 1 [(validate.rules).string.min_bytes = 1]; + + // Specifies the rate limit configuration stage. Each configured rate limit filter performs a + // rate limit check using descriptors configured in the + // :ref:`envoy_api_msg_config.filter.network.thrift_proxy.v3alpha.RouteAction` for the request. + // Only those entries with a matching stage number are used for a given filter. If not set, the + // default stage number is 0. + // + // .. note:: + // + // The filter supports a range of 0 - 10 inclusively for stage numbers. + uint32 stage = 2 [(validate.rules).uint32.lte = 10]; + + // The timeout in milliseconds for the rate limit service RPC. If not + // set, this defaults to 20ms. + google.protobuf.Duration timeout = 3; + + // 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 = 4; + + // Configuration for an external rate limit service provider. If not + // specified, any calls to the rate limit service will immediately return + // success. + envoy.config.ratelimit.v3alpha.RateLimitServiceConfig rate_limit_service = 5 + [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/filter/thrift/router/v2alpha1/BUILD b/api/envoy/config/filter/thrift/router/v2alpha1/BUILD index 51c69c0d5b..68bd8c126b 100644 --- a/api/envoy/config/filter/thrift/router/v2alpha1/BUILD +++ b/api/envoy/config/filter/thrift/router/v2alpha1/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "router", srcs = ["router.proto"], diff --git a/api/envoy/config/filter/thrift/router/v2alpha1/router.proto b/api/envoy/config/filter/thrift/router/v2alpha1/router.proto index c515752c2a..9c9383caf3 100644 --- a/api/envoy/config/filter/thrift/router/v2alpha1/router.proto +++ b/api/envoy/config/filter/thrift/router/v2alpha1/router.proto @@ -5,7 +5,6 @@ package envoy.config.filter.thrift.router.v2alpha1; option java_outer_classname = "RouterProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.filter.thrift.router.v2alpha1"; -option go_package = "v2alpha1"; // [#protodoc-title: Router] // Thrift router :ref:`configuration overview `. diff --git a/api/envoy/config/filter/thrift/router/v3alpha/BUILD b/api/envoy/config/filter/thrift/router/v3alpha/BUILD new file mode 100644 index 0000000000..68bd8c126b --- /dev/null +++ b/api/envoy/config/filter/thrift/router/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "router", + srcs = ["router.proto"], +) diff --git a/api/envoy/config/filter/thrift/router/v3alpha/router.proto b/api/envoy/config/filter/thrift/router/v3alpha/router.proto new file mode 100644 index 0000000000..9fe86566a4 --- /dev/null +++ b/api/envoy/config/filter/thrift/router/v3alpha/router.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package envoy.config.filter.thrift.router.v3alpha; + +option java_outer_classname = "RouterProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.filter.thrift.router.v3alpha"; + +// [#protodoc-title: Router] +// Thrift router :ref:`configuration overview `. + +message Router { +} diff --git a/api/envoy/config/grpc_credential/v2alpha/BUILD b/api/envoy/config/grpc_credential/v2alpha/BUILD index f299179ecb..484aa5680d 100644 --- a/api/envoy/config/grpc_credential/v2alpha/BUILD +++ b/api/envoy/config/grpc_credential/v2alpha/BUILD @@ -1,6 +1,10 @@ licenses(["notice"]) # Apache 2 -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +api_proto_package( + deps = ["//envoy/api/v2/core"], +) api_proto_library_internal( name = "file_based_metadata", @@ -8,20 +12,7 @@ api_proto_library_internal( deps = ["//envoy/api/v2/core:base"], ) -api_go_proto_library( - name = "file_based_metadata", - proto = ":file_based_metadata", - deps = [ - "//envoy/api/v2/core:base_go_proto", - ], -) - api_proto_library_internal( name = "aws_iam", srcs = ["aws_iam.proto"], ) - -api_go_proto_library( - name = "aws_iam", - proto = ":aws_iam", -) diff --git a/api/envoy/config/grpc_credential/v2alpha/aws_iam.proto b/api/envoy/config/grpc_credential/v2alpha/aws_iam.proto index 3689b80611..e7a7bf94cc 100644 --- a/api/envoy/config/grpc_credential/v2alpha/aws_iam.proto +++ b/api/envoy/config/grpc_credential/v2alpha/aws_iam.proto @@ -8,7 +8,6 @@ package envoy.config.grpc_credential.v2alpha; option java_outer_classname = "AwsIamProto"; option java_package = "io.envoyproxy.envoy.config.grpc_credential.v2alpha"; option java_multiple_files = true; -option go_package = "v2alpha"; import "validate/validate.proto"; diff --git a/api/envoy/config/grpc_credential/v2alpha/file_based_metadata.proto b/api/envoy/config/grpc_credential/v2alpha/file_based_metadata.proto index c91c50e39a..1746492fe2 100644 --- a/api/envoy/config/grpc_credential/v2alpha/file_based_metadata.proto +++ b/api/envoy/config/grpc_credential/v2alpha/file_based_metadata.proto @@ -8,7 +8,6 @@ package envoy.config.grpc_credential.v2alpha; option java_outer_classname = "FileBasedMetadataProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.grpc_credential.v2alpha"; -option go_package = "v2alpha"; import "envoy/api/v2/core/base.proto"; diff --git a/api/envoy/config/grpc_credential/v3alpha/BUILD b/api/envoy/config/grpc_credential/v3alpha/BUILD new file mode 100644 index 0000000000..7c327f91f0 --- /dev/null +++ b/api/envoy/config/grpc_credential/v3alpha/BUILD @@ -0,0 +1,18 @@ +licenses(["notice"]) # Apache 2 + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library_internal( + name = "file_based_metadata", + srcs = ["file_based_metadata.proto"], + deps = ["//envoy/api/v3alpha/core:base"], +) + +api_proto_library_internal( + name = "aws_iam", + srcs = ["aws_iam.proto"], +) diff --git a/api/envoy/config/grpc_credential/v3alpha/aws_iam.proto b/api/envoy/config/grpc_credential/v3alpha/aws_iam.proto new file mode 100644 index 0000000000..29c9cf140a --- /dev/null +++ b/api/envoy/config/grpc_credential/v3alpha/aws_iam.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +// [#protodoc-title: Grpc Credentials AWS IAM] +// Configuration for AWS IAM Grpc Credentials Plugin + +package envoy.config.grpc_credential.v3alpha; + +option java_outer_classname = "AwsIamProto"; +option java_package = "io.envoyproxy.envoy.config.grpc_credential.v3alpha"; +option java_multiple_files = true; + +import "validate/validate.proto"; + +message AwsIamConfig { + // The `service namespace + // `_ + // of the Grpc endpoint. + // + // Example: appmesh + string service_name = 1 [(validate.rules).string.min_bytes = 1]; + + // The `region `_ hosting the Grpc + // endpoint. If unspecified, the extension will use the value in the ``AWS_REGION`` environment + // variable. + // + // Example: us-west-2 + string region = 2; +} diff --git a/api/envoy/config/grpc_credential/v3alpha/file_based_metadata.proto b/api/envoy/config/grpc_credential/v3alpha/file_based_metadata.proto new file mode 100644 index 0000000000..9bab390cc8 --- /dev/null +++ b/api/envoy/config/grpc_credential/v3alpha/file_based_metadata.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +// [#protodoc-title: Grpc Credentials File Based Metadata] +// Configuration for File Based Metadata Grpc Credentials Plugin + +package envoy.config.grpc_credential.v3alpha; + +option java_outer_classname = "FileBasedMetadataProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.grpc_credential.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; + +message FileBasedMetadataConfig { + + // Location or inline data of secret to use for authentication of the Google gRPC connection + // this secret will be attached to a header of the gRPC connection + envoy.api.v3alpha.core.DataSource secret_data = 1; + + // Metadata header key to use for sending the secret data + // if no header key is set, "authorization" header will be used + string header_key = 2; + + // Prefix to prepend to the secret in the metadata header + // if no prefix is set, the default is to use no prefix + string header_prefix = 3; +} diff --git a/api/envoy/config/health_checker/redis/v2/BUILD b/api/envoy/config/health_checker/redis/v2/BUILD index 239d1f224f..f7b289b08f 100644 --- a/api/envoy/config/health_checker/redis/v2/BUILD +++ b/api/envoy/config/health_checker/redis/v2/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "redis", srcs = ["redis.proto"], diff --git a/api/envoy/config/health_checker/redis/v2/redis.proto b/api/envoy/config/health_checker/redis/v2/redis.proto index 130454b5d4..8ab2de269a 100644 --- a/api/envoy/config/health_checker/redis/v2/redis.proto +++ b/api/envoy/config/health_checker/redis/v2/redis.proto @@ -5,7 +5,6 @@ package envoy.config.health_checker.redis.v2; option java_outer_classname = "RedisProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.health_checker.redis.v2"; -option go_package = "v2"; // [#protodoc-title: Redis] // Redis health checker :ref:`configuration overview `. diff --git a/api/envoy/config/health_checker/redis/v3alpha/BUILD b/api/envoy/config/health_checker/redis/v3alpha/BUILD new file mode 100644 index 0000000000..f7b289b08f --- /dev/null +++ b/api/envoy/config/health_checker/redis/v3alpha/BUILD @@ -0,0 +1,10 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "redis", + srcs = ["redis.proto"], +) diff --git a/api/envoy/config/health_checker/redis/v3alpha/redis.proto b/api/envoy/config/health_checker/redis/v3alpha/redis.proto new file mode 100644 index 0000000000..1409e9545f --- /dev/null +++ b/api/envoy/config/health_checker/redis/v3alpha/redis.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package envoy.config.health_checker.redis.v3alpha; + +option java_outer_classname = "RedisProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.health_checker.redis.v3alpha"; + +// [#protodoc-title: Redis] +// Redis health checker :ref:`configuration overview `. + +message Redis { + // If set, optionally perform ``EXISTS `` instead of ``PING``. A return value + // from Redis of 0 (does not exist) is considered a passing healthcheck. A return value other + // than 0 is considered a failure. This allows the user to mark a Redis instance for maintenance + // by setting the specified key to any value and waiting for traffic to drain. + string key = 1; +} diff --git a/api/envoy/config/metrics/v2/BUILD b/api/envoy/config/metrics/v2/BUILD index 157b09c4d8..13ac8bdd99 100644 --- a/api/envoy/config/metrics/v2/BUILD +++ b/api/envoy/config/metrics/v2/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/type/matcher", + ], +) + api_proto_library_internal( name = "metrics_service", srcs = ["metrics_service.proto"], @@ -13,14 +20,6 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "metrics_service", - proto = ":metrics_service", - deps = [ - "//envoy/api/v2/core:grpc_service_go_proto", - ], -) - api_proto_library_internal( name = "stats", srcs = ["stats.proto"], @@ -32,12 +31,3 @@ api_proto_library_internal( "//envoy/type/matcher:string", ], ) - -api_go_proto_library( - name = "stats", - proto = ":stats", - deps = [ - "//envoy/api/v2/core:address_go_proto", - "//envoy/type/matcher:string_go_proto", - ], -) diff --git a/api/envoy/config/metrics/v2/stats.proto b/api/envoy/config/metrics/v2/stats.proto index 08172180b5..fea8b9b0f8 100644 --- a/api/envoy/config/metrics/v2/stats.proto +++ b/api/envoy/config/metrics/v2/stats.proto @@ -8,7 +8,6 @@ package envoy.config.metrics.v2; option java_outer_classname = "StatsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.metrics.v2"; -option go_package = "v2"; import "envoy/api/v2/core/address.proto"; import "envoy/type/matcher/string.proto"; diff --git a/api/envoy/config/metrics/v3alpha/BUILD b/api/envoy/config/metrics/v3alpha/BUILD new file mode 100644 index 0000000000..399ec44420 --- /dev/null +++ b/api/envoy/config/metrics/v3alpha/BUILD @@ -0,0 +1,33 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/type/matcher", + ], +) + +api_proto_library_internal( + name = "metrics_service", + srcs = ["metrics_service.proto"], + visibility = [ + "//envoy/config/bootstrap/v3alpha:__pkg__", + ], + deps = [ + "//envoy/api/v3alpha/core:grpc_service", + ], +) + +api_proto_library_internal( + name = "stats", + srcs = ["stats.proto"], + visibility = [ + "//envoy/config/bootstrap/v3alpha:__pkg__", + ], + deps = [ + "//envoy/api/v3alpha/core:address", + "//envoy/type/matcher:string", + ], +) diff --git a/api/envoy/config/metrics/v3alpha/metrics_service.proto b/api/envoy/config/metrics/v3alpha/metrics_service.proto new file mode 100644 index 0000000000..392ceb8d6f --- /dev/null +++ b/api/envoy/config/metrics/v3alpha/metrics_service.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +// [#protodoc-title: Metrics service] + +package envoy.config.metrics.v3alpha; + +option java_outer_classname = "MetricsServiceProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.metrics.v3alpha"; + +import "envoy/api/v3alpha/core/grpc_service.proto"; + +import "validate/validate.proto"; + +// Metrics Service is configured as a built-in *envoy.metrics_service* :ref:`StatsSink +// `. This opaque configuration will be used to +// create Metrics Service. +message MetricsServiceConfig { + // The upstream gRPC cluster that hosts the metrics service. + envoy.api.v3alpha.core.GrpcService grpc_service = 1 [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/metrics/v3alpha/stats.proto b/api/envoy/config/metrics/v3alpha/stats.proto new file mode 100644 index 0000000000..afa4468b34 --- /dev/null +++ b/api/envoy/config/metrics/v3alpha/stats.proto @@ -0,0 +1,330 @@ +// [#protodoc-title: Stats] +// Statistics :ref:`architecture overview `. + +syntax = "proto3"; + +package envoy.config.metrics.v3alpha; + +option java_outer_classname = "StatsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.metrics.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/type/matcher/string.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// Configuration for pluggable stats sinks. +message StatsSink { + // The name of the stats sink to instantiate. The name must match a supported + // stats sink. The built-in stats sinks are: + // + // * :ref:`envoy.statsd ` + // * :ref:`envoy.dog_statsd ` + // * :ref:`envoy.metrics_service ` + // * :ref:`envoy.stat_sinks.hystrix ` + // + // Sinks optionally support tagged/multiple dimensional metrics. + string name = 1; + + // Stats sink specific configuration which depends on the sink being instantiated. See + // :ref:`StatsdSink ` for an example. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} + +// Statistics configuration such as tagging. +message StatsConfig { + // Each stat name is iteratively processed through these tag specifiers. + // When a tag is matched, the first capture group is removed from the name so + // later :ref:`TagSpecifiers ` cannot match + // that same portion of the match. + repeated TagSpecifier stats_tags = 1; + + // Use all default tag regexes specified in Envoy. These can be combined with + // custom tags specified in :ref:`stats_tags + // `. They will be processed before + // the custom tags. + // + // .. note:: + // + // If any default tags are specified twice, the config will be considered + // invalid. + // + // See :repo:`well_known_names.h ` for a list of the + // default tags in Envoy. + // + // If not provided, the value is assumed to be true. + google.protobuf.BoolValue use_all_default_tags = 2; + + // Inclusion/exclusion matcher for stat name creation. If not provided, all stats are instantiated + // as normal. Preventing the instantiation of certain families of stats can improve memory + // performance for Envoys running especially large configs. + StatsMatcher stats_matcher = 3; +} + +// Configuration for disabling stat instantiation. +message StatsMatcher { + // The instantiation of stats is unrestricted by default. If the goal is to configure Envoy to + // instantiate all stats, there is no need to construct a StatsMatcher. + // + // However, StatsMatcher can be used to limit the creation of families of stats in order to + // conserve memory. Stats can either be disabled entirely, or they can be + // limited by either an exclusion or an inclusion list of :ref:`StringMatcher + // ` protos: + // + // * If `reject_all` is set to `true`, no stats will be instantiated. If `reject_all` is set to + // `false`, all stats will be instantiated. + // + // * If an exclusion list is supplied, any stat name matching *any* of the StringMatchers in the + // list will not instantiate. + // + // * If an inclusion list is supplied, no stats will instantiate, except those matching *any* of + // the StringMatchers in the list. + // + // + // A StringMatcher can be used to match against an exact string, a suffix / prefix, or a regex. + // **NB:** For performance reasons, it is highly recommended to use a prefix- or suffix-based + // matcher rather than a regex-based matcher. + // + // Example 1. Excluding all stats. + // + // .. code-block:: json + // + // { + // "statsMatcher": { + // "rejectAll": "true" + // } + // } + // + // Example 2. Excluding all cluster-specific stats, but not cluster-manager stats: + // + // .. code-block:: json + // + // { + // "statsMatcher": { + // "exclusionList": { + // "patterns": [ + // { + // "prefix": "cluster." + // } + // ] + // } + // } + // } + // + // Example 3. Including only manager-related stats: + // + // .. code-block:: json + // + // { + // "statsMatcher": { + // "inclusionList": { + // "patterns": [ + // { + // "prefix": "cluster_manager." + // }, + // { + // "prefix": "listener_manager." + // } + // ] + // } + // } + // } + // + + oneof stats_matcher { + option (validate.required) = true; + + // If `reject_all` is true, then all stats are disabled. If `reject_all` is false, then all + // stats are enabled. + bool reject_all = 1; + + // Exclusive match. All stats are enabled except for those matching one of the supplied + // StringMatcher protos. + envoy.type.matcher.ListStringMatcher exclusion_list = 2; + + // Inclusive match. No stats are enabled except for those matching one of the supplied + // StringMatcher protos. + envoy.type.matcher.ListStringMatcher inclusion_list = 3; + }; +} + +// Designates a tag name and value pair. The value may be either a fixed value +// or a regex providing the value via capture groups. The specified tag will be +// unconditionally set if a fixed value, otherwise it will only be set if one +// or more capture groups in the regex match. +message TagSpecifier { + // Attaches an identifier to the tag values to identify the tag being in the + // sink. Envoy has a set of default names and regexes to extract dynamic + // portions of existing stats, which can be found in :repo:`well_known_names.h + // ` in the Envoy repository. If a :ref:`tag_name + // ` is provided in the config and + // neither :ref:`regex ` or + // :ref:`fixed_value ` were + // specified, Envoy will attempt to find that name in its set of defaults and use the accompanying + // regex. + // + // .. note:: + // + // It is invalid to specify the same tag name twice in a config. + string tag_name = 1; + + oneof tag_value { + // Designates a tag to strip from the tag extracted name and provide as a named + // tag value for all statistics. This will only occur if any part of the name + // matches the regex provided with one or more capture groups. + // + // The first capture group identifies the portion of the name to remove. The + // second capture group (which will normally be nested inside the first) will + // designate the value of the tag for the statistic. If no second capture + // group is provided, the first will also be used to set the value of the tag. + // All other capture groups will be ignored. + // + // Example 1. a stat name ``cluster.foo_cluster.upstream_rq_timeout`` and + // one tag specifier: + // + // .. code-block:: json + // + // { + // "tag_name": "envoy.cluster_name", + // "regex": "^cluster\.((.+?)\.)" + // } + // + // Note that the regex will remove ``foo_cluster.`` making the tag extracted + // name ``cluster.upstream_rq_timeout`` and the tag value for + // ``envoy.cluster_name`` will be ``foo_cluster`` (note: there will be no + // ``.`` character because of the second capture group). + // + // Example 2. a stat name + // ``http.connection_manager_1.user_agent.ios.downstream_cx_total`` and two + // tag specifiers: + // + // .. code-block:: json + // + // [ + // { + // "tag_name": "envoy.http_user_agent", + // "regex": "^http(?=\.).*?\.user_agent\.((.+?)\.)\w+?$" + // }, + // { + // "tag_name": "envoy.http_conn_manager_prefix", + // "regex": "^http\.((.*?)\.)" + // } + // ] + // + // The two regexes of the specifiers will be processed in the definition order. + // + // The first regex will remove ``ios.``, leaving the tag extracted name + // ``http.connection_manager_1.user_agent.downstream_cx_total``. The tag + // ``envoy.http_user_agent`` will be added with tag value ``ios``. + // + // The second regex will remove ``connection_manager_1.`` from the tag + // extracted name produced by the first regex + // ``http.connection_manager_1.user_agent.downstream_cx_total``, leaving + // ``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 [(validate.rules).string.max_bytes = 1024]; + + // Specifies a fixed tag value for the ``tag_name``. + string fixed_value = 3; + } +} + +// Stats configuration proto schema for built-in *envoy.statsd* sink. This sink does not support +// tagged metrics. +message StatsdSink { + oneof statsd_specifier { + option (validate.required) = true; + + // The UDP address of a running `statsd `_ + // compliant listener. If specified, statistics will be flushed to this + // address. + envoy.api.v3alpha.core.Address address = 1; + + // The name of a cluster that is running a TCP `statsd + // `_ compliant listener. If specified, + // Envoy will connect to this cluster to flush statistics. + string tcp_cluster_name = 2; + } + // Optional custom prefix for StatsdSink. If + // specified, this will override the default prefix. + // For example: + // + // .. code-block:: json + // + // { + // "prefix" : "envoy-prod" + // } + // + // will change emitted stats to + // + // .. code-block:: cpp + // + // envoy-prod.test_counter:1|c + // envoy-prod.test_timer:5|ms + // + // Note that the default prefix, "envoy", will be used if a prefix is not + // specified. + // + // Stats with default prefix: + // + // .. code-block:: cpp + // + // envoy.test_counter:1|c + // envoy.test_timer:5|ms + string prefix = 3; +} + +// Stats configuration proto schema for built-in *envoy.dog_statsd* sink. +// The sink emits stats with `DogStatsD `_ +// compatible tags. Tags are configurable via :ref:`StatsConfig +// `. +// [#comment:next free field: 3] +message DogStatsdSink { + oneof dog_statsd_specifier { + option (validate.required) = true; + + // The UDP address of a running DogStatsD compliant listener. If specified, + // statistics will be flushed to this address. + envoy.api.v3alpha.core.Address address = 1; + } + + reserved 2; + + // Optional custom metric name prefix. See :ref:`StatsdSink's prefix field + // ` for more details. + string prefix = 3; +} + +// Stats configuration proto schema for built-in *envoy.stat_sinks.hystrix* sink. +// The sink emits stats in `text/event-stream +// `_ +// formatted stream for use by `Hystrix dashboard +// `_. +// +// Note that only a single HystrixSink should be configured. +// +// Streaming is started through an admin endpoint :http:get:`/hystrix_event_stream`. +message HystrixSink { + // The number of buckets the rolling statistical window is divided into. + // + // Each time the sink is flushed, all relevant Envoy statistics are sampled and + // added to the rolling window (removing the oldest samples in the window + // in the process). The sink then outputs the aggregate statistics across the + // current rolling window to the event stream(s). + // + // rolling_window(ms) = stats_flush_interval(ms) * num_of_buckets + // + // More detailed explanation can be found in `Hystrix wiki + // `_. + int64 num_buckets = 1; +} diff --git a/api/envoy/config/overload/v2alpha/BUILD b/api/envoy/config/overload/v2alpha/BUILD index bfffb5639c..e247848d07 100644 --- a/api/envoy/config/overload/v2alpha/BUILD +++ b/api/envoy/config/overload/v2alpha/BUILD @@ -1,14 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "overload", srcs = ["overload.proto"], visibility = ["//visibility:public"], ) - -api_go_proto_library( - name = "overload", - proto = ":overload", -) diff --git a/api/envoy/config/overload/v2alpha/overload.proto b/api/envoy/config/overload/v2alpha/overload.proto index efdba5a09a..e32764675c 100644 --- a/api/envoy/config/overload/v2alpha/overload.proto +++ b/api/envoy/config/overload/v2alpha/overload.proto @@ -5,7 +5,6 @@ package envoy.config.overload.v2alpha; option java_outer_classname = "OverloadProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.overload.v2alpha"; -option go_package = "v2alpha"; import "google/protobuf/any.proto"; import "google/protobuf/duration.proto"; diff --git a/api/envoy/config/overload/v3alpha/BUILD b/api/envoy/config/overload/v3alpha/BUILD new file mode 100644 index 0000000000..e247848d07 --- /dev/null +++ b/api/envoy/config/overload/v3alpha/BUILD @@ -0,0 +1,11 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "overload", + srcs = ["overload.proto"], + visibility = ["//visibility:public"], +) diff --git a/api/envoy/config/overload/v3alpha/overload.proto b/api/envoy/config/overload/v3alpha/overload.proto new file mode 100644 index 0000000000..857b510e66 --- /dev/null +++ b/api/envoy/config/overload/v3alpha/overload.proto @@ -0,0 +1,77 @@ +syntax = "proto3"; + +package envoy.config.overload.v3alpha; + +option java_outer_classname = "OverloadProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.overload.v3alpha"; + +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Overload Manager] + +// The Overload Manager provides an extensible framework to protect Envoy instances +// from overload of various resources (memory, cpu, file descriptors, etc). +// It monitors a configurable set of resources and notifies registered listeners +// when triggers related to those resources fire. + +message ResourceMonitor { + // The name of the resource monitor to instantiate. Must match a registered + // resource monitor type. The built-in resource monitors are: + // + // * :ref:`envoy.resource_monitors.fixed_heap + // ` + // * :ref:`envoy.resource_monitors.injected_resource + // ` + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Configuration for the resource monitor being instantiated. + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } +} + +message ThresholdTrigger { + // If the resource pressure is greater than or equal to this value, the trigger + // will fire. + double value = 1 [(validate.rules).double = {gte: 0, lte: 1}]; +} + +message Trigger { + // The name of the resource this is a trigger for. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + oneof trigger_oneof { + option (validate.required) = true; + ThresholdTrigger threshold = 2; + } +} + +message OverloadAction { + // The name of the overload action. This is just a well-known string that listeners can + // use for registering callbacks. Custom overload actions should be named using reverse + // DNS to ensure uniqueness. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // A set of triggers for this action. If any of these triggers fire the overload action + // is activated. Listeners are notified when the overload action transitions from + // inactivated to activated, or vice versa. + repeated Trigger triggers = 2 [(validate.rules).repeated .min_items = 1]; +} + +message OverloadManager { + // The interval for refreshing resource usage. + google.protobuf.Duration refresh_interval = 1; + + // The set of resources to monitor. + repeated ResourceMonitor resource_monitors = 2 [(validate.rules).repeated .min_items = 1]; + + // The set of overload actions. + repeated OverloadAction actions = 3; +} diff --git a/api/envoy/config/ratelimit/v2/BUILD b/api/envoy/config/ratelimit/v2/BUILD index be3fc1c212..432f4b9592 100644 --- a/api/envoy/config/ratelimit/v2/BUILD +++ b/api/envoy/config/ratelimit/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library_internal( name = "rls", srcs = ["rls.proto"], @@ -10,11 +14,3 @@ api_proto_library_internal( "//envoy/api/v2/core:grpc_service", ], ) - -api_go_grpc_library( - name = "rls", - proto = ":rls", - deps = [ - "//envoy/api/v2/core:grpc_service_go_proto", - ], -) diff --git a/api/envoy/config/ratelimit/v2/rls.proto b/api/envoy/config/ratelimit/v2/rls.proto index 8f039b44ef..55577d4ab0 100644 --- a/api/envoy/config/ratelimit/v2/rls.proto +++ b/api/envoy/config/ratelimit/v2/rls.proto @@ -5,7 +5,6 @@ package envoy.config.ratelimit.v2; option java_outer_classname = "RlsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.ratelimit.v2"; -option go_package = "v2"; import "envoy/api/v2/core/grpc_service.proto"; diff --git a/api/envoy/config/ratelimit/v3alpha/BUILD b/api/envoy/config/ratelimit/v3alpha/BUILD new file mode 100644 index 0000000000..1d009164ba --- /dev/null +++ b/api/envoy/config/ratelimit/v3alpha/BUILD @@ -0,0 +1,16 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library_internal( + name = "rls", + srcs = ["rls.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha/core:grpc_service", + ], +) diff --git a/api/envoy/config/ratelimit/v3alpha/rls.proto b/api/envoy/config/ratelimit/v3alpha/rls.proto new file mode 100644 index 0000000000..16d5a4ad77 --- /dev/null +++ b/api/envoy/config/ratelimit/v3alpha/rls.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package envoy.config.ratelimit.v3alpha; + +option java_outer_classname = "RlsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.ratelimit.v3alpha"; + +import "envoy/api/v3alpha/core/grpc_service.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Rate limit service] + +// Rate limit :ref:`configuration overview `. +message RateLimitServiceConfig { + reserved 1; + + // Specifies the gRPC service that hosts the rate limit service. The client + // will connect to this cluster when it needs to make rate limit service + // requests. + envoy.api.v3alpha.core.GrpcService grpc_service = 2 [(validate.rules).message.required = true]; + + reserved 3; +} diff --git a/api/envoy/config/rbac/v2/BUILD b/api/envoy/config/rbac/v2/BUILD index fac50eb66f..18b1bb24f2 100644 --- a/api/envoy/config/rbac/v2/BUILD +++ b/api/envoy/config/rbac/v2/BUILD @@ -1,6 +1,15 @@ licenses(["notice"]) # Apache 2 -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/api/v2/route:pkg", + "//envoy/type/matcher", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_proto", + ], +) api_proto_library_internal( name = "rbac", @@ -22,15 +31,3 @@ api_proto_library_internal( "//envoy/type/matcher:string", ], ) - -api_go_proto_library( - name = "rbac", - proto = ":rbac", - deps = [ - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/route:route_go_proto", - "//envoy/type/matcher:metadata_go_proto", - "//envoy/type/matcher:string_go_proto", - "@com_google_googleapis//google/api/expr/v1alpha1:cel_go_proto", - ], -) diff --git a/api/envoy/config/rbac/v2/rbac.proto b/api/envoy/config/rbac/v2/rbac.proto index 15554e561d..1d797d4186 100644 --- a/api/envoy/config/rbac/v2/rbac.proto +++ b/api/envoy/config/rbac/v2/rbac.proto @@ -1,7 +1,6 @@ syntax = "proto3"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; import "envoy/api/v2/core/address.proto"; import "envoy/api/v2/route/route.proto"; import "envoy/type/matcher/metadata.proto"; @@ -14,9 +13,6 @@ package envoy.config.rbac.v2; option java_outer_classname = "RbacProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.rbac.v2"; -option go_package = "v2"; - -option (gogoproto.stable_marshaler_all) = true; // [#protodoc-title: Role Based Access Control (RBAC)] @@ -95,8 +91,9 @@ message Policy { // Principal with the `any` field set to true should be used. repeated Principal principals = 2 [(validate.rules).repeated .min_items = 1]; - // An optional symbolic expression specifying an access control condition. - // The condition is combined with AND semantics. + // An optional symbolic expression specifying an access control + // :ref:`condition `. The condition is combined + // with the permissions and the principals as a clause with AND semantics. google.api.expr.v1alpha1.Expr condition = 3; } diff --git a/api/envoy/config/rbac/v3alpha/BUILD b/api/envoy/config/rbac/v3alpha/BUILD new file mode 100644 index 0000000000..60200f034e --- /dev/null +++ b/api/envoy/config/rbac/v3alpha/BUILD @@ -0,0 +1,33 @@ +licenses(["notice"]) # Apache 2 + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/route:pkg", + "//envoy/type/matcher", + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_proto", + ], +) + +api_proto_library_internal( + name = "rbac", + srcs = ["rbac.proto"], + external_cc_proto_deps = [ + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_cc_proto", + ], + external_proto_deps = [ + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_proto", + ], + external_py_proto_deps = [ + "@com_google_googleapis//google/api/expr/v1alpha1:syntax_py_proto", + ], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/route", + "//envoy/type/matcher:metadata", + "//envoy/type/matcher:string", + ], +) diff --git a/api/envoy/config/rbac/v3alpha/rbac.proto b/api/envoy/config/rbac/v3alpha/rbac.proto new file mode 100644 index 0000000000..3fe9fe41c9 --- /dev/null +++ b/api/envoy/config/rbac/v3alpha/rbac.proto @@ -0,0 +1,211 @@ +syntax = "proto3"; + +import "validate/validate.proto"; +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/route/route.proto"; +import "envoy/type/matcher/metadata.proto"; +import "envoy/type/matcher/string.proto"; + +import "google/api/expr/v1alpha1/syntax.proto"; + +package envoy.config.rbac.v3alpha; + +option java_outer_classname = "RbacProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.rbac.v3alpha"; + +// [#protodoc-title: Role Based Access Control (RBAC)] + +// Role Based Access Control (RBAC) provides service-level and method-level access control for a +// service. RBAC policies are additive. The policies are examined in order. A request is allowed +// once a matching policy is found (suppose the `action` is ALLOW). +// +// Here is an example of RBAC configuration. It has two policies: +// +// * Service account "cluster.local/ns/default/sa/admin" has full access to the service, and so +// does "cluster.local/ns/default/sa/superuser". +// +// * Any user can read ("GET") the service at paths with prefix "/products", so long as the +// destination port is either 80 or 443. +// +// .. code-block:: yaml +// +// action: ALLOW +// policies: +// "service-admin": +// permissions: +// - any: true +// principals: +// - authenticated: +// principal_name: +// exact: "cluster.local/ns/default/sa/admin" +// - authenticated: +// principal_name: +// exact: "cluster.local/ns/default/sa/superuser" +// "product-viewer": +// permissions: +// - and_rules: +// rules: +// - header: { name: ":method", exact_match: "GET" } +// - header: { name: ":path", regex_match: "/products(/.*)?" } +// - or_rules: +// rules: +// - destination_port: 80 +// - destination_port: 443 +// principals: +// - any: true +// +message RBAC { + // Should we do safe-list or block-list style access control? + enum Action { + // The policies grant access to principals. The rest is denied. This is safe-list style + // access control. This is the default type. + ALLOW = 0; + + // The policies deny access to principals. The rest is allowed. This is block-list style + // access control. + DENY = 1; + } + + // The action to take if a policy matches. The request is allowed if and only if: + // + // * `action` is "ALLOWED" and at least one policy matches + // * `action` is "DENY" and none of the policies match + Action action = 1; + + // Maps from policy name to policy. A match occurs when at least one policy matches the request. + map policies = 2; +} + +// Policy specifies a role and the principals that are assigned/denied the role. A policy matches if +// and only if at least one of its permissions match the action taking place AND at least one of its +// principals match the downstream AND the condition is true if specified. +message Policy { + // Required. The set of permissions that define a role. Each permission is matched with OR + // semantics. To match all actions for this policy, a single Permission with the `any` field set + // to true should be used. + repeated Permission permissions = 1 [(validate.rules).repeated .min_items = 1]; + + // Required. The set of principals that are assigned/denied the role based on “action”. Each + // principal is matched with OR semantics. To match all downstreams for this policy, a single + // Principal with the `any` field set to true should be used. + repeated Principal principals = 2 [(validate.rules).repeated .min_items = 1]; + + // An optional symbolic expression specifying an access control condition. + // The condition is combined with AND semantics. + google.api.expr.v1alpha1.Expr condition = 3; +} + +// Permission defines an action (or actions) that a principal can take. +message Permission { + + // Used in the `and_rules` and `or_rules` fields in the `rule` oneof. Depending on the context, + // each are applied with the associated behavior. + message Set { + repeated Permission rules = 1 [(validate.rules).repeated .min_items = 1]; + } + + oneof rule { + option (validate.required) = true; + + // A set of rules that all must match in order to define the action. + Set and_rules = 1; + + // A set of rules where at least one must match in order to define the action. + Set or_rules = 2; + + // When any is set, it matches any action. + bool any = 3 [(validate.rules).bool.const = true]; + + // A header (or pseudo-header such as :path or :method) on the incoming HTTP request. Only + // available for HTTP request. + envoy.api.v3alpha.route.HeaderMatcher header = 4; + + // A CIDR block that describes the destination IP. + envoy.api.v3alpha.core.CidrRange destination_ip = 5; + + // A port number that describes the destination port connecting to. + uint32 destination_port = 6 [(validate.rules).uint32.lte = 65535]; + + // Metadata that describes additional information about the action. + envoy.type.matcher.MetadataMatcher metadata = 7; + + // Negates matching the provided permission. For instance, if the value of `not_rule` would + // match, this permission would not match. Conversely, if the value of `not_rule` would not + // match, this permission would match. + Permission not_rule = 8; + + // The request server from the client's connection request. This is + // typically TLS SNI. + // + // .. attention:: + // + // The behavior of this field may be affected by how Envoy is configured + // as explained below. + // + // * If the :ref:`TLS Inspector ` + // filter is not added, and if a `FilterChainMatch` is not defined for + // the :ref:`server name `, + // a TLS connection's requested SNI server name will be treated as if it + // wasn't present. + // + // * A :ref:`listener filter ` may + // overwrite a connection's requested server name within Envoy. + // + // Please refer to :ref:`this FAQ entry ` to learn to + // setup SNI. + envoy.type.matcher.StringMatcher requested_server_name = 9; + } +} + +// Principal defines an identity or a group of identities for a downstream subject. +message Principal { + + // Used in the `and_ids` and `or_ids` fields in the `identifier` oneof. Depending on the context, + // each are applied with the associated behavior. + message Set { + repeated Principal ids = 1 [(validate.rules).repeated .min_items = 1]; + } + + // Authentication attributes for a downstream. + message Authenticated { + reserved 1; + reserved "name"; + + // The name of the principal. If set, The URI SAN or DNS SAN in that order is used from the + // certificate, otherwise the subject field is used. If unset, it applies to any user that is + // authenticated. + envoy.type.matcher.StringMatcher principal_name = 2; + } + + oneof identifier { + option (validate.required) = true; + + // A set of identifiers that all must match in order to define the downstream. + Set and_ids = 1; + + // A set of identifiers at least one must match in order to define the downstream. + Set or_ids = 2; + + // When any is set, it matches any downstream. + bool any = 3 [(validate.rules).bool.const = true]; + + // Authenticated attributes that identify the downstream. + Authenticated authenticated = 4; + + // A CIDR block that describes the downstream IP. + envoy.api.v3alpha.core.CidrRange source_ip = 5; + + // A header (or pseudo-header such as :path or :method) on the incoming HTTP request. Only + // available for HTTP request. + envoy.api.v3alpha.route.HeaderMatcher header = 6; + + // Metadata that describes additional information about the principal. + envoy.type.matcher.MetadataMatcher metadata = 7; + + // Negates matching the provided principal. For instance, if the value of `not_id` would match, + // this principal would not match. Conversely, if the value of `not_id` would not match, this + // principal would match. + Principal not_id = 8; + } +} diff --git a/api/envoy/config/resource_monitor/fixed_heap/v2alpha/BUILD b/api/envoy/config/resource_monitor/fixed_heap/v2alpha/BUILD index 363d90f118..a5003e219c 100644 --- a/api/envoy/config/resource_monitor/fixed_heap/v2alpha/BUILD +++ b/api/envoy/config/resource_monitor/fixed_heap/v2alpha/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "fixed_heap", srcs = ["fixed_heap.proto"], diff --git a/api/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto b/api/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto index f7efe0b564..110123e3c3 100644 --- a/api/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto +++ b/api/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto @@ -5,7 +5,6 @@ package envoy.config.resource_monitor.fixed_heap.v2alpha; option java_outer_classname = "FixedHeapProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.resource_monitor.fixed_heap.v2alpha"; -option go_package = "v2alpha"; import "validate/validate.proto"; diff --git a/api/envoy/config/resource_monitor/fixed_heap/v3alpha/BUILD b/api/envoy/config/resource_monitor/fixed_heap/v3alpha/BUILD new file mode 100644 index 0000000000..a5003e219c --- /dev/null +++ b/api/envoy/config/resource_monitor/fixed_heap/v3alpha/BUILD @@ -0,0 +1,11 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "fixed_heap", + srcs = ["fixed_heap.proto"], + visibility = ["//visibility:public"], +) diff --git a/api/envoy/config/resource_monitor/fixed_heap/v3alpha/fixed_heap.proto b/api/envoy/config/resource_monitor/fixed_heap/v3alpha/fixed_heap.proto new file mode 100644 index 0000000000..bc84ee9924 --- /dev/null +++ b/api/envoy/config/resource_monitor/fixed_heap/v3alpha/fixed_heap.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package envoy.config.resource_monitor.fixed_heap.v3alpha; + +option java_outer_classname = "FixedHeapProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.resource_monitor.fixed_heap.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: Fixed heap] + +// The fixed heap resource monitor reports the Envoy process memory pressure, computed as a +// fraction of currently reserved heap memory divided by a statically configured maximum +// specified in the FixedHeapConfig. +message FixedHeapConfig { + uint64 max_heap_size_bytes = 1 [(validate.rules).uint64.gt = 0]; +} diff --git a/api/envoy/config/resource_monitor/injected_resource/v2alpha/BUILD b/api/envoy/config/resource_monitor/injected_resource/v2alpha/BUILD index 10abf09e9e..3a1764216b 100644 --- a/api/envoy/config/resource_monitor/injected_resource/v2alpha/BUILD +++ b/api/envoy/config/resource_monitor/injected_resource/v2alpha/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "injected_resource", srcs = ["injected_resource.proto"], diff --git a/api/envoy/config/resource_monitor/injected_resource/v2alpha/injected_resource.proto b/api/envoy/config/resource_monitor/injected_resource/v2alpha/injected_resource.proto index cab704a4b6..64c984fa0c 100644 --- a/api/envoy/config/resource_monitor/injected_resource/v2alpha/injected_resource.proto +++ b/api/envoy/config/resource_monitor/injected_resource/v2alpha/injected_resource.proto @@ -5,7 +5,6 @@ package envoy.config.resource_monitor.injected_resource.v2alpha; option java_outer_classname = "InjectedResourceProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.resource_monitor.injected_resource.v2alpha"; -option go_package = "v2alpha"; import "validate/validate.proto"; diff --git a/api/envoy/config/resource_monitor/injected_resource/v3alpha/BUILD b/api/envoy/config/resource_monitor/injected_resource/v3alpha/BUILD new file mode 100644 index 0000000000..3a1764216b --- /dev/null +++ b/api/envoy/config/resource_monitor/injected_resource/v3alpha/BUILD @@ -0,0 +1,11 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library_internal( + name = "injected_resource", + srcs = ["injected_resource.proto"], + visibility = ["//visibility:public"], +) diff --git a/api/envoy/config/resource_monitor/injected_resource/v3alpha/injected_resource.proto b/api/envoy/config/resource_monitor/injected_resource/v3alpha/injected_resource.proto new file mode 100644 index 0000000000..555e15323f --- /dev/null +++ b/api/envoy/config/resource_monitor/injected_resource/v3alpha/injected_resource.proto @@ -0,0 +1,19 @@ +syntax = "proto3"; + +package envoy.config.resource_monitor.injected_resource.v3alpha; + +option java_outer_classname = "InjectedResourceProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.resource_monitor.injected_resource.v3alpha"; + +import "validate/validate.proto"; + +// [#protodoc-title: Injected resource] + +// The injected resource monitor allows injecting a synthetic resource pressure into Envoy +// via a text file, which must contain a floating-point number in the range [0..1] representing +// the resource pressure and be updated atomically by a symbolic link swap. +// This is intended primarily for integration tests to force Envoy into an overloaded state. +message InjectedResourceConfig { + string filename = 1 [(validate.rules).string.min_bytes = 1]; +} diff --git a/api/envoy/config/retry/previous_priorities/BUILD b/api/envoy/config/retry/previous_priorities/BUILD index 13a694af37..8140346d47 100644 --- a/api/envoy/config/retry/previous_priorities/BUILD +++ b/api/envoy/config/retry/previous_priorities/BUILD @@ -1,6 +1,10 @@ licenses(["notice"]) # Apache 2 -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +api_proto_package( + deps = ["//envoy/api/v2/core"], +) api_proto_library_internal( name = "previous_priorities", diff --git a/api/envoy/config/trace/v2/BUILD b/api/envoy/config/trace/v2/BUILD index b00f63dafb..f894a5289f 100644 --- a/api/envoy/config/trace/v2/BUILD +++ b/api/envoy/config/trace/v2/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "@opencensus_proto//opencensus/proto/trace/v1:trace_config_proto", + ], +) + api_proto_library_internal( name = "trace", srcs = ["trace.proto"], @@ -13,12 +20,3 @@ api_proto_library_internal( "@opencensus_proto//opencensus/proto/trace/v1:trace_config_proto", ], ) - -api_go_proto_library( - name = "trace", - proto = ":trace", - deps = [ - "//envoy/api/v2/core:grpc_service_go_proto", - "@opencensus_proto//opencensus/proto/trace/v1:trace_and_config_proto_go", - ], -) diff --git a/api/envoy/config/trace/v2/trace.proto b/api/envoy/config/trace/v2/trace.proto index e4e4dec64e..43f5013b27 100644 --- a/api/envoy/config/trace/v2/trace.proto +++ b/api/envoy/config/trace/v2/trace.proto @@ -8,7 +8,6 @@ package envoy.config.trace.v2; option java_outer_classname = "TraceProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.trace.v2"; -option go_package = "v2"; import "envoy/api/v2/core/grpc_service.proto"; import "opencensus/proto/trace/v1/trace_config.proto"; @@ -65,6 +64,7 @@ message LightstepConfig { string access_token_file = 2 [(validate.rules).string.min_bytes = 1]; } +// Configuration for the Zipkin tracer. message ZipkinConfig { // The cluster manager cluster that hosts the Zipkin collectors. Note that the // Zipkin cluster must be defined in the :ref:`Bootstrap static cluster @@ -80,9 +80,34 @@ message ZipkinConfig { // trace instance. The default value is false, which will result in a 64 bit trace id being used. bool trace_id_128bit = 3; - // Determines whether client and server spans will shared the same span id. + // Determines whether client and server spans will share the same span context. // The default value is true. google.protobuf.BoolValue shared_span_context = 4; + + // Available Zipkin collector endpoint versions. + enum CollectorEndpointVersion { + // Zipkin API v1, JSON over HTTP. + // [#comment: The default implementation of Zipkin client before this field is added was only v1 + // and the way user configure this was by not explicitly specifying the version. Consequently, + // before this is added, the corresponding Zipkin collector expected to receive v1 payload. + // Hence the motivation of adding HTTP_JSON_V1 as the default is to avoid a breaking change when + // user upgrading Envoy with this change. Furthermore, we also immediately deprecate this field, + // since in Zipkin realm this v1 version is considered to be not preferable anymore.] + HTTP_JSON_V1 = 0 [deprecated = true]; + + // Zipkin API v2, JSON over HTTP. + HTTP_JSON = 1; + + // Zipkin API v2, protobuf over HTTP. + HTTP_PROTO = 2; + + // [#not-implemented-hide:] + GRPC = 3; + } + + // Determines the selected collector endpoint version. By default, the ``HTTP_JSON_V1`` will be + // used. + CollectorEndpointVersion collector_endpoint_version = 5; } // DynamicOtConfig is used to dynamically load a tracer from a shared library @@ -136,6 +161,14 @@ message OpenCensusConfig { // The URL to Zipkin, e.g. "http://127.0.0.1:9411/api/v2/spans" string zipkin_url = 6; + // Enables the OpenCensus Agent exporter if set to true. The address must also + // be set. + bool ocagent_exporter_enabled = 11; + + // The address of the OpenCensus Agent, if its exporter is enabled, in gRPC + // format: https://github.com/grpc/grpc/blob/master/doc/naming.md + string ocagent_address = 12; + reserved 7; // Formerly zipkin_service_name. enum TraceContext { diff --git a/api/envoy/config/trace/v3alpha/BUILD b/api/envoy/config/trace/v3alpha/BUILD new file mode 100644 index 0000000000..97014ca68f --- /dev/null +++ b/api/envoy/config/trace/v3alpha/BUILD @@ -0,0 +1,22 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "@opencensus_proto//opencensus/proto/trace/v1:trace_config_proto", + ], +) + +api_proto_library_internal( + name = "trace", + srcs = ["trace.proto"], + visibility = [ + "//envoy/config/bootstrap/v3alpha:__pkg__", + ], + deps = [ + "//envoy/api/v3alpha/core:grpc_service", + "@opencensus_proto//opencensus/proto/trace/v1:trace_config_proto", + ], +) diff --git a/api/envoy/config/trace/v3alpha/trace.proto b/api/envoy/config/trace/v3alpha/trace.proto new file mode 100644 index 0000000000..f98f1f7089 --- /dev/null +++ b/api/envoy/config/trace/v3alpha/trace.proto @@ -0,0 +1,203 @@ +// [#protodoc-title: Tracing] +// Tracing :ref:`architecture overview `. + +syntax = "proto3"; + +package envoy.config.trace.v3alpha; + +option java_outer_classname = "TraceProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.trace.v3alpha"; + +import "envoy/api/v3alpha/core/grpc_service.proto"; +import "opencensus/proto/trace/v1/trace_config.proto"; + +import "google/protobuf/any.proto"; +import "google/protobuf/struct.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// The tracing configuration specifies global +// settings for the HTTP tracer used by Envoy. The configuration is defined by +// the :ref:`Bootstrap ` :ref:`tracing +// ` field. Envoy may support other +// tracers in the future, but right now the HTTP tracer is the only one supported. +message Tracing { + message Http { + // The name of the HTTP trace driver to instantiate. The name must match a + // supported HTTP trace driver. Built-in trace drivers: + // + // - *envoy.lightstep* + // - *envoy.zipkin* + // - *envoy.dynamic.ot* + // - *envoy.tracers.datadog* + // - *envoy.tracers.opencensus* + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Trace driver specific configuration which depends on the driver being instantiated. + // See the trace drivers for examples: + // + // - :ref:`LightstepConfig ` + // - :ref:`ZipkinConfig ` + // - :ref:`DynamicOtConfig ` + // - :ref:`DatadogConfig ` + // - :ref:`OpenCensusConfig ` + oneof config_type { + google.protobuf.Struct config = 2; + + google.protobuf.Any typed_config = 3; + } + } + // Provides configuration for the HTTP tracer. + Http http = 1; +} + +// Configuration for the LightStep tracer. +message LightstepConfig { + // The cluster manager cluster that hosts the LightStep collectors. + string collector_cluster = 1 [(validate.rules).string.min_bytes = 1]; + + // File containing the access token to the `LightStep + // `_ API. + string access_token_file = 2 [(validate.rules).string.min_bytes = 1]; +} + +// Configuration for the Zipkin tracer. +message ZipkinConfig { + // The cluster manager cluster that hosts the Zipkin collectors. Note that the + // Zipkin cluster must be defined in the :ref:`Bootstrap static cluster + // resources `. + string collector_cluster = 1 [(validate.rules).string.min_bytes = 1]; + + // The API endpoint of the Zipkin service where the spans will be sent. When + // using a standard Zipkin installation, the API endpoint is typically + // /api/v1/spans, which is the default value. + string collector_endpoint = 2 [(validate.rules).string.min_bytes = 1]; + + // Determines whether a 128bit trace id will be used when creating a new + // trace instance. The default value is false, which will result in a 64 bit trace id being used. + bool trace_id_128bit = 3; + + // Determines whether client and server spans will share the same span context. + // The default value is true. + google.protobuf.BoolValue shared_span_context = 4; + + // Available Zipkin collector endpoint versions. + enum CollectorEndpointVersion { + // Zipkin API v1, JSON over HTTP. + // [#comment: The default implementation of Zipkin client before this field is added was only v1 + // and the way user configure this was by not explicitly specifying the version. Consequently, + // before this is added, the corresponding Zipkin collector expected to receive v1 payload. + // Hence the motivation of adding HTTP_JSON_V1 as the default is to avoid a breaking change when + // user upgrading Envoy with this change. Furthermore, we also immediately deprecate this field, + // since in Zipkin realm this v1 version is considered to be not preferable anymore.] + HTTP_JSON_V1 = 0 [deprecated = true]; + + // Zipkin API v2, JSON over HTTP. + HTTP_JSON = 1; + + // Zipkin API v2, protobuf over HTTP. + HTTP_PROTO = 2; + + // [#not-implemented-hide:] + GRPC = 3; + } + + // Determines the selected collector endpoint version. By default, the ``HTTP_JSON_V1`` will be + // used. + CollectorEndpointVersion collector_endpoint_version = 5; +} + +// DynamicOtConfig is used to dynamically load a tracer from a shared library +// that implements the `OpenTracing dynamic loading API +// `_. +message DynamicOtConfig { + // Dynamic library implementing the `OpenTracing API + // `_. + string library = 1 [(validate.rules).string.min_bytes = 1]; + + // The configuration to use when creating a tracer from the given dynamic + // library. + google.protobuf.Struct config = 2; +} + +// Configuration for the Datadog tracer. +message DatadogConfig { + // The cluster to use for submitting traces to the Datadog agent. + string collector_cluster = 1 [(validate.rules).string.min_bytes = 1]; + // The name used for the service when traces are generated by envoy. + string service_name = 2 [(validate.rules).string.min_bytes = 1]; +} + +// Configuration for the OpenCensus tracer. +// [#proto-status: experimental] +message OpenCensusConfig { + // Configures tracing, e.g. the sampler, max number of annotations, etc. + opencensus.proto.trace.v1.TraceConfig trace_config = 1; + + // Enables the stdout exporter if set to true. This is intended for debugging + // purposes. + bool stdout_exporter_enabled = 2; + + // Enables the Stackdriver exporter if set to true. The project_id must also + // be set. + bool stackdriver_exporter_enabled = 3; + + // The Cloud project_id to use for Stackdriver tracing. + string stackdriver_project_id = 4; + + // (optional) By default, the Stackdriver exporter will connect to production + // Stackdriver. If stackdriver_address is non-empty, it will instead connect + // to this address, which is in the gRPC format: + // https://github.com/grpc/grpc/blob/master/doc/naming.md + string stackdriver_address = 10; + + // Enables the Zipkin exporter if set to true. The url and service name must + // also be set. + bool zipkin_exporter_enabled = 5; + + // The URL to Zipkin, e.g. "http://127.0.0.1:9411/api/v3alpha/spans" + string zipkin_url = 6; + + // Enables the OpenCensus Agent exporter if set to true. The address must also + // be set. + bool ocagent_exporter_enabled = 11; + + // The address of the OpenCensus Agent, if its exporter is enabled, in gRPC + // format: https://github.com/grpc/grpc/blob/master/doc/naming.md + string ocagent_address = 12; + + reserved 7; // Formerly zipkin_service_name. + + enum TraceContext { + // No-op default, no trace context is utilized. + NONE = 0; + + // W3C Trace-Context format "traceparent:" header. + TRACE_CONTEXT = 1; + + // Binary "grpc-trace-bin:" header. + GRPC_TRACE_BIN = 2; + + // "X-Cloud-Trace-Context:" header. + CLOUD_TRACE_CONTEXT = 3; + + // X-B3-* headers. + B3 = 4; + } + + // List of incoming trace context headers we will accept. First one found + // wins. + repeated TraceContext incoming_trace_context = 8; + + // List of outgoing trace context headers we will produce. + repeated TraceContext outgoing_trace_context = 9; +} + +// Configuration structure. +message TraceServiceConfig { + // The upstream gRPC cluster that hosts the metrics service. + envoy.api.v3alpha.core.GrpcService grpc_service = 1 [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/transport_socket/alts/v2alpha/BUILD b/api/envoy/config/transport_socket/alts/v2alpha/BUILD index 6cb181f202..eb247ae14b 100644 --- a/api/envoy/config/transport_socket/alts/v2alpha/BUILD +++ b/api/envoy/config/transport_socket/alts/v2alpha/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library( name = "alts", srcs = ["alts.proto"], diff --git a/api/envoy/config/transport_socket/alts/v2alpha/alts.proto b/api/envoy/config/transport_socket/alts/v2alpha/alts.proto index f5a9db64c0..ec294af174 100644 --- a/api/envoy/config/transport_socket/alts/v2alpha/alts.proto +++ b/api/envoy/config/transport_socket/alts/v2alpha/alts.proto @@ -5,7 +5,6 @@ package envoy.config.transport_socket.alts.v2alpha; option java_outer_classname = "AltsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.transport_socket.alts.v2alpha"; -option go_package = "v2"; // [#protodoc-title: ALTS] diff --git a/api/envoy/config/transport_socket/alts/v3alpha/BUILD b/api/envoy/config/transport_socket/alts/v3alpha/BUILD new file mode 100644 index 0000000000..4e6642283e --- /dev/null +++ b/api/envoy/config/transport_socket/alts/v3alpha/BUILD @@ -0,0 +1,15 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library( + name = "alts", + srcs = ["alts.proto"], + deps = [ + "//envoy/api/v3alpha/core:base", + ], +) diff --git a/api/envoy/config/transport_socket/alts/v3alpha/alts.proto b/api/envoy/config/transport_socket/alts/v3alpha/alts.proto new file mode 100644 index 0000000000..adec43c25c --- /dev/null +++ b/api/envoy/config/transport_socket/alts/v3alpha/alts.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package envoy.config.transport_socket.alts.v3alpha; + +option java_outer_classname = "AltsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.transport_socket.alts.v3alpha"; + +// [#protodoc-title: ALTS] + +import "validate/validate.proto"; + +// Configuration for ALTS transport socket. This provides Google's ALTS protocol to Envoy. +// https://cloud.google.com/security/encryption-in-transit/application-layer-transport-security/ +message Alts { + // The location of a handshaker service, this is usually 169.254.169.254:8080 + // on GCE. + string handshaker_service = 1 [(validate.rules).string.min_bytes = 1]; + + // The acceptable service accounts from peer, peers not in the list will be rejected in the + // handshake validation step. If empty, no validation will be performed. + repeated string peer_service_accounts = 2; +} diff --git a/api/envoy/config/transport_socket/tap/v2alpha/BUILD b/api/envoy/config/transport_socket/tap/v2alpha/BUILD index 75810cd0c2..e18d4fc1c1 100644 --- a/api/envoy/config/transport_socket/tap/v2alpha/BUILD +++ b/api/envoy/config/transport_socket/tap/v2alpha/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = [ + "//envoy/api/v2/core", + "//envoy/config/common/tap/v2alpha:pkg", + ], +) + api_proto_library_internal( name = "tap", srcs = ["tap.proto"], diff --git a/api/envoy/config/transport_socket/tap/v2alpha/tap.proto b/api/envoy/config/transport_socket/tap/v2alpha/tap.proto index 84918699ef..e68b40dae5 100644 --- a/api/envoy/config/transport_socket/tap/v2alpha/tap.proto +++ b/api/envoy/config/transport_socket/tap/v2alpha/tap.proto @@ -5,7 +5,6 @@ package envoy.config.transport_socket.tap.v2alpha; option java_outer_classname = "TapProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.config.transport_socket.tap.v2alpha"; -option go_package = "v2"; // [#protodoc-title: Tap] diff --git a/api/envoy/config/transport_socket/tap/v3alpha/BUILD b/api/envoy/config/transport_socket/tap/v3alpha/BUILD new file mode 100644 index 0000000000..0f24cca4c1 --- /dev/null +++ b/api/envoy/config/transport_socket/tap/v3alpha/BUILD @@ -0,0 +1,19 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/config/common/tap/v3alpha:pkg", + ], +) + +api_proto_library_internal( + name = "tap", + srcs = ["tap.proto"], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/config/common/tap/v3alpha:common", + ], +) diff --git a/api/envoy/config/transport_socket/tap/v3alpha/tap.proto b/api/envoy/config/transport_socket/tap/v3alpha/tap.proto new file mode 100644 index 0000000000..21625e17ef --- /dev/null +++ b/api/envoy/config/transport_socket/tap/v3alpha/tap.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package envoy.config.transport_socket.tap.v3alpha; + +option java_outer_classname = "TapProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.config.transport_socket.tap.v3alpha"; + +// [#protodoc-title: Tap] + +import "envoy/config/common/tap/v3alpha/common.proto"; +import "envoy/api/v3alpha/core/base.proto"; + +import "validate/validate.proto"; + +// Configuration for tap transport socket. This wraps another transport socket, providing the +// ability to interpose and record in plain text any traffic that is surfaced to Envoy. +message Tap { + // Common configuration for the tap transport socket. + common.tap.v3alpha.CommonExtensionConfig common_config = 1 + [(validate.rules).message.required = true]; + + // The underlying transport socket being wrapped. + api.v3alpha.core.TransportSocket transport_socket = 2 [(validate.rules).message.required = true]; +} diff --git a/api/envoy/config/wasm/v2/BUILD b/api/envoy/config/wasm/v2/BUILD index 99623a90ff..8db564bb6e 100644 --- a/api/envoy/config/wasm/v2/BUILD +++ b/api/envoy/config/wasm/v2/BUILD @@ -1,7 +1,9 @@ -load("//bazel:api_build_system.bzl", "api_go_grpc_library", "api_go_proto_library", "api_proto_library_internal") +load("//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library_internal( name = "wasm", srcs = ["wasm.proto"], @@ -15,8 +17,3 @@ api_proto_library_internal( "//envoy/api/v2/core:base", ], ) - -api_go_proto_library( - name = "wasm", - proto = ":wasm", -) diff --git a/api/envoy/data/accesslog/v2/BUILD b/api/envoy/data/accesslog/v2/BUILD index d3ade88e92..22c4c45ee8 100644 --- a/api/envoy/data/accesslog/v2/BUILD +++ b/api/envoy/data/accesslog/v2/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library_internal( name = "accesslog", srcs = ["accesslog.proto"], @@ -13,12 +17,3 @@ api_proto_library_internal( "//envoy/api/v2/core:base", ], ) - -api_go_proto_library( - name = "accesslog", - proto = ":accesslog", - deps = [ - "//envoy/api/v2/core:address_go_proto", - "//envoy/api/v2/core:base_go_proto", - ], -) diff --git a/api/envoy/data/accesslog/v2/accesslog.proto b/api/envoy/data/accesslog/v2/accesslog.proto index 1396c729f8..bc6ff86bbd 100644 --- a/api/envoy/data/accesslog/v2/accesslog.proto +++ b/api/envoy/data/accesslog/v2/accesslog.proto @@ -12,11 +12,8 @@ import "envoy/api/v2/core/base.proto"; import "google/protobuf/duration.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; -import "gogoproto/gogo.proto"; import "validate/validate.proto"; -option (gogoproto.stable_marshaler_all) = true; - // [#protodoc-title: gRPC access logs] // Envoy access logs describe incoming interaction with Envoy over a fixed // period of time, and typically cover a single request/response exchange, @@ -28,10 +25,12 @@ option (gogoproto.stable_marshaler_all) = true; // Fields describing *upstream* interaction will explicitly include ``upstream`` // in their name. -// [#not-implemented-hide:] message TCPAccessLogEntry { // Common properties shared by all Envoy access logs. AccessLogCommon common_properties = 1; + + // Properties of the TCP connection. + ConnectionProperties connection_properties = 2; } message HTTPAccessLogEntry { @@ -54,6 +53,15 @@ message HTTPAccessLogEntry { HTTPResponseProperties response = 4; } +// Defines fields for a connection +message ConnectionProperties { + // Number of bytes received from downstream. + uint64 received_bytes = 1; + + // Number of bytes sent to downstream. + uint64 sent_bytes = 2; +} + // Defines fields that are shared by all Envoy access logs. message AccessLogCommon { // [#not-implemented-hide:] @@ -74,37 +82,37 @@ message AccessLogCommon { // The time that Envoy started servicing this request. This is effectively the time that the first // downstream byte is received. - google.protobuf.Timestamp start_time = 5 [(gogoproto.stdtime) = true]; + google.protobuf.Timestamp start_time = 5; // Interval between the first downstream byte received and the last // downstream byte received (i.e. time it takes to receive a request). - google.protobuf.Duration time_to_last_rx_byte = 6 [(gogoproto.stdduration) = true]; + google.protobuf.Duration time_to_last_rx_byte = 6; // Interval between the first downstream byte received and the first upstream byte sent. There may // by considerable delta between *time_to_last_rx_byte* and this value due to filters. // Additionally, the same caveats apply as documented in *time_to_last_downstream_tx_byte* about // not accounting for kernel socket buffer time, etc. - google.protobuf.Duration time_to_first_upstream_tx_byte = 7 [(gogoproto.stdduration) = true]; + google.protobuf.Duration time_to_first_upstream_tx_byte = 7; // Interval between the first downstream byte received and the last upstream byte sent. There may // by considerable delta between *time_to_last_rx_byte* and this value due to filters. // Additionally, the same caveats apply as documented in *time_to_last_downstream_tx_byte* about // not accounting for kernel socket buffer time, etc. - google.protobuf.Duration time_to_last_upstream_tx_byte = 8 [(gogoproto.stdduration) = true]; + google.protobuf.Duration time_to_last_upstream_tx_byte = 8; // Interval between the first downstream byte received and the first upstream // byte received (i.e. time it takes to start receiving a response). - google.protobuf.Duration time_to_first_upstream_rx_byte = 9 [(gogoproto.stdduration) = true]; + google.protobuf.Duration time_to_first_upstream_rx_byte = 9; // Interval between the first downstream byte received and the last upstream // byte received (i.e. time it takes to receive a complete response). - google.protobuf.Duration time_to_last_upstream_rx_byte = 10 [(gogoproto.stdduration) = true]; + google.protobuf.Duration time_to_last_upstream_rx_byte = 10; // Interval between the first downstream byte received and the first downstream byte sent. // There may be a considerable delta between the *time_to_first_upstream_rx_byte* and this field // due to filters. Additionally, the same caveats apply as documented in // *time_to_last_downstream_tx_byte* about not accounting for kernel socket buffer time, etc. - google.protobuf.Duration time_to_first_downstream_tx_byte = 11 [(gogoproto.stdduration) = true]; + google.protobuf.Duration time_to_first_downstream_tx_byte = 11; // Interval between the first downstream byte received and the last downstream byte sent. // Depending on protocol, buffering, windowing, filters, etc. there may be a considerable delta @@ -112,7 +120,7 @@ message AccessLogCommon { // time. In the current implementation it does not include kernel socket buffer time. In the // current implementation it also does not include send window buffering inside the HTTP/2 codec. // In the future it is likely that work will be done to make this duration more accurate. - google.protobuf.Duration time_to_last_downstream_tx_byte = 12 [(gogoproto.stdduration) = true]; + google.protobuf.Duration time_to_last_downstream_tx_byte = 12; // The upstream remote/destination address that handles this exchange. This does not include // retries. diff --git a/api/envoy/data/accesslog/v3alpha/BUILD b/api/envoy/data/accesslog/v3alpha/BUILD new file mode 100644 index 0000000000..e1fafc0034 --- /dev/null +++ b/api/envoy/data/accesslog/v3alpha/BUILD @@ -0,0 +1,19 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library_internal( + name = "accesslog", + srcs = ["accesslog.proto"], + visibility = [ + "//envoy/service/accesslog/v3alpha:__pkg__", + ], + deps = [ + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + ], +) diff --git a/api/envoy/data/accesslog/v3alpha/accesslog.proto b/api/envoy/data/accesslog/v3alpha/accesslog.proto new file mode 100644 index 0000000000..2cdd44bbd1 --- /dev/null +++ b/api/envoy/data/accesslog/v3alpha/accesslog.proto @@ -0,0 +1,353 @@ +syntax = "proto3"; + +package envoy.data.accesslog.v3alpha; + +option java_outer_classname = "AccesslogProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.data.accesslog.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "validate/validate.proto"; + +// [#protodoc-title: gRPC access logs] +// Envoy access logs describe incoming interaction with Envoy over a fixed +// period of time, and typically cover a single request/response exchange, +// (e.g. HTTP), stream (e.g. over HTTP/gRPC), or proxied connection (e.g. TCP). +// Access logs contain fields defined in protocol-specific protobuf messages. +// +// Except where explicitly declared otherwise, all fields describe +// *downstream* interaction between Envoy and a connected client. +// Fields describing *upstream* interaction will explicitly include ``upstream`` +// in their name. + +message TCPAccessLogEntry { + // Common properties shared by all Envoy access logs. + AccessLogCommon common_properties = 1; + + // Properties of the TCP connection. + ConnectionProperties connection_properties = 2; +} + +message HTTPAccessLogEntry { + // Common properties shared by all Envoy access logs. + AccessLogCommon common_properties = 1; + + // HTTP version + enum HTTPVersion { + PROTOCOL_UNSPECIFIED = 0; + HTTP10 = 1; + HTTP11 = 2; + HTTP2 = 3; + } + HTTPVersion protocol_version = 2; + + // Description of the incoming HTTP request. + HTTPRequestProperties request = 3; + + // Description of the outgoing HTTP response. + HTTPResponseProperties response = 4; +} + +// Defines fields for a connection +message ConnectionProperties { + // Number of bytes received from downstream. + uint64 received_bytes = 1; + + // Number of bytes sent to downstream. + uint64 sent_bytes = 2; +} + +// Defines fields that are shared by all Envoy access logs. +message AccessLogCommon { + // [#not-implemented-hide:] + // This field indicates the rate at which this log entry was sampled. + // Valid range is (0.0, 1.0]. + double sample_rate = 1 [(validate.rules).double.gt = 0.0, (validate.rules).double.lte = 1.0]; + + // This field is the remote/origin address on which the request from the user was received. + // Note: This may not be the physical peer. E.g, if the remote address is inferred from for + // example the x-forwarder-for header, proxy protocol, etc. + envoy.api.v3alpha.core.Address downstream_remote_address = 2; + + // This field is the local/destination address on which the request from the user was received. + envoy.api.v3alpha.core.Address downstream_local_address = 3; + + // If the connection is secure,S this field will contain TLS properties. + TLSProperties tls_properties = 4; + + // The time that Envoy started servicing this request. This is effectively the time that the first + // downstream byte is received. + google.protobuf.Timestamp start_time = 5; + + // Interval between the first downstream byte received and the last + // downstream byte received (i.e. time it takes to receive a request). + google.protobuf.Duration time_to_last_rx_byte = 6; + + // Interval between the first downstream byte received and the first upstream byte sent. There may + // by considerable delta between *time_to_last_rx_byte* and this value due to filters. + // Additionally, the same caveats apply as documented in *time_to_last_downstream_tx_byte* about + // not accounting for kernel socket buffer time, etc. + google.protobuf.Duration time_to_first_upstream_tx_byte = 7; + + // Interval between the first downstream byte received and the last upstream byte sent. There may + // by considerable delta between *time_to_last_rx_byte* and this value due to filters. + // Additionally, the same caveats apply as documented in *time_to_last_downstream_tx_byte* about + // not accounting for kernel socket buffer time, etc. + google.protobuf.Duration time_to_last_upstream_tx_byte = 8; + + // Interval between the first downstream byte received and the first upstream + // byte received (i.e. time it takes to start receiving a response). + google.protobuf.Duration time_to_first_upstream_rx_byte = 9; + + // Interval between the first downstream byte received and the last upstream + // byte received (i.e. time it takes to receive a complete response). + google.protobuf.Duration time_to_last_upstream_rx_byte = 10; + + // Interval between the first downstream byte received and the first downstream byte sent. + // There may be a considerable delta between the *time_to_first_upstream_rx_byte* and this field + // due to filters. Additionally, the same caveats apply as documented in + // *time_to_last_downstream_tx_byte* about not accounting for kernel socket buffer time, etc. + google.protobuf.Duration time_to_first_downstream_tx_byte = 11; + + // Interval between the first downstream byte received and the last downstream byte sent. + // Depending on protocol, buffering, windowing, filters, etc. there may be a considerable delta + // between *time_to_last_upstream_rx_byte* and this field. Note also that this is an approximate + // time. In the current implementation it does not include kernel socket buffer time. In the + // current implementation it also does not include send window buffering inside the HTTP/2 codec. + // In the future it is likely that work will be done to make this duration more accurate. + google.protobuf.Duration time_to_last_downstream_tx_byte = 12; + + // The upstream remote/destination address that handles this exchange. This does not include + // retries. + envoy.api.v3alpha.core.Address upstream_remote_address = 13; + + // The upstream local/origin address that handles this exchange. This does not include retries. + envoy.api.v3alpha.core.Address upstream_local_address = 14; + + // The upstream cluster that *upstream_remote_address* belongs to. + string upstream_cluster = 15; + + // Flags indicating occurrences during request/response processing. + ResponseFlags response_flags = 16; + + // All metadata encountered during request processing, including endpoint + // selection. + // + // This can be used to associate IDs attached to the various configurations + // used to process this request with the access log entry. For example, a + // route created from a higher level forwarding rule with some ID can place + // that ID in this field and cross reference later. It can also be used to + // determine if a canary endpoint was used or not. + envoy.api.v3alpha.core.Metadata metadata = 17; + + // If upstream connection failed due to transport socket (e.g. TLS handshake), provides the + // failure reason from the transport socket. The format of this field depends on the configured + // upstream transport socket. Common TLS failures are in + // :ref:`TLS trouble shooting `. + string upstream_transport_failure_reason = 18; + + // The name of the route + string route_name = 19; +} + +// Flags indicating occurrences during request/response processing. +message ResponseFlags { + // Indicates local server healthcheck failed. + bool failed_local_healthcheck = 1; + + // Indicates there was no healthy upstream. + bool no_healthy_upstream = 2; + + // Indicates an there was an upstream request timeout. + bool upstream_request_timeout = 3; + + // Indicates local codec level reset was sent on the stream. + bool local_reset = 4; + + // Indicates remote codec level reset was received on the stream. + bool upstream_remote_reset = 5; + + // Indicates there was a local reset by a connection pool due to an initial connection failure. + bool upstream_connection_failure = 6; + + // Indicates the stream was reset due to an upstream connection termination. + bool upstream_connection_termination = 7; + + // Indicates the stream was reset because of a resource overflow. + bool upstream_overflow = 8; + + // Indicates no route was found for the request. + bool no_route_found = 9; + + // Indicates that the request was delayed before proxying. + bool delay_injected = 10; + + // Indicates that the request was aborted with an injected error code. + bool fault_injected = 11; + + // Indicates that the request was rate-limited locally. + bool rate_limited = 12; + + message Unauthorized { + // Reasons why the request was unauthorized + enum Reason { + REASON_UNSPECIFIED = 0; + // The request was denied by the external authorization service. + EXTERNAL_SERVICE = 1; + } + + Reason reason = 1; + } + + // 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; + + // Indicates the stream was reset due to a downstream connection termination. + bool downstream_connection_termination = 15; + + // Indicates that the upstream retry limit was exceeded, resulting in a downstream error. + bool upstream_retry_limit_exceeded = 16; + + // Indicates that the stream idle timeout was hit, resulting in a downstream 408. + bool stream_idle_timeout = 17; + + // Indicates that the request was rejected because an envoy request header failed strict + // validation. + bool invalid_envoy_request_headers = 18; +} + +// Properties of a negotiated TLS connection. +message TLSProperties { + enum TLSVersion { + VERSION_UNSPECIFIED = 0; + TLSv1 = 1; + TLSv1_1 = 2; + TLSv1_2 = 3; + TLSv1_3 = 4; + } + // Version of TLS that was negotiated. + TLSVersion tls_version = 1; + + // TLS cipher suite negotiated during handshake. The value is a + // four-digit hex code defined by the IANA TLS Cipher Suite Registry + // (e.g. ``009C`` for ``TLS_RSA_WITH_AES_128_GCM_SHA256``). + // + // Here it is expressed as an integer. + google.protobuf.UInt32Value tls_cipher_suite = 2; + + // SNI hostname from handshake. + string tls_sni_hostname = 3; + + message CertificateProperties { + message SubjectAltName { + oneof san { + string uri = 1; + // [#not-implemented-hide:] + string dns = 2; + } + } + + // SANs present in the certificate. + repeated SubjectAltName subject_alt_name = 1; + + // The subject field of the certificate. + string subject = 2; + } + + // Properties of the local certificate used to negotiate TLS. + CertificateProperties local_certificate_properties = 4; + + // Properties of the peer certificate used to negotiate TLS. + CertificateProperties peer_certificate_properties = 5; + + // The TLS session ID. + string tls_session_id = 6; +} + +message HTTPRequestProperties { + // The request method (RFC 7231/2616). + // [#comment:TODO(htuch): add (validate.rules).enum.defined_only = true once + // https://github.com/lyft/protoc-gen-validate/issues/42 is resolved.] + envoy.api.v3alpha.core.RequestMethod request_method = 1; + + // The scheme portion of the incoming request URI. + string scheme = 2; + + // HTTP/2 ``:authority`` or HTTP/1.1 ``Host`` header value. + string authority = 3; + + // The port of the incoming request URI + // (unused currently, as port is composed onto authority). + google.protobuf.UInt32Value port = 4; + + // The path portion from the incoming request URI. + string path = 5; + + // Value of the ``User-Agent`` request header. + string user_agent = 6; + + // Value of the ``Referer`` request header. + string referer = 7; + + // Value of the ``X-Forwarded-For`` request header. + string forwarded_for = 8; + + // Value of the ``X-Request-Id`` request header + // + // This header is used by Envoy to uniquely identify a request. + // It will be generated for all external requests and internal requests that + // do not already have a request ID. + string request_id = 9; + + // Value of the ``X-Envoy-Original-Path`` request header. + string original_path = 10; + + // Size of the HTTP request headers in bytes. + // + // This value is captured from the OSI layer 7 perspective, i.e. it does not + // include overhead from framing or encoding at other networking layers. + uint64 request_headers_bytes = 11; + + // Size of the HTTP request body in bytes. + // + // This value is captured from the OSI layer 7 perspective, i.e. it does not + // include overhead from framing or encoding at other networking layers. + uint64 request_body_bytes = 12; + + // Map of additional headers that have been configured to be logged. + map request_headers = 13; +} + +message HTTPResponseProperties { + // The HTTP response code returned by Envoy. + google.protobuf.UInt32Value response_code = 1; + + // Size of the HTTP response headers in bytes. + // + // This value is captured from the OSI layer 7 perspective, i.e. it does not + // include overhead from framing or encoding at other networking layers. + uint64 response_headers_bytes = 2; + + // Size of the HTTP response body in bytes. + // + // This value is captured from the OSI layer 7 perspective, i.e. it does not + // include overhead from framing or encoding at other networking layers. + uint64 response_body_bytes = 3; + + // Map of additional headers configured to be logged. + map response_headers = 4; + + // Map of trailers configured to be logged. + map response_trailers = 5; + + // The HTTP response code details. + string response_code_details = 6; +} diff --git a/api/envoy/data/cluster/v2alpha/BUILD b/api/envoy/data/cluster/v2alpha/BUILD index 00edd8294b..4d921f4d97 100644 --- a/api/envoy/data/cluster/v2alpha/BUILD +++ b/api/envoy/data/cluster/v2alpha/BUILD @@ -1,7 +1,9 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package() + api_proto_library( name = "outlier_detection_event", srcs = ["outlier_detection_event.proto"], diff --git a/api/envoy/data/cluster/v2alpha/outlier_detection_event.proto b/api/envoy/data/cluster/v2alpha/outlier_detection_event.proto index 65559abc54..1273f84d6d 100644 --- a/api/envoy/data/cluster/v2alpha/outlier_detection_event.proto +++ b/api/envoy/data/cluster/v2alpha/outlier_detection_event.proto @@ -10,9 +10,6 @@ import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Outlier detection logging events] // :ref:`Outlier detection logging `. @@ -21,7 +18,7 @@ message OutlierDetectionEvent { // In case of eject represents type of ejection that took place. OutlierEjectionType type = 1 [(validate.rules).enum.defined_only = true]; // Timestamp for event. - google.protobuf.Timestamp timestamp = 2 [(gogoproto.stdtime) = true]; + google.protobuf.Timestamp timestamp = 2; // The time in seconds since the last action (either an ejection or unejection) took place. google.protobuf.UInt64Value secs_since_last_action = 3; // The :ref:`cluster ` that owns the ejected host. @@ -42,6 +39,7 @@ message OutlierDetectionEvent { option (validate.required) = true; OutlierEjectSuccessRate eject_success_rate_event = 9; OutlierEjectConsecutive eject_consecutive_event = 10; + OutlierEjectFailurePercentage eject_failure_percentage_event = 11; } } @@ -78,6 +76,12 @@ enum OutlierEjectionType { // is set to *true*. // See :ref:`Cluster outlier detection ` documentation for SUCCESS_RATE_LOCAL_ORIGIN = 4; + // Runs over aggregated success rate statistics from every host in cluster and selects hosts for + // which ratio of failed replies is above configured value. + FAILURE_PERCENTAGE = 5; + // Runs over aggregated success rate statistics for local origin failures from every host in + // cluster and selects hosts for which ratio of failed replies is above configured value. + FAILURE_PERCENTAGE_LOCAL_ORIGIN = 6; } // Represents possible action applied to upstream host @@ -100,3 +104,8 @@ message OutlierEjectSuccessRate { message OutlierEjectConsecutive { } + +message OutlierEjectFailurePercentage { + // Host's success rate at the time of the ejection event on a 0-100 range. + uint32 host_success_rate = 1 [(validate.rules).uint32.lte = 100]; +} diff --git a/api/envoy/data/cluster/v3alpha/BUILD b/api/envoy/data/cluster/v3alpha/BUILD new file mode 100644 index 0000000000..4d921f4d97 --- /dev/null +++ b/api/envoy/data/cluster/v3alpha/BUILD @@ -0,0 +1,13 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package() + +api_proto_library( + name = "outlier_detection_event", + srcs = ["outlier_detection_event.proto"], + visibility = [ + "//visibility:public", + ], +) diff --git a/api/envoy/data/cluster/v3alpha/outlier_detection_event.proto b/api/envoy/data/cluster/v3alpha/outlier_detection_event.proto new file mode 100644 index 0000000000..3051636597 --- /dev/null +++ b/api/envoy/data/cluster/v3alpha/outlier_detection_event.proto @@ -0,0 +1,99 @@ +syntax = "proto3"; + +package envoy.data.cluster.v3alpha; + +option java_outer_classname = "OutlierDetectionEventProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.data.cluster.v3alpha"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Outlier detection logging events] +// :ref:`Outlier detection logging `. + +message OutlierDetectionEvent { + // In case of eject represents type of ejection that took place. + OutlierEjectionType type = 1 [(validate.rules).enum.defined_only = true]; + // Timestamp for event. + google.protobuf.Timestamp timestamp = 2; + // The time in seconds since the last action (either an ejection or unejection) took place. + google.protobuf.UInt64Value secs_since_last_action = 3; + // The :ref:`cluster ` that owns the ejected host. + string cluster_name = 4 [(validate.rules).string.min_bytes = 1]; + // The URL of the ejected host. E.g., ``tcp://1.2.3.4:80``. + string upstream_url = 5 [(validate.rules).string.min_bytes = 1]; + // The action that took place. + Action action = 6 [(validate.rules).enum.defined_only = true]; + // If ``action`` is ``eject``, specifies the number of times the host has been ejected (local to + // that Envoy and gets reset if the host gets removed from the upstream cluster for any reason and + // then re-added). + uint32 num_ejections = 7; + // If ``action`` is ``eject``, specifies if the ejection was enforced. ``true`` means the host was + // ejected. ``false`` means the event was logged but the host was not actually ejected. + bool enforced = 8; + + oneof event { + option (validate.required) = true; + OutlierEjectSuccessRate eject_success_rate_event = 9; + OutlierEjectConsecutive eject_consecutive_event = 10; + } +} + +// Type of ejection that took place +enum OutlierEjectionType { + // In case upstream host returns certain number of consecutive 5xx. + // If + // :ref:`outlier_detection.split_external_local_origin_errors` + // is *false*, all type of errors are treated as HTTP 5xx errors. + // See :ref:`Cluster outlier detection ` documentation for + // details. + CONSECUTIVE_5XX = 0; + // In case upstream host returns certain number of consecutive gateway errors + CONSECUTIVE_GATEWAY_FAILURE = 1; + // Runs over aggregated success rate statistics from every host in cluster + // and selects hosts for which ratio of successful replies deviates from other hosts + // in the cluster. + // If + // :ref:`outlier_detection.split_external_local_origin_errors` + // is *false*, all errors (externally and locally generated) are used to calculate success rate + // statistics. See :ref:`Cluster outlier detection ` + // documentation for details. + SUCCESS_RATE = 2; + // Consecutive local origin failures: Connection failures, resets, timeouts, etc + // This type of ejection happens only when + // :ref:`outlier_detection.split_external_local_origin_errors` + // is set to *true*. + // See :ref:`Cluster outlier detection ` documentation for + CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 3; + // Runs over aggregated success rate statistics for local origin failures + // for all hosts in the cluster and selects hosts for which success rate deviates from other + // hosts in the cluster. This type of ejection happens only when + // :ref:`outlier_detection.split_external_local_origin_errors` + // is set to *true*. + // See :ref:`Cluster outlier detection ` documentation for + SUCCESS_RATE_LOCAL_ORIGIN = 4; +} + +// Represents possible action applied to upstream host +enum Action { + // In case host was excluded from service + EJECT = 0; + // In case host was brought back into service + UNEJECT = 1; +} + +message OutlierEjectSuccessRate { + // Host’s success rate at the time of the ejection event on a 0-100 range. + uint32 host_success_rate = 1 [(validate.rules).uint32.lte = 100]; + // Average success rate of the hosts in the cluster at the time of the ejection event on a 0-100 + // range. + uint32 cluster_average_success_rate = 2 [(validate.rules).uint32.lte = 100]; + // Success rate ejection threshold at the time of the ejection event. + uint32 cluster_success_rate_ejection_threshold = 3 [(validate.rules).uint32.lte = 100]; +} + +message OutlierEjectConsecutive { +} diff --git a/api/envoy/data/core/v2alpha/BUILD b/api/envoy/data/core/v2alpha/BUILD index 8320031d84..3310323483 100644 --- a/api/envoy/data/core/v2alpha/BUILD +++ b/api/envoy/data/core/v2alpha/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library( name = "health_check_event", srcs = ["health_check_event.proto"], diff --git a/api/envoy/data/core/v2alpha/health_check_event.proto b/api/envoy/data/core/v2alpha/health_check_event.proto index adfb6c67e5..c5b2f70a5e 100644 --- a/api/envoy/data/core/v2alpha/health_check_event.proto +++ b/api/envoy/data/core/v2alpha/health_check_event.proto @@ -11,9 +11,6 @@ import "envoy/api/v2/core/address.proto"; import "google/protobuf/timestamp.proto"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Health check logging events] // :ref:`Health check logging `. @@ -43,7 +40,7 @@ message HealthCheckEvent { } // Timestamp for event. - google.protobuf.Timestamp timestamp = 6 [(gogoproto.stdtime) = true]; + google.protobuf.Timestamp timestamp = 6; } enum HealthCheckFailureType { diff --git a/api/envoy/data/core/v3alpha/BUILD b/api/envoy/data/core/v3alpha/BUILD new file mode 100644 index 0000000000..6c44f3e4d7 --- /dev/null +++ b/api/envoy/data/core/v3alpha/BUILD @@ -0,0 +1,19 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library( + name = "health_check_event", + srcs = ["health_check_event.proto"], + visibility = [ + "//visibility:public", + ], + deps = [ + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + ], +) diff --git a/api/envoy/data/core/v3alpha/health_check_event.proto b/api/envoy/data/core/v3alpha/health_check_event.proto new file mode 100644 index 0000000000..c714743f70 --- /dev/null +++ b/api/envoy/data/core/v3alpha/health_check_event.proto @@ -0,0 +1,82 @@ +syntax = "proto3"; + +package envoy.data.core.v3alpha; + +option java_outer_classname = "HealthCheckEventProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.data.core.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; + +import "google/protobuf/timestamp.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Health check logging events] +// :ref:`Health check logging `. + +message HealthCheckEvent { + HealthCheckerType health_checker_type = 1 [(validate.rules).enum.defined_only = true]; + envoy.api.v3alpha.core.Address host = 2; + string cluster_name = 3 [(validate.rules).string.min_bytes = 1]; + + oneof event { + option (validate.required) = true; + + // Host ejection. + HealthCheckEjectUnhealthy eject_unhealthy_event = 4; + + // Host addition. + HealthCheckAddHealthy add_healthy_event = 5; + + // Host failure. + HealthCheckFailure health_check_failure_event = 7; + + // Healthy host became degraded. + DegradedHealthyHost degraded_healthy_host = 8; + + // A degraded host returned to being healthy. + NoLongerDegradedHost no_longer_degraded_host = 9; + } + + // Timestamp for event. + google.protobuf.Timestamp timestamp = 6; +} + +enum HealthCheckFailureType { + ACTIVE = 0; + PASSIVE = 1; + NETWORK = 2; +} + +enum HealthCheckerType { + HTTP = 0; + TCP = 1; + GRPC = 2; + REDIS = 3; +} + +message HealthCheckEjectUnhealthy { + // The type of failure that caused this ejection. + HealthCheckFailureType failure_type = 1 [(validate.rules).enum.defined_only = true]; +} + +message HealthCheckAddHealthy { + // Whether this addition is the result of the first ever health check on a host, in which case + // the configured :ref:`healthy threshold ` + // is bypassed and the host is immediately added. + bool first_check = 1; +} + +message HealthCheckFailure { + // The type of failure that caused this event. + HealthCheckFailureType failure_type = 1 [(validate.rules).enum.defined_only = true]; + // Whether this event is the result of the first ever health check on a host. + bool first_check = 2; +} + +message DegradedHealthyHost { +} + +message NoLongerDegradedHost { +} diff --git a/api/envoy/data/tap/v2alpha/BUILD b/api/envoy/data/tap/v2alpha/BUILD index 1b373eee86..bf108c4792 100644 --- a/api/envoy/data/tap/v2alpha/BUILD +++ b/api/envoy/data/tap/v2alpha/BUILD @@ -1,7 +1,11 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + deps = ["//envoy/api/v2/core"], +) + api_proto_library_internal( name = "common", srcs = ["common.proto"], diff --git a/api/envoy/data/tap/v2alpha/transport.proto b/api/envoy/data/tap/v2alpha/transport.proto index 3b8c244b9b..c3a3d8b8eb 100644 --- a/api/envoy/data/tap/v2alpha/transport.proto +++ b/api/envoy/data/tap/v2alpha/transport.proto @@ -9,7 +9,6 @@ package envoy.data.tap.v2alpha; option java_outer_classname = "TransportProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.data.tap.v2alpha"; -option go_package = "v2"; import "envoy/api/v2/core/address.proto"; import "envoy/data/tap/v2alpha/common.proto"; diff --git a/api/envoy/data/tap/v3alpha/BUILD b/api/envoy/data/tap/v3alpha/BUILD new file mode 100644 index 0000000000..33d151e333 --- /dev/null +++ b/api/envoy/data/tap/v3alpha/BUILD @@ -0,0 +1,41 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["//envoy/api/v3alpha/core"], +) + +api_proto_library_internal( + name = "common", + srcs = ["common.proto"], +) + +api_proto_library_internal( + name = "transport", + srcs = ["transport.proto"], + deps = [ + ":common", + "//envoy/api/v3alpha/core:address", + ], +) + +api_proto_library_internal( + name = "http", + srcs = ["http.proto"], + deps = [ + ":common", + "//envoy/api/v3alpha/core:base", + ], +) + +api_proto_library_internal( + name = "wrapper", + srcs = ["wrapper.proto"], + visibility = ["//visibility:public"], + deps = [ + ":http", + ":transport", + "//envoy/api/v3alpha/core:address", + ], +) diff --git a/api/envoy/data/tap/v3alpha/common.proto b/api/envoy/data/tap/v3alpha/common.proto new file mode 100644 index 0000000000..21da336e74 --- /dev/null +++ b/api/envoy/data/tap/v3alpha/common.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package envoy.data.tap.v3alpha; + +option java_outer_classname = "CommonProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.data.tap.v3alpha"; + +// [#protodoc-title: Tap common data] + +// Wrapper for tapped body data. This includes HTTP request/response body, transport socket received +// and transmitted data, etc. +message Body { + oneof body_type { + // Body data as bytes. By default, tap body data will be present in this field, as the proto + // `bytes` type can contain any valid byte. + bytes as_bytes = 1; + + // Body data as string. This field is only used when the :ref:`JSON_BODY_AS_STRING + // ` sink + // format type is selected. See the documentation for that option for why this is useful. + string as_string = 2; + } + + // Specifies whether body data has been truncated to fit within the specified + // :ref:`max_buffered_rx_bytes + // ` and + // :ref:`max_buffered_tx_bytes + // ` settings. + bool truncated = 3; +} diff --git a/api/envoy/data/tap/v3alpha/http.proto b/api/envoy/data/tap/v3alpha/http.proto new file mode 100644 index 0000000000..36d82f7a04 --- /dev/null +++ b/api/envoy/data/tap/v3alpha/http.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +package envoy.data.tap.v3alpha; + +option java_outer_classname = "HttpProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.data.tap.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/data/tap/v3alpha/common.proto"; + +// [#protodoc-title: HTTP tap data] + +// A fully buffered HTTP trace message. +message HttpBufferedTrace { + // HTTP message wrapper. + message Message { + // Message headers. + repeated api.v3alpha.core.HeaderValue headers = 1; + + // Message body. + Body body = 2; + + // Message trailers. + repeated api.v3alpha.core.HeaderValue trailers = 3; + } + + // Request message. + Message request = 1; + + // Response message. + Message response = 2; +} + +// A streamed HTTP trace segment. Multiple segments make up a full trace. +message HttpStreamedTraceSegment { + // Trace ID unique to the originating Envoy only. Trace IDs can repeat and should not be used + // for long term stable uniqueness. + uint64 trace_id = 1; + + oneof message_piece { + // Request headers. + api.v3alpha.core.HeaderMap request_headers = 2; + + // Request body chunk. + Body request_body_chunk = 3; + + // Request trailers. + api.v3alpha.core.HeaderMap request_trailers = 4; + + // Response headers. + api.v3alpha.core.HeaderMap response_headers = 5; + + // Response body chunk. + Body response_body_chunk = 6; + + // Response trailers. + api.v3alpha.core.HeaderMap response_trailers = 7; + } +} diff --git a/api/envoy/data/tap/v3alpha/transport.proto b/api/envoy/data/tap/v3alpha/transport.proto new file mode 100644 index 0000000000..e35f036f5d --- /dev/null +++ b/api/envoy/data/tap/v3alpha/transport.proto @@ -0,0 +1,96 @@ +syntax = "proto3"; + +// [#protodoc-title: Transport tap data] +// Trace format for the tap transport socket extension. This dumps plain text read/write +// sequences on a socket. + +package envoy.data.tap.v3alpha; + +option java_outer_classname = "TransportProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.data.tap.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/data/tap/v3alpha/common.proto"; + +import "google/protobuf/timestamp.proto"; + +// Connection properties. +message Connection { + // Local address. + envoy.api.v3alpha.core.Address local_address = 2; + + // Remote address. + envoy.api.v3alpha.core.Address remote_address = 3; +} + +// Event in a socket trace. +message SocketEvent { + // Timestamp for event. + google.protobuf.Timestamp timestamp = 1; + + // Data read by Envoy from the transport socket. + message Read { + // Binary data read. + Body data = 1; + + // TODO(htuch): Half-close for reads. + } + + // Data written by Envoy to the transport socket. + message Write { + // Binary data written. + Body data = 1; + + // Stream was half closed after this write. + bool end_stream = 2; + } + + // The connection was closed. + message Closed { + // TODO(mattklein123): Close event type. + } + + // Read or write with content as bytes string. + oneof event_selector { + Read read = 2; + Write write = 3; + Closed closed = 4; + } +} + +// Sequence of read/write events that constitute a buffered trace on a socket. +message SocketBufferedTrace { + // Trace ID unique to the originating Envoy only. Trace IDs can repeat and should not be used + // for long term stable uniqueness. Matches connection IDs used in Envoy logs. + uint64 trace_id = 1; + + // Connection properties. + Connection connection = 2; + + // Sequence of observed events. + repeated SocketEvent events = 3; + + // Set to true if read events were truncated due to the :ref:`max_buffered_rx_bytes + // ` setting. + bool read_truncated = 4; + + // Set to true if write events were truncated due to the :ref:`max_buffered_tx_bytes + // ` setting. + bool write_truncated = 5; +} + +// A streamed socket trace segment. Multiple segments make up a full trace. +message SocketStreamedTraceSegment { + // Trace ID unique to the originating Envoy only. Trace IDs can repeat and should not be used + // for long term stable uniqueness. Matches connection IDs used in Envoy logs. + uint64 trace_id = 1; + + oneof message_piece { + // Connection properties. + Connection connection = 2; + + // Socket event. + SocketEvent event = 3; + } +} diff --git a/api/envoy/data/tap/v3alpha/wrapper.proto b/api/envoy/data/tap/v3alpha/wrapper.proto new file mode 100644 index 0000000000..1aff052e90 --- /dev/null +++ b/api/envoy/data/tap/v3alpha/wrapper.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +import "envoy/data/tap/v3alpha/http.proto"; +import "envoy/data/tap/v3alpha/transport.proto"; + +import "validate/validate.proto"; + +package envoy.data.tap.v3alpha; + +option java_outer_classname = "WrapperProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.data.tap.v3alpha"; + +// [#protodoc-title: Tap data wrappers] + +// Wrapper for all fully buffered and streamed tap traces that Envoy emits. This is required for +// sending traces over gRPC APIs or more easily persisting binary messages to files. +message TraceWrapper { + oneof trace { + option (validate.required) = true; + + // An HTTP buffered tap trace. + HttpBufferedTrace http_buffered_trace = 1; + + // An HTTP streamed tap trace segment. + HttpStreamedTraceSegment http_streamed_trace_segment = 2; + + // A socket buffered tap trace. + SocketBufferedTrace socket_buffered_trace = 3; + + // A socket streamed tap trace segment. + SocketStreamedTraceSegment socket_streamed_trace_segment = 4; + } +} diff --git a/api/envoy/service/accesslog/v2/BUILD b/api/envoy/service/accesslog/v2/BUILD index 1dad944704..d4f7c30036 100644 --- a/api/envoy/service/accesslog/v2/BUILD +++ b/api/envoy/service/accesslog/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v2/core", + "//envoy/data/accesslog/v2:pkg", + ], +) + api_proto_library_internal( name = "als", srcs = ["als.proto"], @@ -12,12 +20,3 @@ api_proto_library_internal( "//envoy/data/accesslog/v2:accesslog", ], ) - -api_go_grpc_library( - name = "als", - proto = ":als", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "//envoy/data/accesslog/v2:accesslog_go_proto", - ], -) diff --git a/api/envoy/service/accesslog/v2/als.proto b/api/envoy/service/accesslog/v2/als.proto index 1ee6ccd009..c06199a2b2 100644 --- a/api/envoy/service/accesslog/v2/als.proto +++ b/api/envoy/service/accesslog/v2/als.proto @@ -5,7 +5,6 @@ package envoy.service.accesslog.v2; option java_outer_classname = "AlsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.accesslog.v2"; -option go_package = "v2"; option java_generic_services = true; import "envoy/api/v2/core/base.proto"; @@ -53,7 +52,6 @@ message StreamAccessLogsMessage { [(validate.rules).repeated .min_items = 1]; } - // [#not-implemented-hide:] // Wrapper for batches of TCP access log entries. message TCPAccessLogEntries { repeated envoy.data.accesslog.v2.TCPAccessLogEntry log_entry = 1 @@ -67,7 +65,6 @@ message StreamAccessLogsMessage { HTTPAccessLogEntries http_logs = 2; - // [#not-implemented-hide:] TCPAccessLogEntries tcp_logs = 3; } } diff --git a/api/envoy/service/accesslog/v3alpha/BUILD b/api/envoy/service/accesslog/v3alpha/BUILD new file mode 100644 index 0000000000..0bb8716b82 --- /dev/null +++ b/api/envoy/service/accesslog/v3alpha/BUILD @@ -0,0 +1,22 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/data/accesslog/v3alpha:pkg", + ], +) + +api_proto_library_internal( + name = "als", + srcs = ["als.proto"], + has_services = 1, + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:grpc_service", + "//envoy/data/accesslog/v3alpha:accesslog", + ], +) diff --git a/api/envoy/service/accesslog/v3alpha/als.proto b/api/envoy/service/accesslog/v3alpha/als.proto new file mode 100644 index 0000000000..ad05b823d1 --- /dev/null +++ b/api/envoy/service/accesslog/v3alpha/als.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +package envoy.service.accesslog.v3alpha; + +option java_outer_classname = "AlsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.accesslog.v3alpha"; +option java_generic_services = true; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/data/accesslog/v3alpha/accesslog.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: gRPC Access Log Service (ALS)] + +// Service for streaming access logs from Envoy to an access log server. +service AccessLogService { + // Envoy will connect and send StreamAccessLogsMessage messages forever. It does not expect any + // response to be sent as nothing would be done in the case of failure. The server should + // disconnect if it expects Envoy to reconnect. In the future we may decide to add a different + // API for "critical" access logs in which Envoy will buffer access logs for some period of time + // until it gets an ACK so it could then retry. This API is designed for high throughput with the + // expectation that it might be lossy. + rpc StreamAccessLogs(stream StreamAccessLogsMessage) returns (StreamAccessLogsResponse) { + } +} + +// Empty response for the StreamAccessLogs API. Will never be sent. See below. +message StreamAccessLogsResponse { +} + +// Stream message for the StreamAccessLogs API. Envoy will open a stream to the server and stream +// access logs without ever expecting a response. +message StreamAccessLogsMessage { + message Identifier { + // The node sending the access log messages over the stream. + envoy.api.v3alpha.core.Node node = 1 [(validate.rules).message.required = true]; + + // The friendly name of the log configured in :ref:`CommonGrpcAccessLogConfig + // `. + string log_name = 2 [(validate.rules).string.min_bytes = 1]; + } + + // Identifier data that will only be sent in the first message on the stream. This is effectively + // structured metadata and is a performance optimization. + Identifier identifier = 1; + + // Wrapper for batches of HTTP access log entries. + message HTTPAccessLogEntries { + repeated envoy.data.accesslog.v3alpha.HTTPAccessLogEntry log_entry = 1 + [(validate.rules).repeated .min_items = 1]; + } + + // Wrapper for batches of TCP access log entries. + message TCPAccessLogEntries { + repeated envoy.data.accesslog.v3alpha.TCPAccessLogEntry log_entry = 1 + [(validate.rules).repeated .min_items = 1]; + } + + // Batches of log entries of a single type. Generally speaking, a given stream should only + // ever include one type of log entry. + oneof log_entries { + option (validate.required) = true; + + HTTPAccessLogEntries http_logs = 2; + + TCPAccessLogEntries tcp_logs = 3; + } +} diff --git a/api/envoy/service/auth/v2/BUILD b/api/envoy/service/auth/v2/BUILD index 57041668dd..91a4eeebbf 100644 --- a/api/envoy/service/auth/v2/BUILD +++ b/api/envoy/service/auth/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v2/core", + "//envoy/type", + ], +) + api_proto_library_internal( name = "attribute_context", srcs = [ diff --git a/api/envoy/service/auth/v2/attribute_context.proto b/api/envoy/service/auth/v2/attribute_context.proto index f5b723e7b6..cfb71ec0ce 100644 --- a/api/envoy/service/auth/v2/attribute_context.proto +++ b/api/envoy/service/auth/v2/attribute_context.proto @@ -7,11 +7,9 @@ option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.auth.v2"; import "envoy/api/v2/core/address.proto"; +import "envoy/api/v2/core/base.proto"; import "google/protobuf/timestamp.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.stable_marshaler_all) = true; // [#protodoc-title: Attribute Context ] @@ -49,8 +47,8 @@ message AttributeContext { // The authenticated identity of this peer. // For example, the identity associated with the workload such as a service account. // If an X.509 certificate is used to assert the identity this field should be sourced from - // `Subject` or `Subject Alternative Names`. The primary identity should be the principal. - // The principal format is issuer specific. + // `URI Subject Alternative Names`, `DNS Subject Alternate Names` or `Subject` in that order. + // The primary identity should be the principal. The principal format is issuer specific. // // Example: // * SPIFFE format is `spiffe://trust-domain/path` @@ -135,6 +133,9 @@ message AttributeContext { // information to the auth server without modifying the proto definition. It maps to the // internal opaque context in the filter chain. map context_extensions = 10; + + // Dynamic metadata associated with the request. + envoy.api.v2.core.Metadata metadata_context = 11; } // The following items are left out of this proto diff --git a/api/envoy/service/auth/v2/external_auth.proto b/api/envoy/service/auth/v2/external_auth.proto index 0f723c98e4..8a3d4f1a62 100644 --- a/api/envoy/service/auth/v2/external_auth.proto +++ b/api/envoy/service/auth/v2/external_auth.proto @@ -5,7 +5,6 @@ package envoy.service.auth.v2; option java_outer_classname = "ExternalAuthProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.auth.v2"; -option go_package = "v2"; option java_generic_services = true; import "envoy/api/v2/core/base.proto"; diff --git a/api/envoy/service/auth/v2alpha/BUILD b/api/envoy/service/auth/v2alpha/BUILD index 1d9873a5ff..1940f4f2f8 100644 --- a/api/envoy/service/auth/v2alpha/BUILD +++ b/api/envoy/service/auth/v2alpha/BUILD @@ -1,7 +1,14 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + has_services = True, + deps = [ + "//envoy/service/auth/v2:pkg", + ], +) + api_proto_library_internal( name = "external_auth", srcs = [ diff --git a/api/envoy/service/auth/v2alpha/external_auth.proto b/api/envoy/service/auth/v2alpha/external_auth.proto index bdf0d2e485..85e9c12c6a 100644 --- a/api/envoy/service/auth/v2alpha/external_auth.proto +++ b/api/envoy/service/auth/v2alpha/external_auth.proto @@ -2,8 +2,6 @@ syntax = "proto3"; package envoy.service.auth.v2alpha; -option go_package = "v2alpha"; - option java_multiple_files = true; option java_generic_services = true; option java_outer_classname = "CertsProto"; diff --git a/api/envoy/service/auth/v3alpha/BUILD b/api/envoy/service/auth/v3alpha/BUILD new file mode 100644 index 0000000000..f6a70cb5b9 --- /dev/null +++ b/api/envoy/service/auth/v3alpha/BUILD @@ -0,0 +1,36 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/type", + ], +) + +api_proto_library_internal( + name = "attribute_context", + srcs = [ + "attribute_context.proto", + ], + deps = [ + "//envoy/api/v3alpha/core:address", + "//envoy/api/v3alpha/core:base", + ], +) + +api_proto_library_internal( + name = "external_auth", + srcs = [ + "external_auth.proto", + ], + has_services = 1, + visibility = ["//visibility:public"], + deps = [ + ":attribute_context", + "//envoy/api/v3alpha/core:base", + "//envoy/type:http_status", + ], +) diff --git a/api/envoy/service/auth/v3alpha/attribute_context.proto b/api/envoy/service/auth/v3alpha/attribute_context.proto new file mode 100644 index 0000000000..95ac3428fc --- /dev/null +++ b/api/envoy/service/auth/v3alpha/attribute_context.proto @@ -0,0 +1,151 @@ +syntax = "proto3"; + +package envoy.service.auth.v3alpha; + +option java_outer_classname = "AttributeContextProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.auth.v3alpha"; + +import "envoy/api/v3alpha/core/address.proto"; +import "envoy/api/v3alpha/core/base.proto"; + +import "google/protobuf/timestamp.proto"; + +// [#protodoc-title: Attribute Context ] + +// See :ref:`network filter configuration overview ` +// and :ref:`HTTP filter configuration overview `. + +// An attribute is a piece of metadata that describes an activity on a network. +// For example, the size of an HTTP request, or the status code of an HTTP response. +// +// Each attribute has a type and a name, which is logically defined as a proto message field +// of the `AttributeContext`. The `AttributeContext` is a collection of individual attributes +// supported by Envoy authorization system. +message AttributeContext { + // This message defines attributes for a node that handles a network request. + // The node can be either a service or an application that sends, forwards, + // or receives the request. Service peers should fill in the `service`, + // `principal`, and `labels` as appropriate. + message Peer { + // The address of the peer, this is typically the IP address. + // It can also be UDS path, or others. + envoy.api.v3alpha.core.Address address = 1; + + // The canonical service name of the peer. + // It should be set to :ref:`the HTTP x-envoy-downstream-service-cluster + // ` + // If a more trusted source of the service name is available through mTLS/secure naming, it + // should be used. + string service = 2; + + // The labels associated with the peer. + // These could be pod labels for Kubernetes or tags for VMs. + // The source of the labels could be an X.509 certificate or other configuration. + map labels = 3; + + // The authenticated identity of this peer. + // For example, the identity associated with the workload such as a service account. + // If an X.509 certificate is used to assert the identity this field should be sourced from + // `URI Subject Alternative Names`, `DNS Subject Alternate Names` or `Subject` in that order. + // The primary identity should be the principal. The principal format is issuer specific. + // + // Example: + // * SPIFFE format is `spiffe://trust-domain/path` + // * Google account format is `https://accounts.google.com/{userid}` + string principal = 4; + } + + // Represents a network request, such as an HTTP request. + message Request { + // The timestamp when the proxy receives the first byte of the request. + google.protobuf.Timestamp time = 1; + + // Represents an HTTP request or an HTTP-like request. + HttpRequest http = 2; + + // More request types are added here as necessary. + } + + // This message defines attributes for an HTTP request. + // HTTP/1.x, HTTP/2, gRPC are all considered as HTTP requests. + message HttpRequest { + // The unique ID for a request, which can be propagated to downstream + // systems. The ID should have low probability of collision + // within a single day for a specific service. + // For HTTP requests, it should be X-Request-ID or equivalent. + string id = 1; + + // The HTTP request method, such as `GET`, `POST`. + string method = 2; + + // The HTTP request headers. If multiple headers share the same key, they + // must be merged according to the HTTP spec. All header keys must be + // lowercased, because HTTP header keys are case-insensitive. + map headers = 3; + + // The request target, as it appears in the first line of the HTTP request. This includes + // the URL path and query-string. No decoding is performed. + string path = 4; + + // The HTTP request `Host` or 'Authority` header value. + string host = 5; + + // The HTTP URL scheme, such as `http` and `https`. + string scheme = 6; + + // This field is always empty, and exists for compatibility reasons. The HTTP URL query is + // included in `path` field. + string query = 7; + + // This field is always empty, and exists for compatibility reasons. The URL fragment is + // not submitted as part of HTTP requests; it is unknowable. + string fragment = 8; + + // The HTTP request size in bytes. If unknown, it must be -1. + int64 size = 9; + + // The network protocol used with the request, such as "HTTP/1.0", "HTTP/1.1", or "HTTP/2". + // + // See :repo:`headers.h:ProtocolStrings ` for a list of all + // possible values. + string protocol = 10; + + // The HTTP request body. + string body = 11; + } + + // The source of a network activity, such as starting a TCP connection. + // In a multi hop network activity, the source represents the sender of the + // last hop. + Peer source = 1; + + // The destination of a network activity, such as accepting a TCP connection. + // In a multi hop network activity, the destination represents the receiver of + // the last hop. + Peer destination = 2; + + // Represents a network request, such as an HTTP request. + Request request = 4; + + // This is analogous to http_request.headers, however these contents will not be sent to the + // upstream server. Context_extensions provide an extension mechanism for sending additional + // information to the auth server without modifying the proto definition. It maps to the + // internal opaque context in the filter chain. + map context_extensions = 10; + + // Dynamic metadata associated with the request. + envoy.api.v3alpha.core.Metadata metadata_context = 11; +} + +// The following items are left out of this proto +// Request.Auth field for jwt tokens +// Request.Api for api management +// Origin peer that originated the request +// Caching Protocol +// request_context return values to inject back into the filter chain +// peer.claims -- from X.509 extensions +// Configuration +// - field mask to send +// - which return values from request_context are copied back +// - which return values are copied into request_headers diff --git a/api/envoy/service/auth/v3alpha/external_auth.proto b/api/envoy/service/auth/v3alpha/external_auth.proto new file mode 100644 index 0000000000..0130040c14 --- /dev/null +++ b/api/envoy/service/auth/v3alpha/external_auth.proto @@ -0,0 +1,76 @@ +syntax = "proto3"; + +package envoy.service.auth.v3alpha; + +option java_outer_classname = "ExternalAuthProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.auth.v3alpha"; +option java_generic_services = true; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/type/http_status.proto"; +import "envoy/service/auth/v3alpha/attribute_context.proto"; + +import "google/rpc/status.proto"; +import "validate/validate.proto"; + +// [#protodoc-title: Authorization Service ] + +// The authorization service request messages used by external authorization :ref:`network filter +// ` and :ref:`HTTP filter `. + +// A generic interface for performing authorization check on incoming +// requests to a networked service. +service Authorization { + // Performs authorization check based on the attributes associated with the + // incoming request, and returns status `OK` or not `OK`. + rpc Check(CheckRequest) returns (CheckResponse); +} + +message CheckRequest { + // The request attributes. + AttributeContext attributes = 1; +} + +// HTTP attributes for a denied response. +message DeniedHttpResponse { + // This field allows the authorization service to send a HTTP response status + // code to the downstream client other than 403 (Forbidden). + envoy.type.HttpStatus status = 1 [(validate.rules).message.required = true]; + + // This field allows the authorization service to send HTTP response headers + // to the downstream client. + repeated envoy.api.v3alpha.core.HeaderValueOption headers = 2; + + // This field allows the authorization service to send a response body data + // to the downstream client. + string body = 3; +} + +// HTTP attributes for an ok response. +message OkHttpResponse { + // HTTP entity headers in addition to the original request headers. This allows the authorization + // service to append, to add or to override headers from the original request before + // dispatching it to the upstream. By setting `append` field to `true` in the `HeaderValueOption`, + // the filter will append the correspondent header value to the matched request header. Note that + // by Leaving `append` as false, the filter will either add a new header, or override an existing + // one if there is a match. + repeated envoy.api.v3alpha.core.HeaderValueOption headers = 2; +} + +// Intended for gRPC and Network Authorization servers `only`. +message CheckResponse { + // Status `OK` allows the request. Any other status indicates the request should be denied. + google.rpc.Status status = 1; + + // An message that contains HTTP response attributes. This message is + // used when the authorization service needs to send custom responses to the + // downstream client or, to modify/add request headers being dispatched to the upstream. + oneof http_response { + // Supplies http attributes for a denied response. + DeniedHttpResponse denied_response = 2; + + // Supplies http attributes for an ok response. + OkHttpResponse ok_response = 3; + } +} diff --git a/api/envoy/service/discovery/v2/BUILD b/api/envoy/service/discovery/v2/BUILD index a9c2efd02f..13db2701c2 100644 --- a/api/envoy/service/discovery/v2/BUILD +++ b/api/envoy/service/discovery/v2/BUILD @@ -1,21 +1,22 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 -api_proto_library_internal( - name = "ads", - srcs = ["ads.proto"], - has_services = 1, +api_proto_package( + has_services = True, deps = [ - "//envoy/api/v2:discovery", + "//envoy/api/v2", + "//envoy/api/v2/core", + "//envoy/api/v2/endpoint:pkg", ], ) -api_go_grpc_library( +api_proto_library_internal( name = "ads", - proto = ":ads", + srcs = ["ads.proto"], + has_services = 1, deps = [ - "//envoy/api/v2:discovery_go_proto", + "//envoy/api/v2:discovery", ], ) @@ -30,16 +31,6 @@ api_proto_library_internal( ], ) -api_go_grpc_library( - name = "hds", - proto = ":hds", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:health_check_go_proto", - "//envoy/api/v2/endpoint:endpoint_go_proto", - ], -) - api_proto_library_internal( name = "sds", srcs = ["sds.proto"], @@ -49,14 +40,6 @@ api_proto_library_internal( ], ) -api_go_grpc_library( - name = "sds", - proto = ":sds", - deps = [ - "//envoy/api/v2:discovery_go_proto", - ], -) - api_proto_library_internal( name = "rtds", srcs = ["rtds.proto"], @@ -65,11 +48,3 @@ api_proto_library_internal( "//envoy/api/v2:discovery", ], ) - -api_go_grpc_library( - name = "rtds", - proto = ":rtds", - deps = [ - "//envoy/api/v2:discovery_go_proto", - ], -) diff --git a/api/envoy/service/discovery/v2/ads.proto b/api/envoy/service/discovery/v2/ads.proto index 6a9d044ab4..45a7407f0c 100644 --- a/api/envoy/service/discovery/v2/ads.proto +++ b/api/envoy/service/discovery/v2/ads.proto @@ -5,7 +5,6 @@ package envoy.service.discovery.v2; option java_outer_classname = "AdsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.discovery.v2"; -option go_package = "v2"; option java_generic_services = true; import "envoy/api/v2/discovery.proto"; diff --git a/api/envoy/service/discovery/v3alpha/BUILD b/api/envoy/service/discovery/v3alpha/BUILD new file mode 100644 index 0000000000..138186e6ea --- /dev/null +++ b/api/envoy/service/discovery/v3alpha/BUILD @@ -0,0 +1,50 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v3alpha", + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/endpoint:pkg", + ], +) + +api_proto_library_internal( + name = "ads", + srcs = ["ads.proto"], + has_services = 1, + deps = [ + "//envoy/api/v3alpha:discovery", + ], +) + +api_proto_library_internal( + name = "hds", + srcs = ["hds.proto"], + has_services = 1, + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:health_check", + "//envoy/api/v3alpha/endpoint", + ], +) + +api_proto_library_internal( + name = "sds", + srcs = ["sds.proto"], + has_services = 1, + deps = [ + "//envoy/api/v3alpha:discovery", + ], +) + +api_proto_library_internal( + name = "rtds", + srcs = ["rtds.proto"], + has_services = 1, + deps = [ + "//envoy/api/v3alpha:discovery", + ], +) diff --git a/api/envoy/service/discovery/v3alpha/ads.proto b/api/envoy/service/discovery/v3alpha/ads.proto new file mode 100644 index 0000000000..251c51301a --- /dev/null +++ b/api/envoy/service/discovery/v3alpha/ads.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package envoy.service.discovery.v3alpha; + +option java_outer_classname = "AdsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.discovery.v3alpha"; +option java_generic_services = true; + +import "envoy/api/v3alpha/discovery.proto"; + +// [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing +// services: https://github.com/google/protobuf/issues/4221 +message AdsDummy { +} + +// [#not-implemented-hide:] Discovery services for endpoints, clusters, routes, +// and listeners are retained in the package `envoy.api.v3alpha` for backwards +// compatibility with existing management servers. New development in discovery +// services should proceed in the package `envoy.service.discovery.v3alpha`. + +// See https://github.com/lyft/envoy-api#apis for a description of the role of +// ADS and how it is intended to be used by a management server. ADS requests +// have the same structure as their singleton xDS counterparts, but can +// multiplex many resource types on a single stream. The type_url in the +// DiscoveryRequest/DiscoveryResponse provides sufficient information to recover +// the multiplexed singleton APIs at the Envoy instance and management server. +service AggregatedDiscoveryService { + // This is a gRPC-only API. + rpc StreamAggregatedResources(stream envoy.api.v3alpha.DiscoveryRequest) + returns (stream envoy.api.v3alpha.DiscoveryResponse) { + } + + rpc DeltaAggregatedResources(stream envoy.api.v3alpha.DeltaDiscoveryRequest) + returns (stream envoy.api.v3alpha.DeltaDiscoveryResponse) { + } +} diff --git a/api/envoy/service/discovery/v3alpha/hds.proto b/api/envoy/service/discovery/v3alpha/hds.proto new file mode 100644 index 0000000000..14955b15db --- /dev/null +++ b/api/envoy/service/discovery/v3alpha/hds.proto @@ -0,0 +1,127 @@ +syntax = "proto3"; + +package envoy.service.discovery.v3alpha; + +option java_outer_classname = "HdsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.discovery.v3alpha"; + +option java_generic_services = true; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/core/health_check.proto"; +import "envoy/api/v3alpha/endpoint/endpoint.proto"; + +import "google/api/annotations.proto"; +import "google/protobuf/duration.proto"; + +// [#proto-status: experimental] +// HDS is Health Discovery Service. It compliments Envoy’s health checking +// service by designating this Envoy to be a healthchecker for a subset of hosts +// in the cluster. The status of these health checks will be reported to the +// management server, where it can be aggregated etc and redistributed back to +// Envoy through EDS. +service HealthDiscoveryService { + // 1. Envoy starts up and if its can_healthcheck option in the static + // bootstrap config is enabled, sends HealthCheckRequest to the management + // server. It supplies its capabilities (which protocol it can health check + // with, what zone it resides in, etc.). + // 2. In response to (1), the management server designates this Envoy as a + // healthchecker to health check a subset of all upstream hosts for a given + // cluster (for example upstream Host 1 and Host 2). It streams + // HealthCheckSpecifier messages with cluster related configuration for all + // clusters this Envoy is designated to health check. Subsequent + // HealthCheckSpecifier message will be sent on changes to: + // a. Endpoints to health checks + // b. Per cluster configuration change + // 3. Envoy creates a health probe based on the HealthCheck config and sends + // it to endpoint(ip:port) of Host 1 and 2. Based on the HealthCheck + // configuration Envoy waits upon the arrival of the probe response and + // looks at the content of the response to decide whether the endpoint is + // healthy or not. If a response hasn't been received within the timeout + // interval, the endpoint health status is considered TIMEOUT. + // 4. Envoy reports results back in an EndpointHealthResponse message. + // Envoy streams responses as often as the interval configured by the + // management server in HealthCheckSpecifier. + // 5. The management Server collects health statuses for all endpoints in the + // cluster (for all clusters) and uses this information to construct + // EndpointDiscoveryResponse messages. + // 6. Once Envoy has a list of upstream endpoints to send traffic to, it load + // balances traffic to them without additional health checking. It may + // use inline healthcheck (i.e. consider endpoint UNHEALTHY if connection + // failed to a particular endpoint to account for health status propagation + // delay between HDS and EDS). + // By default, can_healthcheck is true. If can_healthcheck is false, Cluster + // configuration may not contain HealthCheck message. + // TODO(htuch): How is can_healthcheck communicated to CDS to ensure the above + // invariant? + // TODO(htuch): Add @amb67's diagram. + rpc StreamHealthCheck(stream HealthCheckRequestOrEndpointHealthResponse) + returns (stream HealthCheckSpecifier) { + } + + // TODO(htuch): Unlike the gRPC version, there is no stream-based binding of + // request/response. Should we add an identifier to the HealthCheckSpecifier + // to bind with the response? + rpc FetchHealthCheck(HealthCheckRequestOrEndpointHealthResponse) returns (HealthCheckSpecifier) { + option (google.api.http) = { + post: "/v3alpha/discovery:health_check" + body: "*" + }; + } +} + +// Defines supported protocols etc, so the management server can assign proper +// endpoints to healthcheck. +message Capability { + // Different Envoy instances may have different capabilities (e.g. Redis) + // and/or have ports enabled for different protocols. + enum Protocol { + HTTP = 0; + TCP = 1; + REDIS = 2; + } + repeated Protocol health_check_protocols = 1; +} + +message HealthCheckRequest { + envoy.api.v3alpha.core.Node node = 1; + Capability capability = 2; +} + +message EndpointHealth { + envoy.api.v3alpha.endpoint.Endpoint endpoint = 1; + envoy.api.v3alpha.core.HealthStatus health_status = 2; +} + +message EndpointHealthResponse { + repeated EndpointHealth endpoints_health = 1; +} + +message HealthCheckRequestOrEndpointHealthResponse { + oneof request_type { + HealthCheckRequest health_check_request = 1; + EndpointHealthResponse endpoint_health_response = 2; + } +} + +message LocalityEndpoints { + envoy.api.v3alpha.core.Locality locality = 1; + repeated envoy.api.v3alpha.endpoint.Endpoint endpoints = 2; +} + +// The cluster name and locality is provided to Envoy for the endpoints that it +// health checks to support statistics reporting, logging and debugging by the +// Envoy instance (outside of HDS). For maximum usefulness, it should match the +// same cluster structure as that provided by EDS. +message ClusterHealthCheck { + string cluster_name = 1; + repeated envoy.api.v3alpha.core.HealthCheck health_checks = 2; + repeated LocalityEndpoints locality_endpoints = 3; +} + +message HealthCheckSpecifier { + repeated ClusterHealthCheck cluster_health_checks = 1; + // The default is 1 second. + google.protobuf.Duration interval = 2; +} diff --git a/api/envoy/service/discovery/v3alpha/rtds.proto b/api/envoy/service/discovery/v3alpha/rtds.proto new file mode 100644 index 0000000000..d4184ab6e1 --- /dev/null +++ b/api/envoy/service/discovery/v3alpha/rtds.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +package envoy.service.discovery.v3alpha; + +option java_outer_classname = "RtdsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.discovery.v3alpha"; +option java_generic_services = true; + +import "envoy/api/v3alpha/discovery.proto"; + +import "google/api/annotations.proto"; +import "google/protobuf/struct.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Runtime Discovery Service (RTDS)] +// RTDS :ref:`configuration overview ` + +// [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing +// services: https://github.com/google/protobuf/issues/4221 +message RtdsDummy { +} + +// Discovery service for Runtime resources. +service RuntimeDiscoveryService { + rpc StreamRuntime(stream envoy.api.v3alpha.DiscoveryRequest) + returns (stream envoy.api.v3alpha.DiscoveryResponse) { + } + + rpc DeltaRuntime(stream envoy.api.v3alpha.DeltaDiscoveryRequest) + returns (stream envoy.api.v3alpha.DeltaDiscoveryResponse) { + } + + rpc FetchRuntime(envoy.api.v3alpha.DiscoveryRequest) + returns (envoy.api.v3alpha.DiscoveryResponse) { + option (google.api.http) = { + post: "/v3alpha/discovery:runtime" + body: "*" + }; + } +} + +// RTDS resource type. This describes a layer in the runtime virtual filesystem. +message Runtime { + // Runtime resource name. This makes the Runtime a self-describing xDS + // resource. + string name = 1 [(validate.rules).string.min_bytes = 1]; + google.protobuf.Struct layer = 2; +} diff --git a/api/envoy/service/discovery/v3alpha/sds.proto b/api/envoy/service/discovery/v3alpha/sds.proto new file mode 100644 index 0000000000..814edd0719 --- /dev/null +++ b/api/envoy/service/discovery/v3alpha/sds.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package envoy.service.discovery.v3alpha; + +option java_outer_classname = "SdsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.discovery.v3alpha"; + +import "envoy/api/v3alpha/discovery.proto"; + +import "google/api/annotations.proto"; + +// [#not-implemented-hide:] Not configuration. Workaround c++ protobuf issue with importing +// services: https://github.com/google/protobuf/issues/4221 +message SdsDummy { +} + +service SecretDiscoveryService { + rpc DeltaSecrets(stream envoy.api.v3alpha.DeltaDiscoveryRequest) + returns (stream envoy.api.v3alpha.DeltaDiscoveryResponse) { + } + + rpc StreamSecrets(stream envoy.api.v3alpha.DiscoveryRequest) + returns (stream envoy.api.v3alpha.DiscoveryResponse) { + } + + rpc FetchSecrets(envoy.api.v3alpha.DiscoveryRequest) + returns (envoy.api.v3alpha.DiscoveryResponse) { + option (google.api.http) = { + post: "/v3alpha/discovery:secrets" + body: "*" + }; + } +} diff --git a/api/envoy/service/load_stats/v2/BUILD b/api/envoy/service/load_stats/v2/BUILD index f126ebcb1d..af07d8aa10 100644 --- a/api/envoy/service/load_stats/v2/BUILD +++ b/api/envoy/service/load_stats/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v2/core", + "//envoy/api/v2/endpoint:pkg", + ], +) + api_proto_library_internal( name = "lrs", srcs = ["lrs.proto"], @@ -11,12 +19,3 @@ api_proto_library_internal( "//envoy/api/v2/endpoint:load_report", ], ) - -api_go_grpc_library( - name = "lrs", - proto = ":lrs", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/endpoint:load_report_go_proto", - ], -) diff --git a/api/envoy/service/load_stats/v2/lrs.proto b/api/envoy/service/load_stats/v2/lrs.proto index 2fe95f3b6a..d7029db0b5 100644 --- a/api/envoy/service/load_stats/v2/lrs.proto +++ b/api/envoy/service/load_stats/v2/lrs.proto @@ -5,7 +5,6 @@ package envoy.service.load_stats.v2; option java_outer_classname = "LrsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.load_stats.v2"; -option go_package = "v2"; option java_generic_services = true; import "envoy/api/v2/core/base.proto"; diff --git a/api/envoy/service/load_stats/v3alpha/BUILD b/api/envoy/service/load_stats/v3alpha/BUILD new file mode 100644 index 0000000000..bc4ff2642c --- /dev/null +++ b/api/envoy/service/load_stats/v3alpha/BUILD @@ -0,0 +1,21 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/endpoint:pkg", + ], +) + +api_proto_library_internal( + name = "lrs", + srcs = ["lrs.proto"], + has_services = 1, + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/endpoint:load_report", + ], +) diff --git a/api/envoy/service/load_stats/v3alpha/lrs.proto b/api/envoy/service/load_stats/v3alpha/lrs.proto new file mode 100644 index 0000000000..ec8adedbf2 --- /dev/null +++ b/api/envoy/service/load_stats/v3alpha/lrs.proto @@ -0,0 +1,81 @@ +syntax = "proto3"; + +package envoy.service.load_stats.v3alpha; + +option java_outer_classname = "LrsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.load_stats.v3alpha"; +option java_generic_services = true; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/endpoint/load_report.proto"; + +import "google/protobuf/duration.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Load reporting service] + +service LoadReportingService { + // Advanced API to allow for multi-dimensional load balancing by remote + // server. For receiving LB assignments, the steps are: + // 1, The management server is configured with per cluster/zone/load metric + // capacity configuration. The capacity configuration definition is + // outside of the scope of this document. + // 2. Envoy issues a standard {Stream,Fetch}Endpoints request for the clusters + // to balance. + // + // Independently, Envoy will initiate a StreamLoadStats bidi stream with a + // management server: + // 1. Once a connection establishes, the management server publishes a + // LoadStatsResponse for all clusters it is interested in learning load + // stats about. + // 2. For each cluster, Envoy load balances incoming traffic to upstream hosts + // based on per-zone weights and/or per-instance weights (if specified) + // based on intra-zone LbPolicy. This information comes from the above + // {Stream,Fetch}Endpoints. + // 3. When upstream hosts reply, they optionally add header with ASCII representation of EndpointLoadMetricStats. + // 4. Envoy aggregates load reports over the period of time given to it in + // LoadStatsResponse.load_reporting_interval. This includes aggregation + // stats Envoy maintains by itself (total_requests, rpc_errors etc.) as + // well as load metrics from upstream hosts. + // 5. When the timer of load_reporting_interval expires, Envoy sends new + // LoadStatsRequest filled with load reports for each cluster. + // 6. The management server uses the load reports from all reported Envoys + // from around the world, computes global assignment and prepares traffic + // assignment destined for each zone Envoys are located in. Goto 2. + rpc StreamLoadStats(stream LoadStatsRequest) returns (stream LoadStatsResponse) { + } +} + +// A load report Envoy sends to the management server. +// [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. +message LoadStatsRequest { + // Node identifier for Envoy instance. + envoy.api.v3alpha.core.Node node = 1; + + // A list of load stats to report. + repeated envoy.api.v3alpha.endpoint.ClusterStats cluster_stats = 2; +} + +// The management server sends envoy a LoadStatsResponse with all clusters it +// is interested in learning load stats about. +// [#not-implemented-hide:] Not configuration. TBD how to doc proto APIs. +message LoadStatsResponse { + // Clusters to report stats for. + repeated string clusters = 1 [(validate.rules).repeated .min_items = 1]; + + // The minimum interval of time to collect stats over. This is only a minimum for two reasons: + // 1. There may be some delay from when the timer fires until stats sampling occurs. + // 2. For clusters that were already feature in the previous *LoadStatsResponse*, any traffic + // that is observed in between the corresponding previous *LoadStatsRequest* and this + // *LoadStatsResponse* will also be accumulated and billed to the cluster. This avoids a period + // of inobservability that might otherwise exists between the messages. New clusters are not + // subject to this consideration. + google.protobuf.Duration load_reporting_interval = 2; + + // Set to *true* if the management server supports endpoint granularity + // report. + bool report_endpoint_granularity = 3; +} diff --git a/api/envoy/service/metrics/v2/BUILD b/api/envoy/service/metrics/v2/BUILD index 7f3921ced6..091d40e7f8 100644 --- a/api/envoy/service/metrics/v2/BUILD +++ b/api/envoy/service/metrics/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v2/core", + "@prometheus_metrics_model//:client_model", + ], +) + api_proto_library_internal( name = "metrics_service", srcs = ["metrics_service.proto"], @@ -13,12 +21,3 @@ api_proto_library_internal( "@prometheus_metrics_model//:client_model", ], ) - -api_go_grpc_library( - name = "metrics_service", - proto = ":metrics_service", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "@prometheus_metrics_model//:client_model_go_proto", - ], -) diff --git a/api/envoy/service/metrics/v2/metrics_service.proto b/api/envoy/service/metrics/v2/metrics_service.proto index b70be3bdd9..10745ba665 100644 --- a/api/envoy/service/metrics/v2/metrics_service.proto +++ b/api/envoy/service/metrics/v2/metrics_service.proto @@ -5,7 +5,6 @@ package envoy.service.metrics.v2; option java_outer_classname = "MetricsServiceProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.metrics.v2"; -option go_package = "v2"; option java_generic_services = true; import "envoy/api/v2/core/base.proto"; diff --git a/api/envoy/service/metrics/v3alpha/BUILD b/api/envoy/service/metrics/v3alpha/BUILD new file mode 100644 index 0000000000..6053aac4f1 --- /dev/null +++ b/api/envoy/service/metrics/v3alpha/BUILD @@ -0,0 +1,23 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v3alpha/core", + "@prometheus_metrics_model//:client_model", + ], +) + +api_proto_library_internal( + name = "metrics_service", + srcs = ["metrics_service.proto"], + has_services = 1, + require_py = 0, + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:grpc_service", + "@prometheus_metrics_model//:client_model", + ], +) diff --git a/api/envoy/service/metrics/v3alpha/metrics_service.proto b/api/envoy/service/metrics/v3alpha/metrics_service.proto new file mode 100644 index 0000000000..bcf1caa28a --- /dev/null +++ b/api/envoy/service/metrics/v3alpha/metrics_service.proto @@ -0,0 +1,40 @@ +syntax = "proto3"; + +package envoy.service.metrics.v3alpha; + +option java_outer_classname = "MetricsServiceProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.metrics.v3alpha"; +option java_generic_services = true; + +import "envoy/api/v3alpha/core/base.proto"; + +import "metrics.proto"; + +import "validate/validate.proto"; + +// Service for streaming metrics to server that consumes the metrics data. It uses Prometheus metric +// data model as a standard to represent metrics information. +service MetricsService { + // Envoy will connect and send StreamMetricsMessage messages forever. It does not expect any + // response to be sent as nothing would be done in the case of failure. + rpc StreamMetrics(stream StreamMetricsMessage) returns (StreamMetricsResponse) { + } +} + +message StreamMetricsResponse { +} + +message StreamMetricsMessage { + message Identifier { + // The node sending metrics over the stream. + envoy.api.v3alpha.core.Node node = 1 [(validate.rules).message.required = true]; + } + + // Identifier data effectively is a structured metadata. As a performance optimization this will + // only be sent in the first message on the stream. + Identifier identifier = 1; + + // A list of metric entries + repeated io.prometheus.client.MetricFamily envoy_metrics = 2; +} diff --git a/api/envoy/service/ratelimit/v2/BUILD b/api/envoy/service/ratelimit/v2/BUILD index 24278fbebc..7bc5db7113 100644 --- a/api/envoy/service/ratelimit/v2/BUILD +++ b/api/envoy/service/ratelimit/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v2/core", + "//envoy/api/v2/ratelimit:pkg", + ], +) + api_proto_library_internal( name = "rls", srcs = ["rls.proto"], @@ -12,13 +20,3 @@ api_proto_library_internal( "//envoy/api/v2/ratelimit", ], ) - -api_go_grpc_library( - name = "rls", - proto = ":rls", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "//envoy/api/v2/core:grpc_service_go_proto", - "//envoy/api/v2/ratelimit:ratelimit_go_proto", - ], -) diff --git a/api/envoy/service/ratelimit/v2/rls.proto b/api/envoy/service/ratelimit/v2/rls.proto index 18b6b678e9..328bb547d6 100644 --- a/api/envoy/service/ratelimit/v2/rls.proto +++ b/api/envoy/service/ratelimit/v2/rls.proto @@ -5,7 +5,6 @@ package envoy.service.ratelimit.v2; option java_outer_classname = "RlsProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.ratelimit.v2"; -option go_package = "v2"; import "envoy/api/v2/core/base.proto"; import "envoy/api/v2/ratelimit/ratelimit.proto"; diff --git a/api/envoy/service/ratelimit/v3alpha/BUILD b/api/envoy/service/ratelimit/v3alpha/BUILD new file mode 100644 index 0000000000..965458beae --- /dev/null +++ b/api/envoy/service/ratelimit/v3alpha/BUILD @@ -0,0 +1,22 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/ratelimit:pkg", + ], +) + +api_proto_library_internal( + name = "rls", + srcs = ["rls.proto"], + has_services = 1, + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:grpc_service", + "//envoy/api/v3alpha/ratelimit", + ], +) diff --git a/api/envoy/service/ratelimit/v3alpha/rls.proto b/api/envoy/service/ratelimit/v3alpha/rls.proto new file mode 100644 index 0000000000..57a3ee98de --- /dev/null +++ b/api/envoy/service/ratelimit/v3alpha/rls.proto @@ -0,0 +1,94 @@ +syntax = "proto3"; + +package envoy.service.ratelimit.v3alpha; + +option java_outer_classname = "RlsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.ratelimit.v3alpha"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/ratelimit/ratelimit.proto"; + +import "validate/validate.proto"; + +// [#protodoc-title: Rate Limit Service (RLS)] + +service RateLimitService { + // Determine whether rate limiting should take place. + rpc ShouldRateLimit(RateLimitRequest) returns (RateLimitResponse) { + } +} + +// Main message for a rate limit request. The rate limit service is designed to be fully generic +// in the sense that it can operate on arbitrary hierarchical key/value pairs. The loaded +// configuration will parse the request and find the most specific limit to apply. In addition, +// a RateLimitRequest can contain multiple "descriptors" to limit on. When multiple descriptors +// are provided, the server will limit on *ALL* of them and return an OVER_LIMIT response if any +// of them are over limit. This enables more complex application level rate limiting scenarios +// if desired. +message RateLimitRequest { + // All rate limit requests must specify a domain. This enables the configuration to be per + // application without fear of overlap. E.g., "envoy". + string domain = 1; + + // All rate limit requests must specify at least one RateLimitDescriptor. Each descriptor is + // processed by the service (see below). If any of the descriptors are over limit, the entire + // request is considered to be over limit. + repeated envoy.api.v3alpha.ratelimit.RateLimitDescriptor descriptors = 2; + + // Rate limit requests can optionally specify the number of hits a request adds to the matched + // limit. If the value is not set in the message, a request increases the matched limit by 1. + uint32 hits_addend = 3; +} + +// A response from a ShouldRateLimit call. +message RateLimitResponse { + enum Code { + // The response code is not known. + UNKNOWN = 0; + // The response code to notify that the number of requests are under limit. + OK = 1; + // The response code to notify that the number of requests are over limit. + OVER_LIMIT = 2; + } + + // Defines an actual rate limit in terms of requests per unit of time and the unit itself. + message RateLimit { + enum Unit { + // The time unit is not known. + UNKNOWN = 0; + // The time unit representing a second. + SECOND = 1; + // The time unit representing a minute. + MINUTE = 2; + // The time unit representing an hour. + HOUR = 3; + // The time unit representing a day. + DAY = 4; + } + + // The number of requests per unit of time. + uint32 requests_per_unit = 1; + // The unit of time. + Unit unit = 2; + } + + message DescriptorStatus { + // The response code for an individual descriptor. + Code code = 1; + // The current limit as configured by the server. Useful for debugging, etc. + RateLimit current_limit = 2; + // The limit remaining in the current time unit. + uint32 limit_remaining = 3; + } + + // The overall response code which takes into account all of the descriptors that were passed + // in the RateLimitRequest message. + Code overall_code = 1; + // A list of DescriptorStatus messages which matches the length of the descriptor list passed + // in the RateLimitRequest. This can be used by the caller to determine which individual + // descriptors failed and/or what the currently configured limits are for all of them. + repeated DescriptorStatus statuses = 2; + // A list of headers to add to the response + repeated envoy.api.v3alpha.core.HeaderValue headers = 3; +} diff --git a/api/envoy/service/tap/v2alpha/BUILD b/api/envoy/service/tap/v2alpha/BUILD index 63d11e80a7..621bf208d4 100644 --- a/api/envoy/service/tap/v2alpha/BUILD +++ b/api/envoy/service/tap/v2alpha/BUILD @@ -1,7 +1,17 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v2", + "//envoy/api/v2/core", + "//envoy/api/v2/route:pkg", + "//envoy/data/tap/v2alpha:pkg", + ], +) + api_proto_library_internal( name = "common", srcs = ["common.proto"], diff --git a/api/envoy/service/tap/v3alpha/BUILD b/api/envoy/service/tap/v3alpha/BUILD new file mode 100644 index 0000000000..005ef96b61 --- /dev/null +++ b/api/envoy/service/tap/v3alpha/BUILD @@ -0,0 +1,46 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v3alpha", + "//envoy/api/v3alpha/core", + "//envoy/api/v3alpha/route:pkg", + "//envoy/data/tap/v3alpha:pkg", + ], +) + +api_proto_library_internal( + name = "common", + srcs = ["common.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha/core:base", + "//envoy/api/v3alpha/core:grpc_service", + "//envoy/api/v3alpha/route", + ], +) + +api_proto_library_internal( + name = "tap", + srcs = ["tap.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha:discovery", + "//envoy/api/v3alpha/core:base", + "//envoy/data/tap/v3alpha:wrapper", + ], +) + +api_proto_library_internal( + name = "tapds", + srcs = ["tapds.proto"], + visibility = ["//visibility:public"], + deps = [ + "//envoy/api/v3alpha:discovery", + "//envoy/api/v3alpha/core:base", + "//envoy/service/tap/v3alpha:common", + ], +) diff --git a/api/envoy/service/tap/v3alpha/common.proto b/api/envoy/service/tap/v3alpha/common.proto new file mode 100644 index 0000000000..7c375d913d --- /dev/null +++ b/api/envoy/service/tap/v3alpha/common.proto @@ -0,0 +1,200 @@ +syntax = "proto3"; + +import "envoy/api/v3alpha/route/route.proto"; +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/api/v3alpha/core/grpc_service.proto"; + +import "google/protobuf/wrappers.proto"; + +import "validate/validate.proto"; + +package envoy.service.tap.v3alpha; + +option java_outer_classname = "CommonProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.tap.v3alpha"; + +// [#protodoc-title: Common tap configuration] + +// Tap configuration. +message TapConfig { + // The match configuration. If the configuration matches the data source being tapped, a tap will + // occur, with the result written to the configured output. + MatchPredicate match_config = 1 [(validate.rules).message.required = true]; + + // The tap output configuration. If a match configuration matches a data source being tapped, + // a tap will occur and the data will be written to the configured output. + OutputConfig output_config = 2 [(validate.rules).message.required = true]; + + // [#not-implemented-hide:] Specify if Tap matching is enabled. The % of requests\connections for + // which the tap matching is enabled. When not enabled, the request\connection will not be + // recorded. + // + // .. note:: + // + // This field defaults to 100/:ref:`HUNDRED + // `. + envoy.api.v3alpha.core.RuntimeFractionalPercent tap_enabled = 3; + + // [#comment:TODO(mattklein123): Rate limiting] +} + +// Tap match configuration. This is a recursive structure which allows complex nested match +// configurations to be built using various logical operators. +message MatchPredicate { + // A set of match configurations used for logical operations. + message MatchSet { + // The list of rules that make up the set. + repeated MatchPredicate rules = 1 [(validate.rules).repeated .min_items = 2]; + } + + oneof rule { + option (validate.required) = true; + + // A set that describes a logical OR. If any member of the set matches, the match configuration + // matches. + MatchSet or_match = 1; + + // A set that describes a logical AND. If all members of the set match, the match configuration + // matches. + MatchSet and_match = 2; + + // A negation match. The match configuration will match if the negated match condition matches. + MatchPredicate not_match = 3; + + // The match configuration will always match. + bool any_match = 4 [(validate.rules).bool.const = true]; + + // HTTP request headers match configuration. + HttpHeadersMatch http_request_headers_match = 5; + + // HTTP request trailers match configuration. + HttpHeadersMatch http_request_trailers_match = 6; + + // HTTP response headers match configuration. + HttpHeadersMatch http_response_headers_match = 7; + + // HTTP response trailers match configuration. + HttpHeadersMatch http_response_trailers_match = 8; + } +} + +// HTTP headers match configuration. +message HttpHeadersMatch { + // HTTP headers to match. + repeated api.v3alpha.route.HeaderMatcher headers = 1; +} + +// Tap output configuration. +message OutputConfig { + // Output sinks for tap data. Currently a single sink is allowed in the list. Once multiple + // sink types are supported this constraint will be relaxed. + repeated OutputSink sinks = 1 [(validate.rules).repeated = {min_items: 1, max_items: 1}]; + + // For buffered tapping, the maximum amount of received body that will be buffered prior to + // truncation. If truncation occurs, the :ref:`truncated + // ` field will be set. If not specified, the + // default is 1KiB. + google.protobuf.UInt32Value max_buffered_rx_bytes = 2; + + // For buffered tapping, the maximum amount of transmitted body that will be buffered prior to + // truncation. If truncation occurs, the :ref:`truncated + // ` field will be set. If not specified, the + // default is 1KiB. + google.protobuf.UInt32Value max_buffered_tx_bytes = 3; + + // Indicates whether taps produce a single buffered message per tap, or multiple streamed + // messages per tap in the emitted :ref:`TraceWrapper + // ` messages. Note that streamed tapping does not + // mean that no buffering takes place. Buffering may be required if data is processed before a + // match can be determined. See the HTTP tap filter :ref:`streaming + // ` documentation for more information. + bool streaming = 4; +} + +// Tap output sink configuration. +message OutputSink { + // Output format. All output is in the form of one or more :ref:`TraceWrapper + // ` messages. This enumeration indicates + // how those messages are written. Note that not all sinks support all output formats. See + // individual sink documentation for more information. + enum Format { + // Each message will be written as JSON. Any :ref:`body ` + // data will be present in the :ref:`as_bytes + // ` field. This means that body data will be + // base64 encoded as per the `proto3 JSON mappings + // `_. + JSON_BODY_AS_BYTES = 0; + + // Each message will be written as JSON. Any :ref:`body ` + // data will be present in the :ref:`as_string + // ` field. This means that body data will be + // string encoded as per the `proto3 JSON mappings + // `_. This format type is + // useful when it is known that that body is human readable (e.g., JSON over HTTP) and the + // user wishes to view it directly without being forced to base64 decode the body. + JSON_BODY_AS_STRING = 1; + + // Binary proto format. Note that binary proto is not self-delimiting. If a sink writes + // multiple binary messages without any length information the data stream will not be + // useful. However, for certain sinks that are self-delimiting (e.g., one message per file) + // this output format makes consumption simpler. + PROTO_BINARY = 2; + + // Messages are written as a sequence tuples, where each tuple is the message length encoded + // as a `protobuf 32-bit varint + // `_ + // followed by the binary message. The messages can be read back using the language specific + // protobuf coded stream implementation to obtain the message length and the message. + PROTO_BINARY_LENGTH_DELIMITED = 3; + + // Text proto format. + PROTO_TEXT = 4; + } + + // Sink output format. + Format format = 1 [(validate.rules).enum.defined_only = true]; + + oneof output_sink_type { + option (validate.required) = true; + + // Tap output will be streamed out the :http:post:`/tap` admin endpoint. + // + // .. attention:: + // + // It is only allowed to specify the streaming admin output sink if the tap is being + // configured from the :http:post:`/tap` admin endpoint. Thus, if an extension has + // been configured to receive tap configuration from some other source (e.g., static + // file, XDS, etc.) configuring the streaming admin output type will fail. + StreamingAdminSink streaming_admin = 2; + + // Tap output will be written to a file per tap sink. + FilePerTapSink file_per_tap = 3; + + // [#not-implemented-hide:] + // GrpcService to stream data to. The format argument must be PROTO_BINARY. + StreamingGrpcSink streaming_grpc = 4; + } +} + +// Streaming admin sink configuration. +message StreamingAdminSink { +} + +// The file per tap sink outputs a discrete file for every tapped stream. +message FilePerTapSink { + // Path prefix. The output file will be of the form _.pb, where is an + // identifier distinguishing the recorded trace for stream instances (the Envoy + // connection ID, HTTP stream ID, etc.). + string path_prefix = 1 [(validate.rules).string.min_bytes = 1]; +} + +// [#not-implemented-hide:] Streaming gRPC sink configuration sends the taps to an external gRPC +// server. +message StreamingGrpcSink { + // Opaque identifier, that will be sent back to the streaming grpc server. + string tap_id = 1; + + // The gRPC server that hosts the Tap Sink Service. + envoy.api.v3alpha.core.GrpcService grpc_service = 2 [(validate.rules).message.required = true]; +} diff --git a/api/envoy/service/tap/v3alpha/tap.proto b/api/envoy/service/tap/v3alpha/tap.proto new file mode 100644 index 0000000000..1e69d42191 --- /dev/null +++ b/api/envoy/service/tap/v3alpha/tap.proto @@ -0,0 +1,50 @@ +syntax = "proto3"; + +import "envoy/api/v3alpha/core/base.proto"; +import "envoy/data/tap/v3alpha/wrapper.proto"; + +package envoy.service.tap.v3alpha; + +import "validate/validate.proto"; + +option java_outer_classname = "TapProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.tap.v3alpha"; + +// [#protodoc-title: Tap Sink Service] + +// [#not-implemented-hide:] Stream message for the Tap API. Envoy will open a stream to the server +// and stream taps without ever expecting a response. +message StreamTapsRequest { + message Identifier { + // The node sending taps over the stream. + envoy.api.v3alpha.core.Node node = 1 [(validate.rules).message.required = true]; + // The opaque identifier that was set in the :ref:`output config + // `. + string tap_id = 2; + } + + // Identifier data effectively is a structured metadata. As a performance optimization this will + // only be sent in the first message on the stream. + Identifier identifier = 1; + // The trace id. this can be used to merge together a streaming trace. Note that the trace_id + // is not guaranteed to be spatially or temporally unique. + uint64 trace_id = 2; + // The trace data. + envoy.data.tap.v3alpha.TraceWrapper trace = 3; +} + +// [#not-implemented-hide:] +message StreamTapsResponse { +} + +// [#not-implemented-hide:] A tap service to receive incoming taps. Envoy will call +// StreamTaps to deliver captured taps to the server +service TapSinkService { + + // Envoy will connect and send StreamTapsRequest messages forever. It does not expect any + // response to be sent as nothing would be done in the case of failure. The server should + // disconnect if it expects Envoy to reconnect. + rpc StreamTaps(stream StreamTapsRequest) returns (StreamTapsResponse) { + } +} \ No newline at end of file diff --git a/api/envoy/service/tap/v3alpha/tapds.proto b/api/envoy/service/tap/v3alpha/tapds.proto new file mode 100644 index 0000000000..11eea61a1d --- /dev/null +++ b/api/envoy/service/tap/v3alpha/tapds.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +import "envoy/api/v3alpha/discovery.proto"; +import "envoy/service/tap/v3alpha/common.proto"; +import "validate/validate.proto"; + +package envoy.service.tap.v3alpha; + +import "google/api/annotations.proto"; + +option java_outer_classname = "TapDsProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.tap.v3alpha"; + +// [#protodoc-title: Tap discovery service] + +// [#not-implemented-hide:] Tap discovery service. +service TapDiscoveryService { + rpc StreamTapConfigs(stream envoy.api.v3alpha.DiscoveryRequest) + returns (stream envoy.api.v3alpha.DiscoveryResponse) { + } + + rpc DeltaTapConfigs(stream envoy.api.v3alpha.DeltaDiscoveryRequest) + returns (stream envoy.api.v3alpha.DeltaDiscoveryResponse) { + } + + rpc FetchTapConfigs(envoy.api.v3alpha.DiscoveryRequest) + returns (envoy.api.v3alpha.DiscoveryResponse) { + option (google.api.http) = { + post: "/v3alpha/discovery:tap_configs" + body: "*" + }; + } +} + +// [#not-implemented-hide:] A tap resource is essentially a tap configuration with a name +// The filter TapDS config references this name. +message TapResource { + // The name of the tap configuration. + string name = 1 [(validate.rules).string.min_bytes = 1]; + + // Tap config to apply + TapConfig config = 2; +} \ No newline at end of file diff --git a/api/envoy/service/trace/v2/BUILD b/api/envoy/service/trace/v2/BUILD index 2b3367f0af..cee54d8b34 100644 --- a/api/envoy/service/trace/v2/BUILD +++ b/api/envoy/service/trace/v2/BUILD @@ -1,7 +1,15 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v2/core", + "@opencensus_proto//opencensus/proto/trace/v1:trace_proto", + ], +) + api_proto_library_internal( name = "trace_service", srcs = ["trace_service.proto"], @@ -12,12 +20,3 @@ api_proto_library_internal( "@opencensus_proto//opencensus/proto/trace/v1:trace_proto", ], ) - -api_go_grpc_library( - name = "trace_service", - proto = ":trace_service", - deps = [ - "//envoy/api/v2/core:base_go_proto", - "@opencensus_proto//opencensus/proto/trace/v1:trace_proto_go", - ], -) diff --git a/api/envoy/service/trace/v2/trace_service.proto b/api/envoy/service/trace/v2/trace_service.proto index ec87b35606..92b8489f21 100644 --- a/api/envoy/service/trace/v2/trace_service.proto +++ b/api/envoy/service/trace/v2/trace_service.proto @@ -7,7 +7,6 @@ package envoy.service.trace.v2; option java_outer_classname = "TraceServiceProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.service.trace.v2"; -option go_package = "v2"; option java_generic_services = true; import "envoy/api/v2/core/base.proto"; diff --git a/api/envoy/service/trace/v3alpha/BUILD b/api/envoy/service/trace/v3alpha/BUILD new file mode 100644 index 0000000000..fbfafec678 --- /dev/null +++ b/api/envoy/service/trace/v3alpha/BUILD @@ -0,0 +1,22 @@ +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + has_services = True, + deps = [ + "//envoy/api/v3alpha/core", + "@opencensus_proto//opencensus/proto/trace/v1:trace_proto", + ], +) + +api_proto_library_internal( + name = "trace_service", + srcs = ["trace_service.proto"], + has_services = 1, + require_py = 0, + deps = [ + "//envoy/api/v3alpha/core:base", + "@opencensus_proto//opencensus/proto/trace/v1:trace_proto", + ], +) diff --git a/api/envoy/service/trace/v3alpha/trace_service.proto b/api/envoy/service/trace/v3alpha/trace_service.proto new file mode 100644 index 0000000000..b6559800cd --- /dev/null +++ b/api/envoy/service/trace/v3alpha/trace_service.proto @@ -0,0 +1,45 @@ +syntax = "proto3"; + +// [#proto-status: draft] + +package envoy.service.trace.v3alpha; + +option java_outer_classname = "TraceServiceProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.service.trace.v3alpha"; +option java_generic_services = true; + +import "envoy/api/v3alpha/core/base.proto"; +import "opencensus/proto/trace/v1/trace.proto"; + +import "google/api/annotations.proto"; + +import "validate/validate.proto"; + +// Service for streaming traces to server that consumes the trace data. It +// uses OpenCensus data model as a standard to represent trace information. +service TraceService { + // Envoy will connect and send StreamTracesMessage messages forever. It does + // not expect any response to be sent as nothing would be done in the case + // of failure. + rpc StreamTraces(stream StreamTracesMessage) returns (StreamTracesResponse) { + } +} + +message StreamTracesResponse { +} + +message StreamTracesMessage { + message Identifier { + // The node sending the access log messages over the stream. + envoy.api.v3alpha.core.Node node = 1 [(validate.rules).message.required = true]; + } + + // Identifier data effectively is a structured metadata. + // As a performance optimization this will only be sent in the first message + // on the stream. + Identifier identifier = 1; + + // A list of Span entries + repeated opencensus.proto.trace.v1.Span spans = 2; +} diff --git a/api/envoy/type/BUILD b/api/envoy/type/BUILD index 97f0fd424f..26dd9730d9 100644 --- a/api/envoy/type/BUILD +++ b/api/envoy/type/BUILD @@ -1,36 +1,25 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + name = "type", +) + api_proto_library_internal( name = "http_status", srcs = ["http_status.proto"], visibility = ["//visibility:public"], ) -api_go_proto_library( - name = "http_status", - proto = ":http_status", -) - api_proto_library_internal( name = "percent", srcs = ["percent.proto"], visibility = ["//visibility:public"], ) -api_go_proto_library( - name = "percent", - proto = ":percent", -) - api_proto_library_internal( name = "range", srcs = ["range.proto"], visibility = ["//visibility:public"], ) - -api_go_proto_library( - name = "range", - proto = ":range", -) diff --git a/api/envoy/type/matcher/BUILD b/api/envoy/type/matcher/BUILD index ec4aa09b6c..c7db01b6cd 100644 --- a/api/envoy/type/matcher/BUILD +++ b/api/envoy/type/matcher/BUILD @@ -1,7 +1,12 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library_internal") +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_library_internal", "api_proto_package") licenses(["notice"]) # Apache 2 +api_proto_package( + name = "matcher", + deps = ["//envoy/type"], +) + api_proto_library_internal( name = "metadata", srcs = ["metadata.proto"], @@ -11,14 +16,6 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "metadata", - proto = ":metadata", - deps = [ - ":value_go_proto", - ], -) - api_proto_library_internal( name = "number", srcs = ["number.proto"], @@ -28,23 +25,13 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "number", - proto = ":number", - deps = [ - "//envoy/type:range_go_proto", - ], -) - api_proto_library_internal( name = "string", srcs = ["string.proto"], visibility = ["//visibility:public"], -) - -api_go_proto_library( - name = "string", - proto = ":string", + deps = [ + ":regex", + ], ) api_proto_library_internal( @@ -57,11 +44,8 @@ api_proto_library_internal( ], ) -api_go_proto_library( - name = "value", - proto = ":value", - deps = [ - ":number_go_proto", - ":string_go_proto", - ], +api_proto_library_internal( + name = "regex", + srcs = ["regex.proto"], + visibility = ["//visibility:public"], ) diff --git a/api/envoy/type/matcher/metadata.proto b/api/envoy/type/matcher/metadata.proto index 08190a9f5d..56b69eae59 100644 --- a/api/envoy/type/matcher/metadata.proto +++ b/api/envoy/type/matcher/metadata.proto @@ -5,7 +5,6 @@ package envoy.type.matcher; option java_outer_classname = "MetadataProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.type.matcher"; -option go_package = "matcher"; import "envoy/type/matcher/value.proto"; diff --git a/api/envoy/type/matcher/number.proto b/api/envoy/type/matcher/number.proto index f6c49b3fcd..5c8cec7bcb 100644 --- a/api/envoy/type/matcher/number.proto +++ b/api/envoy/type/matcher/number.proto @@ -5,7 +5,6 @@ package envoy.type.matcher; option java_outer_classname = "NumberProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.type.matcher"; -option go_package = "matcher"; import "envoy/type/range.proto"; diff --git a/api/envoy/type/matcher/regex.proto b/api/envoy/type/matcher/regex.proto new file mode 100644 index 0000000000..cf6343c9ac --- /dev/null +++ b/api/envoy/type/matcher/regex.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; + +package envoy.type.matcher; + +option java_outer_classname = "RegexProto"; +option java_multiple_files = true; +option java_package = "io.envoyproxy.envoy.type.matcher"; + +import "google/protobuf/wrappers.proto"; +import "validate/validate.proto"; + +// [#protodoc-title: RegexMatcher] + +// A regex matcher designed for safety when used with untrusted input. +message RegexMatcher { + // Google's `RE2 `_ regex engine. The regex string must adhere to + // the documented `syntax `_. The engine is designed + // to complete execution in linear time as well as limit the amount of memory used. + message GoogleRE2 { + // This field controls the RE2 "program size" which is a rough estimate of how complex a + // compiled regex is to evaluate. A regex that has a program size greater than the configured + // value will fail to compile. In this case, the configured max program size can be increased + // or the regex can be simplified. If not specified, the default is 100. + google.protobuf.UInt32Value max_program_size = 1; + } + + oneof engine_type { + option (validate.required) = true; + + // Google's RE2 regex engine. + GoogleRE2 google_re2 = 1 [(validate.rules).message.required = true]; + } + + // The regex match string. The string must be supported by the configured engine. + string regex = 2 [(validate.rules).string.min_bytes = 1]; +} diff --git a/api/envoy/type/matcher/string.proto b/api/envoy/type/matcher/string.proto index 55f2171af5..986e393be1 100644 --- a/api/envoy/type/matcher/string.proto +++ b/api/envoy/type/matcher/string.proto @@ -5,7 +5,8 @@ package envoy.type.matcher; option java_outer_classname = "StringProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.type.matcher"; -option go_package = "matcher"; + +import "envoy/type/matcher/regex.proto"; import "validate/validate.proto"; @@ -48,7 +49,14 @@ 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 [(validate.rules).string.max_bytes = 1024]; + // + // .. attention:: + // This field has been deprecated in favor of `safe_regex` as it is not safe for use with + // untrusted input in all cases. + string regex = 4 [(validate.rules).string.max_bytes = 1024, deprecated = true]; + + // The input string must match the regular expression specified here. + RegexMatcher safe_regex = 5 [(validate.rules).message.required = true]; } } diff --git a/api/envoy/type/matcher/value.proto b/api/envoy/type/matcher/value.proto index 52f5e5b100..7164504366 100644 --- a/api/envoy/type/matcher/value.proto +++ b/api/envoy/type/matcher/value.proto @@ -5,7 +5,6 @@ package envoy.type.matcher; option java_outer_classname = "ValueProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.type.matcher"; -option go_package = "matcher"; import "envoy/type/matcher/number.proto"; import "envoy/type/matcher/string.proto"; diff --git a/api/envoy/type/percent.proto b/api/envoy/type/percent.proto index 551e93bfdd..c577093eea 100644 --- a/api/envoy/type/percent.proto +++ b/api/envoy/type/percent.proto @@ -7,9 +7,6 @@ option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.type"; import "validate/validate.proto"; -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Percent] diff --git a/api/envoy/type/range.proto b/api/envoy/type/range.proto index e64b71e440..f31cf32f07 100644 --- a/api/envoy/type/range.proto +++ b/api/envoy/type/range.proto @@ -5,11 +5,6 @@ package envoy.type; option java_outer_classname = "RangeProto"; option java_multiple_files = true; option java_package = "io.envoyproxy.envoy.type"; -option go_package = "envoy_type"; - -import "gogoproto/gogo.proto"; - -option (gogoproto.equal_all) = true; // [#protodoc-title: Range] diff --git a/api/migration/v3alpha.sh b/api/migration/v3alpha.sh new file mode 100755 index 0000000000..2b081dabaa --- /dev/null +++ b/api/migration/v3alpha.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +./tools/api/clone.sh v2 v3alpha +./tools/check_format.py fix diff --git a/api/test/build/BUILD b/api/test/build/BUILD index ab1e8640a9..c8390c1fed 100644 --- a/api/test/build/BUILD +++ b/api/test/build/BUILD @@ -16,6 +16,7 @@ api_cc_test( "//envoy/service/discovery/v2:rtds", "//envoy/service/metrics/v2:metrics_service", "//envoy/service/ratelimit/v2:rls", + "@com_github_cncf_udpa//udpa/service/orca/v1:orca", ], ) @@ -25,18 +26,13 @@ api_go_test( srcs = ["go_build_test.go"], importpath = "go_build_test", deps = [ - "//envoy/api/v2:cds_go_grpc", - "//envoy/api/v2:eds_go_grpc", - "//envoy/api/v2:lds_go_grpc", - "//envoy/api/v2:rds_go_grpc", - "//envoy/api/v2/auth:cert_go_proto", - "//envoy/config/bootstrap/v2:bootstrap_go_proto", - "//envoy/service/accesslog/v2:als_go_grpc", - "//envoy/service/discovery/v2:ads_go_grpc", - "//envoy/service/discovery/v2:hds_go_grpc", - "//envoy/service/discovery/v2:sds_go_grpc", - "//envoy/service/metrics/v2:metrics_service_go_grpc", - "//envoy/service/ratelimit/v2:rls_go_grpc", - "//envoy/service/trace/v2:trace_service_go_grpc", + "//envoy/api/v2:v2_go_proto", + "//envoy/api/v2/auth:auth_go_proto", + "//envoy/config/bootstrap/v2:pkg_go_proto", + "//envoy/service/accesslog/v2:pkg_go_proto", + "//envoy/service/discovery/v2:pkg_go_proto", + "//envoy/service/metrics/v2:pkg_go_proto", + "//envoy/service/ratelimit/v2:pkg_go_proto", + "//envoy/service/trace/v2:pkg_go_proto", ], ) diff --git a/api/test/build/build_test.cc b/api/test/build/build_test.cc index f0a8e7bf43..540b2a5150 100644 --- a/api/test/build/build_test.cc +++ b/api/test/build/build_test.cc @@ -23,6 +23,7 @@ int main(int argc, char* argv[]) { "envoy.service.accesslog.v2.AccessLogService.StreamAccessLogs", "envoy.service.metrics.v2.MetricsService.StreamMetrics", "envoy.service.ratelimit.v2.RateLimitService.ShouldRateLimit", + "udpa.service.orca.v1.OpenRcaService.StreamCoreMetrics", }; for (const auto& method : methods) { diff --git a/api/test/build/go_build_test.go b/api/test/build/go_build_test.go index 911d3ef396..c5c15becff 100644 --- a/api/test/build/go_build_test.go +++ b/api/test/build/go_build_test.go @@ -3,19 +3,14 @@ package go_build_test import ( "testing" - _ "github.com/envoyproxy/data-plane-api/api/ads" - _ "github.com/envoyproxy/data-plane-api/api/als" - _ "github.com/envoyproxy/data-plane-api/api/bootstrap" - _ "github.com/envoyproxy/data-plane-api/api/cds" - _ "github.com/envoyproxy/data-plane-api/api/cert" - _ "github.com/envoyproxy/data-plane-api/api/eds" - _ "github.com/envoyproxy/data-plane-api/api/hds" - _ "github.com/envoyproxy/data-plane-api/api/lds" - _ "github.com/envoyproxy/data-plane-api/api/metrics_service" - _ "github.com/envoyproxy/data-plane-api/api/rds" - _ "github.com/envoyproxy/data-plane-api/api/rls" - _ "github.com/envoyproxy/data-plane-api/api/sds" - _ "github.com/envoyproxy/data-plane-api/api/trace_service" + _ "github.com/envoyproxy/data-plane-api/api/envoy/api/v2" + _ "github.com/envoyproxy/data-plane-api/api/envoy/api/v2/auth" + _ "github.com/envoyproxy/data-plane-api/api/envoy/config/bootstrap/v2" + _ "github.com/envoyproxy/data-plane-api/api/envoy/service/accesslog/v2" + _ "github.com/envoyproxy/data-plane-api/api/envoy/service/discovery/v2" + _ "github.com/envoyproxy/data-plane-api/api/envoy/service/metrics/v2" + _ "github.com/envoyproxy/data-plane-api/api/envoy/service/ratelimit/v2" + _ "github.com/envoyproxy/data-plane-api/api/envoy/service/trace/v2" ) func TestNoop(t *testing.T) { diff --git a/api/udpa/data/orca/v1/BUILD b/api/udpa/data/orca/v1/BUILD deleted file mode 100644 index 096ca28bac..0000000000 --- a/api/udpa/data/orca/v1/BUILD +++ /dev/null @@ -1,16 +0,0 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_proto_library", "api_proto_library") - -licenses(["notice"]) # Apache 2 - -api_proto_library( - name = "orca_load_report", - srcs = ["orca_load_report.proto"], - visibility = [ - "//visibility:public", - ], -) - -api_go_proto_library( - name = "orca_load_report", - proto = ":orca_load_report", -) diff --git a/api/udpa/data/orca/v1/orca_load_report.proto b/api/udpa/data/orca/v1/orca_load_report.proto deleted file mode 100644 index 5c2eda6cd2..0000000000 --- a/api/udpa/data/orca/v1/orca_load_report.proto +++ /dev/null @@ -1,36 +0,0 @@ -syntax = "proto3"; - -package udpa.data.orca.v1; - -option java_outer_classname = "OrcaLoadReportProto"; -option java_multiple_files = true; -option java_package = "io.envoyproxy.udpa.data.orca.v1"; -option go_package = "v1"; - -import "validate/validate.proto"; - -// See section `ORCA load report format` of the design document in -// :ref:`https://github.com/envoyproxy/envoy/issues/6614`. - -message OrcaLoadReport { - // CPU utilization expressed as a fraction of available CPU resources. This - // should be derived from the latest sample or measurement. - double cpu_utilization = 1 [(validate.rules).double.gte = 0, (validate.rules).double.lte = 1]; - - // Memory utilization expressed as a fraction of available memory - // resources. This should be derived from the latest sample or measurement. - double mem_utilization = 2 [(validate.rules).double.gte = 0, (validate.rules).double.lte = 1]; - - // Total RPS being served by an endpoint. This should cover all services that an endpoint is - // responsible for. - uint64 rps = 3; - - // Application specific requests costs. Each value is an absolute cost (e.g. 3487 bytes of - // storage) associated with the request. - map request_cost = 4; - - // Resource utilization values. Each value is expressed as a fraction of total resources - // available, derived from the latest sample or measurement. - map utilization = 5 - [(validate.rules).map.values.double.gte = 0, (validate.rules).map.values.double.lte = 1]; -} diff --git a/api/udpa/service/orca/v1/BUILD b/api/udpa/service/orca/v1/BUILD deleted file mode 100644 index 72543e8092..0000000000 --- a/api/udpa/service/orca/v1/BUILD +++ /dev/null @@ -1,20 +0,0 @@ -load("@envoy_api//bazel:api_build_system.bzl", "api_go_grpc_library", "api_proto_library_internal") - -licenses(["notice"]) # Apache 2 - -api_proto_library_internal( - name = "orca", - srcs = ["orca.proto"], - has_services = 1, - deps = [ - "//udpa/data/orca/v1:orca_load_report", - ], -) - -api_go_grpc_library( - name = "orca", - proto = ":orca", - deps = [ - "//udpa/data/orca/v1:orca_load_report_go_proto", - ], -) diff --git a/api/udpa/service/orca/v1/orca.proto b/api/udpa/service/orca/v1/orca.proto deleted file mode 100644 index 87871d209a..0000000000 --- a/api/udpa/service/orca/v1/orca.proto +++ /dev/null @@ -1,38 +0,0 @@ -syntax = "proto3"; - -package udpa.service.orca.v1; - -option java_outer_classname = "OrcaProto"; -option java_multiple_files = true; -option java_package = "io.envoyproxy.udpa.service.orca.v1"; -option go_package = "v1"; - -import "udpa/data/orca/v1/orca_load_report.proto"; - -import "google/protobuf/duration.proto"; - -import "validate/validate.proto"; - -// See section `Out-of-band (OOB) reporting` of the design document in -// :ref:`https://github.com/envoyproxy/envoy/issues/6614`. - -// Out-of-band (OOB) load reporting service for the additional load reporting -// agent that does not sit in the request path. Reports are periodically sampled -// with sufficient frequency to provide temporal association with requests. -// OOB reporting compensates the limitation of in-band reporting in revealing -// costs for backends that do not provide a steady stream of telemetry such as -// long running stream operations and zero QPS services. This is a server -// streaming service, client needs to terminate current RPC and initiate -// a new call to change backend reporting frequency. -service OpenRcaService { - rpc StreamCoreMetrics(OrcaLoadReportRequest) returns (stream udpa.data.orca.v1.OrcaLoadReport); -} - -message OrcaLoadReportRequest { - // Interval for generating Open RCA core metric responses. - google.protobuf.Duration report_interval = 1; - // Request costs to collect. If this is empty, all known requests costs tracked by - // the load reporting agent will be returned. This provides an opportunity for - // the client to selectively obtain a subset of tracked costs. - repeated string request_cost_names = 2; -} diff --git a/bazel/BUILD b/bazel/BUILD old mode 100755 new mode 100644 index 3ce73e899b..dffc9cf6f0 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -1,11 +1,14 @@ licenses(["notice"]) # Apache 2 -package(default_visibility = ["//visibility:public"]) +load("//bazel:envoy_build_system.bzl", "envoy_package") + +envoy_package() + +load("//bazel:envoy_internal.bzl", "envoy_select_force_libcpp") exports_files([ "gen_sh_test_runner.sh", "sh_test_wrapper.sh", - "cc_wrapper.py", ]) genrule( @@ -37,6 +40,25 @@ genrule( stamp = 1, ) +# A target to optionally link C++ standard library dynamically in sanitizer runs. +# TSAN doesn't support libc/libstdc++ static linking per doc: +# http://releases.llvm.org/8.0.1/tools/clang/docs/ThreadSanitizer.html +cc_library( + name = "dynamic_stdlib", + linkopts = envoy_select_force_libcpp( + ["-lc++"], + ["-lstdc++"], + ), +) + +cc_library( + name = "static_stdlib", + linkopts = select({ + "//bazel:linux": ["-static-libgcc"], + "//conditions:default": [], + }), +) + config_setting( name = "windows_opt_build", values = { @@ -81,6 +103,11 @@ config_setting( values = {"define": "ENVOY_CONFIG_ASAN=1"}, ) +config_setting( + name = "tsan_build", + values = {"define": "ENVOY_CONFIG_TSAN=1"}, +) + config_setting( name = "coverage_build", values = {"define": "ENVOY_CONFIG_COVERAGE=1"}, @@ -120,6 +147,11 @@ config_setting( values = {"define": "object_dump_on_signal_trace=disabled"}, ) +config_setting( + name = "disable_deprecated_features", + values = {"define": "deprecated_features=disabled"}, +) + config_setting( name = "disable_hot_restart", values = {"define": "hot_restart=disabled"}, diff --git a/bazel/README.md b/bazel/README.md index 2631fbec01..499dd46b80 100644 --- a/bazel/README.md +++ b/bazel/README.md @@ -65,6 +65,7 @@ for how to update or override dependencies. ``` _notes_: `coreutils` is used for `realpath`, `gmd5sum` and `gsha256sum` + XCode is also required to build Envoy on macOS. Envoy compiles and passes tests with the version of clang installed by XCode 9.3.0: Apple LLVM version 9.1.0 (clang-902.0.30). @@ -102,7 +103,7 @@ CI Docker image. ## Building Envoy with Remote Execution -Envoy can also be built with Bazel [Remote Executioon](https://docs.bazel.build/versions/master/remote-execution.html), +Envoy can also be built with Bazel [Remote Execution](https://docs.bazel.build/versions/master/remote-execution.html), part of the CI is running with the hosted [GCP RBE](https://blog.bazel.build/2018/10/05/remote-build-execution.html) service. To build Envoy with a remote build services, run Bazel with your remote build service flags and with `--config=remote-clang`. @@ -407,6 +408,8 @@ The following optional features can be disabled on the Bazel build command-line: * Backtracing on signals with `--define signal_trace=disabled` * Active stream state dump on signals with `--define signal_trace=disabled` or `--define disable_object_dump_on_signal_trace=disabled` * tcmalloc with `--define tcmalloc=disabled` +* deprecated features with `--define deprecated_features=disabled` + ## Enabling optional features diff --git a/bazel/envoy_binary.bzl b/bazel/envoy_binary.bzl index edcdd09ae0..8a9d396dcf 100644 --- a/bazel/envoy_binary.bzl +++ b/bazel/envoy_binary.bzl @@ -4,6 +4,7 @@ load( ":envoy_internal.bzl", "envoy_copts", "envoy_external_dep_path", + "envoy_stdlib_deps", "tcmalloc_external_dep", ) @@ -24,7 +25,7 @@ def envoy_cc_binary( if stamped: linkopts = linkopts + _envoy_stamped_linkopts() deps = deps + _envoy_stamped_deps() - deps = deps + [envoy_external_dep_path(dep) for dep in external_deps] + deps = deps + [envoy_external_dep_path(dep) for dep in external_deps] + envoy_stdlib_deps() native.cc_binary( name = name, srcs = srcs, diff --git a/bazel/envoy_internal.bzl b/bazel/envoy_internal.bzl index 325ea94f55..5dca0a028f 100644 --- a/bazel/envoy_internal.bzl +++ b/bazel/envoy_internal.bzl @@ -56,6 +56,9 @@ def envoy_copts(repository, test = False): }) + select({ repository + "//bazel:disable_object_dump_on_signal_trace": [], "//conditions:default": ["-DENVOY_OBJECT_TRACE_ON_DUMP"], + }) + select({ + repository + "//bazel:disable_deprecated_features": ["-DENVOY_DISABLE_DEPRECATED_FEATURES"], + "//conditions:default": [], }) + select({ repository + "//bazel:enable_log_debug_assert_in_release": ["-DENVOY_LOG_DEBUG_ASSERT_IN_RELEASE"], "//conditions:default": [], @@ -86,6 +89,13 @@ def envoy_select_force_libcpp(if_libcpp, default = None): "//conditions:default": default or [], }) +def envoy_stdlib_deps(): + return select({ + "@envoy//bazel:asan_build": ["@envoy//bazel:dynamic_stdlib"], + "@envoy//bazel:tsan_build": ["@envoy//bazel:dynamic_stdlib"], + "//conditions:default": ["@envoy//bazel:static_stdlib"], + }) + # Dependencies on tcmalloc_and_profiler should be wrapped with this function. def tcmalloc_external_dep(repository): return select({ diff --git a/bazel/envoy_library.bzl b/bazel/envoy_library.bzl index f96f7fe0b8..8120d2f103 100644 --- a/bazel/envoy_library.bzl +++ b/bazel/envoy_library.bzl @@ -72,6 +72,17 @@ def envoy_cc_library( **kargs ) + # Intended for usage by external consumers. This allows them to disambiguate + # include paths via `external/envoy...` + native.cc_library( + name = name + "_with_external_headers", + hdrs = hdrs, + copts = envoy_copts(repository) + copts, + visibility = visibility, + deps = [":" + name], + strip_include_prefix = strip_include_prefix, + ) + # Used to specify a library that only builds on POSIX def envoy_cc_posix_library(name, srcs = [], hdrs = [], **kargs): envoy_cc_library( diff --git a/bazel/envoy_test.bzl b/bazel/envoy_test.bzl index 91d65b8803..73fdff21dc 100644 --- a/bazel/envoy_test.bzl +++ b/bazel/envoy_test.bzl @@ -8,6 +8,7 @@ load( "envoy_external_dep_path", "envoy_linkstatic", "envoy_select_force_libcpp", + "envoy_stdlib_deps", "tcmalloc_external_dep", ) @@ -62,8 +63,15 @@ def _envoy_test_linkopts(): }) + envoy_select_force_libcpp([], ["-lstdc++fs", "-latomic"]) # Envoy C++ fuzz test targets. These are not included in coverage runs. -def envoy_cc_fuzz_test(name, corpus, deps = [], tags = [], **kwargs): - if not (corpus.startswith("//") or corpus.startswith(":")): +def envoy_cc_fuzz_test( + name, + corpus, + repository = "", + size = "medium", + deps = [], + tags = [], + **kwargs): + if not (corpus.startswith("//") or corpus.startswith(":") or corpus.startswith("@")): corpus_name = name + "_corpus" corpus = native.glob([corpus + "/**"]) native.filegroup( @@ -80,7 +88,11 @@ def envoy_cc_fuzz_test(name, corpus, deps = [], tags = [], **kwargs): test_lib_name = name + "_lib" envoy_cc_test_library( name = test_lib_name, - deps = deps + ["//test/fuzz:fuzz_runner_lib"], + deps = deps + [ + repository + "//test/fuzz:fuzz_runner_lib", + repository + "//bazel:dynamic_stdlib", + ], + repository = repository, **kwargs ) native.cc_test( @@ -92,12 +104,13 @@ def envoy_cc_fuzz_test(name, corpus, deps = [], tags = [], **kwargs): data = [corpus_name], # No fuzzing on macOS. deps = select({ - "@envoy//bazel:apple": ["//test:dummy_main"], + "@envoy//bazel:apple": [repository + "//test:dummy_main"], "//conditions:default": [ ":" + test_lib_name, - "//test/fuzz:main", + repository + "//test/fuzz:main", ], }), + size = size, tags = tags, ) @@ -115,7 +128,7 @@ def envoy_cc_fuzz_test(name, corpus, deps = [], tags = [], **kwargs): tags = ["manual"] + tags, ) - native.cc_binary( + native.cc_test( name = name + "_with_libfuzzer", copts = envoy_copts("@envoy", test = True), linkopts = ["-fsanitize=fuzzer"] + _envoy_test_linkopts(), @@ -123,7 +136,7 @@ def envoy_cc_fuzz_test(name, corpus, deps = [], tags = [], **kwargs): testonly = 1, data = [corpus_name], deps = [":" + test_lib_name], - tags = ["manual"] + tags, + tags = ["manual", "fuzzer"] + tags, ) # Envoy C++ test targets should be specified with this function. @@ -163,7 +176,7 @@ def envoy_cc_test( linkopts = _envoy_test_linkopts(), linkstatic = envoy_linkstatic(), malloc = tcmalloc_external_dep(repository), - deps = [ + deps = envoy_stdlib_deps() + [ ":" + name + "_lib_internal_only", repository + "//test:main", ], diff --git a/bazel/external/quiche.BUILD b/bazel/external/quiche.BUILD index e08d8a5987..67d7d4207c 100644 --- a/bazel/external/quiche.BUILD +++ b/bazel/external/quiche.BUILD @@ -59,6 +59,8 @@ quiche_copt = [ "-Wno-type-limits", # quic_inlined_frame.h uses offsetof() to optimize memory usage in frames. "-Wno-invalid-offsetof", + "-Wno-type-limits", + "-Wno-return-type", ] envoy_cc_test_library( @@ -711,6 +713,7 @@ envoy_cc_library( "quiche/spdy/platform/api/spdy_flags.h", "quiche/spdy/platform/api/spdy_logging.h", "quiche/spdy/platform/api/spdy_macros.h", + "quiche/spdy/platform/api/spdy_map_util.h", "quiche/spdy/platform/api/spdy_mem_slice.h", "quiche/spdy/platform/api/spdy_ptr_util.h", "quiche/spdy/platform/api/spdy_string.h", @@ -766,6 +769,16 @@ envoy_cc_library( deps = [":spdy_platform"], ) +envoy_cc_library( + name = "spdy_core_fifo_write_scheduler_lib", + hdrs = ["quiche/spdy/core/fifo_write_scheduler.h"], + repository = "@envoy", + deps = [ + ":spdy_core_write_scheduler_lib", + ":spdy_platform", + ], +) + envoy_cc_library( name = "spdy_core_framer_lib", srcs = [ @@ -849,6 +862,34 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "spdy_core_lifo_write_scheduler_lib", + hdrs = ["quiche/spdy/core/lifo_write_scheduler.h"], + repository = "@envoy", + deps = [ + ":spdy_core_write_scheduler_lib", + ":spdy_platform", + ], +) + +envoy_cc_library( + name = "spdy_core_intrusive_list_lib", + hdrs = ["quiche/spdy/core/spdy_intrusive_list.h"], + repository = "@envoy", +) + +envoy_cc_library( + name = "spdy_core_http2_priority_write_scheduler_lib", + hdrs = ["quiche/spdy/core/http2_priority_write_scheduler.h"], + repository = "@envoy", + deps = [ + ":spdy_core_intrusive_list_lib", + ":spdy_core_protocol_lib", + ":spdy_core_write_scheduler_lib", + ":spdy_platform", + ], +) + envoy_cc_library( name = "spdy_core_hpack_hpack_lib", srcs = [ @@ -976,6 +1017,7 @@ envoy_cc_library( "quiche/quic/platform/api/quic_pcc_sender.h", ], repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ ":quic_core_time_lib", @@ -1021,6 +1063,7 @@ envoy_cc_library( # "quiche/quic/platform/api/quic_test_loopback.h", ], repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ ":quic_platform_export", @@ -1033,6 +1076,7 @@ envoy_cc_library( name = "quic_platform_bbr2_sender", hdrs = ["quiche/quic/platform/api/quic_bbr2_sender.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//source/extensions/quic_listeners/quiche/platform:quic_platform_bbr2_sender_impl_lib"], ) @@ -1040,6 +1084,7 @@ envoy_cc_test_library( name = "quic_platform_epoll_lib", hdrs = ["quiche/quic/platform/api/quic_epoll.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_epoll_impl_lib"], ) @@ -1047,6 +1092,7 @@ envoy_cc_test_library( name = "quic_platform_expect_bug", hdrs = ["quiche/quic/platform/api/quic_expect_bug.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_expect_bug_impl_lib"], ) @@ -1054,6 +1100,7 @@ envoy_cc_library( name = "quic_platform_export", hdrs = ["quiche/quic/platform/api/quic_export.h"], repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = ["@envoy//source/extensions/quic_listeners/quiche/platform:quic_platform_export_impl_lib"], ) @@ -1062,6 +1109,7 @@ envoy_cc_library( name = "quic_platform_ip_address_family", hdrs = ["quiche/quic/platform/api/quic_ip_address_family.h"], repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], ) @@ -1071,6 +1119,7 @@ envoy_cc_library( hdrs = ["quiche/quic/platform/api/quic_ip_address.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ ":quic_platform_base", @@ -1083,6 +1132,7 @@ envoy_cc_test_library( name = "quic_platform_mock_log", hdrs = ["quiche/quic/platform/api/quic_mock_log.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_mock_log_impl_lib"], ) @@ -1090,6 +1140,7 @@ envoy_cc_test_library( name = "quic_platform_port_utils", hdrs = ["quiche/quic/platform/api/quic_port_utils.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_port_utils_impl_lib"], ) @@ -1097,6 +1148,7 @@ envoy_cc_test_library( name = "quic_platform_sleep", hdrs = ["quiche/quic/platform/api/quic_sleep.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_sleep_impl_lib"], ) @@ -1106,6 +1158,7 @@ envoy_cc_library( hdrs = ["quiche/quic/platform/api/quic_socket_address.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ ":quic_platform_export", @@ -1117,6 +1170,7 @@ envoy_cc_test_library( name = "quic_platform_test", hdrs = ["quiche/quic/platform/api/quic_test.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_test_impl_lib"], ) @@ -1124,6 +1178,7 @@ envoy_cc_test_library( name = "quic_platform_test_output", hdrs = ["quiche/quic/platform/api/quic_test_output.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_test_output_impl_lib"], ) @@ -1131,6 +1186,7 @@ envoy_cc_test_library( name = "quic_platform_system_event_loop", hdrs = ["quiche/quic/platform/api/quic_system_event_loop.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_system_event_loop_impl_lib"], ) @@ -1138,6 +1194,7 @@ envoy_cc_test_library( name = "quic_platform_thread", hdrs = ["quiche/quic/platform/api/quic_thread.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_thread_impl_lib"], ) @@ -1156,6 +1213,7 @@ envoy_cc_library( name = "quic_core_proto_cached_network_parameters_proto_header", hdrs = ["quiche/quic/core/proto/cached_network_parameters_proto.h"], repository = "@envoy", + tags = ["nofips"], deps = [":quic_core_proto_cached_network_parameters_proto_cc"], ) @@ -1174,6 +1232,7 @@ envoy_cc_library( name = "quic_core_proto_source_address_token_proto_header", hdrs = ["quiche/quic/core/proto/source_address_token_proto.h"], repository = "@envoy", + tags = ["nofips"], deps = [":quic_core_proto_source_address_token_proto_cc"], ) @@ -1191,6 +1250,7 @@ envoy_cc_library( name = "quic_core_proto_crypto_server_config_proto_header", hdrs = ["quiche/quic/core/proto/crypto_server_config_proto.h"], repository = "@envoy", + tags = ["nofips"], deps = [":quic_core_proto_crypto_server_config_proto_cc"], ) @@ -1278,6 +1338,7 @@ envoy_cc_library( "quiche/quic/core/quic_simple_buffer_allocator.h", ], repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = [":quic_platform_export"], ) @@ -1311,6 +1372,8 @@ envoy_cc_library( tags = ["nofips"], deps = [ ":quic_core_bandwidth_lib", + ":quic_core_congestion_control_congestion_control_interface_lib", + ":quic_core_congestion_control_windowed_filter_lib", ":quic_core_packet_number_indexed_queue_lib", ":quic_core_packets_lib", ":quic_core_time_lib", @@ -1903,6 +1966,14 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "quic_core_http_http_constants_lib", + hdrs = ["quiche/quic/core/http/http_constants.h"], + copts = quiche_copt, + repository = "@envoy", + deps = [":quic_core_types_lib"], +) + envoy_cc_library( name = "quic_core_http_client_lib", srcs = [ @@ -1945,6 +2016,7 @@ envoy_cc_library( copts = quiche_copt, repository = "@envoy", tags = ["nofips"], + visibility = ["//visibility:public"], deps = [ ":quic_core_packets_lib", ":quic_platform_base", @@ -2003,6 +2075,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/http/spdy_server_push_utils.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ ":quic_platform_base", @@ -2039,6 +2112,7 @@ envoy_cc_library( ":quic_core_crypto_crypto_handshake_lib", ":quic_core_error_codes_lib", ":quic_core_http_header_list_lib", + ":quic_core_http_http_constants_lib", ":quic_core_http_http_decoder_lib", ":quic_core_http_http_encoder_lib", ":quic_core_http_spdy_stream_body_buffer_lib", @@ -2230,6 +2304,9 @@ envoy_cc_library( ":quic_core_versions_lib", ":quic_platform", ":quic_platform_socket_address", + ":spdy_core_fifo_write_scheduler_lib", + ":spdy_core_http2_priority_write_scheduler_lib", + ":spdy_core_lifo_write_scheduler_lib", ":spdy_core_priority_write_scheduler_lib", ], ) @@ -2260,29 +2337,13 @@ envoy_cc_library( ) envoy_cc_library( - name = "quic_core_qpack_qpack_instruction_encoder_lib", - srcs = ["quiche/quic/core/qpack/qpack_instruction_encoder.cc"], - hdrs = ["quiche/quic/core/qpack/qpack_instruction_encoder.h"], - copts = quiche_copt, - repository = "@envoy", - deps = [ - ":http2_hpack_huffman_hpack_huffman_encoder_lib", - ":http2_hpack_varint_hpack_varint_encoder_lib", - ":quic_core_qpack_qpack_constants_lib", - ":quic_platform", - ], -) - -envoy_cc_library( - name = "quic_core_qpack_qpack_instruction_decoder_lib", - srcs = ["quiche/quic/core/qpack/qpack_instruction_decoder.cc"], - hdrs = ["quiche/quic/core/qpack/qpack_instruction_decoder.h"], - copts = quiche_copt, + name = "quic_core_qpack_blocking_manager_lib", + srcs = ["quiche/quic/core/qpack/qpack_blocking_manager.cc"], + hdrs = ["quiche/quic/core/qpack/qpack_blocking_manager.h"], repository = "@envoy", + tags = ["nofips"], deps = [ - ":http2_hpack_huffman_hpack_huffman_decoder_lib", - ":http2_hpack_varint_hpack_varint_decoder_lib", - ":quic_core_qpack_qpack_constants_lib", + ":quic_core_types_lib", ":quic_platform_base", ], ) @@ -2293,21 +2354,42 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_constants.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":quic_platform_base"], ) +envoy_cc_library( + name = "quic_core_qpack_qpack_decoder_lib", + srcs = ["quiche/quic/core/qpack/qpack_decoder.cc"], + hdrs = ["quiche/quic/core/qpack/qpack_decoder.h"], + copts = quiche_copt, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_qpack_qpack_decoder_stream_sender_lib", + ":quic_core_qpack_qpack_encoder_stream_receiver_lib", + ":quic_core_qpack_qpack_header_table_lib", + ":quic_core_qpack_qpack_progressive_decoder_lib", + ":quic_core_types_lib", + ":quic_platform_base", + ], +) + envoy_cc_library( name = "quic_core_qpack_qpack_encoder_lib", srcs = ["quiche/quic/core/qpack/qpack_encoder.cc"], hdrs = ["quiche/quic/core/qpack/qpack_encoder.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ + ":quic_core_qpack_blocking_manager_lib", ":quic_core_qpack_qpack_constants_lib", ":quic_core_qpack_qpack_decoder_stream_receiver_lib", ":quic_core_qpack_qpack_encoder_stream_sender_lib", ":quic_core_qpack_qpack_header_table_lib", ":quic_core_qpack_qpack_instruction_encoder_lib", + ":quic_core_qpack_qpack_required_insert_count_lib", ":quic_core_qpack_value_splitting_header_list_lib", ":quic_core_types_lib", ":quic_platform_base", @@ -2315,49 +2397,76 @@ envoy_cc_library( ) envoy_cc_library( - name = "quic_core_qpack_qpack_progressive_decoder_lib", - srcs = ["quiche/quic/core/qpack/qpack_progressive_decoder.cc"], - hdrs = ["quiche/quic/core/qpack/qpack_progressive_decoder.h"], + name = "quic_core_qpack_qpack_header_table_lib", + srcs = ["quiche/quic/core/qpack/qpack_header_table.cc"], + hdrs = ["quiche/quic/core/qpack/qpack_header_table.h"], + copts = quiche_copt, + repository = "@envoy", + tags = ["nofips"], + deps = [ + ":quic_core_qpack_qpack_static_table_lib", + ":quic_platform_base", + ":spdy_core_hpack_hpack_lib", + ], +) + +envoy_cc_library( + name = "quic_core_qpack_qpack_instruction_decoder_lib", + srcs = ["quiche/quic/core/qpack/qpack_instruction_decoder.cc"], + hdrs = ["quiche/quic/core/qpack/qpack_instruction_decoder.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ + ":http2_hpack_huffman_hpack_huffman_decoder_lib", + ":http2_hpack_varint_hpack_varint_decoder_lib", ":quic_core_qpack_qpack_constants_lib", - ":quic_core_qpack_qpack_decoder_stream_sender_lib", - ":quic_core_qpack_qpack_encoder_stream_receiver_lib", - ":quic_core_qpack_qpack_header_table_lib", - ":quic_core_qpack_qpack_instruction_decoder_lib", - ":quic_core_types_lib", ":quic_platform_base", ], ) envoy_cc_library( - name = "quic_core_qpack_qpack_decoder_lib", - srcs = ["quiche/quic/core/qpack/qpack_decoder.cc"], - hdrs = ["quiche/quic/core/qpack/qpack_decoder.h"], + name = "quic_core_qpack_qpack_instruction_encoder_lib", + srcs = ["quiche/quic/core/qpack/qpack_instruction_encoder.cc"], + hdrs = ["quiche/quic/core/qpack/qpack_instruction_encoder.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], + deps = [ + ":http2_hpack_huffman_hpack_huffman_encoder_lib", + ":http2_hpack_varint_hpack_varint_encoder_lib", + ":quic_core_qpack_qpack_constants_lib", + ":quic_platform", + ], +) + +envoy_cc_library( + name = "quic_core_qpack_qpack_progressive_decoder_lib", + srcs = ["quiche/quic/core/qpack/qpack_progressive_decoder.cc"], + hdrs = ["quiche/quic/core/qpack/qpack_progressive_decoder.h"], + copts = quiche_copt, + repository = "@envoy", + tags = ["nofips"], deps = [ + ":quic_core_qpack_qpack_constants_lib", ":quic_core_qpack_qpack_decoder_stream_sender_lib", ":quic_core_qpack_qpack_encoder_stream_receiver_lib", ":quic_core_qpack_qpack_header_table_lib", - ":quic_core_qpack_qpack_progressive_decoder_lib", + ":quic_core_qpack_qpack_instruction_decoder_lib", + ":quic_core_qpack_qpack_required_insert_count_lib", ":quic_core_types_lib", ":quic_platform_base", ], ) envoy_cc_library( - name = "quic_core_qpack_qpack_header_table_lib", - srcs = ["quiche/quic/core/qpack/qpack_header_table.cc"], - hdrs = ["quiche/quic/core/qpack/qpack_header_table.h"], + name = "quic_core_qpack_qpack_required_insert_count_lib", + srcs = ["quiche/quic/core/qpack/qpack_required_insert_count.cc"], + hdrs = ["quiche/quic/core/qpack/qpack_required_insert_count.h"], copts = quiche_copt, repository = "@envoy", - deps = [ - ":quic_core_qpack_qpack_static_table_lib", - ":quic_platform_base", - ":spdy_core_hpack_hpack_lib", - ], + tags = ["nofips"], + deps = [":quic_platform_base"], ) envoy_cc_library( @@ -2365,6 +2474,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_utils.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":quic_core_qpack_qpack_stream_sender_delegate_lib"], ) @@ -2374,6 +2484,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_encoder_stream_sender.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_qpack_qpack_constants_lib", ":quic_core_qpack_qpack_instruction_encoder_lib", @@ -2388,6 +2499,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_encoder_stream_receiver.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":http2_decoder_decode_buffer_lib", ":http2_decoder_decode_status_lib", @@ -2404,6 +2516,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_decoder_stream_sender.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_qpack_qpack_constants_lib", ":quic_core_qpack_qpack_instruction_encoder_lib", @@ -2419,6 +2532,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_decoder_stream_receiver.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":http2_decoder_decode_buffer_lib", ":http2_decoder_decode_status_lib", @@ -2436,6 +2550,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_static_table.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_platform_base", ":spdy_core_hpack_hpack_lib", @@ -2447,6 +2562,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_stream_receiver.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":quic_platform_base"], ) @@ -2456,6 +2572,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_decoded_headers_accumulator.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_http_header_list_lib", ":quic_core_qpack_qpack_decoder_lib", @@ -2471,6 +2588,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/value_splitting_header_list.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_platform_base", ":spdy_core_header_block_lib", @@ -2502,6 +2620,7 @@ envoy_cc_library( hdrs = ["quiche/quic/core/qpack/qpack_stream_sender_delegate.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":quic_platform_base"], ) @@ -2767,6 +2886,7 @@ envoy_cc_library( srcs = ["quiche/quic/core/quic_time.cc"], hdrs = ["quiche/quic/core/quic_time.h"], repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = [":quic_platform_base"], ) @@ -2819,10 +2939,12 @@ envoy_cc_library( "quiche/quic/core/quic_types.h", ], copts = quiche_copt, + external_deps = ["ssl"], repository = "@envoy", tags = ["nofips"], visibility = ["//visibility:public"], deps = [ + ":quic_core_crypto_random_lib", ":quic_core_error_codes_lib", ":quic_core_time_lib", ":quic_platform_base", @@ -2915,6 +3037,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/quic_config_peer.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_config_lib", ":quic_core_packets_lib", @@ -2928,6 +3051,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/quic_framer_peer.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_crypto_encryption_lib", ":quic_core_framer_lib", @@ -2942,6 +3066,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/mock_clock.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_time_lib", ":quic_platform", @@ -2954,6 +3079,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/mock_random.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":quic_core_crypto_random_lib"], ) @@ -2963,6 +3089,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/quic_packet_generator_peer.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_packet_creator_lib", ":quic_core_packet_generator_lib", @@ -2976,6 +3103,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/quic_sent_packet_manager_peer.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_congestion_control_congestion_control_interface_lib", ":quic_core_packets_lib", @@ -2990,6 +3118,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/simple_quic_framer.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_crypto_encryption_lib", ":quic_core_framer_lib", @@ -3004,6 +3133,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/quic_stream_send_buffer_peer.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":quic_core_stream_send_buffer_lib"], ) @@ -3013,6 +3143,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/quic_stream_peer.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_packets_lib", ":quic_core_session_lib", @@ -3043,6 +3174,7 @@ envoy_cc_test_library( copts = quiche_copt, external_deps = ["ssl"], repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_buffer_allocator_lib", ":quic_core_congestion_control_congestion_control_interface_lib", @@ -3086,6 +3218,7 @@ envoy_cc_test_library( hdrs = ["quiche/quic/test_tools/quic_unacked_packet_map_peer.h"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":quic_core_unacked_packet_map_lib"], ) @@ -3103,6 +3236,7 @@ envoy_cc_test_library( "quiche/epoll_server/platform/api/epoll_time.h", ], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:epoll_server_platform_impl_lib"], ) @@ -3124,6 +3258,7 @@ envoy_cc_test_library( }), copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":epoll_server_platform"], ) @@ -3135,6 +3270,7 @@ envoy_cc_library( "quiche/common/platform/api/quiche_unordered_containers.h", ], repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = ["@envoy//source/extensions/quic_listeners/quiche/platform:quiche_common_platform_impl_lib"], ) @@ -3143,6 +3279,7 @@ envoy_cc_test_library( name = "quiche_common_platform_test", hdrs = ["quiche/common/platform/api/quiche_test.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quiche_common_platform_test_impl_lib"], ) @@ -3150,6 +3287,7 @@ envoy_cc_library( name = "quiche_common_lib", hdrs = ["quiche/common/simple_linked_hash_map.h"], repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = [":quiche_common_platform"], ) @@ -3162,6 +3300,7 @@ envoy_cc_test( }), copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [":epoll_server_lib"], ) @@ -3170,6 +3309,7 @@ envoy_cc_test( srcs = ["quiche/common/simple_linked_hash_map_test.cc"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quiche_common_lib", ":quiche_common_platform_test", @@ -3183,6 +3323,7 @@ envoy_cc_test( "quiche/http2/test_tools/http2_random_test.cc", ], repository = "@envoy", + tags = ["nofips"], deps = [ ":http2_platform", ":http2_test_tools_random", @@ -3193,6 +3334,7 @@ envoy_cc_test( name = "spdy_platform_api_test", srcs = ["quiche/spdy/platform/api/spdy_string_utils_test.cc"], repository = "@envoy", + tags = ["nofips"], deps = [ ":spdy_platform", ":spdy_platform_test", @@ -3206,6 +3348,7 @@ envoy_cc_library( ], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], visibility = ["//visibility:public"], deps = ["@envoy//source/extensions/quic_listeners/quiche/platform:quic_platform_mem_slice_span_impl_lib"], ) @@ -3214,6 +3357,7 @@ envoy_cc_test_library( name = "quic_platform_test_mem_slice_vector_lib", hdrs = ["quiche/quic/platform/api/quic_test_mem_slice_vector.h"], repository = "@envoy", + tags = ["nofips"], deps = ["@envoy//test/extensions/quic_listeners/quiche/platform:quic_platform_test_mem_slice_vector_impl_lib"], ) @@ -3230,6 +3374,7 @@ envoy_cc_test( srcs = ["quiche/spdy/core/spdy_header_block_test.cc"], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":spdy_core_header_block_lib", ":spdy_core_test_utils_lib", @@ -3250,6 +3395,7 @@ envoy_cc_test( ], copts = quiche_copt, repository = "@envoy", + tags = ["nofips"], deps = [ ":quic_core_buffer_allocator_lib", ":quic_platform", diff --git a/bazel/foreign_cc/BUILD b/bazel/foreign_cc/BUILD index 4c9e506f3f..7ae723cf99 100644 --- a/bazel/foreign_cc/BUILD +++ b/bazel/foreign_cc/BUILD @@ -64,23 +64,47 @@ envoy_cmake_external( envoy_cmake_external( name = "curl", + # Options from https://github.com/curl/curl/blob/master/CMakeLists.txt. cache_entries = { "BUILD_CURL_EXE": "off", - "BUILD_SHARED_LIBS": "off", - "CMAKE_BUILD_TYPE": "RelWithDebInfo", - "HTTP_ONLY": "on", "BUILD_TESTING": "off", - "CMAKE_USE_OPENSSL": "off", - "CURL_CA_PATH": "none", + "BUILD_SHARED_LIBS": "off", "CURL_HIDDEN_SYMBOLS": "off", "CMAKE_USE_LIBSSH2": "off", + "CURL_BROTLI": "off", + "CMAKE_USE_GSSAPI": "off", + "HTTP_ONLY": "on", + "CMAKE_BUILD_TYPE": "RelWithDebInfo", "CMAKE_INSTALL_LIBDIR": "lib", + # C-Ares. + "ENABLE_ARES": "on", + "CARES_LIBRARY": "$EXT_BUILD_DEPS/ares", + "CARES_INCLUDE_DIR": "$EXT_BUILD_DEPS/ares/include", + # SSL (via Envoy's SSL dependency) is disabled, curl's CMake uses FindOpenSSL.cmake which fails at what looks like + # version parsing (the libraries are found ok). + "CURL_CA_PATH": "none", + "CMAKE_USE_OPENSSL": "off", + "OPENSSL_ROOT_DIR": "$EXT_BUILD_DEPS", + # NGHTTP2. + "USE_NGHTTP2": "on", + "NGHTTP2_LIBRARY": "$EXT_BUILD_DEPS/nghttp2", + "NGHTTP2_INCLUDE_DIR": "$EXT_BUILD_DEPS/nghttp2/include", + # ZLIB. + "CURL_ZLIB": "on", + "ZLIB_LIBRARY": "$EXT_BUILD_DEPS/zlib", + "ZLIB_INCLUDE_DIR": "$EXT_BUILD_DEPS/zlib/include", }, lib_source = "@com_github_curl//:all", static_libraries = select({ "//bazel:windows_x86_64": ["curl.lib"], "//conditions:default": ["libcurl.a"], }), + deps = [ + ":ares", + ":nghttp2", + ":zlib", + "//external:ssl", + ], ) envoy_cmake_external( diff --git a/bazel/grpc-rename-gettid.patch b/bazel/grpc-rename-gettid.patch new file mode 100644 index 0000000000..90bd911589 --- /dev/null +++ b/bazel/grpc-rename-gettid.patch @@ -0,0 +1,78 @@ +From d1d017390b799c59d6fdf7b8afa6136d218bdd61 Mon Sep 17 00:00:00 2001 +From: Benjamin Peterson +Date: Fri, 3 May 2019 08:11:00 -0700 +Subject: [PATCH] Rename gettid() functions. + +glibc 2.30 will declare its own gettid; see https://sourceware.org/git/?p=glibc.git;a=commit;h=1d0fc213824eaa2a8f8c4385daaa698ee8fb7c92. Rename the grpc versions to avoid naming conflicts. +--- + src/core/lib/gpr/log_linux.cc | 4 ++-- + src/core/lib/gpr/log_posix.cc | 4 ++-- + src/core/lib/iomgr/ev_epollex_linux.cc | 4 ++-- + 3 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/core/lib/gpr/log_linux.cc b/src/core/lib/gpr/log_linux.cc +index 561276f0c20..8b597b4cf2f 100644 +--- a/src/core/lib/gpr/log_linux.cc ++++ b/src/core/lib/gpr/log_linux.cc +@@ -40,7 +40,7 @@ + #include + #include + +-static long gettid(void) { return syscall(__NR_gettid); } ++static long sys_gettid(void) { return syscall(__NR_gettid); } + + void gpr_log(const char* file, int line, gpr_log_severity severity, + const char* format, ...) { +@@ -70,7 +70,7 @@ void gpr_default_log(gpr_log_func_args* args) { + gpr_timespec now = gpr_now(GPR_CLOCK_REALTIME); + struct tm tm; + static __thread long tid = 0; +- if (tid == 0) tid = gettid(); ++ if (tid == 0) tid = sys_gettid(); + + timer = static_cast(now.tv_sec); + final_slash = strrchr(args->file, '/'); +diff --git a/src/core/lib/gpr/log_posix.cc b/src/core/lib/gpr/log_posix.cc +index b6edc14ab6b..2f7c6ce3760 100644 +--- a/src/core/lib/gpr/log_posix.cc ++++ b/src/core/lib/gpr/log_posix.cc +@@ -31,7 +31,7 @@ + #include + #include + +-static intptr_t gettid(void) { return (intptr_t)pthread_self(); } ++static intptr_t sys_gettid(void) { return (intptr_t)pthread_self(); } + + void gpr_log(const char* file, int line, gpr_log_severity severity, + const char* format, ...) { +@@ -86,7 +86,7 @@ void gpr_default_log(gpr_log_func_args* args) { + char* prefix; + gpr_asprintf(&prefix, "%s%s.%09d %7" PRIdPTR " %s:%d]", + gpr_log_severity_string(args->severity), time_buffer, +- (int)(now.tv_nsec), gettid(), display_file, args->line); ++ (int)(now.tv_nsec), sys_gettid(), display_file, args->line); + + fprintf(stderr, "%-70s %s\n", prefix, args->message); + gpr_free(prefix); +diff --git a/src/core/lib/iomgr/ev_epollex_linux.cc b/src/core/lib/iomgr/ev_epollex_linux.cc +index 08116b3ab53..76f59844312 100644 +--- a/src/core/lib/iomgr/ev_epollex_linux.cc ++++ b/src/core/lib/iomgr/ev_epollex_linux.cc +@@ -1102,7 +1102,7 @@ static void end_worker(grpc_pollset* pollset, grpc_pollset_worker* worker, + } + + #ifndef NDEBUG +-static long gettid(void) { return syscall(__NR_gettid); } ++static long sys_gettid(void) { return syscall(__NR_gettid); } + #endif + + /* pollset->mu lock must be held by the caller before calling this. +@@ -1122,7 +1122,7 @@ static grpc_error* pollset_work(grpc_pollset* pollset, + #define WORKER_PTR (&worker) + #endif + #ifndef NDEBUG +- WORKER_PTR->originator = gettid(); ++ WORKER_PTR->originator = sys_gettid(); + #endif + if (GRPC_TRACE_FLAG_ENABLED(grpc_polling_trace)) { + gpr_log(GPR_INFO, diff --git a/bazel/io_opentracing_cpp.patch b/bazel/io_opentracing_cpp.patch new file mode 100644 index 0000000000..6389489d65 --- /dev/null +++ b/bazel/io_opentracing_cpp.patch @@ -0,0 +1,17 @@ +diff --git a/src/dynamic_load_unix.cpp b/src/dynamic_load_unix.cpp +index 17e08fd..d25e0c8 100644 +--- a/src/dynamic_load_unix.cpp ++++ b/src/dynamic_load_unix.cpp +@@ -35,7 +35,11 @@ DynamicallyLoadTracingLibrary(const char* shared_library, + std::string& error_message) noexcept try { + dlerror(); // Clear any existing error. + +- const auto handle = dlopen(shared_library, RTLD_NOW | RTLD_LOCAL); ++ const auto handle = dlopen(shared_library, RTLD_NOW | RTLD_LOCAL ++#ifdef __SANITIZE_ADDRESS__ ++ | RTLD_NODELETE ++#endif ++ ); + if (handle == nullptr) { + error_message = dlerror(); + return make_unexpected(dynamic_load_failure_error); diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 8b53b129a7..f1c5ef506d 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -338,7 +338,12 @@ def _com_github_nghttp2_nghttp2(): ) def _io_opentracing_cpp(): - _repository_impl("io_opentracing_cpp") + _repository_impl( + name = "io_opentracing_cpp", + patch_args = ["-p1"], + # Workaround for LSAN false positive in https://github.com/envoyproxy/envoy/issues/7647 + patches = ["@envoy//bazel:io_opentracing_cpp.patch"], + ) native.bind( name = "opentracing", actual = "@io_opentracing_cpp//:opentracing", @@ -552,6 +557,10 @@ def _io_opencensus_cpp(): name = "opencensus_trace_trace_context", actual = "@io_opencensus_cpp//opencensus/trace:trace_context", ) + native.bind( + name = "opencensus_exporter_ocagent", + actual = "@io_opencensus_cpp//opencensus/exporters/trace/ocagent:ocagent_exporter", + ) native.bind( name = "opencensus_exporter_stdout", actual = "@io_opencensus_cpp//opencensus/exporters/trace/stdout:stdout_exporter", @@ -616,6 +625,8 @@ def _com_github_grpc_grpc(): "@envoy//bazel:grpc-protoinfo-2.patch", # Pre-integration of https://github.com/grpc/grpc/pull/19860 "@envoy//bazel:grpc-protoinfo-3.patch", + # Pre-integration of https://github.com/grpc/grpc/pull/18950 + "@envoy//bazel:grpc-rename-gettid.patch", ], patch_args = ["-p1"], ) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index fc1a43d44c..2e6b7752a9 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -4,9 +4,12 @@ REPOSITORY_LOCATIONS = dict( urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.18.1/bazel-gazelle-0.18.1.tar.gz"], ), bazel_toolchains = dict( - sha256 = "b72e7a911436b2900b05759a1fcd735070edbd4442f0a3506ef021fdcd6e15b3", - strip_prefix = "bazel-toolchains-0.28.5", - urls = ["https://github.com/bazelbuild/bazel-toolchains/archive/0.28.5.tar.gz"], + sha256 = "ab0d8aaeaeeef413ddb03922dbdb99bbae9e1b2c157a87c77d70d45a830be5b0", + strip_prefix = "bazel-toolchains-0.29.1", + urls = [ + "https://github.com/bazelbuild/bazel-toolchains/releases/download/0.29.1/bazel-toolchains-0.29.1.tar.gz", + "https://mirror.bazel.build/github.com/bazelbuild/bazel-toolchains/archive/0.29.1.tar.gz", + ], ), boringssl = dict( sha256 = "c712766ddc844de2a38e686e1cdd7288795e9a6fe7f699c6636f1b76703db84e", @@ -158,10 +161,10 @@ REPOSITORY_LOCATIONS = dict( urls = ["https://github.com/msgpack/msgpack-c/releases/download/cpp-3.2.0/msgpack-3.2.0.tar.gz"], ), com_github_google_jwt_verify = dict( - sha256 = "8ab9a0b3f8b7eab5f1cd059920e81fdc138cd4ee657c1412af891652929885c5", - strip_prefix = "jwt_verify_lib-6356535ae83a3f1820b6b06dae80cd6a0a03e7f2", - # 2019-07-01 - urls = ["https://github.com/google/jwt_verify_lib/archive/6356535ae83a3f1820b6b06dae80cd6a0a03e7f2.tar.gz"], + sha256 = "2d57d336239d5fe36a03849ddbea1bff09a1720e1c4a46bbb9743c71732b0d43", + strip_prefix = "jwt_verify_lib-0f14d43f20381cfae0469cb2309b2e220c0f0ea3", + # 2019-07-08 + urls = ["https://github.com/google/jwt_verify_lib/archive/0f14d43f20381cfae0469cb2309b2e220c0f0ea3.tar.gz"], ), com_github_nodejs_http_parser = dict( sha256 = "ef26268c54c8084d17654ba2ed5140bffeffd2a040a895ffb22a6cca3f6c613f", @@ -212,10 +215,10 @@ REPOSITORY_LOCATIONS = dict( urls = ["https://github.com/protocolbuffers/protobuf/releases/download/v3.8.0/protobuf-all-3.8.0.tar.gz"], ), grpc_httpjson_transcoding = dict( - sha256 = "dedd76b0169eb8c72e479529301a1d9b914a4ccb4d2b5ddb4ebe92d63a7b2152", - strip_prefix = "grpc-httpjson-transcoding-64d6ac985360b624d8e95105701b64a3814794cd", - # 2018-12-19 - urls = ["https://github.com/grpc-ecosystem/grpc-httpjson-transcoding/archive/64d6ac985360b624d8e95105701b64a3814794cd.tar.gz"], + sha256 = "a447458b47ea4dc1d31499f555769af437c5d129d988ec1e13d5fdd0a6a36b4e", + strip_prefix = "grpc-httpjson-transcoding-2feabd5d64436e670084091a937855972ee35161", + # 2019-08-28 + urls = ["https://github.com/grpc-ecosystem/grpc-httpjson-transcoding/archive/2feabd5d64436e670084091a937855972ee35161.tar.gz"], ), io_bazel_rules_go = dict( sha256 = "96b1f81de5acc7658e1f5a86d7dc9e1b89bc935d83799b711363a748652c471a", @@ -248,10 +251,10 @@ REPOSITORY_LOCATIONS = dict( urls = ["https://storage.googleapis.com/envoyproxy-wee8/wee8-7.8.196.tar.gz"], ), io_opencensus_cpp = dict( - sha256 = "8d6016e47c2e19e7acbadb6f905b8c422748c64299d71101ac8f28151677e195", - strip_prefix = "opencensus-cpp-cad0d03ff3474cf14389fc249e16847ab7b6895f", - # 2019-07-31 - urls = ["https://github.com/census-instrumentation/opencensus-cpp/archive/cad0d03ff3474cf14389fc249e16847ab7b6895f.tar.gz"], + sha256 = "145e42594db358905737dc07400657be62a2961f4e93ab7f4c9765dd2441033c", + strip_prefix = "opencensus-cpp-cc198ff64569bc47beed5384777a4bb563d268e7", + # 2019-09-04 + urls = ["https://github.com/census-instrumentation/opencensus-cpp/archive/cc198ff64569bc47beed5384777a4bb563d268e7.tar.gz"], ), com_github_curl = dict( sha256 = "4376ac72b95572fb6c4fbffefb97c7ea0dd083e1974c0e44cd7e49396f454839", @@ -259,9 +262,9 @@ REPOSITORY_LOCATIONS = dict( urls = ["https://github.com/curl/curl/releases/download/curl-7_65_3/curl-7.65.3.tar.gz"], ), com_googlesource_quiche = dict( - # Static snapshot of https://quiche.googlesource.com/quiche/+archive/2a930469533c3b541443488a629fe25cd8ff53d0.tar.gz - sha256 = "fcdebf54c89d839ffa7eefae166c8e4b551c765559db13ff15bff98047f344fb", - urls = ["https://storage.googleapis.com/quiche-envoy-integration/2a930469533c3b541443488a629fe25cd8ff53d0.tar.gz"], + # Static snapshot of https://quiche.googlesource.com/quiche/+archive/4abb566fbbc63df8fe7c1ac30b21632b9eb18d0c.tar.gz + sha256 = "c60bca3cf7f58b91394a89da96080657ff0fbe4d5675be9b21e90da8f68bc06f", + urls = ["https://storage.googleapis.com/quiche-envoy-integration/4abb566fbbc63df8fe7c1ac30b21632b9eb18d0c.tar.gz"], ), com_google_cel_cpp = dict( sha256 = "f027c551d57d38fb9f0b5e4f21a2b0b8663987119e23b1fd8dfcc7588e9a2350", @@ -269,8 +272,8 @@ REPOSITORY_LOCATIONS = dict( urls = ["https://github.com/google/cel-cpp/archive/d9d02b20ab85da2444dbdd03410bac6822141364.tar.gz"], ), com_googlesource_code_re2 = dict( - sha256 = "f31db9cd224d018a7e4fe88ef84aaa874b0b3ed91d4d98ee5a1531101d3fdc64", - strip_prefix = "re2-87e2ad45e7b18738e1551474f7ee5886ff572059", - urls = ["https://github.com/google/re2/archive/87e2ad45e7b18738e1551474f7ee5886ff572059.tar.gz"], + sha256 = "38bc0426ee15b5ed67957017fd18201965df0721327be13f60496f2b356e3e01", + strip_prefix = "re2-2019-08-01", + urls = ["https://github.com/google/re2/archive/2019-08-01.tar.gz"], ), ) diff --git a/bazel/toolchains/configs/clang/bazel_0.28.1/cc/BUILD b/bazel/toolchains/configs/clang/bazel_0.29.1/cc/BUILD similarity index 95% rename from bazel/toolchains/configs/clang/bazel_0.28.1/cc/BUILD rename to bazel/toolchains/configs/clang/bazel_0.29.1/cc/BUILD index 1726bdef2c..da902b38d5 100755 --- a/bazel/toolchains/configs/clang/bazel_0.28.1/cc/BUILD +++ b/bazel/toolchains/configs/clang/bazel_0.29.1/cc/BUILD @@ -18,6 +18,7 @@ package(default_visibility = ["//visibility:public"]) load(":cc_toolchain_config.bzl", "cc_toolchain_config") load(":armeabi_cc_toolchain_config.bzl", "armeabi_cc_toolchain_config") +load("@rules_cc//cc:defs.bzl", "cc_toolchain", "cc_toolchain_suite") licenses(["notice"]) # Apache 2.0 @@ -37,7 +38,7 @@ filegroup( filegroup( name = "compiler_deps", - srcs = glob(["extra_tools/**"], allow_empty = True) + [":empty"], + srcs = glob(["extra_tools/**"], allow_empty = True) + [":builtin_include_directory_paths"], ) # This is the entry point for --crosstool_top. Toolchains are found @@ -111,12 +112,11 @@ cc_toolchain_config( "-fdata-sections"], dbg_compile_flags = ["-g"], cxx_flags = ["-std=c++0x"], - link_flags = ["-fuse-ld=gold", + link_flags = ["-fuse-ld=/usr/bin/ld.gold", "-Wl,-no-as-needed", "-Wl,-z,relro,-z,now", "-B/usr/lib/llvm-8/bin", "-lm", - "-static-libgcc", "-fuse-ld=lld"], link_libs = ["-l:libstdc++.a"], opt_link_flags = ["-Wl,--gc-sections"], diff --git a/bazel/toolchains/configs/clang/bazel_0.28.1/cc/armeabi_cc_toolchain_config.bzl b/bazel/toolchains/configs/clang/bazel_0.29.1/cc/armeabi_cc_toolchain_config.bzl similarity index 100% rename from bazel/toolchains/configs/clang/bazel_0.28.1/cc/armeabi_cc_toolchain_config.bzl rename to bazel/toolchains/configs/clang/bazel_0.29.1/cc/armeabi_cc_toolchain_config.bzl diff --git a/bazel/toolchains/configs/clang/bazel_0.29.1/cc/builtin_include_directory_paths b/bazel/toolchains/configs/clang/bazel_0.29.1/cc/builtin_include_directory_paths new file mode 100755 index 0000000000..151e3b008a --- /dev/null +++ b/bazel/toolchains/configs/clang/bazel_0.29.1/cc/builtin_include_directory_paths @@ -0,0 +1,14 @@ +This file is generated by cc_configure and contains builtin include directories +that /usr/lib/llvm-8/bin/clang reported. This file is a dependency of every compilation action and +changes to it will be reflected in the action cache key. When some of these +paths change, Bazel will make sure to rerun the action, even though none of +declared action inputs or the action commandline changes. + +/usr/local/include +/usr/lib/llvm-8/lib/clang/8.0.1/include +/usr/include/x86_64-linux-gnu +/usr/include +/usr/include/c++/7.4.0 +/usr/include/x86_64-linux-gnu/c++/7.4.0 +/usr/include/c++/7.4.0/backward +/usr/include/clang/8.0.1/include diff --git a/bazel/toolchains/configs/clang/bazel_0.28.1/cc/cc_toolchain_config.bzl b/bazel/toolchains/configs/clang/bazel_0.29.1/cc/cc_toolchain_config.bzl similarity index 88% rename from bazel/toolchains/configs/clang/bazel_0.28.1/cc/cc_toolchain_config.bzl rename to bazel/toolchains/configs/clang/bazel_0.29.1/cc/cc_toolchain_config.bzl index f2b12d9629..bf4d83940f 100755 --- a/bazel/toolchains/configs/clang/bazel_0.28.1/cc/cc_toolchain_config.bzl +++ b/bazel/toolchains/configs/clang/bazel_0.29.1/cc/cc_toolchain_config.bzl @@ -74,6 +74,12 @@ all_link_actions = [ ACTION_NAMES.cpp_link_nodeps_dynamic_library, ] +lto_index_actions = [ + ACTION_NAMES.lto_index_for_executable, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, +] + def _impl(ctx): tool_paths = [ tool_path(name = name, path = path) @@ -95,18 +101,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.compile_flags, @@ -114,18 +109,7 @@ def _impl(ctx): ] if ctx.attr.compile_flags else []), ), flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.dbg_compile_flags, @@ -134,18 +118,7 @@ def _impl(ctx): with_features = [with_feature_set(features = ["dbg"])], ), flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.opt_compile_flags, @@ -154,15 +127,7 @@ def _impl(ctx): with_features = [with_feature_set(features = ["opt"])], ), flag_set( - actions = [ - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_cpp_compile_actions + [ACTION_NAMES.lto_backend], flag_groups = ([ flag_group( flags = ctx.attr.cxx_flags, @@ -177,7 +142,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group( flags = ctx.attr.link_flags, @@ -185,7 +150,7 @@ def _impl(ctx): ] if ctx.attr.link_flags else []), ), flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group( flags = ctx.attr.opt_link_flags, @@ -215,10 +180,7 @@ def _impl(ctx): ACTION_NAMES.cpp_module_codegen, ACTION_NAMES.lto_backend, ACTION_NAMES.clif_match, - ACTION_NAMES.cpp_link_executable, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["--sysroot=%{sysroot}"], @@ -255,18 +217,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = [ flag_group( flags = ["%{user_compile_flags}"], @@ -283,18 +234,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.unfiltered_compile_flags, @@ -308,7 +248,7 @@ def _impl(ctx): name = "library_search_directories", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-L%{library_search_directories}"], @@ -328,6 +268,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_executable, ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.lto_index_for_executable, + ACTION_NAMES.lto_index_for_dynamic_library, ], flag_groups = [flag_group(flags = ["-static-libgcc"])], with_features = [ @@ -447,7 +389,7 @@ def _impl(ctx): name = "runtime_library_search_directories", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "runtime_library_search_directories", @@ -474,7 +416,7 @@ def _impl(ctx): ], ), flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "runtime_library_search_directories", @@ -502,7 +444,7 @@ def _impl(ctx): name = "fission_support", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-Wl,--gdb-index"], @@ -520,6 +462,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [flag_group(flags = ["-shared"])], ), @@ -581,10 +525,7 @@ def _impl(ctx): actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -607,10 +548,7 @@ def _impl(ctx): ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ACTION_NAMES.lto_backend, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -662,7 +600,7 @@ def _impl(ctx): name = "symbol_counts", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -697,10 +635,7 @@ def _impl(ctx): ], ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, + actions = all_link_actions + lto_index_actions + [ "objc-executable", "objc++-executable", ], @@ -717,7 +652,7 @@ def _impl(ctx): name = "strip_debug_symbols", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-Wl,-S"], @@ -735,6 +670,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [ flag_group( @@ -760,7 +697,7 @@ def _impl(ctx): name = "libraries_to_link", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "libraries_to_link", @@ -847,7 +784,7 @@ def _impl(ctx): name = "user_link_flags", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["%{user_link_flags}"], @@ -885,7 +822,7 @@ def _impl(ctx): name = "linkstamps", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["%{linkstamp_paths}"], @@ -919,11 +856,7 @@ def _impl(ctx): ], ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = [flag_group(flags = ["--coverage"])], ), ], @@ -977,7 +910,10 @@ def _impl(ctx): name = "force_pic_flags", flag_sets = [ flag_set( - actions = [ACTION_NAMES.cpp_link_executable], + actions = [ + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.lto_index_for_executable, + ], flag_groups = [ flag_group( flags = ["-pie"], @@ -1014,6 +950,7 @@ def _impl(ctx): ], ) + dynamic_library_linker_tool_path = tool_paths dynamic_library_linker_tool_feature = feature( name = "dynamic_library_linker_tool", flag_sets = [ @@ -1021,6 +958,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [ flag_group( @@ -1041,11 +980,7 @@ def _impl(ctx): name = "output_execpath_flags", flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-o", "%{output_execpath}"], @@ -1076,11 +1011,7 @@ def _impl(ctx): ] if ctx.attr.coverage_compile_flags else []), ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group(flags = ctx.attr.coverage_link_flags), ] if ctx.attr.coverage_link_flags else []), diff --git a/bazel/toolchains/configs/clang/bazel_0.28.1/cc/cc_wrapper.sh b/bazel/toolchains/configs/clang/bazel_0.29.1/cc/cc_wrapper.sh similarity index 100% rename from bazel/toolchains/configs/clang/bazel_0.28.1/cc/cc_wrapper.sh rename to bazel/toolchains/configs/clang/bazel_0.29.1/cc/cc_wrapper.sh diff --git a/bazel/toolchains/configs/clang/bazel_0.28.1/config/BUILD b/bazel/toolchains/configs/clang/bazel_0.29.1/config/BUILD similarity index 89% rename from bazel/toolchains/configs/clang/bazel_0.28.1/config/BUILD rename to bazel/toolchains/configs/clang/bazel_0.29.1/config/BUILD index 7af5e24e5d..5fb181617f 100644 --- a/bazel/toolchains/configs/clang/bazel_0.28.1/config/BUILD +++ b/bazel/toolchains/configs/clang/bazel_0.29.1/config/BUILD @@ -29,7 +29,7 @@ toolchain( "@bazel_tools//platforms:linux", "@bazel_tools//platforms:x86_64", ], - toolchain = "//bazel/toolchains/configs/clang/bazel_0.28.1/cc:cc-compiler-k8", + toolchain = "//bazel/toolchains/configs/clang/bazel_0.29.1/cc:cc-compiler-k8", toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", ) @@ -43,7 +43,7 @@ platform( remote_execution_properties = """ properties: { name: "container-image" - value:"docker://gcr.io/envoy-ci/envoy-build@sha256:d1f6087fdeb6a6e5d4fd52a5dc06b15f43f49e2c20fc813bcaaa12333485a70b" + value:"docker://gcr.io/envoy-ci/envoy-build@sha256:9236915d10004a35f2439ce4a1c33c1dbb06f95f84c4a4497d4e4f95cdc9e07f" } properties { name: "OSFamily" diff --git a/bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/BUILD b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/BUILD similarity index 95% rename from bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/BUILD rename to bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/BUILD index dae58f58c9..625db85820 100755 --- a/bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/BUILD +++ b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/BUILD @@ -18,6 +18,7 @@ package(default_visibility = ["//visibility:public"]) load(":cc_toolchain_config.bzl", "cc_toolchain_config") load(":armeabi_cc_toolchain_config.bzl", "armeabi_cc_toolchain_config") +load("@rules_cc//cc:defs.bzl", "cc_toolchain", "cc_toolchain_suite") licenses(["notice"]) # Apache 2.0 @@ -37,7 +38,7 @@ filegroup( filegroup( name = "compiler_deps", - srcs = glob(["extra_tools/**"], allow_empty = True) + [":empty"], + srcs = glob(["extra_tools/**"], allow_empty = True) + [":builtin_include_directory_paths"], ) # This is the entry point for --crosstool_top. Toolchains are found @@ -109,12 +110,11 @@ cc_toolchain_config( "-fdata-sections"], dbg_compile_flags = ["-g"], cxx_flags = ["-stdlib=libc++"], - link_flags = ["-fuse-ld=gold", + link_flags = ["-fuse-ld=/usr/bin/ld.gold", "-Wl,-no-as-needed", "-Wl,-z,relro,-z,now", "-B/usr/lib/llvm-8/bin", "-lm", - "-static-libgcc", "-pthread", "-fuse-ld=lld"], link_libs = ["-l:libc++.a", diff --git a/bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/armeabi_cc_toolchain_config.bzl b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/armeabi_cc_toolchain_config.bzl similarity index 100% rename from bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/armeabi_cc_toolchain_config.bzl rename to bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/armeabi_cc_toolchain_config.bzl diff --git a/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/builtin_include_directory_paths b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/builtin_include_directory_paths new file mode 100755 index 0000000000..d809f97268 --- /dev/null +++ b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/builtin_include_directory_paths @@ -0,0 +1,12 @@ +This file is generated by cc_configure and contains builtin include directories +that /usr/lib/llvm-8/bin/clang reported. This file is a dependency of every compilation action and +changes to it will be reflected in the action cache key. When some of these +paths change, Bazel will make sure to rerun the action, even though none of +declared action inputs or the action commandline changes. + +/usr/local/include +/usr/lib/llvm-8/lib/clang/8.0.1/include +/usr/include/x86_64-linux-gnu +/usr/include +/usr/lib/llvm-8/include/c++/v1 +/usr/include/clang/8.0.1/include diff --git a/bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/cc_toolchain_config.bzl b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/cc_toolchain_config.bzl similarity index 88% rename from bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/cc_toolchain_config.bzl rename to bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/cc_toolchain_config.bzl index f2b12d9629..bf4d83940f 100755 --- a/bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/cc_toolchain_config.bzl +++ b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/cc_toolchain_config.bzl @@ -74,6 +74,12 @@ all_link_actions = [ ACTION_NAMES.cpp_link_nodeps_dynamic_library, ] +lto_index_actions = [ + ACTION_NAMES.lto_index_for_executable, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, +] + def _impl(ctx): tool_paths = [ tool_path(name = name, path = path) @@ -95,18 +101,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.compile_flags, @@ -114,18 +109,7 @@ def _impl(ctx): ] if ctx.attr.compile_flags else []), ), flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.dbg_compile_flags, @@ -134,18 +118,7 @@ def _impl(ctx): with_features = [with_feature_set(features = ["dbg"])], ), flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.opt_compile_flags, @@ -154,15 +127,7 @@ def _impl(ctx): with_features = [with_feature_set(features = ["opt"])], ), flag_set( - actions = [ - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_cpp_compile_actions + [ACTION_NAMES.lto_backend], flag_groups = ([ flag_group( flags = ctx.attr.cxx_flags, @@ -177,7 +142,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group( flags = ctx.attr.link_flags, @@ -185,7 +150,7 @@ def _impl(ctx): ] if ctx.attr.link_flags else []), ), flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group( flags = ctx.attr.opt_link_flags, @@ -215,10 +180,7 @@ def _impl(ctx): ACTION_NAMES.cpp_module_codegen, ACTION_NAMES.lto_backend, ACTION_NAMES.clif_match, - ACTION_NAMES.cpp_link_executable, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["--sysroot=%{sysroot}"], @@ -255,18 +217,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = [ flag_group( flags = ["%{user_compile_flags}"], @@ -283,18 +234,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.unfiltered_compile_flags, @@ -308,7 +248,7 @@ def _impl(ctx): name = "library_search_directories", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-L%{library_search_directories}"], @@ -328,6 +268,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_executable, ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.lto_index_for_executable, + ACTION_NAMES.lto_index_for_dynamic_library, ], flag_groups = [flag_group(flags = ["-static-libgcc"])], with_features = [ @@ -447,7 +389,7 @@ def _impl(ctx): name = "runtime_library_search_directories", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "runtime_library_search_directories", @@ -474,7 +416,7 @@ def _impl(ctx): ], ), flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "runtime_library_search_directories", @@ -502,7 +444,7 @@ def _impl(ctx): name = "fission_support", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-Wl,--gdb-index"], @@ -520,6 +462,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [flag_group(flags = ["-shared"])], ), @@ -581,10 +525,7 @@ def _impl(ctx): actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -607,10 +548,7 @@ def _impl(ctx): ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ACTION_NAMES.lto_backend, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -662,7 +600,7 @@ def _impl(ctx): name = "symbol_counts", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -697,10 +635,7 @@ def _impl(ctx): ], ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, + actions = all_link_actions + lto_index_actions + [ "objc-executable", "objc++-executable", ], @@ -717,7 +652,7 @@ def _impl(ctx): name = "strip_debug_symbols", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-Wl,-S"], @@ -735,6 +670,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [ flag_group( @@ -760,7 +697,7 @@ def _impl(ctx): name = "libraries_to_link", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "libraries_to_link", @@ -847,7 +784,7 @@ def _impl(ctx): name = "user_link_flags", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["%{user_link_flags}"], @@ -885,7 +822,7 @@ def _impl(ctx): name = "linkstamps", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["%{linkstamp_paths}"], @@ -919,11 +856,7 @@ def _impl(ctx): ], ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = [flag_group(flags = ["--coverage"])], ), ], @@ -977,7 +910,10 @@ def _impl(ctx): name = "force_pic_flags", flag_sets = [ flag_set( - actions = [ACTION_NAMES.cpp_link_executable], + actions = [ + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.lto_index_for_executable, + ], flag_groups = [ flag_group( flags = ["-pie"], @@ -1014,6 +950,7 @@ def _impl(ctx): ], ) + dynamic_library_linker_tool_path = tool_paths dynamic_library_linker_tool_feature = feature( name = "dynamic_library_linker_tool", flag_sets = [ @@ -1021,6 +958,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [ flag_group( @@ -1041,11 +980,7 @@ def _impl(ctx): name = "output_execpath_flags", flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-o", "%{output_execpath}"], @@ -1076,11 +1011,7 @@ def _impl(ctx): ] if ctx.attr.coverage_compile_flags else []), ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group(flags = ctx.attr.coverage_link_flags), ] if ctx.attr.coverage_link_flags else []), diff --git a/bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/cc_wrapper.sh b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/cc_wrapper.sh similarity index 100% rename from bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc/cc_wrapper.sh rename to bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc/cc_wrapper.sh diff --git a/bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/config/BUILD b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/config/BUILD similarity index 91% rename from bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/config/BUILD rename to bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/config/BUILD index 385ea14041..3d87dd780c 100644 --- a/bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/config/BUILD +++ b/bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/config/BUILD @@ -29,7 +29,7 @@ toolchain( "@bazel_tools//platforms:linux", "@bazel_tools//platforms:x86_64", ], - toolchain = "//bazel/toolchains/configs/clang_libcxx/bazel_0.28.1/cc:cc-compiler-k8", + toolchain = "//bazel/toolchains/configs/clang_libcxx/bazel_0.29.1/cc:cc-compiler-k8", toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", ) @@ -43,7 +43,7 @@ platform( remote_execution_properties = """ properties: { name: "container-image" - value:"docker://gcr.io/envoy-ci/envoy-build@sha256:d1f6087fdeb6a6e5d4fd52a5dc06b15f43f49e2c20fc813bcaaa12333485a70b" + value:"docker://gcr.io/envoy-ci/envoy-build@sha256:9236915d10004a35f2439ce4a1c33c1dbb06f95f84c4a4497d4e4f95cdc9e07f" } properties { name: "OSFamily" diff --git a/bazel/toolchains/configs/gcc/bazel_0.28.1/cc/BUILD b/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/BUILD similarity index 96% rename from bazel/toolchains/configs/gcc/bazel_0.28.1/cc/BUILD rename to bazel/toolchains/configs/gcc/bazel_0.29.1/cc/BUILD index e936d4b915..ae7728d61b 100755 --- a/bazel/toolchains/configs/gcc/bazel_0.28.1/cc/BUILD +++ b/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/BUILD @@ -18,6 +18,7 @@ package(default_visibility = ["//visibility:public"]) load(":cc_toolchain_config.bzl", "cc_toolchain_config") load(":armeabi_cc_toolchain_config.bzl", "armeabi_cc_toolchain_config") +load("@rules_cc//cc:defs.bzl", "cc_toolchain", "cc_toolchain_suite") licenses(["notice"]) # Apache 2.0 @@ -37,7 +38,7 @@ filegroup( filegroup( name = "compiler_deps", - srcs = glob(["extra_tools/**"], allow_empty = True) + [":empty"], + srcs = glob(["extra_tools/**"], allow_empty = True) + [":builtin_include_directory_paths"], ) # This is the entry point for --crosstool_top. Toolchains are found @@ -115,8 +116,7 @@ cc_toolchain_config( "-Wl,-z,relro,-z,now", "-B/usr/bin", "-pass-exit-codes", - "-lm", - "-static-libgcc"], + "-lm"], link_libs = ["-l:libstdc++.a"], opt_link_flags = ["-Wl,--gc-sections"], unfiltered_compile_flags = ["-fno-canonical-system-headers", diff --git a/bazel/toolchains/configs/gcc/bazel_0.28.1/cc/armeabi_cc_toolchain_config.bzl b/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/armeabi_cc_toolchain_config.bzl similarity index 100% rename from bazel/toolchains/configs/gcc/bazel_0.28.1/cc/armeabi_cc_toolchain_config.bzl rename to bazel/toolchains/configs/gcc/bazel_0.29.1/cc/armeabi_cc_toolchain_config.bzl diff --git a/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/builtin_include_directory_paths b/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/builtin_include_directory_paths new file mode 100755 index 0000000000..30a600ae8b --- /dev/null +++ b/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/builtin_include_directory_paths @@ -0,0 +1,14 @@ +This file is generated by cc_configure and contains builtin include directories +that /usr/bin/gcc reported. This file is a dependency of every compilation action and +changes to it will be reflected in the action cache key. When some of these +paths change, Bazel will make sure to rerun the action, even though none of +declared action inputs or the action commandline changes. + +/usr/lib/gcc/x86_64-linux-gnu/7/include +/usr/local/include +/usr/lib/gcc/x86_64-linux-gnu/7/include-fixed +/usr/include/x86_64-linux-gnu +/usr/include +/usr/include/c++/7 +/usr/include/x86_64-linux-gnu/c++/7 +/usr/include/c++/7/backward diff --git a/bazel/toolchains/configs/gcc/bazel_0.28.1/cc/cc_toolchain_config.bzl b/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/cc_toolchain_config.bzl similarity index 88% rename from bazel/toolchains/configs/gcc/bazel_0.28.1/cc/cc_toolchain_config.bzl rename to bazel/toolchains/configs/gcc/bazel_0.29.1/cc/cc_toolchain_config.bzl index f2b12d9629..bf4d83940f 100755 --- a/bazel/toolchains/configs/gcc/bazel_0.28.1/cc/cc_toolchain_config.bzl +++ b/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/cc_toolchain_config.bzl @@ -74,6 +74,12 @@ all_link_actions = [ ACTION_NAMES.cpp_link_nodeps_dynamic_library, ] +lto_index_actions = [ + ACTION_NAMES.lto_index_for_executable, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, +] + def _impl(ctx): tool_paths = [ tool_path(name = name, path = path) @@ -95,18 +101,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.compile_flags, @@ -114,18 +109,7 @@ def _impl(ctx): ] if ctx.attr.compile_flags else []), ), flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.dbg_compile_flags, @@ -134,18 +118,7 @@ def _impl(ctx): with_features = [with_feature_set(features = ["dbg"])], ), flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.opt_compile_flags, @@ -154,15 +127,7 @@ def _impl(ctx): with_features = [with_feature_set(features = ["opt"])], ), flag_set( - actions = [ - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_cpp_compile_actions + [ACTION_NAMES.lto_backend], flag_groups = ([ flag_group( flags = ctx.attr.cxx_flags, @@ -177,7 +142,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group( flags = ctx.attr.link_flags, @@ -185,7 +150,7 @@ def _impl(ctx): ] if ctx.attr.link_flags else []), ), flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group( flags = ctx.attr.opt_link_flags, @@ -215,10 +180,7 @@ def _impl(ctx): ACTION_NAMES.cpp_module_codegen, ACTION_NAMES.lto_backend, ACTION_NAMES.clif_match, - ACTION_NAMES.cpp_link_executable, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["--sysroot=%{sysroot}"], @@ -255,18 +217,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = [ flag_group( flags = ["%{user_compile_flags}"], @@ -283,18 +234,7 @@ def _impl(ctx): enabled = True, flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.c_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, - ], + actions = all_compile_actions, flag_groups = ([ flag_group( flags = ctx.attr.unfiltered_compile_flags, @@ -308,7 +248,7 @@ def _impl(ctx): name = "library_search_directories", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-L%{library_search_directories}"], @@ -328,6 +268,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_executable, ACTION_NAMES.cpp_link_dynamic_library, + ACTION_NAMES.lto_index_for_executable, + ACTION_NAMES.lto_index_for_dynamic_library, ], flag_groups = [flag_group(flags = ["-static-libgcc"])], with_features = [ @@ -447,7 +389,7 @@ def _impl(ctx): name = "runtime_library_search_directories", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "runtime_library_search_directories", @@ -474,7 +416,7 @@ def _impl(ctx): ], ), flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "runtime_library_search_directories", @@ -502,7 +444,7 @@ def _impl(ctx): name = "fission_support", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-Wl,--gdb-index"], @@ -520,6 +462,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [flag_group(flags = ["-shared"])], ), @@ -581,10 +525,7 @@ def _impl(ctx): actions = [ ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -607,10 +548,7 @@ def _impl(ctx): ACTION_NAMES.c_compile, ACTION_NAMES.cpp_compile, ACTION_NAMES.lto_backend, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + ] + all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -662,7 +600,7 @@ def _impl(ctx): name = "symbol_counts", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = [ @@ -697,10 +635,7 @@ def _impl(ctx): ], ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, + actions = all_link_actions + lto_index_actions + [ "objc-executable", "objc++-executable", ], @@ -717,7 +652,7 @@ def _impl(ctx): name = "strip_debug_symbols", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-Wl,-S"], @@ -735,6 +670,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [ flag_group( @@ -760,7 +697,7 @@ def _impl(ctx): name = "libraries_to_link", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( iterate_over = "libraries_to_link", @@ -847,7 +784,7 @@ def _impl(ctx): name = "user_link_flags", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["%{user_link_flags}"], @@ -885,7 +822,7 @@ def _impl(ctx): name = "linkstamps", flag_sets = [ flag_set( - actions = all_link_actions, + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["%{linkstamp_paths}"], @@ -919,11 +856,7 @@ def _impl(ctx): ], ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = [flag_group(flags = ["--coverage"])], ), ], @@ -977,7 +910,10 @@ def _impl(ctx): name = "force_pic_flags", flag_sets = [ flag_set( - actions = [ACTION_NAMES.cpp_link_executable], + actions = [ + ACTION_NAMES.cpp_link_executable, + ACTION_NAMES.lto_index_for_executable, + ], flag_groups = [ flag_group( flags = ["-pie"], @@ -1014,6 +950,7 @@ def _impl(ctx): ], ) + dynamic_library_linker_tool_path = tool_paths dynamic_library_linker_tool_feature = feature( name = "dynamic_library_linker_tool", flag_sets = [ @@ -1021,6 +958,8 @@ def _impl(ctx): actions = [ ACTION_NAMES.cpp_link_dynamic_library, ACTION_NAMES.cpp_link_nodeps_dynamic_library, + ACTION_NAMES.lto_index_for_dynamic_library, + ACTION_NAMES.lto_index_for_nodeps_dynamic_library, ], flag_groups = [ flag_group( @@ -1041,11 +980,7 @@ def _impl(ctx): name = "output_execpath_flags", flag_sets = [ flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = [ flag_group( flags = ["-o", "%{output_execpath}"], @@ -1076,11 +1011,7 @@ def _impl(ctx): ] if ctx.attr.coverage_compile_flags else []), ), flag_set( - actions = [ - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, - ACTION_NAMES.cpp_link_executable, - ], + actions = all_link_actions + lto_index_actions, flag_groups = ([ flag_group(flags = ctx.attr.coverage_link_flags), ] if ctx.attr.coverage_link_flags else []), diff --git a/bazel/toolchains/configs/gcc/bazel_0.28.1/cc/cc_wrapper.sh b/bazel/toolchains/configs/gcc/bazel_0.29.1/cc/cc_wrapper.sh similarity index 100% rename from bazel/toolchains/configs/gcc/bazel_0.28.1/cc/cc_wrapper.sh rename to bazel/toolchains/configs/gcc/bazel_0.29.1/cc/cc_wrapper.sh diff --git a/bazel/toolchains/configs/gcc/bazel_0.28.1/config/BUILD b/bazel/toolchains/configs/gcc/bazel_0.29.1/config/BUILD similarity index 89% rename from bazel/toolchains/configs/gcc/bazel_0.28.1/config/BUILD rename to bazel/toolchains/configs/gcc/bazel_0.29.1/config/BUILD index 4584bd320c..6de35eeeaf 100644 --- a/bazel/toolchains/configs/gcc/bazel_0.28.1/config/BUILD +++ b/bazel/toolchains/configs/gcc/bazel_0.29.1/config/BUILD @@ -29,7 +29,7 @@ toolchain( "@bazel_tools//platforms:linux", "@bazel_tools//platforms:x86_64", ], - toolchain = "//bazel/toolchains/configs/gcc/bazel_0.28.1/cc:cc-compiler-k8", + toolchain = "//bazel/toolchains/configs/gcc/bazel_0.29.1/cc:cc-compiler-k8", toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", ) @@ -43,7 +43,7 @@ platform( remote_execution_properties = """ properties: { name: "container-image" - value:"docker://gcr.io/envoy-ci/envoy-build@sha256:d1f6087fdeb6a6e5d4fd52a5dc06b15f43f49e2c20fc813bcaaa12333485a70b" + value:"docker://gcr.io/envoy-ci/envoy-build@sha256:9236915d10004a35f2439ce4a1c33c1dbb06f95f84c4a4497d4e4f95cdc9e07f" } properties { name: "OSFamily" diff --git a/bazel/toolchains/configs/versions.bzl b/bazel/toolchains/configs/versions.bzl index b7fee4d503..bdf2a5c264 100644 --- a/bazel/toolchains/configs/versions.bzl +++ b/bazel/toolchains/configs/versions.bzl @@ -1,13 +1,13 @@ # Generated file, do not modify by hand # Generated by 'rbe_ubuntu_gcc_gen' rbe_autoconfig rule """Definitions to be used in rbe_repo attr of an rbe_autoconf rule """ -toolchain_config_spec0 = struct(config_repos = [], create_cc_configs = True, create_java_configs = False, env = {"BAZEL_COMPILER": "clang", "BAZEL_LINKLIBS": "-l%:libstdc++.a", "BAZEL_LINKOPTS": "-lm:-static-libgcc:-fuse-ld=lld", "BAZEL_USE_LLVM_NATIVE_COVERAGE": "1", "GCOV": "llvm-profdata", "CC": "clang", "CXX": "clang++", "PATH": "/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/llvm-8/bin"}, java_home = None, name = "clang") -toolchain_config_spec1 = struct(config_repos = [], create_cc_configs = True, create_java_configs = False, env = {"BAZEL_COMPILER": "clang", "BAZEL_LINKLIBS": "-l%:libc++.a:-l%:libc++abi.a", "BAZEL_LINKOPTS": "-lm:-static-libgcc:-pthread:-fuse-ld=lld", "BAZEL_USE_LLVM_NATIVE_COVERAGE": "1", "GCOV": "llvm-profdata", "CC": "clang", "CXX": "clang++", "PATH": "/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/llvm-8/bin", "BAZEL_CXXOPTS": "-stdlib=libc++", "CXXFLAGS": "-stdlib=libc++"}, java_home = None, name = "clang_libcxx") -toolchain_config_spec2 = struct(config_repos = [], create_cc_configs = True, create_java_configs = False, env = {"BAZEL_COMPILER": "gcc", "BAZEL_LINKLIBS": "-l%:libstdc++.a", "BAZEL_LINKOPTS": "-lm:-static-libgcc", "CC": "gcc", "CXX": "g++", "PATH": "/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/llvm-8/bin"}, java_home = None, name = "gcc") +toolchain_config_spec0 = struct(config_repos = [], create_cc_configs = True, create_java_configs = False, env = {"BAZEL_COMPILER": "clang", "BAZEL_LINKLIBS": "-l%:libstdc++.a", "BAZEL_LINKOPTS": "-lm:-fuse-ld=lld", "BAZEL_USE_LLVM_NATIVE_COVERAGE": "1", "GCOV": "llvm-profdata", "CC": "clang", "CXX": "clang++", "PATH": "/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/llvm-8/bin"}, java_home = None, name = "clang") +toolchain_config_spec1 = struct(config_repos = [], create_cc_configs = True, create_java_configs = False, env = {"BAZEL_COMPILER": "clang", "BAZEL_LINKLIBS": "-l%:libc++.a:-l%:libc++abi.a", "BAZEL_LINKOPTS": "-lm:-pthread:-fuse-ld=lld", "BAZEL_USE_LLVM_NATIVE_COVERAGE": "1", "GCOV": "llvm-profdata", "CC": "clang", "CXX": "clang++", "PATH": "/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/llvm-8/bin", "BAZEL_CXXOPTS": "-stdlib=libc++", "CXXFLAGS": "-stdlib=libc++"}, java_home = None, name = "clang_libcxx") +toolchain_config_spec2 = struct(config_repos = [], create_cc_configs = True, create_java_configs = False, env = {"BAZEL_COMPILER": "gcc", "BAZEL_LINKLIBS": "-l%:libstdc++.a", "BAZEL_LINKOPTS": "-lm", "CC": "gcc", "CXX": "g++", "PATH": "/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/llvm-8/bin"}, java_home = None, name = "gcc") _TOOLCHAIN_CONFIG_SPECS = [toolchain_config_spec0,toolchain_config_spec1,toolchain_config_spec2] -_BAZEL_TO_CONFIG_SPEC_NAMES = {"0.28.1": ["clang", "clang_libcxx", "gcc"]} -LATEST = "sha256:d1f6087fdeb6a6e5d4fd52a5dc06b15f43f49e2c20fc813bcaaa12333485a70b" -CONTAINER_TO_CONFIG_SPEC_NAMES = {"sha256:d1f6087fdeb6a6e5d4fd52a5dc06b15f43f49e2c20fc813bcaaa12333485a70b": ["clang", "clang_libcxx", "gcc"]} +_BAZEL_TO_CONFIG_SPEC_NAMES = {"0.29.1": ["clang", "clang_libcxx", "gcc"]} +LATEST = "sha256:9236915d10004a35f2439ce4a1c33c1dbb06f95f84c4a4497d4e4f95cdc9e07f" +CONTAINER_TO_CONFIG_SPEC_NAMES = {"sha256:9236915d10004a35f2439ce4a1c33c1dbb06f95f84c4a4497d4e4f95cdc9e07f": ["clang", "clang_libcxx", "gcc"]} _DEFAULT_TOOLCHAIN_CONFIG_SPEC = toolchain_config_spec0 TOOLCHAIN_CONFIG_AUTOGEN_SPEC = struct( bazel_to_config_spec_names_map = _BAZEL_TO_CONFIG_SPEC_NAMES, diff --git a/bazel/toolchains/rbe_toolchains_config.bzl b/bazel/toolchains/rbe_toolchains_config.bzl index fd7210db1f..8820710297 100644 --- a/bazel/toolchains/rbe_toolchains_config.bzl +++ b/bazel/toolchains/rbe_toolchains_config.bzl @@ -4,13 +4,13 @@ load("@envoy//bazel/toolchains:configs/versions.bzl", _generated_toolchain_confi _ENVOY_BUILD_IMAGE_REGISTRY = "gcr.io" _ENVOY_BUILD_IMAGE_REPOSITORY = "envoy-ci/envoy-build" -_ENVOY_BUILD_IMAGE_DIGEST = "sha256:d1f6087fdeb6a6e5d4fd52a5dc06b15f43f49e2c20fc813bcaaa12333485a70b" +_ENVOY_BUILD_IMAGE_DIGEST = "sha256:9236915d10004a35f2439ce4a1c33c1dbb06f95f84c4a4497d4e4f95cdc9e07f" _CONFIGS_OUTPUT_BASE = "bazel/toolchains/configs" _CLANG_ENV = { "BAZEL_COMPILER": "clang", "BAZEL_LINKLIBS": "-l%:libstdc++.a", - "BAZEL_LINKOPTS": "-lm:-static-libgcc:-fuse-ld=lld", + "BAZEL_LINKOPTS": "-lm:-fuse-ld=lld", "BAZEL_USE_LLVM_NATIVE_COVERAGE": "1", "GCOV": "llvm-profdata", "CC": "clang", @@ -20,7 +20,7 @@ _CLANG_ENV = { _CLANG_LIBCXX_ENV = dicts.add(_CLANG_ENV, { "BAZEL_LINKLIBS": "-l%:libc++.a:-l%:libc++abi.a", - "BAZEL_LINKOPTS": "-lm:-static-libgcc:-pthread:-fuse-ld=lld", + "BAZEL_LINKOPTS": "-lm:-pthread:-fuse-ld=lld", "BAZEL_CXXOPTS": "-stdlib=libc++", "CXXFLAGS": "-stdlib=libc++", }) @@ -28,7 +28,7 @@ _CLANG_LIBCXX_ENV = dicts.add(_CLANG_ENV, { _GCC_ENV = { "BAZEL_COMPILER": "gcc", "BAZEL_LINKLIBS": "-l%:libstdc++.a", - "BAZEL_LINKOPTS": "-lm:-static-libgcc", + "BAZEL_LINKOPTS": "-lm", "CC": "gcc", "CXX": "g++", "PATH": "/usr/sbin:/usr/bin:/sbin:/bin:/usr/lib/llvm-8/bin", diff --git a/ci/README.md b/ci/README.md index 1f3c4301fb..72d1600fac 100644 --- a/ci/README.md +++ b/ci/README.md @@ -98,6 +98,8 @@ The `./ci/run_envoy_docker.sh './ci/do_ci.sh '` targets are: * `bazel.coverity` — build Envoy static binary and run Coverity Scan static analysis. * `bazel.tsan` — build and run tests under `-c dbg --config=clang-tsan` with clang. * `bazel.tsan ` — build and run a specified test or test dir under `-c dbg --config=clang-tsan` with clang. +* `bazel.fuzz` — build and run fuzz tests under `-c dbg --config=asan-fuzzer` with clang. +* `bazel.fuzz ` — build and run a specified fuzz test or test dir under `-c dbg --config=asan-fuzzer` with clang. If specifying a single fuzz test, must use the full target name with "_with_libfuzzer" for ``. * `bazel.compile_time_options` — build Envoy and run tests with various compile-time options toggled to their non-default state, to ensure they still build. * `bazel.compile_time_options ` — build Envoy and run a specified test or test dir with various compile-time options toggled to their non-default state, to ensure they still build. * `bazel.clang_tidy` — build and run clang-tidy over all source files. @@ -131,7 +133,8 @@ The macOS CI build is part of the [CircleCI](https://circleci.com/gh/envoyproxy/ Dependencies are installed by the `ci/mac_ci_setup.sh` script, via [Homebrew](https://brew.sh), which is pre-installed on the CircleCI macOS image. The dependencies are cached are re-installed on every build. The `ci/mac_ci_steps.sh` script executes the specific commands that -build and test Envoy. +build and test Envoy. If Envoy cannot be built (`error: /Library/Developer/CommandLineTools/usr/bin/libtool: no output file specified (specify with -o output)`), +ensure that XCode is installed. # Coverity Scan Build Flow diff --git a/ci/build_setup.sh b/ci/build_setup.sh index 53379d9ab8..9dc5366882 100755 --- a/ci/build_setup.sh +++ b/ci/build_setup.sh @@ -95,6 +95,7 @@ if [ "$1" != "-nofetch" ]; then # This is the hash on https://github.com/envoyproxy/envoy-filter-example.git we pin to. (cd "${ENVOY_FILTER_EXAMPLE_SRCDIR}" && git fetch origin && git checkout -f 1995c1e0eccea84bbb39f64e75ef3e9102d1ae82) sed -e "s|{ENVOY_SRCDIR}|${ENVOY_SRCDIR}|" "${ENVOY_SRCDIR}"/ci/WORKSPACE.filter.example > "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/WORKSPACE + cp -f "${ENVOY_SRCDIR}"/.bazelversion "${ENVOY_FILTER_EXAMPLE_SRCDIR}"/.bazelversion fi # Also setup some space for building Envoy standalone. diff --git a/ci/do_ci.sh b/ci/do_ci.sh index d526c87d17..f94d96fc8c 100755 --- a/ci/do_ci.sh +++ b/ci/do_ci.sh @@ -197,6 +197,7 @@ elif [[ "$CI_TARGET" == "bazel.compile_time_options" ]]; then --define log_debug_assert_in_release=enabled \ --define quiche=enabled \ --define path_normalization_by_default=true \ + --define deprecated_features=disabled \ " setup_clang_libcxx_toolchain # This doesn't go into CI but is available for developer convenience. @@ -252,6 +253,13 @@ elif [[ "$CI_TARGET" == "bazel.coverity" ]]; then "${ENVOY_BUILD_DIR}"/envoy-coverity-output.tgz \ "${ENVOY_DELIVERY_DIR}"/envoy-coverity-output.tgz exit 0 +elif [[ "$CI_TARGET" == "bazel.fuzz" ]]; then + setup_clang_toolchain + FUZZ_TEST_TARGETS="$(bazel query "attr('tags','fuzzer',${TEST_TARGETS})")" + echo "bazel ASAN libFuzzer build with fuzz tests ${FUZZ_TEST_TARGETS}" + echo "Building envoy fuzzers and executing 100 fuzz iterations..." + bazel_with_collection test ${BAZEL_BUILD_OPTIONS} --config=asan-fuzzer ${FUZZ_TEST_TARGETS} --test_arg="-runs=10" + exit 0 elif [[ "$CI_TARGET" == "fix_format" ]]; then echo "fix_format..." ./tools/check_format.py fix diff --git a/ci/envoy_build_sha.sh b/ci/envoy_build_sha.sh index 709617afe9..bdc6fefe40 100644 --- a/ci/envoy_build_sha.sh +++ b/ci/envoy_build_sha.sh @@ -1,2 +1,2 @@ -ENVOY_BUILD_SHA=$(grep envoyproxy/envoy-build .circleci/config.yml | sed -e 's#.*envoyproxy/envoy-build:\(.*\)#\1#' | uniq) +ENVOY_BUILD_SHA=$(grep envoyproxy/envoy-build $(dirname $0)/../.circleci/config.yml | sed -e 's#.*envoyproxy/envoy-build:\(.*\)#\1#' | uniq) [[ $(wc -l <<< "${ENVOY_BUILD_SHA}" | awk '{$1=$1};1') == 1 ]] || (echo ".circleci/config.yml hashes are inconsistent!" && exit 1) diff --git a/ci/run_envoy_docker.sh b/ci/run_envoy_docker.sh index a93802b261..ede21c5de8 100755 --- a/ci/run_envoy_docker.sh +++ b/ci/run_envoy_docker.sh @@ -2,7 +2,7 @@ set -e -. ci/envoy_build_sha.sh +. $(dirname $0)/envoy_build_sha.sh # We run as root and later drop permissions. This is required to setup the USER # in useradd below, which is need for correct Python execution in the Docker diff --git a/docs/build.sh b/docs/build.sh index 6cb75c700e..a2c64b123f 100755 --- a/docs/build.sh +++ b/docs/build.sh @@ -44,133 +44,28 @@ rm -rf "${GENERATED_RST_DIR}" mkdir -p "${GENERATED_RST_DIR}" source_venv "$BUILD_DIR" -pip install -r "${SCRIPT_DIR}"/requirements.txt +pip3 install -r "${SCRIPT_DIR}"/requirements.txt bazel build ${BAZEL_BUILD_OPTIONS} @envoy_api//docs:protos --aspects \ - tools/protodoc/protodoc.bzl%proto_doc_aspect --output_groups=rst --action_env=CPROFILE_ENABLED \ - --action_env=ENVOY_BLOB_SHA --spawn_strategy=standalone --host_force_python=PY2 - -# These are the protos we want to put in docs, this list will grow. -# TODO(htuch): Factor this out of this script. -PROTO_RST=" - /envoy/admin/v2alpha/certs/envoy/admin/v2alpha/certs.proto.rst - /envoy/admin/v2alpha/clusters/envoy/admin/v2alpha/clusters.proto.rst - /envoy/admin/v2alpha/config_dump/envoy/admin/v2alpha/config_dump.proto.rst - /envoy/admin/v2alpha/listeners/envoy/admin/v2alpha/listeners.proto.rst - /envoy/admin/v2alpha/memory/envoy/admin/v2alpha/memory.proto.rst - /envoy/admin/v2alpha/clusters/envoy/admin/v2alpha/metrics.proto.rst - /envoy/admin/v2alpha/mutex_stats/envoy/admin/v2alpha/mutex_stats.proto.rst - /envoy/admin/v2alpha/server_info/envoy/admin/v2alpha/server_info.proto.rst - /envoy/admin/v2alpha/tap/envoy/admin/v2alpha/tap.proto.rst - /envoy/api/v2/core/address/envoy/api/v2/core/address.proto.rst - /envoy/api/v2/core/base/envoy/api/v2/core/base.proto.rst - /envoy/api/v2/core/http_uri/envoy/api/v2/core/http_uri.proto.rst - /envoy/api/v2/core/config_source/envoy/api/v2/core/config_source.proto.rst - /envoy/api/v2/core/grpc_service/envoy/api/v2/core/grpc_service.proto.rst - /envoy/api/v2/core/health_check/envoy/api/v2/core/health_check.proto.rst - /envoy/api/v2/core/protocol/envoy/api/v2/core/protocol.proto.rst - /envoy/api/v2/discovery/envoy/api/v2/discovery.proto.rst - /envoy/api/v2/auth/cert/envoy/api/v2/auth/cert.proto.rst - /envoy/api/v2/eds/envoy/api/v2/eds.proto.rst - /envoy/api/v2/endpoint/endpoint/envoy/api/v2/endpoint/endpoint.proto.rst - /envoy/api/v2/cds/envoy/api/v2/cds.proto.rst - /envoy/api/v2/cluster/outlier_detection/envoy/api/v2/cluster/outlier_detection.proto.rst - /envoy/api/v2/cluster/circuit_breaker/envoy/api/v2/cluster/circuit_breaker.proto.rst - /envoy/api/v2/cluster/filter/envoy/api/v2/cluster/filter.proto.rst - /envoy/api/v2/rds/envoy/api/v2/rds.proto.rst - /envoy/api/v2/route/route/envoy/api/v2/route/route.proto.rst - /envoy/api/v2/srds/envoy/api/v2/srds.proto.rst - /envoy/api/v2/lds/envoy/api/v2/lds.proto.rst - /envoy/api/v2/listener/listener/envoy/api/v2/listener/listener.proto.rst - /envoy/api/v2/ratelimit/ratelimit/envoy/api/v2/ratelimit/ratelimit.proto.rst - /envoy/config/accesslog/v2/als/envoy/config/accesslog/v2/als.proto.rst - /envoy/config/accesslog/v2/file/envoy/config/accesslog/v2/file.proto.rst - /envoy/config/bootstrap/v2/bootstrap/envoy/config/bootstrap/v2/bootstrap.proto.rst - /envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster/envoy/config/cluster/dynamic_forward_proxy/v2alpha/cluster.proto.rst - /envoy/config/cluster/redis/redis_cluster/envoy/config/cluster/redis/redis_cluster.proto.rst - /envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache/envoy/config/common/dynamic_forward_proxy/v2alpha/dns_cache.proto.rst - /envoy/config/common/tap/v2alpha/common/envoy/config/common/tap/v2alpha/common.proto.rst - /envoy/config/ratelimit/v2/rls/envoy/config/ratelimit/v2/rls.proto.rst - /envoy/config/metrics/v2/metrics_service/envoy/config/metrics/v2/metrics_service.proto.rst - /envoy/config/metrics/v2/stats/envoy/config/metrics/v2/stats.proto.rst - /envoy/config/trace/v2/trace/envoy/config/trace/v2/trace.proto.rst - /envoy/config/filter/accesslog/v2/accesslog/envoy/config/filter/accesslog/v2/accesslog.proto.rst - /envoy/config/filter/fault/v2/fault/envoy/config/filter/fault/v2/fault.proto.rst - /envoy/config/filter/http/buffer/v2/buffer/envoy/config/filter/http/buffer/v2/buffer.proto.rst - /envoy/config/filter/http/csrf/v2/csrf/envoy/config/filter/http/csrf/v2/csrf.proto.rst - /envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy/envoy/config/filter/http/dynamic_forward_proxy/v2alpha/dynamic_forward_proxy.proto.rst - /envoy/config/filter/http/ext_authz/v2/ext_authz/envoy/config/filter/http/ext_authz/v2/ext_authz.proto.rst - /envoy/config/filter/http/fault/v2/fault/envoy/config/filter/http/fault/v2/fault.proto.rst - /envoy/config/filter/http/gzip/v2/gzip/envoy/config/filter/http/gzip/v2/gzip.proto.rst - /envoy/config/filter/http/health_check/v2/health_check/envoy/config/filter/http/health_check/v2/health_check.proto.rst - /envoy/config/filter/http/header_to_metadata/v2/header_to_metadata/envoy/config/filter/http/header_to_metadata/v2/header_to_metadata.proto.rst - /envoy/config/filter/http/ip_tagging/v2/ip_tagging/envoy/config/filter/http/ip_tagging/v2/ip_tagging.proto.rst - /envoy/config/filter/http/jwt_authn/v2alpha/jwt_authn/envoy/config/filter/http/jwt_authn/v2alpha/config.proto.rst - /envoy/config/filter/http/lua/v2/lua/envoy/config/filter/http/lua/v2/lua.proto.rst - /envoy/config/filter/http/original_src/v2alpha1/original_src/envoy/config/filter/http/original_src/v2alpha1/original_src.proto.rst - /envoy/config/filter/http/rate_limit/v2/rate_limit/envoy/config/filter/http/rate_limit/v2/rate_limit.proto.rst - /envoy/config/filter/http/rbac/v2/rbac/envoy/config/filter/http/rbac/v2/rbac.proto.rst - /envoy/config/filter/http/router/v2/router/envoy/config/filter/http/router/v2/router.proto.rst - /envoy/config/filter/http/squash/v2/squash/envoy/config/filter/http/squash/v2/squash.proto.rst - /envoy/config/filter/http/tap/v2alpha/tap/envoy/config/filter/http/tap/v2alpha/tap.proto.rst - /envoy/config/filter/http/transcoder/v2/transcoder/envoy/config/filter/http/transcoder/v2/transcoder.proto.rst - /envoy/config/filter/listener/original_src/v2alpha1/original_src/envoy/config/filter/listener/original_src/v2alpha1/original_src.proto.rst - /envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy/envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy.proto.rst - /envoy/config/filter/network/dubbo_proxy/v2alpha1/dubbo_proxy/envoy/config/filter/network/dubbo_proxy/v2alpha1/route.proto.rst - /envoy/config/filter/dubbo/router/v2alpha1/router/envoy/config/filter/dubbo/router/v2alpha1/router.proto.rst - /envoy/config/filter/network/client_ssl_auth/v2/client_ssl_auth/envoy/config/filter/network/client_ssl_auth/v2/client_ssl_auth.proto.rst - /envoy/config/filter/network/ext_authz/v2/ext_authz/envoy/config/filter/network/ext_authz/v2/ext_authz.proto.rst - /envoy/config/filter/network/http_connection_manager/v2/http_connection_manager/envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.proto.rst - /envoy/config/filter/network/mongo_proxy/v2/mongo_proxy/envoy/config/filter/network/mongo_proxy/v2/mongo_proxy.proto.rst - /envoy/config/filter/network/rate_limit/v2/rate_limit/envoy/config/filter/network/rate_limit/v2/rate_limit.proto.rst - /envoy/config/filter/network/rbac/v2/rbac/envoy/config/filter/network/rbac/v2/rbac.proto.rst - /envoy/config/filter/network/redis_proxy/v2/redis_proxy/envoy/config/filter/network/redis_proxy/v2/redis_proxy.proto.rst - /envoy/config/filter/network/tcp_proxy/v2/tcp_proxy/envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.proto.rst - /envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy/envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy.proto.rst - /envoy/config/filter/network/thrift_proxy/v2alpha1/thrift_proxy/envoy/config/filter/network/thrift_proxy/v2alpha1/route.proto.rst - /envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit/envoy/config/filter/thrift/rate_limit/v2alpha1/rate_limit.proto.rst - /envoy/config/filter/thrift/router/v2alpha1/router/envoy/config/filter/thrift/router/v2alpha1/router.proto.rst - /envoy/config/grpc_credential/v2alpha/aws_iam/envoy/config/grpc_credential/v2alpha/aws_iam.proto.rst - /envoy/config/health_checker/redis/v2/redis/envoy/config/health_checker/redis/v2/redis.proto.rst - /envoy/config/overload/v2alpha/overload/envoy/config/overload/v2alpha/overload.proto.rst - /envoy/config/rbac/v2/rbac/envoy/config/rbac/v2/rbac.proto.rst - /envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap/envoy/config/resource_monitor/fixed_heap/v2alpha/fixed_heap.proto.rst - /envoy/config/resource_monitor/injected_resource/v2alpha/injected_resource/envoy/config/resource_monitor/injected_resource/v2alpha/injected_resource.proto.rst - /envoy/config/transport_socket/tap/v2alpha/tap/envoy/config/transport_socket/tap/v2alpha/tap.proto.rst - /envoy/data/accesslog/v2/accesslog/envoy/data/accesslog/v2/accesslog.proto.rst - /envoy/data/core/v2alpha/health_check_event/envoy/data/core/v2alpha/health_check_event.proto.rst - /envoy/data/tap/v2alpha/common/envoy/data/tap/v2alpha/common.proto.rst - /envoy/data/tap/v2alpha/transport/envoy/data/tap/v2alpha/transport.proto.rst - /envoy/data/tap/v2alpha/http/envoy/data/tap/v2alpha/http.proto.rst - /envoy/data/tap/v2alpha/wrapper/envoy/data/tap/v2alpha/wrapper.proto.rst - /envoy/data/cluster/v2alpha/outlier_detection_event/envoy/data/cluster/v2alpha/outlier_detection_event.proto.rst - /envoy/service/accesslog/v2/als/envoy/service/accesslog/v2/als.proto.rst - /envoy/service/auth/v2/external_auth/envoy/service/auth/v2/attribute_context.proto.rst - /envoy/service/auth/v2/external_auth/envoy/service/auth/v2/external_auth.proto.rst - /envoy/service/discovery/v2/rtds/envoy/service/discovery/v2/rtds.proto.rst - /envoy/service/ratelimit/v2/rls/envoy/service/ratelimit/v2/rls.proto.rst - /envoy/service/tap/v2alpha/common/envoy/service/tap/v2alpha/common.proto.rst - /envoy/type/http_status/envoy/type/http_status.proto.rst - /envoy/type/percent/envoy/type/percent.proto.rst - /envoy/type/range/envoy/type/range.proto.rst - /envoy/type/matcher/metadata/envoy/type/matcher/metadata.proto.rst - /envoy/type/matcher/value/envoy/type/matcher/value.proto.rst - /envoy/type/matcher/number/envoy/type/matcher/number.proto.rst - /envoy/type/matcher/string/envoy/type/matcher/string.proto.rst - /envoy/config/filter/http/wasm/v2/wasm/envoy/config/filter/http/wasm/v2/wasm.proto.rst - /envoy/config/wasm/v2/wasm/envoy/config/wasm/v2/wasm.proto.rst -" - -# Dump all the generated RST so they can be added to PROTO_RST easily. -find -L bazel-bin/external/envoy_api -name "*.proto.rst" + tools/protodoc/protodoc.bzl%proto_doc_aspect --output_groups=rst --action_env=CPROFILE_ENABLED=1 \ + --action_env=ENVOY_BLOB_SHA --spawn_strategy=standalone --host_force_python=PY3 + +declare -r DOCS_DEPS=$(bazel query "labels(deps, @envoy_api//docs:protos)") # Only copy in the protos we care about and know how to deal with in protodoc. -for p in $PROTO_RST +for PROTO_TARGET in ${DOCS_DEPS} do - DEST="${GENERATED_RST_DIR}/api-v2/$(sed -e 's#/envoy\/.*/envoy/##' <<< "$p")" - mkdir -p "$(dirname "${DEST}")" - cp -f bazel-bin/external/envoy_api/"${p}" "$(dirname "${DEST}")" - [ -n "${CPROFILE_ENABLED}" ] && cp -f bazel-bin/"${p}".profile "$(dirname "${DEST}")" + for p in $(bazel query "labels(srcs, ${PROTO_TARGET})" ) + do + declare PROTO_TARGET_WITHOUT_PREFIX="${PROTO_TARGET#@envoy_api//}" + declare PROTO_TARGET_CANONICAL="${PROTO_TARGET_WITHOUT_PREFIX/://}" + declare PROTO_FILE_WITHOUT_PREFIX="${p#@envoy_api//}" + declare PROTO_FILE_CANONICAL="${PROTO_FILE_WITHOUT_PREFIX/://}" + declare DEST="${GENERATED_RST_DIR}/api-v2/${PROTO_FILE_CANONICAL#envoy/}".rst + mkdir -p "$(dirname "${DEST}")" + cp -f bazel-bin/external/envoy_api/"${PROTO_TARGET_CANONICAL}/${PROTO_FILE_CANONICAL}.rst" "$(dirname "${DEST}")" + [ -n "${CPROFILE_ENABLED}" ] && cp -f bazel-bin/"${p}".profile "$(dirname "${DEST}")" + done done mkdir -p ${GENERATED_RST_DIR}/api-docs diff --git a/docs/root/_static/css/envoy.css b/docs/root/_static/css/envoy.css index 390a0fec18..05569d4f13 100644 --- a/docs/root/_static/css/envoy.css +++ b/docs/root/_static/css/envoy.css @@ -1,4 +1,9 @@ -@import "theme.css"; +@import url("theme.css"); + +/*Changing content max-width 100% ( 800px is default )*/ +.wy-nav-content { + max-width: 100% !important; +} /* Splits a long line descriptions in tables in to multiple lines */ .wy-table-responsive table td, .wy-table-responsive table th { @@ -8,4 +13,4 @@ /* align multi line csv table columns */ table.docutils div.line-block { margin-left: 0; -} \ No newline at end of file +} diff --git a/docs/root/api-v2/listeners/listeners.rst b/docs/root/api-v2/listeners/listeners.rst index d933ccd32d..47d92c85cd 100644 --- a/docs/root/api-v2/listeners/listeners.rst +++ b/docs/root/api-v2/listeners/listeners.rst @@ -7,3 +7,5 @@ Listeners ../api/v2/lds.proto ../api/v2/listener/listener.proto + ../api/v2/listener/udp_listener_config.proto + ../api/v2/listener/quic_config.proto diff --git a/docs/root/api-v2/types/types.rst b/docs/root/api-v2/types/types.rst index 4a6ba6194c..a5a84c33a1 100644 --- a/docs/root/api-v2/types/types.rst +++ b/docs/root/api-v2/types/types.rst @@ -10,5 +10,6 @@ Types ../type/range.proto ../type/matcher/metadata.proto ../type/matcher/number.proto + ../type/matcher/regex.proto ../type/matcher/string.proto ../type/matcher/value.proto diff --git a/docs/root/configuration/advanced/advanced.rst b/docs/root/configuration/advanced/advanced.rst new file mode 100644 index 0000000000..2753c5bb89 --- /dev/null +++ b/docs/root/configuration/advanced/advanced.rst @@ -0,0 +1,7 @@ +Advanced +======== + +.. toctree:: + :maxdepth: 2 + + well_known_dynamic_metadata diff --git a/docs/root/configuration/well_known_dynamic_metadata.rst b/docs/root/configuration/advanced/well_known_dynamic_metadata.rst similarity index 100% rename from docs/root/configuration/well_known_dynamic_metadata.rst rename to docs/root/configuration/advanced/well_known_dynamic_metadata.rst diff --git a/docs/root/configuration/configuration.rst b/docs/root/configuration/configuration.rst index ffbce401b3..ec5da63ffc 100644 --- a/docs/root/configuration/configuration.rst +++ b/docs/root/configuration/configuration.rst @@ -8,21 +8,12 @@ Configuration reference overview/v2_overview listeners/listeners - listener_filters/listener_filters - network_filters/network_filters - http_conn_man/http_conn_man - http_filters/http_filters - thrift_filters/thrift_filters - dubbo_filters/dubbo_filters - cluster_manager/cluster_manager - health_checkers/health_checkers - access_log - rate_limit - runtime - statistics - xds_subscription_stats - tools/router_check - overload_manager/overload_manager - secret - well_known_dynamic_metadata + http/http + upstream/upstream + observability/observability + security/security + operations/operations + other_features/other_features + other_protocols/other_protocols + advanced/advanced best_practices/best_practices diff --git a/docs/root/configuration/http/http.rst b/docs/root/configuration/http/http.rst new file mode 100644 index 0000000000..f9c8124ff2 --- /dev/null +++ b/docs/root/configuration/http/http.rst @@ -0,0 +1,8 @@ +HTTP +==== + +.. toctree:: + :maxdepth: 2 + + http_conn_man/http_conn_man + http_filters/http_filters diff --git a/docs/root/configuration/http_conn_man/header_sanitizing.rst b/docs/root/configuration/http/http_conn_man/header_sanitizing.rst similarity index 100% rename from docs/root/configuration/http_conn_man/header_sanitizing.rst rename to docs/root/configuration/http/http_conn_man/header_sanitizing.rst diff --git a/docs/root/configuration/http_conn_man/headers.rst b/docs/root/configuration/http/http_conn_man/headers.rst similarity index 98% rename from docs/root/configuration/http_conn_man/headers.rst rename to docs/root/configuration/http/http_conn_man/headers.rst index bfb3a0682a..0a5a1877f7 100644 --- a/docs/root/configuration/http_conn_man/headers.rst +++ b/docs/root/configuration/http/http_conn_man/headers.rst @@ -602,9 +602,13 @@ Supported variable names are: namespace and key(s) are specified as a JSON array of strings. Finally, percent symbols in the parameters **do not** need to be escaped by doubling them. + Upstream metadata cannot be added to request headers as the upstream host has not been selected + when custom request headers are generated. + %UPSTREAM_REMOTE_ADDRESS% Remote address of the upstream host. If the address is an IP address it includes both address - and port. + and port. The upstream remote address cannot be added to request headers as the upstream host + has not been selected when custom request headers are generated. %PER_REQUEST_STATE(reverse.dns.data.name)% Populates the header with values set on the stream info filterState() object. To be diff --git a/docs/root/configuration/http_conn_man/http_conn_man.rst b/docs/root/configuration/http/http_conn_man/http_conn_man.rst similarity index 100% rename from docs/root/configuration/http_conn_man/http_conn_man.rst rename to docs/root/configuration/http/http_conn_man/http_conn_man.rst diff --git a/docs/root/configuration/http_conn_man/overview.rst b/docs/root/configuration/http/http_conn_man/overview.rst similarity index 100% rename from docs/root/configuration/http_conn_man/overview.rst rename to docs/root/configuration/http/http_conn_man/overview.rst diff --git a/docs/root/configuration/http_conn_man/rds.rst b/docs/root/configuration/http/http_conn_man/rds.rst similarity index 100% rename from docs/root/configuration/http_conn_man/rds.rst rename to docs/root/configuration/http/http_conn_man/rds.rst diff --git a/docs/root/configuration/http_conn_man/route_matching.rst b/docs/root/configuration/http/http_conn_man/route_matching.rst similarity index 100% rename from docs/root/configuration/http_conn_man/route_matching.rst rename to docs/root/configuration/http/http_conn_man/route_matching.rst diff --git a/docs/root/configuration/http_conn_man/runtime.rst b/docs/root/configuration/http/http_conn_man/runtime.rst similarity index 100% rename from docs/root/configuration/http_conn_man/runtime.rst rename to docs/root/configuration/http/http_conn_man/runtime.rst diff --git a/docs/root/configuration/http_conn_man/stats.rst b/docs/root/configuration/http/http_conn_man/stats.rst similarity index 100% rename from docs/root/configuration/http_conn_man/stats.rst rename to docs/root/configuration/http/http_conn_man/stats.rst diff --git a/docs/root/configuration/http_conn_man/traffic_splitting.rst b/docs/root/configuration/http/http_conn_man/traffic_splitting.rst similarity index 100% rename from docs/root/configuration/http_conn_man/traffic_splitting.rst rename to docs/root/configuration/http/http_conn_man/traffic_splitting.rst diff --git a/docs/root/configuration/http_filters/buffer_filter.rst b/docs/root/configuration/http/http_filters/buffer_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/buffer_filter.rst rename to docs/root/configuration/http/http_filters/buffer_filter.rst diff --git a/docs/root/configuration/http_filters/cors_filter.rst b/docs/root/configuration/http/http_filters/cors_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/cors_filter.rst rename to docs/root/configuration/http/http_filters/cors_filter.rst diff --git a/docs/root/configuration/http_filters/csrf_filter.rst b/docs/root/configuration/http/http_filters/csrf_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/csrf_filter.rst rename to docs/root/configuration/http/http_filters/csrf_filter.rst diff --git a/docs/root/configuration/http_filters/dynamic_forward_proxy_filter.rst b/docs/root/configuration/http/http_filters/dynamic_forward_proxy_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/dynamic_forward_proxy_filter.rst rename to docs/root/configuration/http/http_filters/dynamic_forward_proxy_filter.rst diff --git a/docs/root/configuration/http_filters/dynamodb_filter.rst b/docs/root/configuration/http/http_filters/dynamodb_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/dynamodb_filter.rst rename to docs/root/configuration/http/http_filters/dynamodb_filter.rst diff --git a/docs/root/configuration/http_filters/ext_authz_filter.rst b/docs/root/configuration/http/http_filters/ext_authz_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/ext_authz_filter.rst rename to docs/root/configuration/http/http_filters/ext_authz_filter.rst diff --git a/docs/root/configuration/http_filters/fault_filter.rst b/docs/root/configuration/http/http_filters/fault_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/fault_filter.rst rename to docs/root/configuration/http/http_filters/fault_filter.rst diff --git a/docs/root/configuration/http_filters/grpc_http1_bridge_filter.rst b/docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/grpc_http1_bridge_filter.rst rename to docs/root/configuration/http/http_filters/grpc_http1_bridge_filter.rst diff --git a/docs/root/configuration/http_filters/grpc_http1_reverse_bridge_filter.rst b/docs/root/configuration/http/http_filters/grpc_http1_reverse_bridge_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/grpc_http1_reverse_bridge_filter.rst rename to docs/root/configuration/http/http_filters/grpc_http1_reverse_bridge_filter.rst diff --git a/docs/root/configuration/http_filters/grpc_json_transcoder_filter.rst b/docs/root/configuration/http/http_filters/grpc_json_transcoder_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/grpc_json_transcoder_filter.rst rename to docs/root/configuration/http/http_filters/grpc_json_transcoder_filter.rst diff --git a/docs/root/configuration/http_filters/grpc_web_filter.rst b/docs/root/configuration/http/http_filters/grpc_web_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/grpc_web_filter.rst rename to docs/root/configuration/http/http_filters/grpc_web_filter.rst diff --git a/docs/root/configuration/http_filters/gzip_filter.rst b/docs/root/configuration/http/http_filters/gzip_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/gzip_filter.rst rename to docs/root/configuration/http/http_filters/gzip_filter.rst diff --git a/docs/root/configuration/http_filters/header_to_metadata_filter.rst b/docs/root/configuration/http/http_filters/header_to_metadata_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/header_to_metadata_filter.rst rename to docs/root/configuration/http/http_filters/header_to_metadata_filter.rst diff --git a/docs/root/configuration/http_filters/health_check_filter.rst b/docs/root/configuration/http/http_filters/health_check_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/health_check_filter.rst rename to docs/root/configuration/http/http_filters/health_check_filter.rst diff --git a/docs/root/configuration/http_filters/http_filters.rst b/docs/root/configuration/http/http_filters/http_filters.rst similarity index 100% rename from docs/root/configuration/http_filters/http_filters.rst rename to docs/root/configuration/http/http_filters/http_filters.rst diff --git a/docs/root/configuration/http_filters/ip_tagging_filter.rst b/docs/root/configuration/http/http_filters/ip_tagging_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/ip_tagging_filter.rst rename to docs/root/configuration/http/http_filters/ip_tagging_filter.rst diff --git a/docs/root/configuration/http_filters/jwt_authn_filter.rst b/docs/root/configuration/http/http_filters/jwt_authn_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/jwt_authn_filter.rst rename to docs/root/configuration/http/http_filters/jwt_authn_filter.rst diff --git a/docs/root/configuration/http_filters/lua_filter.rst b/docs/root/configuration/http/http_filters/lua_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/lua_filter.rst rename to docs/root/configuration/http/http_filters/lua_filter.rst diff --git a/docs/root/configuration/http_filters/original_src_filter.rst b/docs/root/configuration/http/http_filters/original_src_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/original_src_filter.rst rename to docs/root/configuration/http/http_filters/original_src_filter.rst diff --git a/docs/root/configuration/http_filters/rate_limit_filter.rst b/docs/root/configuration/http/http_filters/rate_limit_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/rate_limit_filter.rst rename to docs/root/configuration/http/http_filters/rate_limit_filter.rst diff --git a/docs/root/configuration/http_filters/rbac_filter.rst b/docs/root/configuration/http/http_filters/rbac_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/rbac_filter.rst rename to docs/root/configuration/http/http_filters/rbac_filter.rst diff --git a/docs/root/configuration/http_filters/router_filter.rst b/docs/root/configuration/http/http_filters/router_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/router_filter.rst rename to docs/root/configuration/http/http_filters/router_filter.rst diff --git a/docs/root/configuration/http_filters/squash_filter.rst b/docs/root/configuration/http/http_filters/squash_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/squash_filter.rst rename to docs/root/configuration/http/http_filters/squash_filter.rst diff --git a/docs/root/configuration/http_filters/tap_filter.rst b/docs/root/configuration/http/http_filters/tap_filter.rst similarity index 100% rename from docs/root/configuration/http_filters/tap_filter.rst rename to docs/root/configuration/http/http_filters/tap_filter.rst diff --git a/docs/root/configuration/listener_filters/http_inspector.rst b/docs/root/configuration/listeners/listener_filters/http_inspector.rst similarity index 100% rename from docs/root/configuration/listener_filters/http_inspector.rst rename to docs/root/configuration/listeners/listener_filters/http_inspector.rst diff --git a/docs/root/configuration/listener_filters/listener_filters.rst b/docs/root/configuration/listeners/listener_filters/listener_filters.rst similarity index 100% rename from docs/root/configuration/listener_filters/listener_filters.rst rename to docs/root/configuration/listeners/listener_filters/listener_filters.rst diff --git a/docs/root/configuration/listener_filters/original_dst_filter.rst b/docs/root/configuration/listeners/listener_filters/original_dst_filter.rst similarity index 100% rename from docs/root/configuration/listener_filters/original_dst_filter.rst rename to docs/root/configuration/listeners/listener_filters/original_dst_filter.rst diff --git a/docs/root/configuration/listener_filters/original_src_filter.rst b/docs/root/configuration/listeners/listener_filters/original_src_filter.rst similarity index 100% rename from docs/root/configuration/listener_filters/original_src_filter.rst rename to docs/root/configuration/listeners/listener_filters/original_src_filter.rst diff --git a/docs/root/configuration/listener_filters/proxy_protocol.rst b/docs/root/configuration/listeners/listener_filters/proxy_protocol.rst similarity index 100% rename from docs/root/configuration/listener_filters/proxy_protocol.rst rename to docs/root/configuration/listeners/listener_filters/proxy_protocol.rst diff --git a/docs/root/configuration/listener_filters/tls_inspector.rst b/docs/root/configuration/listeners/listener_filters/tls_inspector.rst similarity index 100% rename from docs/root/configuration/listener_filters/tls_inspector.rst rename to docs/root/configuration/listeners/listener_filters/tls_inspector.rst diff --git a/docs/root/configuration/listeners/listeners.rst b/docs/root/configuration/listeners/listeners.rst index 1efab35c09..73605a8536 100644 --- a/docs/root/configuration/listeners/listeners.rst +++ b/docs/root/configuration/listeners/listeners.rst @@ -8,4 +8,6 @@ Listeners overview stats + listener_filters/listener_filters + network_filters/network_filters lds diff --git a/docs/root/configuration/network_filters/client_ssl_auth_filter.rst b/docs/root/configuration/listeners/network_filters/client_ssl_auth_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/client_ssl_auth_filter.rst rename to docs/root/configuration/listeners/network_filters/client_ssl_auth_filter.rst diff --git a/docs/root/configuration/network_filters/dubbo_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/dubbo_proxy_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/dubbo_proxy_filter.rst rename to docs/root/configuration/listeners/network_filters/dubbo_proxy_filter.rst diff --git a/docs/root/configuration/network_filters/echo_filter.rst b/docs/root/configuration/listeners/network_filters/echo_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/echo_filter.rst rename to docs/root/configuration/listeners/network_filters/echo_filter.rst diff --git a/docs/root/configuration/network_filters/ext_authz_filter.rst b/docs/root/configuration/listeners/network_filters/ext_authz_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/ext_authz_filter.rst rename to docs/root/configuration/listeners/network_filters/ext_authz_filter.rst diff --git a/docs/root/configuration/network_filters/mongo_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/mongo_proxy_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/mongo_proxy_filter.rst rename to docs/root/configuration/listeners/network_filters/mongo_proxy_filter.rst diff --git a/docs/root/configuration/network_filters/mysql_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/mysql_proxy_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/mysql_proxy_filter.rst rename to docs/root/configuration/listeners/network_filters/mysql_proxy_filter.rst diff --git a/docs/root/configuration/network_filters/network_filters.rst b/docs/root/configuration/listeners/network_filters/network_filters.rst similarity index 100% rename from docs/root/configuration/network_filters/network_filters.rst rename to docs/root/configuration/listeners/network_filters/network_filters.rst diff --git a/docs/root/configuration/network_filters/rate_limit_filter.rst b/docs/root/configuration/listeners/network_filters/rate_limit_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/rate_limit_filter.rst rename to docs/root/configuration/listeners/network_filters/rate_limit_filter.rst diff --git a/docs/root/configuration/network_filters/rbac_filter.rst b/docs/root/configuration/listeners/network_filters/rbac_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/rbac_filter.rst rename to docs/root/configuration/listeners/network_filters/rbac_filter.rst diff --git a/docs/root/configuration/network_filters/redis_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/redis_proxy_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/redis_proxy_filter.rst rename to docs/root/configuration/listeners/network_filters/redis_proxy_filter.rst diff --git a/docs/root/configuration/network_filters/sni_cluster_filter.rst b/docs/root/configuration/listeners/network_filters/sni_cluster_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/sni_cluster_filter.rst rename to docs/root/configuration/listeners/network_filters/sni_cluster_filter.rst diff --git a/docs/root/configuration/network_filters/tcp_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/tcp_proxy_filter.rst rename to docs/root/configuration/listeners/network_filters/tcp_proxy_filter.rst diff --git a/docs/root/configuration/network_filters/thrift_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/thrift_proxy_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/thrift_proxy_filter.rst rename to docs/root/configuration/listeners/network_filters/thrift_proxy_filter.rst diff --git a/docs/root/configuration/network_filters/zookeeper_proxy_filter.rst b/docs/root/configuration/listeners/network_filters/zookeeper_proxy_filter.rst similarity index 100% rename from docs/root/configuration/network_filters/zookeeper_proxy_filter.rst rename to docs/root/configuration/listeners/network_filters/zookeeper_proxy_filter.rst diff --git a/docs/root/configuration/access_log.rst b/docs/root/configuration/observability/access_log.rst similarity index 100% rename from docs/root/configuration/access_log.rst rename to docs/root/configuration/observability/access_log.rst diff --git a/docs/root/configuration/observability/observability.rst b/docs/root/configuration/observability/observability.rst new file mode 100644 index 0000000000..6c2be157e8 --- /dev/null +++ b/docs/root/configuration/observability/observability.rst @@ -0,0 +1,8 @@ +Observability +============= + +.. toctree:: + :maxdepth: 2 + + statistics + access_log diff --git a/docs/root/configuration/statistics.rst b/docs/root/configuration/observability/statistics.rst similarity index 91% rename from docs/root/configuration/statistics.rst rename to docs/root/configuration/observability/statistics.rst index 0042051e0a..376263f42b 100644 --- a/docs/root/configuration/statistics.rst +++ b/docs/root/configuration/observability/statistics.rst @@ -16,7 +16,7 @@ Server related statistics are rooted at *server.* with following statistics: 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_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" state, Gauge, Current :ref:`State ` of the Server. @@ -30,6 +30,8 @@ Server related statistics are rooted at *server.* with following statistics: static_unknown_fields, Counter, Number of messages in static configuration with unknown fields dynamic_unknown_fields, Counter, Number of messages in dynamic configuration with unknown fields +.. _filesystem_stats: + File system ----------- @@ -40,7 +42,8 @@ Statistics related to file system are emitted in the *filesystem.* namespace. :widths: 1, 1, 2 write_buffered, Counter, Total number of times file data is moved to Envoy's internal flush buffer - write_completed, Counter, Total number of times a file was written + write_completed, Counter, Total number of times a file was successfully written + write_failed, Counter, Total number of times an error occurred during a file write operation flushed_by_timer, Counter, Total number of times internal flush buffers are written to a file due to flush timeout reopen_failed, Counter, Total number of times a file was failed to be opened write_total_buffered, Gauge, Current total size of internal flush buffer in bytes diff --git a/docs/root/configuration/operations/operations.rst b/docs/root/configuration/operations/operations.rst new file mode 100644 index 0000000000..faa9c0f374 --- /dev/null +++ b/docs/root/configuration/operations/operations.rst @@ -0,0 +1,9 @@ +Operations +========== + +.. toctree:: + :maxdepth: 2 + + runtime + overload_manager/overload_manager + tools/router_check diff --git a/docs/root/configuration/overload_manager/overload_manager.rst b/docs/root/configuration/operations/overload_manager/overload_manager.rst similarity index 100% rename from docs/root/configuration/overload_manager/overload_manager.rst rename to docs/root/configuration/operations/overload_manager/overload_manager.rst diff --git a/docs/root/configuration/runtime.rst b/docs/root/configuration/operations/runtime.rst similarity index 98% rename from docs/root/configuration/runtime.rst rename to docs/root/configuration/operations/runtime.rst index ca1aa31a94..6c9f81b84d 100644 --- a/docs/root/configuration/runtime.rst +++ b/docs/root/configuration/operations/runtime.rst @@ -257,7 +257,7 @@ The file system runtime provider emits some statistics in the *runtime.* namespa :widths: 1, 1, 2 admin_overrides_active, Gauge, 1 if any admin overrides are active otherwise 0 - deprecated_feature_use, Counter, Total number of times deprecated features were used + deprecated_feature_use, Counter, Total number of times deprecated features were used. Detailed information about the feature used will be logged to warning logs in the form "Using deprecated option 'X' from file Y". load_error, Counter, Total number of load attempts that resulted in an error in any layer load_success, Counter, Total number of load attempts that were successful at all layers num_keys, Gauge, Number of keys currently loaded diff --git a/docs/root/configuration/tools/router_check.rst b/docs/root/configuration/operations/tools/router_check.rst similarity index 100% rename from docs/root/configuration/tools/router_check.rst rename to docs/root/configuration/operations/tools/router_check.rst diff --git a/docs/root/configuration/other_features/other_features.rst b/docs/root/configuration/other_features/other_features.rst new file mode 100644 index 0000000000..84d8f49483 --- /dev/null +++ b/docs/root/configuration/other_features/other_features.rst @@ -0,0 +1,7 @@ +Other features +============== + +.. toctree:: + :maxdepth: 2 + + rate_limit diff --git a/docs/root/configuration/rate_limit.rst b/docs/root/configuration/other_features/rate_limit.rst similarity index 100% rename from docs/root/configuration/rate_limit.rst rename to docs/root/configuration/other_features/rate_limit.rst diff --git a/docs/root/configuration/dubbo_filters/dubbo_filters.rst b/docs/root/configuration/other_protocols/dubbo_filters/dubbo_filters.rst similarity index 100% rename from docs/root/configuration/dubbo_filters/dubbo_filters.rst rename to docs/root/configuration/other_protocols/dubbo_filters/dubbo_filters.rst diff --git a/docs/root/configuration/dubbo_filters/router_filter.rst b/docs/root/configuration/other_protocols/dubbo_filters/router_filter.rst similarity index 100% rename from docs/root/configuration/dubbo_filters/router_filter.rst rename to docs/root/configuration/other_protocols/dubbo_filters/router_filter.rst diff --git a/docs/root/configuration/other_protocols/other_protocols.rst b/docs/root/configuration/other_protocols/other_protocols.rst new file mode 100644 index 0000000000..8ad84b892d --- /dev/null +++ b/docs/root/configuration/other_protocols/other_protocols.rst @@ -0,0 +1,8 @@ +Other protocols +=============== + +.. toctree:: + :maxdepth: 2 + + thrift_filters/thrift_filters + dubbo_filters/dubbo_filters diff --git a/docs/root/configuration/thrift_filters/rate_limit_filter.rst b/docs/root/configuration/other_protocols/thrift_filters/rate_limit_filter.rst similarity index 100% rename from docs/root/configuration/thrift_filters/rate_limit_filter.rst rename to docs/root/configuration/other_protocols/thrift_filters/rate_limit_filter.rst diff --git a/docs/root/configuration/thrift_filters/router_filter.rst b/docs/root/configuration/other_protocols/thrift_filters/router_filter.rst similarity index 100% rename from docs/root/configuration/thrift_filters/router_filter.rst rename to docs/root/configuration/other_protocols/thrift_filters/router_filter.rst diff --git a/docs/root/configuration/thrift_filters/thrift_filters.rst b/docs/root/configuration/other_protocols/thrift_filters/thrift_filters.rst similarity index 100% rename from docs/root/configuration/thrift_filters/thrift_filters.rst rename to docs/root/configuration/other_protocols/thrift_filters/thrift_filters.rst diff --git a/docs/root/configuration/overview/v2_overview.rst b/docs/root/configuration/overview/v2_overview.rst index 7802030f43..3dcefb0067 100644 --- a/docs/root/configuration/overview/v2_overview.rst +++ b/docs/root/configuration/overview/v2_overview.rst @@ -599,6 +599,30 @@ Management Server has a statistics tree rooted at *control_plane.* with the foll rate_limit_enforced, Counter, Total number of times rate limit was enforced for management server requests pending_requests, Gauge, Total number of pending requests when the rate limit was enforced +.. _subscription_statistics: + +xDS subscription statistics +--------------------------- + +Envoy discovers its various dynamic resources via discovery +services referred to as *xDS*. Resources are requested via :ref:`subscriptions `, +by specifying a filesystem path to watch, initiating gRPC streams or polling a REST-JSON URL. + +The following statistics are generated for all subscriptions. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + config_reload, Counter, Total API fetches that resulted in a config reload due to a different config + init_fetch_timeout, Counter, Total :ref:`initial fetch timeouts ` + update_attempt, Counter, Total API fetches attempted + update_success, Counter, Total API fetches completed successfully + update_failure, Counter, Total API fetches that failed because of network errors + update_rejected, Counter, Total API fetches that failed because of schema/validation errors + version, Gauge, Hash of the contents from the last successful API fetch + control_plane.connected_state, Gauge, A boolean (1 for connected and 0 for disconnected) that indicates the current connection state with management server + .. _config_overview_v2_status: Status diff --git a/docs/root/configuration/secret.rst b/docs/root/configuration/security/secret.rst similarity index 100% rename from docs/root/configuration/secret.rst rename to docs/root/configuration/security/secret.rst diff --git a/docs/root/configuration/security/security.rst b/docs/root/configuration/security/security.rst new file mode 100644 index 0000000000..223a9ee5e3 --- /dev/null +++ b/docs/root/configuration/security/security.rst @@ -0,0 +1,7 @@ +Security +======== + +.. toctree:: + :maxdepth: 2 + + secret diff --git a/docs/root/configuration/cluster_manager/cds.rst b/docs/root/configuration/upstream/cluster_manager/cds.rst similarity index 100% rename from docs/root/configuration/cluster_manager/cds.rst rename to docs/root/configuration/upstream/cluster_manager/cds.rst diff --git a/docs/root/configuration/cluster_manager/cluster_circuit_breakers.rst b/docs/root/configuration/upstream/cluster_manager/cluster_circuit_breakers.rst similarity index 100% rename from docs/root/configuration/cluster_manager/cluster_circuit_breakers.rst rename to docs/root/configuration/upstream/cluster_manager/cluster_circuit_breakers.rst diff --git a/docs/root/configuration/cluster_manager/cluster_hc.rst b/docs/root/configuration/upstream/cluster_manager/cluster_hc.rst similarity index 100% rename from docs/root/configuration/cluster_manager/cluster_hc.rst rename to docs/root/configuration/upstream/cluster_manager/cluster_hc.rst diff --git a/docs/root/configuration/cluster_manager/cluster_manager.rst b/docs/root/configuration/upstream/cluster_manager/cluster_manager.rst similarity index 100% rename from docs/root/configuration/cluster_manager/cluster_manager.rst rename to docs/root/configuration/upstream/cluster_manager/cluster_manager.rst diff --git a/docs/root/configuration/cluster_manager/cluster_runtime.rst b/docs/root/configuration/upstream/cluster_manager/cluster_runtime.rst similarity index 84% rename from docs/root/configuration/cluster_manager/cluster_runtime.rst rename to docs/root/configuration/upstream/cluster_manager/cluster_runtime.rst index a64750cf66..195d025c24 100644 --- a/docs/root/configuration/cluster_manager/cluster_runtime.rst +++ b/docs/root/configuration/upstream/cluster_manager/cluster_runtime.rst @@ -102,6 +102,31 @@ outlier_detection.success_rate_stdev_factor ` setting in outlier detection +outlier_detection.enforcing_failure_percentage + :ref:`enforcing_failure_percentage + ` + setting in outlier detection + +outlier_detection.enforcing_failure_percentage_local_origin + :ref:`enforcing_failure_percentage_local_origin + ` + setting in outlier detection + +outlier_detection.failure_percentage_request_volume + :ref:`failure_percentage_request_volume + ` + setting in outlier detection + +outlier_detection.failure_percentage_minimum_hosts + :ref:`failure_percentage_minimum_hosts + ` + setting in outlier detection + +outlier_detection.failure_percentage_threshold + :ref:`failure_percentage_threshold + ` + setting in outlier detection + Core ---- diff --git a/docs/root/configuration/cluster_manager/cluster_stats.rst b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst similarity index 94% rename from docs/root/configuration/cluster_manager/cluster_stats.rst rename to docs/root/configuration/upstream/cluster_manager/cluster_stats.rst index f4f1890baf..57bd438109 100644 --- a/docs/root/configuration/cluster_manager/cluster_stats.rst +++ b/docs/root/configuration/upstream/cluster_manager/cluster_stats.rst @@ -141,6 +141,10 @@ statistics will be rooted at *cluster..outlier_detection.* and contain the ejections_detected_consecutive_local_origin_failure, Counter, Number of detected consecutive local origin failure ejections (even if unenforced) ejections_enforced_local_origin_success_rate, Counter, Number of enforced success rate outlier ejections for locally originated failures ejections_detected_local_origin_success_rate, Counter, Number of detected success rate outlier ejections for locally originated failures (even if unenforced) + ejections_enforced_failure_percentage, Counter, Number of enforced failure percentage outlier ejections. Exact meaning of this counter depends on :ref:`outlier_detection.split_external_local_origin_errors` config item. Refer to :ref:`Outlier Detection documentation` for details. + ejections_detected_failure_percentage, Counter, Number of detected failure percentage outlier ejections (even if unenforced). Exact meaning of this counter depends on :ref:`outlier_detection.split_external_local_origin_errors` config item. Refer to :ref:`Outlier Detection documentation` for details. + ejections_enforced_failure_percentage_local_origin, Counter, Number of enforced failure percentage outlier ejections for locally originated failures + ejections_detected_failure_percentage_local_origin, Counter, Number of detected failure percentage outlier ejections for locally originated failures (even if unenforced) ejections_total, Counter, Deprecated. Number of ejections due to any outlier type (even if unenforced) ejections_consecutive_5xx, Counter, Deprecated. Number of consecutive 5xx ejections (even if unenforced) diff --git a/docs/root/configuration/cluster_manager/overview.rst b/docs/root/configuration/upstream/cluster_manager/overview.rst similarity index 100% rename from docs/root/configuration/cluster_manager/overview.rst rename to docs/root/configuration/upstream/cluster_manager/overview.rst diff --git a/docs/root/configuration/health_checkers/health_checkers.rst b/docs/root/configuration/upstream/health_checkers/health_checkers.rst similarity index 100% rename from docs/root/configuration/health_checkers/health_checkers.rst rename to docs/root/configuration/upstream/health_checkers/health_checkers.rst diff --git a/docs/root/configuration/health_checkers/redis.rst b/docs/root/configuration/upstream/health_checkers/redis.rst similarity index 100% rename from docs/root/configuration/health_checkers/redis.rst rename to docs/root/configuration/upstream/health_checkers/redis.rst diff --git a/docs/root/configuration/upstream/upstream.rst b/docs/root/configuration/upstream/upstream.rst new file mode 100644 index 0000000000..3e84e6352d --- /dev/null +++ b/docs/root/configuration/upstream/upstream.rst @@ -0,0 +1,8 @@ +Upstream clusters +================= + +.. toctree:: + :maxdepth: 2 + + cluster_manager/cluster_manager + health_checkers/health_checkers diff --git a/docs/root/configuration/xds_subscription_stats.rst b/docs/root/configuration/xds_subscription_stats.rst deleted file mode 100644 index c15bbcc22a..0000000000 --- a/docs/root/configuration/xds_subscription_stats.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. _subscription_statistics: - -xDS subscription statistics -=========================== - -Envoy discovers its various dynamic resources via discovery -services referred to as *xDS*. Resources are requested via :ref:`subscriptions `, -by specifying a filesystem path to watch, initiating gRPC streams or polling a REST-JSON URL. - -The following statistics are generated for all subscriptions. - -.. csv-table:: - :header: Name, Type, Description - :widths: 1, 1, 2 - - config_reload, Counter, Total API fetches that resulted in a config reload due to a different config - init_fetch_timeout, Counter, Total :ref:`initial fetch timeouts ` - update_attempt, Counter, Total API fetches attempted - update_success, Counter, Total API fetches completed successfully - update_failure, Counter, Total API fetches that failed because of network errors - update_rejected, Counter, Total API fetches that failed because of schema/validation errors - version, Gauge, Hash of the contents from the last successful API fetch - control_plane.connected_state, Gauge, A boolean (1 for connected and 0 for disconnected) that indicates the current connection state with management server diff --git a/docs/root/extending/extending.rst b/docs/root/extending/extending.rst index 1551d09924..51198fefac 100644 --- a/docs/root/extending/extending.rst +++ b/docs/root/extending/extending.rst @@ -19,6 +19,7 @@ types including: * :ref:`Stat sinks ` * :ref:`Tracers ` * Transport sockets +* BoringSSL private key methods As of this writing there is no high level extension developer documentation. The :repo:`existing extensions ` are a good way to learn what is possible. diff --git a/docs/root/install/tools/route_table_check_tool.rst b/docs/root/install/tools/route_table_check_tool.rst index ac0b523eec..186df20b13 100644 --- a/docs/root/install/tools/route_table_check_tool.rst +++ b/docs/root/install/tools/route_table_check_tool.rst @@ -37,9 +37,22 @@ Usage -d, --details Show detailed test execution results. The first line indicates the test name. + --only-show-failures + Displays test results for failed tests. Omits test names for passing tests if the details flag is set. + -p, --useproto Use Proto test file schema + -f, --fail-under + Represents a percent value for route test coverage under which the run should fail. + + --covall + Enables comprehensive code coverage percent calculation taking into account all the possible + asserts. + + --disable-deprecation-check + Disables the deprecation check for RouteConfiguration proto. + -h, --help Displays usage information and exits. diff --git a/docs/root/intro/arch_overview/http/http_routing.rst b/docs/root/intro/arch_overview/http/http_routing.rst index 6a191be268..574efa611e 100644 --- a/docs/root/intro/arch_overview/http/http_routing.rst +++ b/docs/root/intro/arch_overview/http/http_routing.rst @@ -50,6 +50,35 @@ request. The router filter supports the following features: * :ref:`Hash policy ` based routing. * :ref:`Absolute urls ` are supported for non-tls forward proxies. +.. _arch_overview_http_routing_route_scope: + +Route Scope +-------------- + +Scoped routing enables Envoy to put constraints on search space of domains and route rules. +A :ref:`Route Scope` associates a key with a :ref:`route table `. +For each request, a scope key is computed dynamically by the HTTP connection manager to pick the :ref:`route table`. + +The Scoped RDS (SRDS) API contains a set of :ref:`Scopes ` resources, each defining independent routing configuration, +along with a :ref:`ScopeKeyBuilder ` +defining the key construction algorithm used by Envoy to look up the scope corresponding to each request. + +For example, for the following scoped route configuration, Envoy will look into the "addr" header value, split the header value by ";" first, and use the first value for key 'x-foo-key' as the scope key. +If the "addr" header value is "foo=1;x-foo-key=127.0.0.1;x-bar-key=1.1.1.1", then "127.0.0.1" will be computed as the scope key to look up for corresponding route configuration. + +.. code-block:: yaml + + name: scope_by_addr + fragments: + - header_value_extractor: + name: Addr + element_separator: ; + element: + key: x-foo-key + separator: = + +.. _arch_overview_http_routing_route_table: + Route table ----------- diff --git a/docs/root/intro/arch_overview/http/websocket.rst b/docs/root/intro/arch_overview/http/websocket.rst index e854eb53bb..fa4e0b1f05 100644 --- a/docs/root/intro/arch_overview/http/websocket.rst +++ b/docs/root/intro/arch_overview/http/websocket.rst @@ -36,19 +36,21 @@ Note that the statistics for upgrades are all bundled together so WebSocket :ref:`statistics ` are tracked by stats such as downstream_cx_upgrades_total and downstream_cx_upgrades_active -Handling H2 hops -^^^^^^^^^^^^^^^^ +Handling HTTP/2 hops +^^^^^^^^^^^^^^^^^^^^ -Envoy supports tunneling WebSockets over H2 streams for deployments that prefer a uniform -H2 mesh throughout; this enables, for example, a deployment of the form: +While HTTP/2 support for WebSockets is off by default, Envoy does support tunneling WebSockets over +HTTP/2 streams for deployments that prefer a uniform HTTP/2 mesh throughout; this enables, for example, +a deployment of the form: [Client] ---- HTTP/1.1 ---- [Front Envoy] ---- HTTP/2 ---- [Sidecar Envoy ---- H1 ---- App] In this case, if a client is for example using WebSocket, we want the Websocket to arrive at the upstream server functionally intact, which means it needs to traverse the HTTP/2 hop. -This is accomplished via -`extended CONNECT `_ support. The +This is accomplished via `extended CONNECT `_ support, +turned on by setting :ref:`allow_connect ` +true at the second layer Envoy. The WebSocket request will be transformed into an HTTP/2 CONNECT stream, with :protocol header indicating the original upgrade, traverse the HTTP/2 hop, and be downgraded back into an HTTP/1 WebSocket Upgrade. This same Upgrade-CONNECT-Upgrade transformation will be performed on any @@ -57,5 +59,5 @@ Non-WebSocket upgrades are allowed to use any valid HTTP method (i.e. POST) and upgrade/downgrade mechanism will drop the original method and transform the Upgrade request to a GET method on the final Envoy-Upstream hop. -Note that the H2 upgrade path has very strict HTTP/1.1 compliance, so will not proxy WebSocket +Note that the HTTP/2 upgrade path has very strict HTTP/1.1 compliance, so will not proxy WebSocket upgrade requests or responses with bodies. diff --git a/docs/root/intro/arch_overview/observability/tracing.rst b/docs/root/intro/arch_overview/observability/tracing.rst index bd8016424d..24072465e2 100644 --- a/docs/root/intro/arch_overview/observability/tracing.rst +++ b/docs/root/intro/arch_overview/observability/tracing.rst @@ -93,9 +93,12 @@ associated with it. Each span generated by Envoy contains the following data: * Originating host set via :option:`--service-node`. * Downstream cluster set via the :ref:`config_http_conn_man_headers_downstream-service-cluster` header. -* HTTP URL. -* HTTP method. -* HTTP response code. +* HTTP request URL, method, protocol and user-agent. +* Additional HTTP request headers set via :ref:`request_headers_for_tags + ` +* HTTP response status code. +* GRPC response status and message (if available). +* An error tag when HTTP status is 5xx or GRPC status is not "OK" * Tracing system-specific metadata. The span also includes a name (or operation) which by default is defined as the host of the invoked diff --git a/docs/root/intro/arch_overview/other_protocols/redis.rst b/docs/root/intro/arch_overview/other_protocols/redis.rst index 47e55418c6..e96cbe6a3e 100644 --- a/docs/root/intro/arch_overview/other_protocols/redis.rst +++ b/docs/root/intro/arch_overview/other_protocols/redis.rst @@ -60,6 +60,8 @@ If passive healthchecking is desired, also configure For the purposes of passive healthchecking, connect timeouts, command timeouts, and connection close map to 5xx. All other responses from Redis are counted as a success. +.. _arch_overview_redis_cluster_support: + Redis Cluster Support (Experimental) ---------------------------------------- @@ -90,6 +92,20 @@ Every Redis cluster has its own extra statistics tree rooted at *cluster.. max_upstream_unknown_connections_reached, Counter, Total number of times that an upstream connection to an unknown host is not created after redirection having reached the connection pool's max_upstream_unknown_connections limit upstream_cx_drained, Counter, Total number of upstream connections drained of active requests before being closed + upstream_commands.upstream_rq_time, Histogram, Histogram of upstream request times for all types of requests + +.. _arch_overview_redis_cluster_command_stats: + +Per-cluster command statistics can be enabled via the setting :ref:`enable_command_stats `: + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + upstream_commands.[command].success, Counter, Total number of successful requests for a specific Redis command + upstream_commands.[command].error, Counter, Total number of failed or cancelled requests for a specific Redis command + upstream_commands.[command].total, Counter, Total number of requests for a specific Redis command (sum of success and error) + upstream_commands.[command].latency, Histogram, Latency of requests for a specific Redis command Supported commands ------------------ diff --git a/docs/root/intro/arch_overview/security/rbac_filter.rst b/docs/root/intro/arch_overview/security/rbac_filter.rst index a0fba7097e..138f2a6b9e 100644 --- a/docs/root/intro/arch_overview/security/rbac_filter.rst +++ b/docs/root/intro/arch_overview/security/rbac_filter.rst @@ -33,3 +33,67 @@ The filter can be configured with a :ref:`shadow policy ` that doesn't have any effect (i.e. not deny the request) but only emit stats and log the result. This is useful for testing a rule before applying in production. + +.. _arch_overview_condition: + +Condition +--------- + +In addition to the pre-defined permissions and principals, a policy may optionally provide an +authorization condition written in the `Common Expression Language +`_. The condition specifies an extra +clause that must be satisfied for the policy to match. For example, the following condition checks +whether the request path starts with `/v1/`: + +.. code-block:: yaml + + call_expr: + function: startsWith + args: + - select_expr: + operand: + ident_expr: + name: request + field: path + - const_expr: + string_value: /v1/ + +The following attributes are exposed to the language runtime: + +.. csv-table:: + :header: Attribute, Type, Description + :widths: 1, 1, 2 + + request.path, string, The path portion of the URL + request.url_path, string, The path portion of the URL without the query string + request.host, string, The host portion of the URL + request.scheme, string, The scheme portion of the URL + request.method, string, Request method + request.headers, string map, All request headers + request.referer, string, Referer request header + request.useragent, string, User agent request header + request.time, timestamp, Time of the first byte received + request.duration, duration, Total duration of the request + request.id, string, Request ID + request.size, int, Size of the request body + request.total_size, int, Total size of the request including the headers + response.code, int, Response HTTP status code + response.headers, string map, All response headers + response.trailers, string map, All response trailers + response.size, int, Size of the response body + source.address, string, Downstream connection remote address + source.port, int, Downstream connection remote port + destination.address, string, Downstream connection local address + destination.port, int, Downstream connection local port + metadata, :ref:`Metadata`, Dynamic metadata + connection.mtls, bool, Indicates whether TLS is applied to the downstream connection and the peer ceritificate is presented + connection.requested_server_name, string, Requested server name in the downstream TLS connection + connection.tls_version, string, TLS version of the downstream TLS connection + upstream.address, string, Upstream connection remote address + upstream.port, int, Upstream connection remote port + upstream.mtls, bool, Indicates whether TLS is applied to the upstream connection and the peer ceritificate is presented + + +Most attributes are optional and provide the default value based on the type of the attribute. +CEL supports presence checks for attributes and maps using `has()` syntax, e.g. +`has(request.referer)`. diff --git a/docs/root/intro/arch_overview/security/ssl.rst b/docs/root/intro/arch_overview/security/ssl.rst index e73d14dd3e..a44a8f5318 100644 --- a/docs/root/intro/arch_overview/security/ssl.rst +++ b/docs/root/intro/arch_overview/security/ssl.rst @@ -23,6 +23,10 @@ requirements (TLS1.2, SNI, etc.). Envoy supports the following TLS features: tickets (see `RFC 5077 `_). Resumption can be performed across hot restarts and between parallel Envoy instances (typically useful in a front proxy configuration). +* **BoringSSL private key methods**: TLS private key operations (signing and decrypting) can be + performed asynchronously from an extension. This allows extending Envoy to support various key + management schemes (such as TPM) and TLS acceleration. This mechanism uses + `BoringSSL private key method interface `_. Underlying implementation ------------------------- diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/panic_threshold.rst b/docs/root/intro/arch_overview/upstream/load_balancing/panic_threshold.rst index e24022a8f0..864ad11171 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/panic_threshold.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/panic_threshold.rst @@ -5,13 +5,24 @@ Panic threshold During load balancing, Envoy will generally only consider available (healthy or degraded) hosts in an upstream cluster. However, if the percentage of available hosts in the cluster becomes too low, -Envoy will disregard health status and balance amongst all hosts. This is known as the *panic -threshold*. The default panic threshold is 50%. This is +Envoy will disregard health status and balance either amongst all hosts or no hosts. This is known +as the *panic threshold*. The default panic threshold is 50%. This is :ref:`configurable ` via runtime as well as in the :ref:`cluster configuration `. The panic threshold is used to avoid a situation in which host failures cascade throughout the cluster as load increases. +There are two modes Envoy can choose from when in a panic state: traffic will either be sent to all +hosts, or will be sent to no hosts (and therefore will always fail). This is configured in the +:ref:`cluster configuration `. +Choosing to fail traffic during panic scenarios can help avoid overwhelming potentially failing +upstream services, as it will reduce the load on the upstream service before all hosts have been +determined to be unhealthy. However, it eliminates the possibility of _some_ requests succeeding +even when many or all hosts in a cluster are unhealthy. This may be a good tradeoff to make if a +given service is observed to fail in an all-or-nothing pattern, as it will more quickly cut off +requests to the cluster. Conversely, if a cluster typically continues to successfully service _some_ +requests even when degraded, enabling this option is probably unhelpful. + Panic thresholds work in conjunction with priorities. If the number of available hosts in a given priority goes down, Envoy will try to shift some traffic to lower priorities. If it succeeds in finding enough available hosts in lower priorities, Envoy will disregard panic thresholds. In @@ -20,8 +31,8 @@ disregards panic thresholds and continues to distribute traffic load across prio the algorithm described :ref:`here `. However, when normalized total availability drops below 100%, Envoy assumes that there are not enough available hosts across all priority levels. It continues to distribute traffic load across priorities, -but if a given priority level's availability is below the panic threshold, traffic will go to all hosts -in that priority level regardless of their availability. +but if a given priority level's availability is below the panic threshold, traffic will go to all +(or no) hosts in that priority level regardless of their availability. The following examples explain the relationship between normalized total availability and panic threshold. It is assumed that the default value of 50% is used for the panic threshold. diff --git a/docs/root/intro/arch_overview/upstream/load_balancing/subsets.rst b/docs/root/intro/arch_overview/upstream/load_balancing/subsets.rst index 710a72f43b..942903ceb6 100644 --- a/docs/root/intro/arch_overview/upstream/load_balancing/subsets.rst +++ b/docs/root/intro/arch_overview/upstream/load_balancing/subsets.rst @@ -35,12 +35,20 @@ therefore, contain a definition that has the same keys as a given route in order balancing to occur. This feature can only be enabled using the V2 configuration API. Furthermore, host metadata is only -supported when using the EDS discovery type for clusters. Host metadata for subset load balancing -must be placed under the filter name ``"envoy.lb"``. Similarly, route metadata match criteria use -the ``"envoy.lb"`` filter name. Host metadata may be hierarchical (e.g., the value for a top-level -key may be a structured value or list), but the subset load balancer only compares top-level keys -and values. Therefore when using structured values, a route's match criteria will only match if an -identical structured value appears in the host's metadata. +supported when hosts are defined using +:ref:`ClusterLoadAssignments `. ClusterLoadAssignments are +available via EDS or the Cluster :ref:`load_assignment ` +field. Host metadata for subset load balancing must be placed under the filter name ``"envoy.lb"``. +Similarly, route metadata match criteria use ``"envoy.lb"`` filter name. Host metadata may be +hierarchical (e.g., the value for a top-level key may be a structured value or list), but the +subset load balancer only compares top-level keys and values. Therefore when using structured +values, a route's match criteria will only match if an identical structured value appears in the +host's metadata. + +Finally, note that subset load balancing is not available for the +:ref:`ORIGINAL_DST_LB ` or +:ref:`CLUSTER_PROVIDED ` load balancer +policies. Examples ^^^^^^^^ diff --git a/docs/root/intro/arch_overview/upstream/outlier.rst b/docs/root/intro/arch_overview/upstream/outlier.rst index b8b4fce4b7..6743fba991 100644 --- a/docs/root/intro/arch_overview/upstream/outlier.rst +++ b/docs/root/intro/arch_overview/upstream/outlier.rst @@ -145,6 +145,40 @@ Most configuration items, namely types of errors, but :ref:`outlier_detection.enforcing_success_rate` applies to externally originated errors only and :ref:`outlier_detection.enforcing_local_origin_success_rate` applies to locally originated errors only. +.. _arch_overview_outlier_detection_failure_percentage: + +Failure Percentage +^^^^^^^^^^^^^^^^^^ + +Failure Percentage based outlier ejection functions similarly to the success rate detecion type, in +that it relies on success rate data from each host in a cluster. However, rather than compare those +values to the mean success rate of the cluster as a whole, they are compared to a flat +user-configured threshold. This threshold is configured via the +:ref:`outlier_detection.failure_percentage_threshold` +field. + +The other configuration fields for failure percentage based ejection are similar to the fields for +success rate ejection. Failure percentage based ejection also obeys +:ref:`outlier_detection.split_external_local_origin_errors`; +the enforcement percentages for externally- and locally-originated errors are controlled by +:ref:`outlier_detection.enforcing_failure_percentage` +and +:ref:`outlier_detection.enforcing_failure_percentage_local_origin`, +respectively. As with success rate detection, detection will not be performed for a host if its +request volume over the aggregation interval is less than the +:ref:`outlier_detection.failure_percentage_request_volume` +value. Detection also will not be performed for a cluster if the number of hosts with the minimum +required request volume in an interval is less than the +:ref:`outlier_detection.failure_percentage_minimum_hosts` +value. + +.. _arch_overview_outlier_detection_grpc: + +gRPC +---------------------- + +For gRPC requests, the outlier detection will use the HTTP status mapped from the `grpc-status `_ response header. This behavior is guarded by the runtime feature `envoy.reloadable_features.outlier_detection_support_for_grpc_status` which defaults to true. + .. _arch_overview_outlier_detection_logging: diff --git a/docs/root/intro/deprecated.rst b/docs/root/intro/deprecated.rst index 6ca0b034b7..d353ef72cf 100644 --- a/docs/root/intro/deprecated.rst +++ b/docs/root/intro/deprecated.rst @@ -12,8 +12,33 @@ Deprecated items below are listed in chronological order. Version 1.12.0 (pending) ======================== -* The ORIGINAL_DST_LB :ref:`load balancing policy ` is deprecated, use CLUSTER_PROVIDED policy instead when configuring an :ref:`original destination cluster `. -* The :option:`--allow-unknown-fields` command-line option, use :option:`--allow-unknown-static-fields` instead. +* The ORIGINAL_DST_LB :ref:`load balancing policy ` is + deprecated, use CLUSTER_PROVIDED policy instead when configuring an :ref:`original destination + cluster `. +* The `regex` field in :ref:`StringMatcher ` has been + deprecated in favor of the `safe_regex` field. +* The `regex` field in :ref:`RouteMatch ` has been + deprecated in favor of the `safe_regex` field. +* The `allow_origin` and `allow_origin_regex` fields in :ref:`CorsPolicy + ` have been deprecated in favor of the + `allow_origin_string_match` field. +* The `pattern` and `method` fields in :ref:`VirtualCluster ` + have been deprecated in favor of the `headers` field. +* The `regex_match` field in :ref:`HeaderMatcher ` has been + deprecated in favor of the `safe_regex_match` field. +* The `value` and `regex` fields in :ref:`QueryParameterMatcher + ` has been deprecated in favor of the `string_match` + and `present_match` fields. +* The :option:`--allow-unknown-fields` command-line option, + use :option:`--allow-unknown-static-fields` instead. +* The use of HTTP_JSON_V1 :ref:`Zipkin collector endpoint version + ` or not explicitly + specifying it is deprecated, use HTTP_JSON or HTTP_PROTO instead. +* The `operation_name` field in :ref:`HTTP connection manager + ` + has been deprecated in favor of the `traffic_direction` field in + :ref:`Listener `. The latter takes priority if + specified. Version 1.11.0 (July 11, 2019) ============================== diff --git a/docs/root/intro/version_history.rst b/docs/root/intro/version_history.rst index 83998544fd..a80968050e 100644 --- a/docs/root/intro/version_history.rst +++ b/docs/root/intro/version_history.rst @@ -4,11 +4,13 @@ Version history 1.12.0 (pending) ================ * access log: added :ref:`buffering ` and :ref:`periodical flushing ` support to gRPC access logger. Defaults to 16KB buffer and flushing every 1 second. +* access log: gRPC Access Log Service (ALS) support added for :ref:`TCP access logs `. +* access log: reintroduce :ref:`filesystem ` stats and added the `write_failed` counter to track failed log writes * admin: added ability to configure listener :ref:`socket options `. * admin: added config dump support for Secret Discovery Service :ref:`SecretConfigDump `. * api: added ::ref:`set_node_on_first_message_only ` option to omit the node identifier from the subsequent discovery requests on the same stream. -* config: enforcing that terminal filters (e.g. HttpConnectionManager for L4, router for L7) be the last in their respective filter chains. * buffer filter: the buffer filter populates content-length header if not present, behavior can be disabled using the runtime feature `envoy.reloadable_features.buffer_filter_populate_content_length`. +* config: enforcing that terminal filters (e.g. HttpConnectionManager for L4, router for L7) be the last in their respective filter chains. * config: added access log :ref:`extension filter`. * config: added support for :option:`--reject-unknown-dynamic-fields`, providing independent control over whether unknown fields are rejected in static and dynamic configuration. By default, unknown @@ -19,26 +21,50 @@ Version history * config: async data access for local and remote data source. * config: changed the default value of :ref:`initial_fetch_timeout ` from 0s to 15s. This is a change in behaviour in the sense that Envoy will move to the next initialization phase, even if the first config is not delivered in 15s. Refer to :ref:`initialization process ` for more details. * config: added stat :ref:`init_fetch_timeout `. +* ext_authz: added :ref:`configurable ability ` to send dynamic metadata to the `ext_authz` service. * fault: added overrides for default runtime keys in :ref:`HTTPFault ` filter. * grpc: added :ref:`AWS IAM grpc credentials extension ` for AWS-managed xDS. * grpc-json: added support for :ref:`ignoring unknown query parameters`. * header to metadata: added :ref:`PROTOBUF_VALUE ` and :ref:`ValueEncode ` to support protobuf Value and Base64 encoding. * http: added the ability to reject HTTP/1.1 requests with invalid HTTP header values, using the runtime feature `envoy.reloadable_features.strict_header_validation`. +* http: changed Envoy to forward existing x-forwarded-proto from upstream trusted proxies. Guarded by `envoy.reloadable_features.trusted_forwarded_proto` which defaults true. +* http: added the ability to configure the behavior of the server response header, via the :ref:`server_header_transformation` field. * http: added the ability to :ref:`merge adjacent slashes` in the path. +* http: remove h2c upgrade headers for HTTP/1 as h2c upgrades are currently not supported. * listeners: added :ref:`continue_on_listener_filters_timeout ` to configure whether a listener will still create a connection when listener filters time out. * listeners: added :ref:`HTTP inspector listener filter `. -* redis: added :ref:`read_policy ` to allow reading from redis replicas for Redis Cluster deployments. -* rbac: added support for DNS SAN as :ref:`principal_name `. * lua: extended `httpCall()` and `respond()` APIs to accept headers with entry values that can be a string or table of strings. +* metrics_service: added support for flushing histogram buckets. +* outlier_detector: added :ref:`support for the grpc-status response header ` by mapping it to HTTP status. Guarded by envoy.reloadable_features.outlier_detection_support_for_grpc_status which defaults to true. * performance: new buffer implementation enabled by default (to disable add "--use-libevent-buffers 1" to the command-line arguments when starting Envoy). +* performance: stats symbol table implementation (disabled by default; to test it, add "--use-fake-symbol-table 0" to the command-line arguments when starting Envoy). +* rbac: added support for DNS SAN as :ref:`principal_name `. +* redis: added :ref:`enable_command_stats ` to enable :ref:`per command statistics ` for upstream clusters. +* redis: added :ref:`read_policy ` to allow reading from redis replicas for Redis Cluster deployments. +* redis: fix a bug where the redis health checker ignored the upstream auth password. +* regex: introduce new :ref:`RegexMatcher ` type that + provides a safe regex implementation for untrusted user input. This type is now used in all + configuration that processes user provided input. See :ref:`deprecated configuration details + ` for more information. * rbac: added conditions to the policy, see :ref:`condition `. * router: added :ref:`rq_retry_skipped_request_not_complete ` counter stat to router stats. +* router: :ref:`Scoped routing ` is supported. * router check tool: add coverage reporting & enforcement. * router check tool: add comprehensive coverage reporting. +* router check tool: add deprecated field check. +* router check tool: add flag for only printing results of failed tests. +* server: added a post initialization lifecycle event, in addition to the existing startup and shutdown events. +* thrift_proxy: fix crashing bug on invalid transport/protocol framing * tls: added verification of IP address SAN fields in certificates against configured SANs in the +* tracing: added support to the Zipkin reporter for sending list of spans as Zipkin JSON v2 and protobuf message over HTTP. certificate validation context. +* tracing: added tags for gRPC response status and meesage. +* tracing: added :ref:`max_path_tag_length ` to support customizing the length of the request path included in the extracted `http.url ` tag. +* upstream: added :ref:`an option ` that allows draining HTTP, TCP connection pools on cluster membership change. * upstream: added network filter chains to upstream connections, see :ref:`filters`. +* upstream: added new :ref:`failure-percentage based outlier detection` mode. * upstream: use p2c to select hosts for least-requests load balancers if all host weights are the same, even in cases where weights are not equal to 1. +* upstream: added :ref:`fail_traffic_on_panic ` to allow failing all requests to a cluster during panic state. * zookeeper: parse responses and emit latency stats. 1.11.1 (August 13, 2019) diff --git a/examples/redis/envoy.yaml b/examples/redis/envoy.yaml index bdf76fefe4..b32dd7d89c 100644 --- a/examples/redis/envoy.yaml +++ b/examples/redis/envoy.yaml @@ -11,9 +11,11 @@ static_resources: typed_config: "@type": type.googleapis.com/envoy.config.filter.network.redis_proxy.v2.RedisProxy stat_prefix: egress_redis - cluster: redis_cluster settings: op_timeout: 5s + prefix_routes: + catch_all_route: + cluster: redis_cluster clusters: - name: redis_cluster connect_timeout: 1s diff --git a/include/envoy/api/io_error.h b/include/envoy/api/io_error.h index 70699e7443..247e102a49 100644 --- a/include/envoy/api/io_error.h +++ b/include/envoy/api/io_error.h @@ -31,6 +31,8 @@ class IoError { Interrupt, // Requested a nonexistent interface or a non-local source address. AddressNotAvailable, + // Bad file descriptor. + BadFd, // Other error codes cannot be mapped to any one above in getErrorCode(). UnknownError }; diff --git a/include/envoy/common/BUILD b/include/envoy/common/BUILD index 4e04008c25..105f8b4374 100644 --- a/include/envoy/common/BUILD +++ b/include/envoy/common/BUILD @@ -29,6 +29,19 @@ envoy_cc_library( hdrs = ["time.h"], ) +envoy_cc_library( + name = "matchers_interface", + hdrs = ["matchers.h"], +) + +envoy_cc_library( + name = "regex_interface", + hdrs = ["regex.h"], + deps = [ + ":matchers_interface", + ], +) + envoy_cc_library( name = "token_bucket_interface", hdrs = ["token_bucket.h"], diff --git a/include/envoy/common/matchers.h b/include/envoy/common/matchers.h new file mode 100644 index 0000000000..4a79d00b97 --- /dev/null +++ b/include/envoy/common/matchers.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "envoy/common/pure.h" + +#include "absl/strings/string_view.h" + +namespace Envoy { +namespace Matchers { + +/** + * Generic string matching interface. + */ +class StringMatcher { +public: + virtual ~StringMatcher() = default; + + /** + * Return whether a passed string value matches. + */ + virtual bool match(const absl::string_view value) const PURE; +}; + +using StringMatcherPtr = std::unique_ptr; + +} // namespace Matchers +} // namespace Envoy diff --git a/include/envoy/common/regex.h b/include/envoy/common/regex.h new file mode 100644 index 0000000000..f4cdc1699e --- /dev/null +++ b/include/envoy/common/regex.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "envoy/common/matchers.h" + +namespace Envoy { +namespace Regex { + +/** + * A compiled regex expression matcher which uses an abstract regex engine. + * + * NOTE: Currently this is the same as StringMatcher, however has been split out as in the future + * we are likely to add other methods such as returning captures, etc. + */ +class CompiledMatcher : public Matchers::StringMatcher {}; + +using CompiledMatcherPtr = std::unique_ptr; + +} // namespace Regex +} // namespace Envoy diff --git a/include/envoy/event/dispatcher.h b/include/envoy/event/dispatcher.h index 8c3a8b8a88..a29f7e9005 100644 --- a/include/envoy/event/dispatcher.h +++ b/include/envoy/event/dispatcher.h @@ -59,7 +59,7 @@ class Dispatcher { virtual TimeSource& timeSource() PURE; /** - * Initialize stats for this dispatcher. Note that this can't generally be done at construction + * Initializes stats for this dispatcher. Note that this can't generally be done at construction * time, since the main and worker thread dispatchers are constructed before * ThreadLocalStoreImpl::initializeThreading. * @param scope the scope to contain the new per-dispatcher stats created here. @@ -68,12 +68,12 @@ class Dispatcher { virtual void initializeStats(Stats::Scope& scope, const std::string& prefix) PURE; /** - * Clear any items in the deferred deletion queue. + * Clears any items in the deferred deletion queue. */ virtual void clearDeferredDeleteList() PURE; /** - * Create a server connection. + * Wraps an already-accepted socket in an instance of Envoy's server Network::Connection. * @param socket supplies an open file descriptor and connection metadata to use for the * connection. Takes ownership of the socket. * @param transport_socket supplies a transport socket to be used by the connection. @@ -84,7 +84,8 @@ class Dispatcher { Network::TransportSocketPtr&& transport_socket) PURE; /** - * Create a client connection. + * Creates an instance of Envoy's Network::ClientConnection. Does NOT initiate the connection; + * the caller must then call connect() on the returned Network::ClientConnection. * @param address supplies the address to connect to. * @param source_address supplies an address to bind to or nullptr if no bind is necessary. * @param transport_socket supplies a transport socket to be used by the connection. @@ -99,7 +100,7 @@ class Dispatcher { const Network::ConnectionSocket::OptionsSharedPtr& options) PURE; /** - * Create an async DNS resolver. The resolver should only be used on the thread that runs this + * Creates an async DNS resolver. The resolver should only be used on the thread that runs this * dispatcher. * @param resolvers supplies the addresses of DNS resolvers that this resolver should use. If left * empty, it will not use any specific resolvers, but use defaults (/etc/resolv.conf) @@ -109,7 +110,7 @@ class Dispatcher { createDnsResolver(const std::vector& resolvers) PURE; /** - * Create a file event that will signal when a file is readable or writable. On UNIX systems this + * Creates a file event that will signal when a file is readable or writable. On UNIX systems this * can be used for any file like interface (files, sockets, etc.). * @param fd supplies the fd to watch. * @param cb supplies the callback to fire when the file is ready. @@ -126,7 +127,7 @@ class Dispatcher { virtual Filesystem::WatcherPtr createFilesystemWatcher() PURE; /** - * Create a listener on a specific port. + * Creates a listener on a specific port. * @param socket supplies the socket to listen on. * @param cb supplies the callbacks to invoke for listener events. * @param bind_to_port controls whether the listener binds to a transport port or not. @@ -139,31 +140,31 @@ class Dispatcher { bool hand_off_restored_destination_connections) PURE; /** - * Create a logical udp listener on a specific port. + * Creates a logical udp listener on a specific port. * @param socket supplies the socket to listen on. * @param cb supplies the udp listener callbacks to invoke for listener events. * @return Network::ListenerPtr a new listener that is owned by the caller. */ - virtual Network::ListenerPtr createUdpListener(Network::Socket& socket, - Network::UdpListenerCallbacks& cb) PURE; + virtual Network::UdpListenerPtr createUdpListener(Network::Socket& socket, + Network::UdpListenerCallbacks& cb) PURE; /** - * Allocate a timer. @see Timer for docs on how to use the timer. + * Allocates a timer. @see Timer for docs on how to use the timer. * @param cb supplies the callback to invoke when the timer fires. */ virtual Event::TimerPtr createTimer(TimerCb cb) PURE; /** - * Submit an item for deferred delete. @see DeferredDeletable. + * Submits an item for deferred delete. @see DeferredDeletable. */ virtual void deferredDelete(DeferredDeletablePtr&& to_delete) PURE; /** - * Exit the event loop. + * Exits the event loop. */ virtual void exit() PURE; /** - * Listen for a signal event. Only a single dispatcher in the process can listen for signals. + * Listens for a signal event. Only a single dispatcher in the process can listen for signals. * If more than one dispatcher calls this routine in the process the behavior is undefined. * * @param signal_num supplies the signal to listen on. @@ -173,13 +174,13 @@ class Dispatcher { virtual SignalEventPtr listenForSignal(int signal_num, SignalCb cb) PURE; /** - * Post a functor to the dispatcher. This is safe cross thread. The functor runs in the context + * Posts a functor to the dispatcher. This is safe cross thread. The functor runs in the context * of the dispatcher event loop which may be on a different thread than the caller. */ virtual void post(PostCb callback) PURE; /** - * Run the event loop. This will not return until exit() is called either from within a callback + * Runs the event loop. This will not return until exit() is called either from within a callback * or from a different thread. * @param type specifies whether to run in blocking mode (run() will not return until exit() is * called) or non-blocking mode where only active events will be executed and then diff --git a/include/envoy/event/timer.h b/include/envoy/event/timer.h index 3a6771904c..c2255e8dec 100644 --- a/include/envoy/event/timer.h +++ b/include/envoy/event/timer.h @@ -8,8 +8,13 @@ #include "envoy/common/time.h" namespace Envoy { + +class ScopeTrackedObject; + namespace Event { +class Dispatcher; + /** * Callback invoked when a timer event fires. */ @@ -30,8 +35,12 @@ class Timer { /** * Enable a pending timeout. If a timeout is already pending, it will be reset to the new timeout. + * + * @param ms supplies the duration of the alarm in milliseconds. + * @param object supplies an optional scope for the duration of the alarm. */ - virtual void enableTimer(const std::chrono::milliseconds& d) PURE; + virtual void enableTimer(const std::chrono::milliseconds& ms, + const ScopeTrackedObject* object = nullptr) PURE; /** * Return whether the timer is currently armed. @@ -48,7 +57,7 @@ class Scheduler { /** * Creates a timer. */ - virtual TimerPtr createTimer(const TimerCb& cb) PURE; + virtual TimerPtr createTimer(const TimerCb& cb, Dispatcher& dispatcher) PURE; }; using SchedulerPtr = std::unique_ptr; diff --git a/include/envoy/http/conn_pool.h b/include/envoy/http/conn_pool.h index cb2d6a8607..8ee9270e6d 100644 --- a/include/envoy/http/conn_pool.h +++ b/include/envoy/http/conn_pool.h @@ -58,9 +58,11 @@ class Callbacks { * @param encoder supplies the request encoder to use. * @param host supplies the description of the host that will carry the request. For logical * connection pools the description may be different each time this is called. + * @param info supplies the stream info object associated with the upstream connection. */ virtual void onPoolReady(Http::StreamEncoder& encoder, - Upstream::HostDescriptionConstSharedPtr host) PURE; + Upstream::HostDescriptionConstSharedPtr host, + const StreamInfo::StreamInfo& info) PURE; }; /** diff --git a/include/envoy/network/connection.h b/include/envoy/network/connection.h index 0eeaa2855d..f97391bf5e 100644 --- a/include/envoy/network/connection.h +++ b/include/envoy/network/connection.h @@ -215,7 +215,7 @@ class Connection : public Event::DeferredDeletable, public FilterManager { * @return the const SSL connection data if this is an SSL connection, or nullptr if it is not. */ // TODO(snowp): Remove this in favor of StreamInfo::downstreamSslConnection. - virtual const Ssl::ConnectionInfo* ssl() const PURE; + virtual Ssl::ConnectionInfoConstSharedPtr ssl() const PURE; /** * @return requested server name (e.g. SNI in TLS), if any. diff --git a/include/envoy/network/connection_handler.h b/include/envoy/network/connection_handler.h index 9a36aed1cc..3ea5df65d8 100644 --- a/include/envoy/network/connection_handler.h +++ b/include/envoy/network/connection_handler.h @@ -9,6 +9,8 @@ #include "envoy/network/listener.h" #include "envoy/ssl/context.h" +#include "spdlog/spdlog.h" + namespace Envoy { namespace Network { @@ -24,6 +26,19 @@ class ConnectionHandler { */ virtual uint64_t numConnections() PURE; + /** + * Increment the return value of numConnections() by one. + * TODO(mattklein123): re-visit the connection accounting interface. Make TCP + * listener to do accounting through these interfaces instead of directly + * access the counter. + */ + virtual void incNumConnections() PURE; + + /** + * Decrement the return value of numConnections() by one. + */ + virtual void decNumConnections() PURE; + /** * Adds a listener to the handler. * @param config listener configuration options. @@ -68,9 +83,59 @@ class ConnectionHandler { * after they have been temporarily disabled. */ virtual void enableListeners() PURE; + + /** + * Used by ConnectionHandler to manage listeners. + */ + class ActiveListener { + public: + virtual ~ActiveListener() = default; + + /** + * @return the tag value as configured. + */ + virtual uint64_t listenerTag() PURE; + /** + * @return the actual Listener object. + */ + virtual Listener* listener() PURE; + /** + * Destroy the actual Listener it wraps. + */ + virtual void destroy() PURE; + }; + + using ActiveListenerPtr = std::unique_ptr; }; using ConnectionHandlerPtr = std::unique_ptr; +/** + * A registered factory interface to create different kinds of + * ActiveUdpListener. + */ +class ActiveUdpListenerFactory { +public: + virtual ~ActiveUdpListenerFactory() = default; + + /** + * Creates an ActiveUdpListener object and a corresponding UdpListener + * according to given config. + * @param parent is the owner of the created ActiveListener objects. + * @param dispatcher is used to create actual UDP listener. + * @param logger might not need to be passed in. + * TODO(danzh): investigate if possible to use statically defined logger in ActiveUdpListener + * implementation instead. + * @param config provides information needed to create ActiveUdpListener and + * UdpListener objects. + * @return the ActiveUdpListener created. + */ + virtual ConnectionHandler::ActiveListenerPtr + createActiveUdpListener(ConnectionHandler& parent, Event::Dispatcher& disptacher, + spdlog::logger& logger, Network::ListenerConfig& config) const PURE; +}; + +using ActiveUdpListenerFactoryPtr = std::unique_ptr; + } // namespace Network } // namespace Envoy diff --git a/include/envoy/network/listener.h b/include/envoy/network/listener.h index 53b06b01be..2c1a1e4f16 100644 --- a/include/envoy/network/listener.h +++ b/include/envoy/network/listener.h @@ -14,6 +14,7 @@ namespace Envoy { namespace Network { class UdpListenerFilterManager; +class ActiveUdpListenerFactory; /** * A configuration for an individual listener. @@ -90,6 +91,12 @@ class ListenerConfig { * @return const std::string& the listener's name. */ virtual const std::string& name() const PURE; + + /** + * @return factory pointer if listening on UDP socket, otherwise return + * nullptr. + */ + virtual const ActiveUdpListenerFactory* udpListenerFactory() PURE; }; /** @@ -235,6 +242,8 @@ class UdpListener : public virtual Listener { virtual Api::IoCallUint64Result send(const UdpSendData& data) PURE; }; +using UdpListenerPtr = std::unique_ptr; + /** * Thrown when there is a runtime error creating/binding a listener. */ diff --git a/include/envoy/network/transport_socket.h b/include/envoy/network/transport_socket.h index 1167f46e19..a04b711c01 100644 --- a/include/envoy/network/transport_socket.h +++ b/include/envoy/network/transport_socket.h @@ -144,7 +144,7 @@ class TransportSocket { /** * @return the const SSL connection data if this is an SSL connection, or nullptr if it is not. */ - virtual const Ssl::ConnectionInfo* ssl() const PURE; + virtual Ssl::ConnectionInfoConstSharedPtr ssl() const PURE; }; using TransportSocketPtr = std::unique_ptr; diff --git a/include/envoy/router/BUILD b/include/envoy/router/BUILD index 3ebe1a1b70..d620540446 100644 --- a/include/envoy/router/BUILD +++ b/include/envoy/router/BUILD @@ -48,6 +48,7 @@ envoy_cc_library( external_deps = ["abseil_optional"], deps = [ "//include/envoy/access_log:access_log_interface", + "//include/envoy/common:matchers_interface", "//include/envoy/config:typed_metadata_interface", "//include/envoy/http:codec_interface", "//include/envoy/http:codes_interface", diff --git a/include/envoy/router/rds.h b/include/envoy/router/rds.h index 9dcd4c3f64..456e449220 100644 --- a/include/envoy/router/rds.h +++ b/include/envoy/router/rds.h @@ -48,6 +48,11 @@ class RouteConfigProvider { * Callback used to notify RouteConfigProvider about configuration changes. */ virtual void onConfigUpdate() PURE; + + /** + * Validate if the route configuration can be applied to the context of the route config provider. + */ + virtual void validateConfig(const envoy::api::v2::RouteConfiguration& config) const PURE; }; using RouteConfigProviderPtr = std::unique_ptr; diff --git a/include/envoy/router/route_config_provider_manager.h b/include/envoy/router/route_config_provider_manager.h index ffd9922bd1..9912aa4603 100644 --- a/include/envoy/router/route_config_provider_manager.h +++ b/include/envoy/router/route_config_provider_manager.h @@ -33,10 +33,13 @@ class RouteConfigProviderManager { * @param rds supplies the proto configuration of an RDS-configured RouteConfigProvider. * @param factory_context is the context to use for the route config provider. * @param stat_prefix supplies the stat_prefix to use for the provider stats. + * @param init_manager the Init::Manager used to coordinate initialization of a the underlying RDS + * subscription. */ virtual RouteConfigProviderPtr createRdsRouteConfigProvider( const envoy::config::filter::network::http_connection_manager::v2::Rds& rds, - Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix) PURE; + Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix, + Init::Manager& init_manager) PURE; /** * Get a RouteConfigSharedPtr for a statically defined route. Ownership is as described for diff --git a/include/envoy/router/route_config_update_receiver.h b/include/envoy/router/route_config_update_receiver.h index 8ac284fae6..6e4d492b16 100644 --- a/include/envoy/router/route_config_update_receiver.h +++ b/include/envoy/router/route_config_update_receiver.h @@ -28,6 +28,7 @@ class RouteConfigUpdateReceiver { */ virtual bool onRdsUpdate(const envoy::api::v2::RouteConfiguration& rc, const std::string& version_info) PURE; + /** * Called on updates via VHDS. * @param added_resources supplies Resources (each containing a VirtualHost) that have been diff --git a/include/envoy/router/router.h b/include/envoy/router/router.h index a14864b4b2..5b793ec8a8 100644 --- a/include/envoy/router/router.h +++ b/include/envoy/router/router.h @@ -10,6 +10,7 @@ #include "envoy/access_log/access_log.h" #include "envoy/api/v2/core/base.pb.h" +#include "envoy/common/matchers.h" #include "envoy/config/typed_metadata.h" #include "envoy/http/codec.h" #include "envoy/http/codes.h" @@ -101,14 +102,9 @@ class CorsPolicy { virtual ~CorsPolicy() = default; /** - * @return std::list& access-control-allow-origin values. + * @return std::vector& access-control-allow-origin matchers. */ - virtual const std::list& allowOrigins() const PURE; - - /* - * @return std::list& regexes that match allowed origins. - */ - virtual const std::list& allowOriginRegexes() const PURE; + virtual const std::vector& allowOrigins() const PURE; /** * @return std::string access-control-allow-methods value. diff --git a/include/envoy/server/BUILD b/include/envoy/server/BUILD index f88cc3c78e..ef8865af12 100644 --- a/include/envoy/server/BUILD +++ b/include/envoy/server/BUILD @@ -234,6 +234,7 @@ envoy_cc_library( ":resource_monitor_interface", "//include/envoy/api:api_interface", "//include/envoy/event:dispatcher_interface", + "//include/envoy/protobuf:message_validator_interface", ], ) @@ -256,6 +257,12 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "active_udp_listener_config_interface", + hdrs = ["active_udp_listener_config.h"], + deps = ["//include/envoy/network:connection_handler_interface"], +) + envoy_cc_library( name = "wasm_interface", hdrs = ["wasm.h"], diff --git a/include/envoy/server/active_udp_listener_config.h b/include/envoy/server/active_udp_listener_config.h new file mode 100644 index 0000000000..e17c314d60 --- /dev/null +++ b/include/envoy/server/active_udp_listener_config.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/network/connection_handler.h" + +#include "common/protobuf/protobuf.h" + +namespace Envoy { +namespace Server { + +/** + * Interface to create udp listener according to + * envoy::api::v2::listener::UdpListenerConfig.udp_listener_name. + */ +class ActiveUdpListenerConfigFactory { +public: + virtual ~ActiveUdpListenerConfigFactory() = default; + + virtual ProtobufTypes::MessagePtr createEmptyConfigProto() PURE; + + /** + * Create an ActiveUdpListenerFactory object according to given message. + */ + virtual Network::ActiveUdpListenerFactoryPtr + createActiveUdpListenerFactory(const Protobuf::Message& message) PURE; + + /** + * Used to identify which udp listener to create: quic or raw udp. + */ + virtual std::string name() PURE; +}; + +} // namespace Server +} // namespace Envoy diff --git a/include/envoy/server/filter_config.h b/include/envoy/server/filter_config.h index 8cd1b40583..a6286ef464 100644 --- a/include/envoy/server/filter_config.h +++ b/include/envoy/server/filter_config.h @@ -180,9 +180,10 @@ class FactoryContext : public virtual CommonFactoryContext { virtual Grpc::Context& grpcContext() PURE; /** - * @return ProcessContext& a reference to the process context. + * @return absl::optional> an optional reference to the + * process context. Will be unset when running in validation mode. */ - virtual ProcessContext& processContext() PURE; + virtual absl::optional> processContext() PURE; }; class ListenerFactoryContext : public virtual FactoryContext { @@ -269,11 +270,14 @@ class ProtocolOptionsFactory { * implementation is unable to produce a factory with the provided parameters, it should throw an * EnvoyException. * @param config supplies the protobuf configuration for the filter + * @param validation_visitor message validation visitor instance. * @return Upstream::ProtocolOptionsConfigConstSharedPtr the protocol options */ virtual Upstream::ProtocolOptionsConfigConstSharedPtr - createProtocolOptionsConfig(const Protobuf::Message& config) { + createProtocolOptionsConfig(const Protobuf::Message& config, + ProtobufMessage::ValidationVisitor& validation_visitor) { UNREFERENCED_PARAMETER(config); + UNREFERENCED_PARAMETER(validation_visitor); return nullptr; } diff --git a/include/envoy/server/health_checker_config.h b/include/envoy/server/health_checker_config.h index 163a1d7c28..638a0362ad 100644 --- a/include/envoy/server/health_checker_config.h +++ b/include/envoy/server/health_checker_config.h @@ -43,6 +43,11 @@ class HealthCheckerFactoryContext { * messages. */ virtual ProtobufMessage::ValidationVisitor& messageValidationVisitor() PURE; + + /** + * @return Api::Api& the API used by the server. + */ + virtual Api::Api& api() PURE; }; /** diff --git a/include/envoy/server/instance.h b/include/envoy/server/instance.h index d87e72f844..d440c8cf4c 100644 --- a/include/envoy/server/instance.h +++ b/include/envoy/server/instance.h @@ -196,7 +196,7 @@ class Instance { /** * @return the server-wide process context. */ - virtual ProcessContext& processContext() PURE; + virtual absl::optional> processContext() PURE; /** * @return ThreadLocal::Instance& the thread local storage engine for the server. This is used to diff --git a/include/envoy/server/lifecycle_notifier.h b/include/envoy/server/lifecycle_notifier.h index dbda0e2e00..2358fb824b 100644 --- a/include/envoy/server/lifecycle_notifier.h +++ b/include/envoy/server/lifecycle_notifier.h @@ -21,6 +21,11 @@ class ServerLifecycleNotifier { */ Startup, + /** + * The server instance init manager has finished initialization. + */ + PostInit, + /** * The server instance is being shutdown and the dispatcher is about to exit. * This provides listeners a last chance to run a callback on the main dispatcher. diff --git a/include/envoy/server/options.h b/include/envoy/server/options.h index 8ecc14f0c5..32b2773217 100644 --- a/include/envoy/server/options.h +++ b/include/envoy/server/options.h @@ -184,6 +184,11 @@ class Options { */ virtual bool libeventBufferEnabled() const PURE; + /** + * @return whether to use the fake symbol table implementation. + */ + virtual bool fakeSymbolTableEnabled() const PURE; + /** * @return bool indicating whether cpuset size should determine the number of worker threads. */ diff --git a/include/envoy/server/resource_monitor_config.h b/include/envoy/server/resource_monitor_config.h index aef576b8e4..db7cc786a1 100644 --- a/include/envoy/server/resource_monitor_config.h +++ b/include/envoy/server/resource_monitor_config.h @@ -3,6 +3,7 @@ #include "envoy/api/api.h" #include "envoy/common/pure.h" #include "envoy/event/dispatcher.h" +#include "envoy/protobuf/message_validator.h" #include "envoy/server/resource_monitor.h" #include "common/protobuf/protobuf.h" @@ -25,6 +26,12 @@ class ResourceMonitorFactoryContext { * @return reference to the Api object */ virtual Api::Api& api() PURE; + + /** + * @return ProtobufMessage::ValidationVisitor& validation visitor for filter configuration + * messages. + */ + virtual ProtobufMessage::ValidationVisitor& messageValidationVisitor() PURE; }; /** diff --git a/include/envoy/ssl/BUILD b/include/envoy/ssl/BUILD index 73373af984..8ea81a6e90 100644 --- a/include/envoy/ssl/BUILD +++ b/include/envoy/ssl/BUILD @@ -48,6 +48,9 @@ envoy_cc_library( envoy_cc_library( name = "tls_certificate_config_interface", hdrs = ["tls_certificate_config.h"], + deps = [ + "//include/envoy/ssl/private_key:private_key_interface", + ], ) envoy_cc_library( diff --git a/include/envoy/ssl/connection.h b/include/envoy/ssl/connection.h index 203bd9a4c8..d586d9fe09 100644 --- a/include/envoy/ssl/connection.h +++ b/include/envoy/ssl/connection.h @@ -33,7 +33,7 @@ class ConnectionInfo { * @return std::string the subject field of the local certificate in RFC 2253 format. Returns "" * if there is no local certificate, or no subject. **/ - virtual std::string subjectLocalCertificate() const PURE; + virtual const std::string& subjectLocalCertificate() const PURE; /** * @return std::string the SHA256 digest of the peer certificate. Returns "" if there is no peer @@ -45,19 +45,19 @@ class ConnectionInfo { * @return std::string the serial number field of the peer certificate. Returns "" if * there is no peer certificate, or no serial number. **/ - virtual std::string serialNumberPeerCertificate() const PURE; + virtual const std::string& serialNumberPeerCertificate() const PURE; /** * @return std::string the issuer field of the peer certificate in RFC 2253 format. Returns "" if * there is no peer certificate, or no issuer. **/ - virtual std::string issuerPeerCertificate() const PURE; + virtual const std::string& issuerPeerCertificate() const PURE; /** * @return std::string the subject field of the peer certificate in RFC 2253 format. Returns "" if * there is no peer certificate, or no subject. **/ - virtual std::string subjectPeerCertificate() const PURE; + virtual const std::string& subjectPeerCertificate() const PURE; /** * @return std::string the URIs in the SAN field of the peer certificate. Returns {} if there is @@ -105,7 +105,7 @@ class ConnectionInfo { /** * @return std::string the hex-encoded TLS session ID as defined in rfc5246. **/ - virtual std::string sessionId() const PURE; + virtual const std::string& sessionId() const PURE; /** * @return uint16_t the standard ID for the ciphers used in the established TLS connection. @@ -123,8 +123,10 @@ class ConnectionInfo { * @return std::string the TLS version (e.g., TLSv1.2, TLSv1.3) used in the established TLS * connection. **/ - virtual std::string tlsVersion() const PURE; + virtual const std::string& tlsVersion() const PURE; }; +using ConnectionInfoConstSharedPtr = std::shared_ptr; + } // namespace Ssl } // namespace Envoy diff --git a/include/envoy/ssl/context_manager.h b/include/envoy/ssl/context_manager.h index 7358b7745b..bb0c104e52 100644 --- a/include/envoy/ssl/context_manager.h +++ b/include/envoy/ssl/context_manager.h @@ -5,6 +5,7 @@ #include "envoy/common/time.h" #include "envoy/ssl/context.h" #include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" #include "envoy/stats/scope.h" namespace Envoy { @@ -39,6 +40,12 @@ class ContextManager { * Iterate through all currently allocated contexts. */ virtual void iterateContexts(std::function callback) PURE; + + /** + * Access the private key operations manager, which is part of SSL + * context manager. + */ + virtual PrivateKeyMethodManager& privateKeyMethodManager() PURE; }; using ContextManagerPtr = std::unique_ptr; diff --git a/include/envoy/ssl/private_key/BUILD b/include/envoy/ssl/private_key/BUILD new file mode 100644 index 0000000000..4bb651d1f8 --- /dev/null +++ b/include/envoy/ssl/private_key/BUILD @@ -0,0 +1,35 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "private_key_interface", + hdrs = ["private_key.h"], + external_deps = ["ssl"], + deps = [ + ":private_key_callbacks_interface", + "//include/envoy/event:dispatcher_interface", + "@envoy_api//envoy/api/v2/auth:cert_cc", + ], +) + +envoy_cc_library( + name = "private_key_config_interface", + hdrs = ["private_key_config.h"], + deps = [ + ":private_key_interface", + "//include/envoy/registry", + ], +) + +envoy_cc_library( + name = "private_key_callbacks_interface", + hdrs = ["private_key_callbacks.h"], + external_deps = ["ssl"], +) diff --git a/include/envoy/ssl/private_key/private_key.h b/include/envoy/ssl/private_key/private_key.h new file mode 100644 index 0000000000..e972d608cd --- /dev/null +++ b/include/envoy/ssl/private_key/private_key.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/common/pure.h" +#include "envoy/event/dispatcher.h" +#include "envoy/ssl/private_key/private_key_callbacks.h" + +#include "openssl/ssl.h" + +namespace Envoy { +namespace Server { +namespace Configuration { +// Prevent a dependency loop with the forward declaration. +class TransportSocketFactoryContext; +} // namespace Configuration +} // namespace Server + +namespace Ssl { + +using BoringSslPrivateKeyMethodSharedPtr = std::shared_ptr; + +class PrivateKeyMethodProvider { +public: + virtual ~PrivateKeyMethodProvider() = default; + + /** + * Register an SSL connection to private key operations by the provider. + * @param ssl a SSL connection object. + * @param cb a callbacks object, whose "complete" method will be invoked + * when the asynchronous processing is complete. + * @param dispatcher supplies the owning thread's dispatcher. + */ + virtual void registerPrivateKeyMethod(SSL* ssl, PrivateKeyConnectionCallbacks& cb, + Event::Dispatcher& dispatcher) PURE; + + /** + * Unregister an SSL connection from private key operations by the provider. + * @param ssl a SSL connection object. + * @throw EnvoyException if registration fails. + */ + virtual void unregisterPrivateKeyMethod(SSL* ssl) PURE; + + /** + * Check whether the private key method satisfies FIPS requirements. + * @return true if FIPS key requirements are satisfied, false if not. + */ + virtual bool checkFips() PURE; + + /** + * Get the private key methods from the provider. + * @return the private key methods associated with this provider and + * configuration. + */ + virtual BoringSslPrivateKeyMethodSharedPtr getBoringSslPrivateKeyMethod() PURE; +}; + +using PrivateKeyMethodProviderSharedPtr = std::shared_ptr; + +/** + * A manager for finding correct user-provided functions for handling BoringSSL private key + * operations. + */ +class PrivateKeyMethodManager { +public: + virtual ~PrivateKeyMethodManager() = default; + + /** + * Finds and returns a private key operations provider for BoringSSL. + * + * @param config a protobuf message object containing a PrivateKeyProvider message. + * @param factory_context context that provides components for creating and + * initializing connections using asynchronous private key operations. + * @return PrivateKeyMethodProvider the private key operations provider, or nullptr if + * no provider can be used with the context configuration. + */ + virtual PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProvider( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Envoy::Server::Configuration::TransportSocketFactoryContext& factory_context) PURE; +}; + +} // namespace Ssl +} // namespace Envoy diff --git a/include/envoy/ssl/private_key/private_key_callbacks.h b/include/envoy/ssl/private_key/private_key_callbacks.h new file mode 100644 index 0000000000..1f370fda94 --- /dev/null +++ b/include/envoy/ssl/private_key/private_key_callbacks.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include "envoy/common/pure.h" + +namespace Envoy { +namespace Ssl { + +class PrivateKeyConnectionCallbacks { +public: + virtual ~PrivateKeyConnectionCallbacks() = default; + + /** + * Callback function which is called when the asynchronous private key + * operation has been completed (with either success or failure). The + * provider will communicate the success status when SSL_do_handshake() + * is called the next time. + */ + virtual void onPrivateKeyMethodComplete() PURE; +}; + +} // namespace Ssl +} // namespace Envoy diff --git a/include/envoy/ssl/private_key/private_key_config.h b/include/envoy/ssl/private_key/private_key_config.h new file mode 100644 index 0000000000..8a5da737ca --- /dev/null +++ b/include/envoy/ssl/private_key/private_key_config.h @@ -0,0 +1,22 @@ +#pragma once + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/registry/registry.h" +#include "envoy/ssl/private_key/private_key.h" + +namespace Envoy { +namespace Ssl { + +// Base class which the private key operation provider implementations can register. + +class PrivateKeyMethodProviderInstanceFactory { +public: + virtual ~PrivateKeyMethodProviderInstanceFactory() = default; + virtual PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProviderInstance( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) PURE; + virtual std::string name() const PURE; +}; + +} // namespace Ssl +} // namespace Envoy diff --git a/include/envoy/ssl/tls_certificate_config.h b/include/envoy/ssl/tls_certificate_config.h index f934e5654a..882d40fe13 100644 --- a/include/envoy/ssl/tls_certificate_config.h +++ b/include/envoy/ssl/tls_certificate_config.h @@ -4,6 +4,7 @@ #include #include "envoy/common/pure.h" +#include "envoy/ssl/private_key/private_key.h" namespace Envoy { namespace Ssl { @@ -34,6 +35,11 @@ class TlsCertificateConfig { */ virtual const std::string& privateKeyPath() const PURE; + /** + * @return private key method provider. + */ + virtual Envoy::Ssl::PrivateKeyMethodProviderSharedPtr privateKeyMethod() const PURE; + /** * @return a string of password. */ diff --git a/include/envoy/stats/symbol_table.h b/include/envoy/stats/symbol_table.h index 8f15df4534..a4346d6939 100644 --- a/include/envoy/stats/symbol_table.h +++ b/include/envoy/stats/symbol_table.h @@ -185,7 +185,7 @@ class SymbolTable { virtual StoragePtr encode(absl::string_view name) PURE; }; -using SharedSymbolTable = std::shared_ptr; +using SymbolTablePtr = std::unique_ptr; } // namespace Stats } // namespace Envoy diff --git a/include/envoy/stream_info/stream_info.h b/include/envoy/stream_info/stream_info.h index 1558503ccf..786c1aa8d4 100644 --- a/include/envoy/stream_info/stream_info.h +++ b/include/envoy/stream_info/stream_info.h @@ -109,6 +109,8 @@ struct ResponseCodeDetailValues { // The request was rejected because it attempted an unsupported upgrade. const std::string UpgradeFailed = "upgrade_failed"; + // The request was rejected by the HCM because there was no route configuration found. + const std::string RouteConfigurationNotFound = "route_configuration_not_found"; // The request was rejected by the router filter because there was no route found. const std::string RouteNotFound = "route_not_found"; // A direct response was generated by the router filter. @@ -422,13 +424,26 @@ class StreamInfo { /** * @param connection_info sets the downstream ssl connection. */ - virtual void setDownstreamSslConnection(const Ssl::ConnectionInfo* ssl_connection_info) PURE; + virtual void + setDownstreamSslConnection(const Ssl::ConnectionInfoConstSharedPtr& ssl_connection_info) PURE; /** * @return the downstream SSL connection. This will be nullptr if the downstream * connection does not use SSL. */ - virtual const Ssl::ConnectionInfo* downstreamSslConnection() const PURE; + virtual Ssl::ConnectionInfoConstSharedPtr downstreamSslConnection() const PURE; + + /** + * @param connection_info sets the upstream ssl connection. + */ + virtual void + setUpstreamSslConnection(const Ssl::ConnectionInfoConstSharedPtr& ssl_connection_info) PURE; + + /** + * @return the upstream SSL connection. This will be nullptr if the upstream + * connection does not use SSL. + */ + virtual Ssl::ConnectionInfoConstSharedPtr upstreamSslConnection() const PURE; /** * @return const Router::RouteEntry* Get the route entry selected for this request. Note: this diff --git a/include/envoy/thread/thread.h b/include/envoy/thread/thread.h index 8d630d8ac1..70452ca5d2 100644 --- a/include/envoy/thread/thread.h +++ b/include/envoy/thread/thread.h @@ -67,13 +67,13 @@ class ThreadFactory { * Like the C++11 "basic lockable concept" but a pure virtual interface vs. a template, and * with thread annotations. */ -class LOCKABLE BasicLockable { +class ABSL_LOCKABLE BasicLockable { public: virtual ~BasicLockable() = default; - virtual void lock() EXCLUSIVE_LOCK_FUNCTION() PURE; - virtual bool tryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true) PURE; - virtual void unlock() UNLOCK_FUNCTION() PURE; + virtual void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() PURE; + virtual bool tryLock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) PURE; + virtual void unlock() ABSL_UNLOCK_FUNCTION() PURE; }; } // namespace Thread diff --git a/include/envoy/thread_local/thread_local.h b/include/envoy/thread_local/thread_local.h index 6f082a4607..41c77d730d 100644 --- a/include/envoy/thread_local/thread_local.h +++ b/include/envoy/thread_local/thread_local.h @@ -74,6 +74,17 @@ class Slot { */ using InitializeCb = std::function; virtual void set(InitializeCb cb) PURE; + + /** + * UpdateCb takes the current stored data, and returns an updated/new version data. + * TLS will run the callback and replace the stored data with the returned value *in each thread*. + * + * NOTE: The update callback is not supposed to capture the Slot, or its owner. As the owner may + * be destructed in main thread before the update_cb gets called in a worker thread. + **/ + using UpdateCb = std::function; + virtual void runOnAllThreads(const UpdateCb& update_cb) PURE; + virtual void runOnAllThreads(const UpdateCb& update_cb, Event::PostCb complete_cb) PURE; }; using SlotPtr = std::unique_ptr; diff --git a/include/envoy/tracing/http_tracer.h b/include/envoy/tracing/http_tracer.h index eb6f4960b6..ec53d00e4b 100644 --- a/include/envoy/tracing/http_tracer.h +++ b/include/envoy/tracing/http_tracer.h @@ -11,6 +11,8 @@ namespace Envoy { namespace Tracing { +constexpr uint32_t DefaultMaxPathTagLength = 256; + enum class OperationName { Ingress, Egress }; /** @@ -58,6 +60,11 @@ class Config { * @return true if spans should be annotated with more detailed information. */ virtual bool verbose() const PURE; + + /** + * @return the maximum length allowed for paths in the extracted HttpUrl tag. + */ + virtual uint32_t maxPathTagLength() const PURE; }; class Span; diff --git a/include/envoy/upstream/retry.h b/include/envoy/upstream/retry.h index dd518eb13e..82928f661f 100644 --- a/include/envoy/upstream/retry.h +++ b/include/envoy/upstream/retry.h @@ -79,8 +79,10 @@ class RetryPriorityFactory { public: virtual ~RetryPriorityFactory() = default; - virtual RetryPrioritySharedPtr createRetryPriority(const Protobuf::Message& config, - uint32_t retry_count) PURE; + virtual RetryPrioritySharedPtr + createRetryPriority(const Protobuf::Message& config, + ProtobufMessage::ValidationVisitor& validation_visitor, + uint32_t retry_count) PURE; virtual std::string name() const PURE; diff --git a/source/common/access_log/access_log_formatter.cc b/source/common/access_log/access_log_formatter.cc index 899ae27ff8..295c544aa7 100644 --- a/source/common/access_log/access_log_formatter.cc +++ b/source/common/access_log/access_log_formatter.cc @@ -1,6 +1,7 @@ #include "common/access_log/access_log_formatter.h" #include +#include #include #include diff --git a/source/common/access_log/access_log_impl.cc b/source/common/access_log/access_log_impl.cc index f59c96d1b6..837842f621 100644 --- a/source/common/access_log/access_log_impl.cc +++ b/source/common/access_log/access_log_impl.cc @@ -53,7 +53,8 @@ bool ComparisonFilter::compareAgainstValue(uint64_t lhs) { FilterPtr FilterFactory::fromProto(const envoy::config::filter::accesslog::v2::AccessLogFilter& config, - Runtime::Loader& runtime, Runtime::RandomGenerator& random) { + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + ProtobufMessage::ValidationVisitor& validation_visitor) { switch (config.filter_specifier_case()) { case envoy::config::filter::accesslog::v2::AccessLogFilter::kStatusCodeFilter: return FilterPtr{new StatusCodeFilter(config.status_code_filter(), runtime)}; @@ -66,19 +67,19 @@ FilterFactory::fromProto(const envoy::config::filter::accesslog::v2::AccessLogFi case envoy::config::filter::accesslog::v2::AccessLogFilter::kRuntimeFilter: return FilterPtr{new RuntimeFilter(config.runtime_filter(), runtime, random)}; case envoy::config::filter::accesslog::v2::AccessLogFilter::kAndFilter: - return FilterPtr{new AndFilter(config.and_filter(), runtime, random)}; + return FilterPtr{new AndFilter(config.and_filter(), runtime, random, validation_visitor)}; case envoy::config::filter::accesslog::v2::AccessLogFilter::kOrFilter: - return FilterPtr{new OrFilter(config.or_filter(), runtime, random)}; + return FilterPtr{new OrFilter(config.or_filter(), runtime, random, validation_visitor)}; case envoy::config::filter::accesslog::v2::AccessLogFilter::kHeaderFilter: return FilterPtr{new HeaderFilter(config.header_filter())}; case envoy::config::filter::accesslog::v2::AccessLogFilter::kResponseFlagFilter: - MessageUtil::validate(config); + MessageUtil::validate(config, validation_visitor); return FilterPtr{new ResponseFlagFilter(config.response_flag_filter())}; case envoy::config::filter::accesslog::v2::AccessLogFilter::kGrpcStatusFilter: - MessageUtil::validate(config); + MessageUtil::validate(config, validation_visitor); return FilterPtr{new GrpcStatusFilter(config.grpc_status_filter())}; case envoy::config::filter::accesslog::v2::AccessLogFilter::kExtensionFilter: - MessageUtil::validate(config); + MessageUtil::validate(config, validation_visitor); { auto& factory = Config::Utility::getAndCheckFactory( config.extension_filter().name()); @@ -140,19 +141,22 @@ bool RuntimeFilter::evaluate(const StreamInfo::StreamInfo&, const Http::HeaderMa OperatorFilter::OperatorFilter(const Protobuf::RepeatedPtrField< envoy::config::filter::accesslog::v2::AccessLogFilter>& configs, - Runtime::Loader& runtime, Runtime::RandomGenerator& random) { + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + ProtobufMessage::ValidationVisitor& validation_visitor) { for (const auto& config : configs) { - filters_.emplace_back(FilterFactory::fromProto(config, runtime, random)); + filters_.emplace_back(FilterFactory::fromProto(config, runtime, random, validation_visitor)); } } OrFilter::OrFilter(const envoy::config::filter::accesslog::v2::OrFilter& config, - Runtime::Loader& runtime, Runtime::RandomGenerator& random) - : OperatorFilter(config.filters(), runtime, random) {} + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + ProtobufMessage::ValidationVisitor& validation_visitor) + : OperatorFilter(config.filters(), runtime, random, validation_visitor) {} AndFilter::AndFilter(const envoy::config::filter::accesslog::v2::AndFilter& config, - Runtime::Loader& runtime, Runtime::RandomGenerator& random) - : OperatorFilter(config.filters(), runtime, random) {} + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + ProtobufMessage::ValidationVisitor& validation_visitor) + : OperatorFilter(config.filters(), runtime, random, validation_visitor) {} bool OrFilter::evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers, const Http::HeaderMap& response_headers, @@ -189,13 +193,12 @@ bool NotHealthCheckFilter::evaluate(const StreamInfo::StreamInfo& info, const Ht return !info.healthCheck(); } -HeaderFilter::HeaderFilter(const envoy::config::filter::accesslog::v2::HeaderFilter& config) { - header_data_.push_back(Http::HeaderUtility::HeaderData(config.header())); -} +HeaderFilter::HeaderFilter(const envoy::config::filter::accesslog::v2::HeaderFilter& config) + : header_data_(std::make_unique(config.header())) {} bool HeaderFilter::evaluate(const StreamInfo::StreamInfo&, const Http::HeaderMap& request_headers, const Http::HeaderMap&, const Http::HeaderMap&) { - return Http::HeaderUtility::matchHeaders(request_headers, header_data_); + return Http::HeaderUtility::matchHeaders(request_headers, *header_data_); } ResponseFlagFilter::ResponseFlagFilter( @@ -268,7 +271,8 @@ AccessLogFactory::fromProto(const envoy::config::filter::accesslog::v2::AccessLo Server::Configuration::FactoryContext& context) { FilterPtr filter; if (config.has_filter()) { - filter = FilterFactory::fromProto(config.filter(), context.runtime(), context.random()); + filter = FilterFactory::fromProto(config.filter(), context.runtime(), context.random(), + context.messageValidationVisitor()); } auto& factory = diff --git a/source/common/access_log/access_log_impl.h b/source/common/access_log/access_log_impl.h index 899e81a290..345ea5a493 100644 --- a/source/common/access_log/access_log_impl.h +++ b/source/common/access_log/access_log_impl.h @@ -28,7 +28,8 @@ class FilterFactory { * Read a filter definition from proto and instantiate a concrete filter class. */ static FilterPtr fromProto(const envoy::config::filter::accesslog::v2::AccessLogFilter& config, - Runtime::Loader& runtime, Runtime::RandomGenerator& random); + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + ProtobufMessage::ValidationVisitor& validation_visitor); }; /** @@ -82,7 +83,8 @@ class OperatorFilter : public Filter { public: OperatorFilter(const Protobuf::RepeatedPtrField< envoy::config::filter::accesslog::v2::AccessLogFilter>& configs, - Runtime::Loader& runtime, Runtime::RandomGenerator& random); + Runtime::Loader& runtime, Runtime::RandomGenerator& random, + ProtobufMessage::ValidationVisitor& validation_visitor); protected: std::vector filters_; @@ -94,7 +96,8 @@ class OperatorFilter : public Filter { class AndFilter : public OperatorFilter { public: AndFilter(const envoy::config::filter::accesslog::v2::AndFilter& config, Runtime::Loader& runtime, - Runtime::RandomGenerator& random); + Runtime::RandomGenerator& random, + ProtobufMessage::ValidationVisitor& validation_visitor); // AccessLog::Filter bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers, @@ -108,7 +111,8 @@ class AndFilter : public OperatorFilter { class OrFilter : public OperatorFilter { public: OrFilter(const envoy::config::filter::accesslog::v2::OrFilter& config, Runtime::Loader& runtime, - Runtime::RandomGenerator& random); + Runtime::RandomGenerator& random, + ProtobufMessage::ValidationVisitor& validation_visitor); // AccessLog::Filter bool evaluate(const StreamInfo::StreamInfo& info, const Http::HeaderMap& request_headers, @@ -174,7 +178,7 @@ class HeaderFilter : public Filter { const Http::HeaderMap& response_trailers) override; private: - std::vector header_data_; + const Http::HeaderUtility::HeaderDataPtr header_data_; }; /** diff --git a/source/common/access_log/access_log_manager_impl.cc b/source/common/access_log/access_log_manager_impl.cc index dd2615421d..fd576b9b00 100644 --- a/source/common/access_log/access_log_manager_impl.cc +++ b/source/common/access_log/access_log_manager_impl.cc @@ -42,9 +42,9 @@ AccessLogFileImpl::AccessLogFileImpl(Filesystem::FilePtr&& file, Event::Dispatch } Filesystem::FlagSet AccessLogFileImpl::defaultFlags() { - static constexpr Filesystem::FlagSet default_flags{ - 1 << Filesystem::File::Operation::Read | 1 << Filesystem::File::Operation::Write | - 1 << Filesystem::File::Operation::Create | 1 << Filesystem::File::Operation::Append}; + static constexpr Filesystem::FlagSet default_flags{1 << Filesystem::File::Operation::Write | + 1 << Filesystem::File::Operation::Create | + 1 << Filesystem::File::Operation::Append}; return default_flags; } @@ -100,8 +100,12 @@ void AccessLogFileImpl::doWrite(Buffer::Instance& buffer) { for (const Buffer::RawSlice& slice : slices) { absl::string_view data(static_cast(slice.mem_), slice.len_); const Api::IoCallSizeResult result = file_->write(data); - ASSERT(result.rc_ == static_cast(slice.len_)); - stats_.write_completed_.inc(); + if (result.ok() && result.rc_ == static_cast(slice.len_)) { + stats_.write_completed_.inc(); + } else { + // Probably disk full. + stats_.write_failed_.inc(); + } } } diff --git a/source/common/access_log/access_log_manager_impl.h b/source/common/access_log/access_log_manager_impl.h index 03efbf2d03..c145be3371 100644 --- a/source/common/access_log/access_log_manager_impl.h +++ b/source/common/access_log/access_log_manager_impl.h @@ -20,6 +20,7 @@ namespace Envoy { COUNTER(reopen_failed) \ COUNTER(write_buffered) \ COUNTER(write_completed) \ + COUNTER(write_failed) \ GAUGE(write_total_buffered, Accumulate) struct AccessLogFileStats { @@ -34,9 +35,9 @@ class AccessLogManagerImpl : public AccessLogManager { Event::Dispatcher& dispatcher, Thread::BasicLockable& lock, Stats::Store& stats_store) : file_flush_interval_msec_(file_flush_interval_msec), api_(api), dispatcher_(dispatcher), - lock_(lock), file_stats_{ACCESS_LOG_FILE_STATS( - POOL_COUNTER_PREFIX(stats_store, "access_log_file."), - POOL_GAUGE_PREFIX(stats_store, "access_log_file."))} {} + lock_(lock), file_stats_{ + ACCESS_LOG_FILE_STATS(POOL_COUNTER_PREFIX(stats_store, "filesystem."), + POOL_GAUGE_PREFIX(stats_store, "filesystem."))} {} // AccessLog::AccessLogManager void reopen() override; @@ -113,12 +114,12 @@ class AccessLogFileImpl : public AccessLogFile { std::atomic flush_thread_exit_{}; std::atomic reopen_file_{}; Buffer::OwnedImpl - flush_buffer_ GUARDED_BY(write_lock_); // This buffer is used by multiple threads. It gets - // filled and then flushed either when max size is - // reached or when a timer fires. - // TODO(jmarantz): this should be GUARDED_BY(flush_lock_) but the analysis cannot poke through - // the std::make_unique assignment. I do not believe it's possible to annotate this properly now - // due to limitations in the clang thread annotation analysis. + flush_buffer_ ABSL_GUARDED_BY(write_lock_); // This buffer is used by multiple threads. It + // gets filled and then flushed either when max + // size is reached or when a timer fires. + // TODO(jmarantz): this should be ABSL_GUARDED_BY(flush_lock_) but the analysis cannot poke + // through the std::make_unique assignment. I do not believe it's possible to annotate this + // properly now due to limitations in the clang thread annotation analysis. Buffer::OwnedImpl about_to_write_buffer_; // This buffer is used only by the flush thread. Data // is moved from flush_buffer_ under lock, and then // the lock is released so that flush_buffer_ can diff --git a/source/common/common/BUILD b/source/common/common/BUILD index 6ace52dbb3..fbe3714e63 100644 --- a/source/common/common/BUILD +++ b/source/common/common/BUILD @@ -165,6 +165,8 @@ envoy_cc_library( external_deps = ["abseil_optional"], deps = [ ":utility_lib", + "//include/envoy/common:matchers_interface", + "//source/common/common:regex_lib", "//source/common/config:metadata_lib", "//source/common/protobuf", "@envoy_api//envoy/type/matcher:metadata_cc", @@ -174,6 +176,19 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "regex_lib", + srcs = ["regex.cc"], + hdrs = ["regex.h"], + deps = [ + ":assert_lib", + "//include/envoy/common:regex_interface", + "//source/common/protobuf:utility_lib", + "@com_googlesource_code_re2//:re2", + "@envoy_api//envoy/type/matcher:regex_cc", + ], +) + envoy_cc_library( name = "non_copyable", hdrs = ["non_copyable.h"], diff --git a/source/common/common/cleanup.h b/source/common/common/cleanup.h index e7039ef069..1eafa29d44 100644 --- a/source/common/common/cleanup.h +++ b/source/common/common/cleanup.h @@ -10,11 +10,19 @@ namespace Envoy { // RAII cleanup via functor. class Cleanup { public: - Cleanup(std::function f) : f_(std::move(f)) {} + Cleanup(std::function f) : f_(std::move(f)), cancelled_(false) {} ~Cleanup() { f_(); } + void cancel() { + cancelled_ = true; + f_ = []() {}; + } + + bool cancelled() { return cancelled_; } + private: std::function f_; + bool cancelled_; }; // RAII helper class to add an element to an std::list on construction and erase diff --git a/source/common/common/lock_guard.h b/source/common/common/lock_guard.h index cde6da486e..d3025a3339 100644 --- a/source/common/common/lock_guard.h +++ b/source/common/common/lock_guard.h @@ -11,14 +11,14 @@ namespace Thread { /** * A lock guard that deals with an optional lock. */ -class SCOPED_LOCKABLE OptionalLockGuard { +class ABSL_SCOPED_LOCKABLE OptionalLockGuard { public: /** * Establishes a scoped mutex-lock. If non-null, the mutex is locked upon construction. * * @param lock the mutex. */ - OptionalLockGuard(BasicLockable* lock) EXCLUSIVE_LOCK_FUNCTION(lock) : lock_(lock) { + OptionalLockGuard(BasicLockable* lock) ABSL_EXCLUSIVE_LOCK_FUNCTION(lock) : lock_(lock) { if (lock_ != nullptr) { lock_->lock(); } @@ -27,7 +27,7 @@ class SCOPED_LOCKABLE OptionalLockGuard { /** * Destruction of the OptionalLockGuard unlocks the lock, if it is non-null. */ - ~OptionalLockGuard() UNLOCK_FUNCTION() { + ~OptionalLockGuard() ABSL_UNLOCK_FUNCTION() { if (lock_ != nullptr) { lock_->unlock(); } @@ -50,7 +50,7 @@ class SCOPED_LOCKABLE OptionalLockGuard { * class lacks thread annotations, as clang currently does appear to be able to handle * conditional thread annotations. So the ones we'd like are commented out. */ -class SCOPED_LOCKABLE TryLockGuard { +class ABSL_SCOPED_LOCKABLE TryLockGuard { public: /** * Establishes a scoped mutex-lock; the a mutex lock is attempted via tryLock, so @@ -92,21 +92,21 @@ class SCOPED_LOCKABLE TryLockGuard { * implementation (no conditionals) and readability at call-sites. In some cases, an early * release is needed, in which case, a ReleasableLockGuard can be used. */ -class SCOPED_LOCKABLE LockGuard { +class ABSL_SCOPED_LOCKABLE LockGuard { public: /** * Establishes a scoped mutex-lock; the mutex is locked upon construction. * * @param lock the mutex. */ - explicit LockGuard(BasicLockable& lock) EXCLUSIVE_LOCK_FUNCTION(lock) : lock_(lock) { + explicit LockGuard(BasicLockable& lock) ABSL_EXCLUSIVE_LOCK_FUNCTION(lock) : lock_(lock) { lock_.lock(); } /** * Destruction of the LockGuard unlocks the lock. */ - ~LockGuard() UNLOCK_FUNCTION() { lock_.unlock(); } + ~LockGuard() ABSL_UNLOCK_FUNCTION() { lock_.unlock(); } private: BasicLockable& lock_; @@ -117,27 +117,28 @@ class SCOPED_LOCKABLE LockGuard { * BasicLockable& to allow usages to be agnostic to cross-process mutexes vs. single-process * mutexes. */ -class SCOPED_LOCKABLE ReleasableLockGuard { +class ABSL_SCOPED_LOCKABLE ReleasableLockGuard { public: /** * Establishes a scoped mutex-lock; the mutex is locked upon construction. * * @param lock the mutex. */ - explicit ReleasableLockGuard(BasicLockable& lock) EXCLUSIVE_LOCK_FUNCTION(lock) : lock_(&lock) { + explicit ReleasableLockGuard(BasicLockable& lock) ABSL_EXCLUSIVE_LOCK_FUNCTION(lock) + : lock_(&lock) { lock_->lock(); } /** * Destruction of the LockGuard unlocks the lock, if it has not already been explicitly released. */ - ~ReleasableLockGuard() UNLOCK_FUNCTION() { release(); } + ~ReleasableLockGuard() ABSL_UNLOCK_FUNCTION() { release(); } /** * Unlocks the mutex. This enables call-sites to release the mutex prior to the Lock going out of * scope. This is called release() for consistency with absl::ReleasableMutexLock. */ - void release() UNLOCK_FUNCTION() { + void release() ABSL_UNLOCK_FUNCTION() { if (lock_ != nullptr) { lock_->unlock(); lock_ = nullptr; diff --git a/source/common/common/logger.h b/source/common/common/logger.h index a443ce11ab..22a0255a0e 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -189,7 +189,7 @@ class DelegatingLogSink : public spdlog::sinks::sink { SinkDelegate* sink_{nullptr}; std::unique_ptr stderr_sink_; // Builtin sink to use as a last resort. - std::unique_ptr formatter_ GUARDED_BY(format_mutex_); + std::unique_ptr formatter_ ABSL_GUARDED_BY(format_mutex_); absl::Mutex format_mutex_; // direct absl reference to break build cycle. }; diff --git a/source/common/common/matchers.cc b/source/common/common/matchers.cc index 2fb5f4b918..4c648ebe8e 100644 --- a/source/common/common/matchers.cc +++ b/source/common/common/matchers.cc @@ -2,6 +2,7 @@ #include "envoy/api/v2/core/base.pb.h" +#include "common/common/regex.h" #include "common/config/metadata.h" #include "absl/strings/match.h" @@ -16,7 +17,7 @@ ValueMatcherConstSharedPtr ValueMatcher::create(const envoy::type::matcher::Valu case envoy::type::matcher::ValueMatcher::kDoubleMatch: return std::make_shared(v.double_match()); case envoy::type::matcher::ValueMatcher::kStringMatch: - return std::make_shared(v.string_match()); + return std::make_shared(v.string_match()); case envoy::type::matcher::ValueMatcher::kBoolMatch: return std::make_shared(v.bool_match()); case envoy::type::matcher::ValueMatcher::kPresentMatch: @@ -56,7 +57,16 @@ bool DoubleMatcher::match(const ProtobufWkt::Value& value) const { }; } -bool StringMatcher::match(const ProtobufWkt::Value& value) const { +StringMatcherImpl::StringMatcherImpl(const envoy::type::matcher::StringMatcher& matcher) + : matcher_(matcher) { + if (matcher.match_pattern_case() == envoy::type::matcher::StringMatcher::kRegex) { + regex_ = Regex::Utility::parseStdRegexAsCompiledMatcher(matcher_.regex()); + } else if (matcher.match_pattern_case() == envoy::type::matcher::StringMatcher::kSafeRegex) { + regex_ = Regex::Utility::parseRegex(matcher_.safe_regex()); + } +} + +bool StringMatcherImpl::match(const ProtobufWkt::Value& value) const { if (value.kind_case() != ProtobufWkt::Value::kStringValue) { return false; } @@ -64,7 +74,7 @@ bool StringMatcher::match(const ProtobufWkt::Value& value) const { return match(value.string_value()); } -bool StringMatcher::match(const absl::string_view value) const { +bool StringMatcherImpl::match(const absl::string_view value) const { switch (matcher_.match_pattern_case()) { case envoy::type::matcher::StringMatcher::kExact: return matcher_.exact() == value; @@ -73,7 +83,8 @@ bool StringMatcher::match(const absl::string_view value) const { case envoy::type::matcher::StringMatcher::kSuffix: return absl::EndsWith(value, matcher_.suffix()); case envoy::type::matcher::StringMatcher::kRegex: - return std::regex_match(value.begin(), value.end(), regex_); + case envoy::type::matcher::StringMatcher::kSafeRegex: + return regex_->match(value); default: NOT_REACHED_GCOVR_EXCL_LINE; } diff --git a/source/common/common/matchers.h b/source/common/common/matchers.h index 8305a3ee4b..bb89941f3d 100644 --- a/source/common/common/matchers.h +++ b/source/common/common/matchers.h @@ -3,6 +3,8 @@ #include #include "envoy/api/v2/core/base.pb.h" +#include "envoy/common/matchers.h" +#include "envoy/common/regex.h" #include "envoy/type/matcher/metadata.pb.h" #include "envoy/type/matcher/number.pb.h" #include "envoy/type/matcher/string.pb.h" @@ -70,21 +72,16 @@ class DoubleMatcher : public ValueMatcher { const envoy::type::matcher::DoubleMatcher matcher_; }; -class StringMatcher : public ValueMatcher { +class StringMatcherImpl : public ValueMatcher, public StringMatcher { public: - StringMatcher(const envoy::type::matcher::StringMatcher& matcher) : matcher_(matcher) { - if (matcher.match_pattern_case() == envoy::type::matcher::StringMatcher::kRegex) { - regex_ = RegexUtil::parseRegex(matcher_.regex()); - } - } - - bool match(const absl::string_view value) const; + explicit StringMatcherImpl(const envoy::type::matcher::StringMatcher& matcher); + bool match(const absl::string_view value) const override; bool match(const ProtobufWkt::Value& value) const override; private: const envoy::type::matcher::StringMatcher matcher_; - std::regex regex_; + Regex::CompiledMatcherPtr regex_; }; class LowerCaseStringMatcher : public ValueMatcher { @@ -100,9 +97,11 @@ class LowerCaseStringMatcher : public ValueMatcher { envoy::type::matcher::StringMatcher toLowerCase(const envoy::type::matcher::StringMatcher& matcher); - const StringMatcher matcher_; + const StringMatcherImpl matcher_; }; +using LowerCaseStringMatcherPtr = std::unique_ptr; + class ListMatcher : public ValueMatcher { public: ListMatcher(const envoy::type::matcher::ListMatcher& matcher); diff --git a/source/common/common/non_copyable.h b/source/common/common/non_copyable.h index c248a37f48..fb356770c3 100644 --- a/source/common/common/non_copyable.h +++ b/source/common/common/non_copyable.h @@ -2,14 +2,19 @@ namespace Envoy { /** - * Mixin class that makes derived classes not copyable. Like boost::noncopyable without boost. + * Mixin class that makes derived classes not copyable and not moveable. Like boost::noncopyable + * without boost. */ class NonCopyable { protected: NonCopyable() = default; -private: - NonCopyable(const NonCopyable&); - NonCopyable& operator=(const NonCopyable&); + // Non-moveable. + NonCopyable(NonCopyable&&) noexcept = delete; + NonCopyable& operator=(NonCopyable&&) noexcept = delete; + + // Non-copyable. + NonCopyable(const NonCopyable&) = delete; + NonCopyable& operator=(const NonCopyable&) = delete; }; } // namespace Envoy diff --git a/source/common/common/regex.cc b/source/common/common/regex.cc new file mode 100644 index 0000000000..b78beef7ce --- /dev/null +++ b/source/common/common/regex.cc @@ -0,0 +1,78 @@ +#include "common/common/regex.h" + +#include "envoy/common/exception.h" + +#include "common/common/assert.h" +#include "common/common/fmt.h" +#include "common/protobuf/utility.h" + +#include "re2/re2.h" + +namespace Envoy { +namespace Regex { +namespace { + +class CompiledStdMatcher : public CompiledMatcher { +public: + CompiledStdMatcher(std::regex&& regex) : regex_(std::move(regex)) {} + + // CompiledMatcher + bool match(absl::string_view value) const override { + return std::regex_match(value.begin(), value.end(), regex_); + } + +private: + const std::regex regex_; +}; + +class CompiledGoogleReMatcher : public CompiledMatcher { +public: + CompiledGoogleReMatcher(const envoy::type::matcher::RegexMatcher& config) + : regex_(config.regex(), re2::RE2::Quiet) { + if (!regex_.ok()) { + throw EnvoyException(regex_.error()); + } + + const uint32_t max_program_size = + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.google_re2(), max_program_size, 100); + if (static_cast(regex_.ProgramSize()) > max_program_size) { + throw EnvoyException(fmt::format("regex '{}' RE2 program size of {} > max program size of " + "{}. Increase configured max program size if necessary.", + config.regex(), regex_.ProgramSize(), max_program_size)); + } + } + + // CompiledMatcher + bool match(absl::string_view value) const override { + return re2::RE2::FullMatch(re2::StringPiece(value.data(), value.size()), regex_); + } + +private: + const re2::RE2 regex_; +}; + +} // namespace + +CompiledMatcherPtr Utility::parseRegex(const envoy::type::matcher::RegexMatcher& matcher) { + // Google Re is the only currently supported engine. + ASSERT(matcher.has_google_re2()); + return std::make_unique(matcher); +} + +CompiledMatcherPtr Utility::parseStdRegexAsCompiledMatcher(const std::string& regex, + std::regex::flag_type flags) { + return std::make_unique(parseStdRegex(regex, flags)); +} + +std::regex Utility::parseStdRegex(const std::string& regex, std::regex::flag_type flags) { + // TODO(zuercher): In the future, PGV (https://github.com/lyft/protoc-gen-validate) annotations + // may allow us to remove this in favor of direct validation of regular expressions. + try { + return std::regex(regex, flags); + } catch (const std::regex_error& e) { + throw EnvoyException(fmt::format("Invalid regex '{}': {}", regex, e.what())); + } +} + +} // namespace Regex +} // namespace Envoy diff --git a/source/common/common/regex.h b/source/common/common/regex.h new file mode 100644 index 0000000000..a88c4739ad --- /dev/null +++ b/source/common/common/regex.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + +#include "envoy/common/regex.h" +#include "envoy/type/matcher/regex.pb.h" + +namespace Envoy { +namespace Regex { + +/** + * Utilities for constructing regular expressions. + */ +class Utility { +public: + /** + * Constructs a std::regex, converting any std::regex_error exception into an EnvoyException. + * @param regex std::string containing the regular expression to parse. + * @param flags std::regex::flag_type containing parser flags. Defaults to std::regex::optimize. + * @return std::regex constructed from regex and flags. + * @throw EnvoyException if the regex string is invalid. + */ + static std::regex parseStdRegex(const std::string& regex, + std::regex::flag_type flags = std::regex::optimize); + + /** + * Construct an std::regex compiled regex matcher. + * + * TODO(mattklein123): In general this is only currently used in deprecated code paths and can be + * removed once all of those code paths are removed. + */ + static CompiledMatcherPtr + parseStdRegexAsCompiledMatcher(const std::string& regex, + std::regex::flag_type flags = std::regex::optimize); + + /** + * Construct a compiled regex matcher from a match config. + */ + static CompiledMatcherPtr parseRegex(const envoy::type::matcher::RegexMatcher& matcher); +}; + +} // namespace Regex +} // namespace Envoy diff --git a/source/common/common/thread.h b/source/common/common/thread.h index 071699fc49..f447ecfb83 100644 --- a/source/common/common/thread.h +++ b/source/common/common/thread.h @@ -16,9 +16,9 @@ namespace Thread { class MutexBasicLockable : public BasicLockable { public: // BasicLockable - void lock() EXCLUSIVE_LOCK_FUNCTION() override { mutex_.Lock(); } - bool tryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true) override { return mutex_.TryLock(); } - void unlock() UNLOCK_FUNCTION() override { mutex_.Unlock(); } + void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { mutex_.Lock(); } + bool tryLock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) override { return mutex_.TryLock(); } + void unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); } private: friend class CondVar; @@ -52,7 +52,7 @@ class CondVar { * source/source/thread.h for an alternate implementation, which does not work * with thread annotation. */ - void wait(MutexBasicLockable& mutex) noexcept EXCLUSIVE_LOCKS_REQUIRED(mutex) { + void wait(MutexBasicLockable& mutex) noexcept ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { condvar_.Wait(&mutex.mutex_); } template @@ -60,9 +60,9 @@ class CondVar { /** * @return WaitStatus whether the condition timed out or not. */ - WaitStatus - waitFor(MutexBasicLockable& mutex, - std::chrono::duration duration) noexcept EXCLUSIVE_LOCKS_REQUIRED(mutex) { + WaitStatus waitFor( + MutexBasicLockable& mutex, + std::chrono::duration duration) noexcept ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { return condvar_.WaitWithTimeout(&mutex.mutex_, absl::FromChrono(duration)) ? WaitStatus::Timeout : WaitStatus::NoTimeout; diff --git a/source/common/common/utility.cc b/source/common/common/utility.cc index ed64837958..4528200b11 100644 --- a/source/common/common/utility.cc +++ b/source/common/common/utility.cc @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "envoy/common/exception.h" @@ -323,6 +324,16 @@ std::vector StringUtil::splitToken(absl::string_view source, return absl::StrSplit(source, absl::ByAnyChar(delimiters), absl::SkipEmpty()); } +std::string StringUtil::removeTokens(absl::string_view source, absl::string_view delimiters, + const CaseUnorderedSet& tokens_to_remove, + absl::string_view joiner) { + auto values = Envoy::StringUtil::splitToken(source, delimiters); + std::for_each(values.begin(), values.end(), [](auto& v) { v = StringUtil::trim(v); }); + auto end = std::remove_if(values.begin(), values.end(), + [&](absl::string_view t) { return tokens_to_remove.count(t) != 0; }); + return absl::StrJoin(values.begin(), end, joiner); +} + uint32_t StringUtil::itoa(char* out, size_t buffer_size, uint64_t i) { // The maximum size required for an unsigned 64-bit integer is 21 chars (including null). if (buffer_size < 21) { @@ -499,16 +510,6 @@ uint32_t Primes::findPrimeLargerThan(uint32_t x) { return x; } -std::regex RegexUtil::parseRegex(const std::string& regex, std::regex::flag_type flags) { - // TODO(zuercher): In the future, PGV (https://github.com/lyft/protoc-gen-validate) annotations - // may allow us to remove this in favor of direct validation of regular expressions. - try { - return std::regex(regex, flags); - } catch (const std::regex_error& e) { - throw EnvoyException(fmt::format("Invalid regex '{}': {}", regex, e.what())); - } -} - // https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm void WelfordStandardDeviation::update(double newValue) { ++count_; diff --git a/source/common/common/utility.h b/source/common/common/utility.h index 6ff000af46..883bd50519 100644 --- a/source/common/common/utility.h +++ b/source/common/common/utility.h @@ -2,7 +2,6 @@ #include #include -#include #include #include #include @@ -145,6 +144,35 @@ class DateUtil { */ class StringUtil { public: + /** + * Callable struct that returns the result of string comparison ignoring case. + * @param lhs supplies the first string view. + * @param rhs supplies the second string view. + * @return true if strings are semantically the same and false otherwise. + */ + struct CaseInsensitiveCompare { + // Enable heterogeneous lookup (https://abseil.io/tips/144) + using is_transparent = void; + bool operator()(absl::string_view lhs, absl::string_view rhs) const; + }; + + /** + * Callable struct that returns the hash representation of a case-insensitive string_view input. + * @param key supplies the string view. + * @return uint64_t hash representation of the supplied string view. + */ + struct CaseInsensitiveHash { + // Enable heterogeneous lookup (https://abseil.io/tips/144) + using is_transparent = void; + uint64_t operator()(absl::string_view key) const; + }; + + /** + * Definition of unordered set of case-insensitive std::string. + */ + using CaseUnorderedSet = + absl::flat_hash_set; + static const char WhitespaceChars[]; /** @@ -282,6 +310,20 @@ class StringUtil { absl::string_view delimiters, bool keep_empty_string = false); + /** + * Remove tokens from a delimiter-separated string view. The tokens are trimmed before + * they are compared ignoring case with the elements of 'tokens_to_remove'. The output is + * built from the trimmed tokens preserving case. + * @param source supplies the delimiter-separated string view. + * @param multi-delimiters supplies chars used to split the delimiter-separated string view. + * @param tokens_to_remove supplies a set of tokens which should not appear in the result. + * @param joiner contains a string used between tokens in the result. + * @return string of the remaining joined tokens. + */ + static std::string removeTokens(absl::string_view source, absl::string_view delimiters, + const CaseUnorderedSet& tokens_to_remove, + absl::string_view joiner); + /** * Size-bounded string copying and concatenation */ @@ -333,35 +375,6 @@ class StringUtil { */ static std::string toLower(absl::string_view s); - /** - * Callable struct that returns the result of string comparison ignoring case. - * @param lhs supplies the first string view. - * @param rhs supplies the second string view. - * @return true if strings are semantically the same and false otherwise. - */ - struct CaseInsensitiveCompare { - // Enable heterogeneous lookup (https://abseil.io/tips/144) - using is_transparent = void; - bool operator()(absl::string_view lhs, absl::string_view rhs) const; - }; - - /** - * Callable struct that returns the hash representation of a case-insensitive string_view input. - * @param key supplies the string view. - * @return uint64_t hash representation of the supplied string view. - */ - struct CaseInsensitiveHash { - // Enable heterogeneous lookup (https://abseil.io/tips/144) - using is_transparent = void; - uint64_t operator()(absl::string_view key) const; - }; - - /** - * Definition of unordered set of case-insensitive std::string. - */ - using CaseUnorderedSet = - absl::flat_hash_set; - /** * Removes all the character indices from str contained in the interval-set. * @param str the string containing the characters to be removed. @@ -388,22 +401,6 @@ class Primes { static uint32_t findPrimeLargerThan(uint32_t x); }; -/** - * Utilities for constructing regular expressions. - */ -class RegexUtil { -public: - /* - * Constructs a std::regex, converting any std::regex_error exception into an EnvoyException. - * @param regex std::string containing the regular expression to parse. - * @param flags std::regex::flag_type containing parser flags. Defaults to std::regex::optimize. - * @return std::regex constructed from regex and flags. - * @throw EnvoyException if the regex string is invalid. - */ - static std::regex parseRegex(const std::string& regex, - std::regex::flag_type flags = std::regex::optimize); -}; - /** * Utilities for working with weighted clusters. */ @@ -503,13 +500,6 @@ template class IntervalSetImpl : public IntervalSet { std::set intervals_; // Intervals do not overlap or abut. }; -/** - * Hashing functor for use with unordered_map and unordered_set with string_view as a key. - */ -struct StringViewHash { - std::size_t operator()(const absl::string_view& k) const { return HashUtil::xxHash64(k); } -}; - /** * Hashing functor for use with enum class types. * This is needed for GCC 5.X; newer versions of GCC, as well as clang7, provide native hashing diff --git a/source/common/config/config_provider_impl.cc b/source/common/config/config_provider_impl.cc index 11cbf993e5..5745647e2d 100644 --- a/source/common/config/config_provider_impl.cc +++ b/source/common/config/config_provider_impl.cc @@ -23,6 +23,16 @@ ConfigSubscriptionCommonBase::~ConfigSubscriptionCommonBase() { init_target_.ready(); config_provider_manager_.unbindSubscription(manager_identifier_); } + +void ConfigSubscriptionCommonBase::applyConfigUpdate(const ConfigUpdateCb& update_fn) { + tls_->runOnAllThreads([update_fn](ThreadLocal::ThreadLocalObjectSharedPtr previous) + -> ThreadLocal::ThreadLocalObjectSharedPtr { + auto prev_thread_local_config = std::dynamic_pointer_cast(previous); + prev_thread_local_config->config_ = update_fn(prev_thread_local_config->config_); + return previous; + }); +} + bool ConfigSubscriptionInstance::checkAndApplyConfigUpdate(const Protobuf::Message& config_proto, const std::string& config_name, const std::string& version_info) { diff --git a/source/common/config/config_provider_impl.h b/source/common/config/config_provider_impl.h index a1a7b02d71..e3a608d1ac 100644 --- a/source/common/config/config_provider_impl.h +++ b/source/common/config/config_provider_impl.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "envoy/config/config_provider.h" #include "envoy/config/config_provider_manager.h" #include "envoy/init/manager.h" @@ -146,9 +144,7 @@ class MutableConfigProviderCommonBase; * shared ownership of the underlying subscription. * */ -class ConfigSubscriptionCommonBase - : protected Logger::Loggable, - public std::enable_shared_from_this { +class ConfigSubscriptionCommonBase : protected Logger::Loggable { public: // Callback for updating a Config implementation held in each worker thread, the callback is // called in applyConfigUpdate() with the current version Config, and is expected to return the @@ -220,26 +216,8 @@ class ConfigSubscriptionCommonBase * * @param update_fn the callback to run on each thread, it takes the previous version Config and * returns a updated/new version Config. - * @param complete_cb the callback to run when the update propagation is done. */ - void applyConfigUpdate( - const ConfigUpdateCb& update_fn, const Event::PostCb& complete_cb = []() {}) { - // It is safe to call shared_from_this here as this is in main thread, and destruction of a - // ConfigSubscriptionCommonBase owner (i.e., a provider) happens in main thread as well. - auto shared_this = shared_from_this(); - tls_->runOnAllThreads( - [this, update_fn]() { - tls_->getTyped().config_ = update_fn(this->getConfig()); - }, - // During the update propagation, a subscription may get teared down in main thread due to - // all owners/providers destructed in a xDS update (e.g. LDS demolishes a - // RouteConfigProvider and its subscription). - // If such a race condition happens, holding a reference to the "*this" subscription - // instance in this cb will ensure the shared "*this" gets posted back to main thread, after - // all the workers finish calling the update_fn, at which point it's safe to destruct - // "*this" instance. - [shared_this, complete_cb]() { complete_cb(); }); - } + void applyConfigUpdate(const ConfigUpdateCb& update_fn); void setLastUpdated() { last_updated_ = time_source_.systemTime(); } @@ -287,8 +265,8 @@ class ConfigSubscriptionInstance : public ConfigSubscriptionCommonBase { /** * Must be called by the derived class' constructor. - * @param initial_config supplies an initial Envoy::Config::ConfigProvider::Config associated with - * the underlying subscription, shared across all providers and workers. + * @param initial_config supplies an initial Envoy::Config::ConfigProvider::Config associated + * with the underlying subscription, shared across all providers and workers. */ void initialize(const ConfigProvider::ConfigConstSharedPtr& initial_config) { tls_->set([initial_config](Event::Dispatcher&) -> ThreadLocal::ThreadLocalObjectSharedPtr { @@ -302,7 +280,8 @@ class ConfigSubscriptionInstance : public ConfigSubscriptionCommonBase { * @param config_proto supplies the newly received config proto. * @param config_name supplies the name associated with the config. * @param version_info supplies the version associated with the config. - * @return bool false when the config proto has no delta from the previous config, true otherwise. + * @return bool false when the config proto has no delta from the previous config, true + * otherwise. */ bool checkAndApplyConfigUpdate(const Protobuf::Message& config_proto, const std::string& config_name, const std::string& version_info); @@ -369,8 +348,8 @@ class MutableConfigProviderCommonBase : public ConfigProvider { }; /** - * Provides generic functionality required by all config provider managers, such as managing shared - * lifetime of subscriptions and dynamic config providers, along with determining which + * Provides generic functionality required by all config provider managers, such as managing + * shared lifetime of subscriptions and dynamic config providers, along with determining which * subscriptions should be associated with newly instantiated providers. * * The implementation of this class is not thread safe. Note that ImmutableConfigProviderBase @@ -379,9 +358,9 @@ class MutableConfigProviderCommonBase : public ConfigProvider { * * All config processing is done on the main thread, so instantiation of *ConfigProvider* objects * via createStaticConfigProvider() and createXdsConfigProvider() is naturally thread safe. Care - * must be taken with regards to destruction of these objects, since it must also happen on the main - * thread _prior_ to destruction of the ConfigProviderManagerImplBase object from which they were - * created. + * must be taken with regards to destruction of these objects, since it must also happen on the + * main thread _prior_ to destruction of the ConfigProviderManagerImplBase object from which they + * were created. * * This class can not be instantiated directly; instead, it provides the foundation for * dynamic config provider implementations which derive from it. @@ -415,8 +394,8 @@ class ConfigProviderManagerImplBase : public ConfigProviderManager, public Singl const ConfigProviderSet& immutableConfigProviders(ConfigProviderInstanceType type) const; /** - * Returns the subscription associated with the config_source_proto; if none exists, a new one is - * allocated according to the subscription_factory_fn. + * Returns the subscription associated with the config_source_proto; if none exists, a new one + * is allocated according to the subscription_factory_fn. * @param config_source_proto supplies the proto specifying the config subscription parameters. * @param init_manager supplies the init manager. * @param subscription_factory_fn supplies a function to be called when a new subscription needs diff --git a/source/common/config/grpc_mux_impl.h b/source/common/config/grpc_mux_impl.h index d1aa494590..ae245c48d7 100644 --- a/source/common/config/grpc_mux_impl.h +++ b/source/common/config/grpc_mux_impl.h @@ -125,7 +125,7 @@ class NullGrpcMuxImpl : public GrpcMux { } void pause(const std::string&) override {} void resume(const std::string&) override {} - bool paused(const std::string&) const override { NOT_REACHED_GCOVR_EXCL_LINE; } + bool paused(const std::string&) const override { return false; } }; } // namespace Config diff --git a/source/common/event/BUILD b/source/common/event/BUILD index 78461dcd31..76c0dc627c 100644 --- a/source/common/event/BUILD +++ b/source/common/event/BUILD @@ -120,6 +120,7 @@ envoy_cc_library( ":event_impl_base_lib", ":libevent_lib", "//include/envoy/event:timer_interface", + "//source/common/common:scope_tracker", ], ) diff --git a/source/common/event/dispatcher_impl.cc b/source/common/event/dispatcher_impl.cc index 7b718b8daf..26bd33a8a6 100644 --- a/source/common/event/dispatcher_impl.cc +++ b/source/common/event/dispatcher_impl.cc @@ -139,17 +139,17 @@ DispatcherImpl::createListener(Network::Socket& socket, Network::ListenerCallbac hand_off_restored_destination_connections)}; } -Network::ListenerPtr DispatcherImpl::createUdpListener(Network::Socket& socket, - Network::UdpListenerCallbacks& cb) { +Network::UdpListenerPtr DispatcherImpl::createUdpListener(Network::Socket& socket, + Network::UdpListenerCallbacks& cb) { ASSERT(isThreadSafe()); - return Network::ListenerPtr{new Network::UdpListenerImpl(*this, socket, cb, timeSource())}; + return std::make_unique(*this, socket, cb, timeSource()); } TimerPtr DispatcherImpl::createTimer(TimerCb cb) { return createTimerInternal(cb); } TimerPtr DispatcherImpl::createTimerInternal(TimerCb cb) { ASSERT(isThreadSafe()); - return scheduler_->createTimer(cb); + return scheduler_->createTimer(cb, *this); } void DispatcherImpl::deferredDelete(DeferredDeletablePtr&& to_delete) { diff --git a/source/common/event/dispatcher_impl.h b/source/common/event/dispatcher_impl.h index 5cbccddb66..650801c2f8 100644 --- a/source/common/event/dispatcher_impl.h +++ b/source/common/event/dispatcher_impl.h @@ -60,8 +60,8 @@ class DispatcherImpl : Logger::Loggable, Network::ListenerPtr createListener(Network::Socket& socket, Network::ListenerCallbacks& cb, bool bind_to_port, bool hand_off_restored_destination_connections) override; - Network::ListenerPtr createUdpListener(Network::Socket& socket, - Network::UdpListenerCallbacks& cb) override; + Network::UdpListenerPtr createUdpListener(Network::Socket& socket, + Network::UdpListenerCallbacks& cb) override; TimerPtr createTimer(TimerCb cb) override; void deferredDelete(DeferredDeletablePtr&& to_delete) override; void exit() override; @@ -110,7 +110,7 @@ class DispatcherImpl : Logger::Loggable, std::vector to_delete_2_; std::vector* current_to_delete_; Thread::MutexBasicLockable post_lock_; - std::list> post_callbacks_ GUARDED_BY(post_lock_); + std::list> post_callbacks_ ABSL_GUARDED_BY(post_lock_); const ScopeTrackedObject* current_object_{}; bool deferred_deleting_{}; }; diff --git a/source/common/event/libevent_scheduler.cc b/source/common/event/libevent_scheduler.cc index df22b45ba7..dcdca25c69 100644 --- a/source/common/event/libevent_scheduler.cc +++ b/source/common/event/libevent_scheduler.cc @@ -19,8 +19,8 @@ LibeventScheduler::LibeventScheduler() : libevent_(event_base_new()) { RELEASE_ASSERT(Libevent::Global::initialized(), ""); } -TimerPtr LibeventScheduler::createTimer(const TimerCb& cb) { - return std::make_unique(libevent_, cb); +TimerPtr LibeventScheduler::createTimer(const TimerCb& cb, Dispatcher& dispatcher) { + return std::make_unique(libevent_, cb, dispatcher); }; void LibeventScheduler::run(Dispatcher::RunType mode) { diff --git a/source/common/event/libevent_scheduler.h b/source/common/event/libevent_scheduler.h index b9157bf405..7694a69ea8 100644 --- a/source/common/event/libevent_scheduler.h +++ b/source/common/event/libevent_scheduler.h @@ -17,7 +17,7 @@ class LibeventScheduler : public Scheduler { LibeventScheduler(); // Scheduler - TimerPtr createTimer(const TimerCb& cb) override; + TimerPtr createTimer(const TimerCb& cb, Dispatcher& dispatcher) override; /** * Runs the event loop. diff --git a/source/common/event/real_time_system.cc b/source/common/event/real_time_system.cc index 9621611c2d..c528b58b4e 100644 --- a/source/common/event/real_time_system.cc +++ b/source/common/event/real_time_system.cc @@ -12,7 +12,9 @@ namespace { class RealScheduler : public Scheduler { public: RealScheduler(Scheduler& base_scheduler) : base_scheduler_(base_scheduler) {} - TimerPtr createTimer(const TimerCb& cb) override { return base_scheduler_.createTimer(cb); }; + TimerPtr createTimer(const TimerCb& cb, Dispatcher& d) override { + return base_scheduler_.createTimer(cb, d); + }; private: Scheduler& base_scheduler_; diff --git a/source/common/event/timer_impl.cc b/source/common/event/timer_impl.cc index 4725e2018c..ba9ea231c5 100644 --- a/source/common/event/timer_impl.cc +++ b/source/common/event/timer_impl.cc @@ -17,16 +17,28 @@ void TimerUtils::millisecondsToTimeval(const std::chrono::milliseconds& d, timev tv.tv_usec = usecs.count(); } -TimerImpl::TimerImpl(Libevent::BasePtr& libevent, TimerCb cb) : cb_(cb) { +TimerImpl::TimerImpl(Libevent::BasePtr& libevent, TimerCb cb, Dispatcher& dispatcher) + : cb_(cb), dispatcher_(dispatcher) { ASSERT(cb_); evtimer_assign( &raw_event_, libevent.get(), - [](evutil_socket_t, short, void* arg) -> void { static_cast(arg)->cb_(); }, this); + [](evutil_socket_t, short, void* arg) -> void { + TimerImpl* timer = static_cast(arg); + if (timer->object_ == nullptr) { + timer->cb_(); + return; + } + ScopeTrackerScopeState scope(timer->object_, timer->dispatcher_); + timer->object_ = nullptr; + timer->cb_(); + }, + this); } void TimerImpl::disableTimer() { event_del(&raw_event_); } -void TimerImpl::enableTimer(const std::chrono::milliseconds& d) { +void TimerImpl::enableTimer(const std::chrono::milliseconds& d, const ScopeTrackedObject* object) { + object_ = object; if (d.count() == 0) { event_active(&raw_event_, EV_TIMEOUT, 0); } else { diff --git a/source/common/event/timer_impl.h b/source/common/event/timer_impl.h index 206525ec1e..172f1b142c 100644 --- a/source/common/event/timer_impl.h +++ b/source/common/event/timer_impl.h @@ -4,6 +4,7 @@ #include "envoy/event/timer.h" +#include "common/common/scope_tracker.h" #include "common/event/event_impl_base.h" #include "common/event/libevent.h" @@ -23,15 +24,20 @@ class TimerUtils { */ class TimerImpl : public Timer, ImplBase { public: - TimerImpl(Libevent::BasePtr& libevent, TimerCb cb); + TimerImpl(Libevent::BasePtr& libevent, TimerCb cb, Event::Dispatcher& dispatcher); // Timer void disableTimer() override; - void enableTimer(const std::chrono::milliseconds& d) override; + void enableTimer(const std::chrono::milliseconds& d, const ScopeTrackedObject* scope) override; bool enabled() override; private: TimerCb cb_; + Dispatcher& dispatcher_; + // This has to be atomic for alarms which are handled out of thread, for + // example if the DispatcherImpl::post is called by two threads, they race to + // both set this to null. + std::atomic object_{}; }; } // namespace Event diff --git a/source/common/http/BUILD b/source/common/http/BUILD index 1dcf5045fa..04c882059d 100644 --- a/source/common/http/BUILD +++ b/source/common/http/BUILD @@ -164,6 +164,7 @@ envoy_cc_library( "//include/envoy/router:rds_interface", "//include/envoy/router:scopes_interface", "//include/envoy/runtime:runtime_interface", + "//include/envoy/server:admin_interface", "//include/envoy/server:overload_manager_interface", "//include/envoy/ssl:connection_interface", "//include/envoy/stats:stats_interface", @@ -177,11 +178,13 @@ envoy_cc_library( "//source/common/common:empty_string", "//source/common/common:enum_to_int", "//source/common/common:linked_object", + "//source/common/common:regex_lib", "//source/common/common:scope_tracker", "//source/common/common:utility_lib", "//source/common/http/http1:codec_lib", "//source/common/http/http2:codec_lib", "//source/common/network:utility_lib", + "//source/common/router:config_lib", "//source/common/runtime:uuid_util_lib", "//source/common/stream_info:stream_info_lib", "//source/common/tracing:http_tracer_lib", diff --git a/source/common/http/codec_client.cc b/source/common/http/codec_client.cc index 7d9ad41f3a..2523d5970b 100644 --- a/source/common/http/codec_client.cc +++ b/source/common/http/codec_client.cc @@ -62,6 +62,7 @@ StreamEncoder& CodecClient::newStream(StreamDecoder& response_decoder) { void CodecClient::onEvent(Network::ConnectionEvent event) { if (event == Network::ConnectionEvent::Connected) { ENVOY_CONN_LOG(debug, "connected", *connection_); + connection_->streamInfo().setDownstreamSslConnection(connection_->ssl()); connected_ = true; } diff --git a/source/common/http/codec_client.h b/source/common/http/codec_client.h index b9cf3482dc..66c6c6d6bb 100644 --- a/source/common/http/codec_client.h +++ b/source/common/http/codec_client.h @@ -118,6 +118,8 @@ class CodecClient : Logger::Loggable, Type type() const { return type_; } + const StreamInfo::StreamInfo& streamInfo() { return connection_->streamInfo(); } + protected: /** * Create a codec client and connect to a remote host/port. diff --git a/source/common/http/codes.h b/source/common/http/codes.h index 45d51a29ff..c312f16759 100644 --- a/source/common/http/codes.h +++ b/source/common/http/codes.h @@ -62,7 +62,7 @@ class CodeStatsImpl : public CodeStats { Stats::StatName upstreamRqGroup(Code response_code) const; Stats::StatName upstreamRqStatName(Code response_code) const; - mutable Stats::StatNamePool stat_name_pool_ GUARDED_BY(mutex_); + mutable Stats::StatNamePool stat_name_pool_ ABSL_GUARDED_BY(mutex_); mutable absl::Mutex mutex_; Stats::SymbolTable& symbol_table_; diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index 52ec547ea9..ce4682de2a 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -1,6 +1,7 @@ #pragma once #include "envoy/config/config_provider.h" +#include "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.pb.h" #include "envoy/http/filter.h" #include "envoy/router/rds.h" #include "envoy/stats/scope.h" @@ -107,6 +108,7 @@ struct TracingConnectionManagerConfig { envoy::type::FractionalPercent random_sampling_; envoy::type::FractionalPercent overall_sampling_; bool verbose_; + uint32_t max_path_tag_length_; }; using TracingConnectionManagerConfigPtr = std::unique_ptr; @@ -170,6 +172,9 @@ class DefaultInternalAddressConfig : public Http::InternalAddressConfig { */ class ConnectionManagerConfig { public: + using HttpConnectionManagerProto = + envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager; + virtual ~ConnectionManagerConfig() = default; /** @@ -265,6 +270,11 @@ class ConnectionManagerConfig { */ virtual const std::string& serverName() PURE; + /** + * @return ServerHeaderTransformation the transformation to apply to Server response headers. + */ + virtual HttpConnectionManagerProto::ServerHeaderTransformation serverHeaderTransformation() PURE; + /** * @return ConnectionManagerStats& the stats to write to. */ diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 8ee08cf2b9..8fb6149151 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -12,6 +12,7 @@ #include "envoy/event/dispatcher.h" #include "envoy/network/drain_decision.h" #include "envoy/router/router.h" +#include "envoy/server/admin.h" #include "envoy/ssl/connection.h" #include "envoy/stats/scope.h" #include "envoy/tracing/http_tracer.h" @@ -33,6 +34,7 @@ #include "common/http/path_utility.h" #include "common/http/utility.h" #include "common/network/utility.h" +#include "common/router/config_impl.h" #include "common/runtime/runtime_impl.h" #include "absl/strings/escaping.h" @@ -431,12 +433,27 @@ void ConnectionManagerImpl::chargeTracingStats(const Tracing::Reason& tracing_re ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connection_manager) : connection_manager_(connection_manager), - snapped_route_config_(connection_manager.config_.routeConfigProvider()->config()), stream_id_(connection_manager.random_generator_.random()), request_response_timespan_(new Stats::Timespan( connection_manager_.stats_.named_.downstream_rq_time_, connection_manager_.timeSource())), stream_info_(connection_manager_.codec_->protocol(), connection_manager_.timeSource()), upstream_options_(std::make_shared()) { + // For Server::Admin, no routeConfigProvider or SRDS route provider is used. + ASSERT(dynamic_cast(&connection_manager_.config_) != nullptr || + ((connection_manager.config_.routeConfigProvider() == nullptr && + connection_manager.config_.scopedRouteConfigProvider() != nullptr) || + (connection_manager.config_.routeConfigProvider() != nullptr && + connection_manager.config_.scopedRouteConfigProvider() == nullptr)), + "Either routeConfigProvider or scopedRouteConfigProvider should be set in " + "ConnectionManagerImpl."); + if (connection_manager.config_.routeConfigProvider() != nullptr) { + snapped_route_config_ = connection_manager.config_.routeConfigProvider()->config(); + } else if (connection_manager.config_.scopedRouteConfigProvider() != nullptr) { + snapped_scoped_routes_config_ = + connection_manager_.config_.scopedRouteConfigProvider()->config(); + ASSERT(snapped_scoped_routes_config_ != nullptr, + "Scoped rds provider returns null for scoped routes config."); + } ScopeTrackerScopeState scope(this, connection_manager_.read_callbacks_->connection().dispatcher()); @@ -471,7 +488,7 @@ ConnectionManagerImpl::ActiveStream::ActiveStream(ConnectionManagerImpl& connect std::chrono::milliseconds request_timeout_ms_ = connection_manager_.config_.requestTimeout(); request_timer_ = connection_manager.read_callbacks_->connection().dispatcher().createTimer( [this]() -> void { onRequestTimeout(); }); - request_timer_->enableTimer(request_timeout_ms_); + request_timer_->enableTimer(request_timeout_ms_, this); } stream_info_.setRequestedServerName( @@ -502,8 +519,9 @@ ConnectionManagerImpl::ActiveStream::~ActiveStream() { } if (active_span_) { - Tracing::HttpTracerUtility::finalizeSpan(*active_span_, request_headers_.get(), stream_info_, - *this); + Tracing::HttpTracerUtility::finalizeSpan(*active_span_, request_headers_.get(), + response_headers_.get(), response_trailers_.get(), + stream_info_, *this); } if (state_.successful_upgrade_) { connection_manager_.stats_.named_.downstream_cx_upgrades_active_.dec(); @@ -612,6 +630,15 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(HeaderMapPtr&& headers, ScopeTrackerScopeState scope(this, connection_manager_.read_callbacks_->connection().dispatcher()); request_headers_ = std::move(headers); + // For Admin thread, we don't use routeConfigProvider or SRDS route provider. + if (dynamic_cast(&connection_manager_.config_) == nullptr && + connection_manager_.config_.scopedRouteConfigProvider() != nullptr) { + ASSERT(snapped_route_config_ == nullptr, + "Route config already latched to the active stream when scoped RDS is enabled."); + // We need to snap snapped_route_config_ here as it's used in mutateRequestHeaders later. + snapScopedRouteConfig(); + } + if (Http::Headers::get().MethodValues.Head == request_headers_->Method()->value().getStringView()) { is_head_request_ = true; @@ -1219,10 +1246,32 @@ void ConnectionManagerImpl::startDrainSequence() { drain_timer_->enableTimer(config_.drainTimeout()); } +void ConnectionManagerImpl::ActiveStream::snapScopedRouteConfig() { + ASSERT(request_headers_ != nullptr, + "Try to snap scoped route config when there is no request headers."); + + // NOTE: if a RDS subscription hasn't got a RouteConfiguration back, a Router::NullConfigImpl is + // returned, in that case we let it pass. + snapped_route_config_ = snapped_scoped_routes_config_->getRouteConfig(*request_headers_); + if (snapped_route_config_ == nullptr) { + ENVOY_STREAM_LOG(trace, "can't find SRDS scope.", *this); + // TODO(stevenzzzz): Consider to pass an error message to router filter, so that it can + // send back 404 with some more details. + snapped_route_config_ = std::make_shared(); + } +} + void ConnectionManagerImpl::ActiveStream::refreshCachedRoute() { Router::RouteConstSharedPtr route; if (request_headers_ != nullptr) { - route = snapped_route_config_->route(*request_headers_, stream_id_); + if (dynamic_cast(&connection_manager_.config_) == nullptr && + connection_manager_.config_.scopedRouteConfigProvider() != nullptr) { + // NOTE: re-select scope as well in case the scope key header has been changed by a filter. + snapScopedRouteConfig(); + } + if (snapped_route_config_ != nullptr) { + route = snapped_route_config_->route(*request_headers_, stream_id_); + } } stream_info_.route_entry_ = route ? route->routeEntry() : nullptr; cached_route_ = std::move(route); @@ -1352,7 +1401,12 @@ void ConnectionManagerImpl::ActiveStream::encodeHeaders(ActiveStreamEncoderFilte // Base headers. connection_manager_.config_.dateProvider().setDateHeader(headers); // Following setReference() is safe because serverName() is constant for the life of the listener. - headers.insertServer().value().setReference(connection_manager_.config_.serverName()); + const auto transformation = connection_manager_.config_.serverHeaderTransformation(); + if (transformation == ConnectionManagerConfig::HttpConnectionManagerProto::OVERWRITE || + (transformation == ConnectionManagerConfig::HttpConnectionManagerProto::APPEND_IF_ABSENT && + headers.Server() == nullptr)) { + headers.insertServer().value().setReference(connection_manager_.config_.serverName()); + } ConnectionManagerUtility::mutateResponseHeaders(headers, request_headers_.get(), connection_manager_.config_.via()); @@ -1698,6 +1752,10 @@ bool ConnectionManagerImpl::ActiveStream::verbose() const { return connection_manager_.config_.tracingConfig()->verbose_; } +uint32_t ConnectionManagerImpl::ActiveStream::maxPathTagLength() const { + return connection_manager_.config_.tracingConfig()->max_path_tag_length_; +} + void ConnectionManagerImpl::ActiveStream::callHighWatermarkCallbacks() { ++high_watermark_count_; for (auto watermark_callbacks : watermark_callbacks_) { diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index 84dde92240..384c18e4ca 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -479,6 +479,7 @@ class ConnectionManagerImpl : Logger::Loggable, Tracing::OperationName operationName() const override; const std::vector& requestHeadersForTags() const override; bool verbose() const override; + uint32_t maxPathTagLength() const override; // ScopeTrackedObject void dumpState(std::ostream& os, int indent_level = 0) const override { @@ -496,6 +497,10 @@ class ConnectionManagerImpl : Logger::Loggable, void traceRequest(); + // Updates the snapped_route_config_ (by reselecting scoped route configuration), if a scope is + // not found, snapped_route_config_ is set to Router::NullConfigImpl. + void snapScopedRouteConfig(); + void refreshCachedRoute(); // Pass on watermark callbacks to watermark subscribers. This boils down to passing watermark @@ -585,7 +590,7 @@ class ConnectionManagerImpl : Logger::Loggable, ConnectionManagerImpl& connection_manager_; Router::ConfigConstSharedPtr snapped_route_config_; - Router::ScopedConfigConstSharedPtr snapped_scoped_route_config_; + Router::ScopedConfigConstSharedPtr snapped_scoped_routes_config_; Tracing::SpanPtr active_span_; const uint64_t stream_id_; StreamEncoder* response_encoder_{}; diff --git a/source/common/http/conn_manager_utility.cc b/source/common/http/conn_manager_utility.cc index 5f9d541693..9c1b8a3b1e 100644 --- a/source/common/http/conn_manager_utility.cc +++ b/source/common/http/conn_manager_utility.cc @@ -85,6 +85,9 @@ Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequest Network::Address::InstanceConstSharedPtr final_remote_address; bool single_xff_address; const uint32_t xff_num_trusted_hops = config.xffNumTrustedHops(); + const bool trusted_forwarded_proto = + Runtime::runtimeFeatureEnabled("envoy.reloadable_features.trusted_forwarded_proto"); + if (config.useRemoteAddress()) { single_xff_address = request_headers.ForwardedFor() == nullptr; // If there are any trusted proxies in front of this Envoy instance (as indicated by @@ -107,8 +110,21 @@ Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequest Utility::appendXff(request_headers, *connection.remoteAddress()); } } - request_headers.insertForwardedProto().value().setReference( - connection.ssl() ? Headers::get().SchemeValues.Https : Headers::get().SchemeValues.Http); + if (trusted_forwarded_proto) { + // If the prior hop is not a trusted proxy, overwrite any x-forwarded-proto value it set as + // untrusted. Alternately if no x-forwarded-proto header exists, add one. + if (xff_num_trusted_hops == 0 || request_headers.ForwardedProto() == nullptr) { + request_headers.insertForwardedProto().value().setReference( + connection.ssl() ? Headers::get().SchemeValues.Https + : Headers::get().SchemeValues.Http); + } + } else { + // Previously, before the trusted_forwarded_proto logic, Envoy would always overwrite the + // x-forwarded-proto header even if it was set by a trusted proxy. This code path is + // deprecated and will be removed. + request_headers.insertForwardedProto().value().setReference( + connection.ssl() ? Headers::get().SchemeValues.Https : Headers::get().SchemeValues.Http); + } } else { // If we are not using remote address, attempt to pull a valid IPv4 or IPv6 address out of XFF. // If we find one, it will be used as the downstream address for logging. It may or may not be @@ -118,8 +134,8 @@ Network::Address::InstanceConstSharedPtr ConnectionManagerUtility::mutateRequest single_xff_address = ret.single_address_; } - // If we didn't already replace x-forwarded-proto because we are using the remote address, and - // remote hasn't set it (trusted proxy), we set it, since we then use this for setting scheme. + // If the x-forwarded-proto header is not set, set it here, since Envoy uses it for determining + // scheme and communicating it upstream. if (!request_headers.ForwardedProto()) { request_headers.insertForwardedProto().value().setReference( connection.ssl() ? Headers::get().SchemeValues.Https : Headers::get().SchemeValues.Http); diff --git a/source/common/http/header_map_impl.cc b/source/common/http/header_map_impl.cc index ff018b77f9..7472b51e75 100644 --- a/source/common/http/header_map_impl.cc +++ b/source/common/http/header_map_impl.cc @@ -145,10 +145,9 @@ void HeaderString::append(const char* data, uint32_t size) { } } } - + ASSERT(validHeaderString(absl::string_view(data, size))); memcpy(buffer_.dynamic_ + string_length_, data, size); string_length_ += size; - ASSERT(valid()); } void HeaderString::clear() { diff --git a/source/common/http/header_utility.cc b/source/common/http/header_utility.cc index b6af8ab75f..2c2f79ca72 100644 --- a/source/common/http/header_utility.cc +++ b/source/common/http/header_utility.cc @@ -1,5 +1,6 @@ #include "common/http/header_utility.h" +#include "common/common/regex.h" #include "common/common/utility.h" #include "common/config/rds_json.h" #include "common/http/header_map_impl.h" @@ -32,7 +33,11 @@ HeaderUtility::HeaderData::HeaderData(const envoy::api::v2::route::HeaderMatcher break; case envoy::api::v2::route::HeaderMatcher::kRegexMatch: header_match_type_ = HeaderMatchType::Regex; - regex_pattern_ = RegexUtil::parseRegex(config.regex_match()); + regex_ = Regex::Utility::parseStdRegexAsCompiledMatcher(config.regex_match()); + break; + case envoy::api::v2::route::HeaderMatcher::kSafeRegexMatch: + header_match_type_ = HeaderMatchType::Regex; + regex_ = Regex::Utility::parseRegex(config.safe_regex_match()); break; case envoy::api::v2::route::HeaderMatcher::kRangeMatch: header_match_type_ = HeaderMatchType::Range; @@ -82,11 +87,11 @@ void HeaderUtility::getAllOfHeader(const Http::HeaderMap& headers, absl::string_ } bool HeaderUtility::matchHeaders(const Http::HeaderMap& request_headers, - const std::vector& config_headers) { + const std::vector& config_headers) { // No headers to match is considered a match. if (!config_headers.empty()) { - for (const HeaderData& cfg_header_data : config_headers) { - if (!matchHeaders(request_headers, cfg_header_data)) { + for (const HeaderDataPtr& cfg_header_data : config_headers) { + if (!matchHeaders(request_headers, *cfg_header_data)) { return false; } } @@ -110,7 +115,7 @@ bool HeaderUtility::matchHeaders(const Http::HeaderMap& request_headers, match = header_data.value_.empty() || header_view == header_data.value_; break; case HeaderMatchType::Regex: - match = std::regex_match(header_view.begin(), header_view.end(), header_data.regex_pattern_); + match = header_data.regex_->match(header_view); break; case HeaderMatchType::Range: { int64_t header_value = 0; diff --git a/source/common/http/header_utility.h b/source/common/http/header_utility.h index 7d3e138530..a4f8c75639 100644 --- a/source/common/http/header_utility.h +++ b/source/common/http/header_utility.h @@ -1,13 +1,15 @@ #pragma once -#include #include #include "envoy/api/v2/route/route.pb.h" +#include "envoy/common/regex.h" #include "envoy/http/header_map.h" #include "envoy/json/json_object.h" #include "envoy/type/range.pb.h" +#include "common/protobuf/protobuf.h" + namespace Envoy { namespace Http { @@ -18,7 +20,8 @@ class HeaderUtility { public: enum class HeaderMatchType { Value, Regex, Range, Present, Prefix, Suffix }; - /* Get all instances of the header key specified, and return the values in the vector provided. + /** + * Get all instances of the header key specified, and return the values in the vector provided. * * This should not be used for inline headers, as it turns a constant time lookup into O(n). * @@ -26,7 +29,7 @@ class HeaderUtility { * @param key the header key to return values for * @param out the vector to return values in */ - static void getAllOfHeader(const Http::HeaderMap& headers, absl::string_view key, + static void getAllOfHeader(const HeaderMap& headers, absl::string_view key, std::vector& out); // A HeaderData specifies one of exact value or regex or range element @@ -36,14 +39,28 @@ class HeaderUtility { HeaderData(const envoy::api::v2::route::HeaderMatcher& config); HeaderData(const Json::Object& config); - const Http::LowerCaseString name_; + const LowerCaseString name_; HeaderMatchType header_match_type_; std::string value_; - std::regex regex_pattern_; + Regex::CompiledMatcherPtr regex_; envoy::type::Int64Range range_; const bool invert_match_; }; + using HeaderDataPtr = std::unique_ptr; + + /** + * Build a vector of HeaderData given input config. + */ + static std::vector buildHeaderDataVector( + const Protobuf::RepeatedPtrField& header_matchers) { + std::vector ret; + for (const auto& header_match : header_matchers) { + ret.emplace_back(std::make_unique(header_match)); + } + return ret; + } + /** * See if the headers specified in the config are present in a request. * @param request_headers supplies the headers from the request. @@ -51,10 +68,10 @@ class HeaderUtility { * @return bool true if all the headers (and values) in the config_headers are found in the * request_headers. If no config_headers are specified, returns true. */ - static bool matchHeaders(const Http::HeaderMap& request_headers, - const std::vector& config_headers); + static bool matchHeaders(const HeaderMap& request_headers, + const std::vector& config_headers); - static bool matchHeaders(const Http::HeaderMap& request_headers, const HeaderData& config_header); + static bool matchHeaders(const HeaderMap& request_headers, const HeaderData& config_header); /** * Validates that a header value is valid, according to RFC 7230, section 3.2. @@ -68,7 +85,7 @@ class HeaderUtility { * @param headers target where headers will be added * @param headers_to_add supplies the headers to be added */ - static void addHeaders(Http::HeaderMap& headers, const Http::HeaderMap& headers_to_add); + static void addHeaders(HeaderMap& headers, const HeaderMap& headers_to_add); }; } // namespace Http } // namespace Envoy diff --git a/source/common/http/headers.h b/source/common/http/headers.h index b711fb4b14..9d53886d68 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -125,6 +125,7 @@ class HeaderValues { const LowerCaseString GrpcAcceptEncoding{"grpc-accept-encoding"}; const LowerCaseString Host{":authority"}; const LowerCaseString HostLegacy{"host"}; + const LowerCaseString Http2Settings{"http2-settings"}; const LowerCaseString KeepAlive{"keep-alive"}; const LowerCaseString LastModified{"last-modified"}; const LowerCaseString Location{"location"}; @@ -153,11 +154,13 @@ class HeaderValues { struct { const std::string Close{"close"}; + const std::string Http2Settings{"http2-settings"}; const std::string KeepAlive{"keep-alive"}; const std::string Upgrade{"upgrade"}; } ConnectionValues; struct { + const std::string H2c{"h2c"}; const std::string WebSocket{"websocket"}; } UpgradeValues; @@ -178,6 +181,7 @@ class HeaderValues { const std::string GrpcWebText{"application/grpc-web-text"}; const std::string GrpcWebTextProto{"application/grpc-web-text+proto"}; const std::string Json{"application/json"}; + const std::string Protobuf{"application/x-protobuf"}; const std::string FormUrlEncoded{"application/x-www-form-urlencoded"}; } ContentTypeValues; diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index ba631706f3..afadd309c1 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -21,6 +21,15 @@ namespace Envoy { namespace Http { namespace Http1 { +namespace { + +const StringUtil::CaseUnorderedSet& caseUnorderdSetContainingUpgradeAndHttp2Settings() { + CONSTRUCT_ON_FIRST_USE(StringUtil::CaseUnorderedSet, + Http::Headers::get().ConnectionValues.Upgrade, + Http::Headers::get().ConnectionValues.Http2Settings); +} + +} // namespace const std::string StreamEncoderImpl::CRLF = "\r\n"; const std::string StreamEncoderImpl::LAST_CHUNK = "0\r\n\r\n"; @@ -469,8 +478,28 @@ int ConnectionImpl::onHeadersCompleteBase() { protocol_ = Protocol::Http10; } if (Utility::isUpgrade(*current_header_map_)) { - ENVOY_CONN_LOG(trace, "codec entering upgrade mode.", connection_); - handling_upgrade_ = true; + // Ignore h2c upgrade requests until we support them. + // See https://github.com/envoyproxy/envoy/issues/7161 for details. + if (current_header_map_->Upgrade() && + absl::EqualsIgnoreCase(current_header_map_->Upgrade()->value().getStringView(), + Http::Headers::get().UpgradeValues.H2c)) { + ENVOY_CONN_LOG(trace, "removing unsupported h2c upgrade headers.", connection_); + current_header_map_->removeUpgrade(); + if (current_header_map_->Connection()) { + const auto& tokens_to_remove = caseUnorderdSetContainingUpgradeAndHttp2Settings(); + std::string new_value = StringUtil::removeTokens( + current_header_map_->Connection()->value().getStringView(), ",", tokens_to_remove, ","); + if (new_value.empty()) { + current_header_map_->removeConnection(); + } else { + current_header_map_->Connection()->value(new_value); + } + } + current_header_map_->remove(Headers::get().Http2Settings); + } else { + ENVOY_CONN_LOG(trace, "codec entering upgrade mode.", connection_); + handling_upgrade_ = true; + } } int rc = onHeadersComplete(std::move(current_header_map_)); diff --git a/source/common/http/http1/conn_pool.cc b/source/common/http/http1/conn_pool.cc index f785298cf8..07f96ce46e 100644 --- a/source/common/http/http1/conn_pool.cc +++ b/source/common/http/http1/conn_pool.cc @@ -70,7 +70,8 @@ void ConnPoolImpl::attachRequestToClient(ActiveClient& client, StreamDecoder& re host_->cluster().stats().upstream_rq_total_.inc(); host_->stats().rq_total_.inc(); client.stream_wrapper_ = std::make_unique(response_decoder, client); - callbacks.onPoolReady(*client.stream_wrapper_, client.real_host_description_); + callbacks.onPoolReady(*client.stream_wrapper_, client.real_host_description_, + client.codec_client_->streamInfo()); } void ConnPoolImpl::checkForDrained() { diff --git a/source/common/http/http2/conn_pool.cc b/source/common/http/http2/conn_pool.cc index a205a9e8b7..62c952e48d 100644 --- a/source/common/http/http2/conn_pool.cc +++ b/source/common/http/http2/conn_pool.cc @@ -101,7 +101,8 @@ void ConnPoolImpl::newClientStream(Http::StreamDecoder& response_decoder, host_->cluster().stats().upstream_rq_active_.inc(); host_->cluster().resourceManager(priority_).requests().inc(); callbacks.onPoolReady(primary_client_->client_->newStream(response_decoder), - primary_client_->real_host_description_); + primary_client_->real_host_description_, + primary_client_->client_->streamInfo()); } } diff --git a/source/common/network/connection_impl.h b/source/common/network/connection_impl.h index 96bb326936..5923951a34 100644 --- a/source/common/network/connection_impl.h +++ b/source/common/network/connection_impl.h @@ -81,7 +81,7 @@ class ConnectionImpl : public FilterManagerConnection, } absl::optional unixSocketPeerCredentials() const override; void setConnectionStats(const ConnectionStats& stats) override; - const Ssl::ConnectionInfo* ssl() const override { return transport_socket_->ssl(); } + Ssl::ConnectionInfoConstSharedPtr ssl() const override { return transport_socket_->ssl(); } State state() const override; void write(Buffer::Instance& data, bool end_stream) override; void setBufferLimits(uint32_t limit) override; diff --git a/source/common/network/io_socket_handle_impl.cc b/source/common/network/io_socket_handle_impl.cc index cce9576553..b44ec48fb3 100644 --- a/source/common/network/io_socket_handle_impl.cc +++ b/source/common/network/io_socket_handle_impl.cc @@ -26,9 +26,11 @@ IoSocketHandleImpl::~IoSocketHandleImpl() { Api::IoCallUint64Result IoSocketHandleImpl::close() { ASSERT(fd_ != -1); - const int rc = ::close(fd_); + auto& os_syscalls = Api::OsSysCallsSingleton::get(); + const auto& result = os_syscalls.close(fd_); fd_ = -1; - return Api::IoCallUint64Result(rc, Api::IoErrorPtr(nullptr, IoSocketError::deleteIoError)); + return Api::IoCallUint64Result(result.rc_, + Api::IoErrorPtr(nullptr, IoSocketError::deleteIoError)); } bool IoSocketHandleImpl::isOpen() const { return fd_ != -1; } diff --git a/source/common/network/raw_buffer_socket.h b/source/common/network/raw_buffer_socket.h index 5183ce18fb..fe87bbeda6 100644 --- a/source/common/network/raw_buffer_socket.h +++ b/source/common/network/raw_buffer_socket.h @@ -20,7 +20,7 @@ class RawBufferSocket : public TransportSocket, protected Logger::Loggable(std::move(info)); + info.value_ = {value_.begin(), value_.end()}; + return absl::make_optional(std::move(info)); } bool SocketOptionImpl::isSupported() const { return optname_.has_value(); } Api::SysCallIntResult SocketOptionImpl::setSocketOption(Socket& socket, const Network::SocketOptionName& optname, - const absl::string_view value) { + const void* value, size_t size) { if (!optname.has_value()) { return {-1, ENOTSUP}; } auto& os_syscalls = Api::OsSysCallsSingleton::get(); - return os_syscalls.setsockopt(socket.ioHandle().fd(), optname.level(), optname.option(), - value.data(), value.size()); + return os_syscalls.setsockopt(socket.ioHandle().fd(), optname.level(), optname.option(), value, + size); } } // namespace Network diff --git a/source/common/network/socket_option_impl.h b/source/common/network/socket_option_impl.h index 5993150632..1a13a67010 100644 --- a/source/common/network/socket_option_impl.h +++ b/source/common/network/socket_option_impl.h @@ -7,6 +7,7 @@ #include "envoy/api/os_sys_calls.h" #include "envoy/network/listen_socket.h" +#include "common/common/assert.h" #include "common/common/logger.h" namespace Envoy { @@ -103,7 +104,9 @@ class SocketOptionImpl : public Socket::Option, Logger::Loggable(value_.data()) % alignof(void*) == 0); + } // Socket::Option bool setOption(Socket& socket, @@ -123,17 +126,20 @@ class SocketOptionImpl : public Socket::Option, Logger::Loggable but not std::string because std::string might inline + // the buffer so its data() is not aligned in to alignof(void*). + const std::vector value_; }; } // namespace Network diff --git a/source/common/protobuf/message_validator_impl.cc b/source/common/protobuf/message_validator_impl.cc index 4e921a0f02..e07ae6ea82 100644 --- a/source/common/protobuf/message_validator_impl.cc +++ b/source/common/protobuf/message_validator_impl.cc @@ -4,7 +4,6 @@ #include "common/common/assert.h" #include "common/common/hash.h" -#include "common/common/logger.h" #include "common/common/macros.h" #include "absl/strings/str_cat.h" diff --git a/source/common/protobuf/message_validator_impl.h b/source/common/protobuf/message_validator_impl.h index 2d5b3d41af..32d705fd44 100644 --- a/source/common/protobuf/message_validator_impl.h +++ b/source/common/protobuf/message_validator_impl.h @@ -3,6 +3,8 @@ #include "envoy/protobuf/message_validator.h" #include "envoy/stats/stats.h" +#include "common/common/logger.h" + #include "absl/container/flat_hash_set.h" namespace Envoy { diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index 50a95b4199..f2c9427831 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -144,7 +144,7 @@ void MessageUtil::loadFromFile(const std::string& path, Protobuf::Message& messa if (absl::EndsWith(path, FileExtensions::get().ProtoBinary)) { // Attempt to parse the binary format. if (message.ParseFromString(contents)) { - MessageUtil::checkUnknownFields(message, validation_visitor); + MessageUtil::checkForUnexpectedFields(message, validation_visitor); return; } throw EnvoyException("Unable to parse file \"" + path + "\" as a binary protobuf (type " + @@ -165,7 +165,23 @@ void MessageUtil::loadFromFile(const std::string& path, Protobuf::Message& messa } } -void MessageUtil::checkForDeprecation(const Protobuf::Message& message, Runtime::Loader* runtime) { +void MessageUtil::checkForUnexpectedFields(const Protobuf::Message& message, + ProtobufMessage::ValidationVisitor& validation_visitor, + Runtime::Loader* runtime) { + // Reject unknown fields. + const auto& unknown_fields = message.GetReflection()->GetUnknownFields(message); + if (!unknown_fields.empty()) { + std::string error_msg; + for (int n = 0; n < unknown_fields.field_count(); ++n) { + error_msg += absl::StrCat(n > 0 ? ", " : "", unknown_fields.field(n).number()); + } + // We use the validation visitor but have hard coded behavior below for deprecated fields. + // TODO(htuch): Unify the deprecated and unknown visitor handling behind the validation + // visitor pattern. https://github.com/envoyproxy/envoy/issues/8092. + validation_visitor.onUnknownField("type " + message.GetTypeName() + + " with unknown field set {" + error_msg + "}"); + } + const Protobuf::Descriptor* descriptor = message.GetDescriptor(); const Protobuf::Reflection* reflection = message.GetReflection(); for (int i = 0; i < descriptor->field_count(); ++i) { @@ -177,7 +193,11 @@ void MessageUtil::checkForDeprecation(const Protobuf::Message& message, Runtime: continue; } +#ifdef ENVOY_DISABLE_DEPRECATED_FEATURES + bool warn_only = false; +#else bool warn_only = true; +#endif absl::string_view filename = filenameFromPath(field->file()->name()); // Allow runtime to be null both to not crash if this is called before server initialization, // and so proto validation works in context where runtime singleton is not set up (e.g. @@ -200,7 +220,7 @@ void MessageUtil::checkForDeprecation(const Protobuf::Message& message, Runtime: } else { const char fatal_error[] = " If continued use of this field is absolutely necessary, see " - "https://www.envoyproxy.io/docs/envoy/latest/configuration/runtime" + "https://www.envoyproxy.io/docs/envoy/latest/configuration/operations/runtime" "#using-runtime-overrides-for-deprecated-features for how to apply a temporary and " "highly discouraged override."; throw ProtoValidationException(err + fatal_error, message); @@ -212,10 +232,12 @@ void MessageUtil::checkForDeprecation(const Protobuf::Message& message, Runtime: if (field->is_repeated()) { const int size = reflection->FieldSize(message, field); for (int j = 0; j < size; ++j) { - checkForDeprecation(reflection->GetRepeatedMessage(message, field, j), runtime); + checkForUnexpectedFields(reflection->GetRepeatedMessage(message, field, j), + validation_visitor, runtime); } } else { - checkForDeprecation(reflection->GetMessage(message, field), runtime); + checkForUnexpectedFields(reflection->GetMessage(message, field), validation_visitor, + runtime); } } } diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index a05587338e..1f29ea1d79 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -84,6 +84,15 @@ uint64_t fractionalPercentDenominatorToInt( } // namespace ProtobufPercentHelper } // namespace Envoy +// Convert an envoy::api::v2::core::Percent to a double or a default. +// @param message supplies the proto message containing the field. +// @param field_name supplies the field name in the message. +// @param default_value supplies the default if the field is not present. +#define PROTOBUF_PERCENT_TO_DOUBLE_OR_DEFAULT(message, field_name, default_value) \ + (!std::isnan((message).field_name().value()) \ + ? (message).has_##field_name() ? (message).field_name().value() : default_value \ + : throw EnvoyException(fmt::format("Value not in the range of 0..100 range."))) + // Convert an envoy::api::v2::core::Percent to a rounded integer or a default. // @param message supplies the proto message containing the field. // @param field_name supplies the field name in the message. @@ -206,13 +215,6 @@ class MessageUtil { return HashUtil::xxHash64(text); } - static void checkUnknownFields(const Protobuf::Message& message, - ProtobufMessage::ValidationVisitor& validation_visitor) { - if (!message.GetReflection()->GetUnknownFields(message).empty()) { - validation_visitor.onUnknownField("type " + message.GetTypeName()); - } - } - static void loadFromJson(const std::string& json, Protobuf::Message& message, ProtobufMessage::ValidationVisitor& validation_visitor); static void loadFromJson(const std::string& json, ProtobufWkt::Struct& message); @@ -229,8 +231,9 @@ class MessageUtil { * in disallowed_features in runtime_features.h */ static void - checkForDeprecation(const Protobuf::Message& message, - Runtime::Loader* loader = Runtime::LoaderSingleton::getExisting()); + checkForUnexpectedFields(const Protobuf::Message& message, + ProtobufMessage::ValidationVisitor& validation_visitor, + Runtime::Loader* loader = Runtime::LoaderSingleton::getExisting()); /** * Validate protoc-gen-validate constraints on a given protobuf. @@ -239,9 +242,11 @@ class MessageUtil { * @param message message to validate. * @throw ProtoValidationException if the message does not satisfy its type constraints. */ - template static void validate(const MessageType& message) { - // Log warnings or throw errors if deprecated fields are in use. - checkForDeprecation(message); + template + static void validate(const MessageType& message, + ProtobufMessage::ValidationVisitor& validation_visitor) { + // Log warnings or throw errors if deprecated fields or unknown fields are in use. + checkForUnexpectedFields(message, validation_visitor); std::string err; if (!Validate(message, &err)) { @@ -253,14 +258,14 @@ class MessageUtil { static void loadFromFileAndValidate(const std::string& path, MessageType& message, ProtobufMessage::ValidationVisitor& validation_visitor) { loadFromFile(path, message, validation_visitor); - validate(message); + validate(message, validation_visitor); } template static void loadFromYamlAndValidate(const std::string& yaml, MessageType& message, ProtobufMessage::ValidationVisitor& validation_visitor) { loadFromYaml(yaml, message, validation_visitor); - validate(message); + validate(message, validation_visitor); } /** @@ -272,9 +277,11 @@ class MessageUtil { * @throw ProtoValidationException if the message does not satisfy its type constraints. */ template - static const MessageType& downcastAndValidate(const Protobuf::Message& config) { + static const MessageType& + downcastAndValidate(const Protobuf::Message& config, + ProtobufMessage::ValidationVisitor& validation_visitor) { const auto& typed_config = dynamic_cast(config); - validate(typed_config); + validate(typed_config, validation_visitor); return typed_config; } @@ -286,13 +293,11 @@ class MessageUtil { * @return MessageType the typed message inside the Any. */ template - static inline MessageType anyConvert(const ProtobufWkt::Any& message, - ProtobufMessage::ValidationVisitor& validation_visitor) { + static inline MessageType anyConvert(const ProtobufWkt::Any& message) { MessageType typed_message; if (!message.UnpackTo(&typed_message)) { throw EnvoyException("Unable to unpack " + message.DebugString()); } - checkUnknownFields(typed_message, validation_visitor); return typed_message; }; diff --git a/source/common/router/BUILD b/source/common/router/BUILD index 31d354477d..21970908db 100644 --- a/source/common/router/BUILD +++ b/source/common/router/BUILD @@ -132,6 +132,7 @@ envoy_cc_library( "//include/envoy/singleton:instance_interface", "//include/envoy/thread_local:thread_local_interface", "//source/common/common:assert_lib", + "//source/common/common:callback_impl_lib", "//source/common/common:minimal_logger_lib", "//source/common/config:rds_json_lib", "//source/common/config:subscription_factory_lib", @@ -146,22 +147,16 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "scoped_config_manager_lib", - srcs = ["scoped_config_manager.cc"], - hdrs = ["scoped_config_manager.h"], - deps = [ - "@envoy_api//envoy/api/v2:srds_cc", - ], -) - envoy_cc_library( name = "scoped_config_lib", srcs = ["scoped_config_impl.cc"], hdrs = ["scoped_config_impl.h"], + external_deps = [ + "abseil_str_format", + ], deps = [ ":config_lib", - ":scoped_config_manager_lib", + "//include/envoy/router:rds_interface", "//include/envoy/router:scopes_interface", "//include/envoy/thread_local:thread_local_interface", "@envoy_api//envoy/api/v2:srds_cc", @@ -174,13 +169,18 @@ envoy_cc_library( srcs = ["scoped_rds.cc"], hdrs = ["scoped_rds.h"], deps = [ + ":rds_lib", ":scoped_config_lib", "//include/envoy/config:config_provider_interface", "//include/envoy/config:subscription_interface", + "//include/envoy/router:route_config_provider_manager_interface", "//include/envoy/stats:stats_interface", "//source/common/common:assert_lib", + "//source/common/common:cleanup_lib", "//source/common/common:minimal_logger_lib", "//source/common/config:config_provider_lib", + "//source/common/init:manager_lib", + "//source/common/init:watcher_lib", "@envoy_api//envoy/admin/v2alpha:config_dump_cc", "@envoy_api//envoy/api/v2:srds_cc", ], diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index a589af9542..7b9898ab7e 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -20,6 +19,7 @@ #include "common/common/fmt.h" #include "common/common/hash.h" #include "common/common/logger.h" +#include "common/common/regex.h" #include "common/common/utility.h" #include "common/config/metadata.h" #include "common/config/rds_json.h" @@ -66,7 +66,8 @@ HedgePolicyImpl::HedgePolicyImpl() : initial_requests_(1), additional_request_chance_({}), hedge_on_per_try_timeout_(false) {} RetryPolicyImpl::RetryPolicyImpl(const envoy::api::v2::route::RetryPolicy& retry_policy, - ProtobufMessage::ValidationVisitor& validation_visitor) { + ProtobufMessage::ValidationVisitor& validation_visitor) + : validation_visitor_(&validation_visitor) { per_try_timeout_ = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(retry_policy, per_try_timeout, 0)); num_retries_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT(retry_policy, num_retries, 1); @@ -141,7 +142,8 @@ Upstream::RetryPrioritySharedPtr RetryPolicyImpl::retryPriority() const { auto& factory = Envoy::Config::Utility::getAndCheckFactory( retry_priority_config_.first); - return factory.createRetryPriority(*retry_priority_config_.second, num_retries_); + return factory.createRetryPriority(*retry_priority_config_.second, *validation_visitor_, + num_retries_); } CorsPolicyImpl::CorsPolicyImpl(const envoy::api::v2::route::CorsPolicy& config, @@ -151,10 +153,17 @@ CorsPolicyImpl::CorsPolicyImpl(const envoy::api::v2::route::CorsPolicy& config, max_age_(config.max_age()), legacy_enabled_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enabled, true)) { for (const auto& origin : config.allow_origin()) { - allow_origin_.push_back(origin); + envoy::type::matcher::StringMatcher matcher_config; + matcher_config.set_exact(origin); + allow_origins_.push_back(std::make_unique(matcher_config)); } for (const auto& regex : config.allow_origin_regex()) { - allow_origin_regex_.push_back(RegexUtil::parseRegex(regex)); + envoy::type::matcher::StringMatcher matcher_config; + matcher_config.set_regex(regex); + allow_origins_.push_back(std::make_unique(matcher_config)); + } + for (const auto& string_match : config.allow_origin_string_match()) { + allow_origins_.push_back(std::make_unique(string_match)); } if (config.has_allow_credentials()) { allow_credentials_ = PROTOBUF_GET_WRAPPED_REQUIRED(config, allow_credentials); @@ -390,6 +399,7 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, factory_context.messageValidationVisitor())), rate_limit_policy_(route.route().rate_limits()), shadow_policy_(route.route()), priority_(ConfigUtility::parsePriority(route.route().priority())), + config_headers_(Http::HeaderUtility::buildHeaderDataVector(route.match().headers())), total_cluster_weight_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(route.route().weighted_clusters(), total_weight, 100UL)), request_headers_parser_(HeaderParser::configure(route.request_headers_to_add(), @@ -438,12 +448,9 @@ RouteEntryImplBase::RouteEntryImplBase(const VirtualHostImpl& vhost, } } - for (const auto& header_map : route.match().headers()) { - config_headers_.push_back(header_map); - } - for (const auto& query_parameter : route.match().query_parameters()) { - config_query_parameters_.push_back(query_parameter); + config_query_parameters_.push_back( + std::make_unique(query_parameter)); } if (!route.route().hash_policy().empty()) { @@ -555,7 +562,7 @@ RouteEntryImplBase::loadRuntimeData(const envoy::api::v2::route::RouteMatch& rou } void RouteEntryImplBase::finalizePathHeader(Http::HeaderMap& headers, - const std::string& matched_path, + absl::string_view matched_path, bool insert_envoy_original_path) const { const auto& rewrite = getPathRewrite(); if (rewrite.empty()) { @@ -896,32 +903,36 @@ RouteConstSharedPtr PathRouteEntryImpl::matches(const Http::HeaderMap& headers, RegexRouteEntryImpl::RegexRouteEntryImpl(const VirtualHostImpl& vhost, const envoy::api::v2::route::Route& route, Server::Configuration::FactoryContext& factory_context) - : RouteEntryImplBase(vhost, route, factory_context), - regex_(RegexUtil::parseRegex(route.match().regex())), regex_str_(route.match().regex()) {} + : RouteEntryImplBase(vhost, route, factory_context) { + if (route.match().path_specifier_case() == envoy::api::v2::route::RouteMatch::kRegex) { + regex_ = Regex::Utility::parseStdRegexAsCompiledMatcher(route.match().regex()); + regex_str_ = route.match().regex(); + } else { + ASSERT(route.match().path_specifier_case() == envoy::api::v2::route::RouteMatch::kSafeRegex); + regex_ = Regex::Utility::parseRegex(route.match().safe_regex()); + regex_str_ = route.match().safe_regex().regex(); + } +} -void RegexRouteEntryImpl::rewritePathHeader(Http::HeaderMap& headers, - bool insert_envoy_original_path) const { +absl::string_view RegexRouteEntryImpl::pathOnly(const Http::HeaderMap& headers) const { const Http::HeaderString& path = headers.Path()->value(); const absl::string_view query_string = Http::Utility::findQueryStringStart(path); const size_t path_string_length = path.size() - query_string.length(); + return path.getStringView().substr(0, path_string_length); +} + +void RegexRouteEntryImpl::rewritePathHeader(Http::HeaderMap& headers, + bool insert_envoy_original_path) const { // TODO(yuval-k): This ASSERT can happen if the path was changed by a filter without clearing the // route cache. We should consider if ASSERT-ing is the desired behavior in this case. - - const absl::string_view path_view = path.getStringView(); - ASSERT(std::regex_match(path_view.begin(), path_view.begin() + path_string_length, regex_)); - const std::string matched_path(path_view.begin(), path_view.begin() + path_string_length); - - finalizePathHeader(headers, matched_path, insert_envoy_original_path); + ASSERT(regex_->match(pathOnly(headers))); + finalizePathHeader(headers, pathOnly(headers), insert_envoy_original_path); } RouteConstSharedPtr RegexRouteEntryImpl::matches(const Http::HeaderMap& headers, uint64_t random_value) const { if (RouteEntryImplBase::matchRoute(headers, random_value)) { - const Http::HeaderString& path = headers.Path()->value(); - const absl::string_view query_string = Http::Utility::findQueryStringStart(path); - if (std::regex_match(path.getStringView().begin(), - path.getStringView().begin() + (path.size() - query_string.length()), - regex_)) { + if (regex_->match(pathOnly(headers))) { return clusterEntry(headers, random_value); } } @@ -967,19 +978,22 @@ VirtualHostImpl::VirtualHostImpl(const envoy::api::v2::route::VirtualHost& virtu } for (const auto& route : virtual_host.routes()) { - const bool has_prefix = - route.match().path_specifier_case() == envoy::api::v2::route::RouteMatch::kPrefix; - const bool has_path = - route.match().path_specifier_case() == envoy::api::v2::route::RouteMatch::kPath; - const bool has_regex = - route.match().path_specifier_case() == envoy::api::v2::route::RouteMatch::kRegex; - if (has_prefix) { + switch (route.match().path_specifier_case()) { + case envoy::api::v2::route::RouteMatch::kPrefix: { routes_.emplace_back(new PrefixRouteEntryImpl(*this, route, factory_context)); - } else if (has_path) { + break; + } + case envoy::api::v2::route::RouteMatch::kPath: { routes_.emplace_back(new PathRouteEntryImpl(*this, route, factory_context)); - } else { - ASSERT(has_regex); + break; + } + case envoy::api::v2::route::RouteMatch::kRegex: + case envoy::api::v2::route::RouteMatch::kSafeRegex: { routes_.emplace_back(new RegexRouteEntryImpl(*this, route, factory_context)); + break; + } + case envoy::api::v2::route::RouteMatch::PATH_SPECIFIER_NOT_SET: + NOT_REACHED_GCOVR_EXCL_LINE; } if (validate_clusters) { @@ -1004,10 +1018,27 @@ VirtualHostImpl::VirtualHostImpl(const envoy::api::v2::route::VirtualHost& virtu VirtualHostImpl::VirtualClusterEntry::VirtualClusterEntry( const envoy::api::v2::route::VirtualCluster& virtual_cluster, Stats::StatNamePool& pool) - : pattern_(RegexUtil::parseRegex(virtual_cluster.pattern())), - stat_name_(pool.add(virtual_cluster.name())) { + : stat_name_(pool.add(virtual_cluster.name())) { + if (virtual_cluster.pattern().empty() == virtual_cluster.headers().empty()) { + throw EnvoyException("virtual clusters must define either 'pattern' or 'headers'"); + } + + if (!virtual_cluster.pattern().empty()) { + envoy::api::v2::route::HeaderMatcher matcher_config; + matcher_config.set_name(Http::Headers::get().Path.get()); + matcher_config.set_regex_match(virtual_cluster.pattern()); + headers_.push_back(std::make_unique(matcher_config)); + } else { + ASSERT(!virtual_cluster.headers().empty()); + headers_ = Http::HeaderUtility::buildHeaderDataVector(virtual_cluster.headers()); + } + if (virtual_cluster.method() != envoy::api::v2::core::RequestMethod::METHOD_UNSPECIFIED) { - method_ = envoy::api::v2::core::RequestMethod_Name(virtual_cluster.method()); + envoy::api::v2::route::HeaderMatcher matcher_config; + matcher_config.set_name(Http::Headers::get().Method.get()); + matcher_config.set_exact_match( + envoy::api::v2::core::RequestMethod_Name(virtual_cluster.method())); + headers_.push_back(std::make_unique(matcher_config)); } } @@ -1152,11 +1183,7 @@ const std::shared_ptr VirtualHostImpl::SSL_REDIRECT_ROUT const VirtualCluster* VirtualHostImpl::virtualClusterFromEntries(const Http::HeaderMap& headers) const { for (const VirtualClusterEntry& entry : virtual_clusters_) { - bool method_matches = - !entry.method_ || headers.Method()->value().getStringView() == entry.method_.value(); - - absl::string_view path_view = headers.Path()->value().getStringView(); - if (method_matches && std::regex_match(path_view.begin(), path_view.end(), entry.pattern_)) { + if (Http::HeaderUtility::matchHeaders(headers, entry.headers_)) { return &entry; } } diff --git a/source/common/router/config_impl.h b/source/common/router/config_impl.h index 22ed8ae088..39a51ba678 100644 --- a/source/common/router/config_impl.h +++ b/source/common/router/config_impl.h @@ -104,8 +104,9 @@ class CorsPolicyImpl : public CorsPolicy { CorsPolicyImpl(const envoy::api::v2::route::CorsPolicy& config, Runtime::Loader& loader); // Router::CorsPolicy - const std::list& allowOrigins() const override { return allow_origin_; }; - const std::list& allowOriginRegexes() const override { return allow_origin_regex_; } + const std::vector& allowOrigins() const override { + return allow_origins_; + }; const std::string& allowMethods() const override { return allow_methods_; }; const std::string& allowHeaders() const override { return allow_headers_; }; const std::string& exposeHeaders() const override { return expose_headers_; }; @@ -131,8 +132,7 @@ class CorsPolicyImpl : public CorsPolicy { private: const envoy::api::v2::route::CorsPolicy config_; Runtime::Loader& loader_; - std::list allow_origin_; - std::list allow_origin_regex_; + std::vector allow_origins_; const std::string allow_methods_; const std::string allow_headers_; const std::string expose_headers_; @@ -182,9 +182,8 @@ class VirtualHostImpl : public VirtualHost { // Router::VirtualCluster Stats::StatName statName() const override { return stat_name_; } - const std::regex pattern_; - absl::optional method_; const Stats::StatName stat_name_; + std::vector headers_; }; class CatchAllVirtualCluster : public VirtualCluster { @@ -258,6 +257,7 @@ class RetryPolicyImpl : public RetryPolicy { std::vector retriable_status_codes_; absl::optional base_interval_; absl::optional max_interval_; + ProtobufMessage::ValidationVisitor* validation_visitor_{}; }; /** @@ -479,7 +479,7 @@ class RouteEntryImplBase : public RouteEntry, return (isRedirect()) ? prefix_rewrite_redirect_ : prefix_rewrite_; } - void finalizePathHeader(Http::HeaderMap& headers, const std::string& matched_path, + void finalizePathHeader(Http::HeaderMap& headers, absl::string_view matched_path, bool insert_envoy_original_path) const; private: @@ -669,8 +669,8 @@ class RouteEntryImplBase : public RouteEntry, const RateLimitPolicyImpl rate_limit_policy_; const ShadowPolicyImpl shadow_policy_; const Upstream::ResourcePriority priority_; - std::vector config_headers_; - std::vector config_query_parameters_; + std::vector config_headers_; + std::vector config_query_parameters_; std::vector weighted_clusters_; UpgradeMap upgrade_map_; @@ -759,8 +759,10 @@ class RegexRouteEntryImpl : public RouteEntryImplBase { void rewritePathHeader(Http::HeaderMap& headers, bool insert_envoy_original_path) const override; private: - const std::regex regex_; - const std::string regex_str_; + absl::string_view pathOnly(const Http::HeaderMap& headers) const; + + Regex::CompiledMatcherPtr regex_; + std::string regex_str_; }; /** diff --git a/source/common/router/config_utility.cc b/source/common/router/config_utility.cc index c48dd794d5..09dc85db59 100644 --- a/source/common/router/config_utility.cc +++ b/source/common/router/config_utility.cc @@ -1,25 +1,59 @@ #include "common/router/config_utility.h" -#include #include #include #include "common/common/assert.h" +#include "common/common/regex.h" namespace Envoy { namespace Router { +namespace { + +absl::optional +maybeCreateStringMatcher(const envoy::api::v2::route::QueryParameterMatcher& config) { + switch (config.query_parameter_match_specifier_case()) { + case envoy::api::v2::route::QueryParameterMatcher::kStringMatch: { + return Matchers::StringMatcherImpl(config.string_match()); + } + case envoy::api::v2::route::QueryParameterMatcher::kPresentMatch: { + return absl::nullopt; + } + case envoy::api::v2::route::QueryParameterMatcher::QUERY_PARAMETER_MATCH_SPECIFIER_NOT_SET: { + if (config.value().empty()) { + // Present match. + return absl::nullopt; + } + + envoy::type::matcher::StringMatcher matcher_config; + if (PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, regex, false)) { + matcher_config.set_regex(config.value()); + } else { + matcher_config.set_exact(config.value()); + } + return Matchers::StringMatcherImpl(matcher_config); + } + } + + NOT_REACHED_GCOVR_EXCL_LINE; // Needed for gcc +} + +} // namespace + +ConfigUtility::QueryParameterMatcher::QueryParameterMatcher( + const envoy::api::v2::route::QueryParameterMatcher& config) + : name_(config.name()), matcher_(maybeCreateStringMatcher(config)) {} bool ConfigUtility::QueryParameterMatcher::matches( const Http::Utility::QueryParams& request_query_params) const { auto query_param = request_query_params.find(name_); if (query_param == request_query_params.end()) { return false; - } else if (is_regex_) { - return std::regex_match(query_param->second, regex_pattern_); - } else if (value_.length() == 0) { + } else if (!matcher_.has_value()) { + // Present match. return true; } else { - return (value_ == query_param->second); + return matcher_.value().match(query_param->second); } } @@ -37,9 +71,9 @@ ConfigUtility::parsePriority(const envoy::api::v2::core::RoutingPriority& priori bool ConfigUtility::matchQueryParams( const Http::Utility::QueryParams& query_params, - const std::vector& config_query_params) { + const std::vector& config_query_params) { for (const auto& config_query_param : config_query_params) { - if (!config_query_param.matches(query_params)) { + if (!config_query_param->matches(query_params)) { return false; } } diff --git a/source/common/router/config_utility.h b/source/common/router/config_utility.h index 4a75396405..030e20ca4c 100644 --- a/source/common/router/config_utility.h +++ b/source/common/router/config_utility.h @@ -1,7 +1,6 @@ #pragma once #include -#include #include #include @@ -11,6 +10,7 @@ #include "envoy/upstream/resource_manager.h" #include "common/common/empty_string.h" +#include "common/common/matchers.h" #include "common/common/utility.h" #include "common/config/rds_json.h" #include "common/http/headers.h" @@ -32,10 +32,7 @@ class ConfigUtility { // equivalent of the QueryParameterMatcher proto in the RDS v2 API. class QueryParameterMatcher { public: - QueryParameterMatcher(const envoy::api::v2::route::QueryParameterMatcher& config) - : name_(config.name()), value_(config.value()), - is_regex_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, regex, false)), - regex_pattern_(is_regex_ ? RegexUtil::parseRegex(value_) : std::regex()) {} + QueryParameterMatcher(const envoy::api::v2::route::QueryParameterMatcher& config); /** * Check if the query parameters for a request contain a match for this @@ -47,11 +44,11 @@ class ConfigUtility { private: const std::string name_; - const std::string value_; - const bool is_regex_; - const std::regex regex_pattern_; + const absl::optional matcher_; }; + using QueryParameterMatcherPtr = std::unique_ptr; + /** * @return the resource priority parsed from proto. */ @@ -66,7 +63,7 @@ class ConfigUtility { * query_params */ static bool matchQueryParams(const Http::Utility::QueryParams& query_params, - const std::vector& config_query_params); + const std::vector& config_query_params); /** * Returns the redirect HTTP Status Code enum parsed from proto. diff --git a/source/common/router/rds_impl.cc b/source/common/router/rds_impl.cc index e316460e0f..af180e8573 100644 --- a/source/common/router/rds_impl.cc +++ b/source/common/router/rds_impl.cc @@ -30,8 +30,8 @@ RouteConfigProviderPtr RouteConfigProviderUtil::create( return route_config_provider_manager.createStaticRouteConfigProvider(config.route_config(), factory_context); case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager::kRds: - return route_config_provider_manager.createRdsRouteConfigProvider(config.rds(), factory_context, - stat_prefix); + return route_config_provider_manager.createRdsRouteConfigProvider( + config.rds(), factory_context, stat_prefix, factory_context.initManager()); default: NOT_REACHED_GCOVR_EXCL_LINE; } @@ -92,13 +92,17 @@ void RdsRouteConfigSubscription::onConfigUpdate( if (!validateUpdateSize(resources.size())) { return; } - auto route_config = MessageUtil::anyConvert( - resources[0], validation_visitor_); - MessageUtil::validate(route_config); + auto route_config = MessageUtil::anyConvert(resources[0]); + MessageUtil::validate(route_config, validation_visitor_); if (route_config.name() != route_config_name_) { throw EnvoyException(fmt::format("Unexpected RDS configuration (expecting {}): {}", route_config_name_, route_config.name())); } + for (auto* provider : route_config_providers_) { + // This seems inefficient, though it is necessary to validate config in each context, + // especially when it comes with per_filter_config, + provider->validateConfig(route_config); + } if (config_update_info_->onRdsUpdate(route_config, version_info)) { stats_.config_reload_.inc(); @@ -120,6 +124,7 @@ void RdsRouteConfigSubscription::onConfigUpdate( } vhds_subscription_.release(); } + update_callback_manager_.runCallbacks(); } init_target_.ready(); @@ -194,8 +199,18 @@ Router::ConfigConstSharedPtr RdsRouteConfigProviderImpl::config() { void RdsRouteConfigProviderImpl::onConfigUpdate() { ConfigConstSharedPtr new_config( new ConfigImpl(config_update_info_->routeConfiguration(), factory_context_, false)); - tls_->runOnAllThreads( - [this, new_config]() -> void { tls_->getTyped().config_ = new_config; }); + tls_->runOnAllThreads([new_config](ThreadLocal::ThreadLocalObjectSharedPtr previous) + -> ThreadLocal::ThreadLocalObjectSharedPtr { + auto prev_config = std::dynamic_pointer_cast(previous); + prev_config->config_ = new_config; + return previous; + }); +} + +void RdsRouteConfigProviderImpl::validateConfig( + const envoy::api::v2::RouteConfiguration& config) const { + // TODO(lizan): consider cache the config here until onConfigUpdate. + ConfigImpl validation_config(config, factory_context_, false); } RouteConfigProviderManagerImpl::RouteConfigProviderManagerImpl(Server::Admin& admin) { @@ -208,8 +223,8 @@ RouteConfigProviderManagerImpl::RouteConfigProviderManagerImpl(Server::Admin& ad Router::RouteConfigProviderPtr RouteConfigProviderManagerImpl::createRdsRouteConfigProvider( const envoy::config::filter::network::http_connection_manager::v2::Rds& rds, - Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix) { - + Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix, + Init::Manager& init_manager) { // RdsRouteConfigSubscriptions are unique based on their serialized RDS config. const uint64_t manager_identifier = MessageUtil::hash(rds); @@ -222,9 +237,7 @@ Router::RouteConfigProviderPtr RouteConfigProviderManagerImpl::createRdsRouteCon // of simplicity. subscription.reset(new RdsRouteConfigSubscription(rds, manager_identifier, factory_context, stat_prefix, *this)); - - factory_context.initManager().add(subscription->init_target_); - + init_manager.add(subscription->init_target_); route_config_subscriptions_.insert({manager_identifier, subscription}); } else { // Because the RouteConfigProviderManager's weak_ptrs only get cleaned up diff --git a/source/common/router/rds_impl.h b/source/common/router/rds_impl.h index 6ec2c3e160..418512bb6e 100644 --- a/source/common/router/rds_impl.h +++ b/source/common/router/rds_impl.h @@ -22,6 +22,7 @@ #include "envoy/stats/scope.h" #include "envoy/thread_local/thread_local.h" +#include "common/common/callback_impl.h" #include "common/common/logger.h" #include "common/init/target_impl.h" #include "common/protobuf/utility.h" @@ -31,6 +32,9 @@ namespace Envoy { namespace Router { +// For friend class declaration in RdsRouteConfigSubscription. +class ScopedRdsConfigSubscription; + /** * Route configuration provider utilities. */ @@ -66,6 +70,7 @@ class StaticRouteConfigProviderImpl : public RouteConfigProvider { } SystemTime lastUpdated() const override { return last_updated_; } void onConfigUpdate() override {} + void validateConfig(const envoy::api::v2::RouteConfiguration&) const override {} private: ConfigConstSharedPtr config_; @@ -117,9 +122,11 @@ class RdsRouteConfigSubscription : Envoy::Config::SubscriptionCallbacks, void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; std::string resourceName(const ProtobufWkt::Any& resource) override { - return MessageUtil::anyConvert(resource, - validation_visitor_) - .name(); + return MessageUtil::anyConvert(resource).name(); + } + + Common::CallbackHandle* addUpdateCallback(std::function callback) { + return update_callback_manager_.add(callback); } RdsRouteConfigSubscription( @@ -143,8 +150,11 @@ class RdsRouteConfigSubscription : Envoy::Config::SubscriptionCallbacks, VhdsSubscriptionPtr vhds_subscription_; RouteConfigUpdatePtr config_update_info_; ProtobufMessage::ValidationVisitor& validation_visitor_; + Common::CallbackManager<> update_callback_manager_; friend class RouteConfigProviderManagerImpl; + // Access to addUpdateCallback + friend class ScopedRdsConfigSubscription; }; using RdsRouteConfigSubscriptionSharedPtr = std::shared_ptr; @@ -159,7 +169,6 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, ~RdsRouteConfigProviderImpl() override; RdsRouteConfigSubscription& subscription() { return *subscription_; } - void onConfigUpdate() override; // Router::RouteConfigProvider Router::ConfigConstSharedPtr config() override; @@ -167,6 +176,8 @@ class RdsRouteConfigProviderImpl : public RouteConfigProvider, return config_update_info_->configInfo(); } SystemTime lastUpdated() const override { return config_update_info_->lastUpdated(); } + void onConfigUpdate() override; + void validateConfig(const envoy::api::v2::RouteConfiguration& config) const override; private: struct ThreadLocalConfig : public ThreadLocal::ThreadLocalObject { @@ -195,8 +206,8 @@ class RouteConfigProviderManagerImpl : public RouteConfigProviderManager, // RouteConfigProviderManager RouteConfigProviderPtr createRdsRouteConfigProvider( const envoy::config::filter::network::http_connection_manager::v2::Rds& rds, - Server::Configuration::FactoryContext& factory_context, - const std::string& stat_prefix) override; + Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix, + Init::Manager& init_manager) override; RouteConfigProviderPtr createStaticRouteConfigProvider(const envoy::api::v2::RouteConfiguration& route_config, diff --git a/source/common/router/route_config_update_receiver_impl.cc b/source/common/router/route_config_update_receiver_impl.cc index ecaa5dbb20..81931e482c 100644 --- a/source/common/router/route_config_update_receiver_impl.cc +++ b/source/common/router/route_config_update_receiver_impl.cc @@ -60,9 +60,8 @@ void RouteConfigUpdateReceiverImpl::updateVhosts( const Protobuf::RepeatedPtrField& added_resources) { for (const auto& resource : added_resources) { envoy::api::v2::route::VirtualHost vhost = - MessageUtil::anyConvert(resource.resource(), - validation_visitor_); - MessageUtil::validate(vhost); + MessageUtil::anyConvert(resource.resource()); + MessageUtil::validate(vhost, validation_visitor_); auto found = vhosts.find(vhost.name()); if (found != vhosts.end()) { vhosts.erase(found); diff --git a/source/common/router/router.cc b/source/common/router/router.cc index 5ad636479a..412bed0392 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -27,6 +27,7 @@ #include "common/router/config_impl.h" #include "common/router/debug_config.h" #include "common/router/retry_state_impl.h" +#include "common/runtime/runtime_impl.h" #include "common/tracing/http_tracer_impl.h" #include "extensions/filters/http/well_known_names.h" @@ -931,15 +932,14 @@ Filter::streamResetReasonToResponseFlag(Http::StreamResetReason reset_reason) { NOT_REACHED_GCOVR_EXCL_LINE; } -void Filter::handleNon5xxResponseHeaders(const Http::HeaderMap& headers, - UpstreamRequest& upstream_request, bool end_stream) { +void Filter::handleNon5xxResponseHeaders(absl::optional grpc_status, + UpstreamRequest& upstream_request, bool end_stream, + uint64_t grpc_to_http_status) { // We need to defer gRPC success until after we have processed grpc-status in // the trailers. if (grpc_request_) { if (end_stream) { - absl::optional grpc_status = Grpc::Common::getGrpcStatus(headers); - if (grpc_status && - !Http::CodeUtility::is5xx(Grpc::Utility::grpcToHttpStatus(grpc_status.value()))) { + if (grpc_status && !Http::CodeUtility::is5xx(grpc_to_http_status)) { upstream_request.upstream_host_->stats().rq_success_.inc(); } else { upstream_request.upstream_host_->stats().rq_error_.inc(); @@ -1002,8 +1002,25 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::HeaderMapPtr&& head ENVOY_STREAM_LOG(debug, "upstream headers complete: end_stream={}", *callbacks_, end_stream); modify_headers_(*headers); + // When grpc-status appears in response headers, convert grpc-status to HTTP status code + // for outlier detection. This does not currently change any stats or logging and does not + // handle the case when an error grpc-status is sent as a trailer. + absl::optional grpc_status; + uint64_t grpc_to_http_status = 0; + if (grpc_request_) { + grpc_status = Grpc::Common::getGrpcStatus(*headers); + if (grpc_status.has_value()) { + grpc_to_http_status = Grpc::Utility::grpcToHttpStatus(grpc_status.value()); + } + } - upstream_request.upstream_host_->outlierDetector().putHttpResponseCode(response_code); + if (grpc_status.has_value() && + Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.outlier_detection_support_for_grpc_status")) { + upstream_request.upstream_host_->outlierDetector().putHttpResponseCode(grpc_to_http_status); + } else { + upstream_request.upstream_host_->outlierDetector().putHttpResponseCode(response_code); + } if (headers->EnvoyImmediateHealthCheckFail() != nullptr) { upstream_request.upstream_host_->healthChecker().setUnhealthy(); @@ -1090,7 +1107,7 @@ void Filter::onUpstreamHeaders(uint64_t response_code, Http::HeaderMapPtr&& head upstream_request.upstream_host_->canary(); chargeUpstreamCode(response_code, *headers, upstream_request.upstream_host_, false); if (!Http::CodeUtility::is5xx(response_code)) { - handleNon5xxResponseHeaders(*headers, upstream_request, end_stream); + handleNon5xxResponseHeaders(grpc_status, upstream_request, end_stream, grpc_to_http_status); } // Append routing cookies @@ -1538,7 +1555,10 @@ void Filter::UpstreamRequest::onPoolFailure(Http::ConnectionPool::PoolFailureRea } void Filter::UpstreamRequest::onPoolReady(Http::StreamEncoder& request_encoder, - Upstream::HostDescriptionConstSharedPtr host) { + Upstream::HostDescriptionConstSharedPtr host, + const StreamInfo::StreamInfo& info) { + // This may be called under an existing ScopeTrackerScopeState but it will unwind correctly. + ScopeTrackerScopeState scope(&parent_.callbacks_->scope(), parent_.callbacks_->dispatcher()); ENVOY_STREAM_LOG(debug, "pool ready", *parent_.callbacks_); host->outlierDetector().putResult(Upstream::Outlier::Result::LOCAL_ORIGIN_CONNECT_SUCCESS); @@ -1547,6 +1567,9 @@ void Filter::UpstreamRequest::onPoolReady(Http::StreamEncoder& request_encoder, onUpstreamHostSelected(host); request_encoder.getStream().addCallbacks(*this); + stream_info_.setUpstreamSslConnection(info.downstreamSslConnection()); + parent_.callbacks_->streamInfo().setUpstreamSslConnection(info.downstreamSslConnection()); + if (parent_.downstream_end_stream_) { setupPerTryTimeout(); } else { diff --git a/source/common/router/router.h b/source/common/router/router.h index fac9bbb07e..89de83d0cf 100644 --- a/source/common/router/router.h +++ b/source/common/router/router.h @@ -427,7 +427,8 @@ class Filter : Logger::Loggable, absl::string_view transport_failure_reason, Upstream::HostDescriptionConstSharedPtr host) override; void onPoolReady(Http::StreamEncoder& request_encoder, - Upstream::HostDescriptionConstSharedPtr host) override; + Upstream::HostDescriptionConstSharedPtr host, + const StreamInfo::StreamInfo& info) override; void setRequestEncoder(Http::StreamEncoder& request_encoder); void clearRequestEncoder(); @@ -535,8 +536,9 @@ class Filter : Logger::Loggable, void doRetry(); // 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, - UpstreamRequest& upstream_request, bool end_stream); + void handleNon5xxResponseHeaders(absl::optional grpc_status, + UpstreamRequest& upstream_request, bool end_stream, + uint64_t grpc_to_http_status); TimeSource& timeSource() { return config_.timeSource(); } Http::Context& httpContext() { return config_.http_context_; } diff --git a/source/common/router/router_ratelimit.cc b/source/common/router/router_ratelimit.cc index f882783636..28519851ea 100644 --- a/source/common/router/router_ratelimit.cc +++ b/source/common/router/router_ratelimit.cc @@ -67,11 +67,8 @@ bool GenericKeyAction::populateDescriptor(const Router::RouteEntry&, HeaderValueMatchAction::HeaderValueMatchAction( const envoy::api::v2::route::RateLimit::Action::HeaderValueMatch& action) : descriptor_value_(action.descriptor_value()), - expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)) { - for (const auto& header_matcher : action.headers()) { - action_headers_.push_back(header_matcher); - } -} + expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)), + action_headers_(Http::HeaderUtility::buildHeaderDataVector(action.headers())) {} bool HeaderValueMatchAction::populateDescriptor(const Router::RouteEntry&, RateLimit::Descriptor& descriptor, diff --git a/source/common/router/router_ratelimit.h b/source/common/router/router_ratelimit.h index e15f493ddb..7f3a368ffb 100644 --- a/source/common/router/router_ratelimit.h +++ b/source/common/router/router_ratelimit.h @@ -97,7 +97,7 @@ class HeaderValueMatchAction : public RateLimitAction { private: const std::string descriptor_value_; const bool expect_match_; - std::vector action_headers_; + const std::vector action_headers_; }; /* diff --git a/source/common/router/scoped_config_impl.cc b/source/common/router/scoped_config_impl.cc index f5c8007f9e..c6f00f58ea 100644 --- a/source/common/router/scoped_config_impl.cc +++ b/source/common/router/scoped_config_impl.cc @@ -10,13 +10,7 @@ bool ScopeKey::operator==(const ScopeKey& other) const { // An empty key equals to nothing, "NULL" != "NULL". return false; } - return std::equal(fragments_.begin(), fragments_.end(), other.fragments_.begin(), - other.fragments_.end(), - [](const std::unique_ptr& left, - const std::unique_ptr& right) -> bool { - // Both should be non-NULL now. - return *left == *right; - }); + return this->hash() == other.hash(); } HeaderValueExtractorImpl::HeaderValueExtractorImpl( @@ -76,6 +70,22 @@ HeaderValueExtractorImpl::computeFragment(const Http::HeaderMap& headers) const return nullptr; } +ScopedRouteInfo::ScopedRouteInfo(envoy::api::v2::ScopedRouteConfiguration&& config_proto, + ConfigConstSharedPtr&& route_config) + : config_proto_(std::move(config_proto)), route_config_(std::move(route_config)) { + // TODO(stevenzzzz): Maybe worth a KeyBuilder abstraction when there are more than one type of + // Fragment. + for (const auto& fragment : config_proto_.key().fragments()) { + switch (fragment.type_case()) { + case envoy::api::v2::ScopedRouteConfiguration::Key::Fragment::kStringKey: + scope_key_.addFragment(std::make_unique(fragment.string_key())); + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + } +} + ScopeKeyBuilderImpl::ScopeKeyBuilderImpl(ScopedRoutes::ScopeKeyBuilder&& config) : ScopeKeyBuilderBase(std::move(config)) { for (const auto& fragment_builder : config_.fragments()) { @@ -104,12 +114,37 @@ ScopeKeyBuilderImpl::computeScopeKey(const Http::HeaderMap& headers) const { return std::make_unique(std::move(key)); } -void ScopedConfigImpl::addOrUpdateRoutingScope(const ScopedRouteInfoConstSharedPtr&) {} +void ScopedConfigImpl::addOrUpdateRoutingScope( + const ScopedRouteInfoConstSharedPtr& scoped_route_info) { + const auto iter = scoped_route_info_by_name_.find(scoped_route_info->scopeName()); + if (iter != scoped_route_info_by_name_.end()) { + ASSERT(scoped_route_info_by_key_.contains(iter->second->scopeKey().hash())); + scoped_route_info_by_key_.erase(iter->second->scopeKey().hash()); + } + scoped_route_info_by_name_[scoped_route_info->scopeName()] = scoped_route_info; + scoped_route_info_by_key_[scoped_route_info->scopeKey().hash()] = scoped_route_info; +} -void ScopedConfigImpl::removeRoutingScope(const std::string&) {} +void ScopedConfigImpl::removeRoutingScope(const std::string& scope_name) { + const auto iter = scoped_route_info_by_name_.find(scope_name); + if (iter != scoped_route_info_by_name_.end()) { + ASSERT(scoped_route_info_by_key_.contains(iter->second->scopeKey().hash())); + scoped_route_info_by_key_.erase(iter->second->scopeKey().hash()); + scoped_route_info_by_name_.erase(iter); + } +} -Router::ConfigConstSharedPtr ScopedConfigImpl::getRouteConfig(const Http::HeaderMap&) const { - return std::make_shared(); +Router::ConfigConstSharedPtr +ScopedConfigImpl::getRouteConfig(const Http::HeaderMap& headers) const { + std::unique_ptr scope_key = scope_key_builder_.computeScopeKey(headers); + if (scope_key == nullptr) { + return nullptr; + } + auto iter = scoped_route_info_by_key_.find(scope_key->hash()); + if (iter != scoped_route_info_by_key_.end()) { + return iter->second->routeConfig(); + } + return nullptr; } } // namespace Router diff --git a/source/common/router/scoped_config_impl.h b/source/common/router/scoped_config_impl.h index 184929f946..91e67e841b 100644 --- a/source/common/router/scoped_config_impl.h +++ b/source/common/router/scoped_config_impl.h @@ -4,13 +4,17 @@ #include "envoy/api/v2/srds.pb.h" #include "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.pb.h" +#include "envoy/router/rds.h" #include "envoy/router/router.h" #include "envoy/router/scopes.h" #include "envoy/thread_local/thread_local.h" +#include "common/common/hash.h" #include "common/protobuf/utility.h" #include "common/router/config_impl.h" -#include "common/router/scoped_config_manager.h" + +#include "absl/numeric/int128.h" +#include "absl/strings/str_format.h" namespace Envoy { namespace Router { @@ -26,15 +30,14 @@ class ScopeKeyFragmentBase { bool operator==(const ScopeKeyFragmentBase& other) const { if (typeid(*this) == typeid(other)) { - return equals(other); + return hash() == other.hash(); } return false; } virtual ~ScopeKeyFragmentBase() = default; -private: - // Returns true if the two fragments equal else false. - virtual bool equals(const ScopeKeyFragmentBase&) const PURE; + // Hash of the fragment. + virtual uint64_t hash() const PURE; }; /** @@ -52,28 +55,39 @@ class ScopeKey { // Caller should guarantee the fragment is not nullptr. void addFragment(std::unique_ptr&& fragment) { ASSERT(fragment != nullptr, "null fragment not allowed in ScopeKey."); + updateHash(*fragment); fragments_.emplace_back(std::move(fragment)); } + uint64_t hash() const { return hash_; } bool operator!=(const ScopeKey& other) const; - bool operator==(const ScopeKey& other) const; private: + // Update the key's hash with the new fragment hash. + void updateHash(const ScopeKeyFragmentBase& fragment) { + std::stringbuf buffer; + buffer.sputn(reinterpret_cast(&hash_), sizeof(hash_)); + const auto& fragment_hash = fragment.hash(); + buffer.sputn(reinterpret_cast(&fragment_hash), sizeof(fragment_hash)); + hash_ = HashUtil::xxHash64(buffer.str()); + } + + uint64_t hash_{0}; std::vector> fragments_; }; // String fragment. class StringKeyFragment : public ScopeKeyFragmentBase { public: - explicit StringKeyFragment(absl::string_view value) : value_(value) {} + explicit StringKeyFragment(absl::string_view value) + : value_(value), hash_(HashUtil::xxHash64(value_)) {} -private: - bool equals(const ScopeKeyFragmentBase& other) const override { - return value_ == static_cast(other).value_; - } + uint64_t hash() const override { return hash_; } +private: const std::string value_; + const uint64_t hash_; }; /** @@ -132,9 +146,27 @@ class ScopeKeyBuilderImpl : public ScopeKeyBuilderBase { std::vector> fragment_builders_; }; +// ScopedRouteConfiguration and corresponding RouteConfigProvider. +class ScopedRouteInfo { +public: + ScopedRouteInfo(envoy::api::v2::ScopedRouteConfiguration&& config_proto, + ConfigConstSharedPtr&& route_config); + + const ConfigConstSharedPtr& routeConfig() const { return route_config_; } + const ScopeKey& scopeKey() const { return scope_key_; } + const envoy::api::v2::ScopedRouteConfiguration& configProto() const { return config_proto_; } + const std::string& scopeName() const { return config_proto_.name(); } + +private: + envoy::api::v2::ScopedRouteConfiguration config_proto_; + ScopeKey scope_key_; + ConfigConstSharedPtr route_config_; +}; +using ScopedRouteInfoConstSharedPtr = std::shared_ptr; +// Ordered map for consistent config dumping. +using ScopedRouteMap = std::map; + /** - * TODO(AndresGuedez): implement scoped routing logic. - * * Each Envoy worker is assigned an instance of this type. When config updates are received, * addOrUpdateRoutingScope() and removeRoutingScope() are called to update the set of scoped routes. * @@ -153,8 +185,11 @@ class ScopedConfigImpl : public ScopedConfig { Router::ConfigConstSharedPtr getRouteConfig(const Http::HeaderMap& headers) const override; private: - const envoy::config::filter::network::http_connection_manager::v2::ScopedRoutes::ScopeKeyBuilder - scope_key_builder_; + ScopeKeyBuilderImpl scope_key_builder_; + // From scope name to cached ScopedRouteInfo. + absl::flat_hash_map scoped_route_info_by_name_; + // Hash by ScopeKey hash to lookup in constant time. + absl::flat_hash_map scoped_route_info_by_key_; }; /** diff --git a/source/common/router/scoped_config_manager.cc b/source/common/router/scoped_config_manager.cc deleted file mode 100644 index 2a5b75f3b2..0000000000 --- a/source/common/router/scoped_config_manager.cc +++ /dev/null @@ -1,22 +0,0 @@ -#include "common/router/scoped_config_manager.h" - -#include "envoy/common/exception.h" - -#include "common/common/fmt.h" - -namespace Envoy { -namespace Router { - -ScopedRouteInfoConstSharedPtr ScopedConfigManager::addOrUpdateRoutingScope( - const envoy::api::v2::ScopedRouteConfiguration& config_proto, const std::string&) { - auto scoped_route_info = std::make_shared(config_proto); - scoped_route_map_[config_proto.name()] = scoped_route_info; - return scoped_route_info; -} - -bool ScopedConfigManager::removeRoutingScope(const std::string& name) { - return scoped_route_map_.erase(name) == 0; -} - -} // namespace Router -} // namespace Envoy diff --git a/source/common/router/scoped_config_manager.h b/source/common/router/scoped_config_manager.h deleted file mode 100644 index 5f8dd6fda8..0000000000 --- a/source/common/router/scoped_config_manager.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include - -#include "envoy/api/v2/srds.pb.h" - -namespace Envoy { -namespace Router { - -// The internal representation of the configuration distributed via the ScopedRouteConfiguration -// proto. -class ScopedRouteInfo { -public: - ScopedRouteInfo(const envoy::api::v2::ScopedRouteConfiguration& config_proto) - : config_proto_(config_proto) {} - - // TODO(AndresGuedez): Add the necessary APIs required for the scoped routing logic. - - const envoy::api::v2::ScopedRouteConfiguration config_proto_; -}; -using ScopedRouteInfoConstSharedPtr = std::shared_ptr; - -// A manager for routing configuration scopes. -// An instance of the manager is owned by each ScopedRdsConfigSubscription. When config updates are -// received (on the main thread), the manager is called to track changes to the set of scoped route -// configurations and build s as needed. -class ScopedConfigManager { -public: - // Ordered map for consistent config dumping. - using ScopedRouteMap = std::map; - - // Adds/updates a routing scope specified via the Scoped RDS API. This scope will be added to the - // set of scopes matched against the scope keys built for each HTTP request. - ScopedRouteInfoConstSharedPtr - addOrUpdateRoutingScope(const envoy::api::v2::ScopedRouteConfiguration& scoped_route_config, - const std::string& version_info); - - // Removes a routing scope from the set of scopes matched against each HTTP request. - bool removeRoutingScope(const std::string& scope_name); - - const ScopedRouteMap& scopedRouteMap() const { return scoped_route_map_; } - -private: - ScopedRouteMap scoped_route_map_; -}; - -} // namespace Router -} // namespace Envoy diff --git a/source/common/router/scoped_rds.cc b/source/common/router/scoped_rds.cc index e30ea4296b..9c71054093 100644 --- a/source/common/router/scoped_rds.cc +++ b/source/common/router/scoped_rds.cc @@ -6,7 +6,12 @@ #include "envoy/api/v2/srds.pb.validate.h" #include "common/common/assert.h" +#include "common/common/cleanup.h" #include "common/common/logger.h" +#include "common/common/utility.h" +#include "common/config/resources.h" +#include "common/init/manager_impl.h" +#include "common/init/watcher_impl.h" // Types are deeply nested under Envoy::Config::ConfigProvider; use 'using-directives' across all // ConfigProvider related types for consistency. @@ -17,9 +22,7 @@ using Envoy::Config::ConfigProviderPtr; namespace Envoy { namespace Router { - namespace ScopedRoutesConfigProviderUtil { - ConfigProviderPtr create(const envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& config, @@ -79,13 +82,17 @@ ScopedRdsConfigSubscription::ScopedRdsConfigSubscription( const envoy::config::filter::network::http_connection_manager::v2::ScopedRoutes:: ScopeKeyBuilder& scope_key_builder, Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix, + envoy::api::v2::core::ConfigSource rds_config_source, + RouteConfigProviderManager& route_config_provider_manager, ScopedRoutesConfigProviderManager& config_provider_manager) : DeltaConfigSubscriptionInstance("SRDS", manager_identifier, config_provider_manager, factory_context), - name_(name), scope_key_builder_(scope_key_builder), + factory_context_(factory_context), name_(name), scope_key_builder_(scope_key_builder), scope_(factory_context.scope().createScope(stat_prefix + "scoped_rds." + name + ".")), stats_({ALL_SCOPED_RDS_STATS(POOL_COUNTER(*scope_))}), - validation_visitor_(factory_context.messageValidationVisitor()) { + rds_config_source_(std::move(rds_config_source)), + validation_visitor_(factory_context.messageValidationVisitor()), stat_prefix_(stat_prefix), + route_config_provider_manager_(route_config_provider_manager) { subscription_ = factory_context.clusterManager().subscriptionFactory().subscriptionFromConfigSource( scoped_rds.scoped_rds_config_source(), @@ -100,72 +107,220 @@ ScopedRdsConfigSubscription::ScopedRdsConfigSubscription( }); } +ScopedRdsConfigSubscription::RdsRouteConfigProviderHelper::RdsRouteConfigProviderHelper( + ScopedRdsConfigSubscription& parent, std::string scope_name, + envoy::config::filter::network::http_connection_manager::v2::Rds& rds, + Init::Manager& init_manager) + : parent_(parent), scope_name_(scope_name), + route_provider_(static_cast( + parent_.route_config_provider_manager_ + .createRdsRouteConfigProvider(rds, parent_.factory_context_, parent_.stat_prefix_, + init_manager) + .release())), + rds_update_callback_handle_(route_provider_->subscription().addUpdateCallback([this]() { + // Subscribe to RDS update. + parent_.onRdsConfigUpdate(scope_name_, route_provider_->subscription()); + })) {} + +bool ScopedRdsConfigSubscription::addOrUpdateScopes( + const Protobuf::RepeatedPtrField& resources, + Init::Manager& init_manager, const std::string& version_info, + std::vector& exception_msgs) { + bool any_applied = false; + envoy::config::filter::network::http_connection_manager::v2::Rds rds; + rds.mutable_config_source()->MergeFrom(rds_config_source_); + absl::flat_hash_set unique_resource_names; + for (const auto& resource : resources) { + envoy::api::v2::ScopedRouteConfiguration scoped_route_config; + try { + scoped_route_config = + MessageUtil::anyConvert(resource.resource()); + MessageUtil::validate(scoped_route_config, validation_visitor_); + const std::string scope_name = scoped_route_config.name(); + if (!unique_resource_names.insert(scope_name).second) { + throw EnvoyException( + fmt::format("duplicate scoped route configuration '{}' found", scope_name)); + } + // TODO(stevenzzz): Creating a new RdsRouteConfigProvider likely expensive, migrate RDS to + // config-provider-framework to make it light weight. + rds.set_route_config_name(scoped_route_config.route_configuration_name()); + auto rds_config_provider_helper = + std::make_unique(*this, scope_name, rds, init_manager); + auto scoped_route_info = std::make_shared( + std::move(scoped_route_config), rds_config_provider_helper->routeConfig()); + // Detect if there is key conflict between two scopes, in which case Envoy won't be able to + // tell which RouteConfiguration to use. Reject the second scope in the delta form API. + auto iter = scope_name_by_hash_.find(scoped_route_info->scopeKey().hash()); + if (iter != scope_name_by_hash_.end()) { + if (iter->second != scoped_route_info->scopeName()) { + throw EnvoyException( + fmt::format("scope key conflict found, first scope is '{}', second scope is '{}'", + iter->second, scoped_route_info->scopeName())); + } + } + // NOTE: delete previous route provider if any. + route_provider_by_scope_.insert({scope_name, std::move(rds_config_provider_helper)}); + scope_name_by_hash_[scoped_route_info->scopeKey().hash()] = scoped_route_info->scopeName(); + scoped_route_map_[scoped_route_info->scopeName()] = scoped_route_info; + applyConfigUpdate([scoped_route_info](ConfigProvider::ConfigConstSharedPtr config) + -> ConfigProvider::ConfigConstSharedPtr { + auto* thread_local_scoped_config = + const_cast(static_cast(config.get())); + thread_local_scoped_config->addOrUpdateRoutingScope(scoped_route_info); + return config; + }); + any_applied = true; + ENVOY_LOG(debug, "srds: add/update scoped_route '{}', version: {}", + scoped_route_info->scopeName(), version_info); + } catch (const EnvoyException& e) { + exception_msgs.emplace_back(fmt::format("{}", e.what())); + } + } + return any_applied; +} + +bool ScopedRdsConfigSubscription::removeScopes( + const Protobuf::RepeatedPtrField& scope_names, const std::string& version_info) { + bool any_applied = false; + for (const auto& scope_name : scope_names) { + auto iter = scoped_route_map_.find(scope_name); + if (iter != scoped_route_map_.end()) { + route_provider_by_scope_.erase(scope_name); + scope_name_by_hash_.erase(iter->second->scopeKey().hash()); + scoped_route_map_.erase(iter); + applyConfigUpdate([scope_name](ConfigProvider::ConfigConstSharedPtr config) + -> ConfigProvider::ConfigConstSharedPtr { + auto* thread_local_scoped_config = + const_cast(static_cast(config.get())); + thread_local_scoped_config->removeRoutingScope(scope_name); + return config; + }); + any_applied = true; + ENVOY_LOG(debug, "srds: remove scoped route '{}', version: {}", scope_name, version_info); + } + } + return any_applied; +} + void ScopedRdsConfigSubscription::onConfigUpdate( - const Protobuf::RepeatedPtrField& resources, + const Protobuf::RepeatedPtrField& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, const std::string& version_info) { - std::vector scoped_routes; - for (const auto& resource_any : resources) { - scoped_routes.emplace_back(MessageUtil::anyConvert( - resource_any, validation_visitor_)); + // If new route config sources come after the factory_context_.initManager()'s initialize() been + // called, that initManager can't accept new targets. Instead we use a local override which will + // start new subscriptions but not wait on them to be ready. + // NOTE: For now we use a local init-manager, in the future when Envoy supports on-demand xDS, we + // will probably make this init-manager as a member of the subscription. + std::unique_ptr noop_init_manager; + // NOTE: This should be defined after noop_init_manager as it depends on the + // noop_init_manager. + std::unique_ptr resume_rds; + if (factory_context_.initManager().state() == Init::Manager::State::Initialized) { + noop_init_manager = + std::make_unique(fmt::format("SRDS {}:{}", name_, version_info)); + // Pause RDS to not send a burst of RDS requests until we start all the new subscriptions. + // In the case if factory_context_.initManager() is uninitialized, RDS is already paused either + // by Server init or LDS init. + factory_context_.clusterManager().adsMux().pause( + Envoy::Config::TypeUrl::get().RouteConfiguration); + resume_rds = std::make_unique([this, &noop_init_manager, version_info] { + // For new RDS subscriptions created after listener warming up, we don't wait for them to warm + // up. + Init::WatcherImpl noop_watcher( + // Note: we just throw it away. + fmt::format("SRDS ConfigUpdate watcher {}:{}", name_, version_info), + []() { /*Do nothing.*/ }); + noop_init_manager->initialize(noop_watcher); + // New RDS subscriptions should have been created, now lift the floodgate. + // Note in the case of partial acceptance, accepted RDS subscriptions should be started + // despite of any error. + factory_context_.clusterManager().adsMux().resume( + Envoy::Config::TypeUrl::get().RouteConfiguration); + }); } + std::vector exception_msgs; + bool any_applied = addOrUpdateScopes( + added_resources, + (noop_init_manager == nullptr ? factory_context_.initManager() : *noop_init_manager), + version_info, exception_msgs); + any_applied = removeScopes(removed_resources, version_info) || any_applied; + ConfigSubscriptionCommonBase::onConfigUpdate(); + if (any_applied) { + setLastConfigInfo(absl::optional({absl::nullopt, version_info})); + } + stats_.config_reload_.inc(); + if (!exception_msgs.empty()) { + throw EnvoyException(fmt::format("Error adding/updating scoped route(s): {}", + StringUtil::join(exception_msgs, ", "))); + } +} + +void ScopedRdsConfigSubscription::onRdsConfigUpdate(const std::string& scope_name, + RdsRouteConfigSubscription& rds_subscription) { + auto iter = scoped_route_map_.find(scope_name); + ASSERT(iter != scoped_route_map_.end(), + fmt::format("trying to update route config for non-existing scope {}", scope_name)); + auto new_scoped_route_info = std::make_shared( + envoy::api::v2::ScopedRouteConfiguration(iter->second->configProto()), + std::make_shared(rds_subscription.routeConfigUpdate()->routeConfiguration(), + factory_context_, false)); + applyConfigUpdate([new_scoped_route_info](ConfigProvider::ConfigConstSharedPtr config) + -> ConfigProvider::ConfigConstSharedPtr { + auto* thread_local_scoped_config = + const_cast(static_cast(config.get())); + thread_local_scoped_config->addOrUpdateRoutingScope(new_scoped_route_info); + return config; + }); +} - std::unordered_set resource_names; - for (const auto& scoped_route : scoped_routes) { - if (!resource_names.insert(scoped_route.name()).second) { +// TODO(stevenzzzz): see issue #7508, consider generalizing this function as it overlaps with +// CdsApiImpl::onConfigUpdate. +void ScopedRdsConfigSubscription::onConfigUpdate( + const Protobuf::RepeatedPtrField& resources, + const std::string& version_info) { + absl::flat_hash_map scoped_routes; + absl::flat_hash_map scope_name_by_key_hash; + for (const auto& resource_any : resources) { + // Throws (thus rejects all) on any error. + auto scoped_route = + MessageUtil::anyConvert(resource_any); + MessageUtil::validate(scoped_route, validation_visitor_); + const std::string scope_name = scoped_route.name(); + auto scope_config_inserted = scoped_routes.try_emplace(scope_name, std::move(scoped_route)); + if (!scope_config_inserted.second) { throw EnvoyException( - fmt::format("duplicate scoped route configuration {} found", scoped_route.name())); + fmt::format("duplicate scoped route configuration '{}' found", scope_name)); + } + const envoy::api::v2::ScopedRouteConfiguration& scoped_route_config = + scope_config_inserted.first->second; + const uint64_t key_fingerprint = MessageUtil::hash(scoped_route_config.key()); + if (!scope_name_by_key_hash.try_emplace(key_fingerprint, scope_name).second) { + throw EnvoyException( + fmt::format("scope key conflict found, first scope is '{}', second scope is '{}'", + scope_name_by_key_hash[key_fingerprint], scope_name)); } } - for (const auto& scoped_route : scoped_routes) { - MessageUtil::validate(scoped_route); - } - - // TODO(AndresGuedez): refactor such that it can be shared with other delta APIs (e.g., CDS). - std::vector exception_msgs; - // We need to keep track of which scoped routes we might need to remove. - ScopedConfigManager::ScopedRouteMap scoped_routes_to_remove = - scoped_config_manager_.scopedRouteMap(); - for (auto& scoped_route : scoped_routes) { - const std::string& scoped_route_name = scoped_route.name(); - scoped_routes_to_remove.erase(scoped_route_name); - ScopedRouteInfoConstSharedPtr scoped_route_info = - scoped_config_manager_.addOrUpdateRoutingScope(scoped_route, version_info); - ENVOY_LOG(debug, "srds: add/update scoped_route '{}'", scoped_route_name); - applyConfigUpdate([scoped_route_info](const ConfigProvider::ConfigConstSharedPtr& config) - -> ConfigProvider::ConfigConstSharedPtr { - auto* thread_local_scoped_config = - const_cast(static_cast(config.get())); - thread_local_scoped_config->addOrUpdateRoutingScope(scoped_route_info); - return config; - }); + ScopedRouteMap scoped_routes_to_remove = scoped_route_map_; + Protobuf::RepeatedPtrField to_add_repeated; + Protobuf::RepeatedPtrField to_remove_repeated; + for (auto& iter : scoped_routes) { + const std::string& scope_name = iter.first; + scoped_routes_to_remove.erase(scope_name); + auto* to_add = to_add_repeated.Add(); + to_add->set_name(scope_name); + to_add->set_version(version_info); + to_add->mutable_resource()->PackFrom(iter.second); } for (const auto& scoped_route : scoped_routes_to_remove) { - const std::string scoped_route_name = scoped_route.first; - ENVOY_LOG(debug, "srds: remove scoped route '{}'", scoped_route_name); - scoped_config_manager_.removeRoutingScope(scoped_route_name); - applyConfigUpdate([scoped_route_name](const ConfigProvider::ConfigConstSharedPtr& config) - -> ConfigProvider::ConfigConstSharedPtr { - // In place update. - auto* thread_local_scoped_config = - const_cast(static_cast(config.get())); - thread_local_scoped_config->removeRoutingScope(scoped_route_name); - return config; - }); + *to_remove_repeated.Add() = scoped_route.first; } - - DeltaConfigSubscriptionInstance::onConfigUpdate(); - setLastConfigInfo(absl::optional({absl::nullopt, version_info})); - stats_.config_reload_.inc(); + onConfigUpdate(to_add_repeated, to_remove_repeated, version_info); } ScopedRdsConfigProvider::ScopedRdsConfigProvider( - ScopedRdsConfigSubscriptionSharedPtr&& subscription, - envoy::api::v2::core::ConfigSource rds_config_source) - : MutableConfigProviderCommonBase(std::move(subscription), ConfigProvider::ApiType::Delta), - subscription_(static_cast( - MutableConfigProviderCommonBase::subscription_.get())), - rds_config_source_(std::move(rds_config_source)) {} + ScopedRdsConfigSubscriptionSharedPtr&& subscription) + : MutableConfigProviderCommonBase(std::move(subscription), ConfigProvider::ApiType::Delta) {} ProtobufTypes::MessagePtr ScopedRoutesConfigProviderManager::dumpConfigs() const { auto config_dump = std::make_unique(); @@ -179,10 +334,9 @@ ProtobufTypes::MessagePtr ScopedRoutesConfigProviderManager::dumpConfigs() const const ScopedRdsConfigSubscription* typed_subscription = static_cast(subscription.get()); dynamic_config->set_name(typed_subscription->name()); - const ScopedConfigManager::ScopedRouteMap& scoped_route_map = - typed_subscription->scopedRouteMap(); + const ScopedRouteMap& scoped_route_map = typed_subscription->scopedRouteMap(); for (const auto& it : scoped_route_map) { - dynamic_config->mutable_scoped_route_configs()->Add()->MergeFrom(it.second->config_proto_); + dynamic_config->mutable_scoped_route_configs()->Add()->MergeFrom(it.second->configProto()); } TimestampUtil::systemClockToTimestamp(subscription->lastUpdated(), *dynamic_config->mutable_last_updated()); @@ -223,11 +377,13 @@ ConfigProviderPtr ScopedRoutesConfigProviderManager::createXdsConfigProvider( return std::make_shared( scoped_rds_config_source, manager_identifier, typed_optarg.scoped_routes_name_, typed_optarg.scope_key_builder_, factory_context, stat_prefix, + typed_optarg.rds_config_source_, + static_cast(config_provider_manager) + .route_config_provider_manager(), static_cast(config_provider_manager)); }); - return std::make_unique(std::move(subscription), - typed_optarg.rds_config_source_); + return std::make_unique(std::move(subscription)); } ConfigProviderPtr ScopedRoutesConfigProviderManager::createStaticConfigProvider( diff --git a/source/common/router/scoped_rds.h b/source/common/router/scoped_rds.h index 4fd6c72c3f..878cca0a32 100644 --- a/source/common/router/scoped_rds.h +++ b/source/common/router/scoped_rds.h @@ -3,10 +3,15 @@ #include #include "envoy/api/v2/srds.pb.h" +#include "envoy/common/callback.h" #include "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.pb.h" +#include "envoy/config/subscription.h" +#include "envoy/router/route_config_provider_manager.h" #include "envoy/stats/scope.h" #include "common/config/config_provider_impl.h" +#include "common/init/manager_impl.h" +#include "common/router/rds_impl.h" #include "common/router/scoped_config_impl.h" namespace Envoy { @@ -89,45 +94,90 @@ class ScopedRdsConfigSubscription : public Envoy::Config::DeltaConfigSubscriptio const envoy::config::filter::network::http_connection_manager::v2::ScopedRoutes:: ScopeKeyBuilder& scope_key_builder, Server::Configuration::FactoryContext& factory_context, const std::string& stat_prefix, + envoy::api::v2::core::ConfigSource rds_config_source, + RouteConfigProviderManager& route_config_provider_manager, ScopedRoutesConfigProviderManager& config_provider_manager); ~ScopedRdsConfigSubscription() override = default; const std::string& name() const { return name_; } - const ScopedConfigManager::ScopedRouteMap& scopedRouteMap() const { - return scoped_config_manager_.scopedRouteMap(); - } + const ScopedRouteMap& scopedRouteMap() const { return scoped_route_map_; } private: + // A helper class that takes care the life cycle management of a RDS route provider and the + // update callback handle. + struct RdsRouteConfigProviderHelper { + RdsRouteConfigProviderHelper( + ScopedRdsConfigSubscription& parent, std::string scope_name, + envoy::config::filter::network::http_connection_manager::v2::Rds& rds, + Init::Manager& init_manager); + ~RdsRouteConfigProviderHelper() { rds_update_callback_handle_->remove(); } + ConfigConstSharedPtr routeConfig() { return route_provider_->config(); } + + ScopedRdsConfigSubscription& parent_; + std::string scope_name_; + std::unique_ptr route_provider_; + // This handle_ is owned by the route config provider's RDS subscription, when the helper + // destructs, the handle is deleted as well. + Common::CallbackHandle* rds_update_callback_handle_; + }; + + // Adds or updates scopes, create a new RDS provider for each resource, if an exception is thrown + // during updating, the exception message is collected via the exception messages vector. + // Returns true if any scope updated, false otherwise. + bool addOrUpdateScopes(const Protobuf::RepeatedPtrField& resources, + Init::Manager& init_manager, const std::string& version_info, + std::vector& exception_msgs); + // Removes given scopes from the managed set of scopes. + // Returns true if any scope updated, false otherwise. + bool removeScopes(const Protobuf::RepeatedPtrField& scope_names, + const std::string& version_info); + // Envoy::Config::DeltaConfigSubscriptionInstance void start() override { subscription_->start({}); } // Envoy::Config::SubscriptionCallbacks + + // NOTE: state-of-the-world form onConfigUpdate(resources, version_info) will throw an + // EnvoyException on any error and essentially reject an update. While the Delta form + // onConfigUpdate(added_resources, removed_resources, version_info) by design will partially + // accept correct RouteConfiguration from management server. void onConfigUpdate(const Protobuf::RepeatedPtrField& resources, const std::string& version_info) override; - void onConfigUpdate(const Protobuf::RepeatedPtrField&, - const Protobuf::RepeatedPtrField&, const std::string&) override { - NOT_IMPLEMENTED_GCOVR_EXCL_LINE; - } + void onConfigUpdate(const Protobuf::RepeatedPtrField& added_resources, + const Protobuf::RepeatedPtrField& removed_resources, + const std::string& version_info) override; void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason, const EnvoyException*) override { - ConfigSubscriptionCommonBase::onConfigUpdateFailed(); + DeltaConfigSubscriptionInstance::onConfigUpdateFailed(); } std::string resourceName(const ProtobufWkt::Any& resource) override { - return MessageUtil::anyConvert(resource, - validation_visitor_) - .name(); + return MessageUtil::anyConvert(resource).name(); } - + // Propagate RDS updates to ScopeConfigImpl in workers. + void onRdsConfigUpdate(const std::string& scope_name, + RdsRouteConfigSubscription& rds_subscription); + + // ScopedRouteInfo by scope name. + ScopedRouteMap scoped_route_map_; + // RdsRouteConfigProvider by scope name. + absl::flat_hash_map> + route_provider_by_scope_; + // A map of (hash, scope-name), used to detect the key conflict between scopes. + absl::flat_hash_map scope_name_by_hash_; + // For creating RDS subscriptions. + Server::Configuration::FactoryContext& factory_context_; const std::string name_; std::unique_ptr subscription_; const envoy::config::filter::network::http_connection_manager::v2::ScopedRoutes::ScopeKeyBuilder scope_key_builder_; Stats::ScopePtr scope_; ScopedRdsStats stats_; - ScopedConfigManager scoped_config_manager_; + const envoy::api::v2::core::ConfigSource rds_config_source_; ProtobufMessage::ValidationVisitor& validation_visitor_; + const std::string stat_prefix_; + RouteConfigProviderManager& route_config_provider_manager_; }; using ScopedRdsConfigSubscriptionSharedPtr = std::shared_ptr; @@ -136,22 +186,21 @@ using ScopedRdsConfigSubscriptionSharedPtr = std::shared_ptr(subscription_.get()); + } }; // A ConfigProviderManager for scoped routing configuration that creates static/inline and dynamic // (xds) config providers. class ScopedRoutesConfigProviderManager : public Envoy::Config::ConfigProviderManagerImplBase { public: - ScopedRoutesConfigProviderManager(Server::Admin& admin) - : Envoy::Config::ConfigProviderManagerImplBase(admin, "route_scopes") {} + ScopedRoutesConfigProviderManager( + Server::Admin& admin, Router::RouteConfigProviderManager& route_config_provider_manager) + : Envoy::Config::ConfigProviderManagerImplBase(admin, "route_scopes"), + route_config_provider_manager_(route_config_provider_manager) {} ~ScopedRoutesConfigProviderManager() override = default; @@ -176,6 +225,13 @@ class ScopedRoutesConfigProviderManager : public Envoy::Config::ConfigProviderMa std::vector>&& config_protos, Server::Configuration::FactoryContext& factory_context, const Envoy::Config::ConfigProviderManager::OptionalArg& optarg) override; + + RouteConfigProviderManager& route_config_provider_manager() { + return route_config_provider_manager_; + } + +private: + RouteConfigProviderManager& route_config_provider_manager_; }; // The optional argument passed to the ConfigProviderManager::create*() functions. diff --git a/source/common/router/vhds.cc b/source/common/router/vhds.cc index f48449f678..6cd05ed015 100644 --- a/source/common/router/vhds.cc +++ b/source/common/router/vhds.cc @@ -28,8 +28,7 @@ VhdsSubscription::VhdsSubscription(RouteConfigUpdatePtr& config_update_info, scope_(factory_context.scope().createScope(stat_prefix + "vhds." + config_update_info_->routeConfigName() + ".")), stats_({ALL_VHDS_STATS(POOL_COUNTER(*scope_))}), - route_config_providers_(route_config_providers), - validation_visitor_(factory_context.messageValidationVisitor()) { + route_config_providers_(route_config_providers) { const auto& config_source = config_update_info_->routeConfiguration() .vhds() .config_source() diff --git a/source/common/router/vhds.h b/source/common/router/vhds.h index 0959bc6eca..a2d3cb6beb 100644 --- a/source/common/router/vhds.h +++ b/source/common/router/vhds.h @@ -59,9 +59,7 @@ class VhdsSubscription : Envoy::Config::SubscriptionCallbacks, void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; std::string resourceName(const ProtobufWkt::Any& resource) override { - return MessageUtil::anyConvert(resource, - validation_visitor_) - .name(); + return MessageUtil::anyConvert(resource).name(); } RouteConfigUpdatePtr& config_update_info_; @@ -70,7 +68,6 @@ class VhdsSubscription : Envoy::Config::SubscriptionCallbacks, Stats::ScopePtr scope_; VhdsStats stats_; std::unordered_set& route_config_providers_; - ProtobufMessage::ValidationVisitor& validation_visitor_; }; using VhdsSubscriptionPtr = std::unique_ptr; diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index e1dc2a53bc..6c3076d546 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -26,6 +26,8 @@ constexpr const char* runtime_features[] = { // Enabled "envoy.reloadable_features.test_feature_true", "envoy.reloadable_features.buffer_filter_populate_content_length", + "envoy.reloadable_features.trusted_forwarded_proto", + "envoy.reloadable_features.outlier_detection_support_for_grpc_status", }; // This is a list of configuration fields which are disallowed by default in Envoy diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index 3e86fcba33..b1f4494b27 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -182,9 +182,14 @@ bool SnapshotImpl::deprecatedFeatureEnabled(const std::string& key) const { // If either disallowed by default or configured off, the feature is not enabled. return false; } + // The feature is allowed. It is assumed this check is called when the feature // is about to be used, so increment the feature use stat. stats_.deprecated_feature_use_.inc(); +#ifdef ENVOY_DISABLE_DEPRECATED_FEATURES + return false; +#endif + return true; } @@ -517,9 +522,8 @@ RtdsSubscription::RtdsSubscription( void RtdsSubscription::onConfigUpdate(const Protobuf::RepeatedPtrField& resources, const std::string&) { validateUpdateSize(resources.size()); - auto runtime = MessageUtil::anyConvert( - resources[0], validation_visitor_); - MessageUtil::validate(runtime); + auto runtime = MessageUtil::anyConvert(resources[0]); + MessageUtil::validate(runtime, validation_visitor_); if (runtime.name() != resource_name_) { throw EnvoyException( fmt::format("Unexpected RTDS runtime (expecting {}): {}", resource_name_, runtime.name())); diff --git a/source/common/runtime/runtime_impl.h b/source/common/runtime/runtime_impl.h index 06be894c5a..9b3eb6a50c 100644 --- a/source/common/runtime/runtime_impl.h +++ b/source/common/runtime/runtime_impl.h @@ -209,9 +209,7 @@ struct RtdsSubscription : Config::SubscriptionCallbacks, Logger::Loggable(resource, - validation_visitor_) - .name(); + return MessageUtil::anyConvert(resource).name(); } void start(); @@ -270,7 +268,7 @@ class LoaderImpl : public Loader, Logger::Loggable { Upstream::ClusterManager* cm_{}; absl::Mutex snapshot_mutex_; - std::shared_ptr thread_safe_snapshot_ GUARDED_BY(snapshot_mutex_); + std::shared_ptr thread_safe_snapshot_ ABSL_GUARDED_BY(snapshot_mutex_); }; } // namespace Runtime diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc index 1aa00bb8c7..7d695040df 100644 --- a/source/common/secret/sds_api.cc +++ b/source/common/secret/sds_api.cc @@ -30,9 +30,8 @@ SdsApi::SdsApi(envoy::api::v2::core::ConfigSource sds_config, absl::string_view void SdsApi::onConfigUpdate(const Protobuf::RepeatedPtrField& resources, const std::string& version_info) { validateUpdateSize(resources.size()); - auto secret = - MessageUtil::anyConvert(resources[0], validation_visitor_); - MessageUtil::validate(secret); + auto secret = MessageUtil::anyConvert(resources[0]); + MessageUtil::validate(secret, validation_visitor_); if (secret.name() != sds_config_name_) { throw EnvoyException( diff --git a/source/common/secret/sds_api.h b/source/common/secret/sds_api.h index d6b9235157..762de28c96 100644 --- a/source/common/secret/sds_api.h +++ b/source/common/secret/sds_api.h @@ -59,8 +59,7 @@ class SdsApi : public Config::SubscriptionCallbacks { void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; std::string resourceName(const ProtobufWkt::Any& resource) override { - return MessageUtil::anyConvert(resource, validation_visitor_) - .name(); + return MessageUtil::anyConvert(resource).name(); } private: diff --git a/source/common/ssl/BUILD b/source/common/ssl/BUILD index 37dcef2ba8..ebbe626473 100644 --- a/source/common/ssl/BUILD +++ b/source/common/ssl/BUILD @@ -13,7 +13,9 @@ envoy_cc_library( srcs = ["tls_certificate_config_impl.cc"], hdrs = ["tls_certificate_config_impl.h"], deps = [ + "//include/envoy/server:transport_socket_config_interface", "//include/envoy/ssl:tls_certificate_config_interface", + "//include/envoy/ssl/private_key:private_key_interface", "//source/common/common:empty_string", "//source/common/config:datasource_lib", "@envoy_api//envoy/api/v2/auth:cert_cc", diff --git a/source/common/ssl/tls_certificate_config_impl.cc b/source/common/ssl/tls_certificate_config_impl.cc index 25f993c38d..5b28c3568e 100644 --- a/source/common/ssl/tls_certificate_config_impl.cc +++ b/source/common/ssl/tls_certificate_config_impl.cc @@ -1,6 +1,7 @@ #include "common/ssl/tls_certificate_config_impl.h" #include "envoy/common/exception.h" +#include "envoy/server/transport_socket_config.h" #include "common/common/empty_string.h" #include "common/common/fmt.h" @@ -12,7 +13,8 @@ namespace Ssl { static const std::string INLINE_STRING = ""; TlsCertificateConfigImpl::TlsCertificateConfigImpl( - const envoy::api::v2::auth::TlsCertificate& config, Api::Api& api) + const envoy::api::v2::auth::TlsCertificate& config, + Server::Configuration::TransportSocketFactoryContext* factory_context, Api::Api& api) : certificate_chain_(Config::DataSource::read(config.certificate_chain(), true, api)), certificate_chain_path_( Config::DataSource::getPath(config.certificate_chain()) @@ -22,9 +24,18 @@ TlsCertificateConfigImpl::TlsCertificateConfigImpl( .value_or(private_key_.empty() ? EMPTY_STRING : INLINE_STRING)), password_(Config::DataSource::read(config.password(), true, api)), password_path_(Config::DataSource::getPath(config.password()) - .value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)) { - - if (certificate_chain_.empty() || private_key_.empty()) { + .value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)), + private_key_method_( + factory_context != nullptr && config.has_private_key_provider() + ? factory_context->sslContextManager() + .privateKeyMethodManager() + .createPrivateKeyMethodProvider(config.private_key_provider(), *factory_context) + : nullptr) { + if (config.has_private_key_provider() && config.has_private_key()) { + throw EnvoyException(fmt::format( + "Certificate configuration can't have both private_key and private_key_provider")); + } + if (certificate_chain_.empty() || (private_key_.empty() && private_key_method_ == nullptr)) { throw EnvoyException(fmt::format("Failed to load incomplete certificate from {}, {}", certificate_chain_path_, private_key_path_)); } diff --git a/source/common/ssl/tls_certificate_config_impl.h b/source/common/ssl/tls_certificate_config_impl.h index ed664521b1..1db9046e92 100644 --- a/source/common/ssl/tls_certificate_config_impl.h +++ b/source/common/ssl/tls_certificate_config_impl.h @@ -4,6 +4,7 @@ #include "envoy/api/api.h" #include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/ssl/private_key/private_key.h" #include "envoy/ssl/tls_certificate_config.h" namespace Envoy { @@ -11,7 +12,9 @@ namespace Ssl { class TlsCertificateConfigImpl : public TlsCertificateConfig { public: - TlsCertificateConfigImpl(const envoy::api::v2::auth::TlsCertificate& config, Api::Api& api); + TlsCertificateConfigImpl(const envoy::api::v2::auth::TlsCertificate& config, + Server::Configuration::TransportSocketFactoryContext* factory_context, + Api::Api& api); const std::string& certificateChain() const override { return certificate_chain_; } const std::string& certificateChainPath() const override { return certificate_chain_path_; } @@ -19,6 +22,9 @@ class TlsCertificateConfigImpl : public TlsCertificateConfig { const std::string& privateKeyPath() const override { return private_key_path_; } const std::string& password() const override { return password_; } const std::string& passwordPath() const override { return password_path_; } + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr privateKeyMethod() const override { + return private_key_method_; + } private: const std::string certificate_chain_; @@ -27,6 +33,7 @@ class TlsCertificateConfigImpl : public TlsCertificateConfig { const std::string private_key_path_; const std::string password_; const std::string password_path_; + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_{}; }; } // namespace Ssl diff --git a/source/common/stats/BUILD b/source/common/stats/BUILD index cb88ed2dc9..ed5efa052b 100644 --- a/source/common/stats/BUILD +++ b/source/common/stats/BUILD @@ -50,6 +50,7 @@ envoy_cc_library( ":scope_prefixer_lib", ":stats_lib", ":store_impl_lib", + ":symbol_table_creator_lib", "//include/envoy/stats:stats_macros", "//source/common/stats:allocator_lib", ], @@ -155,6 +156,17 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "symbol_table_creator_lib", + srcs = ["symbol_table_creator.cc"], + hdrs = ["symbol_table_creator.h"], + external_deps = ["abseil_base"], + deps = [ + ":fake_symbol_table_lib", + ":symbol_table_lib", + ], +) + envoy_cc_library( name = "fake_symbol_table_lib", hdrs = ["fake_symbol_table_impl.h"], @@ -168,6 +180,7 @@ envoy_cc_library( deps = [ "//include/envoy/stats:stats_interface", "//source/common/common:perf_annotation_lib", + "//source/common/common:regex_lib", ], ) diff --git a/source/common/stats/isolated_store_impl.cc b/source/common/stats/isolated_store_impl.cc index 6f2da0f899..aa58676ae5 100644 --- a/source/common/stats/isolated_store_impl.cc +++ b/source/common/stats/isolated_store_impl.cc @@ -8,13 +8,13 @@ #include "common/stats/fake_symbol_table_impl.h" #include "common/stats/histogram_impl.h" #include "common/stats/scope_prefixer.h" +#include "common/stats/symbol_table_creator.h" #include "common/stats/utility.h" namespace Envoy { namespace Stats { -IsolatedStoreImpl::IsolatedStoreImpl() - : IsolatedStoreImpl(std::make_unique()) {} +IsolatedStoreImpl::IsolatedStoreImpl() : IsolatedStoreImpl(SymbolTableCreator::makeSymbolTable()) {} IsolatedStoreImpl::IsolatedStoreImpl(std::unique_ptr&& symbol_table) : IsolatedStoreImpl(*symbol_table) { diff --git a/source/common/stats/isolated_store_impl.h b/source/common/stats/isolated_store_impl.h index 6a11ceec2a..cce741211d 100644 --- a/source/common/stats/isolated_store_impl.h +++ b/source/common/stats/isolated_store_impl.h @@ -133,7 +133,7 @@ class IsolatedStoreImpl : public StoreImpl { private: IsolatedStoreImpl(std::unique_ptr&& symbol_table); - std::unique_ptr symbol_table_storage_; + SymbolTablePtr symbol_table_storage_; AllocatorImpl alloc_; IsolatedStatsCache counters_; IsolatedStatsCache gauges_; diff --git a/source/common/stats/stats_matcher_impl.cc b/source/common/stats/stats_matcher_impl.cc index e4f14872ae..bd73c46adf 100644 --- a/source/common/stats/stats_matcher_impl.cc +++ b/source/common/stats/stats_matcher_impl.cc @@ -19,14 +19,14 @@ StatsMatcherImpl::StatsMatcherImpl(const envoy::config::metrics::v2::StatsConfig case envoy::config::metrics::v2::StatsMatcher::kInclusionList: // If we have an inclusion list, we are being default-exclusive. for (const auto& stats_matcher : config.stats_matcher().inclusion_list().patterns()) { - matchers_.push_back(Matchers::StringMatcher(stats_matcher)); + matchers_.push_back(Matchers::StringMatcherImpl(stats_matcher)); } is_inclusive_ = false; break; case envoy::config::metrics::v2::StatsMatcher::kExclusionList: // If we have an exclusion list, we are being default-inclusive. for (const auto& stats_matcher : config.stats_matcher().exclusion_list().patterns()) { - matchers_.push_back(Matchers::StringMatcher(stats_matcher)); + matchers_.push_back(Matchers::StringMatcherImpl(stats_matcher)); } FALLTHRU; default: @@ -48,7 +48,7 @@ bool StatsMatcherImpl::rejects(const std::string& name) const { // This is an XNOR, which can be evaluated by checking for equality. return (is_inclusive_ == std::any_of(matchers_.begin(), matchers_.end(), - [&name](auto matcher) { return matcher.match(name); })); + [&name](auto& matcher) { return matcher.match(name); })); } } // namespace Stats diff --git a/source/common/stats/stats_matcher_impl.h b/source/common/stats/stats_matcher_impl.h index 3712a466d3..7fe65e7fc4 100644 --- a/source/common/stats/stats_matcher_impl.h +++ b/source/common/stats/stats_matcher_impl.h @@ -33,7 +33,7 @@ class StatsMatcherImpl : public StatsMatcher { // StatsMatcherImpl::rejects() for much more detail. bool is_inclusive_{true}; - std::vector matchers_; + std::vector matchers_; }; } // namespace Stats diff --git a/source/common/stats/symbol_table_creator.cc b/source/common/stats/symbol_table_creator.cc new file mode 100644 index 0000000000..8b29313130 --- /dev/null +++ b/source/common/stats/symbol_table_creator.cc @@ -0,0 +1,24 @@ +#include "common/stats/symbol_table_creator.h" + +namespace Envoy { +namespace Stats { + +bool SymbolTableCreator::initialized_ = false; +bool SymbolTableCreator::use_fake_symbol_tables_ = true; + +SymbolTablePtr SymbolTableCreator::initAndMakeSymbolTable(bool use_fake) { + ASSERT(!initialized_ || (use_fake_symbol_tables_ == use_fake)); + use_fake_symbol_tables_ = use_fake; + return makeSymbolTable(); +} + +SymbolTablePtr SymbolTableCreator::makeSymbolTable() { + initialized_ = true; + if (use_fake_symbol_tables_) { + return std::make_unique(); + } + return std::make_unique(); +} + +} // namespace Stats +} // namespace Envoy diff --git a/source/common/stats/symbol_table_creator.h b/source/common/stats/symbol_table_creator.h new file mode 100644 index 0000000000..4b51468890 --- /dev/null +++ b/source/common/stats/symbol_table_creator.h @@ -0,0 +1,57 @@ +#pragma once + +#include "common/stats/fake_symbol_table_impl.h" +#include "common/stats/symbol_table_impl.h" + +namespace Envoy { +namespace Stats { + +namespace TestUtil { +class SymbolTableCreatorTestPeer; +} + +class SymbolTableCreator { +public: + /** + * Initializes the symbol-table creation system. Once this is called, it is a + * runtime assertion to call this again in production code, changing the + * use_fakes setting. However, tests can change the setting via + * TestUtil::SymbolTableCreatorTestPeer::setUseFakeSymbolTables(use_fakes). + * + * @param use_fakes Whether to use fake symbol tables; typically from a command-line option. + * @return a SymbolTable. + */ + static SymbolTablePtr initAndMakeSymbolTable(bool use_fakes); + + /** + * Factory method to create SymbolTables. This is needed to help make it + * possible to flag-flip use of real symbol tables, and ultimately should be + * removed. + * + * @return a SymbolTable. + */ + static SymbolTablePtr makeSymbolTable(); + + /** + * @return whether the system is initialized to use fake symbol tables. + */ + static bool useFakeSymbolTables() { return use_fake_symbol_tables_; } + +private: + friend class TestUtil::SymbolTableCreatorTestPeer; + + /** + * Sets whether fake or real symbol tables should be used. Tests that alter + * this should restore previous value at the end of the test. This must be + * called via TestUtil::SymbolTableCreatorTestPeer. + * + * *param use_fakes whether to use fake symbol tables. + */ + static void setUseFakeSymbolTables(bool use_fakes) { use_fake_symbol_tables_ = use_fakes; } + + static bool initialized_; + static bool use_fake_symbol_tables_; +}; + +} // namespace Stats +} // namespace Envoy diff --git a/source/common/stats/symbol_table_impl.h b/source/common/stats/symbol_table_impl.h index 925ff26368..b83da159c0 100644 --- a/source/common/stats/symbol_table_impl.h +++ b/source/common/stats/symbol_table_impl.h @@ -229,7 +229,7 @@ class SymbolTableImpl : public SymbolTable { // Bitmap implementation. // The encode map stores both the symbol and the ref count of that symbol. // Using absl::string_view lets us only store the complete string once, in the decode map. - using EncodeMap = absl::flat_hash_map; + using EncodeMap = absl::flat_hash_map; using DecodeMap = absl::flat_hash_map; EncodeMap encode_map_ GUARDED_BY(lock_); DecodeMap decode_map_ GUARDED_BY(lock_); @@ -317,16 +317,31 @@ class StatName { // for lookup in a cache, and then on a miss need to store the data directly. StatName(const StatName& src, SymbolTable::Storage memory); + /** + * Defines default hash function so StatName can be used as a key in an absl + * hash-table without specifying a functor. See + * https://abseil.io/docs/cpp/guides/hash for details. + */ + template friend H AbslHashValue(H h, StatName stat_name) { + if (stat_name.empty()) { + return H::combine(std::move(h), absl::string_view()); + } + + // Casts the raw data as a string_view. Note that this string_view will not + // be in human-readable form, but it will be compatible with a string-view + // hasher. + const char* cdata = reinterpret_cast(stat_name.data()); + absl::string_view data_as_string_view = absl::string_view(cdata, stat_name.dataSize()); + return H::combine(std::move(h), data_as_string_view); + } + /** * Note that this hash function will return a different hash than that of * the elaborated string. * * @return uint64_t a hash of the underlying representation. */ - uint64_t hash() const { - const char* cdata = reinterpret_cast(data()); - return HashUtil::xxHash64(absl::string_view(cdata, dataSize())); - } + uint64_t hash() const { return absl::Hash()(*this); } bool operator==(const StatName& rhs) const { const uint64_t sz = dataSize(); @@ -339,6 +354,9 @@ class StatName { * overhead for the size itself. */ uint64_t dataSize() const { + if (size_and_data_ == nullptr) { + return 0; + } return size_and_data_[0] | (static_cast(size_and_data_[1]) << 8); } @@ -523,22 +541,11 @@ class StatNameList { SymbolTable::StoragePtr storage_; }; -// Helper class for constructing hash-tables with StatName keys. -struct StatNameHash { - size_t operator()(const StatName& a) const { return a.hash(); } -}; - -// Helper class for constructing hash-tables with StatName keys. -struct StatNameCompare { - bool operator()(const StatName& a, const StatName& b) const { return a == b; } -}; - // Value-templatized hash-map with StatName key. -template -using StatNameHashMap = absl::flat_hash_map; +template using StatNameHashMap = absl::flat_hash_map; // Hash-set of StatNames -using StatNameHashSet = absl::flat_hash_set; +using StatNameHashSet = absl::flat_hash_set; // Helper class for sorting StatNames. struct StatNameLessThan { @@ -652,10 +659,13 @@ class StatNameSet { void rememberBuiltin(absl::string_view str); /** - * Finds a StatName by name. If 'token' has been remembered as a built-in, then - * no lock is required. Otherwise we first consult dynamic_stat_names_ under a - * lock that's private to the StatNameSet. If that's empty, we need to create - * the StatName in the pool, which requires taking a global lock. + * Finds a StatName by name. If 'token' has been remembered as a built-in, + * then no lock is required. Otherwise we must consult dynamic_stat_names_ + * under a lock that's private to the StatNameSet. If that's empty, we need to + * create the StatName in the pool, which requires taking a global lock, and + * then remember the new StatName in the dynamic_stat_names_. This allows + * subsequent lookups of the same string to take only the set's lock, and not + * the whole symbol-table lock. * * TODO(jmarantz): Potential perf issue here with contention, both on this * set's mutex and also the SymbolTable mutex which must be taken during diff --git a/source/common/stats/tag_extractor_impl.cc b/source/common/stats/tag_extractor_impl.cc index 7bb02e6f53..a56398895c 100644 --- a/source/common/stats/tag_extractor_impl.cc +++ b/source/common/stats/tag_extractor_impl.cc @@ -5,8 +5,9 @@ #include "envoy/common/exception.h" +#include "common/common/fmt.h" #include "common/common/perf_annotation.h" -#include "common/common/utility.h" +#include "common/common/regex.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" @@ -25,7 +26,7 @@ bool regexStartsWithDot(absl::string_view regex) { TagExtractorImpl::TagExtractorImpl(const std::string& name, const std::string& regex, const std::string& substr) : name_(name), prefix_(std::string(extractRegexPrefix(regex))), substr_(substr), - regex_(RegexUtil::parseRegex(regex)) {} + regex_(Regex::Utility::parseStdRegex(regex)) {} std::string TagExtractorImpl::extractRegexPrefix(absl::string_view regex) { std::string prefix; diff --git a/source/common/stats/tag_producer_impl.h b/source/common/stats/tag_producer_impl.h index 6a4fcb07a6..e18f111e92 100644 --- a/source/common/stats/tag_producer_impl.h +++ b/source/common/stats/tag_producer_impl.h @@ -17,6 +17,7 @@ #include "common/config/well_known_names.h" #include "common/protobuf/protobuf.h" +#include "absl/container/flat_hash_map.h" #include "absl/strings/string_view.h" namespace Envoy { @@ -97,8 +98,7 @@ class TagProducerImpl : public TagProducer { // Maps a prefix word extracted out of a regex to a vector of TagExtractors. Note that // the storage for the prefix string is owned by the TagExtractor, which, depending on // implementation, may need make a copy of the prefix. - std::unordered_map, StringViewHash> - tag_extractor_prefix_map_; + absl::flat_hash_map> tag_extractor_prefix_map_; std::vector default_tags_; }; diff --git a/source/common/stream_info/stream_info_impl.h b/source/common/stream_info/stream_info_impl.h index 7f7dd70ad3..cd9f0db4a3 100644 --- a/source/common/stream_info/stream_info_impl.h +++ b/source/common/stream_info/stream_info_impl.h @@ -175,14 +175,23 @@ struct StreamInfoImpl : public StreamInfo { return downstream_remote_address_; } - void setDownstreamSslConnection(const Ssl::ConnectionInfo* connection_info) override { + void + setDownstreamSslConnection(const Ssl::ConnectionInfoConstSharedPtr& connection_info) override { downstream_ssl_info_ = connection_info; } - const Ssl::ConnectionInfo* downstreamSslConnection() const override { + Ssl::ConnectionInfoConstSharedPtr downstreamSslConnection() const override { return downstream_ssl_info_; } + void setUpstreamSslConnection(const Ssl::ConnectionInfoConstSharedPtr& connection_info) override { + upstream_ssl_info_ = connection_info; + } + + Ssl::ConnectionInfoConstSharedPtr upstreamSslConnection() const override { + return upstream_ssl_info_; + } + const Router::RouteEntry* routeEntry() const override { return route_entry_; } envoy::api::v2::core::Metadata& dynamicMetadata() override { return metadata_; }; @@ -243,7 +252,8 @@ struct StreamInfoImpl : public StreamInfo { Network::Address::InstanceConstSharedPtr downstream_local_address_; Network::Address::InstanceConstSharedPtr downstream_direct_remote_address_; Network::Address::InstanceConstSharedPtr downstream_remote_address_; - const Ssl::ConnectionInfo* downstream_ssl_info_{}; + Ssl::ConnectionInfoConstSharedPtr downstream_ssl_info_; + Ssl::ConnectionInfoConstSharedPtr upstream_ssl_info_; std::string requested_server_name_; UpstreamTiming upstream_timing_; std::string upstream_transport_failure_reason_; diff --git a/source/common/tcp/conn_pool.cc b/source/common/tcp/conn_pool.cc index 66cd20da46..f7b7467f89 100644 --- a/source/common/tcp/conn_pool.cc +++ b/source/common/tcp/conn_pool.cc @@ -194,6 +194,7 @@ void ConnPoolImpl::onConnectionEvent(ActiveConn& conn, Network::ConnectionEvent // whether the connection is in the ready list (connected) or the pending list (failed to // connect). if (event == Network::ConnectionEvent::Connected) { + conn.conn_->streamInfo().setDownstreamSslConnection(conn.conn_->ssl()); conn_connect_ms_->complete(); processIdleConnection(conn, true, false); } diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index ce466c2512..aee0dbb70f 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -439,6 +439,7 @@ void Filter::onPoolReady(Tcp::ConnectionPool::ConnectionDataPtr&& conn_data, getStreamInfo().onUpstreamHostSelected(host); getStreamInfo().setUpstreamLocalAddress(connection.localAddress()); + getStreamInfo().setUpstreamSslConnection(connection.streamInfo().downstreamSslConnection()); // Simulate the event that onPoolReady represents. upstream_callbacks_->onEvent(Network::ConnectionEvent::Connected); diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 5a8534a562..928accb654 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -231,6 +231,8 @@ class Filter : public Network::ReadFilter, bool on_high_watermark_called_{false}; }; + virtual StreamInfo::StreamInfo& getStreamInfo() { return stream_info_; } + protected: struct DownstreamCallbacks : public Network::ConnectionCallbacks { DownstreamCallbacks(Filter& parent) : parent_(parent) {} @@ -260,8 +262,6 @@ class Filter : public Network::ReadFilter, read_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush); } - virtual StreamInfo::StreamInfo& getStreamInfo() { return stream_info_; } - void initialize(Network::ReadFilterCallbacks& callbacks, bool set_connection_stats); Network::FilterStatus initializeUpstreamConnection(); void onConnectTimeout(); diff --git a/source/common/thread_local/thread_local_impl.cc b/source/common/thread_local/thread_local_impl.cc index 4e8c32fed7..5d9f584b51 100644 --- a/source/common/thread_local/thread_local_impl.cc +++ b/source/common/thread_local/thread_local_impl.cc @@ -1,5 +1,6 @@ #include "common/thread_local/thread_local_impl.h" +#include #include #include #include @@ -24,16 +25,17 @@ SlotPtr InstanceImpl::allocateSlot() { ASSERT(std::this_thread::get_id() == main_thread_id_); ASSERT(!shutdown_); - for (uint64_t i = 0; i < slots_.size(); i++) { - if (slots_[i] == nullptr) { - std::unique_ptr slot(new SlotImpl(*this, i)); - slots_[i] = slot.get(); - return slot; - } + if (free_slot_indexes_.empty()) { + std::unique_ptr slot(new SlotImpl(*this, slots_.size())); + auto wrapper = std::make_unique(*this, std::move(slot)); + slots_.push_back(wrapper->slot_.get()); + return wrapper; } - - std::unique_ptr slot(new SlotImpl(*this, slots_.size())); - slots_.push_back(slot.get()); + const uint32_t idx = free_slot_indexes_.front(); + free_slot_indexes_.pop_front(); + ASSERT(idx < slots_.size()); + std::unique_ptr slot(new SlotImpl(*this, idx)); + slots_[idx] = slot.get(); return slot; } @@ -41,11 +43,64 @@ bool InstanceImpl::SlotImpl::currentThreadRegistered() { return thread_local_data_.data_.size() > index_; } +void InstanceImpl::SlotImpl::runOnAllThreads(const UpdateCb& cb) { + parent_.runOnAllThreads([this, cb]() { setThreadLocal(index_, cb(get())); }); +} + +void InstanceImpl::SlotImpl::runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) { + parent_.runOnAllThreads([this, cb]() { setThreadLocal(index_, cb(get())); }, complete_cb); +} + ThreadLocalObjectSharedPtr InstanceImpl::SlotImpl::get() { ASSERT(currentThreadRegistered()); return thread_local_data_.data_[index_]; } +InstanceImpl::Bookkeeper::Bookkeeper(InstanceImpl& parent, std::unique_ptr&& slot) + : parent_(parent), slot_(std::move(slot)), + ref_count_(/*not used.*/ nullptr, + [slot = slot_.get(), &parent = this->parent_](uint32_t* /* not used */) { + // On destruction, post a cleanup callback on main thread, this could happen on + // any thread. + parent.scheduleCleanup(slot); + }) {} + +ThreadLocalObjectSharedPtr InstanceImpl::Bookkeeper::get() { return slot_->get(); } + +void InstanceImpl::Bookkeeper::runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) { + slot_->runOnAllThreads( + [cb, ref_count = this->ref_count_](ThreadLocalObjectSharedPtr previous) { + return cb(std::move(previous)); + }, + complete_cb); +} + +void InstanceImpl::Bookkeeper::runOnAllThreads(const UpdateCb& cb) { + slot_->runOnAllThreads([cb, ref_count = this->ref_count_](ThreadLocalObjectSharedPtr previous) { + return cb(std::move(previous)); + }); +} + +bool InstanceImpl::Bookkeeper::currentThreadRegistered() { + return slot_->currentThreadRegistered(); +} + +void InstanceImpl::Bookkeeper::runOnAllThreads(Event::PostCb cb) { + // Use ref_count_ to bookkeep how many on-the-fly callback are out there. + slot_->runOnAllThreads([cb, ref_count = this->ref_count_]() { cb(); }); +} + +void InstanceImpl::Bookkeeper::runOnAllThreads(Event::PostCb cb, Event::PostCb main_callback) { + // Use ref_count_ to bookkeep how many on-the-fly callback are out there. + slot_->runOnAllThreads([cb, main_callback, ref_count = this->ref_count_]() { cb(); }, + main_callback); +} + +void InstanceImpl::Bookkeeper::set(InitializeCb cb) { + slot_->set([cb, ref_count = this->ref_count_](Event::Dispatcher& dispatcher) + -> ThreadLocalObjectSharedPtr { return cb(dispatcher); }); +} + void InstanceImpl::registerThread(Event::Dispatcher& dispatcher, bool main_thread) { ASSERT(std::this_thread::get_id() == main_thread_id_); ASSERT(!shutdown_); @@ -60,6 +115,38 @@ void InstanceImpl::registerThread(Event::Dispatcher& dispatcher, bool main_threa } } +// Puts the slot into a deferred delete container, the slot will be destructed when its out-going +// callback reference count goes to 0. +void InstanceImpl::recycle(std::unique_ptr&& slot) { + ASSERT(std::this_thread::get_id() == main_thread_id_); + ASSERT(slot != nullptr); + auto* slot_addr = slot.get(); + deferred_deletes_.insert({slot_addr, std::move(slot)}); +} + +// Called by the Bookkeeper ref_count destructor, the SlotImpl in the deferred deletes map can be +// destructed now. +void InstanceImpl::scheduleCleanup(SlotImpl* slot) { + if (shutdown_) { + // If server is shutting down, do nothing here. + // The destruction of Bookkeeper has already transferred the SlotImpl to the deferred_deletes_ + // queue. No matter if this method is called from a Worker thread, the SlotImpl will be + // destructed on main thread when InstanceImpl destructs. + return; + } + if (std::this_thread::get_id() == main_thread_id_) { + // If called from main thread, save a callback. + ASSERT(deferred_deletes_.contains(slot)); + deferred_deletes_.erase(slot); + return; + } + main_thread_dispatcher_->post([slot, this]() { + ASSERT(deferred_deletes_.contains(slot)); + // The slot is guaranteed to be put into the deferred_deletes_ map by Bookkeeper destructor. + deferred_deletes_.erase(slot); + }); +} + void InstanceImpl::removeSlot(SlotImpl& slot) { ASSERT(std::this_thread::get_id() == main_thread_id_); @@ -73,6 +160,10 @@ void InstanceImpl::removeSlot(SlotImpl& slot) { const uint64_t index = slot.index_; slots_[index] = nullptr; + ASSERT(std::find(free_slot_indexes_.begin(), free_slot_indexes_.end(), index) == + free_slot_indexes_.end(), + fmt::format("slot index {} already in free slot set!", index)); + free_slot_indexes_.push_back(index); runOnAllThreads([index]() -> void { // This runs on each thread and clears the slot, making it available for a new allocations. // This is safe even if a new allocation comes in, because everything happens with post() and diff --git a/source/common/thread_local/thread_local_impl.h b/source/common/thread_local/thread_local_impl.h index 3e8e39c8fa..49f1889e44 100644 --- a/source/common/thread_local/thread_local_impl.h +++ b/source/common/thread_local/thread_local_impl.h @@ -8,6 +8,9 @@ #include "envoy/thread_local/thread_local.h" #include "common/common/logger.h" +#include "common/common/non_copyable.h" + +#include "absl/container/flat_hash_map.h" namespace Envoy { namespace ThreadLocal { @@ -15,7 +18,7 @@ namespace ThreadLocal { /** * Implementation of ThreadLocal that relies on static thread_local objects. */ -class InstanceImpl : Logger::Loggable, public Instance { +class InstanceImpl : Logger::Loggable, public NonCopyable, public Instance { public: InstanceImpl() : main_thread_id_(std::this_thread::get_id()) {} ~InstanceImpl() override; @@ -35,6 +38,8 @@ class InstanceImpl : Logger::Loggable, public Instance { // ThreadLocal::Slot ThreadLocalObjectSharedPtr get() override; bool currentThreadRegistered() override; + void runOnAllThreads(const UpdateCb& cb) override; + void runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) override; void runOnAllThreads(Event::PostCb cb) override { parent_.runOnAllThreads(cb); } void runOnAllThreads(Event::PostCb cb, Event::PostCb main_callback) override { parent_.runOnAllThreads(cb, main_callback); @@ -45,18 +50,51 @@ class InstanceImpl : Logger::Loggable, public Instance { const uint64_t index_; }; + // A Wrapper of SlotImpl which on destruction returns the SlotImpl to the deferred delete queue + // (detaches it). + struct Bookkeeper : public Slot { + Bookkeeper(InstanceImpl& parent, std::unique_ptr&& slot); + ~Bookkeeper() override { parent_.recycle(std::move(slot_)); } + + // ThreadLocal::Slot + ThreadLocalObjectSharedPtr get() override; + void runOnAllThreads(const UpdateCb& cb) override; + void runOnAllThreads(const UpdateCb& cb, Event::PostCb complete_cb) override; + bool currentThreadRegistered() override; + void runOnAllThreads(Event::PostCb cb) override; + void runOnAllThreads(Event::PostCb cb, Event::PostCb main_callback) override; + void set(InitializeCb cb) override; + + InstanceImpl& parent_; + std::unique_ptr slot_; + std::shared_ptr ref_count_; + }; + struct ThreadLocalData { Event::Dispatcher* dispatcher_{}; std::vector data_; }; + void recycle(std::unique_ptr&& slot); + // Cleanup the deferred deletes queue. + void scheduleCleanup(SlotImpl* slot); + void removeSlot(SlotImpl& slot); void runOnAllThreads(Event::PostCb cb); void runOnAllThreads(Event::PostCb cb, Event::PostCb main_callback); static void setThreadLocal(uint32_t index, ThreadLocalObjectSharedPtr object); static thread_local ThreadLocalData thread_local_data_; + + // A indexed container for Slots that has to be deferred to delete due to out-going callbacks + // pointing to the Slot. To let the ref_count_ deleter find the SlotImpl by address, the container + // is defined as a map of SlotImpl address to the unique_ptr. + absl::flat_hash_map> deferred_deletes_; + std::vector slots_; + // A list of index of freed slots. + std::list free_slot_indexes_; + std::list> registered_threads_; std::thread::id main_thread_id_; Event::Dispatcher* main_thread_dispatcher_{}; diff --git a/source/common/tracing/http_tracer_impl.cc b/source/common/tracing/http_tracer_impl.cc index 46ff74ac58..35bee4ff7d 100644 --- a/source/common/tracing/http_tracer_impl.cc +++ b/source/common/tracing/http_tracer_impl.cc @@ -7,6 +7,7 @@ #include "common/common/fmt.h" #include "common/common/macros.h" #include "common/common/utility.h" +#include "common/grpc/common.h" #include "common/http/codes.h" #include "common/http/header_map_impl.h" #include "common/http/headers.h" @@ -26,11 +27,12 @@ static std::string valueOrDefault(const Http::HeaderEntry* header, const char* d return header ? std::string(header->value().getStringView()) : default_value; } -static std::string buildUrl(const Http::HeaderMap& request_headers) { +static std::string buildUrl(const Http::HeaderMap& request_headers, + const uint32_t max_path_length) { std::string path(request_headers.EnvoyOriginalPath() ? request_headers.EnvoyOriginalPath()->value().getStringView() : request_headers.Path()->value().getStringView()); - static const size_t max_path_length = 256; + if (path.length() > max_path_length) { path = path.substr(0, max_path_length); } @@ -81,6 +83,22 @@ Decision HttpTracerUtility::isTracing(const StreamInfo::StreamInfo& stream_info, NOT_REACHED_GCOVR_EXCL_LINE; } +static void addGrpcTags(Span& span, const Http::HeaderMap& headers) { + const Http::HeaderEntry* grpc_status_header = headers.GrpcStatus(); + if (grpc_status_header) { + span.setTag(Tracing::Tags::get().GrpcStatusCode, grpc_status_header->value().getStringView()); + } + const Http::HeaderEntry* grpc_message_header = headers.GrpcMessage(); + if (grpc_message_header) { + span.setTag(Tracing::Tags::get().GrpcMessage, grpc_message_header->value().getStringView()); + } + absl::optional grpc_status_code = Grpc::Common::getGrpcStatus(headers); + // Set error tag when status is not OK. + if (grpc_status_code && grpc_status_code.value() != Grpc::Status::GrpcStatus::Ok) { + span.setTag(Tracing::Tags::get().Error, Tracing::Tags::get().True); + } +} + static void annotateVerbose(Span& span, const StreamInfo::StreamInfo& stream_info) { const auto start_time = stream_info.startTime(); if (stream_info.lastDownstreamRxByteReceived()) { @@ -121,6 +139,8 @@ static void annotateVerbose(Span& span, const StreamInfo::StreamInfo& stream_inf } void HttpTracerUtility::finalizeSpan(Span& span, const Http::HeaderMap* request_headers, + const Http::HeaderMap* response_headers, + const Http::HeaderMap* response_trailers, const StreamInfo::StreamInfo& stream_info, const Config& tracing_config) { // Pre response data. @@ -129,7 +149,8 @@ void HttpTracerUtility::finalizeSpan(Span& span, const Http::HeaderMap* request_ span.setTag(Tracing::Tags::get().GuidXRequestId, std::string(request_headers->RequestId()->value().getStringView())); } - span.setTag(Tracing::Tags::get().HttpUrl, buildUrl(*request_headers)); + span.setTag(Tracing::Tags::get().HttpUrl, + buildUrl(*request_headers, tracing_config.maxPathTagLength())); span.setTag(Tracing::Tags::get().HttpMethod, std::string(request_headers->Method()->value().getStringView())); span.setTag(Tracing::Tags::get().DownstreamCluster, @@ -163,6 +184,13 @@ void HttpTracerUtility::finalizeSpan(Span& span, const Http::HeaderMap* request_ span.setTag(Tracing::Tags::get().ResponseFlags, StreamInfo::ResponseFlagUtils::toShortString(stream_info)); + // GRPC data. + if (response_trailers && response_trailers->GrpcStatus() != nullptr) { + addGrpcTags(span, *response_trailers); + } else if (response_headers && response_headers->GrpcStatus() != nullptr) { + addGrpcTags(span, *response_headers); + } + if (tracing_config.verbose()) { annotateVerbose(span, stream_info); } diff --git a/source/common/tracing/http_tracer_impl.h b/source/common/tracing/http_tracer_impl.h index 50a7821460..e7560acac0 100644 --- a/source/common/tracing/http_tracer_impl.h +++ b/source/common/tracing/http_tracer_impl.h @@ -41,6 +41,7 @@ class TracingTagValues { // Non-standard tag names. const std::string DownstreamCluster = "downstream_cluster"; const std::string GrpcStatusCode = "grpc.status_code"; + const std::string GrpcMessage = "grpc.message"; const std::string GuidXClientTraceId = "guid:x-client-trace-id"; const std::string GuidXRequestId = "guid:x-request-id"; const std::string HttpProtocol = "http.protocol"; @@ -101,6 +102,8 @@ class HttpTracerUtility { * 2) Finish active span. */ static void finalizeSpan(Span& span, const Http::HeaderMap* request_headers, + const Http::HeaderMap* response_headers, + const Http::HeaderMap* response_trailers, const StreamInfo::StreamInfo& stream_info, const Config& tracing_config); static const std::string IngressOperation; @@ -115,6 +118,7 @@ class EgressConfigImpl : public Config { return request_headers_for_tags_; } bool verbose() const override { return false; } + uint32_t maxPathTagLength() const override { return Tracing::DefaultMaxPathTagLength; } private: const std::vector request_headers_for_tags_{}; diff --git a/source/common/upstream/BUILD b/source/common/upstream/BUILD index e11007731e..cf2e800ef6 100644 --- a/source/common/upstream/BUILD +++ b/source/common/upstream/BUILD @@ -23,6 +23,7 @@ envoy_cc_library( "//source/common/config:utility_lib", "//source/common/protobuf:utility_lib", "@envoy_api//envoy/api/v2:cds_cc", + "@envoy_api//envoy/api/v2/cluster:outlier_detection_cc", ], ) @@ -267,6 +268,7 @@ envoy_cc_library( "//source/common/http:codes_lib", "//source/common/protobuf", "@envoy_api//envoy/api/v2:cds_cc", + "@envoy_api//envoy/api/v2/cluster:outlier_detection_cc", "@envoy_api//envoy/data/cluster/v2alpha:outlier_detection_event_cc", ], ) diff --git a/source/common/upstream/cds_api_impl.cc b/source/common/upstream/cds_api_impl.cc index 2d0271f56e..41f7ec4fac 100644 --- a/source/common/upstream/cds_api_impl.cc +++ b/source/common/upstream/cds_api_impl.cc @@ -35,8 +35,7 @@ void CdsApiImpl::onConfigUpdate(const Protobuf::RepeatedPtrField clusters; for (const auto& cluster_blob : resources) { - clusters.push_back( - MessageUtil::anyConvert(cluster_blob, validation_visitor_)); + clusters.push_back(MessageUtil::anyConvert(cluster_blob)); clusters_to_remove.erase(clusters.back().name()); } Protobuf::RepeatedPtrField to_remove_repeated; @@ -60,15 +59,17 @@ void CdsApiImpl::onConfigUpdate( cm_.adsMux().pause(Config::TypeUrl::get().ClusterLoadAssignment); Cleanup eds_resume([this] { cm_.adsMux().resume(Config::TypeUrl::get().ClusterLoadAssignment); }); + ENVOY_LOG(info, "cds: add {} cluster(s), remove {} cluster(s)", added_resources.size(), + removed_resources.size()); + std::vector exception_msgs; std::unordered_set cluster_names; bool any_applied = false; for (const auto& resource : added_resources) { envoy::api::v2::Cluster cluster; try { - cluster = MessageUtil::anyConvert(resource.resource(), - validation_visitor_); - MessageUtil::validate(cluster); + cluster = MessageUtil::anyConvert(resource.resource()); + MessageUtil::validate(cluster, validation_visitor_); if (!cluster_names.insert(cluster.name()).second) { // NOTE: at this point, the first of these duplicates has already been successfully applied. throw EnvoyException(fmt::format("duplicate cluster {} found", cluster.name())); diff --git a/source/common/upstream/cds_api_impl.h b/source/common/upstream/cds_api_impl.h index 2825213f31..b17d4bbc99 100644 --- a/source/common/upstream/cds_api_impl.h +++ b/source/common/upstream/cds_api_impl.h @@ -43,7 +43,7 @@ class CdsApiImpl : public CdsApi, void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; std::string resourceName(const ProtobufWkt::Any& resource) override { - return MessageUtil::anyConvert(resource, validation_visitor_).name(); + return MessageUtil::anyConvert(resource).name(); } CdsApiImpl(const envoy::api::v2::core::ConfigSource& cds_config, ClusterManager& cm, diff --git a/source/common/upstream/cluster_factory_impl.cc b/source/common/upstream/cluster_factory_impl.cc index 43c6d55819..bebf10b933 100644 --- a/source/common/upstream/cluster_factory_impl.cc +++ b/source/common/upstream/cluster_factory_impl.cc @@ -109,7 +109,8 @@ ClusterFactoryImplBase::create(const envoy::api::v2::Cluster& cluster, } else { new_cluster_pair.first->setHealthChecker(HealthCheckerFactory::create( cluster.health_checks()[0], *new_cluster_pair.first, context.runtime(), context.random(), - context.dispatcher(), context.logManager(), context.messageValidationVisitor())); + context.dispatcher(), context.logManager(), context.messageValidationVisitor(), + context.api())); } } diff --git a/source/common/upstream/cluster_factory_impl.h b/source/common/upstream/cluster_factory_impl.h index ae709eb094..4b3f536dd3 100644 --- a/source/common/upstream/cluster_factory_impl.h +++ b/source/common/upstream/cluster_factory_impl.h @@ -178,7 +178,8 @@ template class ConfigurableClusterFactoryBase : public Clust cluster.cluster_type().typed_config(), ProtobufWkt::Struct::default_instance(), socket_factory_context.messageValidationVisitor(), *config); return createClusterWithConfig(cluster, - MessageUtil::downcastAndValidate(*config), + MessageUtil::downcastAndValidate( + *config, context.messageValidationVisitor()), context, socket_factory_context, std::move(stats_scope)); } diff --git a/source/common/upstream/cluster_manager_impl.cc b/source/common/upstream/cluster_manager_impl.cc index 7c4ac14f1b..e5c06231c4 100644 --- a/source/common/upstream/cluster_manager_impl.cc +++ b/source/common/upstream/cluster_manager_impl.cc @@ -103,6 +103,19 @@ void ClusterManagerInitHelper::removeCluster(Cluster& cluster) { maybeFinishInitialize(); } +void ClusterManagerInitHelper::initializeSecondaryClusters() { + started_secondary_initialize_ = true; + // Cluster::initialize() method can modify the list of secondary_init_clusters_ to remove + // the item currently being initialized, so we eschew range-based-for and do this complicated + // dance to increment the iterator before calling initialize. + for (auto iter = secondary_init_clusters_.begin(); iter != secondary_init_clusters_.end();) { + Cluster* cluster = *iter; + ++iter; + ENVOY_LOG(debug, "initializing secondary cluster {}", cluster->info()->name()); + cluster->initialize([cluster, this] { onClusterInit(*cluster); }); + } +} + void ClusterManagerInitHelper::maybeFinishInitialize() { // Do not do anything if we are still doing the initial static load or if we are waiting for // CDS initialize. @@ -121,15 +134,16 @@ void ClusterManagerInitHelper::maybeFinishInitialize() { if (!secondary_init_clusters_.empty()) { if (!started_secondary_initialize_) { ENVOY_LOG(info, "cm init: initializing secondary clusters"); - started_secondary_initialize_ = true; - // Cluster::initialize() method can modify the list of secondary_init_clusters_ to remove - // the item currently being initialized, so we eschew range-based-for and do this complicated - // dance to increment the iterator before calling initialize. - for (auto iter = secondary_init_clusters_.begin(); iter != secondary_init_clusters_.end();) { - Cluster* cluster = *iter; - ++iter; - ENVOY_LOG(debug, "initializing secondary cluster {}", cluster->info()->name()); - cluster->initialize([cluster, this] { onClusterInit(*cluster); }); + // If the first CDS response doesn't have any primary cluster, ClusterLoadAssignment + // should be already paused by CdsApiImpl::onConfigUpdate(). Need to check that to + // avoid double pause ClusterLoadAssignment. + if (cm_.adsMux().paused(Config::TypeUrl::get().ClusterLoadAssignment)) { + initializeSecondaryClusters(); + } else { + cm_.adsMux().pause(Config::TypeUrl::get().ClusterLoadAssignment); + Cleanup eds_resume( + [this] { cm_.adsMux().resume(Config::TypeUrl::get().ClusterLoadAssignment); }); + initializeSecondaryClusters(); } } @@ -188,7 +202,7 @@ ClusterManagerImpl::ClusterManagerImpl( : factory_(factory), runtime_(runtime), stats_(stats), tls_(tls.allocateSlot()), random_(random), bind_config_(bootstrap.cluster_manager().upstream_bind_config()), local_info_(local_info), cm_stats_(generateStats(stats)), - init_helper_([this](Cluster& cluster) { onClusterInit(cluster); }), + init_helper_(*this, [this](Cluster& cluster) { onClusterInit(cluster); }), config_tracker_entry_( admin.getConfigTracker().add("clusters", [this] { return dumpClusterConfigs(); })), time_source_(main_thread_dispatcher.timeSource()), dispatcher_(main_thread_dispatcher), @@ -314,12 +328,21 @@ void ClusterManagerImpl::onClusterInit(Cluster& cluster) { // Now setup for cross-thread updates. cluster.prioritySet().addMemberUpdateCb( [&cluster, this](const HostVector&, const HostVector& hosts_removed) -> void { - // TODO(snowp): Should this be subject to merge windows? - - // Whenever hosts are removed from the cluster, we make each TLS cluster drain it's - // connection pools for the removed hosts. - if (!hosts_removed.empty()) { - postThreadLocalHostRemoval(cluster, hosts_removed); + if (cluster.info()->lbConfig().close_connections_on_host_set_change()) { + for (const auto& host_set : cluster.prioritySet().hostSetsPerPriority()) { + // This will drain all tcp and http connection pools. + postThreadLocalDrainConnections(cluster, host_set->hosts()); + } + } else { + // TODO(snowp): Should this be subject to merge windows? + + // Whenever hosts are removed from the cluster, we make each TLS cluster drain it's + // connection pools for the removed hosts. If `close_connections_on_host_set_change` is + // enabled, this case will be covered by first `if` statement, where all + // connection pools are drained. + if (!hosts_removed.empty()) { + postThreadLocalDrainConnections(cluster, hosts_removed); + } } }); @@ -464,9 +487,22 @@ bool ClusterManagerImpl::addOrUpdateCluster(const envoy::api::v2::Cluster& clust if (existing_active_cluster != active_clusters_.end() || existing_warming_cluster != warming_clusters_.end()) { - // The following init manager remove call is a NOP in the case we are already initialized. It's - // just kept here to avoid additional logic. - init_helper_.removeCluster(*existing_active_cluster->second->cluster_); + if (existing_active_cluster != active_clusters_.end()) { + // The following init manager remove call is a NOP in the case we are already initialized. + // It's just kept here to avoid additional logic. + init_helper_.removeCluster(*existing_active_cluster->second->cluster_); + } else { + // Validate that warming clusters are not added to the init_helper_. + // NOTE: This loop is compiled out in optimized builds. + for (const std::list& cluster_list : + {std::cref(init_helper_.primary_init_clusters_), + std::cref(init_helper_.secondary_init_clusters_)}) { + ASSERT(!std::any_of(cluster_list.begin(), cluster_list.end(), + [&existing_warming_cluster](Cluster* cluster) { + return existing_warming_cluster->second->cluster_.get() == cluster; + })); + } + } cm_stats_.cluster_modified_.inc(); } else { cm_stats_.cluster_added_.inc(); @@ -487,7 +523,7 @@ bool ClusterManagerImpl::addOrUpdateCluster(const envoy::api::v2::Cluster& clust loadCluster(cluster, version_info, true, use_active_map ? active_clusters_ : warming_clusters_); if (use_active_map) { - ENVOY_LOG(info, "add/update cluster {} during init", cluster_name); + ENVOY_LOG(debug, "add/update cluster {} during init", cluster_name); auto& cluster_entry = active_clusters_.at(cluster_name); createOrUpdateThreadLocalCluster(*cluster_entry); init_helper_.addCluster(*cluster_entry->cluster_); @@ -553,10 +589,10 @@ bool ClusterManagerImpl::removeCluster(const std::string& cluster_name) { ASSERT(cluster_manager.thread_local_clusters_.count(cluster_name) == 1); ENVOY_LOG(debug, "removing TLS cluster {}", cluster_name); - cluster_manager.thread_local_clusters_.erase(cluster_name); for (auto& cb : cluster_manager.update_callbacks_) { cb->onClusterRemoval(cluster_name); } + cluster_manager.thread_local_clusters_.erase(cluster_name); }); } @@ -631,17 +667,22 @@ void ClusterManagerImpl::loadCluster(const envoy::api::v2::Cluster& cluster, 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 - // finishes. + // finishes. For RingHash/Maglev don't create the LB here if subset balancing is enabled, + // because the thread_aware_lb_ field takes precedence over the subset lb). if (cluster_reference.info()->lbType() == LoadBalancerType::RingHash) { - cluster_entry_it->second->thread_aware_lb_ = std::make_unique( - cluster_reference.prioritySet(), cluster_reference.info()->stats(), - cluster_reference.info()->statsScope(), runtime_, random_, - cluster_reference.info()->lbRingHashConfig(), cluster_reference.info()->lbConfig()); + if (!cluster_reference.info()->lbSubsetInfo().isEnabled()) { + cluster_entry_it->second->thread_aware_lb_ = std::make_unique( + cluster_reference.prioritySet(), cluster_reference.info()->stats(), + cluster_reference.info()->statsScope(), runtime_, random_, + cluster_reference.info()->lbRingHashConfig(), cluster_reference.info()->lbConfig()); + } } else if (cluster_reference.info()->lbType() == LoadBalancerType::Maglev) { - cluster_entry_it->second->thread_aware_lb_ = std::make_unique( - cluster_reference.prioritySet(), cluster_reference.info()->stats(), - cluster_reference.info()->statsScope(), runtime_, random_, - cluster_reference.info()->lbConfig()); + if (!cluster_reference.info()->lbSubsetInfo().isEnabled()) { + cluster_entry_it->second->thread_aware_lb_ = std::make_unique( + cluster_reference.prioritySet(), cluster_reference.info()->stats(), + cluster_reference.info()->statsScope(), runtime_, random_, + cluster_reference.info()->lbConfig()); + } } else if (cluster_reference.info()->lbType() == LoadBalancerType::ClusterProvided) { cluster_entry_it->second->thread_aware_lb_ = std::move(new_cluster_pair.second); } @@ -712,8 +753,8 @@ Tcp::ConnectionPool::Instance* ClusterManagerImpl::tcpConnPoolForCluster( return entry->second->tcpConnPool(priority, context, transport_socket_options); } -void ClusterManagerImpl::postThreadLocalHostRemoval(const Cluster& cluster, - const HostVector& hosts_removed) { +void ClusterManagerImpl::postThreadLocalDrainConnections(const Cluster& cluster, + const HostVector& hosts_removed) { tls_->runOnAllThreads([this, name = cluster.info()->name(), hosts_removed]() { ThreadLocalClusterManagerImpl::removeHosts(name, hosts_removed, *tls_); }); diff --git a/source/common/upstream/cluster_manager_impl.h b/source/common/upstream/cluster_manager_impl.h index ae0ecb2dbd..cb45bb14ac 100644 --- a/source/common/upstream/cluster_manager_impl.h +++ b/source/common/upstream/cluster_manager_impl.h @@ -90,6 +90,9 @@ class ProdClusterManagerFactory : public ClusterManagerFactory { Singleton::Manager& singleton_manager_; }; +// For friend declaration in ClusterManagerInitHelper. +class ClusterManagerImpl; + /** * This is a helper class used during cluster management initialization. Dealing with primary * clusters, secondary clusters, and CDS, is quite complicated, so this makes it easier to test. @@ -100,8 +103,9 @@ class ClusterManagerInitHelper : Logger::Loggable { * @param per_cluster_init_callback supplies the callback to call when a cluster has itself * initialized. The cluster manager can use this for post-init processing. */ - ClusterManagerInitHelper(const std::function& per_cluster_init_callback) - : per_cluster_init_callback_(per_cluster_init_callback) {} + ClusterManagerInitHelper(ClusterManager& cm, + const std::function& per_cluster_init_callback) + : cm_(cm), per_cluster_init_callback_(per_cluster_init_callback) {} enum class State { // Initial state. During this state all static clusters are loaded. Any phase 1 clusters @@ -128,9 +132,14 @@ class ClusterManagerInitHelper : Logger::Loggable { State state() const { return state_; } private: + // To enable invariant assertions on the cluster lists. + friend ClusterManagerImpl; + + void initializeSecondaryClusters(); void maybeFinishInitialize(); void onClusterInit(Cluster& cluster); + ClusterManager& cm_; std::function per_cluster_init_callback_; CdsApi* cds_{}; std::function initialized_callback_; @@ -232,7 +241,8 @@ class ClusterManagerImpl : public ClusterManager, Logger::Loggable( - resources[0], validation_visitor_); - MessageUtil::validate(cluster_load_assignment); + auto cluster_load_assignment = + MessageUtil::anyConvert(resources[0]); + MessageUtil::validate(cluster_load_assignment, validation_visitor_); if (cluster_load_assignment.cluster_name() != cluster_name_) { throw EnvoyException(fmt::format("Unexpected EDS cluster (expecting {}): {}", cluster_name_, cluster_load_assignment.cluster_name())); diff --git a/source/common/upstream/eds.h b/source/common/upstream/eds.h index c7b3ffaba0..0df5f4c844 100644 --- a/source/common/upstream/eds.h +++ b/source/common/upstream/eds.h @@ -38,9 +38,7 @@ class EdsClusterImpl : public BaseDynamicClusterImpl, Config::SubscriptionCallba void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; std::string resourceName(const ProtobufWkt::Any& resource) override { - return MessageUtil::anyConvert(resource, - validation_visitor_) - .cluster_name(); + return MessageUtil::anyConvert(resource).cluster_name(); } using LocalityWeightsMap = diff --git a/source/common/upstream/health_checker_impl.cc b/source/common/upstream/health_checker_impl.cc index 708f5e1329..ddd52756ea 100644 --- a/source/common/upstream/health_checker_impl.cc +++ b/source/common/upstream/health_checker_impl.cc @@ -29,9 +29,11 @@ class HealthCheckerFactoryContextImpl : public Server::Configuration::HealthChec Envoy::Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher, HealthCheckEventLoggerPtr&& event_logger, - ProtobufMessage::ValidationVisitor& validation_visitor) + ProtobufMessage::ValidationVisitor& validation_visitor, + Api::Api& api) : cluster_(cluster), runtime_(runtime), random_(random), dispatcher_(dispatcher), - event_logger_(std::move(event_logger)), validation_visitor_(validation_visitor) {} + event_logger_(std::move(event_logger)), validation_visitor_(validation_visitor), api_(api) { + } Upstream::Cluster& cluster() override { return cluster_; } Envoy::Runtime::Loader& runtime() override { return runtime_; } Envoy::Runtime::RandomGenerator& random() override { return random_; } @@ -40,6 +42,7 @@ class HealthCheckerFactoryContextImpl : public Server::Configuration::HealthChec ProtobufMessage::ValidationVisitor& messageValidationVisitor() override { return validation_visitor_; } + Api::Api& api() override { return api_; } private: Upstream::Cluster& cluster_; @@ -48,14 +51,14 @@ class HealthCheckerFactoryContextImpl : public Server::Configuration::HealthChec Event::Dispatcher& dispatcher_; HealthCheckEventLoggerPtr event_logger_; ProtobufMessage::ValidationVisitor& validation_visitor_; + Api::Api& api_; }; -HealthCheckerSharedPtr -HealthCheckerFactory::create(const envoy::api::v2::core::HealthCheck& health_check_config, - Upstream::Cluster& cluster, Runtime::Loader& runtime, - Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher, - AccessLog::AccessLogManager& log_manager, - ProtobufMessage::ValidationVisitor& validation_visitor) { +HealthCheckerSharedPtr HealthCheckerFactory::create( + const envoy::api::v2::core::HealthCheck& health_check_config, Upstream::Cluster& cluster, + Runtime::Loader& runtime, Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher, + AccessLog::AccessLogManager& log_manager, + ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api) { HealthCheckEventLoggerPtr event_logger; if (!health_check_config.event_log_path().empty()) { event_logger = std::make_unique( @@ -81,7 +84,7 @@ HealthCheckerFactory::create(const envoy::api::v2::core::HealthCheck& health_che health_check_config.custom_health_check().name()); std::unique_ptr context( new HealthCheckerFactoryContextImpl(cluster, runtime, random, dispatcher, - std::move(event_logger), validation_visitor)); + std::move(event_logger), validation_visitor, api)); return factory.createCustomHealthChecker(health_check_config, *context); } default: diff --git a/source/common/upstream/health_checker_impl.h b/source/common/upstream/health_checker_impl.h index 6ca106d947..c16a4393df 100644 --- a/source/common/upstream/health_checker_impl.h +++ b/source/common/upstream/health_checker_impl.h @@ -11,6 +11,7 @@ #include "common/stream_info/stream_info_impl.h" #include "common/upstream/health_checker_base_impl.h" +#include "include/envoy/api/_virtual_includes/api_interface/envoy/api/api.h" #include "src/proto/grpc/health/v1/health.pb.h" namespace Envoy { @@ -32,12 +33,11 @@ class HealthCheckerFactory : public Logger::Loggable * @param validation_visitor message validation visitor instance. * @return a health checker. */ - static HealthCheckerSharedPtr create(const envoy::api::v2::core::HealthCheck& health_check_config, - Upstream::Cluster& cluster, Runtime::Loader& runtime, - Runtime::RandomGenerator& random, - Event::Dispatcher& dispatcher, - AccessLog::AccessLogManager& log_manager, - ProtobufMessage::ValidationVisitor& validation_visitor); + static HealthCheckerSharedPtr + create(const envoy::api::v2::core::HealthCheck& health_check_config, Upstream::Cluster& cluster, + Runtime::Loader& runtime, Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher, + AccessLog::AccessLogManager& log_manager, + ProtobufMessage::ValidationVisitor& validation_visitor, Api::Api& api); }; /** diff --git a/source/common/upstream/health_discovery_service.cc b/source/common/upstream/health_discovery_service.cc index 8fb6f44b1f..894be7a4c9 100644 --- a/source/common/upstream/health_discovery_service.cc +++ b/source/common/upstream/health_discovery_service.cc @@ -152,7 +152,8 @@ void HdsDelegate::processMessage( info_factory_, cm_, local_info_, dispatcher_, random_, singleton_manager_, tls_, validation_visitor_, api_)); - hds_clusters_.back()->startHealthchecks(access_log_manager_, runtime_, random_, dispatcher_); + hds_clusters_.back()->startHealthchecks(access_log_manager_, runtime_, random_, dispatcher_, + api_); } } @@ -243,10 +244,11 @@ ProdClusterInfoFactory::createClusterInfo(const CreateClusterInfoParams& params) void HdsCluster::startHealthchecks(AccessLog::AccessLogManager& access_log_manager, Runtime::Loader& runtime, Runtime::RandomGenerator& random, - Event::Dispatcher& dispatcher) { + Event::Dispatcher& dispatcher, Api::Api& api) { for (auto& health_check : cluster_.health_checks()) { - health_checkers_.push_back(Upstream::HealthCheckerFactory::create( - health_check, *this, runtime, random, dispatcher, access_log_manager, validation_visitor_)); + health_checkers_.push_back( + Upstream::HealthCheckerFactory::create(health_check, *this, runtime, random, dispatcher, + access_log_manager, validation_visitor_, api)); health_checkers_.back()->start(); } } diff --git a/source/common/upstream/health_discovery_service.h b/source/common/upstream/health_discovery_service.h index b1c889a30c..250b915896 100644 --- a/source/common/upstream/health_discovery_service.h +++ b/source/common/upstream/health_discovery_service.h @@ -59,7 +59,8 @@ class HdsCluster : public Cluster, Logger::Loggable { // Creates and starts healthcheckers to its endpoints void startHealthchecks(AccessLog::AccessLogManager& access_log_manager, Runtime::Loader& runtime, - Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher); + Runtime::RandomGenerator& random, Event::Dispatcher& dispatcher, + Api::Api& api); std::vector healthCheckers() { return health_checkers_; }; diff --git a/source/common/upstream/load_balancer_impl.cc b/source/common/upstream/load_balancer_impl.cc index 79bf87b79c..b3172d9c9a 100644 --- a/source/common/upstream/load_balancer_impl.cc +++ b/source/common/upstream/load_balancer_impl.cc @@ -282,7 +282,8 @@ ZoneAwareLoadBalancerBase::ZoneAwareLoadBalancerBase( routing_enabled_(PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( common_config.zone_aware_lb_config(), routing_enabled, 100, 100)), min_cluster_size_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(common_config.zone_aware_lb_config(), - min_cluster_size, 6U)) { + min_cluster_size, 6U)), + fail_traffic_on_panic_(common_config.zone_aware_lb_config().fail_traffic_on_panic()) { ASSERT(!priority_set.hostSetsPerPriority().empty()); resizePerPriorityState(); priority_set_.addPriorityUpdateCb( @@ -539,7 +540,7 @@ uint32_t ZoneAwareLoadBalancerBase::tryChooseLocalLocalityHosts(const HostSet& h return i; } -ZoneAwareLoadBalancerBase::HostsSource +absl::optional ZoneAwareLoadBalancerBase::hostSourceToUse(LoadBalancerContext* context) { auto host_set_and_source = chooseHostSet(context); @@ -549,11 +550,16 @@ ZoneAwareLoadBalancerBase::hostSourceToUse(LoadBalancerContext* context) { HostsSource hosts_source; hosts_source.priority_ = host_set.priority(); - // If the selected host set has insufficient healthy hosts, return all hosts. + // If the selected host set has insufficient healthy hosts, return all hosts (unless we should + // fail traffic on panic, in which case return no host). if (per_priority_panic_[hosts_source.priority_]) { stats_.lb_healthy_panic_.inc(); - hosts_source.source_type_ = HostsSource::SourceType::AllHosts; - return hosts_source; + if (fail_traffic_on_panic_) { + return absl::nullopt; + } else { + hosts_source.source_type_ = HostsSource::SourceType::AllHosts; + return hosts_source; + } } // If we're doing locality weighted balancing, pick locality. @@ -586,10 +592,14 @@ ZoneAwareLoadBalancerBase::hostSourceToUse(LoadBalancerContext* context) { if (isGlobalPanic(localHostSet())) { stats_.lb_local_cluster_not_ok_.inc(); - // If the local Envoy instances are in global panic, do not do locality - // based routing. - hosts_source.source_type_ = sourceType(host_availability); - return hosts_source; + // If the local Envoy instances are in global panic, and we should not fail traffic, do + // not do locality based routing. + if (fail_traffic_on_panic_) { + return absl::nullopt; + } else { + hosts_source.source_type_ = sourceType(host_availability); + return hosts_source; + } } hosts_source.source_type_ = localitySourceType(host_availability); @@ -699,8 +709,11 @@ void EdfLoadBalancerBase::refresh(uint32_t priority) { } HostConstSharedPtr EdfLoadBalancerBase::chooseHostOnce(LoadBalancerContext* context) { - const HostsSource hosts_source = hostSourceToUse(context); - auto scheduler_it = scheduler_.find(hosts_source); + const absl::optional hosts_source = hostSourceToUse(context); + if (!hosts_source) { + return nullptr; + } + auto scheduler_it = scheduler_.find(*hosts_source); // We should always have a scheduler for any return value from // hostSourceToUse() via the construction in refresh(); ASSERT(scheduler_it != scheduler_.end()); @@ -717,11 +730,11 @@ HostConstSharedPtr EdfLoadBalancerBase::chooseHostOnce(LoadBalancerContext* cont } return host; } else { - const HostVector& hosts_to_use = hostSourceToHosts(hosts_source); + const HostVector& hosts_to_use = hostSourceToHosts(*hosts_source); if (hosts_to_use.empty()) { return nullptr; } - return unweightedHostPick(hosts_to_use, hosts_source); + return unweightedHostPick(hosts_to_use, *hosts_source); } } @@ -749,7 +762,12 @@ HostConstSharedPtr LeastRequestLoadBalancer::unweightedHostPick(const HostVector } HostConstSharedPtr RandomLoadBalancer::chooseHostOnce(LoadBalancerContext* context) { - const HostVector& hosts_to_use = hostSourceToHosts(hostSourceToUse(context)); + const absl::optional hosts_source = hostSourceToUse(context); + if (!hosts_source) { + return nullptr; + } + + const HostVector& hosts_to_use = hostSourceToHosts(*hosts_source); 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 98fd0c75d7..988955f359 100644 --- a/source/common/upstream/load_balancer_impl.h +++ b/source/common/upstream/load_balancer_impl.h @@ -223,8 +223,9 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { /** * Pick the host source to use, doing zone aware routing when the hosts are sufficiently healthy. + * If no host is chosen (due to fail_traffic_on_panic being set), return absl::nullopt. */ - HostsSource hostSourceToUse(LoadBalancerContext* context); + absl::optional hostSourceToUse(LoadBalancerContext* context); /** * Index into priority_set via hosts source descriptor. @@ -300,6 +301,7 @@ class ZoneAwareLoadBalancerBase : public LoadBalancerBase { const uint32_t routing_enabled_; const uint64_t min_cluster_size_; + const bool fail_traffic_on_panic_; struct PerPriorityState { // The percent of requests which can be routed to the local locality. diff --git a/source/common/upstream/original_dst_cluster.cc b/source/common/upstream/original_dst_cluster.cc index ec24c36e0e..04b91f58f2 100644 --- a/source/common/upstream/original_dst_cluster.cc +++ b/source/common/upstream/original_dst_cluster.cc @@ -184,10 +184,6 @@ OriginalDstClusterFactory::createClusterImpl( envoy::api::v2::Cluster_LbPolicy_Name(cluster.lb_policy()), envoy::api::v2::Cluster_DiscoveryType_Name(cluster.type()))); } - if (cluster.has_lb_subset_config() && cluster.lb_subset_config().subset_selectors_size() != 0) { - throw EnvoyException( - fmt::format("cluster: cluster type 'original_dst' may not be used with lb_subset_config")); - } // TODO(mattklein123): The original DST load balancer type should be deprecated and instead // the cluster should directly supply the load balancer. This will remove diff --git a/source/common/upstream/outlier_detection_impl.cc b/source/common/upstream/outlier_detection_impl.cc index 0d0efa1473..428e5ad9c7 100644 --- a/source/common/upstream/outlier_detection_impl.cc +++ b/source/common/upstream/outlier_detection_impl.cc @@ -207,35 +207,49 @@ void DetectorHostMonitorImpl::localOriginNoFailure() { } DetectorConfig::DetectorConfig(const envoy::api::v2::cluster::OutlierDetection& config) - : interval_ms_(static_cast(PROTOBUF_GET_MS_OR_DEFAULT(config, interval, 10000))), - base_ejection_time_ms_( - static_cast(PROTOBUF_GET_MS_OR_DEFAULT(config, base_ejection_time, 30000))), - consecutive_5xx_( - static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, consecutive_5xx, 5))), - consecutive_gateway_failure_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, consecutive_gateway_failure, 5))), - max_ejection_percent_( - static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_ejection_percent, 10))), - success_rate_minimum_hosts_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, success_rate_minimum_hosts, 5))), - success_rate_request_volume_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, success_rate_request_volume, 100))), - success_rate_stdev_factor_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, success_rate_stdev_factor, 1900))), - enforcing_consecutive_5xx_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_consecutive_5xx, 100))), + : interval_ms_( + static_cast(PROTOBUF_GET_MS_OR_DEFAULT(config, interval, DEFAULT_INTERVAL_MS))), + base_ejection_time_ms_(static_cast( + PROTOBUF_GET_MS_OR_DEFAULT(config, base_ejection_time, DEFAULT_BASE_EJECTION_TIME_MS))), + consecutive_5xx_(static_cast( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, consecutive_5xx, DEFAULT_CONSECUTIVE_5XX))), + consecutive_gateway_failure_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, consecutive_gateway_failure, DEFAULT_CONSECUTIVE_GATEWAY_FAILURE))), + max_ejection_percent_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, max_ejection_percent, DEFAULT_MAX_EJECTION_PERCENT))), + success_rate_minimum_hosts_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, success_rate_minimum_hosts, DEFAULT_SUCCESS_RATE_MINIMUM_HOSTS))), + success_rate_request_volume_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, success_rate_request_volume, DEFAULT_SUCCESS_RATE_REQUEST_VOLUME))), + success_rate_stdev_factor_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, success_rate_stdev_factor, DEFAULT_SUCCESS_RATE_STDEV_FACTOR))), + failure_percentage_threshold_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, failure_percentage_threshold, DEFAULT_FAILURE_PERCENTAGE_THRESHOLD))), + failure_percentage_minimum_hosts_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, failure_percentage_minimum_hosts, DEFAULT_FAILURE_PERCENTAGE_MINIMUM_HOSTS))), + failure_percentage_request_volume_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, failure_percentage_request_volume, DEFAULT_FAILURE_PERCENTAGE_REQUEST_VOLUME))), + enforcing_consecutive_5xx_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, enforcing_consecutive_5xx, DEFAULT_ENFORCING_CONSECUTIVE_5XX))), enforcing_consecutive_gateway_failure_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_consecutive_gateway_failure, 0))), - enforcing_success_rate_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_success_rate, 100))), + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_consecutive_gateway_failure, + DEFAULT_ENFORCING_CONSECUTIVE_GATEWAY_FAILURE))), + enforcing_success_rate_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, enforcing_success_rate, DEFAULT_ENFORCING_SUCCESS_RATE))), + enforcing_failure_percentage_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, enforcing_failure_percentage, DEFAULT_ENFORCING_FAILURE_PERCENTAGE))), + enforcing_failure_percentage_local_origin_(static_cast( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_failure_percentage_local_origin, + DEFAULT_ENFORCING_FAILURE_PERCENTAGE_LOCAL_ORIGIN))), split_external_local_origin_errors_(config.split_external_local_origin_errors()), - consecutive_local_origin_failure_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, consecutive_local_origin_failure, 5))), - enforcing_consecutive_local_origin_failure_( - static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( - config, enforcing_consecutive_local_origin_failure, 100))), + consecutive_local_origin_failure_(static_cast(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + config, consecutive_local_origin_failure, DEFAULT_CONSECUTIVE_LOCAL_ORIGIN_FAILURE))), + enforcing_consecutive_local_origin_failure_(static_cast( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_consecutive_local_origin_failure, + DEFAULT_ENFORCING_CONSECUTIVE_LOCAL_ORIGIN_FAILURE))), enforcing_local_origin_success_rate_(static_cast( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_local_origin_success_rate, 100))) {} + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, enforcing_local_origin_success_rate, + DEFAULT_ENFORCING_LOCAL_ORIGIN_SUCCESS_RATE))) {} DetectorImpl::DetectorImpl(const Cluster& cluster, const envoy::api::v2::cluster::OutlierDetection& config, @@ -355,6 +369,13 @@ bool DetectorImpl::enforceEjection(envoy::data::cluster::v2alpha::OutlierEjectio return runtime_.snapshot().featureEnabled( "outlier_detection.enforcing_local_origin_success_rate", config_.enforcingLocalOriginSuccessRate()); + case envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE: + return runtime_.snapshot().featureEnabled("outlier_detection.enforcing_failure_percentage", + config_.enforcingFailurePercentage()); + case envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE_LOCAL_ORIGIN: + return runtime_.snapshot().featureEnabled( + "outlier_detection.enforcing_failure_percentage_local_origin", + config_.enforcingFailurePercentageLocalOrigin()); default: // Checked by schema. NOT_REACHED_GCOVR_EXCL_LINE; @@ -382,6 +403,12 @@ void DetectorImpl::updateEnforcedEjectionStats( case envoy::data::cluster::v2alpha::OutlierEjectionType::SUCCESS_RATE_LOCAL_ORIGIN: stats_.ejections_enforced_local_origin_success_rate_.inc(); break; + case envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE: + stats_.ejections_enforced_failure_percentage_.inc(); + break; + case envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE_LOCAL_ORIGIN: + stats_.ejections_enforced_local_origin_failure_percentage_.inc(); + break; default: // Checked by schema. NOT_REACHED_GCOVR_EXCL_LINE; @@ -406,6 +433,12 @@ void DetectorImpl::updateDetectedEjectionStats( case envoy::data::cluster::v2alpha::OutlierEjectionType::SUCCESS_RATE_LOCAL_ORIGIN: stats_.ejections_detected_local_origin_success_rate_.inc(); break; + case envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE: + stats_.ejections_detected_failure_percentage_.inc(); + break; + case envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE_LOCAL_ORIGIN: + stats_.ejections_detected_local_origin_failure_percentage_.inc(); + break; default: // Checked by schema. NOT_REACHED_GCOVR_EXCL_LINE; @@ -556,32 +589,55 @@ void DetectorImpl::processSuccessRateEjections( "outlier_detection.success_rate_minimum_hosts", config_.successRateMinimumHosts()); uint64_t success_rate_request_volume = runtime_.snapshot().getInteger( "outlier_detection.success_rate_request_volume", config_.successRateRequestVolume()); + uint64_t failure_percentage_minimum_hosts = + runtime_.snapshot().getInteger("outlier_detection.failure_percentage_minimum_hosts", + config_.failurePercentageMinimumHosts()); + uint64_t failure_percentage_request_volume = + runtime_.snapshot().getInteger("outlier_detection.failure_percentage_request_volume", + config_.failurePercentageRequestVolume()); + std::vector valid_success_rate_hosts; + std::vector valid_failure_percentage_hosts; double success_rate_sum = 0; // Reset the Detector's success rate mean and stdev. getSRNums(monitor_type) = {-1, -1}; // Exit early if there are not enough hosts. - if (host_monitors_.size() < success_rate_minimum_hosts) { + if (host_monitors_.size() < success_rate_minimum_hosts && + host_monitors_.size() < failure_percentage_minimum_hosts) { return; } // reserve upper bound of vector size to avoid reallocation. valid_success_rate_hosts.reserve(host_monitors_.size()); + valid_failure_percentage_hosts.reserve(host_monitors_.size()); for (const auto& host : host_monitors_) { // Don't do work if the host is already ejected. if (!host.first->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)) { - absl::optional host_success_rate = host.second->getSRMonitor(monitor_type) - .successRateAccumulator() - .getSuccessRate(success_rate_request_volume); - - if (host_success_rate) { - valid_success_rate_hosts.emplace_back( - HostSuccessRatePair(host.first, host_success_rate.value())); - success_rate_sum += host_success_rate.value(); - host.second->successRate(monitor_type, host_success_rate.value()); + absl::optional> host_success_rate_and_volume = + host.second->getSRMonitor(monitor_type) + .successRateAccumulator() + .getSuccessRateAndVolume(); + + if (!host_success_rate_and_volume) { + continue; + } + double success_rate = host_success_rate_and_volume.value().first; + double request_volume = host_success_rate_and_volume.value().second; + + if (request_volume >= + std::min(success_rate_request_volume, failure_percentage_request_volume)) { + host.second->successRate(monitor_type, success_rate); + } + + if (request_volume >= success_rate_request_volume) { + valid_success_rate_hosts.emplace_back(HostSuccessRatePair(host.first, success_rate)); + success_rate_sum += success_rate; + } + if (request_volume >= failure_percentage_request_volume) { + valid_failure_percentage_hosts.emplace_back(HostSuccessRatePair(host.first, success_rate)); } } } @@ -607,6 +663,28 @@ void DetectorImpl::processSuccessRateEjections( } } } + + if (!valid_failure_percentage_hosts.empty() && + valid_failure_percentage_hosts.size() >= failure_percentage_minimum_hosts) { + const double failure_percentage_threshold = runtime_.snapshot().getInteger( + "outlier_detection.failure_percentage_threshold", config_.failurePercentageThreshold()); + + for (const auto& host_success_rate_pair : valid_failure_percentage_hosts) { + if ((100.0 - host_success_rate_pair.success_rate_) >= failure_percentage_threshold) { + // We should eject. + + // The ejection type returned by the SuccessRateMonitor's getEjectionType() will be a + // SUCCESS_RATE type, so we need to figure it out for ourselves. + const envoy::data::cluster::v2alpha::OutlierEjectionType type = + (monitor_type == DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin) + ? envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE + : envoy::data::cluster::v2alpha::OutlierEjectionType:: + FAILURE_PERCENTAGE_LOCAL_ORIGIN; + updateDetectedEjectionStats(type); + ejectHost(host_success_rate_pair.host_, type); + } + } + } } void DetectorImpl::onIntervalTimer() { @@ -660,6 +738,15 @@ void EventLoggerImpl::logEject(const HostDescriptionConstSharedPtr& host, Detect detector.successRateEjectionThreshold(monitor_type)); event.mutable_eject_success_rate_event()->set_host_success_rate( host->outlierDetector().successRate(monitor_type)); + } else if ((type == envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE) || + (type == envoy::data::cluster::v2alpha::OutlierEjectionType:: + FAILURE_PERCENTAGE_LOCAL_ORIGIN)) { + const DetectorHostMonitor::SuccessRateMonitorType monitor_type = + (type == envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE) + ? DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin + : DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin; + event.mutable_eject_failure_percentage_event()->set_host_success_rate( + host->outlierDetector().successRate(monitor_type)); } else { event.mutable_eject_consecutive_event(); } @@ -707,14 +794,15 @@ SuccessRateAccumulatorBucket* SuccessRateAccumulator::updateCurrentWriter() { return current_success_rate_bucket_.get(); } -absl::optional -SuccessRateAccumulator::getSuccessRate(uint64_t success_rate_request_volume) { - if (backup_success_rate_bucket_->total_request_counter_ < success_rate_request_volume) { - return {}; +absl::optional> SuccessRateAccumulator::getSuccessRateAndVolume() { + if (!backup_success_rate_bucket_->total_request_counter_) { + return absl::nullopt; } - return {backup_success_rate_bucket_->success_request_counter_ * 100.0 / - backup_success_rate_bucket_->total_request_counter_}; + double success_rate = backup_success_rate_bucket_->success_request_counter_ * 100.0 / + backup_success_rate_bucket_->total_request_counter_; + + return {{success_rate, backup_success_rate_bucket_->total_request_counter_}}; } } // namespace Outlier diff --git a/source/common/upstream/outlier_detection_impl.h b/source/common/upstream/outlier_detection_impl.h index 2b703c2adb..609df03355 100644 --- a/source/common/upstream/outlier_detection_impl.h +++ b/source/common/upstream/outlier_detection_impl.h @@ -92,7 +92,7 @@ class SuccessRateAccumulator { * @return a valid absl::optional with the success rate. If there were not enough * requests, an invalid absl::optional is returned. */ - absl::optional getSuccessRate(uint64_t success_rate_request_volume); + absl::optional> getSuccessRateAndVolume(); private: std::unique_ptr current_success_rate_bucket_; @@ -214,13 +214,17 @@ class DetectorHostMonitorImpl : public DetectorHostMonitor { COUNTER(ejections_detected_consecutive_5xx) \ COUNTER(ejections_detected_consecutive_gateway_failure) \ COUNTER(ejections_detected_success_rate) \ + COUNTER(ejections_detected_failure_percentage) \ COUNTER(ejections_enforced_consecutive_5xx) \ COUNTER(ejections_enforced_consecutive_gateway_failure) \ COUNTER(ejections_enforced_success_rate) \ + COUNTER(ejections_enforced_failure_percentage) \ COUNTER(ejections_detected_consecutive_local_origin_failure) \ COUNTER(ejections_enforced_consecutive_local_origin_failure) \ COUNTER(ejections_detected_local_origin_success_rate) \ COUNTER(ejections_enforced_local_origin_success_rate) \ + COUNTER(ejections_detected_local_origin_failure_percentage) \ + COUNTER(ejections_enforced_local_origin_failure_percentage) \ COUNTER(ejections_enforced_total) \ COUNTER(ejections_overflow) \ COUNTER(ejections_success_rate) \ @@ -249,11 +253,18 @@ class DetectorConfig { uint64_t successRateMinimumHosts() const { return success_rate_minimum_hosts_; } uint64_t successRateRequestVolume() const { return success_rate_request_volume_; } uint64_t successRateStdevFactor() const { return success_rate_stdev_factor_; } + uint64_t failurePercentageThreshold() const { return failure_percentage_threshold_; } + uint64_t failurePercentageMinimumHosts() const { return failure_percentage_minimum_hosts_; } + uint64_t failurePercentageRequestVolume() const { return failure_percentage_request_volume_; } uint64_t enforcingConsecutive5xx() const { return enforcing_consecutive_5xx_; } uint64_t enforcingConsecutiveGatewayFailure() const { return enforcing_consecutive_gateway_failure_; } uint64_t enforcingSuccessRate() const { return enforcing_success_rate_; } + uint64_t enforcingFailurePercentage() const { return enforcing_failure_percentage_; } + uint64_t enforcingFailurePercentageLocalOrigin() const { + return enforcing_failure_percentage_local_origin_; + } bool splitExternalLocalOriginErrors() const { return split_external_local_origin_errors_; } uint64_t consecutiveLocalOriginFailure() const { return consecutive_local_origin_failure_; } uint64_t enforcingConsecutiveLocalOriginFailure() const { @@ -270,13 +281,38 @@ class DetectorConfig { const uint64_t success_rate_minimum_hosts_; const uint64_t success_rate_request_volume_; const uint64_t success_rate_stdev_factor_; + const uint64_t failure_percentage_threshold_; + const uint64_t failure_percentage_minimum_hosts_; + const uint64_t failure_percentage_request_volume_; const uint64_t enforcing_consecutive_5xx_; const uint64_t enforcing_consecutive_gateway_failure_; const uint64_t enforcing_success_rate_; + const uint64_t enforcing_failure_percentage_; + const uint64_t enforcing_failure_percentage_local_origin_; const bool split_external_local_origin_errors_; const uint64_t consecutive_local_origin_failure_; const uint64_t enforcing_consecutive_local_origin_failure_; const uint64_t enforcing_local_origin_success_rate_; + + static const uint64_t DEFAULT_INTERVAL_MS = 10000; + static const uint64_t DEFAULT_BASE_EJECTION_TIME_MS = 30000; + static const uint64_t DEFAULT_CONSECUTIVE_5XX = 5; + static const uint64_t DEFAULT_CONSECUTIVE_GATEWAY_FAILURE = 5; + static const uint64_t DEFAULT_MAX_EJECTION_PERCENT = 10; + static const uint64_t DEFAULT_SUCCESS_RATE_MINIMUM_HOSTS = 5; + static const uint64_t DEFAULT_SUCCESS_RATE_REQUEST_VOLUME = 100; + static const uint64_t DEFAULT_SUCCESS_RATE_STDEV_FACTOR = 1900; + static const uint64_t DEFAULT_FAILURE_PERCENTAGE_THRESHOLD = 85; + static const uint64_t DEFAULT_FAILURE_PERCENTAGE_MINIMUM_HOSTS = 5; + static const uint64_t DEFAULT_FAILURE_PERCENTAGE_REQUEST_VOLUME = 50; + static const uint64_t DEFAULT_ENFORCING_CONSECUTIVE_5XX = 100; + static const uint64_t DEFAULT_ENFORCING_CONSECUTIVE_GATEWAY_FAILURE = 0; + static const uint64_t DEFAULT_ENFORCING_SUCCESS_RATE = 100; + static const uint64_t DEFAULT_ENFORCING_FAILURE_PERCENTAGE = 0; + static const uint64_t DEFAULT_ENFORCING_FAILURE_PERCENTAGE_LOCAL_ORIGIN = 0; + static const uint64_t DEFAULT_CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 5; + static const uint64_t DEFAULT_ENFORCING_CONSECUTIVE_LOCAL_ORIGIN_FAILURE = 100; + static const uint64_t DEFAULT_ENFORCING_LOCAL_ORIGIN_SUCCESS_RATE = 100; }; /** diff --git a/source/common/upstream/thread_aware_lb_impl.h b/source/common/upstream/thread_aware_lb_impl.h index 2cf47f4e75..3f68d1afbb 100644 --- a/source/common/upstream/thread_aware_lb_impl.h +++ b/source/common/upstream/thread_aware_lb_impl.h @@ -73,10 +73,10 @@ class ThreadAwareLoadBalancerBase : public LoadBalancerBase, public ThreadAwareL ClusterStats& stats_; Runtime::RandomGenerator& random_; absl::Mutex mutex_; - std::shared_ptr> per_priority_state_ GUARDED_BY(mutex_); + std::shared_ptr> per_priority_state_ ABSL_GUARDED_BY(mutex_); // This is split out of PerPriorityState so LoadBalancerBase::ChoosePriority can be reused. - std::shared_ptr healthy_per_priority_load_ GUARDED_BY(mutex_); - std::shared_ptr degraded_per_priority_load_ GUARDED_BY(mutex_); + std::shared_ptr healthy_per_priority_load_ ABSL_GUARDED_BY(mutex_); + std::shared_ptr degraded_per_priority_load_ ABSL_GUARDED_BY(mutex_); }; virtual HashingLoadBalancerSharedPtr diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index f44e01f5a5..7c4dfa231b 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -140,7 +140,7 @@ createProtocolOptionsConfig(const std::string& name, const ProtobufWkt::Any& typ Envoy::Config::Utility::translateOpaqueConfig(typed_config, config, validation_visitor, *proto_config); - return factory->createProtocolOptionsConfig(*proto_config); + return factory->createProtocolOptionsConfig(*proto_config, validation_visitor); } std::map @@ -598,7 +598,7 @@ ClusterInfoImpl::ClusterInfoImpl( per_connection_buffer_limit_bytes_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, per_connection_buffer_limit_bytes, 1024 * 1024)), transport_socket_factory_(std::move(socket_factory)), stats_scope_(std::move(stats_scope)), - stats_(generateStats(*stats_scope_)), + stats_(generateStats(*stats_scope_)), load_report_stats_store_(stats_scope_->symbolTable()), load_report_stats_(generateLoadReportStats(load_report_stats_store_)), features_(parseFeatures(config)), http2_settings_(Http::Utility::parseHttp2Settings(config.http2_protocol_options())), @@ -643,12 +643,24 @@ ClusterInfoImpl::ClusterInfoImpl( envoy::api::v2::Cluster_LbPolicy_Name(config.lb_policy()), envoy::api::v2::Cluster_DiscoveryType_Name(config.type()))); } + if (config.has_lb_subset_config()) { + throw EnvoyException( + fmt::format("cluster: LB policy {} cannot be combined with lb_subset_config", + envoy::api::v2::Cluster_LbPolicy_Name(config.lb_policy()))); + } + lb_type_ = LoadBalancerType::ClusterProvided; break; case envoy::api::v2::Cluster::MAGLEV: lb_type_ = LoadBalancerType::Maglev; break; case envoy::api::v2::Cluster::CLUSTER_PROVIDED: + if (config.has_lb_subset_config()) { + throw EnvoyException( + fmt::format("cluster: LB policy {} cannot be combined with lb_subset_config", + envoy::api::v2::Cluster_LbPolicy_Name(config.lb_policy()))); + } + lb_type_ = LoadBalancerType::ClusterProvided; break; default: @@ -1311,12 +1323,6 @@ bool BaseDynamicClusterImpl::updateDynamicHostList(const HostVector& new_hosts, // At this point we've accounted for all the new hosts as well the hosts that previously // existed in this priority. - - // TODO(mattklein123): This stat is used by both the RR and LR load balancer to decide at - // runtime whether to use either the weighted or unweighted mode. If we extend weights to - // static clusters or DNS SRV clusters we need to make sure this gets set. Better, we should - // avoid pivoting on this entirely and probably just force a host set refresh if any weights - // change. info_->stats().max_host_weight_.set(max_host_weight); // Whatever remains in current_priority_hosts should be removed. diff --git a/source/common/upstream/upstream_impl.h b/source/common/upstream/upstream_impl.h index 07469e36d0..89c5071daa 100644 --- a/source/common/upstream/upstream_impl.h +++ b/source/common/upstream/upstream_impl.h @@ -75,7 +75,9 @@ class HostDescriptionImpl : virtual public HostDescription { .bool_value()), metadata_(std::make_shared(metadata)), locality_(locality), locality_zone_stat_name_(locality.zone(), cluster->statsScope().symbolTable()), - stats_{ALL_HOST_STATS(POOL_COUNTER(stats_store_), POOL_GAUGE(stats_store_))}, + stats_store_(cluster->statsScope().symbolTable()), stats_{ALL_HOST_STATS( + POOL_COUNTER(stats_store_), + POOL_GAUGE(stats_store_))}, priority_(priority) { if (health_check_config.port_value() != 0 && dest_address->type() != Network::Address::Type::Ip) { @@ -151,7 +153,7 @@ class HostDescriptionImpl : virtual public HostDescription { Network::Address::InstanceConstSharedPtr health_check_address_; std::atomic canary_; mutable absl::Mutex metadata_mutex_; - std::shared_ptr metadata_ GUARDED_BY(metadata_mutex_); + std::shared_ptr metadata_ ABSL_GUARDED_BY(metadata_mutex_); const envoy::api::v2::core::Locality locality_; Stats::StatNameManagedStorage locality_zone_stat_name_; Stats::IsolatedStoreImpl stats_store_; diff --git a/source/exe/BUILD b/source/exe/BUILD index 4117a4bb83..7ed716a317 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -57,7 +57,8 @@ envoy_cc_library( ], deps = [ ":envoy_main_common_lib", - ] + envoy_cc_platform_dep("platform_impl_lib"), + ":platform_impl_lib", + ], ) envoy_cc_library( @@ -66,11 +67,12 @@ envoy_cc_library( hdrs = ["main_common.h"], deps = [ ":envoy_common_lib", + ":platform_impl_lib", ":process_wide_lib", "//source/common/api:os_sys_calls_lib", "//source/common/common:compiler_requirements_lib", "//source/common/common:perf_annotation_lib", - "//source/common/stats:fake_symbol_table_lib", + "//source/common/stats:symbol_table_creator_lib", "//source/server:hot_restart_lib", "//source/server:hot_restart_nop_lib", "//source/server/config_validation:server_lib", @@ -80,7 +82,7 @@ envoy_cc_library( "//source/common/signal:sigaction_lib", ":terminate_handler_lib", ], - }) + envoy_cc_platform_dep("platform_impl_lib"), + }), ) envoy_cc_library( @@ -96,11 +98,26 @@ envoy_cc_library( ] + envoy_google_grpc_external_deps(), ) +envoy_cc_library( + name = "platform_impl_lib", + deps = [":platform_header_lib"] + + envoy_cc_platform_dep("platform_impl_lib"), +) + +envoy_cc_library( + name = "platform_header_lib", + hdrs = ["platform_impl.h"], + deps = [ + "//include/envoy/filesystem:filesystem_interface", + "//include/envoy/thread:thread_interface", + ], +) + envoy_cc_posix_library( name = "platform_impl_lib", - hdrs = ["posix/platform_impl.h"], - strip_include_prefix = "posix", + srcs = ["posix/platform_impl.cc"], deps = [ + ":platform_header_lib", "//source/common/common:thread_lib", "//source/common/filesystem:filesystem_lib", ], @@ -108,9 +125,9 @@ envoy_cc_posix_library( envoy_cc_win32_library( name = "platform_impl_lib", - hdrs = ["win32/platform_impl.h"], - strip_include_prefix = "win32", + srcs = ["win32/platform_impl.cc"], deps = [ + ":platform_header_lib", "//source/common/common:assert_lib", "//source/common/common:thread_lib", "//source/common/filesystem:filesystem_lib", diff --git a/source/exe/main_common.cc b/source/exe/main_common.cc index 84d57abfc5..ed76b43818 100644 --- a/source/exe/main_common.cc +++ b/source/exe/main_common.cc @@ -7,6 +7,7 @@ #include "common/common/compiler_requirements.h" #include "common/common/perf_annotation.h" #include "common/network/utility.h" +#include "common/stats/symbol_table_creator.h" #include "common/stats/thread_local_store.h" #include "server/config_validation/server.h" @@ -45,7 +46,9 @@ MainCommonBase::MainCommonBase(const OptionsImpl& options, Event::TimeSystem& ti Filesystem::Instance& file_system, std::unique_ptr process_context) : options_(options), component_factory_(component_factory), thread_factory_(thread_factory), - file_system_(file_system), stats_allocator_(symbol_table_) { + file_system_(file_system), symbol_table_(Stats::SymbolTableCreator::initAndMakeSymbolTable( + options_.fakeSymbolTableEnabled())), + stats_allocator_(*symbol_table_) { switch (options_.mode()) { case Server::Mode::InitOnly: case Server::Mode::Serve: { diff --git a/source/exe/main_common.h b/source/exe/main_common.h index 5faf31b547..a0a4796de1 100644 --- a/source/exe/main_common.h +++ b/source/exe/main_common.h @@ -67,10 +67,10 @@ class MainCommonBase { protected: ProcessWide process_wide_; // Process-wide state setup/teardown. const Envoy::OptionsImpl& options_; - Stats::FakeSymbolTableImpl symbol_table_; Server::ComponentFactory& component_factory_; Thread::ThreadFactory& thread_factory_; Filesystem::Instance& file_system_; + Stats::SymbolTablePtr symbol_table_; Stats::AllocatorImpl stats_allocator_; std::unique_ptr tls_; diff --git a/source/exe/platform_impl.h b/source/exe/platform_impl.h new file mode 100644 index 0000000000..4c05dff225 --- /dev/null +++ b/source/exe/platform_impl.h @@ -0,0 +1,20 @@ +#pragma once + +#include "envoy/filesystem/filesystem.h" +#include "envoy/thread/thread.h" + +namespace Envoy { + +class PlatformImpl { +public: + PlatformImpl(); + ~PlatformImpl(); + Thread::ThreadFactory& threadFactory() { return *thread_factory_; } + Filesystem::Instance& fileSystem() { return *file_system_; } + +private: + std::unique_ptr thread_factory_; + std::unique_ptr file_system_; +}; + +} // namespace Envoy diff --git a/source/exe/posix/platform_impl.cc b/source/exe/posix/platform_impl.cc new file mode 100644 index 0000000000..8fc227724b --- /dev/null +++ b/source/exe/posix/platform_impl.cc @@ -0,0 +1,14 @@ +#include "common/common/thread_impl.h" +#include "common/filesystem/filesystem_impl.h" + +#include "exe/platform_impl.h" + +namespace Envoy { + +PlatformImpl::PlatformImpl() + : thread_factory_(std::make_unique()), + file_system_(std::make_unique()) {} + +PlatformImpl::~PlatformImpl() = default; + +} // namespace Envoy diff --git a/source/exe/posix/platform_impl.h b/source/exe/posix/platform_impl.h deleted file mode 100644 index 45fbd73407..0000000000 --- a/source/exe/posix/platform_impl.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "common/common/thread_impl.h" -#include "common/filesystem/filesystem_impl.h" - -namespace Envoy { - -class PlatformImpl { -public: - Thread::ThreadFactory& threadFactory() { return thread_factory_; } - Filesystem::Instance& fileSystem() { return file_system_; } - -private: - Thread::ThreadFactoryImplPosix thread_factory_; - Filesystem::InstanceImplPosix file_system_; -}; - -} // namespace Envoy diff --git a/source/exe/win32/platform_impl.cc b/source/exe/win32/platform_impl.cc new file mode 100644 index 0000000000..674ad0db0b --- /dev/null +++ b/source/exe/win32/platform_impl.cc @@ -0,0 +1,24 @@ +#include "common/common/assert.h" +#include "common/common/thread_impl.h" +#include "common/filesystem/filesystem_impl.h" + +#include "exe/platform_impl.h" + +// clang-format off +#include +// clang-format on + +namespace Envoy { + +PlatformImpl::PlatformImpl() + : thread_factory_(std::make_unique()), + file_system_(std::make_unique()) { + const WORD wVersionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + const int rc = ::WSAStartup(wVersionRequested, &wsaData); + RELEASE_ASSERT(rc == 0, "WSAStartup failed with error"); +} + +PlatformImpl::~PlatformImpl() { ::WSACleanup(); } + +} // namespace Envoy diff --git a/source/exe/win32/platform_impl.h b/source/exe/win32/platform_impl.h deleted file mode 100644 index ffb239dd7e..0000000000 --- a/source/exe/win32/platform_impl.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include "common/common/assert.h" -#include "common/common/thread_impl.h" -#include "common/filesystem/filesystem_impl.h" - -// clang-format off -#include -// clang-format on - -namespace Envoy { - -class PlatformImpl { -public: - PlatformImpl() { - const WORD wVersionRequested = MAKEWORD(2, 2); - WSADATA wsaData; - const int rc = ::WSAStartup(wVersionRequested, &wsaData); - RELEASE_ASSERT(rc == 0, "WSAStartup failed with error"); - } - - ~PlatformImpl() { ::WSACleanup(); } - - Thread::ThreadFactory& threadFactory() { return thread_factory_; } - Filesystem::Instance& fileSystem() { return file_system_; } - -private: - Thread::ThreadFactoryImplWin32 thread_factory_; - Filesystem::InstanceImplWin32 file_system_; -}; - -} // namespace Envoy diff --git a/source/extensions/access_loggers/file/config.cc b/source/extensions/access_loggers/file/config.cc index d04f3965a0..fcf432a861 100644 --- a/source/extensions/access_loggers/file/config.cc +++ b/source/extensions/access_loggers/file/config.cc @@ -24,7 +24,8 @@ FileAccessLogFactory::createAccessLogInstance(const Protobuf::Message& config, AccessLog::FilterPtr&& filter, Server::Configuration::FactoryContext& context) { const auto& fal_config = - MessageUtil::downcastAndValidate(config); + MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()); AccessLog::FormatterPtr formatter; if (fal_config.access_log_format_case() == envoy::config::accesslog::v2::FileAccessLog::kFormat || diff --git a/source/extensions/access_loggers/grpc/BUILD b/source/extensions/access_loggers/grpc/BUILD index e0e7059526..e7ad7b2995 100644 --- a/source/extensions/access_loggers/grpc/BUILD +++ b/source/extensions/access_loggers/grpc/BUILD @@ -11,6 +11,18 @@ load( envoy_package() +envoy_cc_library( + name = "config_utils", + srcs = ["config_utils.cc"], + hdrs = ["config_utils.h"], + deps = [ + ":grpc_access_log_lib", + "//include/envoy/registry", + "//include/envoy/server:filter_config_interface", + "//include/envoy/singleton:instance_interface", + ], +) + envoy_cc_library( name = "grpc_access_log_lib", srcs = ["grpc_access_log_impl.cc"], @@ -18,7 +30,6 @@ envoy_cc_library( deps = [ "//include/envoy/grpc:async_client_interface", "//include/envoy/grpc:async_client_manager_interface", - "//include/envoy/singleton:instance_interface", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/upstream:cluster_manager_interface", "//include/envoy/upstream:upstream_interface", @@ -54,6 +65,16 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "tcp_grpc_access_log_lib", + srcs = ["tcp_grpc_access_log_impl.cc"], + hdrs = ["tcp_grpc_access_log_impl.h"], + deps = [ + ":grpc_access_log_lib", + ":grpc_access_log_utils", + ], +) + envoy_cc_library( name = "grpc_access_log_proto_descriptors_lib", srcs = ["grpc_access_log_proto_descriptors.cc"], @@ -70,7 +91,7 @@ envoy_cc_library( srcs = ["http_config.cc"], hdrs = ["http_config.h"], deps = [ - "//include/envoy/registry", + ":config_utils", "//include/envoy/server:access_log_config_interface", "//source/common/common:assert_lib", "//source/common/protobuf", @@ -79,3 +100,18 @@ envoy_cc_library( "//source/extensions/access_loggers/grpc:http_grpc_access_log_lib", ], ) + +envoy_cc_library( + name = "tcp_config", + srcs = ["tcp_config.cc"], + hdrs = ["tcp_config.h"], + deps = [ + ":config_utils", + "//include/envoy/server:access_log_config_interface", + "//source/common/common:assert_lib", + "//source/common/protobuf", + "//source/extensions/access_loggers:well_known_names", + "//source/extensions/access_loggers/grpc:grpc_access_log_proto_descriptors_lib", + "//source/extensions/access_loggers/grpc:tcp_grpc_access_log_lib", + ], +) diff --git a/source/extensions/access_loggers/grpc/config_utils.cc b/source/extensions/access_loggers/grpc/config_utils.cc new file mode 100644 index 0000000000..5d2a648a0f --- /dev/null +++ b/source/extensions/access_loggers/grpc/config_utils.cc @@ -0,0 +1,25 @@ +#include "extensions/access_loggers/grpc/config_utils.h" + +#include "envoy/singleton/manager.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace GrpcCommon { + +// Singleton registration via macro defined in envoy/singleton/manager.h +SINGLETON_MANAGER_REGISTRATION(grpc_access_logger_cache); + +std::shared_ptr +getGrpcAccessLoggerCacheSingleton(Server::Configuration::FactoryContext& context) { + return context.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(grpc_access_logger_cache), [&context] { + return std::make_shared( + context.clusterManager().grpcAsyncClientManager(), context.scope(), + context.threadLocal(), context.localInfo()); + }); +} +} // namespace GrpcCommon +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/source/extensions/access_loggers/grpc/config_utils.h b/source/extensions/access_loggers/grpc/config_utils.h new file mode 100644 index 0000000000..f95b53f6a7 --- /dev/null +++ b/source/extensions/access_loggers/grpc/config_utils.h @@ -0,0 +1,18 @@ +#pragma once + +#include "envoy/server/filter_config.h" + +#include "extensions/access_loggers/grpc/grpc_access_log_impl.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace GrpcCommon { + +GrpcAccessLoggerCacheSharedPtr +getGrpcAccessLoggerCacheSingleton(Server::Configuration::FactoryContext& context); + +} // namespace GrpcCommon +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc index 38020326b7..962e3a6808 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc @@ -38,14 +38,22 @@ GrpcAccessLoggerImpl::GrpcAccessLoggerImpl(Grpc::RawAsyncClientPtr&& client, std void GrpcAccessLoggerImpl::log(envoy::data::accesslog::v2::HTTPAccessLogEntry&& entry) { approximate_message_size_bytes_ += entry.ByteSizeLong(); - message_.mutable_http_logs()->add_log_entry()->Swap(&entry); + message_.mutable_http_logs()->mutable_log_entry()->Add(std::move(entry)); + if (approximate_message_size_bytes_ >= buffer_size_bytes_) { + flush(); + } +} + +void GrpcAccessLoggerImpl::log(envoy::data::accesslog::v2::TCPAccessLogEntry&& entry) { + approximate_message_size_bytes_ += entry.ByteSizeLong(); + message_.mutable_tcp_logs()->mutable_log_entry()->Add(std::move(entry)); if (approximate_message_size_bytes_ >= buffer_size_bytes_) { flush(); } } void GrpcAccessLoggerImpl::flush() { - if (!message_.has_http_logs()) { + if (!message_.has_http_logs() && !message_.has_tcp_logs()) { // Nothing to flush. return; } @@ -88,11 +96,11 @@ GrpcAccessLoggerCacheImpl::GrpcAccessLoggerCacheImpl(Grpc::AsyncClientManager& a } GrpcAccessLoggerSharedPtr GrpcAccessLoggerCacheImpl::getOrCreateLogger( - const envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config) { + const envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config, + GrpcAccessLoggerType logger_type) { // TODO(euroelessar): Consider cleaning up loggers. auto& cache = tls_slot_->getTyped(); - // TODO(lizan): Include logger type in the hash - const std::size_t cache_key = MessageUtil::hash(config); + const auto cache_key = std::make_pair(MessageUtil::hash(config), logger_type); const auto it = cache.access_loggers_.find(cache_key); if (it != cache.access_loggers_.end()) { return it->second; diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_impl.h b/source/extensions/access_loggers/grpc/grpc_access_log_impl.h index 71745adc54..8c254e47bc 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_impl.h +++ b/source/extensions/access_loggers/grpc/grpc_access_log_impl.h @@ -36,10 +36,18 @@ class GrpcAccessLogger { * @param entry supplies the access log to send. */ virtual void log(envoy::data::accesslog::v2::HTTPAccessLogEntry&& entry) PURE; + + /** + * Log tcp access entry. + * @param entry supplies the access log to send. + */ + virtual void log(envoy::data::accesslog::v2::TCPAccessLogEntry&& entry) PURE; }; using GrpcAccessLoggerSharedPtr = std::shared_ptr; +enum class GrpcAccessLoggerType { TCP, HTTP }; + /** * Interface for an access logger cache. The cache deals with threading and de-duplicates loggers * for the same configuration. @@ -54,7 +62,8 @@ class GrpcAccessLoggerCache { * @return GrpcAccessLoggerSharedPtr ready for logging requests. */ virtual GrpcAccessLoggerSharedPtr - getOrCreateLogger(const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config) PURE; + getOrCreateLogger(const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config, + GrpcAccessLoggerType logger_type) PURE; }; using GrpcAccessLoggerCacheSharedPtr = std::shared_ptr; @@ -66,7 +75,9 @@ class GrpcAccessLoggerImpl : public GrpcAccessLogger { uint64_t buffer_size_bytes, Event::Dispatcher& dispatcher, const LocalInfo::LocalInfo& local_info); + // Extensions::AccessLoggers::GrpcCommon::GrpcAccessLogger void log(envoy::data::accesslog::v2::HTTPAccessLogEntry&& entry) override; + void log(envoy::data::accesslog::v2::TCPAccessLogEntry&& entry) override; private: struct LocalStream @@ -106,8 +117,9 @@ class GrpcAccessLoggerCacheImpl : public Singleton::Instance, public GrpcAccessL ThreadLocal::SlotAllocator& tls, const LocalInfo::LocalInfo& local_info); - GrpcAccessLoggerSharedPtr getOrCreateLogger( - const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config) override; + GrpcAccessLoggerSharedPtr + getOrCreateLogger(const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config, + GrpcAccessLoggerType logger_type) override; private: /** @@ -117,8 +129,9 @@ class GrpcAccessLoggerCacheImpl : public Singleton::Instance, public GrpcAccessL ThreadLocalCache(Event::Dispatcher& dispatcher) : dispatcher_(dispatcher) {} Event::Dispatcher& dispatcher_; - // Access loggers indexed by the hash of logger's configuration. - absl::flat_hash_map access_loggers_; + // Access loggers indexed by the hash of logger's configuration and logger type. + absl::flat_hash_map, GrpcAccessLoggerSharedPtr> + access_loggers_; }; Grpc::AsyncClientManager& async_client_manager_; diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.cc b/source/extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.cc index 3b038045ee..6936800be0 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.cc @@ -9,7 +9,7 @@ namespace Envoy { namespace Extensions { namespace AccessLoggers { -namespace HttpGrpc { +namespace GrpcCommon { void validateProtoDescriptors() { const auto method = "envoy.service.accesslog.v2.AccessLogService.StreamAccessLogs"; @@ -17,7 +17,7 @@ void validateProtoDescriptors() { RELEASE_ASSERT(Protobuf::DescriptorPool::generated_pool()->FindMethodByName(method) != nullptr, ""); }; -} // namespace HttpGrpc +} // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions } // namespace Envoy diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.h b/source/extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.h index 62b70a387a..988723cfa2 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.h +++ b/source/extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.h @@ -3,12 +3,12 @@ namespace Envoy { namespace Extensions { namespace AccessLoggers { -namespace HttpGrpc { +namespace GrpcCommon { // This function validates that the method descriptors for gRPC services and type descriptors that // are referenced in Any messages are available in the descriptor pool. void validateProtoDescriptors(); -} // namespace HttpGrpc +} // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions } // namespace Envoy diff --git a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc index 6bab3fd1e2..a8610a68f0 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc @@ -116,6 +116,7 @@ void Utility::responseFlagsToAccessLogResponseFlags( void Utility::extractCommonAccessLogProperties( envoy::data::accesslog::v2::AccessLogCommon& common_access_log, const StreamInfo::StreamInfo& stream_info) { + // TODO(mattklein123): Populate sample_rate field. if (stream_info.downstreamRemoteAddress() != nullptr) { Network::Utility::addressToProtobufAddress( *stream_info.downstreamRemoteAddress(), @@ -128,7 +129,8 @@ void Utility::extractCommonAccessLogProperties( } if (stream_info.downstreamSslConnection() != nullptr) { auto* tls_properties = common_access_log.mutable_tls_properties(); - const auto* downstream_ssl_connection = stream_info.downstreamSslConnection(); + const Ssl::ConnectionInfoConstSharedPtr downstream_ssl_connection = + stream_info.downstreamSslConnection(); tls_properties->set_tls_sni_hostname(stream_info.requestedServerName()); @@ -229,4 +231,4 @@ void Utility::extractCommonAccessLogProperties( } // namespace GrpcCommon } // namespace AccessLoggers } // namespace Extensions -} // namespace Envoy \ No newline at end of file +} // namespace Envoy diff --git a/source/extensions/access_loggers/grpc/http_config.cc b/source/extensions/access_loggers/grpc/http_config.cc index 2e3f19e879..e8f6992600 100644 --- a/source/extensions/access_loggers/grpc/http_config.cc +++ b/source/extensions/access_loggers/grpc/http_config.cc @@ -10,6 +10,7 @@ #include "common/grpc/async_client_impl.h" #include "common/protobuf/protobuf.h" +#include "extensions/access_loggers/grpc/config_utils.h" #include "extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.h" #include "extensions/access_loggers/grpc/http_grpc_access_log_impl.h" #include "extensions/access_loggers/well_known_names.h" @@ -19,31 +20,23 @@ namespace Extensions { namespace AccessLoggers { namespace HttpGrpc { -// Singleton registration via macro defined in envoy/singleton/manager.h -SINGLETON_MANAGER_REGISTRATION(grpc_access_logger_cache); - AccessLog::InstanceSharedPtr HttpGrpcAccessLogFactory::createAccessLogInstance(const Protobuf::Message& config, AccessLog::FilterPtr&& filter, Server::Configuration::FactoryContext& context) { - validateProtoDescriptors(); + GrpcCommon::validateProtoDescriptors(); const auto& proto_config = MessageUtil::downcastAndValidate< - const envoy::config::accesslog::v2::HttpGrpcAccessLogConfig&>(config); - std::shared_ptr grpc_access_logger_cache = - context.singletonManager().getTyped( - SINGLETON_MANAGER_REGISTERED_NAME(grpc_access_logger_cache), [&context] { - return std::make_shared( - context.clusterManager().grpcAsyncClientManager(), context.scope(), - context.threadLocal(), context.localInfo()); - }); - - return std::make_shared(std::move(filter), proto_config, context.threadLocal(), - grpc_access_logger_cache); + const envoy::config::accesslog::v2::HttpGrpcAccessLogConfig&>( + config, context.messageValidationVisitor()); + + return std::make_shared( + std::move(filter), proto_config, context.threadLocal(), + GrpcCommon::getGrpcAccessLoggerCacheSingleton(context)); } ProtobufTypes::MessagePtr HttpGrpcAccessLogFactory::createEmptyConfigProto() { - return ProtobufTypes::MessagePtr{new envoy::config::accesslog::v2::HttpGrpcAccessLogConfig()}; + return std::make_unique(); } std::string HttpGrpcAccessLogFactory::name() const { return AccessLogNames::get().HttpGrpc; } diff --git a/source/extensions/access_loggers/grpc/http_config.h b/source/extensions/access_loggers/grpc/http_config.h index 9e046ac392..c88a3a5ac6 100644 --- a/source/extensions/access_loggers/grpc/http_config.h +++ b/source/extensions/access_loggers/grpc/http_config.h @@ -23,8 +23,6 @@ class HttpGrpcAccessLogFactory : public Server::Configuration::AccessLogInstance std::string name() const override; }; -// TODO(mattklein123): Add TCP access log. - } // namespace HttpGrpc } // namespace AccessLoggers } // namespace Extensions diff --git a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc index 07b91a0a89..e5bad26a3e 100644 --- a/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/http_grpc_access_log_impl.cc @@ -34,8 +34,8 @@ HttpGrpcAccessLog::HttpGrpcAccessLog(AccessLog::FilterPtr&& filter, } tls_slot_->set([this](Event::Dispatcher&) { - return std::make_shared( - access_logger_cache_->getOrCreateLogger(config_.common_config())); + return std::make_shared(access_logger_cache_->getOrCreateLogger( + config_.common_config(), GrpcCommon::GrpcAccessLoggerType::HTTP)); }); } diff --git a/source/extensions/access_loggers/grpc/tcp_config.cc b/source/extensions/access_loggers/grpc/tcp_config.cc new file mode 100644 index 0000000000..b8c053ae0c --- /dev/null +++ b/source/extensions/access_loggers/grpc/tcp_config.cc @@ -0,0 +1,51 @@ +#include "extensions/access_loggers/grpc/tcp_config.h" + +#include "envoy/config/accesslog/v2/als.pb.validate.h" +#include "envoy/config/filter/accesslog/v2/accesslog.pb.validate.h" +#include "envoy/registry/registry.h" +#include "envoy/server/filter_config.h" + +#include "common/common/assert.h" +#include "common/common/macros.h" +#include "common/grpc/async_client_impl.h" +#include "common/protobuf/protobuf.h" + +#include "extensions/access_loggers/grpc/config_utils.h" +#include "extensions/access_loggers/grpc/grpc_access_log_proto_descriptors.h" +#include "extensions/access_loggers/grpc/tcp_grpc_access_log_impl.h" +#include "extensions/access_loggers/well_known_names.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace TcpGrpc { + +AccessLog::InstanceSharedPtr +TcpGrpcAccessLogFactory::createAccessLogInstance(const Protobuf::Message& config, + AccessLog::FilterPtr&& filter, + Server::Configuration::FactoryContext& context) { + GrpcCommon::validateProtoDescriptors(); + + const auto& proto_config = + MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()); + + return std::make_shared(std::move(filter), proto_config, context.threadLocal(), + GrpcCommon::getGrpcAccessLoggerCacheSingleton(context)); +} + +ProtobufTypes::MessagePtr TcpGrpcAccessLogFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +std::string TcpGrpcAccessLogFactory::name() const { return AccessLogNames::get().TcpGrpc; } + +/** + * Static registration for the TCP gRPC access log. @see RegisterFactory. + */ +REGISTER_FACTORY(TcpGrpcAccessLogFactory, Server::Configuration::AccessLogInstanceFactory); + +} // namespace TcpGrpc +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/grpc/tcp_config.h b/source/extensions/access_loggers/grpc/tcp_config.h new file mode 100644 index 0000000000..39bc986146 --- /dev/null +++ b/source/extensions/access_loggers/grpc/tcp_config.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include "envoy/server/access_log_config.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace TcpGrpc { + +/** + * Config registration for the TCP gRPC access log. @see AccessLogInstanceFactory. + */ +class TcpGrpcAccessLogFactory : public Server::Configuration::AccessLogInstanceFactory { +public: + AccessLog::InstanceSharedPtr + createAccessLogInstance(const Protobuf::Message& config, AccessLog::FilterPtr&& filter, + Server::Configuration::FactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + std::string name() const override; +}; + +} // namespace TcpGrpc +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc b/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc new file mode 100644 index 0000000000..c6c8a1bb5a --- /dev/null +++ b/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.cc @@ -0,0 +1,48 @@ +#include "extensions/access_loggers/grpc/tcp_grpc_access_log_impl.h" + +#include "common/common/assert.h" +#include "common/network/utility.h" +#include "common/stream_info/utility.h" + +#include "extensions/access_loggers/grpc/grpc_access_log_utils.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace TcpGrpc { + +TcpGrpcAccessLog::ThreadLocalLogger::ThreadLocalLogger(GrpcCommon::GrpcAccessLoggerSharedPtr logger) + : logger_(std::move(logger)) {} + +TcpGrpcAccessLog::TcpGrpcAccessLog(AccessLog::FilterPtr&& filter, + envoy::config::accesslog::v2::TcpGrpcAccessLogConfig config, + ThreadLocal::SlotAllocator& tls, + GrpcCommon::GrpcAccessLoggerCacheSharedPtr access_logger_cache) + : Common::ImplBase(std::move(filter)), config_(std::move(config)), + tls_slot_(tls.allocateSlot()), access_logger_cache_(std::move(access_logger_cache)) { + tls_slot_->set([this](Event::Dispatcher&) { + return std::make_shared(access_logger_cache_->getOrCreateLogger( + config_.common_config(), GrpcCommon::GrpcAccessLoggerType::TCP)); + }); +} + +void TcpGrpcAccessLog::emitLog(const Http::HeaderMap&, const Http::HeaderMap&, + const Http::HeaderMap&, const StreamInfo::StreamInfo& stream_info) { + // Common log properties. + envoy::data::accesslog::v2::TCPAccessLogEntry log_entry; + GrpcCommon::Utility::extractCommonAccessLogProperties(*log_entry.mutable_common_properties(), + stream_info); + + envoy::data::accesslog::v2::ConnectionProperties& connection_properties = + *log_entry.mutable_connection_properties(); + connection_properties.set_received_bytes(stream_info.bytesReceived()); + connection_properties.set_sent_bytes(stream_info.bytesSent()); + + // request_properties->set_request_body_bytes(stream_info.bytesReceived()); + tls_slot_->getTyped().logger_->log(std::move(log_entry)); +} + +} // namespace TcpGrpc +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.h b/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.h new file mode 100644 index 0000000000..115c8a4677 --- /dev/null +++ b/source/extensions/access_loggers/grpc/tcp_grpc_access_log_impl.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +#include "envoy/config/accesslog/v2/als.pb.h" +#include "envoy/config/filter/accesslog/v2/accesslog.pb.h" +#include "envoy/grpc/async_client.h" +#include "envoy/grpc/async_client_manager.h" +#include "envoy/local_info/local_info.h" +#include "envoy/service/accesslog/v2/als.pb.h" +#include "envoy/singleton/instance.h" +#include "envoy/thread_local/thread_local.h" + +#include "common/grpc/typed_async_client.h" + +#include "extensions/access_loggers/common/access_log_base.h" +#include "extensions/access_loggers/grpc/grpc_access_log_impl.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace TcpGrpc { + +// TODO(mattklein123): Stats + +/** + * Access log Instance that streams TCP logs over gRPC. + */ +class TcpGrpcAccessLog : public Common::ImplBase { +public: + TcpGrpcAccessLog(AccessLog::FilterPtr&& filter, + envoy::config::accesslog::v2::TcpGrpcAccessLogConfig config, + ThreadLocal::SlotAllocator& tls, + GrpcCommon::GrpcAccessLoggerCacheSharedPtr access_logger_cache); + +private: + /** + * Per-thread cached logger. + */ + struct ThreadLocalLogger : public ThreadLocal::ThreadLocalObject { + ThreadLocalLogger(GrpcCommon::GrpcAccessLoggerSharedPtr logger); + + const GrpcCommon::GrpcAccessLoggerSharedPtr logger_; + }; + + // Common::ImplBase + void emitLog(const Http::HeaderMap& request_headers, const Http::HeaderMap& response_headers, + const Http::HeaderMap& response_trailers, + const StreamInfo::StreamInfo& stream_info) override; + + const envoy::config::accesslog::v2::TcpGrpcAccessLogConfig config_; + const ThreadLocal::SlotPtr tls_slot_; + const GrpcCommon::GrpcAccessLoggerCacheSharedPtr access_logger_cache_; +}; + +} // namespace TcpGrpc +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/wasm/config.cc b/source/extensions/access_loggers/wasm/config.cc index db2c6643b2..9d5da8c72f 100644 --- a/source/extensions/access_loggers/wasm/config.cc +++ b/source/extensions/access_loggers/wasm/config.cc @@ -25,7 +25,7 @@ WasmAccessLogFactory::createAccessLogInstance(const Protobuf::Message& proto_con Server::Configuration::FactoryContext& context) { const auto& config = MessageUtil::downcastAndValidate( - proto_config); + proto_config, context.messageValidationVisitor()); auto vm_id = config.vm_id(); auto root_id = config.root_id(); auto configuration = std::make_shared(config.configuration()); @@ -41,7 +41,7 @@ WasmAccessLogFactory::createAccessLogInstance(const Protobuf::Message& proto_con tls_slot->set([base_wasm, root_id, configuration](Event::Dispatcher& dispatcher) { auto result = Common::Wasm::createThreadLocalWasm(*base_wasm, root_id, *configuration, dispatcher); - return std::static_pointer_cast(result); + return ThreadLocal::ThreadLocalObjectSharedPtr{result}; }); } else { if (vm_id.empty()) { diff --git a/source/extensions/access_loggers/well_known_names.h b/source/extensions/access_loggers/well_known_names.h index b4cdcdc39c..d50d50db0c 100644 --- a/source/extensions/access_loggers/well_known_names.h +++ b/source/extensions/access_loggers/well_known_names.h @@ -18,6 +18,8 @@ class AccessLogNameValues { const std::string File = "envoy.file_access_log"; // HTTP gRPC access log const std::string HttpGrpc = "envoy.http_grpc_access_log"; + // TCP gRPC access log + const std::string TcpGrpc = "envoy.tcp_grpc_access_log"; // WASM access log const std::string Wasm = "envoy.wasm_access_log"; }; diff --git a/source/extensions/clusters/redis/redis_cluster.cc b/source/extensions/clusters/redis/redis_cluster.cc index 9e70ceaf7c..4906f5e0b5 100644 --- a/source/extensions/clusters/redis/redis_cluster.cc +++ b/source/extensions/clusters/redis/redis_cluster.cc @@ -34,7 +34,8 @@ RedisCluster::RedisCluster( : Config::Utility::translateClusterHosts(cluster.hosts())), local_info_(factory_context.localInfo()), random_(factory_context.random()), redis_discovery_session_(*this, redis_client_factory), lb_factory_(std::move(lb_factory)), - api_(api) { + auth_password_( + NetworkFilters::RedisProxy::ProtocolOptionsConfigImpl::auth_password(info(), api)) { const auto& locality_lb_endpoints = load_assignment_.endpoints(); for (const auto& locality_lb_endpoint : locality_lb_endpoints) { for (const auto& lb_endpoint : locality_lb_endpoint.lb_endpoints()) { @@ -43,13 +44,6 @@ RedisCluster::RedisCluster( *this, host.socket_address().address(), host.socket_address().port_value())); } } - - auto options = - info()->extensionProtocolOptionsTyped( - NetworkFilters::NetworkFilterNames::get().RedisProxy); - if (options) { - auth_password_datasource_ = options->auth_password_datasource(); - } } void RedisCluster::startPreInit() { @@ -168,13 +162,16 @@ RedisCluster::RedisDiscoverySession::RedisDiscoverySession( NetworkFilters::Common::Redis::Client::ClientFactory& client_factory) : parent_(parent), dispatcher_(parent.dispatcher_), resolve_timer_(parent.dispatcher_.createTimer([this]() -> void { startResolveRedis(); })), - client_factory_(client_factory), buffer_timeout_(0) {} + client_factory_(client_factory), buffer_timeout_(0), + redis_command_stats_( + NetworkFilters::Common::Redis::RedisCommandStats::createRedisCommandStats( + parent_.info()->statsScope().symbolTable())) {} -namespace { // Convert the cluster slot IP/Port response to and address, return null if the response does not // match the expected type. Network::Address::InstanceConstSharedPtr -ProcessCluster(const NetworkFilters::Common::Redis::RespValue& value) { +RedisCluster::RedisDiscoverySession::RedisDiscoverySession::ProcessCluster( + const NetworkFilters::Common::Redis::RespValue& value) { if (value.type() != NetworkFilters::Common::Redis::RespType::Array) { return nullptr; } @@ -185,14 +182,13 @@ ProcessCluster(const NetworkFilters::Common::Redis::RespValue& value) { return nullptr; } - std::string address = array[0].asString(); - bool ipv6 = (address.find(':') != std::string::npos); - if (ipv6) { - return std::make_shared(address, array[1].asInteger()); + try { + return Network::Utility::parseInternetAddress(array[0].asString(), array[1].asInteger(), false); + } catch (const EnvoyException& ex) { + ENVOY_LOG(debug, "Invalid ip address in CLUSTER SLOTS response: {}", ex.what()); + return nullptr; } - return std::make_shared(address, array[1].asInteger()); } -} // namespace RedisCluster::RedisDiscoverySession::~RedisDiscoverySession() { if (current_request_) { @@ -250,16 +246,9 @@ void RedisCluster::RedisDiscoverySession::startResolveRedis() { if (!client) { client = std::make_unique(*this); client->host_ = current_host_address_; - client->client_ = client_factory_.create(host, dispatcher_, *this); + client->client_ = client_factory_.create(host, dispatcher_, *this, redis_command_stats_, + parent_.info()->statsScope(), parent_.auth_password_); client->client_->addConnectionCallbacks(*client); - std::string auth_password = - Envoy::Config::DataSource::read(parent_.auth_password_datasource_, true, parent_.api_); - if (!auth_password.empty()) { - // Send an AUTH command to the upstream server. - client->client_->makeRequest( - Extensions::NetworkFilters::Common::Redis::Utility::makeAuthCommand(auth_password), - null_pool_callbacks); - } } current_request_ = client->client_->makeRequest(ClusterSlotsRequest::instance_, *this); diff --git a/source/extensions/clusters/redis/redis_cluster.h b/source/extensions/clusters/redis/redis_cluster.h index 9117fa7dac..aaee4f97b9 100644 --- a/source/extensions/clusters/redis/redis_cluster.h +++ b/source/extensions/clusters/redis/redis_cluster.h @@ -214,10 +214,13 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { uint32_t maxBufferSizeBeforeFlush() const override { return 0; } std::chrono::milliseconds bufferFlushTimeoutInMs() const override { return buffer_timeout_; } uint32_t maxUpstreamUnknownConnections() const override { return 0; } - // This is effectively not in used for making the "Cluster Slots" calls. - // since we call cluster slots on both the master and slaves, ANY is more appropriate here. + bool enableCommandStats() const override { return false; } + // For any readPolicy other than Master, the RedisClientFactory will send a READONLY command + // when establishing a new connection. Since we're only using this for making the "cluster + // slots" commands, the READONLY command is not relevant in this context. We're setting it to + // Master to avoid the additional READONLY command. Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy readPolicy() const override { - return Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy::Any; + return Extensions::NetworkFilters::Common::Redis::Client::ReadPolicy::Master; } // Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks @@ -227,6 +230,9 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { bool onRedirection(const NetworkFilters::Common::Redis::RespValue&) override { return true; } void onUnexpectedResponse(const NetworkFilters::Common::Redis::RespValuePtr&); + Network::Address::InstanceConstSharedPtr + ProcessCluster(const NetworkFilters::Common::Redis::RespValue& value); + RedisCluster& parent_; Event::Dispatcher& dispatcher_; std::string current_host_address_; @@ -238,6 +244,7 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { Event::TimerPtr resolve_timer_; NetworkFilters::Common::Redis::Client::ClientFactory& client_factory_; const std::chrono::milliseconds buffer_timeout_; + NetworkFilters::Common::Redis::RedisCommandStatsSharedPtr redis_command_stats_; }; Upstream::ClusterManager& cluster_manager_; @@ -256,8 +263,7 @@ class RedisCluster : public Upstream::BaseDynamicClusterImpl { Upstream::HostVector hosts_; Upstream::HostMap all_hosts_; - envoy::api::v2::core::DataSource auth_password_datasource_; - Api::Api& api_; + const std::string auth_password_; }; class RedisClusterFactory : public Upstream::ConfigurableClusterFactoryBase< diff --git a/source/extensions/clusters/redis/redis_cluster_lb.cc b/source/extensions/clusters/redis/redis_cluster_lb.cc index fefd17fff2..b4f3b1d06a 100644 --- a/source/extensions/clusters/redis/redis_cluster_lb.cc +++ b/source/extensions/clusters/redis/redis_cluster_lb.cc @@ -75,6 +75,11 @@ void RedisClusterLoadBalancerFactory::onHostHealthUpdate() { current_shard_vector = shard_vector_; } + // This can get called by cluster initialization before the Redis Cluster topology is resolved. + if (!current_shard_vector) { + return; + } + auto shard_vector = std::make_shared>(); for (auto const& shard : *current_shard_vector) { diff --git a/source/extensions/common/tap/tap_matcher.cc b/source/extensions/common/tap/tap_matcher.cc index 1b0d53bfa6..66511e2f88 100644 --- a/source/extensions/common/tap/tap_matcher.cc +++ b/source/extensions/common/tap/tap_matcher.cc @@ -111,11 +111,8 @@ void NotMatcher::updateLocalStatus(MatchStatusVector& statuses, HttpHeaderMatcherBase::HttpHeaderMatcherBase( const envoy::service::tap::v2alpha::HttpHeadersMatch& config, const std::vector& matchers) - : SimpleMatcher(matchers) { - for (const auto& header_match : config.headers()) { - headers_to_match_.emplace_back(header_match); - } -} + : SimpleMatcher(matchers), + headers_to_match_(Http::HeaderUtility::buildHeaderDataVector(config.headers())) {} void HttpHeaderMatcherBase::matchHeaders(const Http::HeaderMap& headers, MatchStatusVector& statuses) const { diff --git a/source/extensions/common/tap/tap_matcher.h b/source/extensions/common/tap/tap_matcher.h index b80ff24256..5f8e89c38b 100644 --- a/source/extensions/common/tap/tap_matcher.h +++ b/source/extensions/common/tap/tap_matcher.h @@ -239,7 +239,7 @@ class HttpHeaderMatcherBase : public SimpleMatcher { protected: void matchHeaders(const Http::HeaderMap& headers, MatchStatusVector& statuses) const; - std::vector headers_to_match_; + const std::vector headers_to_match_; }; /** diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 5208ed210c..597baba388 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -24,6 +24,7 @@ envoy_cc_library( external_deps = ["abseil_optional"], deps = [ ":well_known_names", + "//source/common/common:minimal_logger_lib", ], ) diff --git a/source/extensions/common/wasm/null/BUILD b/source/extensions/common/wasm/null/BUILD index 44fba5e547..1574468215 100644 --- a/source/extensions/common/wasm/null/BUILD +++ b/source/extensions/common/wasm/null/BUILD @@ -10,8 +10,10 @@ envoy_package() envoy_cc_library( name = "null_vm_plugin_interface", - hdrs = [ - "null_vm_plugin.h", + hdrs = ["null_vm_plugin.h"], + deps = [ + "//source/extensions/common/wasm:wasm_vm_interface", + "//source/extensions/common/wasm:well_known_names", ], ) diff --git a/source/extensions/common/wasm/null/null.cc b/source/extensions/common/wasm/null/null.cc index 06b439e85a..185dde6078 100644 --- a/source/extensions/common/wasm/null/null.cc +++ b/source/extensions/common/wasm/null/null.cc @@ -18,7 +18,7 @@ namespace Common { namespace Wasm { namespace Null { -std::unique_ptr createVm() { return std::make_unique(); } +WasmVmPtr createVm() { return std::make_unique(); } } // namespace Null } // namespace Wasm diff --git a/source/extensions/common/wasm/null/null.h b/source/extensions/common/wasm/null/null.h index 1fe6978908..7d88fb3569 100644 --- a/source/extensions/common/wasm/null/null.h +++ b/source/extensions/common/wasm/null/null.h @@ -11,7 +11,7 @@ namespace Common { namespace Wasm { namespace Null { -std::unique_ptr createVm(); +WasmVmPtr createVm(); } // namespace Null } // namespace Wasm diff --git a/source/extensions/common/wasm/null/null_plugin.cc b/source/extensions/common/wasm/null/null_plugin.cc index 8d836c2115..c0173faa0e 100644 --- a/source/extensions/common/wasm/null/null_plugin.cc +++ b/source/extensions/common/wasm/null/null_plugin.cc @@ -29,202 +29,203 @@ namespace Null { using Plugin::Context; using Plugin::WasmData; -void NullPlugin::getFunction(absl::string_view function_name, WasmCall0Void* /* f */) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallVoid<0>* /* f */) { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); } -void NullPlugin::getFunction(absl::string_view function_name, WasmCall0Word* f) { - if (function_name == "___errno_location") { - *f = [](Common::Wasm::Context*) -> Word { return Word(reinterpret_cast(&errno)); }; - } else { - throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); - } -} - -void NullPlugin::getFunction(absl::string_view function_name, WasmCall1Void* f) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallVoid<1>* f) { if (function_name == "_free") { - *f = [](Common::Wasm::Context*, Word ptr) { return ::free(reinterpret_cast(ptr.u64)); }; + *f = [](Common::Wasm::Context*, Word ptr) { return ::free(reinterpret_cast(ptr.u64_)); }; } else if (function_name == "_proxy_onTick") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) { SaveRestoreContext saved_context(context); - plugin->onTick(context_id.u64); + plugin->onTick(context_id.u64_); }; } else if (function_name == "_proxy_onDone") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) { SaveRestoreContext saved_context(context); - plugin->onDone(context_id.u64); + plugin->onDone(context_id.u64_); }; } else if (function_name == "_proxy_onLog") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) { SaveRestoreContext saved_context(context); - plugin->onLog(context_id.u64); + plugin->onLog(context_id.u64_); }; } else if (function_name == "_proxy_onDelete") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) { SaveRestoreContext saved_context(context); - plugin->onDelete(context_id.u64); + plugin->onDelete(context_id.u64_); }; } else { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); } } -void NullPlugin::getFunction(absl::string_view function_name, WasmCall2Void* f) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallVoid<2>* f) { if (function_name == "_proxy_onCreate") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word root_context_id) { SaveRestoreContext saved_context(context); - plugin->onCreate(context_id.u64, root_context_id.u64); + plugin->onCreate(context_id.u64_, root_context_id.u64_); }; } else if (function_name == "_proxy_onGrpcCreateInitialMetadata") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word token) { SaveRestoreContext saved_context(context); - plugin->onGrpcCreateInitialMetadata(context_id.u64, token.u64); + plugin->onGrpcCreateInitialMetadata(context_id.u64_, token.u64_); }; } else if (function_name == "_proxy_onGrpcReceiveInitialMetadata") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word token) { SaveRestoreContext saved_context(context); - plugin->onGrpcReceiveInitialMetadata(context_id.u64, token.u64); + plugin->onGrpcReceiveInitialMetadata(context_id.u64_, token.u64_); }; } else if (function_name == "_proxy_onGrpcReceiveTrailingMetadata") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word token) { SaveRestoreContext saved_context(context); - plugin->onGrpcReceiveTrailingMetadata(context_id.u64, token.u64); + plugin->onGrpcReceiveTrailingMetadata(context_id.u64_, token.u64_); }; } else if (function_name == "_proxy_onQueueReady") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word token) { SaveRestoreContext saved_context(context); - plugin->onQueueReady(context_id.u64, token.u64); + plugin->onQueueReady(context_id.u64_, token.u64_); }; } else { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); } } -void NullPlugin::getFunction(absl::string_view function_name, WasmCall3Void* f) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallVoid<3>* f) { if (function_name == "_proxy_onConfigure") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word ptr, Word size) { SaveRestoreContext saved_context(context); - plugin->onConfigure(context_id.u64, ptr.u64, size.u64); + plugin->onConfigure(context_id.u64_, ptr.u64_, size.u64_); }; } else { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); } } -void NullPlugin::getFunction(absl::string_view function_name, WasmCall4Void* f) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallVoid<4>* f) { if (function_name == "_proxy_onGrpcReceive") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word token, Word response_ptr, Word response_size) { SaveRestoreContext saved_context(context); - plugin->onGrpcReceive(context_id.u64, token.u64, response_ptr.u64, response_size.u64); + plugin->onGrpcReceive(context_id.u64_, token.u64_, response_ptr.u64_, response_size.u64_); }; } else { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); } } -void NullPlugin::getFunction(absl::string_view function_name, WasmCall5Void* f) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallVoid<5>* f) { if (function_name == "_proxy_onGrpcClose") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word token, Word status_code, Word status_message_ptr, Word status_message_size) { SaveRestoreContext saved_context(context); - plugin->onGrpcClose(context_id.u64, token.u64, status_code.u64, status_message_ptr.u64, - status_message_size.u64); + plugin->onGrpcClose(context_id.u64_, token.u64_, status_code.u64_, status_message_ptr.u64_, + status_message_size.u64_); }; } else if (function_name == "_proxy_onStart") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word root_id_ptr, Word root_id_size, Word vm_configuration_ptr, Word vm_configuration_size) { SaveRestoreContext saved_context(context); - plugin->onStart(context_id.u64, root_id_ptr.u64, root_id_size.u64, vm_configuration_ptr.u64, - vm_configuration_size.u64); + plugin->onStart(context_id.u64_, root_id_ptr.u64_, root_id_size.u64_, + vm_configuration_ptr.u64_, vm_configuration_size.u64_); }; } else { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); } } -void NullPlugin::getFunction(absl::string_view function_name, WasmCall8Void* f) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallVoid<8>* f) { if (function_name == "_proxy_onHttpCallResponse") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word token, Word header_pairs_ptr, Word header_pairs_size, Word body_ptr, Word body_size, Word trailer_pairs_ptr, Word trailer_pairs_size) { SaveRestoreContext saved_context(context); - plugin->onHttpCallResponse(context_id.u64, token.u64, header_pairs_ptr.u64, - header_pairs_size.u64, body_ptr.u64, body_size.u64, - trailer_pairs_ptr.u64, trailer_pairs_size.u64); + plugin->onHttpCallResponse(context_id.u64_, token.u64_, header_pairs_ptr.u64_, + header_pairs_size.u64_, body_ptr.u64_, body_size.u64_, + trailer_pairs_ptr.u64_, trailer_pairs_size.u64_); }; } else { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); } } -void NullPlugin::getFunction(absl::string_view function_name, WasmCall1Word* f) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallWord<0>* f) { + if (function_name == "___errno_location") { + *f = [](Common::Wasm::Context*) -> Word { return Word(reinterpret_cast(&errno)); }; + } else { + throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); + } +} + +void NullPlugin::getFunction(absl::string_view function_name, WasmCallWord<1>* f) { if (function_name == "_malloc") { *f = [](Common::Wasm::Context*, Word size) -> Word { - return Word(reinterpret_cast(::malloc(size.u64))); + return Word(reinterpret_cast(::malloc(size.u64_))); }; } else if (function_name == "_proxy_onRequestHeaders") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) -> Word { SaveRestoreContext saved_context(context); - return Word(plugin->onRequestHeaders(context_id.u64)); + return Word(plugin->onRequestHeaders(context_id.u64_)); }; } else if (function_name == "_proxy_onRequestTrailers") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) -> Word { SaveRestoreContext saved_context(context); - return Word(plugin->onRequestTrailers(context_id.u64)); + return Word(plugin->onRequestTrailers(context_id.u64_)); }; } else if (function_name == "_proxy_onRequestMetadata") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) -> Word { SaveRestoreContext saved_context(context); - return Word(plugin->onRequestMetadata(context_id.u64)); + return Word(plugin->onRequestMetadata(context_id.u64_)); }; } else if (function_name == "_proxy_onResponseHeaders") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) -> Word { SaveRestoreContext saved_context(context); - return Word(plugin->onResponseHeaders(context_id.u64)); + return Word(plugin->onResponseHeaders(context_id.u64_)); }; } else if (function_name == "_proxy_onResponseTrailers") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) -> Word { SaveRestoreContext saved_context(context); - return Word(plugin->onResponseTrailers(context_id.u64)); + return Word(plugin->onResponseTrailers(context_id.u64_)); }; } else if (function_name == "_proxy_onResponseMetadata") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id) -> Word { SaveRestoreContext saved_context(context); - return Word(plugin->onResponseMetadata(context_id.u64)); + return Word(plugin->onResponseMetadata(context_id.u64_)); }; } else { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); } } -void NullPlugin::getFunction(absl::string_view function_name, WasmCall3Word* f) { +void NullPlugin::getFunction(absl::string_view function_name, WasmCallWord<3>* f) { if (function_name == "_proxy_onRequestBody") { auto plugin = this; *f = [plugin](Common::Wasm::Context* context, Word context_id, Word body_buffer_length, Word end_of_stream) -> Word { SaveRestoreContext saved_context(context); - return Word(plugin->onRequestBody(context_id.u64, body_buffer_length.u64, end_of_stream.u64)); + return Word( + plugin->onRequestBody(context_id.u64_, body_buffer_length.u64_, end_of_stream.u64_)); }; } else if (function_name == "_proxy_onResponseBody") { auto plugin = this; @@ -232,7 +233,7 @@ void NullPlugin::getFunction(absl::string_view function_name, WasmCall3Word* f) Word end_of_stream) -> Word { SaveRestoreContext saved_context(context); return Word( - plugin->onResponseBody(context_id.u64, body_buffer_length.u64, end_of_stream.u64)); + plugin->onResponseBody(context_id.u64_, body_buffer_length.u64_, end_of_stream.u64_)); }; } else { throw WasmVmException(fmt::format("Missing getFunction for: {}", function_name)); diff --git a/source/extensions/common/wasm/null/null_vm.cc b/source/extensions/common/wasm/null/null_vm.cc index 3ad1abaa59..b9957c6a67 100644 --- a/source/extensions/common/wasm/null/null_vm.cc +++ b/source/extensions/common/wasm/null/null_vm.cc @@ -17,10 +17,15 @@ namespace Common { namespace Wasm { namespace Null { -std::unique_ptr NullVm::clone() { return std::make_unique(*this); } +WasmVmPtr NullVm::clone() { + auto cloned_null_vm = std::make_unique(*this); + cloned_null_vm->load(plugin_name_, false /* unused */); + return cloned_null_vm; +} +// "Load" the plugin by obtaining a pointer to it from the factory. bool NullVm::load(const std::string& name, bool /* allow_precompiled */) { - auto factory = Registry::FactoryRegistry::getFactory(name); + auto factory = Registry::FactoryRegistry::getFactory(name); if (!factory) { return false; } @@ -32,7 +37,7 @@ bool NullVm::load(const std::string& name, bool /* allow_precompiled */) { void NullVm::link(absl::string_view /* name */, bool /* needs_emscripten */) {} void NullVm::makeModule(absl::string_view /* name */) { - // NullVm does not advertize code as emscripten so this will not get called. + // NullVm does not advertise code as emscripten so this will not get called. NOT_REACHED_GCOVR_EXCL_LINE; } @@ -43,6 +48,7 @@ void NullVm::start(Common::Wasm::Context* context) { uint64_t NullVm::getMemorySize() { return std::numeric_limits::max(); } +// NulVm pointers are just native pointers. absl::optional NullVm::getMemory(uint64_t pointer, uint64_t size) { if (pointer == 0 && size != 0) { return absl::nullopt; @@ -56,8 +62,12 @@ bool NullVm::getMemoryOffset(void* host_pointer, uint64_t* vm_pointer) { } bool NullVm::setMemory(uint64_t pointer, uint64_t size, const void* data) { - if ((pointer == 0 || data == nullptr) && size != 0) { - return false; + if ((pointer == 0 || data == nullptr)) { + if (size != 0) { + return false; + } else { + return true; + } } auto p = reinterpret_cast(pointer); memcpy(p, data, size); @@ -69,7 +79,16 @@ bool NullVm::setWord(uint64_t pointer, Word data) { return false; } auto p = reinterpret_cast(pointer); - memcpy(p, &data.u64, sizeof(data.u64)); + memcpy(p, &data.u64_, sizeof(data.u64_)); + return true; +} + +bool NullVm::getWord(uint64_t pointer, Word* data) { + if (pointer == 0) { + return false; + } + auto p = reinterpret_cast(pointer); + memcpy(&data->u64_, p, sizeof(data->u64_)); return true; } diff --git a/source/extensions/common/wasm/null/null_vm.h b/source/extensions/common/wasm/null/null_vm.h index 9927988e6d..f3b90fabf1 100644 --- a/source/extensions/common/wasm/null/null_vm.h +++ b/source/extensions/common/wasm/null/null_vm.h @@ -17,15 +17,17 @@ namespace Common { namespace Wasm { namespace Null { +// The NullVm wraps a C++ WASM plugin which has been compiled with the WASM API +// and linked directly into the Envoy process. This is useful for development +// in that it permits the debugger to set breakpoints in both Envoy and the plugin. struct NullVm : public WasmVm { NullVm() = default; - NullVm(const NullVm& other) { load(other.plugin_name_, false /* unused */); } - ~NullVm() override{}; + NullVm(const NullVm& other) : plugin_name_(other.plugin_name_) {} // WasmVm absl::string_view vm() override { return WasmVmNames::get().Null; } - bool clonable() override { return true; }; - std::unique_ptr clone() override; + bool cloneable() override { return true; }; + WasmVmPtr clone() override; bool load(const std::string& code, bool allow_precompiled) override; void link(absl::string_view debug_name, bool needs_emscripten) override; void setMemoryLayout(uint64_t, uint64_t, uint64_t) override {} @@ -35,6 +37,7 @@ struct NullVm : public WasmVm { bool getMemoryOffset(void* host_pointer, uint64_t* vm_pointer) override; bool setMemory(uint64_t pointer, uint64_t size, const void* data) override; bool setWord(uint64_t pointer, Word data) override; + bool getWord(uint64_t pointer, Word* data) override; void makeModule(absl::string_view name) override; absl::string_view getUserSection(absl::string_view name) override; @@ -45,14 +48,14 @@ struct NullVm : public WasmVm { FOR_ALL_WASM_VM_EXPORTS(_FORWARD_GET_FUNCTION) #undef _FORWARD_GET_FUNCTION - // These are noops for NullVm. + // These are not needed for NullVm which invokes the handlers directly. #define _REGISTER_CALLBACK(_T) \ void registerCallback(absl::string_view, absl::string_view, _T, \ typename ConvertFunctionTypeWordToUint32<_T>::type) override{}; FOR_ALL_WASM_VM_IMPORTS(_REGISTER_CALLBACK) #undef _REGISTER_CALLBACK - // NullVm does not advertize code as emscripten so this will not get called. + // NullVm does not advertise code as emscripten so this will not get called. std::unique_ptr> makeGlobal(absl::string_view, absl::string_view, double) override { NOT_REACHED_GCOVR_EXCL_LINE; diff --git a/source/extensions/common/wasm/null/null_vm_plugin.h b/source/extensions/common/wasm/null/null_vm_plugin.h index db2cde285a..4dce2c6172 100644 --- a/source/extensions/common/wasm/null/null_vm_plugin.h +++ b/source/extensions/common/wasm/null/null_vm_plugin.h @@ -8,14 +8,18 @@ namespace Common { namespace Wasm { namespace Null { +// A wrapper for the natively compiled NullVm plugin which implements the WASM ABI. class NullVmPlugin { public: - NullVmPlugin() {} - virtual ~NullVmPlugin() {} + NullVmPlugin() = default; + virtual ~NullVmPlugin() = default; -#define _DECLARE_PURE(_t) virtual void getFunction(absl::string_view function_name, _t* f) PURE; - FOR_ALL_WASM_VM_EXPORTS(_DECLARE_PURE) -#undef _DECLARE_PURE + // NB: These are defined rather than declared PURE because gmock uses __LINE__ internally for + // uniqueness, making it impossible to use FOR_ALL_WASM_VM_EXPORTS with MOCK_METHOD2. +#define _DEFINE_GET_FUNCTION(_T) \ + virtual void getFunction(absl::string_view, _T* f) { *f = nullptr; } + FOR_ALL_WASM_VM_EXPORTS(_DEFINE_GET_FUNCTION) +#undef _DEFIN_GET_FUNCTIONE virtual void start() PURE; }; @@ -24,9 +28,9 @@ class NullVmPlugin { * Pseudo-WASM plugins using the NullVM should implement this factory and register via * Registry::registerFactory or the convenience class RegisterFactory. */ -class NullPluginFactory { +class NullVmPluginFactory { public: - virtual ~NullPluginFactory() {} + virtual ~NullVmPluginFactory() = default; /** * Name of the plugin. diff --git a/source/extensions/common/wasm/null/sample_plugin/plugin_wrapper.cc b/source/extensions/common/wasm/null/sample_plugin/plugin_wrapper.cc index 247b1bfa53..9f7b94fa76 100644 --- a/source/extensions/common/wasm/null/sample_plugin/plugin_wrapper.cc +++ b/source/extensions/common/wasm/null/sample_plugin/plugin_wrapper.cc @@ -13,7 +13,7 @@ NullPluginRootRegistry* context_registry_{}; /** * Config registration for a Wasm filter plugin. @see NamedHttpFilterConfigFactory. */ -class PluginFactory : public NullPluginFactory { +class PluginFactory : public NullVmPluginFactory { public: PluginFactory() {} @@ -27,7 +27,7 @@ class PluginFactory : public NullPluginFactory { /** * Static registration for the null Wasm filter. @see RegisterFactory. */ -static Registry::RegisterFactory register_; +static Registry::RegisterFactory register_; } // namespace Plugin } // namespace Null diff --git a/source/extensions/common/wasm/null/wasm_api_impl.h b/source/extensions/common/wasm/null/wasm_api_impl.h index f7061b1228..8b1b22e72d 100644 --- a/source/extensions/common/wasm/null/wasm_api_impl.h +++ b/source/extensions/common/wasm/null/wasm_api_impl.h @@ -19,7 +19,7 @@ namespace Plugin { #define WS(_x) Word(static_cast(_x)) #define WR(_x) Word(reinterpret_cast(_x)) -inline WasmResult wordToWasmResult(Word w) { return static_cast(w.u64); } +inline WasmResult wordToWasmResult(Word w) { return static_cast(w.u64_); } // Logging inline WasmResult proxy_log(LogLevel level, const char* logMessage, size_t messageSize) { @@ -181,7 +181,8 @@ inline uint64_t proxy_httpCall(const char* uri_ptr, size_t uri_size, void* heade uint64_t timeout_milliseconds) { return httpCallHandler(current_context_, WR(uri_ptr), WS(uri_size), WR(header_pairs_ptr), WS(header_pairs_size), WR(body_ptr), WS(body_size), WR(trailer_pairs_ptr), - WS(trailer_pairs_size), WS(timeout_milliseconds)); + WS(trailer_pairs_size), WS(timeout_milliseconds)) + .u64_; } // gRPC // Returns token, used in gRPC callbacks (onGrpc...) @@ -192,14 +193,16 @@ inline uint64_t proxy_grpcCall(const char* service_ptr, size_t service_size, uint64_t timeout_milliseconds) { return grpcCallHandler(current_context_, WR(service_ptr), WS(service_size), WR(service_name_ptr), WS(service_name_size), WR(method_name_ptr), WS(method_name_size), - WR(request_ptr), WS(request_size), WS(timeout_milliseconds)); + WR(request_ptr), WS(request_size), WS(timeout_milliseconds)) + .u64_; } inline uint64_t proxy_grpcStream(const char* service_ptr, size_t service_size, const char* service_name_ptr, size_t service_name_size, const char* method_name_ptr, size_t method_name_size) { return grpcStreamHandler(current_context_, WR(service_ptr), WS(service_size), WR(service_name_ptr), WS(service_name_size), WR(method_name_ptr), - WS(method_name_size)); + WS(method_name_size)) + .u64_; } inline WasmResult proxy_grpcCancel(uint64_t token) { return wordToWasmResult(grpcCancelHandler(current_context_, WS(token))); @@ -221,10 +224,10 @@ inline WasmResult proxy_defineMetric(MetricType type, const char* name_ptr, size defineMetricHandler(current_context_, WS(type), WR(name_ptr), WS(name_size), WR(metric_id))); } inline WasmResult proxy_incrementMetric(uint32_t metric_id, int64_t offset) { - return wordToWasmResult(incrementMetricHandler(current_context_, WS(metric_id), WS(offset))); + return wordToWasmResult(incrementMetricHandler(current_context_, WS(metric_id), offset)); } inline WasmResult proxy_recordMetric(uint32_t metric_id, uint64_t value) { - return wordToWasmResult(recordMetricHandler(current_context_, WS(metric_id), WS(value))); + return wordToWasmResult(recordMetricHandler(current_context_, WS(metric_id), value)); } inline WasmResult proxy_getMetric(uint32_t metric_id, uint64_t* value) { return wordToWasmResult(getMetricHandler(current_context_, WS(metric_id), WR(value))); diff --git a/source/extensions/common/wasm/v8/v8.cc b/source/extensions/common/wasm/v8/v8.cc index d51a69da4e..2e6f5a047d 100644 --- a/source/extensions/common/wasm/v8/v8.cc +++ b/source/extensions/common/wasm/v8/v8.cc @@ -50,7 +50,7 @@ class V8 : public WasmVm { void makeModule(absl::string_view) override {} // v8 is currently not clonable. - bool clonable() override { return false; } + bool cloneable() override { return false; } std::unique_ptr clone() override { return nullptr; } void start(Context* context) override; @@ -59,6 +59,7 @@ class V8 : public WasmVm { absl::optional getMemory(uint64_t pointer, uint64_t size) override; bool getMemoryOffset(void* host_pointer, uint64_t* vm_pointer) override; bool setMemory(uint64_t pointer, uint64_t size, const void* data) override; + bool getWord(uint64_t pointer, Word* word) override; bool setWord(uint64_t pointer, Word word) override; #define _REGISTER_HOST_GLOBAL(_T) \ @@ -200,7 +201,7 @@ template struct ConvertWordType { using type = T; }; template <> struct ConvertWordType { using type = uint32_t; }; template wasm::Val makeVal(T t) { return wasm::Val::make(t); } -template <> wasm::Val makeVal(Word t) { return wasm::Val::make(static_cast(t.u64)); } +template <> wasm::Val makeVal(Word t) { return wasm::Val::make(static_cast(t.u64_)); } template constexpr auto convertArgToValKind(); template <> constexpr auto convertArgToValKind() { return wasm::I32; }; @@ -524,8 +525,20 @@ bool V8::setMemory(uint64_t pointer, uint64_t size, const void* data) { return true; } +bool V8::getWord(uint64_t pointer, Word* word) { + ENVOY_LOG(trace, "[wasm] getWord({})", pointer); + auto size = sizeof(uint32_t); + if (pointer + size > memory_->data_size()) { + return false; + } + uint32_t word32; + ::memcpy(&word32, memory_->data() + pointer, size); + word->u64_ = word32; + return true; +} + bool V8::setWord(uint64_t pointer, Word word) { - ENVOY_LOG(trace, "[wasm] setWord({}, {})", pointer, word.u64); + ENVOY_LOG(trace, "[wasm] setWord({}, {})", pointer, word.u64_); auto size = sizeof(uint32_t); if (pointer + size > memory_->data_size()) { return false; @@ -653,7 +666,7 @@ void V8::getModuleFunctionImpl(absl::string_view function_name, }; } -std::unique_ptr createVm() { return std::make_unique(); } +WasmVmPtr createVm() { return std::make_unique(); } } // namespace V8 } // namespace Wasm diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index 7bbf5094a6..288c57f621 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -52,7 +52,7 @@ namespace { inline Word wasmResultToWord(WasmResult r) { return Word(static_cast(r)); } -inline uint32_t convertWordToUint32(Word w) { return static_cast(w.u64); } +inline uint32_t convertWordToUint32(Word w) { return static_cast(w.u64_); } // Convert a function of the form Word(Word...) to one of the form uint32_t(uint32_t...). template struct ConvertFunctionWordToUint32 { @@ -385,20 +385,20 @@ uint32_t resolveQueueForTest(absl::string_view vm_id, absl::string_view queue_na // Metadata Word getMetadataHandler(void* raw_context, Word type, Word key_ptr, Word key_size, Word value_ptr_ptr, Word value_size_ptr) { - if (type > static_cast(MetadataType::MAX)) { + if (type.u64_ > static_cast(MetadataType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); std::string value; - auto key = context->wasmVm()->getMemory(key_ptr, key_size); + auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_); if (!key) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - auto result = context->getMetadata(static_cast(type.u64), key.value(), &value); + auto result = context->getMetadata(static_cast(type.u64_), key.value(), &value); if (result != WasmResult::Ok) { return wasmResultToWord(result); } - if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr, value_size_ptr)) { + if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr.u64_, value_size_ptr.u64_)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(result); @@ -407,8 +407,8 @@ Word getMetadataHandler(void* raw_context, Word type, Word key_ptr, Word key_siz Word setStateHandler(void* raw_context, Word key_ptr, Word key_size, Word value_ptr, Word value_size) { auto context = WASM_CONTEXT(raw_context); - auto key = context->wasmVm()->getMemory(key_ptr, key_size); - auto value = context->wasmVm()->getMemory(value_ptr, value_size); + auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_); + auto value = context->wasmVm()->getMemory(value_ptr.u64_, value_size.u64_); if (!key || !value) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } @@ -416,13 +416,13 @@ Word setStateHandler(void* raw_context, Word key_ptr, Word key_size, Word value_ } Word getMetadataPairsHandler(void* raw_context, Word type, Word ptr_ptr, Word size_ptr) { - if (type > static_cast(MetadataType::MAX)) { + if (type.u64_ > static_cast(MetadataType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); PairsWithStringValues pairs; - auto result = context->getMetadataPairs(static_cast(type.u64), &pairs); - if (!getPairs(context, pairs, ptr_ptr, size_ptr)) { + auto result = context->getMetadataPairs(static_cast(type.u64_), &pairs); + if (!getPairs(context, pairs, ptr_ptr.u64_, size_ptr.u64_)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(result); @@ -430,21 +430,21 @@ Word getMetadataPairsHandler(void* raw_context, Word type, Word ptr_ptr, Word si Word getMetadataStructHandler(void* raw_context, Word type, Word name_ptr, Word name_size, Word value_ptr_ptr, Word value_size_ptr) { - if (type > static_cast(MetadataType::MAX)) { + if (type.u64_ > static_cast(MetadataType::MAX)) { return Word(static_cast(WasmResult::BadArgument)); } auto context = WASM_CONTEXT(raw_context); std::string value; - auto name = context->wasmVm()->getMemory(name_ptr, name_size); + auto name = context->wasmVm()->getMemory(name_ptr.u64_, name_size.u64_); if (!name) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } auto result = - context->getMetadataStruct(static_cast(type.u64), name.value(), &value); + context->getMetadataStruct(static_cast(type.u64_), name.value(), &value); if (result != WasmResult::Ok) { return wasmResultToWord(result); } - if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr, value_size_ptr)) { + if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr.u64_, value_size_ptr.u64_)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -454,7 +454,7 @@ Word getMetadataStructHandler(void* raw_context, Word type, Word name_ptr, Word Word getSelectorExpressionHandler(void* raw_context, Word path_ptr, Word path_size, Word value_ptr_ptr, Word value_size_ptr) { auto context = WASM_CONTEXT(raw_context); - auto path = context->wasmVm()->getMemory(path_ptr, path_size); + auto path = context->wasmVm()->getMemory(path_ptr.u64_, path_size.u64_); if (!path.has_value()) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } @@ -463,7 +463,7 @@ Word getSelectorExpressionHandler(void* raw_context, Word path_ptr, Word path_si if (result != WasmResult::Ok) { return wasmResultToWord(result); } - if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr, value_size_ptr)) { + if (!context->wasm()->copyToPointerSize(value, value_ptr_ptr.u64_, value_size_ptr.u64_)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -488,10 +488,10 @@ Word sendLocalResponseHandler(void* raw_context, Word response_code, Word respon Word additional_response_header_pairs_size, Word grpc_code) { auto context = WASM_CONTEXT(raw_context); auto details = - context->wasmVm()->getMemory(response_code_details_ptr, response_code_details_size); - auto body = context->wasmVm()->getMemory(body_ptr, body_size); + context->wasmVm()->getMemory(response_code_details_ptr.u64_, response_code_details_size.u64_); + auto body = context->wasmVm()->getMemory(body_ptr.u64_, body_size.u64_); auto additional_response_header_pairs = context->wasmVm()->getMemory( - additional_response_header_pairs_ptr, additional_response_header_pairs_size); + additional_response_header_pairs_ptr.u64_, additional_response_header_pairs_size.u64_); if (!details || !body || !additional_response_header_pairs) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } @@ -502,18 +502,18 @@ Word sendLocalResponseHandler(void* raw_context, Word response_code, Word respon headers.addCopy(lower_key, std::string(p.second)); } }; - auto grpc_status = static_cast(grpc_code.u64); + auto grpc_status = static_cast(grpc_code.u64_); auto grpc_status_opt = (grpc_status != Grpc::Status::GrpcStatus::InvalidCode) ? absl::optional(grpc_status) : absl::optional(); - context->sendLocalResponse(static_cast(response_code.u64), body.value(), + context->sendLocalResponse(static_cast(response_code.u64_), body.value(), modify_headers, grpc_status_opt, details.value()); return wasmResultToWord(WasmResult::Ok); } Word setEffectiveContextHandler(void* raw_context, Word context_id) { auto context = WASM_CONTEXT(raw_context); - uint32_t cid = static_cast(context_id.u64); + uint32_t cid = static_cast(context_id.u64_); auto c = context->wasm()->getContext(cid); if (!c) { return wasmResultToWord(WasmResult::BadArgument); @@ -532,7 +532,7 @@ Word clearRouteCacheHandler(void* raw_context) { Word getSharedDataHandler(void* raw_context, Word key_ptr, Word key_size, Word value_ptr_ptr, Word value_size_ptr, Word cas_ptr) { auto context = WASM_CONTEXT(raw_context); - auto key = context->wasmVm()->getMemory(key_ptr, key_size); + auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_); if (!key) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } @@ -541,10 +541,10 @@ Word getSharedDataHandler(void* raw_context, Word key_ptr, Word key_size, Word v if (result != WasmResult::Ok) { return wasmResultToWord(result); } - if (!context->wasm()->copyToPointerSize(data.first, value_ptr_ptr, value_size_ptr)) { + if (!context->wasm()->copyToPointerSize(data.first, value_ptr_ptr.u64_, value_size_ptr.u64_)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - if (!context->wasmVm()->setMemory(cas_ptr, sizeof(uint32_t), &data.second)) { + if (!context->wasmVm()->setMemory(cas_ptr.u64_, sizeof(uint32_t), &data.second)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -553,23 +553,23 @@ Word getSharedDataHandler(void* raw_context, Word key_ptr, Word key_size, Word v Word setSharedDataHandler(void* raw_context, Word key_ptr, Word key_size, Word value_ptr, Word value_size, Word cas) { auto context = WASM_CONTEXT(raw_context); - auto key = context->wasmVm()->getMemory(key_ptr, key_size); - auto value = context->wasmVm()->getMemory(value_ptr, value_size); + auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_); + auto value = context->wasmVm()->getMemory(value_ptr.u64_, value_size.u64_); if (!key || !value) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - return wasmResultToWord(context->setSharedData(key.value(), value.value(), cas)); + return wasmResultToWord(context->setSharedData(key.value(), value.value(), cas.u64_)); } Word registerSharedQueueHandler(void* raw_context, Word queue_name_ptr, Word queue_name_size, Word token_ptr) { auto context = WASM_CONTEXT(raw_context); - auto queue_name = context->wasmVm()->getMemory(queue_name_ptr, queue_name_size); + auto queue_name = context->wasmVm()->getMemory(queue_name_ptr.u64_, queue_name_size.u64_); if (!queue_name) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } uint32_t token = context->registerSharedQueue(queue_name.value()); - if (!context->wasm()->setDatatype(token_ptr, token)) { + if (!context->wasm()->setDatatype(token_ptr.u64_, token)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -583,7 +583,7 @@ Word dequeueSharedQueueHandler(void* raw_context, Word token, Word data_ptr_ptr, if (result != WasmResult::Ok) { return wasmResultToWord(result); } - if (!context->wasm()->copyToPointerSize(data, data_ptr_ptr, data_size_ptr)) { + if (!context->wasm()->copyToPointerSize(data, data_ptr_ptr.u64_, data_size_ptr.u64_)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -592,8 +592,8 @@ Word dequeueSharedQueueHandler(void* raw_context, Word token, Word data_ptr_ptr, Word resolveSharedQueueHandler(void* raw_context, Word vm_id_ptr, Word vm_id_size, Word queue_name_ptr, Word queue_name_size, Word token_ptr) { auto context = WASM_CONTEXT(raw_context); - auto vm_id = context->wasmVm()->getMemory(vm_id_ptr, vm_id_size); - auto queue_name = context->wasmVm()->getMemory(queue_name_ptr, queue_name_size); + auto vm_id = context->wasmVm()->getMemory(vm_id_ptr.u64_, vm_id_size.u64_); + auto queue_name = context->wasmVm()->getMemory(queue_name_ptr.u64_, queue_name_size.u64_); if (!vm_id || !queue_name) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } @@ -602,7 +602,7 @@ Word resolveSharedQueueHandler(void* raw_context, Word vm_id_ptr, Word vm_id_siz if (result != WasmResult::Ok) { return wasmResultToWord(result); } - if (!context->wasm()->setDatatype(token_ptr, token)) { + if (!context->wasm()->setDatatype(token_ptr.u64_, token)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -610,7 +610,7 @@ Word resolveSharedQueueHandler(void* raw_context, Word vm_id_ptr, Word vm_id_siz Word enqueueSharedQueueHandler(void* raw_context, Word token, Word data_ptr, Word data_size) { auto context = WASM_CONTEXT(raw_context); - auto data = context->wasmVm()->getMemory(data_ptr, data_size); + auto data = context->wasmVm()->getMemory(data_ptr.u64_, data_size.u64_); if (!data) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } @@ -620,94 +620,94 @@ Word enqueueSharedQueueHandler(void* raw_context, Word token, Word data_ptr, Wor // Header/Trailer/Metadata Maps Word addHeaderMapValueHandler(void* raw_context, Word type, Word key_ptr, Word key_size, Word value_ptr, Word value_size) { - if (type > static_cast(HeaderMapType::MAX)) { + if (type.u64_ > static_cast(HeaderMapType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); - auto key = context->wasmVm()->getMemory(key_ptr, key_size); - auto value = context->wasmVm()->getMemory(value_ptr, value_size); + auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_); + auto value = context->wasmVm()->getMemory(value_ptr.u64_, value_size.u64_); if (!key || !value) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - context->addHeaderMapValue(static_cast(type.u64), key.value(), value.value()); + context->addHeaderMapValue(static_cast(type.u64_), key.value(), value.value()); return wasmResultToWord(WasmResult::Ok); } Word getHeaderMapValueHandler(void* raw_context, Word type, Word key_ptr, Word key_size, Word value_ptr_ptr, Word value_size_ptr) { - if (type > static_cast(HeaderMapType::MAX)) { + if (type.u64_ > static_cast(HeaderMapType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); - auto key = context->wasmVm()->getMemory(key_ptr, key_size); + auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_); if (!key) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - auto result = context->getHeaderMapValue(static_cast(type.u64), key.value()); - context->wasm()->copyToPointerSize(result, value_ptr_ptr, value_size_ptr); + auto result = context->getHeaderMapValue(static_cast(type.u64_), key.value()); + context->wasm()->copyToPointerSize(result, value_ptr_ptr.u64_, value_size_ptr.u64_); return wasmResultToWord(WasmResult::Ok); } Word replaceHeaderMapValueHandler(void* raw_context, Word type, Word key_ptr, Word key_size, Word value_ptr, Word value_size) { - if (type > static_cast(HeaderMapType::MAX)) { + if (type.u64_ > static_cast(HeaderMapType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); - auto key = context->wasmVm()->getMemory(key_ptr, key_size); - auto value = context->wasmVm()->getMemory(value_ptr, value_size); + auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_); + auto value = context->wasmVm()->getMemory(value_ptr.u64_, value_size.u64_); if (!key || !value) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - context->replaceHeaderMapValue(static_cast(type.u64), key.value(), value.value()); + context->replaceHeaderMapValue(static_cast(type.u64_), key.value(), value.value()); return wasmResultToWord(WasmResult::Ok); } Word removeHeaderMapValueHandler(void* raw_context, Word type, Word key_ptr, Word key_size) { - if (type > static_cast(HeaderMapType::MAX)) { + if (type.u64_ > static_cast(HeaderMapType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); - auto key = context->wasmVm()->getMemory(key_ptr, key_size); + auto key = context->wasmVm()->getMemory(key_ptr.u64_, key_size.u64_); if (!key) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - context->removeHeaderMapValue(static_cast(type.u64), key.value()); + context->removeHeaderMapValue(static_cast(type.u64_), key.value()); return wasmResultToWord(WasmResult::Ok); } Word getHeaderMapPairsHandler(void* raw_context, Word type, Word ptr_ptr, Word size_ptr) { - if (type > static_cast(HeaderMapType::MAX)) { + if (type.u64_ > static_cast(HeaderMapType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); - auto result = context->getHeaderMapPairs(static_cast(type.u64)); - if (!getPairs(context, result, ptr_ptr, size_ptr)) { + auto result = context->getHeaderMapPairs(static_cast(type.u64_)); + if (!getPairs(context, result, ptr_ptr.u64_, size_ptr.u64_)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); } Word setHeaderMapPairsHandler(void* raw_context, Word type, Word ptr, Word size) { - if (type > static_cast(HeaderMapType::MAX)) { + if (type.u64_ > static_cast(HeaderMapType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); - auto data = context->wasmVm()->getMemory(ptr, size); + auto data = context->wasmVm()->getMemory(ptr.u64_, size.u64_); if (!data) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - context->setHeaderMapPairs(static_cast(type.u64), toPairs(data.value())); + context->setHeaderMapPairs(static_cast(type.u64_), toPairs(data.value())); return wasmResultToWord(WasmResult::Ok); } Word getHeaderMapSizeHandler(void* raw_context, Word type, Word result_ptr) { - if (type > static_cast(HeaderMapType::MAX)) { + if (type.u64_ > static_cast(HeaderMapType::MAX)) { return wasmResultToWord(WasmResult::BadArgument); } auto context = WASM_CONTEXT(raw_context); - size_t result = context->getHeaderMapSize(static_cast(type.u64)); - if (!context->wasmVm()->setWord(result_ptr, Word(result))) { + size_t result = context->getHeaderMapSize(static_cast(type.u64_)); + if (!context->wasmVm()->setWord(result_ptr.u64_, Word(result))) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -717,16 +717,16 @@ Word getHeaderMapSizeHandler(void* raw_context, Word type, Word result_ptr) { Word getRequestBodyBufferBytesHandler(void* raw_context, Word start, Word length, Word ptr_ptr, Word size_ptr) { auto context = WASM_CONTEXT(raw_context); - auto result = context->getRequestBodyBufferBytes(start, length); - context->wasm()->copyToPointerSize(result, ptr_ptr, size_ptr); + auto result = context->getRequestBodyBufferBytes(start.u64_, length.u64_); + context->wasm()->copyToPointerSize(result, ptr_ptr.u64_, size_ptr.u64_); return wasmResultToWord(WasmResult::Ok); } Word getResponseBodyBufferBytesHandler(void* raw_context, Word start, Word length, Word ptr_ptr, Word size_ptr) { auto context = WASM_CONTEXT(raw_context); - auto result = context->getResponseBodyBufferBytes(start, length); - context->wasm()->copyToPointerSize(result, ptr_ptr, size_ptr); + auto result = context->getResponseBodyBufferBytes(start.u64_, length.u64_); + context->wasm()->copyToPointerSize(result, ptr_ptr.u64_, size_ptr.u64_); return wasmResultToWord(WasmResult::Ok); } @@ -734,35 +734,36 @@ Word httpCallHandler(void* raw_context, Word uri_ptr, Word uri_size, Word header Word header_pairs_size, Word body_ptr, Word body_size, Word trailer_pairs_ptr, Word trailer_pairs_size, Word timeout_milliseconds) { auto context = WASM_CONTEXT(raw_context)->root_context(); - auto uri = context->wasmVm()->getMemory(uri_ptr, uri_size); - auto body = context->wasmVm()->getMemory(body_ptr, body_size); - auto header_pairs = context->wasmVm()->getMemory(header_pairs_ptr, header_pairs_size); - auto trailer_pairs = context->wasmVm()->getMemory(trailer_pairs_ptr, trailer_pairs_size); + auto uri = context->wasmVm()->getMemory(uri_ptr.u64_, uri_size.u64_); + auto body = context->wasmVm()->getMemory(body_ptr.u64_, body_size.u64_); + auto header_pairs = context->wasmVm()->getMemory(header_pairs_ptr.u64_, header_pairs_size.u64_); + auto trailer_pairs = + context->wasmVm()->getMemory(trailer_pairs_ptr.u64_, trailer_pairs_size.u64_); if (!uri || !body || !header_pairs || !trailer_pairs) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } auto headers = toPairs(header_pairs.value()); auto trailers = toPairs(trailer_pairs.value()); - return context->httpCall(uri.value(), headers, body.value(), trailers, timeout_milliseconds); + return context->httpCall(uri.value(), headers, body.value(), trailers, timeout_milliseconds.u64_); } Word defineMetricHandler(void* raw_context, Word metric_type, Word name_ptr, Word name_size, Word metric_id_ptr) { - if (metric_type > static_cast(Context::MetricType::Max)) { + if (metric_type.u64_ > static_cast(Context::MetricType::Max)) { return 0; } auto context = WASM_CONTEXT(raw_context); - auto name = context->wasmVm()->getMemory(name_ptr, name_size); + auto name = context->wasmVm()->getMemory(name_ptr.u64_, name_size.u64_); if (!name) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } uint32_t metric_id = 0; - auto result = context->defineMetric(static_cast(metric_type.u64), + auto result = context->defineMetric(static_cast(metric_type.u64_), name.value(), &metric_id); if (result != WasmResult::Ok) { return wasmResultToWord(result); } - if (!context->wasm()->setDatatype(metric_id_ptr, metric_id)) { + if (!context->wasm()->setDatatype(metric_id_ptr.u64_, metric_id)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -770,22 +771,22 @@ Word defineMetricHandler(void* raw_context, Word metric_type, Word name_ptr, Wor Word incrementMetricHandler(void* raw_context, Word metric_id, int64_t offset) { auto context = WASM_CONTEXT(raw_context); - return wasmResultToWord(context->incrementMetric(metric_id, offset)); + return wasmResultToWord(context->incrementMetric(metric_id.u64_, offset)); } Word recordMetricHandler(void* raw_context, Word metric_id, uint64_t value) { auto context = WASM_CONTEXT(raw_context); - return wasmResultToWord(context->recordMetric(metric_id, value)); + return wasmResultToWord(context->recordMetric(metric_id.u64_, value)); } Word getMetricHandler(void* raw_context, Word metric_id, Word result_uint64_ptr) { auto context = WASM_CONTEXT(raw_context); uint64_t value = 0; - auto result = context->getMetric(metric_id, &value); + auto result = context->getMetric(metric_id.u64_, &value); if (result != WasmResult::Ok) { return wasmResultToWord(result); } - if (!context->wasm()->setDatatype(result_uint64_ptr, value)) { + if (!context->wasm()->setDatatype(result_uint64_ptr.u64_, value)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -795,10 +796,10 @@ Word grpcCallHandler(void* raw_context, Word service_ptr, Word service_size, Wor Word service_name_size, Word method_name_ptr, Word method_name_size, Word request_ptr, Word request_size, Word timeout_milliseconds) { auto context = WASM_CONTEXT(raw_context)->root_context(); - auto service = context->wasmVm()->getMemory(service_ptr, service_size); - auto service_name = context->wasmVm()->getMemory(service_name_ptr, service_name_size); - auto method_name = context->wasmVm()->getMemory(method_name_ptr, method_name_size); - auto request = context->wasmVm()->getMemory(request_ptr, request_size); + auto service = context->wasmVm()->getMemory(service_ptr.u64_, service_size.u64_); + auto service_name = context->wasmVm()->getMemory(service_name_ptr.u64_, service_name_size.u64_); + auto method_name = context->wasmVm()->getMemory(method_name_ptr.u64_, method_name_size.u64_); + auto request = context->wasmVm()->getMemory(request_ptr.u64_, request_size.u64_); if (!service || !service_name || !method_name || !request) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } @@ -807,16 +808,16 @@ Word grpcCallHandler(void* raw_context, Word service_ptr, Word service_size, Wor return false; } return context->grpcCall(service_proto, service_name.value(), method_name.value(), - request.value(), std::chrono::milliseconds(timeout_milliseconds)); + request.value(), std::chrono::milliseconds(timeout_milliseconds.u64_)); } Word grpcStreamHandler(void* raw_context, Word service_ptr, Word service_size, Word service_name_ptr, Word service_name_size, Word method_name_ptr, Word method_name_size) { auto context = WASM_CONTEXT(raw_context)->root_context(); - auto service = context->wasmVm()->getMemory(service_ptr, service_size); - auto service_name = context->wasmVm()->getMemory(service_name_ptr, service_name_size); - auto method_name = context->wasmVm()->getMemory(method_name_ptr, method_name_size); + auto service = context->wasmVm()->getMemory(service_ptr.u64_, service_size.u64_); + auto service_name = context->wasmVm()->getMemory(service_name_ptr.u64_, service_name_size.u64_); + auto method_name = context->wasmVm()->getMemory(method_name_ptr.u64_, method_name_size.u64_); if (!service || !service_name || !method_name) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } @@ -829,22 +830,22 @@ Word grpcStreamHandler(void* raw_context, Word service_ptr, Word service_size, Word grpcCancelHandler(void* raw_context, Word token) { auto context = WASM_CONTEXT(raw_context)->root_context(); - return wasmResultToWord(context->grpcCancel(token)); + return wasmResultToWord(context->grpcCancel(token.u64_)); } Word grpcCloseHandler(void* raw_context, Word token) { auto context = WASM_CONTEXT(raw_context)->root_context(); - return wasmResultToWord(context->grpcClose(token)); + return wasmResultToWord(context->grpcClose(token.u64_)); } Word grpcSendHandler(void* raw_context, Word token, Word message_ptr, Word message_size, Word end_stream) { auto context = WASM_CONTEXT(raw_context)->root_context(); - auto message = context->wasmVm()->getMemory(message_ptr, message_size); + auto message = context->wasmVm()->getMemory(message_ptr.u64_, message_size.u64_); if (!message) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - return wasmResultToWord(context->grpcSend(token, message.value(), end_stream)); + return wasmResultToWord(context->grpcSend(token.u64_, message.value(), end_stream.u64_)); } Word _emscripten_get_heap_sizeHandler(void* raw_context) { @@ -854,11 +855,11 @@ Word _emscripten_get_heap_sizeHandler(void* raw_context) { Word _emscripten_memcpy_bigHandler(void* raw_context, Word dst, Word src, Word size) { auto context = WASM_CONTEXT(raw_context); - auto data = context->wasmVm()->getMemory(src, size); + auto data = context->wasmVm()->getMemory(src.u64_, size.u64_); if (!data) { return 0; } - context->wasmVm()->setMemory(dst, size, data.value().data()); + context->wasmVm()->setMemory(dst.u64_, size.u64_, data.value().data()); return dst; } @@ -920,7 +921,7 @@ Word ___syscall146Handler(void* raw_context, Word, Word syscall_args_ptr) { auto context = WASM_CONTEXT(raw_context); // Read syscall args. - auto memslice = context->wasmVm()->getMemory(syscall_args_ptr, 3 * sizeof(uint32_t)); + auto memslice = context->wasmVm()->getMemory(syscall_args_ptr.u64_, 3 * sizeof(uint32_t)); if (!memslice) { context->wasm()->setErrno(EINVAL); return -1; @@ -971,7 +972,7 @@ Word ___syscall146Handler(void* raw_context, Word, Word syscall_args_ptr) { void ___setErrNoHandler(void*, Word) { throw WasmException("emscripten setErrNo"); } -Word _pthread_equalHandler(void*, Word left, Word right) { return left == right; } +Word _pthread_equalHandler(void*, Word left, Word right) { return left.u64_ == right.u64_; } // NB: pthread_mutex_destroy is required to return 0 by the protobuf libarary. Word _pthread_mutex_destroyHandler(void*, Word) { return 0; } Word _pthread_cond_waitHandler(void*, Word, Word) { @@ -990,14 +991,15 @@ Word _pthread_setspecificHandler(void*, Word, Word) { void setTempRet0Handler(void*, Word) { throw WasmException("emscripten setTempRet0"); } Word setTickPeriodMillisecondsHandler(void* raw_context, Word tick_period_milliseconds) { - return wasmResultToWord(WASM_CONTEXT(raw_context) - ->setTickPeriod(std::chrono::milliseconds(tick_period_milliseconds))); + return wasmResultToWord( + WASM_CONTEXT(raw_context) + ->setTickPeriod(std::chrono::milliseconds(tick_period_milliseconds.u64_))); } Word getCurrentTimeNanosecondsHandler(void* raw_context, Word result_uint64_ptr) { auto context = WASM_CONTEXT(raw_context); uint64_t result = context->getCurrentTimeNanoseconds(); - if (!context->wasm()->setDatatype(result_uint64_ptr.u64, result)) { + if (!context->wasm()->setDatatype(result_uint64_ptr.u64_, result)) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } return wasmResultToWord(WasmResult::Ok); @@ -1005,11 +1007,11 @@ Word getCurrentTimeNanosecondsHandler(void* raw_context, Word result_uint64_ptr) Word logHandler(void* raw_context, Word level, Word address, Word size) { auto context = WASM_CONTEXT(raw_context); - auto message = context->wasmVm()->getMemory(address, size); + auto message = context->wasmVm()->getMemory(address.u64_, size.u64_); if (!message) { return wasmResultToWord(WasmResult::InvalidMemoryAccess); } - context->scriptLog(static_cast(level.u64), message.value()); + context->scriptLog(static_cast(level.u64_), message.value()); return wasmResultToWord(WasmResult::Ok); } @@ -1776,7 +1778,7 @@ Http::FilterHeadersStatus Context::onRequestHeaders() { if (!wasm_->onRequestHeaders_) { return Http::FilterHeadersStatus::Continue; } - if (wasm_->onRequestHeaders_(this, id_) == 0) { + if (wasm_->onRequestHeaders_(this, id_).u64_ == 0) { return Http::FilterHeadersStatus::Continue; } return Http::FilterHeadersStatus::StopIteration; @@ -1786,8 +1788,10 @@ Http::FilterDataStatus Context::onRequestBody(int body_buffer_length, bool end_o if (!wasm_->onRequestBody_) { return Http::FilterDataStatus::Continue; } - switch (wasm_->onRequestBody_(this, id_, static_cast(body_buffer_length), - static_cast(end_of_stream))) { + switch (wasm_ + ->onRequestBody_(this, id_, static_cast(body_buffer_length), + static_cast(end_of_stream)) + .u64_) { case 0: return Http::FilterDataStatus::Continue; case 1: @@ -1803,7 +1807,7 @@ Http::FilterTrailersStatus Context::onRequestTrailers() { if (!wasm_->onRequestTrailers_) { return Http::FilterTrailersStatus::Continue; } - if (wasm_->onRequestTrailers_(this, id_) == 0) { + if (wasm_->onRequestTrailers_(this, id_).u64_ == 0) { return Http::FilterTrailersStatus::Continue; } return Http::FilterTrailersStatus::StopIteration; @@ -1813,7 +1817,7 @@ Http::FilterMetadataStatus Context::onRequestMetadata() { if (!wasm_->onRequestMetadata_) { return Http::FilterMetadataStatus::Continue; } - if (wasm_->onRequestMetadata_(this, id_) == 0) { + if (wasm_->onRequestMetadata_(this, id_).u64_ == 0) { return Http::FilterMetadataStatus::Continue; } return Http::FilterMetadataStatus::Continue; // This is currently the only return code. @@ -1831,7 +1835,7 @@ Http::FilterHeadersStatus Context::onResponseHeaders() { if (!wasm_->onResponseHeaders_) { return Http::FilterHeadersStatus::Continue; } - if (wasm_->onResponseHeaders_(this, id_) == 0) { + if (wasm_->onResponseHeaders_(this, id_).u64_ == 0) { return Http::FilterHeadersStatus::Continue; } return Http::FilterHeadersStatus::StopIteration; @@ -1841,8 +1845,10 @@ Http::FilterDataStatus Context::onResponseBody(int body_buffer_length, bool end_ if (!wasm_->onResponseBody_) { return Http::FilterDataStatus::Continue; } - switch (wasm_->onResponseBody_(this, id_, static_cast(body_buffer_length), - static_cast(end_of_stream))) { + switch (wasm_ + ->onResponseBody_(this, id_, static_cast(body_buffer_length), + static_cast(end_of_stream)) + .u64_) { case 0: return Http::FilterDataStatus::Continue; case 1: @@ -1858,7 +1864,7 @@ Http::FilterTrailersStatus Context::onResponseTrailers() { if (!wasm_->onResponseTrailers_) { return Http::FilterTrailersStatus::Continue; } - if (wasm_->onResponseTrailers_(this, id_) == 0) { + if (wasm_->onResponseTrailers_(this, id_).u64_ == 0) { return Http::FilterTrailersStatus::Continue; } return Http::FilterTrailersStatus::StopIteration; @@ -1868,7 +1874,7 @@ Http::FilterMetadataStatus Context::onResponseMetadata() { if (!wasm_->onResponseMetadata_) { return Http::FilterMetadataStatus::Continue; } - if (wasm_->onResponseMetadata_(this, id_) == 0) { + if (wasm_->onResponseMetadata_(this, id_).u64_ == 0) { return Http::FilterMetadataStatus::Continue; } return Http::FilterMetadataStatus::Continue; // This is currently the only return code. @@ -2305,7 +2311,7 @@ void Wasm::setErrno(int32_t err) { return; } Word location = __errno_location_(vmContext()); - setDatatype(location.u64, err); + setDatatype(location.u64_, err); } void Wasm::setTickPeriod(uint32_t context_id, std::chrono::milliseconds new_tick_period) { @@ -2691,7 +2697,7 @@ std::shared_ptr createThreadLocalWasm(Wasm& base_wasm, absl::string_view r Event::Dispatcher& dispatcher) { std::shared_ptr wasm; Context* root_context; - if (base_wasm.wasmVm()->clonable()) { + if (base_wasm.wasmVm()->cloneable()) { wasm = std::make_shared(base_wasm, dispatcher); root_context = wasm->start(root_id, base_wasm.vm_configuration()); } else { diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index 60afa471c0..d4a44ba228 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -578,40 +578,40 @@ class Wasm : public Envoy::Server::Wasm, owned_scope_; // When scope_ is not owned by a higher level (e.g. for WASM services). TimeSource& time_source_; - WasmCall1Word malloc_; - WasmCall1Void free_; - WasmCall0Word __errno_location_; + WasmCallWord<1> malloc_; + WasmCallVoid<1> free_; + WasmCallWord<0> __errno_location_; // Calls into the VM. - WasmCall5Void onStart_; - WasmCall3Void onConfigure_; - WasmCall1Void onTick_; + WasmCallVoid<5> onStart_; + WasmCallVoid<3> onConfigure_; + WasmCallVoid<1> onTick_; - WasmCall2Void onCreate_; + WasmCallVoid<2> onCreate_; - WasmCall1Word onRequestHeaders_; - WasmCall3Word onRequestBody_; - WasmCall1Word onRequestTrailers_; - WasmCall1Word onRequestMetadata_; + WasmCallWord<1> onRequestHeaders_; + WasmCallWord<3> onRequestBody_; + WasmCallWord<1> onRequestTrailers_; + WasmCallWord<1> onRequestMetadata_; - WasmCall1Word onResponseHeaders_; - WasmCall3Word onResponseBody_; - WasmCall1Word onResponseTrailers_; - WasmCall1Word onResponseMetadata_; + WasmCallWord<1> onResponseHeaders_; + WasmCallWord<3> onResponseBody_; + WasmCallWord<1> onResponseTrailers_; + WasmCallWord<1> onResponseMetadata_; - WasmCall8Void onHttpCallResponse_; + WasmCallVoid<8> onHttpCallResponse_; - WasmCall4Void onGrpcReceive_; - WasmCall5Void onGrpcClose_; - WasmCall2Void onGrpcCreateInitialMetadata_; - WasmCall2Void onGrpcReceiveInitialMetadata_; - WasmCall2Void onGrpcReceiveTrailingMetadata_; + WasmCallVoid<4> onGrpcReceive_; + WasmCallVoid<5> onGrpcClose_; + WasmCallVoid<2> onGrpcCreateInitialMetadata_; + WasmCallVoid<2> onGrpcReceiveInitialMetadata_; + WasmCallVoid<2> onGrpcReceiveTrailingMetadata_; - WasmCall2Void onQueueReady_; + WasmCallVoid<2> onQueueReady_; - WasmCall1Void onDone_; - WasmCall1Void onLog_; - WasmCall1Void onDelete_; + WasmCallVoid<1> onDone_; + WasmCallVoid<1> onLog_; + WasmCallVoid<1> onDelete_; // Used by the base_wasm to enable non-clonable thread local Wasm(s) to be constructed. std::string code_; @@ -721,14 +721,14 @@ inline absl::string_view Context::root_id() const { inline void* Wasm::allocMemory(uint64_t size, uint64_t* address) { Word a = malloc_(vmContext(), size); - if (!a.u64) { + if (!a.u64_) { throw WasmException("malloc_ returns nullptr (OOM)"); } - auto memory = wasm_vm_->getMemory(a, size); + auto memory = wasm_vm_->getMemory(a.u64_, size); if (!memory) { throw WasmException("malloc_ returned illegal address"); } - *address = a.u64; + *address = a.u64_; return const_cast(reinterpret_cast(memory.value().data())); } diff --git a/source/extensions/common/wasm/wasm_vm.h b/source/extensions/common/wasm/wasm_vm.h index 0b4cc5d14a..dee5af3f90 100644 --- a/source/extensions/common/wasm/wasm_vm.h +++ b/source/extensions/common/wasm/wasm_vm.h @@ -18,80 +18,71 @@ class Context; // Represents a WASM-native word-sized datum. On 32-bit VMs, the high bits are always zero. // The WASM/VM API treats all bits as significant. struct Word { - Word(uint64_t w) : u64(w) {} // Implicit conversion into Word. - operator uint64_t() const { return u64; } // Implicit conversion into uint64_t. - // Note: no implicit conversion to uint32_t as it is lossy. - uint32_t u32() const { return static_cast(u64); } - uint64_t u64; + Word(uint64_t w) : u64_(w) {} // Implicit conversion into Word. + uint32_t u32() const { return static_cast(u64_); } + uint64_t u64_; }; +inline std::ostream& operator<<(std::ostream& os, const Word& w) { return os << w.u64_; } + +// Convert Word type for use by 32-bit VMs. template struct ConvertWordTypeToUint32 { using type = T; }; template <> struct ConvertWordTypeToUint32 { using type = uint32_t; }; +// Convert Word-based function types for 32-bit VMs. template struct ConvertFunctionTypeWordToUint32 {}; template struct ConvertFunctionTypeWordToUint32 { using type = typename ConvertWordTypeToUint32::type (*)( typename ConvertWordTypeToUint32::type...); }; +// A wrapper for a global variable within the VM. template struct Global { - virtual ~Global() {} + virtual ~Global() = default; virtual T get() PURE; virtual void set(const T& t) PURE; }; +// These are templates and its helper for constructing signatures of functions calling into and +// out of WASM VMs. +// - WasmFuncTypeHelper is a helper for WasmFuncType and shouldn't be used anywhere else than +// WasmFuncType definition. +// - WasmFuncType takes 4 template parameter which are number of argument, return type, context +// type and param type respectively, resolve to a function type. +// For example `WasmFuncType<3, void, Context*, Word>` resolves to `void(Context*, Word, Word, +// Word)` +template +struct WasmFuncTypeHelper {}; + +template +struct WasmFuncTypeHelper { + using type = typename WasmFuncTypeHelper::type; +}; + +template +struct WasmFuncTypeHelper<0, ReturnType, ContextType, ParamType, ReturnType(ContextType, Args...)> { + using type = ReturnType(ContextType, Args...); +}; + +template +using WasmFuncType = typename WasmFuncTypeHelper::type; + // Calls into the WASM VM. // 1st arg is always a pointer to Context (Context*). -using WasmCall0Void = std::function; -using WasmCall1Void = std::function; -using WasmCall2Void = std::function; -using WasmCall3Void = std::function; -using WasmCall4Void = std::function; -using WasmCall5Void = std::function; -using WasmCall6Void = std::function; -using WasmCall7Void = std::function; -using WasmCall8Void = std::function; -using WasmCall0Word = std::function; -using WasmCall1Word = std::function; -using WasmCall2Word = std::function; -using WasmCall3Word = std::function; -using WasmCall4Word = std::function; -using WasmCall5Word = std::function; -using WasmCall6Word = std::function; -using WasmCall7Word = std::function; -using WasmCall8Word = std::function; +template using WasmCallVoid = std::function>; +template using WasmCallWord = std::function>; + #define FOR_ALL_WASM_VM_EXPORTS(_f) \ - _f(WasmCall0Void) _f(WasmCall1Void) _f(WasmCall2Void) _f(WasmCall3Void) _f(WasmCall4Void) \ - _f(WasmCall5Void) _f(WasmCall8Void) _f(WasmCall0Word) _f(WasmCall1Word) _f(WasmCall3Word) + _f(WasmCallVoid<0>) _f(WasmCallVoid<1>) _f(WasmCallVoid<2>) _f(WasmCallVoid<3>) \ + _f(WasmCallVoid<4>) _f(WasmCallVoid<5>) _f(WasmCallVoid<8>) _f(WasmCallWord<0>) \ + _f(WasmCallWord<1>) _f(WasmCallWord<3>) // Calls out of the WASM VM. // 1st arg is always a pointer to raw_context (void*). -using WasmCallback0Void = void (*)(void*); -using WasmCallback1Void = void (*)(void*, Word); -using WasmCallback2Void = void (*)(void*, Word, Word); -using WasmCallback3Void = void (*)(void*, Word, Word, Word); -using WasmCallback4Void = void (*)(void*, Word, Word, Word, Word); -using WasmCallback5Void = void (*)(void*, Word, Word, Word, Word, Word); -using WasmCallback6Void = void (*)(void*, Word, Word, Word, Word, Word, Word); -using WasmCallback7Void = void (*)(void*, Word, Word, Word, Word, Word, Word, Word); -using WasmCallback8Void = void (*)(void*, Word, Word, Word, Word, Word, Word, Word, Word); -using WasmCallback0Word = Word (*)(void*); -using WasmCallback1Word = Word (*)(void*, Word); -using WasmCallback2Word = Word (*)(void*, Word, Word); -using WasmCallback3Word = Word (*)(void*, Word, Word, Word); -using WasmCallback4Word = Word (*)(void*, Word, Word, Word, Word); -using WasmCallback5Word = Word (*)(void*, Word, Word, Word, Word, Word); -using WasmCallback6Word = Word (*)(void*, Word, Word, Word, Word, Word, Word); -using WasmCallback7Word = Word (*)(void*, Word, Word, Word, Word, Word, Word, Word, Word); -using WasmCallback8Word = Word (*)(void*, Word, Word, Word, Word, Word, Word, Word, Word, Word); -using WasmCallback9Word = Word (*)(void*, Word, Word, Word, Word, Word, Word, Word, Word, Word, - Word); -#define FOR_ALL_WASM_VM_IMPORTS(_f) \ - _f(WasmCallback0Void) _f(WasmCallback1Void) _f(WasmCallback2Void) _f(WasmCallback3Void) \ - _f(WasmCallback4Void) _f(WasmCallback0Word) _f(WasmCallback1Word) _f(WasmCallback2Word) \ - _f(WasmCallback3Word) _f(WasmCallback4Word) _f(WasmCallback5Word) _f(WasmCallback6Word) \ - _f(WasmCallback7Word) _f(WasmCallback8Word) _f(WasmCallback9Word) \ - _f(WasmCallback_WWl) _f(WasmCallback_WWm) +template using WasmCallbackVoid = WasmFuncType*; +template using WasmCallbackWord = WasmFuncType*; // Using the standard g++/clang mangling algorithm: // https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-builtin @@ -100,65 +91,194 @@ using WasmCallback9Word = Word (*)(void*, Word, Word, Word, Word, Word, Word, Wo using WasmCallback_WWl = Word (*)(void*, Word, int64_t); using WasmCallback_WWm = Word (*)(void*, Word, uint64_t); +#define FOR_ALL_WASM_VM_IMPORTS(_f) \ + _f(WasmCallbackVoid<0>) _f(WasmCallbackVoid<1>) _f(WasmCallbackVoid<2>) _f(WasmCallbackVoid<3>) \ + _f(WasmCallbackVoid<4>) _f(WasmCallbackWord<0>) _f(WasmCallbackWord<1>) \ + _f(WasmCallbackWord<2>) _f(WasmCallbackWord<3>) _f(WasmCallbackWord<4>) \ + _f(WasmCallbackWord<5>) _f(WasmCallbackWord<6>) _f(WasmCallbackWord<7>) \ + _f(WasmCallbackWord<8>) _f(WasmCallbackWord<9>) _f(WasmCallback_WWl) \ + _f(WasmCallback_WWm) + // Wasm VM instance. Provides the low level WASM interface. class WasmVm : public Logger::Loggable { public: - virtual ~WasmVm() {} + using WasmVmPtr = std::unique_ptr; + + virtual ~WasmVm() = default; + /** + * Return the VM identifier. + * @return one of WasmVmValues from well_known_names.h e.g. "envoy.wasm.vm.null". + */ virtual absl::string_view vm() PURE; - // Whether or not the VM implementation supports cloning. - virtual bool clonable() PURE; - // Make a thread-specific copy. This may not be supported by the underlying VM system in which - // case it will return nullptr and the caller will need to create a new VM from scratch. - virtual std::unique_ptr clone() PURE; + /** + * Whether or not the VM implementation supports cloning. Cloning is VM system dependent. + * When a VM is configured a single VM is instantiated to check that the .wasm file is valid and + * to do VM system specific initialization. In the case of WAVM this is potentially + * ahead-of-time compilation. Then, if cloning is supported, we clone that VM for each worker, + * potentially copying and sharing the initialized data structures for efficiency. Otherwise we + * create an new VM from scratch for each worker. + * @return true if the VM is cloneable. + */ + virtual bool cloneable() PURE; + + /** + * Make a worker/thread-specific copy if supported by the underlying VM system (see cloneable() + * above). If not supported, the caller will need to create a new VM from scratch. If supported, + * the clone may share compiled code and other read-only data with the source VM. + * @return a clone of 'this' (e.g. for a different worker/thread). + */ + virtual WasmVmPtr clone() PURE; - // Load the WASM code from a file. Return true on success. + /** + * Load the WASM code from a file. Return true on success. Once the module is loaded it can be + * queried, e.g. to see which version of emscripten support is required. After loading, the + * appropriate ABI callbacks can be registered and then the module can be link()ed (see below). + * @param code the WASM binary code (or registered NullVm plugin name). + * @param allow_precompiled if true, allows supporting VMs (e.g. WAVM) to load the binary + * machine code from a user-defined section of the WASM file. Because that code is not verified + * by the envoy process it is up to the user to ensure that the code is both safe and is built + * for the linked in version of WAVM. + * @return whether or not the load was successful. + */ virtual bool load(const std::string& code, bool allow_precompiled) PURE; - // Link to registered function. + + /** + * Link the WASM code to the host-provided functions and globals, e.g. the ABI. Prior to + * linking, the module should be loaded and the ABI callbacks registered (see above). Linking + * should be done once between load() and start(). + * @param debug_name user-provided name for use in log and error messages. + * @param needs_emscripten whether emscripten support should be provided (e.g. + * _emscripten_memcpy_bigHandler). Emscripten (http://https://emscripten.org/) is + * a C++ WebAssembly tool chain. + */ virtual void link(absl::string_view debug_name, bool needs_emscripten) PURE; - // Set memory layout (start of dynamic heap base, etc.) in the VM. + /** + * Set memory layout (start of dynamic heap base, etc.) in the VM. + * @param stack_base the location in VM memory of the stack. + * @param heap_base the location in VM memory of the heap. + * @param heap_base_ptr the location in VM memory of a location to store the heap pointer. + */ virtual void setMemoryLayout(uint64_t stack_base, uint64_t heap_base, uint64_t heap_base_pointer) PURE; - // Call the 'start' function and initialize globals. - virtual void start(Context*) PURE; + /** + * Initialize globals (including calling global constructors) and call the 'start' function. + * Prior to calling start() the module should be load()ed, ABI callbacks should be registered + * (registerCallback), the module link()ed, and any exported functions should be gotten + * (getFunction). + * @param vm_context a context which represents the caller: in this case Envoy itself. + */ + virtual void start(Context* vm_context) PURE; - // Get size of the currently allocated memory in the VM. + /** + * Get size of the currently allocated memory in the VM. + * @return the size of memory in bytes. + */ virtual uint64_t getMemorySize() PURE; - // Convert a block of memory in the VM to a string_view. Returns 'false' in second if the - // pointer/size is invalid. + + /** + * Convert a block of memory in the VM to a string_view. + * @param pointer the offset into VM memory of the requested VM memory block. + * @param size the size of the requested VM memory block. + * @return if std::nullopt then the pointer/size pair were invalid, otherwise returns + * a host string_view pointing to the pointer/size pair in VM memory. + */ virtual absl::optional getMemory(uint64_t pointer, uint64_t size) PURE; - // Convert a host pointer to memory in the VM into a VM "pointer" (an offset into the Memory). + + /** + * Convert a host pointer to memory in the VM into a VM "pointer" (an offset into the Memory). + * @param host_pointer a pointer to host memory to be converted into a VM offset (pointer). + * @param vm_pointer a pointer to an uint64_t to be filled with the offset in VM memory + * corresponding to 'host_pointer'. + * @return whether or not the host_pointer was a valid VM memory offset. + */ virtual bool getMemoryOffset(void* host_pointer, uint64_t* vm_pointer) PURE; - // Set a block of memory in the VM, returns true on success, false if the pointer/size is invalid. + + /** + * Set a block of memory in the VM, returns true on success, false if the pointer/size is + * invalid. + * @param pointer the offset into VM memory describing the start of a region of VM memory. + * @param size the size of the region of VM memory. + * @return whether or not the pointer/size pair was a valid VM memory block. + */ virtual bool setMemory(uint64_t pointer, uint64_t size, const void* data) PURE; - // Set a Word in the VM, returns true on success, false if the pointer is invalid. + + /** + * Get a VM native Word (e.g. sizeof(void*) or sizeof(size_t)) from VM memory, returns true on + * success, false if the pointer is invalid. WASM-32 VMs have 32-bit native words and WASM-64 + * VMs (not yet supported) will have 64-bit words as does the Null VM (compiled into 64-bit + * Envoy). This function can be used to chase pointers in VM memory. + * @param pointer the offset into VM memory describing the start of VM native word size block. + * @param data a pointer to a Word whose contents will be filled from the VM native word at + * 'pointer'. + * @return whether or not the pointer was to a valid VM memory block of VM native word size. + */ + virtual bool getWord(uint64_t pointer, Word* data) PURE; + + /** + * Set a Word in the VM, returns true on success, false if the pointer is invalid. + * See getWord above for details. This function can be used (for example) to set indirect + * pointer return values (e.g. proxy_getHeaderHapValue(... const char** value_ptr, size_t* + * value_size). + * @param pointer the offset into VM memory describing the start of VM native word size block. + * @param data a Word whose contents will be written in VM native word size at 'pointer'. + * @return whether or not the pointer was to a valid VM memory block of VM native word size. + */ virtual bool setWord(uint64_t pointer, Word data) PURE; - // Make a new intrinsic module (e.g. for Emscripten support). + + /** + * Make a new intrinsic module (e.g. for Emscripten support). + * @param name the name of the module to make. + */ virtual void makeModule(absl::string_view name) PURE; - // Get the contents of the user section with the given name or "" if it does not exist. + /** + * Get the contents of the user section with the given name or "" if it does not exist. + * @param name the name of the user section to get. + * @return the contents of the user section (if any). The result will be empty() if there + * is no such section. + */ virtual absl::string_view getUserSection(absl::string_view name) PURE; - // Get typed function exported by the WASM module. + /** + * Get typed function exported by the WASM module. + */ #define _GET_FUNCTION(_T) virtual void getFunction(absl::string_view function_name, _T* f) PURE; FOR_ALL_WASM_VM_EXPORTS(_GET_FUNCTION) #undef _GET_FUNCTION - // Register typed callbacks exported by the host environment. + /** + * Register typed callbacks exported by the host environment. + */ #define _REGISTER_CALLBACK(_T) \ - virtual void registerCallback(absl::string_view module_name, absl::string_view function_name, \ + virtual void registerCallback(absl::string_view moduleName, absl::string_view function_name, \ _T f, typename ConvertFunctionTypeWordToUint32<_T>::type) PURE; FOR_ALL_WASM_VM_IMPORTS(_REGISTER_CALLBACK) #undef _REGISTER_CALLBACK - // Register typed value exported by the host environment. + /** + * Register typed value exported by the host environment. + * @param module_name the name of the module to which to export the global. + * @param name the name of the global variable to export. + * @param initial_value the initial value of the global. + * @return a Global object which can be used to access the exported global. + */ virtual std::unique_ptr> makeGlobal(absl::string_view module_name, absl::string_view name, Word initial_value) PURE; + + /** + * Register typed value exported by the host environment. + * @param module_name the name of the module to which to export the global. + * @param name the name of the global variable to export. + * @param initial_value the initial value of the global. + * @return a Global object which can be used to access the exported global. + */ virtual std::unique_ptr> makeGlobal(absl::string_view module_name, absl::string_view name, double initial_value) PURE; }; +using WasmVmPtr = std::unique_ptr; // Exceptions for issues with the WasmVm. class WasmVmException : public EnvoyException { @@ -172,9 +292,20 @@ class WasmException : public EnvoyException { using EnvoyException::EnvoyException; }; +// Thread local state set during a call into a WASM VM so that calls coming out of the +// VM can be attributed correctly to calling Filter. We use thread_local instead of ThreadLocal +// because this state is live only during the calls and does not need to be initialized +// consistently over all workers as with ThreadLocal data. extern thread_local Envoy::Extensions::Common::Wasm::Context* current_context_; + +// Requested effective context set by code within the VM to request that the calls coming out of +// the VM be attributed to another filter, for example if a control plane gRPC comes back to the +// RootContext which effects some set of waiting filters. extern thread_local uint32_t effective_context_id_; +// Helper to save and restore thread local VM call context information to support reentrant calls. +// NB: this happens for example when a call from the VM invokes a handler which needs to _malloc +// memory in the VM. struct SaveRestoreContext { explicit SaveRestoreContext(Context* context) { saved_context = current_context_; @@ -191,7 +322,7 @@ struct SaveRestoreContext { }; // Create a new low-level WASM VM of the give type (e.g. "envoy.wasm.vm.wavm"). -std::unique_ptr createWasmVm(absl::string_view vm); +WasmVmPtr createWasmVm(absl::string_view vm); } // namespace Wasm } // namespace Common diff --git a/source/extensions/common/wasm/wavm/wavm.cc b/source/extensions/common/wasm/wavm/wavm.cc index 78dcfbd900..83c26d09be 100644 --- a/source/extensions/common/wasm/wavm/wavm.cc +++ b/source/extensions/common/wasm/wavm/wavm.cc @@ -76,7 +76,7 @@ struct WasmUntaggedValue : public WAVM::IR::UntaggedValue { WasmUntaggedValue(I32 inI32) { i32 = inI32; } WasmUntaggedValue(I64 inI64) { i64 = inI64; } WasmUntaggedValue(U32 inU32) { u32 = inU32; } - WasmUntaggedValue(Word w) { u32 = static_cast(w.u64); } + WasmUntaggedValue(Word w) { u32 = static_cast(w.u64_); } WasmUntaggedValue(U64 inU64) { u64 = inU64; } WasmUntaggedValue(F32 inF32) { f32 = inF32; } WasmUntaggedValue(F64 inF64) { f64 = inF64; } @@ -185,13 +185,17 @@ struct WavmGlobalBase { template struct NativeWord { using type = T; }; template <> struct NativeWord { using type = uint32_t; }; +template typename NativeWord::type ToNative(const T& t) { return t; } +template <> typename NativeWord::type ToNative(const Word& t) { return t.u32(); } + template struct WavmGlobal : Global, Intrinsics::GenericGlobal::type>, WavmGlobalBase { WavmGlobal(Common::Wasm::Wavm::Wavm* wavm, Intrinsics::Module& module, const std::string& name, T value) - : Intrinsics::GenericGlobal::type>(module, name.c_str(), value), + : Intrinsics::GenericGlobal::type>(module, name.c_str(), + ToNative(value)), wavm_(wavm) {} virtual ~WavmGlobal() {} @@ -213,7 +217,7 @@ struct Wavm : public WasmVm { // WasmVm absl::string_view vm() override { return WasmVmNames::get().Wavm; } - bool clonable() override { return true; }; + bool cloneable() override { return true; }; std::unique_ptr clone() override; bool load(const std::string& code, bool allow_precompiled) override; void setMemoryLayout(uint64_t, uint64_t, uint64_t) override {} @@ -223,6 +227,7 @@ struct Wavm : public WasmVm { absl::optional getMemory(uint64_t pointer, uint64_t size) override; bool getMemoryOffset(void* host_pointer, uint64_t* vm_pointer) override; bool setMemory(uint64_t pointer, uint64_t size, const void* data) override; + bool getWord(uint64_t pointer, Word* data) override; bool setWord(uint64_t pointer, Word data) override; void makeModule(absl::string_view name) override; absl::string_view getUserSection(absl::string_view name) override; @@ -430,6 +435,18 @@ bool Wavm::setMemory(uint64_t pointer, uint64_t size, const void* data) { return true; } +bool Wavm::getWord(uint64_t pointer, Word* data) { + auto memory_num_bytes = WAVM::Runtime::getMemoryNumPages(memory_) * WasmPageSize; + if (pointer + sizeof(uint32_t) > memory_num_bytes) { + return false; + } + auto p = reinterpret_cast(memory_base_ + pointer); + uint32_t data32; + memcpy(&data32, p, sizeof(uint32_t)); + data->u64_ = data32; + return true; +} + bool Wavm::setWord(uint64_t pointer, Word data) { uint32_t data32 = data.u32(); return setMemory(pointer, sizeof(uint32_t), &data32); @@ -706,6 +723,10 @@ template void WavmGlobal::set(const T& t) { setGlobalValue(wavm_->context_, global_, IR::Value(t)); } +template <> void WavmGlobal::set(const Word& t) { + setGlobalValue(wavm_->context_, global_, IR::Value(t.u64_)); +} + template std::unique_ptr> makeGlobalWavm(WasmVm* vm, absl::string_view module_name, absl::string_view name, T initial_value) { diff --git a/source/extensions/common/wasm/well_known_names.h b/source/extensions/common/wasm/well_known_names.h index e3d8abad26..17ba7356a0 100644 --- a/source/extensions/common/wasm/well_known_names.h +++ b/source/extensions/common/wasm/well_known_names.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "common/singleton/const_singleton.h" namespace Envoy { @@ -25,7 +27,7 @@ class WasmVmValues { const std::string FilterState = "envoy.wasm"; }; -typedef ConstSingleton WasmVmNames; +using WasmVmNames = ConstSingleton; } // namespace Wasm } // namespace Common diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index 1ff3ebca97..183a08c56c 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -6,6 +6,7 @@ EXTENSIONS = { "envoy.access_loggers.file": "//source/extensions/access_loggers/file:config", "envoy.access_loggers.http_grpc": "//source/extensions/access_loggers/grpc:http_config", + "envoy.access_loggers.tcp_grpc": "//source/extensions/access_loggers/grpc:tcp_config", "envoy.access_loggers.wasm": "//source/extensions/access_loggers/wasm:config", # diff --git a/source/extensions/filters/common/expr/context.cc b/source/extensions/filters/common/expr/context.cc index 5ee1e91e69..cf2e3a1b06 100644 --- a/source/extensions/filters/common/expr/context.cc +++ b/source/extensions/filters/common/expr/context.cc @@ -110,22 +110,41 @@ absl::optional ConnectionWrapper::operator[](CelValue key) const { return {}; } auto value = key.StringOrDie().value(); - if (value == UpstreamAddress) { + if (value == MTLS) { + return CelValue::CreateBool(info_.downstreamSslConnection() != nullptr && + info_.downstreamSslConnection()->peerCertificatePresented()); + } else if (value == RequestedServerName) { + return CelValue::CreateString(info_.requestedServerName()); + } + + if (info_.downstreamSslConnection() != nullptr) { + if (value == TLSVersion) { + return CelValue::CreateString(info_.downstreamSslConnection()->tlsVersion()); + } + } + + return {}; +} + +absl::optional UpstreamWrapper::operator[](CelValue key) const { + if (!key.IsString()) { + return {}; + } + auto value = key.StringOrDie().value(); + if (value == Address) { auto upstream_host = info_.upstreamHost(); if (upstream_host != nullptr && upstream_host->address() != nullptr) { return CelValue::CreateString(upstream_host->address()->asStringView()); } - } else if (value == UpstreamPort) { + } else if (value == Port) { auto upstream_host = info_.upstreamHost(); if (upstream_host != nullptr && upstream_host->address() != nullptr && upstream_host->address()->ip() != nullptr) { return CelValue::CreateInt64(upstream_host->address()->ip()->port()); } } else if (value == MTLS) { - return CelValue::CreateBool(info_.downstreamSslConnection() != nullptr && - info_.downstreamSslConnection()->peerCertificatePresented()); - } else if (value == RequestedServerName) { - return CelValue::CreateString(info_.requestedServerName()); + return CelValue::CreateBool(info_.upstreamSslConnection() != nullptr && + info_.upstreamSslConnection()->peerCertificatePresented()); } return {}; diff --git a/source/extensions/filters/common/expr/context.h b/source/extensions/filters/common/expr/context.h index 7b59c41a5a..3d3ecfb4c4 100644 --- a/source/extensions/filters/common/expr/context.h +++ b/source/extensions/filters/common/expr/context.h @@ -40,10 +40,9 @@ constexpr absl::string_view Metadata = "metadata"; // Connection properties constexpr absl::string_view Connection = "connection"; -constexpr absl::string_view UpstreamAddress = "upstream_address"; -constexpr absl::string_view UpstreamPort = "upstream_port"; constexpr absl::string_view MTLS = "mtls"; constexpr absl::string_view RequestedServerName = "requested_server_name"; +constexpr absl::string_view TLSVersion = "tls_version"; // Source properties constexpr absl::string_view Source = "source"; @@ -53,6 +52,9 @@ constexpr absl::string_view Port = "port"; // Destination properties constexpr absl::string_view Destination = "destination"; +// Upstream properties +constexpr absl::string_view Upstream = "upstream"; + class RequestWrapper; class HeadersWrapper : public google::api::expr::runtime::CelMap { @@ -112,6 +114,15 @@ class ConnectionWrapper : public BaseWrapper { const StreamInfo::StreamInfo& info_; }; +class UpstreamWrapper : public BaseWrapper { +public: + UpstreamWrapper(const StreamInfo::StreamInfo& info) : info_(info) {} + absl::optional operator[](CelValue key) const override; + +private: + const StreamInfo::StreamInfo& info_; +}; + class PeerWrapper : public BaseWrapper { public: PeerWrapper(const StreamInfo::StreamInfo& info, bool local) : info_(info), local_(local) {} diff --git a/source/extensions/filters/common/expr/evaluator.cc b/source/extensions/filters/common/expr/evaluator.cc index bd25da5297..0fb973a368 100644 --- a/source/extensions/filters/common/expr/evaluator.cc +++ b/source/extensions/filters/common/expr/evaluator.cc @@ -51,12 +51,14 @@ absl::optional evaluate(const Expression& expr, Protobuf::Arena* arena const RequestWrapper request(request_headers, info); const ResponseWrapper response(response_headers, response_trailers, info); const ConnectionWrapper connection(info); + const UpstreamWrapper upstream(info); const PeerWrapper source(info, false); const PeerWrapper destination(info, true); activation.InsertValue(Request, CelValue::CreateMap(&request)); activation.InsertValue(Response, CelValue::CreateMap(&response)); activation.InsertValue(Metadata, CelValue::CreateMessage(&info.dynamicMetadata(), arena)); activation.InsertValue(Connection, CelValue::CreateMap(&connection)); + activation.InsertValue(Upstream, CelValue::CreateMap(&upstream)); activation.InsertValue(Source, CelValue::CreateMap(&source)); activation.InsertValue(Destination, CelValue::CreateMap(&destination)); diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.cc b/source/extensions/filters/common/ext_authz/check_request_utils.cc index d243e04c59..b29c397084 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.cc +++ b/source/extensions/filters/common/ext_authz/check_request_utils.cc @@ -37,24 +37,33 @@ void CheckRequestUtils::setAttrContextPeer(envoy::service::auth::v2::AttributeCo Envoy::Network::Utility::addressToProtobufAddress(*connection.remoteAddress(), *addr); } - // Set the principal - // Preferably the SAN from the peer's cert or - // Subject from the peer's cert. - Ssl::ConnectionInfo* ssl = const_cast(connection.ssl()); + // Set the principal. Preferably the URI SAN, DNS SAN or Subject in that order from the peer's + // cert. + auto ssl = connection.ssl(); if (ssl != nullptr) { if (local) { - const auto uriSans = ssl->uriSanLocalCertificate(); - if (uriSans.empty()) { - peer.set_principal(ssl->subjectLocalCertificate()); + const auto uri_sans = ssl->uriSanLocalCertificate(); + if (uri_sans.empty()) { + const auto dns_sans = ssl->dnsSansLocalCertificate(); + if (dns_sans.empty()) { + peer.set_principal(ssl->subjectLocalCertificate()); + } else { + peer.set_principal(dns_sans[0]); + } } else { - peer.set_principal(uriSans[0]); + peer.set_principal(uri_sans[0]); } } else { - const auto uriSans = ssl->uriSanPeerCertificate(); - if (uriSans.empty()) { - peer.set_principal(ssl->subjectPeerCertificate()); + const auto uri_sans = ssl->uriSanPeerCertificate(); + if (uri_sans.empty()) { + const auto dns_sans = ssl->dnsSansPeerCertificate(); + if (dns_sans.empty()) { + peer.set_principal(ssl->subjectPeerCertificate()); + } else { + peer.set_principal(dns_sans[0]); + } } else { - peer.set_principal(uriSans[0]); + peer.set_principal(uri_sans[0]); } } } @@ -143,6 +152,7 @@ void CheckRequestUtils::createHttpCheck( const Envoy::Http::StreamDecoderFilterCallbacks* callbacks, const Envoy::Http::HeaderMap& headers, Protobuf::Map&& context_extensions, + envoy::api::v2::core::Metadata&& metadata_context, envoy::service::auth::v2::CheckRequest& request, uint64_t max_request_bytes) { auto attrs = request.mutable_attributes(); @@ -158,6 +168,7 @@ void CheckRequestUtils::createHttpCheck( // Fill in the context extensions: (*attrs->mutable_context_extensions()) = std::move(context_extensions); + (*attrs->mutable_metadata_context()) = std::move(metadata_context); } void CheckRequestUtils::createTcpCheck(const Network::ReadFilterCallbacks* callbacks, diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.h b/source/extensions/filters/common/ext_authz/check_request_utils.h index 5fa997c80a..6f90d8d86b 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.h +++ b/source/extensions/filters/common/ext_authz/check_request_utils.h @@ -48,6 +48,7 @@ class CheckRequestUtils { static void createHttpCheck(const Envoy::Http::StreamDecoderFilterCallbacks* callbacks, const Envoy::Http::HeaderMap& headers, Protobuf::Map&& context_extensions, + envoy::api::v2::core::Metadata&& metadata_context, envoy::service::auth::v2::CheckRequest& request, uint64_t max_request_bytes); diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc index aff72f7a53..b49960bf51 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.cc @@ -50,18 +50,28 @@ struct SuccessResponse { const MatcherSharedPtr& matchers_; ResponsePtr response_; }; + +std::vector +createLowerCaseMatchers(const envoy::type::matcher::ListStringMatcher& list) { + std::vector matchers; + for (const auto& matcher : list.patterns()) { + matchers.push_back(std::make_unique(matcher)); + } + return matchers; +} + } // namespace // Matchers -HeaderKeyMatcher::HeaderKeyMatcher(std::vector&& list) +HeaderKeyMatcher::HeaderKeyMatcher(std::vector&& list) : matchers_(std::move(list)) {} bool HeaderKeyMatcher::matches(absl::string_view key) const { return std::any_of(matchers_.begin(), matchers_.end(), - [&key](auto matcher) { return matcher.match(key); }); + [&key](auto& matcher) { return matcher->match(key); }); } -NotHeaderKeyMatcher::NotHeaderKeyMatcher(std::vector&& list) +NotHeaderKeyMatcher::NotHeaderKeyMatcher(std::vector&& list) : matcher_(std::move(list)) {} bool NotHeaderKeyMatcher::matches(absl::string_view key) const { return !matcher_.matches(key); } @@ -86,12 +96,11 @@ ClientConfig::toRequestMatchers(const envoy::type::matcher::ListStringMatcher& l {Http::Headers::get().Authorization, Http::Headers::get().Method, Http::Headers::get().Path, Http::Headers::get().Host}}; - std::vector matchers{list.patterns().begin(), - list.patterns().end()}; + std::vector matchers(createLowerCaseMatchers(list)); for (const auto& key : keys) { envoy::type::matcher::StringMatcher matcher; matcher.set_exact(key.get()); - matchers.push_back(matcher); + matchers.push_back(std::make_unique(matcher)); } return std::make_shared(std::move(matchers)); @@ -99,15 +108,14 @@ ClientConfig::toRequestMatchers(const envoy::type::matcher::ListStringMatcher& l MatcherSharedPtr ClientConfig::toClientMatchers(const envoy::type::matcher::ListStringMatcher& list) { - std::vector matchers{list.patterns().begin(), - list.patterns().end()}; + std::vector matchers(createLowerCaseMatchers(list)); // If list is empty, all authorization response headers, except Host, should be added to // the client response. if (matchers.empty()) { envoy::type::matcher::StringMatcher matcher; matcher.set_exact(Http::Headers::get().Host.get()); - matchers.push_back(matcher); + matchers.push_back(std::make_unique(matcher)); return std::make_shared(std::move(matchers)); } @@ -121,7 +129,7 @@ ClientConfig::toClientMatchers(const envoy::type::matcher::ListStringMatcher& li for (const auto& key : keys) { envoy::type::matcher::StringMatcher matcher; matcher.set_exact(key.get()); - matchers.push_back(matcher); + matchers.push_back(std::make_unique(matcher)); } return std::make_shared(std::move(matchers)); @@ -129,9 +137,7 @@ ClientConfig::toClientMatchers(const envoy::type::matcher::ListStringMatcher& li MatcherSharedPtr ClientConfig::toUpstreamMatchers(const envoy::type::matcher::ListStringMatcher& list) { - std::vector matchers{list.patterns().begin(), - list.patterns().end()}; - return std::make_unique(std::move(matchers)); + return std::make_unique(createLowerCaseMatchers(list)); } Http::LowerCaseStrPairVector ClientConfig::toHeadersAdd( @@ -196,9 +202,20 @@ void RawHttpClientImpl::check(RequestCallbacks& callbacks, std::make_unique(request.attributes().request().http().body()); } - request_ = cm_.httpAsyncClientForCluster(config_->cluster()) - .send(std::move(message), *this, - Http::AsyncClient::RequestOptions().setTimeout(config_->timeout())); + const std::string& cluster = config_->cluster(); + + // It's possible that the cluster specified in the filter configuration no longer exists due to a + // CDS removal. + if (cm_.get(cluster) == nullptr) { + // TODO(dio): Add stats and tracing related to this. + ENVOY_LOG(debug, "ext_authz cluster '{}' does not exist", cluster); + callbacks_->onComplete(std::make_unique(errorResponse())); + callbacks_ = nullptr; + } else { + request_ = cm_.httpAsyncClientForCluster(cluster).send( + std::move(message), *this, + Http::AsyncClient::RequestOptions().setTimeout(config_->timeout())); + } } void RawHttpClientImpl::onSuccess(Http::MessagePtr&& message) { diff --git a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h index a26d5e4532..6d4c57128a 100644 --- a/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h +++ b/source/extensions/filters/common/ext_authz/ext_authz_http_impl.h @@ -34,17 +34,17 @@ class Matcher { class HeaderKeyMatcher : public Matcher { public: - HeaderKeyMatcher(std::vector&& list); + HeaderKeyMatcher(std::vector&& list); bool matches(absl::string_view key) const override; private: - const std::vector matchers_; + const std::vector matchers_; }; class NotHeaderKeyMatcher : public Matcher { public: - NotHeaderKeyMatcher(std::vector&& list); + NotHeaderKeyMatcher(std::vector&& list); bool matches(absl::string_view key) const override; diff --git a/source/extensions/filters/common/lua/wrappers.h b/source/extensions/filters/common/lua/wrappers.h index 5ac8ff217c..3cdff2298d 100644 --- a/source/extensions/filters/common/lua/wrappers.h +++ b/source/extensions/filters/common/lua/wrappers.h @@ -106,7 +106,7 @@ class MetadataMapWrapper : public BaseLuaObject { */ class SslConnectionWrapper : public BaseLuaObject { public: - SslConnectionWrapper(const Ssl::ConnectionInfo*) {} + SslConnectionWrapper(const Ssl::ConnectionInfoConstSharedPtr) {} static ExportedFunctions exportedFunctions() { return {}; } // TODO(dio): Add more Lua APIs around Ssl::Connection. diff --git a/source/extensions/filters/common/rbac/matchers.cc b/source/extensions/filters/common/rbac/matchers.cc index d2742e2e9e..271d54c295 100644 --- a/source/extensions/filters/common/rbac/matchers.cc +++ b/source/extensions/filters/common/rbac/matchers.cc @@ -133,7 +133,7 @@ bool PortMatcher::matches(const Network::Connection& connection, const Envoy::Ht bool AuthenticatedMatcher::matches(const Network::Connection& connection, const Envoy::Http::HeaderMap&, const StreamInfo::StreamInfo&) const { - const auto* ssl = connection.ssl(); + const auto& ssl = connection.ssl(); if (!ssl) { // connection was not authenticated return false; } else if (!matcher_.has_value()) { // matcher allows any subject diff --git a/source/extensions/filters/common/rbac/matchers.h b/source/extensions/filters/common/rbac/matchers.h index 98f369d49e..8b2ef2bf10 100644 --- a/source/extensions/filters/common/rbac/matchers.h +++ b/source/extensions/filters/common/rbac/matchers.h @@ -166,14 +166,14 @@ class AuthenticatedMatcher : public Matcher { public: AuthenticatedMatcher(const envoy::config::rbac::v2::Principal_Authenticated& auth) : matcher_(auth.has_principal_name() - ? absl::make_optional(auth.principal_name()) + ? absl::make_optional(auth.principal_name()) : absl::nullopt) {} bool matches(const Network::Connection& connection, const Envoy::Http::HeaderMap& headers, const StreamInfo::StreamInfo&) const override; private: - const absl::optional matcher_; + const absl::optional matcher_; }; /** @@ -217,10 +217,10 @@ class MetadataMatcher : public Matcher { * Perform a match against the request server from the client's connection * request. This is typically TLS SNI. */ -class RequestedServerNameMatcher : public Matcher, Envoy::Matchers::StringMatcher { +class RequestedServerNameMatcher : public Matcher, Envoy::Matchers::StringMatcherImpl { public: RequestedServerNameMatcher(const envoy::type::matcher::StringMatcher& requested_server_name) - : Envoy::Matchers::StringMatcher(requested_server_name) {} + : Envoy::Matchers::StringMatcherImpl(requested_server_name) {} bool matches(const Network::Connection& connection, const Envoy::Http::HeaderMap& headers, const StreamInfo::StreamInfo&) const override; diff --git a/source/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.cc b/source/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.cc index 1ec4dd8247..076ff9c57b 100644 --- a/source/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.cc +++ b/source/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.cc @@ -32,13 +32,35 @@ Http::FilterHeadersStatus AdaptiveConcurrencyFilter::decodeHeaders(Http::HeaderM return Http::FilterHeadersStatus::StopIteration; } - rq_start_time_ = config_->timeSource().monotonicTime(); + // When the deferred_sample_task_ object is destroyed, the time difference between its destruction + // and the request start time is measured as the request latency. This value is sampled by the + // concurrency controller either when encoding is complete or during destruction of this filter + // object. + deferred_sample_task_ = + std::make_unique([this, rq_start_time = config_->timeSource().monotonicTime()]() { + const auto now = config_->timeSource().monotonicTime(); + const std::chrono::nanoseconds rq_latency = now - rq_start_time; + controller_->recordLatencySample(rq_latency); + }); + return Http::FilterHeadersStatus::Continue; } void AdaptiveConcurrencyFilter::encodeComplete() { - const auto rq_latency = config_->timeSource().monotonicTime() - rq_start_time_; - controller_->recordLatencySample(rq_latency); + ASSERT(deferred_sample_task_); + deferred_sample_task_.reset(); +} + +void AdaptiveConcurrencyFilter::onDestroy() { + if (deferred_sample_task_) { + // The sampling task hasn't been destroyed yet, so this implies we did not complete encoding. + // Let's stop the sampling from happening and perform request cleanup inside the controller. + // + // TODO (tonya11en): Return some RAII handle from the concurrency controller that performs this + // logic as part of its lifecycle. + deferred_sample_task_->cancel(); + controller_->cancelLatencySample(); + } } } // namespace AdaptiveConcurrency diff --git a/source/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.h b/source/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.h index 8807018027..0ebf7479b0 100644 --- a/source/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.h +++ b/source/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.h @@ -11,6 +11,8 @@ #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" +#include "common/common/cleanup.h" + #include "extensions/filters/http/adaptive_concurrency/concurrency_controller/concurrency_controller.h" #include "extensions/filters/http/common/pass_through_filter.h" @@ -57,12 +59,12 @@ class AdaptiveConcurrencyFilter : public Http::PassThroughFilter, // Http::StreamEncoderFilter void encodeComplete() override; + void onDestroy() override; private: AdaptiveConcurrencyFilterConfigSharedPtr config_; const ConcurrencyControllerSharedPtr controller_; - MonotonicTime rq_start_time_; - std::unique_ptr forwarding_action_; + std::unique_ptr deferred_sample_task_; }; } // namespace AdaptiveConcurrency diff --git a/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/BUILD b/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/BUILD index d213690d63..604221865c 100644 --- a/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/BUILD +++ b/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/BUILD @@ -14,10 +14,20 @@ envoy_package() envoy_cc_library( name = "concurrency_controller_lib", - srcs = [], + srcs = ["gradient_controller.cc"], hdrs = [ "concurrency_controller.h", + "gradient_controller.h", + ], + external_deps = [ + "libcircllhist", ], deps = [ + "//source/common/event:dispatcher_lib", + "//source/common/protobuf", + "//source/common/runtime:runtime_lib", + "//source/common/stats:isolated_store_lib", + "//source/common/stats:stats_lib", + "@envoy_api//envoy/config/filter/http/adaptive_concurrency/v2alpha:adaptive_concurrency_cc", ], ) diff --git a/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/concurrency_controller.h b/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/concurrency_controller.h index 0c0dbe456c..20342c0bd6 100644 --- a/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/concurrency_controller.h +++ b/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/concurrency_controller.h @@ -43,7 +43,18 @@ class ConcurrencyController { * * @param rq_latency is the clocked round-trip time for the request. */ - virtual void recordLatencySample(const std::chrono::nanoseconds& rq_latency) PURE; + virtual void recordLatencySample(std::chrono::nanoseconds rq_latency) PURE; + + /** + * Omit sampling an outstanding request and update the internal state of the controller to reflect + * request completion. + */ + virtual void cancelLatencySample() PURE; + + /** + * Returns the current concurrency limit. + */ + virtual uint32_t concurrencyLimit() const PURE; }; } // namespace ConcurrencyController diff --git a/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.cc b/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.cc new file mode 100644 index 0000000000..3391c55fb6 --- /dev/null +++ b/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.cc @@ -0,0 +1,186 @@ +#include "extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.h" + +#include +#include + +#include "envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.pb.h" +#include "envoy/event/dispatcher.h" +#include "envoy/runtime/runtime.h" +#include "envoy/stats/stats.h" + +#include "common/common/cleanup.h" +#include "common/protobuf/protobuf.h" +#include "common/protobuf/utility.h" + +#include "extensions/filters/http/adaptive_concurrency/concurrency_controller/concurrency_controller.h" + +#include "absl/synchronization/mutex.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace AdaptiveConcurrency { +namespace ConcurrencyController { + +GradientControllerConfig::GradientControllerConfig( + const envoy::config::filter::http::adaptive_concurrency::v2alpha::GradientControllerConfig& + proto_config) + : min_rtt_calc_interval_(std::chrono::milliseconds( + DurationUtil::durationToMilliseconds(proto_config.min_rtt_calc_params().interval()))), + sample_rtt_calc_interval_(std::chrono::milliseconds(DurationUtil::durationToMilliseconds( + proto_config.concurrency_limit_params().concurrency_update_interval()))), + max_concurrency_limit_(PROTOBUF_GET_WRAPPED_OR_DEFAULT( + proto_config.concurrency_limit_params(), max_concurrency_limit, 1000)), + min_rtt_aggregate_request_count_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(proto_config.min_rtt_calc_params(), request_count, 50)), + max_gradient_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(proto_config.concurrency_limit_params(), + max_gradient, 2.0)), + sample_aggregate_percentile_( + PROTOBUF_PERCENT_TO_DOUBLE_OR_DEFAULT(proto_config, sample_aggregate_percentile, 50) / + 100.0) {} + +GradientController::GradientController(GradientControllerConfigSharedPtr config, + Event::Dispatcher& dispatcher, Runtime::Loader&, + const std::string& stats_prefix, Stats::Scope& scope) + : config_(std::move(config)), dispatcher_(dispatcher), scope_(scope), + stats_(generateStats(scope_, stats_prefix)), deferred_limit_value_(1), num_rq_outstanding_(0), + concurrency_limit_(1), latency_sample_hist_(hist_fast_alloc(), hist_free) { + min_rtt_calc_timer_ = dispatcher_.createTimer([this]() -> void { enterMinRTTSamplingWindow(); }); + + sample_reset_timer_ = dispatcher_.createTimer([this]() -> void { + if (inMinRTTSamplingWindow()) { + // The minRTT sampling window started since the sample reset timer was enabled last. Since the + // minRTT value is being calculated, let's give up on this timer to avoid blocking the + // dispatcher thread and rely on it being enabled again as part of the minRTT calculation. + return; + } + + { + absl::MutexLock ml(&sample_mutation_mtx_); + resetSampleWindow(); + } + + sample_reset_timer_->enableTimer(config_->sampleRTTCalcInterval()); + }); + + sample_reset_timer_->enableTimer(config_->sampleRTTCalcInterval()); + stats_.concurrency_limit_.set(concurrency_limit_.load()); +} + +GradientControllerStats GradientController::generateStats(Stats::Scope& scope, + const std::string& stats_prefix) { + return {ALL_GRADIENT_CONTROLLER_STATS(POOL_GAUGE_PREFIX(scope, stats_prefix))}; +} + +void GradientController::enterMinRTTSamplingWindow() { + absl::MutexLock ml(&sample_mutation_mtx_); + + // Set the minRTT flag to indicate we're gathering samples to update the value. This will + // prevent the sample window from resetting until enough requests are gathered to complete the + // recalculation. + deferred_limit_value_.store(concurrencyLimit()); + updateConcurrencyLimit(1); + + // Throw away any latency samples from before the recalculation window as it may not represent + // the minRTT. + hist_clear(latency_sample_hist_.get()); +} + +void GradientController::updateMinRTT() { + ASSERT(inMinRTTSamplingWindow()); + + { + absl::MutexLock ml(&sample_mutation_mtx_); + min_rtt_ = processLatencySamplesAndClear(); + stats_.min_rtt_msecs_.set( + std::chrono::duration_cast(min_rtt_).count()); + updateConcurrencyLimit(deferred_limit_value_.load()); + deferred_limit_value_.store(0); + } + + min_rtt_calc_timer_->enableTimer(config_->minRTTCalcInterval()); +} + +void GradientController::resetSampleWindow() { + // The sampling window must not be reset while sampling for the new minRTT value. + ASSERT(!inMinRTTSamplingWindow()); + + if (hist_sample_count(latency_sample_hist_.get()) == 0) { + return; + } + + sample_rtt_ = processLatencySamplesAndClear(); + updateConcurrencyLimit(calculateNewLimit()); +} + +std::chrono::microseconds GradientController::processLatencySamplesAndClear() { + const std::array quantile{config_->sampleAggregatePercentile()}; + std::array calculated_quantile; + hist_approx_quantile(latency_sample_hist_.get(), quantile.data(), 1, calculated_quantile.data()); + hist_clear(latency_sample_hist_.get()); + return std::chrono::microseconds(static_cast(calculated_quantile[0])); +} + +uint32_t GradientController::calculateNewLimit() { + // Calculate the gradient value, ensuring it remains below the configured maximum. + ASSERT(sample_rtt_.count() > 0); + const double raw_gradient = static_cast(min_rtt_.count()) / sample_rtt_.count(); + const double gradient = std::min(config_->maxGradient(), raw_gradient); + stats_.gradient_.set(gradient); + + const double limit = concurrencyLimit() * gradient; + const double burst_headroom = sqrt(limit); + stats_.burst_queue_size_.set(burst_headroom); + + // The final concurrency value factors in the burst headroom and must be clamped to keep the value + // in the range [1, configured_max]. + const auto clamp = [](int min, int max, int val) { return std::max(min, std::min(max, val)); }; + const uint32_t new_limit = limit + burst_headroom; + return clamp(1, config_->maxConcurrencyLimit(), new_limit); +} + +RequestForwardingAction GradientController::forwardingDecision() { + // Note that a race condition exists here which would allow more outstanding requests than the + // concurrency limit bounded by the number of worker threads. After loading num_rq_outstanding_ + // and before loading concurrency_limit_, another thread could potentially swoop in and modify + // num_rq_outstanding_, causing us to move forward with stale values and increment + // num_rq_outstanding_. + // + // TODO (tonya11en): Reconsider using a CAS loop here. + if (num_rq_outstanding_.load() < concurrencyLimit()) { + ++num_rq_outstanding_; + return RequestForwardingAction::Forward; + } + return RequestForwardingAction::Block; +} + +void GradientController::recordLatencySample(std::chrono::nanoseconds rq_latency) { + const uint32_t latency_usec = + std::chrono::duration_cast(rq_latency).count(); + ASSERT(num_rq_outstanding_.load() > 0); + --num_rq_outstanding_; + + uint32_t sample_count; + { + absl::MutexLock ml(&sample_mutation_mtx_); + hist_insert(latency_sample_hist_.get(), latency_usec, 1); + sample_count = hist_sample_count(latency_sample_hist_.get()); + } + + if (inMinRTTSamplingWindow() && sample_count >= config_->minRTTAggregateRequestCount()) { + // This sample has pushed the request count over the request count requirement for the minRTT + // recalculation. It must now be finished. + updateMinRTT(); + } +} + +void GradientController::cancelLatencySample() { + ASSERT(num_rq_outstanding_.load() > 0); + --num_rq_outstanding_; +} + +} // namespace ConcurrencyController +} // namespace AdaptiveConcurrency +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.h b/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.h new file mode 100644 index 0000000000..a7e27f3114 --- /dev/null +++ b/source/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.h @@ -0,0 +1,205 @@ +#pragma once + +#include +#include + +#include "envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.pb.h" +#include "envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.pb.validate.h" +#include "envoy/event/dispatcher.h" +#include "envoy/runtime/runtime.h" +#include "envoy/stats/stats_macros.h" + +#include "extensions/filters/http/adaptive_concurrency/concurrency_controller/concurrency_controller.h" + +#include "absl/base/thread_annotations.h" +#include "absl/synchronization/mutex.h" +#include "circllhist.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace AdaptiveConcurrency { +namespace ConcurrencyController { + +/** + * All stats for the gradient controller. + */ +#define ALL_GRADIENT_CONTROLLER_STATS(GAUGE) \ + GAUGE(concurrency_limit, NeverImport) \ + GAUGE(gradient, NeverImport) \ + GAUGE(burst_queue_size, NeverImport) \ + GAUGE(min_rtt_msecs, NeverImport) + +/** + * Wrapper struct for gradient controller stats. @see stats_macros.h + */ +struct GradientControllerStats { + ALL_GRADIENT_CONTROLLER_STATS(GENERATE_GAUGE_STRUCT) +}; + +class GradientControllerConfig { +public: + GradientControllerConfig( + const envoy::config::filter::http::adaptive_concurrency::v2alpha::GradientControllerConfig& + proto_config); + + std::chrono::milliseconds minRTTCalcInterval() const { return min_rtt_calc_interval_; } + std::chrono::milliseconds sampleRTTCalcInterval() const { return sample_rtt_calc_interval_; } + uint32_t maxConcurrencyLimit() const { return max_concurrency_limit_; } + uint32_t minRTTAggregateRequestCount() const { return min_rtt_aggregate_request_count_; } + double maxGradient() const { return max_gradient_; } + double sampleAggregatePercentile() const { return sample_aggregate_percentile_; } + +private: + // The measured request round-trip time under ideal conditions. + const std::chrono::milliseconds min_rtt_calc_interval_; + + // The measured sample round-trip time from the previous time window. + const std::chrono::milliseconds sample_rtt_calc_interval_; + + // The maximum allowed concurrency value. + const uint32_t max_concurrency_limit_; + + // The number of requests to aggregate/sample during the minRTT recalculation. + const uint32_t min_rtt_aggregate_request_count_; + + // The maximum value the gradient may take. + const double max_gradient_; + + // The percentile value considered when processing samples. + const double sample_aggregate_percentile_; +}; +using GradientControllerConfigSharedPtr = std::shared_ptr; + +/** + * A concurrency controller that implements a variation of the Gradient algorithm described in: + * + * https://medium.com/@NetflixTechBlog/performance-under-load-3e6fa9a60581 + * + * This is used to control the allowed request concurrency limit in the adaptive concurrency control + * filter. + * + * The algorithm: + * ============== + * An ideal round-trip time (minRTT) is measured periodically by only allowing a single outstanding + * request at a time and measuring the round-trip time to the upstream. This information is then + * used in the calculation of a number called the gradient, using time-sampled latencies + * (sampleRTT): + * + * gradient = minRTT / sampleRTT + * + * This gradient value has a useful property, such that it decreases as the sampled latencies + * increase. The value is then used to periodically update the concurrency limit via: + * + * limit = old_limit * gradient + * new_limit = limit + headroom + * + * The headroom value allows for request bursts and is also the driving factor behind increasing the + * concurrency limit when the sampleRTT is in the same ballpark as the minRTT. This value must be + * present in the calculation, since it forces the concurrency limit to increase until there is a + * deviation from the minRTT latency. In its absence, the concurrency limit could remain stagnant at + * an unnecessarily small value if sampleRTT ~= minRTT. Therefore, the headroom value is + * unconfigurable and is set to the square-root of the new limit. + * + * Sampling: + * ========= + * The controller makes use of latency samples to either determine the minRTT or the sampleRTT which + * is used to periodically update the concurrency limit. Each calculation occurs at separate + * configurable frequencies and they may not occur at the same time. To prevent this, there exists a + * concept of mutually exclusive sampling windows. + * + * When the gradient controller is instantiated, it starts inside of a minRTT calculation window + * (indicated by inMinRTTSamplingWindow() returning true) and the concurrency limit is pinned to 1. + * This window lasts until the configured number of requests is received, the minRTT value is + * updated, and the minRTT value is set by a single worker thread. To prevent sampleRTT calculations + * from triggering during this window, the update window mutex is held. Since it's necessary for a + * worker thread to know which update window update window mutex is held for, they check the state + * of inMinRTTSamplingWindow() after each sample. When the minRTT calculation is complete, a timer + * is set to trigger the next minRTT sampling window by the worker thread who updates the minRTT + * value. + * + * If the controller is not in a minRTT sampling window, it's possible that the controller is in a + * sampleRTT calculation window. In this, all of the latency samples are consolidated into a + * configurable quantile value to represent the measured latencies. This quantile value sets + * sampleRTT and the concurrency limit is updated as described in the algorithm section above. + * + * When not in a sampling window, the controller is simply servicing the adaptive concurrency filter + * via the public functions. + * + * Locking: + * ======== + * There are 2 mutually exclusive calculation windows, so the sample mutation mutex is held to + * prevent the overlap of these windows. It is necessary for a worker thread to know specifically if + * the controller is inside of a minRTT recalculation window during the recording of a latency + * sample, so this extra bit of information is stored in inMinRTTSamplingWindow(). + */ +class GradientController : public ConcurrencyController { +public: + GradientController(GradientControllerConfigSharedPtr config, Event::Dispatcher& dispatcher, + Runtime::Loader& runtime, const std::string& stats_prefix, + Stats::Scope& scope); + + // ConcurrencyController. + RequestForwardingAction forwardingDecision() override; + void recordLatencySample(std::chrono::nanoseconds rq_latency) override; + void cancelLatencySample() override; + uint32_t concurrencyLimit() const override { return concurrency_limit_.load(); } + +private: + static GradientControllerStats generateStats(Stats::Scope& scope, + const std::string& stats_prefix); + void updateMinRTT(); + std::chrono::microseconds processLatencySamplesAndClear() + ABSL_EXCLUSIVE_LOCKS_REQUIRED(sample_mutation_mtx_); + uint32_t calculateNewLimit() ABSL_EXCLUSIVE_LOCKS_REQUIRED(sample_mutation_mtx_); + void enterMinRTTSamplingWindow(); + bool inMinRTTSamplingWindow() const { return deferred_limit_value_.load() > 0; } + void resetSampleWindow() ABSL_EXCLUSIVE_LOCKS_REQUIRED(sample_mutation_mtx_); + void updateConcurrencyLimit(const uint32_t new_limit) { + concurrency_limit_.store(new_limit); + stats_.concurrency_limit_.set(concurrency_limit_.load()); + } + + const GradientControllerConfigSharedPtr config_; + Event::Dispatcher& dispatcher_; + Stats::Scope& scope_; + GradientControllerStats stats_; + + // Protects data related to latency sampling and RTT values. In addition to protecting the latency + // sample histogram, the mutex ensures that the minRTT calculation window and the sample window + // (where the new concurrency limit is determined) do not overlap. + absl::Mutex sample_mutation_mtx_; + + // Stores the value of the concurrency limit prior to entering the minRTT update window. If this + // is non-zero, then we are actively in the minRTT sampling window. + std::atomic deferred_limit_value_; + + // Stores the expected upstream latency value under ideal conditions. This is the numerator in the + // gradient value explained above. + std::chrono::nanoseconds min_rtt_; + std::chrono::nanoseconds sample_rtt_ ABSL_GUARDED_BY(sample_mutation_mtx_); + + // Tracks the count of requests that have been forwarded whose replies have + // not been sampled yet. Atomicity is required because this variable is used to make the + // forwarding decision without locking. + std::atomic num_rq_outstanding_; + + // Stores the current concurrency limit. Atomicity is required because this variable is used to + // make the forwarding decision without locking. + std::atomic concurrency_limit_; + + // Stores all sampled latencies and provides percentile estimations when using the sampled data to + // calculate a new concurrency limit. + std::unique_ptr + latency_sample_hist_ ABSL_GUARDED_BY(sample_mutation_mtx_); + + Event::TimerPtr min_rtt_calc_timer_; + Event::TimerPtr sample_reset_timer_; +}; +using GradientControllerSharedPtr = std::shared_ptr; + +} // namespace ConcurrencyController +} // namespace AdaptiveConcurrency +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/http/common/factory_base.h b/source/extensions/filters/http/common/factory_base.h index 8e3a966917..73c91d4d1b 100644 --- a/source/extensions/filters/http/common/factory_base.h +++ b/source/extensions/filters/http/common/factory_base.h @@ -25,8 +25,9 @@ class FactoryBase : public Server::Configuration::NamedHttpFilterConfigFactory { createFilterFactoryFromProto(const Protobuf::Message& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override { - return createFilterFactoryFromProtoTyped( - MessageUtil::downcastAndValidate(proto_config), stats_prefix, context); + return createFilterFactoryFromProtoTyped(MessageUtil::downcastAndValidate( + proto_config, context.messageValidationVisitor()), + stats_prefix, context); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { @@ -41,7 +42,9 @@ class FactoryBase : public Server::Configuration::NamedHttpFilterConfigFactory { createRouteSpecificFilterConfig(const Protobuf::Message& proto_config, Server::Configuration::FactoryContext& context) override { return createRouteSpecificFilterConfigTyped( - MessageUtil::downcastAndValidate(proto_config), context); + MessageUtil::downcastAndValidate( + proto_config, context.messageValidationVisitor()), + context); } std::string name() override { return name_; } diff --git a/source/extensions/filters/http/cors/cors_filter.cc b/source/extensions/filters/http/cors/cors_filter.cc index 997beb5385..2bd26c8b84 100644 --- a/source/extensions/filters/http/cors/cors_filter.cc +++ b/source/extensions/filters/http/cors/cors_filter.cc @@ -115,35 +115,19 @@ void CorsFilter::setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& c } bool CorsFilter::isOriginAllowed(const Http::HeaderString& origin) { - return isOriginAllowedString(origin) || isOriginAllowedRegex(origin); -} - -bool CorsFilter::isOriginAllowedString(const Http::HeaderString& origin) { - if (allowOrigins() == nullptr) { - return false; - } - for (const auto& o : *allowOrigins()) { - if (o == "*" || origin == o.c_str()) { - return true; - } - } - return false; -} - -bool CorsFilter::isOriginAllowedRegex(const Http::HeaderString& origin) { - if (allowOriginRegexes() == nullptr) { + const auto allow_origins = allowOrigins(); + if (allow_origins == nullptr) { return false; } - for (const auto& regex : *allowOriginRegexes()) { - const absl::string_view origin_view = origin.getStringView(); - if (std::regex_match(origin_view.begin(), origin_view.end(), regex)) { + for (const auto& allow_origin : *allow_origins) { + if (allow_origin->match("*") || allow_origin->match(origin.getStringView())) { return true; } } return false; } -const std::list* CorsFilter::allowOrigins() { +const std::vector* CorsFilter::allowOrigins() { for (const auto policy : policies_) { if (policy && !policy->allowOrigins().empty()) { return &policy->allowOrigins(); @@ -152,15 +136,6 @@ const std::list* CorsFilter::allowOrigins() { return nullptr; } -const std::list* CorsFilter::allowOriginRegexes() { - for (const auto policy : policies_) { - if (policy && !policy->allowOriginRegexes().empty()) { - return &policy->allowOriginRegexes(); - } - } - return nullptr; -} - const std::string& CorsFilter::allowMethods() { for (const auto policy : policies_) { if (policy && !policy->allowMethods().empty()) { diff --git a/source/extensions/filters/http/cors/cors_filter.h b/source/extensions/filters/http/cors/cors_filter.h index b70b382a21..51b13efeeb 100644 --- a/source/extensions/filters/http/cors/cors_filter.h +++ b/source/extensions/filters/http/cors/cors_filter.h @@ -82,8 +82,7 @@ class CorsFilter : public Http::StreamFilter { private: friend class CorsFilterTest; - const std::list* allowOrigins(); - const std::list* allowOriginRegexes(); + const std::vector* allowOrigins(); const std::string& allowMethods(); const std::string& allowHeaders(); const std::string& exposeHeaders(); @@ -92,8 +91,6 @@ class CorsFilter : public Http::StreamFilter { bool shadowEnabled(); bool enabled(); bool isOriginAllowed(const Http::HeaderString& origin); - bool isOriginAllowedString(const Http::HeaderString& origin); - bool isOriginAllowedRegex(const Http::HeaderString& origin); Http::StreamDecoderFilterCallbacks* decoder_callbacks_{}; Http::StreamEncoderFilterCallbacks* encoder_callbacks_{}; diff --git a/source/extensions/filters/http/csrf/csrf_filter.cc b/source/extensions/filters/http/csrf/csrf_filter.cc index 85edec966c..ee8db9074c 100644 --- a/source/extensions/filters/http/csrf/csrf_filter.cc +++ b/source/extensions/filters/http/csrf/csrf_filter.cc @@ -59,10 +59,9 @@ static CsrfStats generateStats(const std::string& prefix, Stats::Scope& scope) { return CsrfStats{ALL_CSRF_STATS(POOL_COUNTER_PREFIX(scope, final_prefix))}; } -static const CsrfPolicy -generatePolicy(const envoy::config::filter::http::csrf::v2::CsrfPolicy& policy, - Runtime::Loader& runtime) { - return CsrfPolicy(policy, runtime); +static CsrfPolicyPtr generatePolicy(const envoy::config::filter::http::csrf::v2::CsrfPolicy& policy, + Runtime::Loader& runtime) { + return std::make_unique(policy, runtime); } } // namespace @@ -128,7 +127,7 @@ bool CsrfFilter::isValid(const absl::string_view source_origin, Http::HeaderMap& } for (const auto& additional_origin : policy_->additional_origins()) { - if (additional_origin.match(source_origin)) { + if (additional_origin->match(source_origin)) { return true; } } diff --git a/source/extensions/filters/http/csrf/csrf_filter.h b/source/extensions/filters/http/csrf/csrf_filter.h index 57def213db..0a36058f73 100644 --- a/source/extensions/filters/http/csrf/csrf_filter.h +++ b/source/extensions/filters/http/csrf/csrf_filter.h @@ -17,12 +17,10 @@ namespace Csrf { /** * All CSRF filter stats. @see stats_macros.h */ -// clang-format off -#define ALL_CSRF_STATS(COUNTER) \ - COUNTER(missing_source_origin)\ - COUNTER(request_invalid) \ - COUNTER(request_valid) \ -// clang-format on +#define ALL_CSRF_STATS(COUNTER) \ + COUNTER(missing_source_origin) \ + COUNTER(request_invalid) \ + COUNTER(request_valid) /** * Struct definition for CSRF stats. @see stats_macros.h @@ -37,9 +35,11 @@ struct CsrfStats { class CsrfPolicy : public Router::RouteSpecificFilterConfig { public: CsrfPolicy(const envoy::config::filter::http::csrf::v2::CsrfPolicy& policy, - Runtime::Loader& runtime) : policy_(policy), runtime_(runtime) { + Runtime::Loader& runtime) + : policy_(policy), runtime_(runtime) { for (const auto& additional_origin : policy.additional_origins()) { - additional_origins_.emplace_back(Matchers::StringMatcher(additional_origin)); + additional_origins_.emplace_back( + std::make_unique(additional_origin)); } } @@ -58,14 +58,16 @@ class CsrfPolicy : public Router::RouteSpecificFilterConfig { shadow_enabled.default_value()); } - const std::vector& additional_origins() const { return additional_origins_; }; + const std::vector& additional_origins() const { + return additional_origins_; + }; private: const envoy::config::filter::http::csrf::v2::CsrfPolicy policy_; - std::vector additional_origins_; + std::vector additional_origins_; Runtime::Loader& runtime_; - }; +using CsrfPolicyPtr = std::unique_ptr; /** * Configuration for the CSRF filter. @@ -73,15 +75,14 @@ class CsrfPolicy : public Router::RouteSpecificFilterConfig { class CsrfFilterConfig { public: CsrfFilterConfig(const envoy::config::filter::http::csrf::v2::CsrfPolicy& policy, - const std::string& stats_prefix, Stats::Scope& scope, - Runtime::Loader& runtime); + const std::string& stats_prefix, Stats::Scope& scope, Runtime::Loader& runtime); CsrfStats& stats() { return stats_; } - const CsrfPolicy* policy() { return &policy_; } + const CsrfPolicy* policy() { return policy_.get(); } private: CsrfStats stats_; - const CsrfPolicy policy_; + const CsrfPolicyPtr policy_; }; using CsrfFilterConfigSharedPtr = std::shared_ptr; diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 87b5660799..d7685ab1a0 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -65,9 +65,20 @@ void Filter::initiateCall(const Http::HeaderMap& headers) { if (maybe_merged_per_route_config) { context_extensions = maybe_merged_per_route_config.value().takeContextExtensions(); } + + // If metadata_context_namespaces is specified, pass matching metadata to the ext_authz service + envoy::api::v2::core::Metadata metadata_context; + const auto& request_metadata = callbacks_->streamInfo().dynamicMetadata().filter_metadata(); + for (const auto& context_key : config_->metadataContextNamespaces()) { + const auto& metadata_it = request_metadata.find(context_key); + if (metadata_it != request_metadata.end()) { + (*metadata_context.mutable_filter_metadata())[metadata_it->first] = metadata_it->second; + } + } + Filters::Common::ExtAuthz::CheckRequestUtils::createHttpCheck( - callbacks_, headers, std::move(context_extensions), check_request_, - config_->maxRequestBytes()); + callbacks_, headers, std::move(context_extensions), std::move(metadata_context), + check_request_, config_->maxRequestBytes()); ENVOY_STREAM_LOG(trace, "ext_authz filter calling authorization server", *callbacks_); state_ = State::Calling; diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 0b0528b534..60ec7ca10f 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -47,6 +47,8 @@ class FilterConfig { max_request_bytes_(config.with_request_body().max_request_bytes()), status_on_error_(toErrorCode(config.status_on_error().code())), local_info_(local_info), scope_(scope), runtime_(runtime), http_context_(http_context), pool_(scope.symbolTable()), + metadata_context_namespaces_(config.metadata_context_namespaces().begin(), + config.metadata_context_namespaces().end()), ext_authz_ok_(pool_.add("ext_authz.ok")), ext_authz_denied_(pool_.add("ext_authz.denied")), ext_authz_error_(pool_.add("ext_authz.error")), ext_authz_failure_mode_allowed_(pool_.add("ext_authz.failure_mode_allowed")) {} @@ -75,6 +77,10 @@ class FilterConfig { scope.counterFromStatName(name).inc(); } + const std::vector& metadataContextNamespaces() { + return metadata_context_namespaces_; + } + private: static Http::Code toErrorCode(uint64_t status) { const auto code = static_cast(status); @@ -93,8 +99,11 @@ class FilterConfig { Stats::Scope& scope_; Runtime::Loader& runtime_; Http::Context& http_context_; + Stats::StatNamePool pool_; + const std::vector metadata_context_namespaces_; + public: const Stats::StatName ext_authz_ok_; const Stats::StatName ext_authz_denied_; diff --git a/source/extensions/filters/http/fault/fault_filter.cc b/source/extensions/filters/http/fault/fault_filter.cc index 7d914f553c..0fb09f1c95 100644 --- a/source/extensions/filters/http/fault/fault_filter.cc +++ b/source/extensions/filters/http/fault/fault_filter.cc @@ -33,7 +33,8 @@ struct RcDetailsValues { using RcDetails = ConstSingleton; FaultSettings::FaultSettings(const envoy::config::filter::http::fault::v2::HTTPFault& fault) - : delay_percent_runtime_(PROTOBUF_GET_STRING_OR_DEFAULT(fault, delay_percent_runtime, + : fault_filter_headers_(Http::HeaderUtility::buildHeaderDataVector(fault.headers())), + delay_percent_runtime_(PROTOBUF_GET_STRING_OR_DEFAULT(fault, delay_percent_runtime, RuntimeKeys::get().DelayPercentKey)), abort_percent_runtime_(PROTOBUF_GET_STRING_OR_DEFAULT(fault, abort_percent_runtime, RuntimeKeys::get().AbortPercentKey)), @@ -57,10 +58,6 @@ FaultSettings::FaultSettings(const envoy::config::filter::http::fault::v2::HTTPF std::make_unique(fault.delay()); } - for (const Http::HeaderUtility::HeaderData& header_map : fault.headers()) { - fault_filter_headers_.push_back(header_map); - } - upstream_cluster_ = fault.upstream_cluster(); for (const auto& node : fault.downstream_nodes()) { @@ -81,7 +78,17 @@ FaultFilterConfig::FaultFilterConfig(const envoy::config::filter::http::fault::v Runtime::Loader& runtime, const std::string& stats_prefix, Stats::Scope& scope, TimeSource& time_source) : settings_(fault), runtime_(runtime), stats_(generateStats(stats_prefix, scope)), - stats_prefix_(stats_prefix), scope_(scope), time_source_(time_source) {} + scope_(scope), time_source_(time_source), stat_name_set_(scope.symbolTable()), + aborts_injected_(stat_name_set_.add("aborts_injected")), + delays_injected_(stat_name_set_.add("delays_injected")), + stats_prefix_(stat_name_set_.add(absl::StrCat(stats_prefix, "fault"))) {} + +void FaultFilterConfig::incCounter(absl::string_view downstream_cluster, + Stats::StatName stat_name) { + Stats::SymbolTable::StoragePtr storage = scope_.symbolTable().join( + {stats_prefix_, stat_name_set_.getStatName(downstream_cluster), stat_name}); + scope_.counterFromStatName(Stats::StatName(storage.get())).inc(); +} FaultFilter::FaultFilter(FaultFilterConfigSharedPtr config) : config_(config) {} @@ -148,7 +155,7 @@ Http::FilterHeadersStatus FaultFilter::decodeHeaders(Http::HeaderMap& headers, b delay_timer_ = decoder_callbacks_->dispatcher().createTimer([this]() -> void { postDelayInjection(); }); ENVOY_LOG(debug, "fault: delaying request {}ms", duration.value().count()); - delay_timer_->enableTimer(duration.value()); + delay_timer_->enableTimer(duration.value(), &decoder_callbacks_->scope()); recordDelaysInjectedStats(); decoder_callbacks_->streamInfo().setResponseFlag(StreamInfo::ResponseFlag::DelayInjected); return Http::FilterHeadersStatus::StopIteration; @@ -192,7 +199,7 @@ void FaultFilter::maybeSetupResponseRateLimit(const Http::HeaderMap& request_hea encoder_callbacks_->injectEncodedDataToFilterChain(data, end_stream); }, [this] { encoder_callbacks_->continueEncoding(); }, config_->timeSource(), - decoder_callbacks_->dispatcher()); + decoder_callbacks_->dispatcher(), decoder_callbacks_->scope()); } bool FaultFilter::faultOverflow() { @@ -282,10 +289,7 @@ uint64_t FaultFilter::abortHttpStatus() { void FaultFilter::recordDelaysInjectedStats() { // Downstream specific stats. if (!downstream_cluster_.empty()) { - const std::string stats_counter = - fmt::format("{}fault.{}.delays_injected", config_->statsPrefix(), downstream_cluster_); - - config_->scope().counter(stats_counter).inc(); + config_->incDelays(downstream_cluster_); } // General stats. All injected faults are considered a single aggregate active fault. @@ -296,10 +300,7 @@ void FaultFilter::recordDelaysInjectedStats() { void FaultFilter::recordAbortsInjectedStats() { // Downstream specific stats. if (!downstream_cluster_.empty()) { - const std::string stats_counter = - fmt::format("{}fault.{}.aborts_injected", config_->statsPrefix(), downstream_cluster_); - - config_->scope().counter(stats_counter).inc(); + config_->incAborts(downstream_cluster_); } // General stats. All injected faults are considered a single aggregate active fault. @@ -423,10 +424,10 @@ StreamRateLimiter::StreamRateLimiter(uint64_t max_kbps, uint64_t max_buffered_da std::function resume_data_cb, std::function write_data_cb, std::function continue_cb, TimeSource& time_source, - Event::Dispatcher& dispatcher) + Event::Dispatcher& dispatcher, const ScopeTrackedObject& scope) : // bytes_per_time_slice is KiB converted to bytes divided by the number of ticks per second. bytes_per_time_slice_((max_kbps * 1024) / SecondDivisor), write_data_cb_(write_data_cb), - continue_cb_(continue_cb), + continue_cb_(continue_cb), scope_(scope), // The token bucket is configured with a max token count of the number of ticks per second, // and refills at the same rate, so that we have a per second limit which refills gradually in // ~63ms intervals. @@ -472,7 +473,7 @@ void StreamRateLimiter::onTokenTimer() { const std::chrono::milliseconds ms = token_bucket_.nextTokenAvailable(); if (ms.count() > 0) { ENVOY_LOG(trace, "limiter: scheduling wakeup for {}ms", ms.count()); - token_timer_->enableTimer(ms); + token_timer_->enableTimer(ms, &scope_); } } @@ -498,7 +499,7 @@ void StreamRateLimiter::writeData(Buffer::Instance& incoming_buffer, bool end_st // The filter API does not currently support that and it will not be a trivial change to add. // Instead we cheat here by scheduling the token timer to run immediately after the stack is // unwound, at which point we can directly called encode/decodeData. - token_timer_->enableTimer(std::chrono::milliseconds(0)); + token_timer_->enableTimer(std::chrono::milliseconds(0), &scope_); } } diff --git a/source/extensions/filters/http/fault/fault_filter.h b/source/extensions/filters/http/fault/fault_filter.h index 824fecd64e..b0d3a0f1bf 100644 --- a/source/extensions/filters/http/fault/fault_filter.h +++ b/source/extensions/filters/http/fault/fault_filter.h @@ -16,6 +16,7 @@ #include "common/buffer/watermark_buffer.h" #include "common/common/token_bucket_impl.h" #include "common/http/header_utility.h" +#include "common/stats/symbol_table_impl.h" #include "extensions/filters/common/fault/fault_config.h" @@ -48,7 +49,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { public: FaultSettings(const envoy::config::filter::http::fault::v2::HTTPFault& fault); - const std::vector& filterHeaders() const { + const std::vector& filterHeaders() const { return fault_filter_headers_; } envoy::type::FractionalPercent abortPercentage() const { return abort_percentage_; } @@ -88,7 +89,7 @@ class FaultSettings : public Router::RouteSpecificFilterConfig { uint64_t http_status_{}; // HTTP or gRPC return codes Filters::Common::Fault::FaultDelayConfigPtr request_delay_config_; std::string upstream_cluster_; // restrict faults to specific upstream cluster - std::vector fault_filter_headers_; + const std::vector fault_filter_headers_; absl::flat_hash_set downstream_nodes_{}; // Inject failures for specific downstream absl::optional max_active_faults_; Filters::Common::Fault::FaultRateLimitConfigPtr response_rate_limit_; @@ -111,20 +112,31 @@ class FaultFilterConfig { Runtime::Loader& runtime() { return runtime_; } FaultFilterStats& stats() { return stats_; } - const std::string& statsPrefix() { return stats_prefix_; } Stats::Scope& scope() { return scope_; } const FaultSettings* settings() { return &settings_; } TimeSource& timeSource() { return time_source_; } + void incDelays(absl::string_view downstream_cluster) { + incCounter(downstream_cluster, delays_injected_); + } + + void incAborts(absl::string_view downstream_cluster) { + incCounter(downstream_cluster, aborts_injected_); + } + private: static FaultFilterStats generateStats(const std::string& prefix, Stats::Scope& scope); + void incCounter(absl::string_view downstream_cluster, Stats::StatName stat_name); const FaultSettings settings_; Runtime::Loader& runtime_; FaultFilterStats stats_; - const std::string stats_prefix_; Stats::Scope& scope_; TimeSource& time_source_; + Stats::StatNameSet stat_name_set_; + const Stats::StatName aborts_injected_; + const Stats::StatName delays_injected_; + const Stats::StatName stats_prefix_; // Includes ".fault". }; using FaultFilterConfigSharedPtr = std::shared_ptr; @@ -144,12 +156,13 @@ class StreamRateLimiter : Logger::Loggable { * trailers that have been paused during body flush. * @param time_source the time source to run the token bucket with. * @param dispatcher the stream's dispatcher to use for creating timers. + * @param scope the stream's scope */ StreamRateLimiter(uint64_t max_kbps, uint64_t max_buffered_data, std::function pause_data_cb, std::function resume_data_cb, std::function write_data_cb, std::function continue_cb, TimeSource& time_source, - Event::Dispatcher& dispatcher); + Event::Dispatcher& dispatcher, const ScopeTrackedObject& scope); /** * Called by the stream to write data. All data writes happen asynchronously, the stream should @@ -180,6 +193,7 @@ class StreamRateLimiter : Logger::Loggable { const uint64_t bytes_per_time_slice_; const std::function write_data_cb_; const std::function continue_cb_; + const ScopeTrackedObject& scope_; TokenBucketImpl token_bucket_; Event::TimerPtr token_timer_; bool saw_data_{}; diff --git a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc index fb26f1f39c..0e7f8caba9 100644 --- a/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc +++ b/source/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter.cc @@ -23,8 +23,6 @@ #include "grpc_transcoding/path_matcher_utility.h" #include "grpc_transcoding/response_to_json_translator.h" -using Envoy::Protobuf::DescriptorPool; -using Envoy::Protobuf::FileDescriptor; using Envoy::Protobuf::FileDescriptorSet; using Envoy::Protobuf::io::ZeroCopyInputStream; using Envoy::ProtobufUtil::Status; diff --git a/source/extensions/filters/http/health_check/config.cc b/source/extensions/filters/http/health_check/config.cc index 5db283e072..6db2fa33ef 100644 --- a/source/extensions/filters/http/health_check/config.cc +++ b/source/extensions/filters/http/health_check/config.cc @@ -21,12 +21,8 @@ Http::FilterFactoryCb HealthCheckFilterConfig::createFilterFactoryFromProtoTyped const bool pass_through_mode = proto_config.pass_through_mode().value(); const int64_t cache_time_ms = PROTOBUF_GET_MS_OR_DEFAULT(proto_config, cache_time, 0); - auto header_match_data = std::make_shared>(); - - for (const envoy::api::v2::route::HeaderMatcher& matcher : proto_config.headers()) { - Http::HeaderUtility::HeaderData single_header_match(matcher); - header_match_data->push_back(std::move(single_header_match)); - } + auto header_match_data = std::make_shared>(); + *header_match_data = Http::HeaderUtility::buildHeaderDataVector(proto_config.headers()); if (!pass_through_mode && cache_time_ms) { throw EnvoyException("cache_time_ms must not be set when path_through_mode is disabled"); diff --git a/source/extensions/filters/http/health_check/health_check.h b/source/extensions/filters/http/health_check/health_check.h index 5515e52e04..c000e41366 100644 --- a/source/extensions/filters/http/health_check/health_check.h +++ b/source/extensions/filters/http/health_check/health_check.h @@ -53,7 +53,7 @@ using ClusterMinHealthyPercentages = std::map; using ClusterMinHealthyPercentagesConstSharedPtr = std::shared_ptr; -using HeaderDataVectorSharedPtr = std::shared_ptr>; +using HeaderDataVectorSharedPtr = std::shared_ptr>; /** * Health check responder filter. diff --git a/source/extensions/filters/http/ip_tagging/BUILD b/source/extensions/filters/http/ip_tagging/BUILD index 583893eadb..bc88c13133 100644 --- a/source/extensions/filters/http/ip_tagging/BUILD +++ b/source/extensions/filters/http/ip_tagging/BUILD @@ -22,6 +22,7 @@ envoy_cc_library( "//source/common/http:header_map_lib", "//source/common/http:headers_lib", "//source/common/network:lc_trie_lib", + "//source/common/stats:symbol_table_lib", "@envoy_api//envoy/config/filter/http/ip_tagging/v2:ip_tagging_cc", ], ) diff --git a/source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc b/source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc index 1d82239c7d..d3b2549a87 100644 --- a/source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc +++ b/source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc @@ -10,6 +10,52 @@ namespace Extensions { namespace HttpFilters { namespace IpTagging { +IpTaggingFilterConfig::IpTaggingFilterConfig( + const envoy::config::filter::http::ip_tagging::v2::IPTagging& config, + const std::string& stat_prefix, Stats::Scope& scope, Runtime::Loader& runtime) + : request_type_(requestTypeEnum(config.request_type())), scope_(scope), runtime_(runtime), + stat_name_set_(scope.symbolTable()), + stats_prefix_(stat_name_set_.add(stat_prefix + "ip_tagging")), + hit_(stat_name_set_.add("hit")), no_hit_(stat_name_set_.add("no_hit")), + total_(stat_name_set_.add("total")) { + + // Once loading IP tags from a file system is supported, the restriction on the size + // of the set should be removed and observability into what tags are loaded needs + // to be implemented. + // TODO(ccaraman): Remove size check once file system support is implemented. + // Work is tracked by issue https://github.com/envoyproxy/envoy/issues/2695. + if (config.ip_tags().empty()) { + throw EnvoyException("HTTP IP Tagging Filter requires ip_tags to be specified."); + } + + std::vector>> tag_data; + tag_data.reserve(config.ip_tags().size()); + for (const auto& ip_tag : config.ip_tags()) { + std::vector cidr_set; + cidr_set.reserve(ip_tag.ip_list().size()); + for (const envoy::api::v2::core::CidrRange& entry : ip_tag.ip_list()) { + + // Currently, CidrRange::create doesn't guarantee that the CidrRanges are valid. + Network::Address::CidrRange cidr_entry = Network::Address::CidrRange::create(entry); + if (cidr_entry.isValid()) { + cidr_set.emplace_back(std::move(cidr_entry)); + } else { + throw EnvoyException( + fmt::format("invalid ip/mask combo '{}/{}' (format is /<# mask bits>)", + entry.address_prefix(), entry.prefix_len().value())); + } + } + tag_data.emplace_back(ip_tag.ip_tag_name(), cidr_set); + } + trie_ = std::make_unique>(tag_data); +} + +void IpTaggingFilterConfig::incCounter(Stats::StatName name, absl::string_view tag) { + Stats::SymbolTable::StoragePtr storage = + scope_.symbolTable().join({stats_prefix_, stat_name_set_.getStatName(tag), name}); + scope_.counterFromStatName(Stats::StatName(storage.get())).inc(); +} + IpTaggingFilter::IpTaggingFilter(IpTaggingFilterConfigSharedPtr config) : config_(config) {} IpTaggingFilter::~IpTaggingFilter() = default; @@ -42,12 +88,12 @@ Http::FilterHeadersStatus IpTaggingFilter::decodeHeaders(Http::HeaderMap& header // If there are use cases with a large set of tags, a way to opt into these stats // should be exposed and other observability options like logging tags need to be implemented. for (const std::string& tag : tags) { - config_->scope().counter(fmt::format("{}{}.hit", config_->statsPrefix(), tag)).inc(); + config_->incHit(tag); } } else { - config_->scope().counter(fmt::format("{}no_hit", config_->statsPrefix())).inc(); + config_->incNoHit(); } - config_->scope().counter(fmt::format("{}total", config_->statsPrefix())).inc(); + config_->incTotal(); return Http::FilterHeadersStatus::Continue; } diff --git a/source/extensions/filters/http/ip_tagging/ip_tagging_filter.h b/source/extensions/filters/http/ip_tagging/ip_tagging_filter.h index b79103aab9..6cf2b19b0e 100644 --- a/source/extensions/filters/http/ip_tagging/ip_tagging_filter.h +++ b/source/extensions/filters/http/ip_tagging/ip_tagging_filter.h @@ -14,6 +14,7 @@ #include "common/network/cidr_range.h" #include "common/network/lc_trie.h" +#include "common/stats/symbol_table_impl.h" namespace Envoy { namespace Extensions { @@ -32,46 +33,16 @@ class IpTaggingFilterConfig { public: IpTaggingFilterConfig(const envoy::config::filter::http::ip_tagging::v2::IPTagging& config, const std::string& stat_prefix, Stats::Scope& scope, - Runtime::Loader& runtime) - : request_type_(requestTypeEnum(config.request_type())), scope_(scope), runtime_(runtime), - stats_prefix_(stat_prefix + "ip_tagging.") { - - // Once loading IP tags from a file system is supported, the restriction on the size - // of the set should be removed and observability into what tags are loaded needs - // to be implemented. - // TODO(ccaraman): Remove size check once file system support is implemented. - // Work is tracked by issue https://github.com/envoyproxy/envoy/issues/2695. - if (config.ip_tags().empty()) { - throw EnvoyException("HTTP IP Tagging Filter requires ip_tags to be specified."); - } - - std::vector>> tag_data; - tag_data.reserve(config.ip_tags().size()); - for (const auto& ip_tag : config.ip_tags()) { - std::vector cidr_set; - cidr_set.reserve(ip_tag.ip_list().size()); - for (const envoy::api::v2::core::CidrRange& entry : ip_tag.ip_list()) { - - // Currently, CidrRange::create doesn't guarantee that the CidrRanges are valid. - Network::Address::CidrRange cidr_entry = Network::Address::CidrRange::create(entry); - if (cidr_entry.isValid()) { - cidr_set.emplace_back(std::move(cidr_entry)); - } else { - throw EnvoyException( - fmt::format("invalid ip/mask combo '{}/{}' (format is /<# mask bits>)", - entry.address_prefix(), entry.prefix_len().value())); - } - } - tag_data.emplace_back(ip_tag.ip_tag_name(), cidr_set); - } - trie_ = std::make_unique>(tag_data); - } + Runtime::Loader& runtime); Runtime::Loader& runtime() { return runtime_; } Stats::Scope& scope() { return scope_; } FilterRequestType requestType() const { return request_type_; } const Network::LcTrie::LcTrie& trie() const { return *trie_; } - const std::string& statsPrefix() const { return stats_prefix_; } + + void incHit(absl::string_view tag) { incCounter(hit_, tag); } + void incNoHit() { incCounter(no_hit_); } + void incTotal() { incCounter(total_); } private: static FilterRequestType requestTypeEnum( @@ -88,10 +59,16 @@ class IpTaggingFilterConfig { } } + void incCounter(Stats::StatName name1, absl::string_view tag = ""); + const FilterRequestType request_type_; Stats::Scope& scope_; Runtime::Loader& runtime_; - const std::string stats_prefix_; + Stats::StatNameSet stat_name_set_; + const Stats::StatName stats_prefix_; + const Stats::StatName hit_; + const Stats::StatName no_hit_; + const Stats::StatName total_; std::unique_ptr> trie_; }; diff --git a/source/extensions/filters/http/jwt_authn/matcher.cc b/source/extensions/filters/http/jwt_authn/matcher.cc index 53c61c2c58..75753cdb42 100644 --- a/source/extensions/filters/http/jwt_authn/matcher.cc +++ b/source/extensions/filters/http/jwt_authn/matcher.cc @@ -1,6 +1,7 @@ #include "extensions/filters/http/jwt_authn/matcher.h" #include "common/common/logger.h" +#include "common/common/regex.h" #include "common/router/config_impl.h" #include "absl/strings/match.h" @@ -21,14 +22,11 @@ namespace { class BaseMatcherImpl : public Matcher, public Logger::Loggable { public: BaseMatcherImpl(const RequirementRule& rule) - : case_sensitive_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(rule.match(), case_sensitive, true)) { - - for (const auto& header_map : rule.match().headers()) { - config_headers_.push_back(header_map); - } - + : case_sensitive_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(rule.match(), case_sensitive, true)), + config_headers_(Http::HeaderUtility::buildHeaderDataVector(rule.match().headers())) { for (const auto& query_parameter : rule.match().query_parameters()) { - config_query_parameters_.push_back(query_parameter); + config_query_parameters_.push_back( + std::make_unique(query_parameter)); } } @@ -50,8 +48,8 @@ class BaseMatcherImpl : public Matcher, public Logger::Loggable const bool case_sensitive_; private: - std::vector config_headers_; - std::vector config_query_parameters_; + std::vector config_headers_; + std::vector config_query_parameters_; }; /** @@ -108,12 +106,20 @@ class PathMatcherImpl : public BaseMatcherImpl { /** * Perform a match against any path with a regex rule. + * TODO(mattklein123): This code needs dedup with RegexRouteEntryImpl. */ class RegexMatcherImpl : public BaseMatcherImpl { public: - RegexMatcherImpl(const RequirementRule& rule) - : BaseMatcherImpl(rule), regex_(RegexUtil::parseRegex(rule.match().regex())), - regex_str_(rule.match().regex()) {} + RegexMatcherImpl(const RequirementRule& rule) : BaseMatcherImpl(rule) { + if (rule.match().path_specifier_case() == envoy::api::v2::route::RouteMatch::kRegex) { + regex_ = Regex::Utility::parseStdRegexAsCompiledMatcher(rule.match().regex()); + regex_str_ = rule.match().regex(); + } else { + ASSERT(rule.match().path_specifier_case() == envoy::api::v2::route::RouteMatch::kSafeRegex); + regex_ = Regex::Utility::parseRegex(rule.match().safe_regex()); + regex_str_ = rule.match().safe_regex().regex(); + } + } bool matches(const Http::HeaderMap& headers) const override { if (BaseMatcherImpl::matchRoute(headers)) { @@ -121,7 +127,7 @@ class RegexMatcherImpl : public BaseMatcherImpl { const absl::string_view query_string = Http::Utility::findQueryStringStart(path); absl::string_view path_view = path.getStringView(); path_view.remove_suffix(query_string.length()); - if (std::regex_match(path_view.begin(), path_view.end(), regex_)) { + if (regex_->match(path_view)) { ENVOY_LOG(debug, "Regex requirement '{}' matched.", regex_str_); return true; } @@ -130,10 +136,9 @@ class RegexMatcherImpl : public BaseMatcherImpl { } private: - // regex object - const std::regex regex_; + Regex::CompiledMatcherPtr regex_; // raw regex string, for logging. - const std::string regex_str_; + std::string regex_str_; }; } // namespace @@ -145,6 +150,7 @@ MatcherConstPtr Matcher::create(const RequirementRule& rule) { case RouteMatch::PathSpecifierCase::kPath: return std::make_unique(rule); case RouteMatch::PathSpecifierCase::kRegex: + case RouteMatch::PathSpecifierCase::kSafeRegex: return std::make_unique(rule); // path specifier is required. case RouteMatch::PathSpecifierCase::PATH_SPECIFIER_NOT_SET: diff --git a/source/extensions/filters/http/squash/squash_filter.cc b/source/extensions/filters/http/squash/squash_filter.cc index 5a3b7d7b12..a4c58205c4 100644 --- a/source/extensions/filters/http/squash/squash_filter.cc +++ b/source/extensions/filters/http/squash/squash_filter.cc @@ -163,7 +163,8 @@ Http::FilterHeadersStatus SquashFilter::decodeHeaders(Http::HeaderMap& headers, attachment_timeout_timer_ = decoder_callbacks_->dispatcher().createTimer([this]() -> void { doneSquashing(); }); - attachment_timeout_timer_->enableTimer(config_->attachmentTimeout()); + attachment_timeout_timer_->enableTimer(config_->attachmentTimeout(), + &decoder_callbacks_->scope()); // Check if the timer expired inline. if (!is_squashing_) { return Http::FilterHeadersStatus::Continue; @@ -261,7 +262,8 @@ void SquashFilter::scheduleRetry() { attachment_poll_period_timer_ = decoder_callbacks_->dispatcher().createTimer([this]() -> void { pollForAttachment(); }); } - attachment_poll_period_timer_->enableTimer(config_->attachmentPollPeriod()); + attachment_poll_period_timer_->enableTimer(config_->attachmentPollPeriod(), + &decoder_callbacks_->scope()); } void SquashFilter::pollForAttachment() { diff --git a/source/extensions/filters/http/squash/squash_filter.h b/source/extensions/filters/http/squash/squash_filter.h index 0e35c3dd52..08f63aa4b2 100644 --- a/source/extensions/filters/http/squash/squash_filter.h +++ b/source/extensions/filters/http/squash/squash_filter.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "envoy/config/filter/http/squash/v2/squash.pb.h" #include "envoy/http/async_client.h" #include "envoy/http/filter.h" diff --git a/source/extensions/filters/listener/original_src/original_src_config_factory.cc b/source/extensions/filters/listener/original_src/original_src_config_factory.cc index aa1efc58b6..14a1a7384b 100644 --- a/source/extensions/filters/listener/original_src/original_src_config_factory.cc +++ b/source/extensions/filters/listener/original_src/original_src_config_factory.cc @@ -14,9 +14,10 @@ namespace ListenerFilters { namespace OriginalSrc { Network::ListenerFilterFactoryCb OriginalSrcConfigFactory::createFilterFactoryFromProto( - const Protobuf::Message& message, Server::Configuration::ListenerFactoryContext&) { + const Protobuf::Message& message, Server::Configuration::ListenerFactoryContext& context) { auto proto_config = MessageUtil::downcastAndValidate< - const envoy::config::filter::listener::original_src::v2alpha1::OriginalSrc&>(message); + const envoy::config::filter::listener::original_src::v2alpha1::OriginalSrc&>( + message, context.messageValidationVisitor()); Config config(proto_config); return [config](Network::ListenerFilterManager& filter_manager) -> void { filter_manager.addAcceptFilter(std::make_unique(config)); diff --git a/source/extensions/filters/listener/tls_inspector/tls_inspector.cc b/source/extensions/filters/listener/tls_inspector/tls_inspector.cc index f5d2a8e4ce..13b52cdc6b 100644 --- a/source/extensions/filters/listener/tls_inspector/tls_inspector.cc +++ b/source/extensions/filters/listener/tls_inspector/tls_inspector.cc @@ -72,23 +72,47 @@ Network::FilterStatus Filter::onAccept(Network::ListenerFilterCallbacks& cb) { ENVOY_LOG(debug, "tls inspector: new connection accepted"); Network::ConnectionSocket& socket = cb.socket(); ASSERT(file_event_ == nullptr); - - file_event_ = cb.dispatcher().createFileEvent( - socket.ioHandle().fd(), - [this](uint32_t events) { - if (events & Event::FileReadyType::Closed) { - config_->stats().connection_closed_.inc(); - done(false); - return; - } - - ASSERT(events == Event::FileReadyType::Read); - onRead(); - }, - Event::FileTriggerType::Edge, Event::FileReadyType::Read | Event::FileReadyType::Closed); - cb_ = &cb; - return Network::FilterStatus::StopIteration; + + ParseState parse_state = onRead(); + switch (parse_state) { + case ParseState::Error: + // As per discussion in https://github.com/envoyproxy/envoy/issues/7864 + // we don't add new enum in FilterStatus so we have to signal the caller + // the new condition. + cb.socket().close(); + return Network::FilterStatus::StopIteration; + case ParseState::Done: + return Network::FilterStatus::Continue; + case ParseState::Continue: + // do nothing but create the event + file_event_ = cb.dispatcher().createFileEvent( + socket.ioHandle().fd(), + [this](uint32_t events) { + if (events & Event::FileReadyType::Closed) { + config_->stats().connection_closed_.inc(); + done(false); + return; + } + + ASSERT(events == Event::FileReadyType::Read); + ParseState parse_state = onRead(); + switch (parse_state) { + case ParseState::Error: + done(false); + break; + case ParseState::Done: + done(true); + break; + case ParseState::Continue: + // do nothing but wait for the next event + break; + } + }, + Event::FileTriggerType::Edge, Event::FileReadyType::Read | Event::FileReadyType::Closed); + return Network::FilterStatus::StopIteration; + } + NOT_REACHED_GCOVR_EXCL_LINE } void Filter::onALPN(const unsigned char* data, unsigned int len) { @@ -122,7 +146,7 @@ void Filter::onServername(absl::string_view name) { clienthello_success_ = true; } -void Filter::onRead() { +ParseState Filter::onRead() { // This receive code is somewhat complicated, because it must be done as a MSG_PEEK because // there is no way for a listener-filter to pass payload data to the ConnectionImpl and filters // that get created later. @@ -141,11 +165,10 @@ void Filter::onRead() { ENVOY_LOG(trace, "tls inspector: recv: {}", result.rc_); if (result.rc_ == -1 && result.errno_ == EAGAIN) { - return; + return ParseState::Continue; } else if (result.rc_ < 0) { config_->stats().read_error_.inc(); - done(false); - return; + return ParseState::Error; } // Because we're doing a MSG_PEEK, data we've seen before gets returned every time, so @@ -154,8 +177,9 @@ void Filter::onRead() { const uint8_t* data = buf_ + read_; const size_t len = result.rc_ - read_; read_ = result.rc_; - parseClientHello(data, len); + return parseClientHello(data, len); } + return ParseState::Continue; } void Filter::done(bool success) { @@ -164,7 +188,7 @@ void Filter::done(bool success) { cb_->continueFilterChain(success); } -void Filter::parseClientHello(const void* data, size_t len) { +ParseState Filter::parseClientHello(const void* data, size_t len) { // Ownership is passed to ssl_ in SSL_set_bio() bssl::UniquePtr bio(BIO_new_mem_buf(data, len)); @@ -185,9 +209,9 @@ void Filter::parseClientHello(const void* data, size_t len) { // We've hit the specified size limit. This is an unreasonably large ClientHello; // indicate failure. config_->stats().client_hello_too_large_.inc(); - done(false); + return ParseState::Error; } - break; + return ParseState::Continue; case SSL_ERROR_SSL: if (clienthello_success_) { config_->stats().tls_found_.inc(); @@ -200,11 +224,9 @@ void Filter::parseClientHello(const void* data, size_t len) { } else { config_->stats().tls_not_found_.inc(); } - done(true); - break; + return ParseState::Done; default: - done(false); - break; + return ParseState::Error; } } diff --git a/source/extensions/filters/listener/tls_inspector/tls_inspector.h b/source/extensions/filters/listener/tls_inspector/tls_inspector.h index cf75fe87e9..ee353ce9ef 100644 --- a/source/extensions/filters/listener/tls_inspector/tls_inspector.h +++ b/source/extensions/filters/listener/tls_inspector/tls_inspector.h @@ -36,6 +36,14 @@ struct TlsInspectorStats { ALL_TLS_INSPECTOR_STATS(GENERATE_COUNTER_STRUCT) }; +enum class ParseState { + // Parse result is out. It could be tls or not. + Done, + // Parser expects more data. + Continue, + // Parser reports unrecoverable error. + Error +}; /** * Global configuration for TLS inspector. */ @@ -68,8 +76,8 @@ class Filter : public Network::ListenerFilter, Logger::Loggable(proto_config), context); + return createFilterFactoryFromProtoTyped(MessageUtil::downcastAndValidate( + proto_config, context.messageValidationVisitor()), + context); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { @@ -38,9 +39,10 @@ class FactoryBase : public Server::Configuration::NamedNetworkFilterConfigFactor } Upstream::ProtocolOptionsConfigConstSharedPtr - createProtocolOptionsConfig(const Protobuf::Message& proto_config) override { - return createProtocolOptionsTyped( - MessageUtil::downcastAndValidate(proto_config)); + createProtocolOptionsConfig(const Protobuf::Message& proto_config, + ProtobufMessage::ValidationVisitor& validation_visitor) override { + return createProtocolOptionsTyped(MessageUtil::downcastAndValidate( + proto_config, validation_visitor)); } std::string name() override { return name_; } diff --git a/source/extensions/filters/network/common/redis/BUILD b/source/extensions/filters/network/common/redis/BUILD index 1757c2a7b9..ae2702107c 100644 --- a/source/extensions/filters/network/common/redis/BUILD +++ b/source/extensions/filters/network/common/redis/BUILD @@ -46,6 +46,7 @@ envoy_cc_library( hdrs = ["client.h"], deps = [ ":codec_lib", + ":redis_command_stats_lib", "//include/envoy/upstream:cluster_manager_interface", ], ) @@ -57,7 +58,9 @@ envoy_cc_library( deps = [ ":client_interface", ":codec_lib", + ":utility_lib", "//include/envoy/router:router_interface", + "//include/envoy/stats:timespan", "//include/envoy/thread_local:thread_local_interface", "//include/envoy/upstream:cluster_manager_interface", "//source/common/buffer:buffer_lib", @@ -78,3 +81,18 @@ envoy_cc_library( ":codec_lib", ], ) + +envoy_cc_library( + name = "redis_command_stats_lib", + srcs = ["redis_command_stats.cc"], + hdrs = ["redis_command_stats.h"], + deps = [ + ":codec_interface", + ":supported_commands_lib", + "//include/envoy/stats:stats_interface", + "//include/envoy/stats:timespan", + "//source/common/common:to_lower_table_lib", + "//source/common/common:utility_lib", + "//source/common/stats:symbol_table_lib", + ], +) diff --git a/source/extensions/filters/network/common/redis/client.h b/source/extensions/filters/network/common/redis/client.h index e20df148fb..fc76c61ac4 100644 --- a/source/extensions/filters/network/common/redis/client.h +++ b/source/extensions/filters/network/common/redis/client.h @@ -5,6 +5,7 @@ #include "envoy/upstream/cluster_manager.h" #include "extensions/filters/network/common/redis/codec_impl.h" +#include "extensions/filters/network/common/redis/redis_command_stats.h" namespace Envoy { namespace Extensions { @@ -95,6 +96,12 @@ class Client : public Event::DeferredDeletable { * for some reason. */ virtual PoolRequest* makeRequest(const RespValue& request, PoolCallbacks& callbacks) PURE; + + /** + * Initialize the connection. Issue the auth command and readonly command as needed. + * @param auth password for upstream host. + */ + virtual void initialize(const std::string& auth_password) PURE; }; using ClientPtr = std::unique_ptr; @@ -163,6 +170,11 @@ class Config { */ virtual uint32_t maxUpstreamUnknownConnections() const PURE; + /** + * @return when enabled, upstream cluster per-command statistics will be recorded. + */ + virtual bool enableCommandStats() const PURE; + /** * @return the read policy the proxy should use. */ @@ -181,10 +193,15 @@ class ClientFactory { * @param host supplies the upstream host. * @param dispatcher supplies the owning thread's dispatcher. * @param config supplies the connection pool configuration. + * @param redis_command_stats supplies the redis command stats. + * @param scope supplies the stats scope. + * @param auth password for upstream host. * @return ClientPtr a new connection pool client. */ virtual ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, - const Config& config) PURE; + const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope, const std::string& auth_password) PURE; }; } // namespace Client diff --git a/source/extensions/filters/network/common/redis/client_impl.cc b/source/extensions/filters/network/common/redis/client_impl.cc index a2610417a8..631221a394 100644 --- a/source/extensions/filters/network/common/redis/client_impl.cc +++ b/source/extensions/filters/network/common/redis/client_impl.cc @@ -6,6 +6,9 @@ namespace NetworkFilters { namespace Common { namespace Redis { namespace Client { +namespace { +Common::Redis::Client::DoNothingPoolCallbacks null_pool_callbacks; +} // namespace ConfigImpl::ConfigImpl( const envoy::config::filter::network::redis_proxy::v2::RedisProxy::ConnPoolSettings& config) @@ -19,7 +22,8 @@ ConfigImpl::ConfigImpl( 3)), // Default timeout is 3ms. If max_buffer_size_before_flush is zero, this is not used // as the buffer is flushed on each request immediately. max_upstream_unknown_connections_( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_upstream_unknown_connections, 100)) { + PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_upstream_unknown_connections, 100)), + enable_command_stats_(config.enable_command_stats()) { switch (config.read_policy()) { case envoy::config::filter::network::redis_proxy::v2:: RedisProxy_ConnPoolSettings_ReadPolicy_MASTER: @@ -31,14 +35,14 @@ ConfigImpl::ConfigImpl( break; case envoy::config::filter::network::redis_proxy::v2:: RedisProxy_ConnPoolSettings_ReadPolicy_REPLICA: - read_policy_ = ReadPolicy::PreferMaster; + read_policy_ = ReadPolicy::Replica; break; case envoy::config::filter::network::redis_proxy::v2:: RedisProxy_ConnPoolSettings_ReadPolicy_PREFER_REPLICA: - read_policy_ = ReadPolicy::PreferMaster; + read_policy_ = ReadPolicy::PreferReplica; break; case envoy::config::filter::network::redis_proxy::v2::RedisProxy_ConnPoolSettings_ReadPolicy_ANY: - read_policy_ = ReadPolicy::PreferMaster; + read_policy_ = ReadPolicy::Any; break; default: NOT_REACHED_GCOVR_EXCL_LINE; @@ -48,10 +52,11 @@ ConfigImpl::ConfigImpl( ClientPtr ClientImpl::create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, EncoderPtr&& encoder, DecoderFactory& decoder_factory, - const Config& config) { - - std::unique_ptr client( - new ClientImpl(host, dispatcher, std::move(encoder), decoder_factory, config)); + const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope) { + auto client = std::make_unique(host, dispatcher, std::move(encoder), decoder_factory, + config, redis_command_stats, scope); client->connection_ = host->createConnection(dispatcher, nullptr, nullptr).connection_; client->connection_->addConnectionCallbacks(*client); client->connection_->addReadFilter(Network::ReadFilterSharedPtr{new UpstreamReadFilter(*client)}); @@ -61,11 +66,14 @@ ClientPtr ClientImpl::create(Upstream::HostConstSharedPtr host, Event::Dispatche } ClientImpl::ClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, - EncoderPtr&& encoder, DecoderFactory& decoder_factory, const Config& config) + EncoderPtr&& encoder, DecoderFactory& decoder_factory, const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope) : host_(host), encoder_(std::move(encoder)), decoder_(decoder_factory.create(*this)), config_(config), - connect_or_op_timer_(dispatcher.createTimer([this]() -> void { onConnectOrOpTimeout(); })), - flush_timer_(dispatcher.createTimer([this]() -> void { flushBufferAndResetTimer(); })) { + connect_or_op_timer_(dispatcher.createTimer([this]() { onConnectOrOpTimeout(); })), + flush_timer_(dispatcher.createTimer([this]() { flushBufferAndResetTimer(); })), + time_source_(dispatcher.timeSource()), redis_command_stats_(redis_command_stats), + scope_(scope) { host->cluster().stats().upstream_cx_total_.inc(); host->stats().cx_total_.inc(); host->cluster().stats().upstream_cx_active_.inc(); @@ -94,7 +102,17 @@ PoolRequest* ClientImpl::makeRequest(const RespValue& request, PoolCallbacks& ca const bool empty_buffer = encoder_buffer_.length() == 0; - pending_requests_.emplace_back(*this, callbacks); + Stats::StatName command; + if (config_.enableCommandStats()) { + // Only lowercase command and get StatName if we enable command stats + command = redis_command_stats_->getCommandFromRequest(request); + redis_command_stats_->updateStatsTotal(scope_, command); + } else { + // If disabled, we use a placeholder stat name "unused" that is not used + command = redis_command_stats_->getUnusedStatName(); + } + + pending_requests_.emplace_back(*this, callbacks, command); encoder_->encode(request, encoder_buffer_); // If buffer is full, flush. If the buffer was empty before the request, start the timer. @@ -186,6 +204,14 @@ void ClientImpl::onRespValue(RespValuePtr&& value) { ASSERT(!pending_requests_.empty()); PendingRequest& request = pending_requests_.front(); const bool canceled = request.canceled_; + + if (config_.enableCommandStats()) { + bool success = !canceled && (value->type() != Common::Redis::RespType::Error); + redis_command_stats_->updateStats(scope_, request.command_, success); + request.command_request_timer_->complete(); + } + request.aggregate_request_timer_->complete(); + PoolCallbacks& callbacks = request.callbacks_; // We need to ensure the request is popped before calling the callback, since the callback might @@ -225,8 +251,15 @@ void ClientImpl::onRespValue(RespValuePtr&& value) { putOutlierEvent(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS); } -ClientImpl::PendingRequest::PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks) - : parent_(parent), callbacks_(callbacks) { +ClientImpl::PendingRequest::PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks, + Stats::StatName command) + : parent_(parent), callbacks_(callbacks), command_{command}, + aggregate_request_timer_(parent_.redis_command_stats_->createAggregateTimer( + parent_.scope_, parent_.time_source_)) { + if (parent_.config_.enableCommandStats()) { + command_request_timer_ = parent_.redis_command_stats_->createCommandTimer( + parent_.scope_, command_, parent_.time_source_); + } parent.host_->cluster().stats().upstream_rq_total_.inc(); parent.host_->stats().rq_total_.inc(); parent.host_->cluster().stats().upstream_rq_active_.inc(); @@ -245,12 +278,29 @@ void ClientImpl::PendingRequest::cancel() { canceled_ = true; } +void ClientImpl::initialize(const std::string& auth_password) { + if (!auth_password.empty()) { + // Send an AUTH command to the upstream server. + makeRequest(Utility::makeAuthCommand(auth_password), null_pool_callbacks); + } + // Any connection to replica requires the READONLY command in order to perform read. + // Also the READONLY command is a no-opt for the master. + // We only need to send the READONLY command iff it's possible that the host is a replica. + if (config_.readPolicy() != Common::Redis::Client::ReadPolicy::Master) { + makeRequest(Utility::ReadOnlyRequest::instance(), null_pool_callbacks); + } +} + ClientFactoryImpl ClientFactoryImpl::instance_; ClientPtr ClientFactoryImpl::create(Upstream::HostConstSharedPtr host, - Event::Dispatcher& dispatcher, const Config& config) { - return ClientImpl::create(host, dispatcher, EncoderPtr{new EncoderImpl()}, decoder_factory_, - config); + Event::Dispatcher& dispatcher, const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope, const std::string& auth_password) { + ClientPtr client = ClientImpl::create(host, dispatcher, EncoderPtr{new EncoderImpl()}, + decoder_factory_, config, redis_command_stats, scope); + client->initialize(auth_password); + return client; } } // namespace Client diff --git a/source/extensions/filters/network/common/redis/client_impl.h b/source/extensions/filters/network/common/redis/client_impl.h index 9522482fb0..be0d1f8119 100644 --- a/source/extensions/filters/network/common/redis/client_impl.h +++ b/source/extensions/filters/network/common/redis/client_impl.h @@ -3,6 +3,7 @@ #include #include "envoy/config/filter/network/redis_proxy/v2/redis_proxy.pb.h" +#include "envoy/stats/timespan.h" #include "envoy/thread_local/thread_local.h" #include "envoy/upstream/cluster_manager.h" @@ -15,6 +16,7 @@ #include "common/upstream/upstream_impl.h" #include "extensions/filters/network/common/redis/client.h" +#include "extensions/filters/network/common/redis/utility.h" namespace Envoy { namespace Extensions { @@ -49,6 +51,7 @@ class ConfigImpl : public Config { uint32_t maxUpstreamUnknownConnections() const override { return max_upstream_unknown_connections_; } + bool enableCommandStats() const override { return enable_command_stats_; } ReadPolicy readPolicy() const override { return read_policy_; } private: @@ -58,6 +61,7 @@ class ConfigImpl : public Config { const uint32_t max_buffer_size_before_flush_; const std::chrono::milliseconds buffer_flush_timeout_; const uint32_t max_upstream_unknown_connections_; + const bool enable_command_stats_; ReadPolicy read_policy_; }; @@ -65,8 +69,13 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne public: static ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, EncoderPtr&& encoder, DecoderFactory& decoder_factory, - const Config& config); + const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope); + ClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, EncoderPtr&& encoder, + DecoderFactory& decoder_factory, const Config& config, + const RedisCommandStatsSharedPtr& redis_command_stats, Stats::Scope& scope); ~ClientImpl() override; // Client @@ -77,6 +86,7 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne PoolRequest* makeRequest(const RespValue& request, PoolCallbacks& callbacks) override; bool active() override { return !pending_requests_.empty(); } void flushBufferAndResetTimer(); + void initialize(const std::string& auth_password) override; private: friend class RedisClientImplTest; @@ -94,7 +104,7 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne }; struct PendingRequest : public PoolRequest { - PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks); + PendingRequest(ClientImpl& parent, PoolCallbacks& callbacks, Stats::StatName stat_name); ~PendingRequest() override; // PoolRequest @@ -102,11 +112,12 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne ClientImpl& parent_; PoolCallbacks& callbacks_; + Stats::StatName command_; bool canceled_{}; + Stats::CompletableTimespanPtr aggregate_request_timer_; + Stats::CompletableTimespanPtr command_request_timer_; }; - ClientImpl(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, EncoderPtr&& encoder, - DecoderFactory& decoder_factory, const Config& config); void onConnectOrOpTimeout(); void onData(Buffer::Instance& data); void putOutlierEvent(Upstream::Outlier::Result result); @@ -129,13 +140,17 @@ class ClientImpl : public Client, public DecoderCallbacks, public Network::Conne Event::TimerPtr connect_or_op_timer_; bool connected_{}; Event::TimerPtr flush_timer_; + Envoy::TimeSource& time_source_; + const RedisCommandStatsSharedPtr redis_command_stats_; + Stats::Scope& scope_; }; class ClientFactoryImpl : public ClientFactory { public: // RedisProxy::ConnPool::ClientFactoryImpl ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher& dispatcher, - const Config& config) override; + const Config& config, const RedisCommandStatsSharedPtr& redis_command_stats, + Stats::Scope& scope, const std::string& auth_password) override; static ClientFactoryImpl instance_; diff --git a/source/extensions/filters/network/common/redis/redis_command_stats.cc b/source/extensions/filters/network/common/redis/redis_command_stats.cc new file mode 100644 index 0000000000..ce11df704b --- /dev/null +++ b/source/extensions/filters/network/common/redis/redis_command_stats.cc @@ -0,0 +1,110 @@ +#include "extensions/filters/network/common/redis/redis_command_stats.h" + +#include "extensions/filters/network/common/redis/supported_commands.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace Common { +namespace Redis { + +RedisCommandStats::RedisCommandStats(Stats::SymbolTable& symbol_table, const std::string& prefix) + : symbol_table_(symbol_table), stat_name_pool_(symbol_table_), + prefix_(stat_name_pool_.add(prefix)), + upstream_rq_time_(stat_name_pool_.add("upstream_rq_time")), + latency_(stat_name_pool_.add("latency")), total_(stat_name_pool_.add("total")), + success_(stat_name_pool_.add("success")), error_(stat_name_pool_.add("error")), + unused_metric_(stat_name_pool_.add("unused")), null_metric_(stat_name_pool_.add("null")), + unknown_metric_(stat_name_pool_.add("unknown")) { + // Note: Even if this is disabled, we track the upstream_rq_time. + // Create StatName for each Redis command. Note that we don't include Auth or Ping. + for (const std::string& command : + Extensions::NetworkFilters::Common::Redis::SupportedCommands::simpleCommands()) { + addCommandToPool(command); + } + for (const std::string& command : + Extensions::NetworkFilters::Common::Redis::SupportedCommands::evalCommands()) { + addCommandToPool(command); + } + for (const std::string& command : Extensions::NetworkFilters::Common::Redis::SupportedCommands:: + hashMultipleSumResultCommands()) { + addCommandToPool(command); + } + addCommandToPool(Extensions::NetworkFilters::Common::Redis::SupportedCommands::mget()); + addCommandToPool(Extensions::NetworkFilters::Common::Redis::SupportedCommands::mset()); +} + +void RedisCommandStats::addCommandToPool(const std::string& command_string) { + Stats::StatName command = stat_name_pool_.add(command_string); + stat_name_map_[command_string] = command; +} + +Stats::Counter& RedisCommandStats::counter(Stats::Scope& scope, + const Stats::StatNameVec& stat_names) { + const Stats::SymbolTable::StoragePtr storage_ptr = symbol_table_.join(stat_names); + Stats::StatName full_stat_name = Stats::StatName(storage_ptr.get()); + return scope.counterFromStatName(full_stat_name); +} + +Stats::Histogram& RedisCommandStats::histogram(Stats::Scope& scope, + const Stats::StatNameVec& stat_names) { + const Stats::SymbolTable::StoragePtr storage_ptr = symbol_table_.join(stat_names); + Stats::StatName full_stat_name = Stats::StatName(storage_ptr.get()); + return scope.histogramFromStatName(full_stat_name); +} + +Stats::CompletableTimespanPtr +RedisCommandStats::createCommandTimer(Stats::Scope& scope, Stats::StatName command, + Envoy::TimeSource& time_source) { + return std::make_unique>( + histogram(scope, {prefix_, command, latency_}), time_source); +} + +Stats::CompletableTimespanPtr +RedisCommandStats::createAggregateTimer(Stats::Scope& scope, Envoy::TimeSource& time_source) { + return std::make_unique>( + histogram(scope, {prefix_, upstream_rq_time_}), time_source); +} + +Stats::StatName RedisCommandStats::getCommandFromRequest(const RespValue& request) { + // Get command from RespValue + switch (request.type()) { + case RespType::Array: + return getCommandFromRequest(request.asArray().front()); + case RespType::Integer: + return unknown_metric_; + case RespType::Null: + return null_metric_; + default: + // Once we have a RespType::String we lowercase it and then look it up in our stat_name_map. + // If it does not exist, we return our unknown stat name. + std::string to_lower_command(request.asString()); + to_lower_table_.toLowerCase(to_lower_command); + + auto iter = stat_name_map_.find(to_lower_command); + if (iter != stat_name_map_.end()) { + return iter->second; + } else { + return unknown_metric_; + } + } +} + +void RedisCommandStats::updateStatsTotal(Stats::Scope& scope, Stats::StatName command) { + counter(scope, {prefix_, command, total_}).inc(); +} + +void RedisCommandStats::updateStats(Stats::Scope& scope, Stats::StatName command, + const bool success) { + if (success) { + counter(scope, {prefix_, command, success_}).inc(); + } else { + counter(scope, {prefix_, command, success_}).inc(); + } +} + +} // namespace Redis +} // namespace Common +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/source/extensions/filters/network/common/redis/redis_command_stats.h b/source/extensions/filters/network/common/redis/redis_command_stats.h new file mode 100644 index 0000000000..0ee2ce824c --- /dev/null +++ b/source/extensions/filters/network/common/redis/redis_command_stats.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#include "envoy/stats/scope.h" +#include "envoy/stats/timespan.h" + +#include "common/common/to_lower_table.h" +#include "common/stats/symbol_table_impl.h" + +#include "extensions/filters/network/common/redis/codec.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace Common { +namespace Redis { + +class RedisCommandStats { +public: + RedisCommandStats(Stats::SymbolTable& symbol_table, const std::string& prefix); + + // TODO (@FAYiEKcbD0XFqF2QK2E4viAHg8rMm2VbjYKdjTg): Use Singleton to manage a single + // RedisCommandStats on the client factory so that it can be used for proxy filter, discovery and + // health check. + static std::shared_ptr + createRedisCommandStats(Stats::SymbolTable& symbol_table) { + return std::make_shared(symbol_table, "upstream_commands"); + } + + Stats::Counter& counter(Stats::Scope& scope, const Stats::StatNameVec& stat_names); + Stats::Histogram& histogram(Stats::Scope& scope, const Stats::StatNameVec& stat_names); + Stats::CompletableTimespanPtr createCommandTimer(Stats::Scope& scope, Stats::StatName command, + Envoy::TimeSource& time_source); + Stats::CompletableTimespanPtr createAggregateTimer(Stats::Scope& scope, + Envoy::TimeSource& time_source); + Stats::StatName getCommandFromRequest(const RespValue& request); + void updateStatsTotal(Stats::Scope& scope, Stats::StatName command); + void updateStats(Stats::Scope& scope, Stats::StatName command, const bool success); + Stats::StatName getUnusedStatName() { return unused_metric_; } + +private: + void addCommandToPool(const std::string& command_string); + + Stats::SymbolTable& symbol_table_; + Stats::StatNamePool stat_name_pool_; + StringMap stat_name_map_; + const Stats::StatName prefix_; + const Stats::StatName upstream_rq_time_; + const Stats::StatName latency_; + const Stats::StatName total_; + const Stats::StatName success_; + const Stats::StatName error_; + const Stats::StatName unused_metric_; + const Stats::StatName null_metric_; + const Stats::StatName unknown_metric_; + const ToLowerTable to_lower_table_; +}; +using RedisCommandStatsSharedPtr = std::shared_ptr; + +} // namespace Redis +} // namespace Common +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/dubbo_proxy/decoder.cc b/source/extensions/filters/network/dubbo_proxy/decoder.cc index f7e9cfc84a..3715acf865 100644 --- a/source/extensions/filters/network/dubbo_proxy/decoder.cc +++ b/source/extensions/filters/network/dubbo_proxy/decoder.cc @@ -18,12 +18,16 @@ DecoderStateMachine::onDecodeStreamHeader(Buffer::Instance& buffer) { return {ProtocolState::WaitForData}; } - // The heartbeat message has no body. auto context = ret.first; if (metadata->message_type() == MessageType::HeartbeatRequest || metadata->message_type() == MessageType::HeartbeatResponse) { + if (buffer.length() < (context->header_size() + context->body_size())) { + ENVOY_LOG(debug, "dubbo decoder: need more data for {} protocol heartbeat", protocol_.name()); + return {ProtocolState::WaitForData}; + } + ENVOY_LOG(debug, "dubbo decoder: this is the {} heartbeat message", protocol_.name()); - buffer.drain(context->header_size()); + buffer.drain(context->header_size() + context->body_size()); delegate_.onHeartbeat(metadata); return {ProtocolState::Done}; } diff --git a/source/extensions/filters/network/dubbo_proxy/filters/factory_base.h b/source/extensions/filters/network/dubbo_proxy/filters/factory_base.h index 1319a84617..021d99b52e 100644 --- a/source/extensions/filters/network/dubbo_proxy/filters/factory_base.h +++ b/source/extensions/filters/network/dubbo_proxy/filters/factory_base.h @@ -21,8 +21,9 @@ template class FactoryBase : public NamedDubboFilterConfigFa createFilterFactoryFromProto(const Protobuf::Message& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override { - return createFilterFactoryFromProtoTyped( - MessageUtil::downcastAndValidate(proto_config), stats_prefix, context); + return createFilterFactoryFromProtoTyped(MessageUtil::downcastAndValidate( + proto_config, context.messageValidationVisitor()), + stats_prefix, context); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc index 81eac5f0f3..35850cfee7 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc +++ b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.cc @@ -15,11 +15,8 @@ namespace Router { RouteEntryImplBase::RouteEntryImplBase( const envoy::config::filter::network::dubbo_proxy::v2alpha1::Route& route) - : cluster_name_(route.route().cluster()) { - for (const auto& header_map : route.match().headers()) { - config_headers_.emplace_back(header_map); - } - + : cluster_name_(route.route().cluster()), + config_headers_(Http::HeaderUtility::buildHeaderDataVector(route.match().headers())) { if (route.route().cluster_specifier_case() == envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteAction::kWeightedClusters) { total_cluster_weight_ = 0UL; diff --git a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h index caa8b9ca31..9ce491f0ae 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h +++ b/source/extensions/filters/network/dubbo_proxy/router/route_matcher.h @@ -77,7 +77,7 @@ class RouteEntryImplBase : public RouteEntry, uint64_t total_cluster_weight_; const std::string cluster_name_; - std::vector config_headers_; + const std::vector config_headers_; std::vector weighted_clusters_; // TODO(gengleilei) Implement it. @@ -123,7 +123,7 @@ class MethodRouteEntryImpl : public RouteEntryImplBase { uint64_t random_value) const override; private: - const Matchers::StringMatcher method_name_; + const Matchers::StringMatcherImpl method_name_; std::shared_ptr parameter_route_; }; diff --git a/source/extensions/filters/network/http_connection_manager/BUILD b/source/extensions/filters/network/http_connection_manager/BUILD index 1f5474a651..fbe72b257b 100644 --- a/source/extensions/filters/network/http_connection_manager/BUILD +++ b/source/extensions/filters/network/http_connection_manager/BUILD @@ -40,5 +40,6 @@ envoy_cc_library( "//source/common/router:scoped_rds_lib", "//source/extensions/filters/network:well_known_names", "//source/extensions/filters/network/common:factory_base_lib", + "@envoy_api//envoy/config/filter/network/http_connection_manager/v2:http_connection_manager_cc", ], ) diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index cc03d23923..998dce6709 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -8,6 +8,7 @@ #include "envoy/config/filter/network/http_connection_manager/v2/http_connection_manager.pb.validate.h" #include "envoy/filesystem/filesystem.h" #include "envoy/server/admin.h" +#include "envoy/tracing/http_tracer.h" #include "common/access_log/access_log_impl.h" #include "common/common/fmt.h" @@ -89,10 +90,12 @@ HttpConnectionManagerFilterConfigFactory::createFilterFactoryFromProtoTyped( return std::make_shared(context.admin()); }); - std::shared_ptr scoped_routes_config_provider_manager = - context.singletonManager().getTyped( - SINGLETON_MANAGER_REGISTERED_NAME(scoped_routes_config_provider_manager), [&context] { - return std::make_shared(context.admin()); + std::shared_ptr scoped_routes_config_provider_manager = + context.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(scoped_routes_config_provider_manager), + [&context, route_config_provider_manager] { + return std::make_shared( + context.admin(), *route_config_provider_manager); }); std::shared_ptr filter_config(new HttpConnectionManagerConfig( @@ -100,10 +103,11 @@ HttpConnectionManagerFilterConfigFactory::createFilterFactoryFromProtoTyped( *scoped_routes_config_provider_manager)); // This lambda captures the shared_ptrs created above, thus preserving the - // reference count. Moreover, keep in mind the capture list determines - // destruction order. - return [route_config_provider_manager, scoped_routes_config_provider_manager, filter_config, - &context, date_provider](Network::FilterManager& filter_manager) -> void { + // reference count. + // Keep in mind the lambda capture list **doesn't** determine the destruction order, but it's fine + // as these captured objects are also global singletons. + return [scoped_routes_config_provider_manager, route_config_provider_manager, date_provider, + filter_config, &context](Network::FilterManager& filter_manager) -> void { filter_manager.addReadFilter(Network::ReadFilterSharedPtr{new Http::ConnectionManagerImpl( *filter_config, context.drainDecision(), context.random(), context.httpContext(), context.runtime(), context.localInfo(), context.clusterManager(), @@ -183,8 +187,6 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( break; case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: kScopedRoutes: - ENVOY_LOG(warn, "Scoped routing has been enabled but it is not yet fully implemented! HTTP " - "request routing DOES NOT work (yet) with this configuration."); scoped_routes_config_provider_ = Router::ScopedRoutesConfigProviderUtil::create( config, context_, stats_prefix_, scoped_routes_config_provider_manager_); break; @@ -243,13 +245,27 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( Tracing::OperationName tracing_operation_name; std::vector request_headers_for_tags; - switch (tracing_config.operation_name()) { - case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: - Tracing::INGRESS: + // Listener level traffic direction overrides the operation name + switch (context.direction()) { + case envoy::api::v2::core::TrafficDirection::UNSPECIFIED: { + switch (tracing_config.operation_name()) { + case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: + Tracing::INGRESS: + tracing_operation_name = Tracing::OperationName::Ingress; + break; + case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: + Tracing::EGRESS: + tracing_operation_name = Tracing::OperationName::Egress; + break; + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } + break; + } + case envoy::api::v2::core::TrafficDirection::INBOUND: tracing_operation_name = Tracing::OperationName::Ingress; break; - case envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager:: - Tracing::EGRESS: + case envoy::api::v2::core::TrafficDirection::OUTBOUND: tracing_operation_name = Tracing::OperationName::Egress; break; default: @@ -266,17 +282,21 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( envoy::type::FractionalPercent random_sampling; // TODO: Random sampling historically was an integer and default to out of 10,000. We should // deprecate that and move to a straight fractional percent config. - random_sampling.set_numerator( - tracing_config.has_random_sampling() ? tracing_config.random_sampling().value() : 10000); + uint64_t random_sampling_numerator{PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( + tracing_config, random_sampling, 10000, 10000)}; + random_sampling.set_numerator(random_sampling_numerator); random_sampling.set_denominator(envoy::type::FractionalPercent::TEN_THOUSAND); envoy::type::FractionalPercent overall_sampling; overall_sampling.set_numerator( tracing_config.has_overall_sampling() ? tracing_config.overall_sampling().value() : 100); + const uint32_t max_path_tag_length = PROTOBUF_GET_WRAPPED_OR_DEFAULT( + tracing_config, max_path_tag_length, Tracing::DefaultMaxPathTagLength); + tracing_config_ = std::make_unique(Http::TracingConnectionManagerConfig{ tracing_operation_name, request_headers_for_tags, client_sampling, random_sampling, - overall_sampling, tracing_config.verbose()}); + overall_sampling, tracing_config.verbose(), max_path_tag_length}); } for (const auto& access_log : config.access_log()) { @@ -285,6 +305,8 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( access_logs_.push_back(current_access_log); } + server_transformation_ = config.server_header_transformation(); + if (!config.server_name().empty()) { server_name_ = config.server_name(); } else { diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index 8e8132e0aa..0385762236 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -116,6 +116,9 @@ class HttpConnectionManagerConfig : Logger::Loggable, return scoped_routes_config_provider_.get(); } const std::string& serverName() override { return server_name_; } + HttpConnectionManagerProto::ServerHeaderTransformation serverHeaderTransformation() override { + return server_transformation_; + } Http::ConnectionManagerStats& stats() override { return stats_; } Http::ConnectionManagerTracingStats& tracingStats() override { return tracing_stats_; } bool useRemoteAddress() override { return use_remote_address_; } @@ -166,6 +169,8 @@ class HttpConnectionManagerConfig : Logger::Loggable, CodecType codec_type_; const Http::Http2Settings http2_settings_; const Http::Http1Settings http1_settings_; + HttpConnectionManagerProto::ServerHeaderTransformation server_transformation_{ + HttpConnectionManagerProto::OVERWRITE}; std::string server_name_; Http::TracingConnectionManagerConfigPtr tracing_config_; absl::optional user_agent_; diff --git a/source/extensions/filters/network/mongo_proxy/BUILD b/source/extensions/filters/network/mongo_proxy/BUILD index 36a94de85a..f2be1d6a97 100644 --- a/source/extensions/filters/network/mongo_proxy/BUILD +++ b/source/extensions/filters/network/mongo_proxy/BUILD @@ -58,6 +58,7 @@ envoy_cc_library( deps = [ ":codec_interface", ":codec_lib", + ":mongo_stats_lib", ":utility_lib", "//include/envoy/access_log:access_log_interface", "//include/envoy/common:time_interface", @@ -81,6 +82,16 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "mongo_stats_lib", + srcs = ["mongo_stats.cc"], + hdrs = ["mongo_stats.h"], + deps = [ + "//include/envoy/stats:stats_interface", + "//source/common/stats:symbol_table_lib", + ], +) + envoy_cc_library( name = "utility_lib", srcs = ["utility.cc"], diff --git a/source/extensions/filters/network/mongo_proxy/config.cc b/source/extensions/filters/network/mongo_proxy/config.cc index a8989947e7..24539b6bd9 100644 --- a/source/extensions/filters/network/mongo_proxy/config.cc +++ b/source/extensions/filters/network/mongo_proxy/config.cc @@ -32,12 +32,13 @@ Network::FilterFactoryCb MongoProxyFilterConfigFactory::createFilterFactoryFromP fault_config = std::make_shared(proto_config.delay()); } + auto stats = std::make_shared(context.scope(), stat_prefix); const bool emit_dynamic_metadata = proto_config.emit_dynamic_metadata(); - return [stat_prefix, &context, access_log, fault_config, - emit_dynamic_metadata](Network::FilterManager& filter_manager) -> void { + return [stat_prefix, &context, access_log, fault_config, emit_dynamic_metadata, + stats](Network::FilterManager& filter_manager) -> void { filter_manager.addFilter(std::make_shared( stat_prefix, context.scope(), context.runtime(), access_log, fault_config, - context.drainDecision(), context.dispatcher().timeSource(), emit_dynamic_metadata)); + context.drainDecision(), context.dispatcher().timeSource(), emit_dynamic_metadata, stats)); }; } diff --git a/source/extensions/filters/network/mongo_proxy/mongo_stats.cc b/source/extensions/filters/network/mongo_proxy/mongo_stats.cc new file mode 100644 index 0000000000..11dd1877ce --- /dev/null +++ b/source/extensions/filters/network/mongo_proxy/mongo_stats.cc @@ -0,0 +1,47 @@ +#include "extensions/filters/network/mongo_proxy/mongo_stats.h" + +#include +#include +#include + +#include "envoy/stats/scope.h" + +#include "common/stats/symbol_table_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace MongoProxy { + +MongoStats::MongoStats(Stats::Scope& scope, const std::string& prefix) + : scope_(scope), stat_name_set_(scope.symbolTable()), prefix_(stat_name_set_.add(prefix)), + callsite_(stat_name_set_.add("callsite")), cmd_(stat_name_set_.add("cmd")), + collection_(stat_name_set_.add("collection")), multi_get_(stat_name_set_.add("multi_get")), + reply_num_docs_(stat_name_set_.add("reply_num_docs")), + reply_size_(stat_name_set_.add("reply_size")), + reply_time_ms_(stat_name_set_.add("reply_time_ms")), time_ms_(stat_name_set_.add("time_ms")), + query_(stat_name_set_.add("query")), scatter_get_(stat_name_set_.add("scatter_get")), + total_(stat_name_set_.add("total")) {} + +Stats::SymbolTable::StoragePtr MongoStats::addPrefix(const std::vector& names) { + std::vector names_with_prefix; + names_with_prefix.reserve(1 + names.size()); + names_with_prefix.push_back(prefix_); + names_with_prefix.insert(names_with_prefix.end(), names.begin(), names.end()); + return scope_.symbolTable().join(names_with_prefix); +} + +void MongoStats::incCounter(const std::vector& names) { + const Stats::SymbolTable::StoragePtr stat_name_storage = addPrefix(names); + scope_.counterFromStatName(Stats::StatName(stat_name_storage.get())).inc(); +} + +void MongoStats::recordHistogram(const std::vector& names, uint64_t sample) { + const Stats::SymbolTable::StoragePtr stat_name_storage = addPrefix(names); + scope_.histogramFromStatName(Stats::StatName(stat_name_storage.get())).recordValue(sample); +} + +} // namespace MongoProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/mongo_proxy/mongo_stats.h b/source/extensions/filters/network/mongo_proxy/mongo_stats.h new file mode 100644 index 0000000000..d27a847882 --- /dev/null +++ b/source/extensions/filters/network/mongo_proxy/mongo_stats.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include +#include + +#include "envoy/stats/scope.h" + +#include "common/stats/symbol_table_impl.h" + +namespace Envoy { +namespace Extensions { +namespace NetworkFilters { +namespace MongoProxy { + +class MongoStats { +public: + MongoStats(Stats::Scope& scope, const std::string& prefix); + + void incCounter(const std::vector& names); + void recordHistogram(const std::vector& names, uint64_t sample); + + /** + * Finds or creates a StatName by string, taking a global lock if needed. + * + * TODO(jmarantz): Potential perf issue here with mutex contention for names + * that have not been remembered as builtins in the constructor. + */ + Stats::StatName getStatName(const std::string& str) { return stat_name_set_.getStatName(str); } + +private: + Stats::SymbolTable::StoragePtr addPrefix(const std::vector& names); + + Stats::Scope& scope_; + Stats::StatNameSet stat_name_set_; + +public: + const Stats::StatName prefix_; + const Stats::StatName callsite_; + const Stats::StatName cmd_; + const Stats::StatName collection_; + const Stats::StatName multi_get_; + const Stats::StatName reply_num_docs_; + const Stats::StatName reply_size_; + const Stats::StatName reply_time_ms_; + const Stats::StatName time_ms_; + const Stats::StatName query_; + const Stats::StatName scatter_get_; + const Stats::StatName total_; +}; +using MongoStatsSharedPtr = std::shared_ptr; + +} // namespace MongoProxy +} // namespace NetworkFilters +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/filters/network/mongo_proxy/proxy.cc b/source/extensions/filters/network/mongo_proxy/proxy.cc index be08a04fc6..bc35859113 100644 --- a/source/extensions/filters/network/mongo_proxy/proxy.cc +++ b/source/extensions/filters/network/mongo_proxy/proxy.cc @@ -58,11 +58,11 @@ ProxyFilter::ProxyFilter(const std::string& stat_prefix, Stats::Scope& scope, Runtime::Loader& runtime, AccessLogSharedPtr access_log, const Filters::Common::Fault::FaultDelayConfigSharedPtr& fault_config, const Network::DrainDecision& drain_decision, TimeSource& time_source, - bool emit_dynamic_metadata) - : stat_prefix_(stat_prefix), scope_(scope), stats_(generateStats(stat_prefix, scope)), - runtime_(runtime), drain_decision_(drain_decision), access_log_(access_log), - fault_config_(fault_config), time_source_(time_source), - emit_dynamic_metadata_(emit_dynamic_metadata) { + bool emit_dynamic_metadata, const MongoStatsSharedPtr& mongo_stats) + : stat_prefix_(stat_prefix), stats_(generateStats(stat_prefix, scope)), runtime_(runtime), + drain_decision_(drain_decision), access_log_(access_log), fault_config_(fault_config), + time_source_(time_source), emit_dynamic_metadata_(emit_dynamic_metadata), + mongo_stats_(mongo_stats) { if (!runtime_.snapshot().featureEnabled(MongoRuntimeConfig::get().ConnectionLoggingEnabled, 100)) { // If we are not logging at the connection level, just release the shared pointer so that we @@ -145,21 +145,23 @@ void ProxyFilter::decodeQuery(QueryMessagePtr&& message) { ActiveQueryPtr active_query(new ActiveQuery(*this, *message)); if (!active_query->query_info_.command().empty()) { // First field key is the operation. - scope_.counter(fmt::format("{}cmd.{}.total", stat_prefix_, active_query->query_info_.command())) - .inc(); + mongo_stats_->incCounter({mongo_stats_->cmd_, + mongo_stats_->getStatName(active_query->query_info_.command()), + mongo_stats_->total_}); } else { // Normal query, get stats on a per collection basis first. - std::string collection_stat_prefix = - fmt::format("{}collection.{}", stat_prefix_, active_query->query_info_.collection()); QueryMessageInfo::QueryType query_type = active_query->query_info_.type(); - chargeQueryStats(collection_stat_prefix, query_type); + Stats::StatNameVec names; + names.reserve(6); // 2 entries are added by chargeQueryStats(). + names.push_back(mongo_stats_->collection_); + names.push_back(mongo_stats_->getStatName(active_query->query_info_.collection())); + chargeQueryStats(names, query_type); // Callsite stats if we have it. if (!active_query->query_info_.callsite().empty()) { - std::string callsite_stat_prefix = - fmt::format("{}collection.{}.callsite.{}", stat_prefix_, - active_query->query_info_.collection(), active_query->query_info_.callsite()); - chargeQueryStats(callsite_stat_prefix, query_type); + names.push_back(mongo_stats_->callsite_); + names.push_back(mongo_stats_->getStatName(active_query->query_info_.callsite())); + chargeQueryStats(names, query_type); } // Global stats. @@ -176,14 +178,26 @@ void ProxyFilter::decodeQuery(QueryMessagePtr&& message) { active_query_list_.emplace_back(std::move(active_query)); } -void ProxyFilter::chargeQueryStats(const std::string& prefix, +void ProxyFilter::chargeQueryStats(Stats::StatNameVec& names, QueryMessageInfo::QueryType query_type) { - scope_.counter(fmt::format("{}.query.total", prefix)).inc(); + // names come in containing {"collection", collection}. Report stats for 1 or + // 2 variations on this array, and then return with the array in the same + // state it had on entry. Both of these variations by appending {"query", "total"}. + size_t orig_size = names.size(); + ASSERT(names.capacity() - orig_size >= 2); // Ensures the caller has reserved() enough memory. + names.push_back(mongo_stats_->query_); + names.push_back(mongo_stats_->total_); + mongo_stats_->incCounter(names); + + // And now replace "total" with either "scatter_get" or "multi_get" if depending on query_type. if (query_type == QueryMessageInfo::QueryType::ScatterGet) { - scope_.counter(fmt::format("{}.query.scatter_get", prefix)).inc(); + names.back() = mongo_stats_->scatter_get_; + mongo_stats_->incCounter(names); } else if (query_type == QueryMessageInfo::QueryType::MultiGet) { - scope_.counter(fmt::format("{}.query.multi_get", prefix)).inc(); + names.back() = mongo_stats_->multi_get_; + mongo_stats_->incCounter(names); } + names.resize(orig_size); } void ProxyFilter::decodeReply(ReplyMessagePtr&& message) { @@ -208,21 +222,25 @@ void ProxyFilter::decodeReply(ReplyMessagePtr&& message) { } if (!active_query.query_info_.command().empty()) { - std::string stat_prefix = - fmt::format("{}cmd.{}", stat_prefix_, active_query.query_info_.command()); - chargeReplyStats(active_query, stat_prefix, *message); + Stats::StatNameVec names{mongo_stats_->cmd_, + mongo_stats_->getStatName(active_query.query_info_.command())}; + chargeReplyStats(active_query, names, *message); } else { // Collection stats first. - std::string stat_prefix = - fmt::format("{}collection.{}.query", stat_prefix_, active_query.query_info_.collection()); - chargeReplyStats(active_query, stat_prefix, *message); + Stats::StatNameVec names{mongo_stats_->collection_, + mongo_stats_->getStatName(active_query.query_info_.collection()), + mongo_stats_->query_}; + chargeReplyStats(active_query, names, *message); // Callsite stats if we have it. if (!active_query.query_info_.callsite().empty()) { - std::string callsite_stat_prefix = - fmt::format("{}collection.{}.callsite.{}.query", stat_prefix_, - active_query.query_info_.collection(), active_query.query_info_.callsite()); - chargeReplyStats(active_query, callsite_stat_prefix, *message); + // Currently, names == {"collection", collection, "query"} and we are going + // to mutate the array to {"collection", collection, "callsite", callsite, "query"}. + ASSERT(names.size() == 3); + names.back() = mongo_stats_->callsite_; // Replaces "query". + names.push_back(mongo_stats_->getStatName(active_query.query_info_.callsite())); + names.push_back(mongo_stats_->query_); + chargeReplyStats(active_query, names, *message); } } @@ -270,20 +288,26 @@ void ProxyFilter::onDrainClose() { read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); } -void ProxyFilter::chargeReplyStats(ActiveQuery& active_query, const std::string& prefix, +void ProxyFilter::chargeReplyStats(ActiveQuery& active_query, Stats::StatNameVec& names, const ReplyMessage& message) { uint64_t reply_documents_byte_size = 0; for (const Bson::DocumentSharedPtr& document : message.documents()) { reply_documents_byte_size += document->byteSize(); } - scope_.histogram(fmt::format("{}.reply_num_docs", prefix)) - .recordValue(message.documents().size()); - scope_.histogram(fmt::format("{}.reply_size", prefix)).recordValue(reply_documents_byte_size); - scope_.histogram(fmt::format("{}.reply_time_ms", prefix)) - .recordValue(std::chrono::duration_cast( - time_source_.monotonicTime() - active_query.start_time_) - .count()); + // Write 3 different histograms; appending 3 different suffixes to the name + // that was passed in. Here we overwrite the passed-in names, but we restore + // names to its original state upon return. + const size_t orig_size = names.size(); + names.push_back(mongo_stats_->reply_num_docs_); + mongo_stats_->recordHistogram(names, message.documents().size()); + names[orig_size] = mongo_stats_->reply_size_; + mongo_stats_->recordHistogram(names, reply_documents_byte_size); + names[orig_size] = mongo_stats_->reply_time_ms_; + mongo_stats_->recordHistogram(names, std::chrono::duration_cast( + time_source_.monotonicTime() - active_query.start_time_) + .count()); + names.resize(orig_size); } void ProxyFilter::doDecode(Buffer::Instance& buffer) { diff --git a/source/extensions/filters/network/mongo_proxy/proxy.h b/source/extensions/filters/network/mongo_proxy/proxy.h index f81ef86017..da85af19f2 100644 --- a/source/extensions/filters/network/mongo_proxy/proxy.h +++ b/source/extensions/filters/network/mongo_proxy/proxy.h @@ -25,6 +25,7 @@ #include "extensions/filters/common/fault/fault_config.h" #include "extensions/filters/network/mongo_proxy/codec.h" +#include "extensions/filters/network/mongo_proxy/mongo_stats.h" #include "extensions/filters/network/mongo_proxy/utility.h" namespace Envoy { @@ -110,7 +111,7 @@ class ProxyFilter : public Network::Filter, AccessLogSharedPtr access_log, const Filters::Common::Fault::FaultDelayConfigSharedPtr& fault_config, const Network::DrainDecision& drain_decision, TimeSource& time_system, - bool emit_dynamic_metadata); + bool emit_dynamic_metadata, const MongoStatsSharedPtr& stats); ~ProxyFilter() override; virtual DecoderPtr createDecoder(DecoderCallbacks& callbacks) PURE; @@ -164,9 +165,17 @@ class ProxyFilter : public Network::Filter, POOL_HISTOGRAM_PREFIX(scope, prefix))}; } - void chargeQueryStats(const std::string& prefix, QueryMessageInfo::QueryType query_type); - void chargeReplyStats(ActiveQuery& active_query, const std::string& prefix, + // Increment counters related to queries. 'names' is passed by non-const + // reference so the implementation can mutate it without copying, though it + // always restores it to its prior state prior to return. + void chargeQueryStats(Stats::StatNameVec& names, QueryMessageInfo::QueryType query_type); + + // Add samples to histograms related to replies. 'names' is passed by + // non-const reference so the implementation can mutate it without copying, + // though it always restores it to its prior state prior to return. + void chargeReplyStats(ActiveQuery& active_query, Stats::StatNameVec& names, const ReplyMessage& message); + void doDecode(Buffer::Instance& buffer); void logMessage(Message& message, bool full); void onDrainClose(); @@ -176,7 +185,6 @@ class ProxyFilter : public Network::Filter, std::unique_ptr decoder_; std::string stat_prefix_; - Stats::Scope& scope_; MongoProxyStats stats_; Runtime::Loader& runtime_; const Network::DrainDecision& drain_decision_; @@ -191,6 +199,7 @@ class ProxyFilter : public Network::Filter, Event::TimerPtr drain_close_timer_; TimeSource& time_source_; const bool emit_dynamic_metadata_; + MongoStatsSharedPtr mongo_stats_; }; class ProdProxyFilter : public ProxyFilter { diff --git a/source/extensions/filters/network/redis_proxy/BUILD b/source/extensions/filters/network/redis_proxy/BUILD index bbc18dff95..7178d82679 100644 --- a/source/extensions/filters/network/redis_proxy/BUILD +++ b/source/extensions/filters/network/redis_proxy/BUILD @@ -116,10 +116,12 @@ envoy_cc_library( hdrs = ["config.h"], deps = [ "//include/envoy/registry", + "//include/envoy/upstream:upstream_interface", "//source/common/config:filter_json_lib", "//source/extensions/filters/network:well_known_names", "//source/extensions/filters/network/common:factory_base_lib", "//source/extensions/filters/network/common/redis:codec_lib", + "//source/extensions/filters/network/common/redis:redis_command_stats_lib", "//source/extensions/filters/network/redis_proxy:command_splitter_lib", "//source/extensions/filters/network/redis_proxy:conn_pool_lib", "//source/extensions/filters/network/redis_proxy:proxy_filter_lib", diff --git a/source/extensions/filters/network/redis_proxy/config.cc b/source/extensions/filters/network/redis_proxy/config.cc index 5e3e401826..f14176ebe2 100644 --- a/source/extensions/filters/network/redis_proxy/config.cc +++ b/source/extensions/filters/network/redis_proxy/config.cc @@ -57,15 +57,19 @@ Network::FilterFactoryCb RedisProxyFilterConfigFactory::createFilterFactoryFromP } addUniqueClusters(unique_clusters, prefix_routes.catch_all_route()); + auto redis_command_stats = + Common::Redis::RedisCommandStats::createRedisCommandStats(context.scope().symbolTable()); + Upstreams upstreams; for (auto& cluster : unique_clusters) { Stats::ScopePtr stats_scope = context.scope().createScope(fmt::format("cluster.{}.redis_cluster", cluster)); + upstreams.emplace(cluster, std::make_shared( cluster, context.clusterManager(), Common::Redis::Client::ClientFactoryImpl::instance_, context.threadLocal(), proto_config.settings(), context.api(), - std::move(stats_scope))); + std::move(stats_scope), redis_command_stats)); } auto router = diff --git a/source/extensions/filters/network/redis_proxy/config.h b/source/extensions/filters/network/redis_proxy/config.h index 37d6a7c020..5a72b8ad9c 100644 --- a/source/extensions/filters/network/redis_proxy/config.h +++ b/source/extensions/filters/network/redis_proxy/config.h @@ -2,9 +2,12 @@ #include +#include "envoy/api/api.h" #include "envoy/config/filter/network/redis_proxy/v2/redis_proxy.pb.h" #include "envoy/config/filter/network/redis_proxy/v2/redis_proxy.pb.validate.h" +#include "envoy/upstream/upstream.h" +#include "common/common/empty_string.h" #include "common/config/datasource.h" #include "extensions/filters/network/common/factory_base.h" @@ -29,6 +32,16 @@ class ProtocolOptionsConfigImpl : public Upstream::ProtocolOptionsConfig { return auth_password_; } + static const std::string auth_password(const Upstream::ClusterInfoConstSharedPtr info, + Api::Api& api) { + auto options = info->extensionProtocolOptionsTyped( + NetworkFilterNames::get().RedisProxy); + if (options) { + return options->auth_password(api); + } + return EMPTY_STRING; + } + private: envoy::api::v2::core::DataSource auth_password_; }; diff --git a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc index 2e1414e025..7304ef9023 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.cc @@ -15,18 +15,17 @@ namespace Extensions { namespace NetworkFilters { namespace RedisProxy { namespace ConnPool { -namespace { -Common::Redis::Client::DoNothingPoolCallbacks null_pool_callbacks; -} // namespace InstanceImpl::InstanceImpl( const std::string& cluster_name, Upstream::ClusterManager& cm, Common::Redis::Client::ClientFactory& client_factory, ThreadLocal::SlotAllocator& tls, const envoy::config::filter::network::redis_proxy::v2::RedisProxy::ConnPoolSettings& config, - Api::Api& api, Stats::ScopePtr&& stats_scope) + Api::Api& api, Stats::ScopePtr&& stats_scope, + const Common::Redis::RedisCommandStatsSharedPtr& redis_command_stats) : cm_(cm), client_factory_(client_factory), tls_(tls.allocateSlot()), config_(config), - api_(api), stats_scope_(std::move(stats_scope)), redis_cluster_stats_{REDIS_CLUSTER_STATS( - POOL_COUNTER(*stats_scope_))} { + api_(api), stats_scope_(std::move(stats_scope)), + redis_command_stats_(redis_command_stats), redis_cluster_stats_{REDIS_CLUSTER_STATS( + POOL_COUNTER(*stats_scope_))} { tls_->set([this, cluster_name]( Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { return std::make_shared(*this, dispatcher, cluster_name); @@ -54,11 +53,7 @@ InstanceImpl::ThreadLocalPool::ThreadLocalPool(InstanceImpl& parent, Event::Disp cluster_update_handle_ = parent_.cm_.addThreadLocalClusterUpdateCallbacks(*this); Upstream::ThreadLocalCluster* cluster = parent_.cm_.get(cluster_name_); if (cluster != nullptr) { - auto options = cluster->info()->extensionProtocolOptionsTyped( - NetworkFilterNames::get().RedisProxy); - if (options) { - auth_password_ = options->auth_password(parent_.api_); - } + auth_password_ = ProtocolOptionsConfigImpl::auth_password(cluster->info(), parent_.api_); onClusterAddOrUpdateNonVirtual(*cluster); } } @@ -195,21 +190,10 @@ InstanceImpl::ThreadLocalPool::threadLocalActiveClient(Upstream::HostConstShared if (!client) { client = std::make_unique(*this); client->host_ = host; - client->redis_client_ = parent_.client_factory_.create(host, dispatcher_, parent_.config_); + client->redis_client_ = parent_.client_factory_.create(host, dispatcher_, parent_.config_, + parent_.redis_command_stats_, + *parent_.stats_scope_, auth_password_); client->redis_client_->addConnectionCallbacks(*client); - // TODO(hyang): should the auth command and readonly command be moved to the factory method? - if (!auth_password_.empty()) { - // Send an AUTH command to the upstream server. - client->redis_client_->makeRequest(Common::Redis::Utility::makeAuthCommand(auth_password_), - null_pool_callbacks); - } - // Any connection to replica requires the READONLY command in order to perform read. - // Also the READONLY command is a no-opt for the master. - // We only need to send the READONLY command iff it's possible that the host is a replica. - if (parent_.config_.readPolicy() != Common::Redis::Client::ReadPolicy::Master) { - client->redis_client_->makeRequest(Common::Redis::Utility::ReadOnlyRequest::instance(), - null_pool_callbacks); - } } return client; } @@ -225,8 +209,8 @@ InstanceImpl::ThreadLocalPool::makeRequest(const std::string& key, } const bool use_crc16 = is_redis_cluster_; - Clusters::Redis::RedisLoadBalancerContextImpl lb_context(key, parent_.config_.enableHashtagging(), - use_crc16, request); + Clusters::Redis::RedisLoadBalancerContextImpl lb_context( + key, parent_.config_.enableHashtagging(), use_crc16, request, parent_.config_.readPolicy()); Upstream::HostConstSharedPtr host = cluster_->loadBalancer().chooseHost(&lb_context); if (!host) { return nullptr; 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 91ea51f3e7..7731943b87 100644 --- a/source/extensions/filters/network/redis_proxy/conn_pool_impl.h +++ b/source/extensions/filters/network/redis_proxy/conn_pool_impl.h @@ -51,7 +51,8 @@ class InstanceImpl : public Instance { const std::string& cluster_name, Upstream::ClusterManager& cm, Common::Redis::Client::ClientFactory& client_factory, ThreadLocal::SlotAllocator& tls, const envoy::config::filter::network::redis_proxy::v2::RedisProxy::ConnPoolSettings& config, - Api::Api& api, Stats::ScopePtr&& stats_scope); + Api::Api& api, Stats::ScopePtr&& stats_scope, + const Common::Redis::RedisCommandStatsSharedPtr& redis_command_stats); // RedisProxy::ConnPool::Instance Common::Redis::Client::PoolRequest* makeRequest(const std::string& key, const Common::Redis::RespValue& request, @@ -131,6 +132,7 @@ class InstanceImpl : public Instance { Common::Redis::Client::ConfigImpl config_; Api::Api& api_; Stats::ScopePtr stats_scope_; + Common::Redis::RedisCommandStatsSharedPtr redis_command_stats_; RedisClusterStats redis_cluster_stats_; }; diff --git a/source/extensions/filters/network/redis_proxy/router_impl.cc b/source/extensions/filters/network/redis_proxy/router_impl.cc index 6cfa590220..1cb86f6876 100644 --- a/source/extensions/filters/network/redis_proxy/router_impl.cc +++ b/source/extensions/filters/network/redis_proxy/router_impl.cc @@ -10,7 +10,9 @@ MirrorPolicyImpl::MirrorPolicyImpl(const envoy::config::filter::network::redis_p const ConnPool::InstanceSharedPtr upstream, Runtime::Loader& runtime) : runtime_key_(config.runtime_fraction().runtime_key()), - default_value_(config.runtime_fraction().default_value()), + default_value_(config.has_runtime_fraction() ? absl::optional( + config.runtime_fraction().default_value()) + : absl::nullopt), exclude_read_commands_(config.exclude_read_commands()), upstream_(upstream), runtime_(runtime) {} @@ -23,8 +25,8 @@ bool MirrorPolicyImpl::shouldMirror(const std::string& command) const { return false; } - if (default_value_.numerator() > 0) { - return runtime_.snapshot().featureEnabled(runtime_key_, default_value_); + if (default_value_.has_value()) { + return runtime_.snapshot().featureEnabled(runtime_key_, default_value_.value()); } return true; diff --git a/source/extensions/filters/network/redis_proxy/router_impl.h b/source/extensions/filters/network/redis_proxy/router_impl.h index 26963adb18..fdacf76089 100644 --- a/source/extensions/filters/network/redis_proxy/router_impl.h +++ b/source/extensions/filters/network/redis_proxy/router_impl.h @@ -37,7 +37,7 @@ class MirrorPolicyImpl : public MirrorPolicy { private: const std::string runtime_key_; - const envoy::type::FractionalPercent default_value_; + const absl::optional default_value_; const bool exclude_read_commands_; ConnPool::InstanceSharedPtr upstream_; Runtime::Loader& runtime_; diff --git a/source/extensions/filters/network/thrift_proxy/conn_manager.cc b/source/extensions/filters/network/thrift_proxy/conn_manager.cc index 509d2f42d3..8f697c0a60 100644 --- a/source/extensions/filters/network/thrift_proxy/conn_manager.cc +++ b/source/extensions/filters/network/thrift_proxy/conn_manager.cc @@ -77,8 +77,14 @@ void ConnectionManager::dispatch() { } catch (const EnvoyException& ex) { ENVOY_CONN_LOG(error, "thrift error: {}", read_callbacks_->connection(), ex.what()); - // Use the current rpc to send an error downstream, if possible. - rpcs_.front()->onError(ex.what()); + if (rpcs_.empty()) { + // Transport/protocol mismatch (including errors in automatic detection). Just hang up + // since we don't know how to encode a response. + read_callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); + } else { + // Use the current rpc's transport/protocol to send an error downstream. + rpcs_.front()->onError(ex.what()); + } } stats_.request_decoding_error_.inc(); diff --git a/source/extensions/filters/network/thrift_proxy/filters/factory_base.h b/source/extensions/filters/network/thrift_proxy/filters/factory_base.h index bf2bb292f0..159328b73a 100644 --- a/source/extensions/filters/network/thrift_proxy/filters/factory_base.h +++ b/source/extensions/filters/network/thrift_proxy/filters/factory_base.h @@ -16,8 +16,9 @@ template class FactoryBase : public NamedThriftFilterConfigF createFilterFactoryFromProto(const Protobuf::Message& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override { - return createFilterFactoryFromProtoTyped( - MessageUtil::downcastAndValidate(proto_config), stats_prefix, context); + return createFilterFactoryFromProtoTyped(MessageUtil::downcastAndValidate( + proto_config, context.messageValidationVisitor()), + stats_prefix, context); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc index 1fc44b5f78..81a9b015ee 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc @@ -22,11 +22,9 @@ namespace Router { RouteEntryImplBase::RouteEntryImplBase( const envoy::config::filter::network::thrift_proxy::v2alpha1::Route& route) - : cluster_name_(route.route().cluster()), rate_limit_policy_(route.route().rate_limits()) { - for (const auto& header_map : route.match().headers()) { - config_headers_.push_back(header_map); - } - + : cluster_name_(route.route().cluster()), + config_headers_(Http::HeaderUtility::buildHeaderDataVector(route.match().headers())), + rate_limit_policy_(route.route().rate_limits()) { if (route.route().has_metadata_match()) { const auto filter_it = route.route().metadata_match().filter_metadata().find( Envoy::Config::MetadataFilters::get().ENVOY_LB); 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 de8bc9e0d4..e6db1bef68 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.h +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.h @@ -83,7 +83,7 @@ class RouteEntryImplBase : public RouteEntry, using WeightedClusterEntrySharedPtr = std::shared_ptr; const std::string cluster_name_; - std::vector config_headers_; + const std::vector config_headers_; std::vector weighted_clusters_; uint64_t total_cluster_weight_; Envoy::Router::MetadataMatchCriteriaConstPtr metadata_match_criteria_; diff --git a/source/extensions/filters/network/thrift_proxy/router/router_ratelimit_impl.cc b/source/extensions/filters/network/thrift_proxy/router/router_ratelimit_impl.cc index c1f3f99f7c..a9b2b1196f 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_ratelimit_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/router/router_ratelimit_impl.cc @@ -70,11 +70,8 @@ bool GenericKeyAction::populateDescriptor(const RouteEntry&, RateLimit::Descript HeaderValueMatchAction::HeaderValueMatchAction( const envoy::api::v2::route::RateLimit::Action::HeaderValueMatch& action) : descriptor_value_(action.descriptor_value()), - expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)) { - for (const auto& header_matcher : action.headers()) { - action_headers_.push_back(header_matcher); - } -} + expect_match_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(action, expect_match, true)), + action_headers_(Http::HeaderUtility::buildHeaderDataVector(action.headers())) {} bool HeaderValueMatchAction::populateDescriptor(const RouteEntry&, RateLimit::Descriptor& descriptor, diff --git a/source/extensions/filters/network/thrift_proxy/router/router_ratelimit_impl.h b/source/extensions/filters/network/thrift_proxy/router/router_ratelimit_impl.h index 4423e06338..976456ec20 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_ratelimit_impl.h +++ b/source/extensions/filters/network/thrift_proxy/router/router_ratelimit_impl.h @@ -104,7 +104,7 @@ class HeaderValueMatchAction : public RateLimitAction { private: const std::string descriptor_value_; const bool expect_match_; - std::vector action_headers_; + const std::vector action_headers_; }; /* diff --git a/source/extensions/grpc_credentials/aws_iam/config.cc b/source/extensions/grpc_credentials/aws_iam/config.cc index 3128a96084..d2b423f744 100644 --- a/source/extensions/grpc_credentials/aws_iam/config.cc +++ b/source/extensions/grpc_credentials/aws_iam/config.cc @@ -34,12 +34,15 @@ std::shared_ptr AwsIamGrpcCredentialsFactory::getChann case envoy::api::v2::core::GrpcService::GoogleGrpc::CallCredentials::kFromPlugin: { if (credential.from_plugin().name() == GrpcCredentialsNames::get().AwsIam) { AwsIamGrpcCredentialsFactory credentials_factory; + // We don't deal with validation failures here at runtime today, see + // https://github.com/envoyproxy/envoy/issues/8010. const Envoy::ProtobufTypes::MessagePtr config_message = Envoy::Config::Utility::translateToFactoryConfig( credential.from_plugin(), ProtobufMessage::getNullValidationVisitor(), credentials_factory); const auto& config = Envoy::MessageUtil::downcastAndValidate< - const envoy::config::grpc_credential::v2alpha::AwsIamConfig&>(*config_message); + const envoy::config::grpc_credential::v2alpha::AwsIamConfig&>( + *config_message, ProtobufMessage::getNullValidationVisitor()); auto credentials_provider = std::make_shared( api, HttpFilters::Common::Aws::Utility::metadataFetcher); diff --git a/source/extensions/grpc_credentials/file_based_metadata/config.cc b/source/extensions/grpc_credentials/file_based_metadata/config.cc index e40c4549f1..03eabb5e90 100644 --- a/source/extensions/grpc_credentials/file_based_metadata/config.cc +++ b/source/extensions/grpc_credentials/file_based_metadata/config.cc @@ -28,16 +28,15 @@ FileBasedMetadataGrpcCredentialsFactory::getChannelCredentials( case envoy::api::v2::core::GrpcService::GoogleGrpc::CallCredentials::kFromPlugin: { if (credential.from_plugin().name() == GrpcCredentialsNames::get().FileBasedMetadata) { FileBasedMetadataGrpcCredentialsFactory file_based_metadata_credentials_factory; - // TODO(htuch): This should probably follow the standard metadata validation pattern, but - // needs error handling, since this happens at runtime, not config time, and we need to - // catch any validation exceptions. + // We don't deal with validation failures here at runtime today, see + // https://github.com/envoyproxy/envoy/issues/8010. const Envoy::ProtobufTypes::MessagePtr file_based_metadata_config_message = Envoy::Config::Utility::translateToFactoryConfig( credential.from_plugin(), ProtobufMessage::getNullValidationVisitor(), file_based_metadata_credentials_factory); const auto& file_based_metadata_config = Envoy::MessageUtil::downcastAndValidate< const envoy::config::grpc_credential::v2alpha::FileBasedMetadataConfig&>( - *file_based_metadata_config_message); + *file_based_metadata_config_message, ProtobufMessage::getNullValidationVisitor()); std::shared_ptr new_call_creds = grpc::MetadataCredentialsFromPlugin( std::make_unique(file_based_metadata_config, api)); if (call_creds == nullptr) { diff --git a/source/extensions/health_checkers/redis/BUILD b/source/extensions/health_checkers/redis/BUILD index 1c92f36629..5c3c7bdffe 100644 --- a/source/extensions/health_checkers/redis/BUILD +++ b/source/extensions/health_checkers/redis/BUILD @@ -17,6 +17,7 @@ envoy_cc_library( deps = [ "//source/common/upstream:health_checker_base_lib", "//source/extensions/filters/network/common/redis:client_lib", + "//source/extensions/filters/network/redis_proxy:config", "//source/extensions/filters/network/redis_proxy:conn_pool_lib", "@envoy_api//envoy/api/v2/core:health_check_cc", "@envoy_api//envoy/config/health_checker/redis/v2:redis_cc", diff --git a/source/extensions/health_checkers/redis/config.cc b/source/extensions/health_checkers/redis/config.cc index 6892781ea8..a3bdb6eab6 100644 --- a/source/extensions/health_checkers/redis/config.cc +++ b/source/extensions/health_checkers/redis/config.cc @@ -17,7 +17,7 @@ Upstream::HealthCheckerSharedPtr RedisHealthCheckerFactory::createCustomHealthCh return std::make_shared( context.cluster(), config, getRedisHealthCheckConfig(config, context.messageValidationVisitor()), context.dispatcher(), - context.runtime(), context.random(), context.eventLogger(), + context.runtime(), context.random(), context.eventLogger(), context.api(), NetworkFilters::Common::Redis::Client::ClientFactoryImpl::instance_); }; diff --git a/source/extensions/health_checkers/redis/redis.cc b/source/extensions/health_checkers/redis/redis.cc index 9f13082463..22a7bd9be0 100644 --- a/source/extensions/health_checkers/redis/redis.cc +++ b/source/extensions/health_checkers/redis/redis.cc @@ -9,10 +9,12 @@ RedisHealthChecker::RedisHealthChecker( const Upstream::Cluster& cluster, const envoy::api::v2::core::HealthCheck& config, const envoy::config::health_checker::redis::v2::Redis& redis_config, Event::Dispatcher& dispatcher, Runtime::Loader& runtime, Runtime::RandomGenerator& random, - Upstream::HealthCheckEventLoggerPtr&& event_logger, + Upstream::HealthCheckEventLoggerPtr&& event_logger, Api::Api& api, Extensions::NetworkFilters::Common::Redis::Client::ClientFactory& client_factory) : HealthCheckerImplBase(cluster, config, dispatcher, runtime, random, std::move(event_logger)), - client_factory_(client_factory), key_(redis_config.key()) { + client_factory_(client_factory), key_(redis_config.key()), + auth_password_(NetworkFilters::RedisProxy::ProtocolOptionsConfigImpl::auth_password( + cluster.info(), api)) { if (!key_.empty()) { type_ = Type::Exists; } else { @@ -22,7 +24,11 @@ RedisHealthChecker::RedisHealthChecker( RedisHealthChecker::RedisActiveHealthCheckSession::RedisActiveHealthCheckSession( RedisHealthChecker& parent, const Upstream::HostSharedPtr& host) - : ActiveHealthCheckSession(parent, host), parent_(parent) {} + : ActiveHealthCheckSession(parent, host), parent_(parent) { + redis_command_stats_ = + Extensions::NetworkFilters::Common::Redis::RedisCommandStats::createRedisCommandStats( + parent_.cluster_.info()->statsScope().symbolTable()); +} RedisHealthChecker::RedisActiveHealthCheckSession::~RedisActiveHealthCheckSession() { ASSERT(current_request_ == nullptr); @@ -51,7 +57,9 @@ void RedisHealthChecker::RedisActiveHealthCheckSession::onEvent(Network::Connect void RedisHealthChecker::RedisActiveHealthCheckSession::onInterval() { if (!client_) { - client_ = parent_.client_factory_.create(host_, parent_.dispatcher_, *this); + client_ = parent_.client_factory_.create( + host_, parent_.dispatcher_, *this, redis_command_stats_, + parent_.cluster_.info()->statsScope(), parent_.auth_password_); client_->addConnectionCallbacks(*this); } diff --git a/source/extensions/health_checkers/redis/redis.h b/source/extensions/health_checkers/redis/redis.h index 4c7cc320e0..5268e6e8e4 100644 --- a/source/extensions/health_checkers/redis/redis.h +++ b/source/extensions/health_checkers/redis/redis.h @@ -2,11 +2,13 @@ #include +#include "envoy/api/api.h" #include "envoy/config/health_checker/redis/v2/redis.pb.validate.h" #include "common/upstream/health_checker_base_impl.h" #include "extensions/filters/network/common/redis/client_impl.h" +#include "extensions/filters/network/redis_proxy/config.h" #include "extensions/filters/network/redis_proxy/conn_pool_impl.h" namespace Envoy { @@ -23,7 +25,7 @@ class RedisHealthChecker : public Upstream::HealthCheckerImplBase { const Upstream::Cluster& cluster, const envoy::api::v2::core::HealthCheck& config, const envoy::config::health_checker::redis::v2::Redis& redis_config, Event::Dispatcher& dispatcher, Runtime::Loader& runtime, Runtime::RandomGenerator& random, - Upstream::HealthCheckEventLoggerPtr&& event_logger, + Upstream::HealthCheckEventLoggerPtr&& event_logger, Api::Api& api, Extensions::NetworkFilters::Common::Redis::Client::ClientFactory& client_factory); static const NetworkFilters::Common::Redis::RespValue& pingHealthCheckRequest() { @@ -81,6 +83,7 @@ class RedisHealthChecker : public Upstream::HealthCheckerImplBase { } uint32_t maxUpstreamUnknownConnections() const override { return 0; } + bool enableCommandStats() const override { return false; } // Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks void onResponse(NetworkFilters::Common::Redis::RespValuePtr&& value) override; @@ -95,6 +98,7 @@ class RedisHealthChecker : public Upstream::HealthCheckerImplBase { RedisHealthChecker& parent_; Extensions::NetworkFilters::Common::Redis::Client::ClientPtr client_; Extensions::NetworkFilters::Common::Redis::Client::PoolRequest* current_request_{}; + Extensions::NetworkFilters::Common::Redis::RedisCommandStatsSharedPtr redis_command_stats_; }; enum class Type { Ping, Exists }; @@ -116,6 +120,7 @@ class RedisHealthChecker : public Upstream::HealthCheckerImplBase { Extensions::NetworkFilters::Common::Redis::Client::ClientFactory& client_factory_; Type type_; const std::string key_; + const std::string auth_password_; }; } // namespace RedisHealthChecker diff --git a/source/extensions/health_checkers/redis/utility.h b/source/extensions/health_checkers/redis/utility.h index df868f85a3..b05a1856f2 100644 --- a/source/extensions/health_checkers/redis/utility.h +++ b/source/extensions/health_checkers/redis/utility.h @@ -21,7 +21,7 @@ getRedisHealthCheckConfig(const envoy::api::v2::core::HealthCheck& health_check_ MessageUtil::jsonConvert(health_check_config.custom_health_check().config(), validation_visitor, *config); return MessageUtil::downcastAndValidate( - *config); + *config, validation_visitor); } } // namespace diff --git a/source/extensions/quic_listeners/quiche/BUILD b/source/extensions/quic_listeners/quiche/BUILD index b4b3369fe9..be1fa5ff80 100644 --- a/source/extensions/quic_listeners/quiche/BUILD +++ b/source/extensions/quic_listeners/quiche/BUILD @@ -35,11 +35,24 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_connection_helper_lib", + hdrs = ["envoy_quic_connection_helper.h"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche/platform:envoy_quic_clock_lib", + "@com_googlesource_quiche//:quic_core_buffer_allocator_lib", + "@com_googlesource_quiche//:quic_core_connection_lib", + "@com_googlesource_quiche//:quic_core_crypto_random_lib", + ], +) + envoy_cc_library( name = "envoy_quic_packet_writer_lib", srcs = ["envoy_quic_packet_writer.cc"], hdrs = ["envoy_quic_packet_writer.h"], external_deps = ["quiche_quic_platform"], + tags = ["nofips"], deps = [ ":envoy_quic_utils_lib", "@com_googlesource_quiche//:quic_core_packet_writer_interface_lib", @@ -71,6 +84,7 @@ envoy_cc_library( envoy_cc_library( name = "spdy_server_push_utils_for_envoy_lib", srcs = ["spdy_server_push_utils_for_envoy.cc"], + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ "//source/common/common:assert_lib", @@ -78,9 +92,145 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "envoy_quic_stream_lib", + hdrs = ["envoy_quic_stream.h"], + tags = ["nofips"], + deps = [ + "//include/envoy/http:codec_interface", + "//source/common/http:codec_helper_lib", + ], +) + +envoy_cc_library( + name = "envoy_quic_server_stream_lib", + srcs = ["envoy_quic_server_stream.cc"], + hdrs = ["envoy_quic_server_stream.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_stream_lib", + ":envoy_quic_utils_lib", + "//source/common/buffer:buffer_lib", + "//source/common/common:assert_lib", + "//source/common/http:header_map_lib", + "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + ], +) + +envoy_cc_library( + name = "codec_impl_lib", + srcs = ["codec_impl.cc"], + hdrs = ["codec_impl.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_server_session_lib", + "//include/envoy/http:codec_interface", + "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + ], +) + +envoy_cc_library( + name = "envoy_quic_server_session_lib", + srcs = ["envoy_quic_server_session.cc"], + hdrs = ["envoy_quic_server_session.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_connection_lib", + ":envoy_quic_server_stream_lib", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/network:connection_interface", + "//source/common/common:empty_string", + "//source/common/network:filter_manager_lib", + "//source/common/stream_info:stream_info_lib", + "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + ], +) + +envoy_cc_library( + name = "quic_io_handle_wrapper_lib", + hdrs = ["quic_io_handle_wrapper.h"], + deps = [ + "//include/envoy/network:io_handle_interface", + "//source/common/network:io_socket_error_lib", + ], +) + +envoy_cc_library( + name = "envoy_quic_connection_lib", + srcs = ["envoy_quic_connection.cc"], + hdrs = ["envoy_quic_connection.h"], + tags = ["nofips"], + deps = [ + ":quic_io_handle_wrapper_lib", + "//include/envoy/network:connection_interface", + "//source/common/network:listen_socket_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_utils_lib", + "//source/server:connection_handler_lib", + "@com_googlesource_quiche//:quic_core_connection_lib", + ], +) + +envoy_cc_library( + name = "envoy_quic_dispatcher_lib", + srcs = [ + "envoy_quic_dispatcher.cc", + ], + hdrs = [ + "envoy_quic_dispatcher.h", + ], + tags = ["nofips"], + deps = [ + ":envoy_quic_connection_lib", + ":envoy_quic_proof_source_lib", + ":envoy_quic_server_session_lib", + "//include/envoy/network:listener_interface", + "//source/server:connection_handler_lib", + "@com_googlesource_quiche//:quic_core_server_lib", + "@com_googlesource_quiche//:quic_core_utils_lib", + ], +) + +envoy_cc_library( + name = "active_quic_listener_lib", + srcs = ["active_quic_listener.cc"], + hdrs = ["active_quic_listener.h"], + tags = ["nofips"], + deps = [ + ":envoy_quic_alarm_factory_lib", + ":envoy_quic_connection_helper_lib", + ":envoy_quic_dispatcher_lib", + ":envoy_quic_packet_writer_lib", + ":envoy_quic_proof_source_lib", + ":envoy_quic_utils_lib", + "//include/envoy/network:listener_interface", + "//source/common/network:listener_lib", + "//source/common/protobuf:utility_lib", + "//source/server:connection_handler_lib", + "@envoy_api//envoy/api/v2/listener:quic_config_cc", + ], +) + +envoy_cc_library( + name = "active_quic_listener_config_lib", + srcs = ["active_quic_listener_config.cc"], + hdrs = ["active_quic_listener_config.h"], + tags = ["nofips"], + deps = [ + ":active_quic_listener_lib", + "//include/envoy/registry", + ], +) + envoy_cc_library( name = "envoy_quic_utils_lib", + srcs = ["envoy_quic_utils.cc"], hdrs = ["envoy_quic_utils.h"], external_deps = ["quiche_quic_platform"], - deps = ["//source/common/network:address_lib"], + tags = ["nofips"], + deps = [ + "//include/envoy/http:codec_interface", + "//source/common/http:header_map_lib", + "//source/common/network:address_lib", + "@com_googlesource_quiche//:quic_core_http_header_list_lib", + ], ) diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.cc b/source/extensions/quic_listeners/quiche/active_quic_listener.cc new file mode 100644 index 0000000000..5f6b5ff71a --- /dev/null +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.cc @@ -0,0 +1,84 @@ +#include "extensions/quic_listeners/quiche/active_quic_listener.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_dispatcher.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" +#include "extensions/quic_listeners/quiche/envoy_quic_packet_writer.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + +namespace Envoy { +namespace Quic { + +ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, + Network::ConnectionHandler& parent, spdlog::logger& logger, + Network::ListenerConfig& listener_config, + const quic::QuicConfig& quic_config) + : ActiveQuicListener(dispatcher, parent, + dispatcher.createUdpListener(listener_config.socket(), *this), logger, + listener_config, quic_config) {} + +ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, + Network::ConnectionHandler& parent, + Network::UdpListenerPtr&& listener, spdlog::logger& logger, + Network::ListenerConfig& listener_config, + const quic::QuicConfig& quic_config) + : ActiveQuicListener(dispatcher, parent, std::make_unique(*listener), + std::move(listener), logger, listener_config, quic_config) {} + +ActiveQuicListener::ActiveQuicListener(Event::Dispatcher& dispatcher, + Network::ConnectionHandler& parent, + std::unique_ptr writer, + Network::UdpListenerPtr&& listener, spdlog::logger& logger, + Network::ListenerConfig& listener_config, + const quic::QuicConfig& quic_config) + : Server::ConnectionHandlerImpl::ActiveListenerImplBase(std::move(listener), listener_config), + logger_(logger), dispatcher_(dispatcher), version_manager_(quic::CurrentSupportedVersions()) { + quic::QuicRandom* const random = quic::QuicRandom::GetInstance(); + random->RandBytes(random_seed_, sizeof(random_seed_)); + crypto_config_ = std::make_unique( + quic::QuicStringPiece(reinterpret_cast(random_seed_), sizeof(random_seed_)), + quic::QuicRandom::GetInstance(), std::make_unique(), + quic::KeyExchangeSource::Default()); + auto connection_helper = std::make_unique(dispatcher_); + auto alarm_factory = + std::make_unique(dispatcher_, *connection_helper->GetClock()); + quic_dispatcher_ = std::make_unique( + crypto_config_.get(), quic_config, &version_manager_, std::move(connection_helper), + std::move(alarm_factory), quic::kQuicDefaultConnectionIdLength, parent, config_, stats_, + dispatcher); + quic_dispatcher_->InitializeWithWriter(writer.release()); +} + +void ActiveQuicListener::onListenerShutdown() { + ENVOY_LOG_TO_LOGGER(logger_, info, "Quic listener {} shutdown.", config_.name()); + quic_dispatcher_->Shutdown(); +} + +void ActiveQuicListener::onData(Network::UdpRecvData& data) { + quic::QuicSocketAddress peer_address(envoyAddressInstanceToQuicSocketAddress(data.peer_address_)); + quic::QuicSocketAddress self_address( + envoyAddressInstanceToQuicSocketAddress(data.local_address_)); + quic::QuicTime timestamp = + quic::QuicTime::Zero() + + quic::QuicTime::Delta::FromMilliseconds(std::chrono::duration_cast( + data.receive_time_.time_since_epoch()) + .count()); + uint64_t num_slice = data.buffer_->getRawSlices(nullptr, 0); + ASSERT(num_slice == 1); + Buffer::RawSlice slice; + data.buffer_->getRawSlices(&slice, 1); + // TODO(danzh): pass in TTL and UDP header. + quic::QuicReceivedPacket packet(reinterpret_cast(slice.mem_), slice.len_, timestamp, + /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true, + /*packet_headers=*/nullptr, /*headers_length=*/0, + /*owns_header_buffer*/ false); + quic_dispatcher_->ProcessPacket(self_address, peer_address, packet); +} + +void ActiveQuicListener::onWriteReady(const Network::Socket& /*socket*/) { + quic_dispatcher_->OnCanWrite(); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener.h b/source/extensions/quic_listeners/quiche/active_quic_listener.h new file mode 100644 index 0000000000..bb327d12fd --- /dev/null +++ b/source/extensions/quic_listeners/quiche/active_quic_listener.h @@ -0,0 +1,107 @@ +#pragma once + +#include "envoy/api/v2/listener/quic_config.pb.h" +#include "envoy/network/connection_handler.h" +#include "envoy/network/listener.h" + +#include "common/protobuf/utility.h" + +#include "server/connection_handler_impl.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_dispatcher.h" + +namespace Envoy { +namespace Quic { + +// QUIC specific UdpListenerCallbacks implemention which delegates incoming +// packets, write signals and listener errors to QuicDispatcher. +class ActiveQuicListener : public Network::UdpListenerCallbacks, + public Server::ConnectionHandlerImpl::ActiveListenerImplBase, + // Inherits below two interfaces just to have common + // interfaces. Not expected to support listener + // filter. + // TODO(danzh): clean up meaningless inheritance. + public Network::UdpListenerFilterManager, + public Network::UdpReadFilterCallbacks { +public: + ActiveQuicListener(Event::Dispatcher& dispatcher, Network::ConnectionHandler& parent, + spdlog::logger& logger, Network::ListenerConfig& listener_config, + const quic::QuicConfig& quic_config); + + ActiveQuicListener(Event::Dispatcher& dispatcher, Network::ConnectionHandler& parent, + Network::UdpListenerPtr&& listener, spdlog::logger& logger, + Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); + + // TODO(#7465): Make this a callback. + void onListenerShutdown(); + + // Network::UdpListenerCallbacks + void onData(Network::UdpRecvData& data) override; + void onWriteReady(const Network::Socket& socket) override; + void onReceiveError(const Network::UdpListenerCallbacks::ErrorCode& /*error_code*/, + Api::IoError::IoErrorCode /*err*/) override { + // No-op. Quic can't do anything upon listener error. + } + + // Network::UdpListenerFilterManager + void addReadFilter(Network::UdpListenerReadFilterPtr&& /*filter*/) override { + // QUIC doesn't support listener filter. + NOT_REACHED_GCOVR_EXCL_LINE; + } + + // Network::UdpReadFilterCallbacks + Network::UdpListener& udpListener() override { NOT_REACHED_GCOVR_EXCL_LINE; } + +private: + friend class ActiveQuicListenerPeer; + + ActiveQuicListener(Event::Dispatcher& dispatcher, Network::ConnectionHandler& parent, + std::unique_ptr writer, + Network::UdpListenerPtr&& listener, spdlog::logger& logger, + Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config); + + uint8_t random_seed_[16]; + std::unique_ptr crypto_config_; + spdlog::logger& logger_; + Event::Dispatcher& dispatcher_; + quic::QuicVersionManager version_manager_; + std::unique_ptr quic_dispatcher_; +}; + +using ActiveQuicListenerPtr = std::unique_ptr; + +// A factory to create ActiveQuicListener based on given config. +class ActiveQuicListenerFactory : public Network::ActiveUdpListenerFactory { +public: + ActiveQuicListenerFactory(const envoy::api::v2::listener::QuicProtocolOptions& config) { + uint64_t idle_network_timeout_ms = + config.has_idle_timeout() ? DurationUtil::durationToMilliseconds(config.idle_timeout()) + : 300000; + quic_config_.SetIdleNetworkTimeout( + quic::QuicTime::Delta::FromMilliseconds(idle_network_timeout_ms), + quic::QuicTime::Delta::FromMilliseconds(idle_network_timeout_ms)); + int32_t max_time_before_crypto_handshake_ms = + config.has_crypto_handshake_timeout() + ? DurationUtil::durationToMilliseconds(config.crypto_handshake_timeout()) + : 20000; + quic_config_.set_max_time_before_crypto_handshake( + quic::QuicTime::Delta::FromMilliseconds(max_time_before_crypto_handshake_ms)); + int32_t max_streams = PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, max_concurrent_streams, 100); + quic_config_.SetMaxIncomingBidirectionalStreamsToSend(max_streams); + quic_config_.SetMaxIncomingUnidirectionalStreamsToSend(max_streams); + } + + Network::ConnectionHandler::ActiveListenerPtr + createActiveUdpListener(Network::ConnectionHandler& parent, Event::Dispatcher& disptacher, + spdlog::logger& logger, Network::ListenerConfig& config) const override { + return std::make_unique(disptacher, parent, logger, config, quic_config_); + } + +private: + friend class ActiveQuicListenerFactoryPeer; + + quic::QuicConfig quic_config_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener_config.cc b/source/extensions/quic_listeners/quiche/active_quic_listener_config.cc new file mode 100644 index 0000000000..3cdadd8e0a --- /dev/null +++ b/source/extensions/quic_listeners/quiche/active_quic_listener_config.cc @@ -0,0 +1,25 @@ +#include "extensions/quic_listeners/quiche/active_quic_listener_config.h" + +#include "envoy/api/v2/listener/quic_config.pb.h" + +#include "extensions/quic_listeners/quiche/active_quic_listener.h" + +namespace Envoy { +namespace Quic { + +ProtobufTypes::MessagePtr ActiveQuicListenerConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +Network::ActiveUdpListenerFactoryPtr +ActiveQuicListenerConfigFactory::createActiveUdpListenerFactory(const Protobuf::Message& message) { + auto& config = dynamic_cast(message); + return std::make_unique(config); +} + +std::string ActiveQuicListenerConfigFactory::name() { return QuicListenerName; } + +REGISTER_FACTORY(ActiveQuicListenerConfigFactory, Server::ActiveUdpListenerConfigFactory); + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/active_quic_listener_config.h b/source/extensions/quic_listeners/quiche/active_quic_listener_config.h new file mode 100644 index 0000000000..78d6e7bb88 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/active_quic_listener_config.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "envoy/registry/registry.h" +#include "envoy/server/active_udp_listener_config.h" + +namespace Envoy { +namespace Quic { + +const std::string QuicListenerName{"quiche_quic_listener"}; + +// A factory to create ActiveQuicListenerFactory based on given protobuf. +class ActiveQuicListenerConfigFactory : public Server::ActiveUdpListenerConfigFactory { +public: + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + Network::ActiveUdpListenerFactoryPtr + createActiveUdpListenerFactory(const Protobuf::Message&) override; + + std::string name() override; +}; + +DECLARE_FACTORY(ActiveQuicListenerConfigFactory); + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.cc b/source/extensions/quic_listeners/quiche/codec_impl.cc new file mode 100644 index 0000000000..4d9846ac6b --- /dev/null +++ b/source/extensions/quic_listeners/quiche/codec_impl.cc @@ -0,0 +1,23 @@ +#include "extensions/quic_listeners/quiche/codec_impl.h" + +namespace Envoy { +namespace Quic { + +void QuicHttpConnectionImplBase::goAway() { + quic_session_.SendGoAway(quic::QUIC_PEER_GOING_AWAY, "server shutdown imminent"); +} + +bool QuicHttpConnectionImplBase::wantsToWrite() { return quic_session_.HasDataToWrite(); } + +// TODO(danzh): modify QUIC stack to react based on aggregated bytes across all +// the streams. And call StreamCallbackHelper::runHighWatermarkCallbacks() for each stream. +void QuicHttpConnectionImplBase::onUnderlyingConnectionAboveWriteBufferHighWatermark() { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} + +void QuicHttpConnectionImplBase::onUnderlyingConnectionBelowWriteBufferLowWatermark() { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/codec_impl.h b/source/extensions/quic_listeners/quiche/codec_impl.h new file mode 100644 index 0000000000..4920b50a02 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/codec_impl.h @@ -0,0 +1,58 @@ +#include "envoy/http/codec.h" + +#include "common/common/assert.h" +#include "common/common/logger.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" + +namespace Envoy { +namespace Quic { + +// QuicHttpConnectionImplBase instance is a thin QUIC codec just providing quic interface to HCM. +// Owned by HCM and created during onNewConnection() if the network connection +// is a QUIC connection. +class QuicHttpConnectionImplBase : public virtual Http::Connection, + protected Logger::Loggable { +public: + QuicHttpConnectionImplBase(EnvoyQuicServerSession& quic_session) : quic_session_(quic_session) {} + + // Http::Connection + void dispatch(Buffer::Instance& /*data*/) override { + // Bypassed. QUIC connection already hands all data to streams. + NOT_REACHED_GCOVR_EXCL_LINE; + } + Http::Protocol protocol() override { + // From HCM's view, QUIC should behave the same as Http2, only the stats + // should be different. + // TODO(danzh) add Http3 enum value for QUIC. + return Http::Protocol::Http2; + } + void goAway() override; + // Returns true if the session has data to send but queued in connection or + // stream send buffer. + bool wantsToWrite() override; + void onUnderlyingConnectionAboveWriteBufferHighWatermark() override; + void onUnderlyingConnectionBelowWriteBufferLowWatermark() override; + +protected: + EnvoyQuicServerSession& quic_session_; +}; + +class QuicHttpServerConnectionImpl : public QuicHttpConnectionImplBase, + public Http::ServerConnection { +public: + QuicHttpServerConnectionImpl(EnvoyQuicServerSession& quic_session, + Http::ServerConnectionCallbacks& callbacks) + : QuicHttpConnectionImplBase(quic_session) { + quic_session.setHttpConnectionCallbacks(callbacks); + } + + // Http::Connection + void shutdownNotice() override { + // TODO(danzh): Add double-GOAWAY support in QUIC. + ENVOY_CONN_LOG(error, "Shutdown notice is not propagated to QUIC.", quic_session_); + } +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc new file mode 100644 index 0000000000..95e97e5f69 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.cc @@ -0,0 +1,59 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_connection.h" + +#include "common/network/listen_socket_impl.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/quic_io_handle_wrapper.h" +#include "extensions/transport_sockets/well_known_names.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicConnection::EnvoyQuicConnection( + const quic::QuicConnectionId& server_connection_id, + quic::QuicSocketAddress initial_peer_address, quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter& writer, bool owns_writer, + quic::Perspective perspective, const quic::ParsedQuicVersionVector& supported_versions, + Network::ListenerConfig& listener_config, Server::ListenerStats& listener_stats) + : quic::QuicConnection(server_connection_id, initial_peer_address, &helper, &alarm_factory, + &writer, owns_writer, perspective, supported_versions), + listener_config_(listener_config), listener_stats_(listener_stats) {} + +bool EnvoyQuicConnection::OnPacketHeader(const quic::QuicPacketHeader& header) { + if (quic::QuicConnection::OnPacketHeader(header) && connection_socket_ == nullptr) { + ASSERT(self_address().IsInitialized()); + // Self address should be initialized by now. It's time to install filters. + Network::Address::InstanceConstSharedPtr local_addr = + quicAddressToEnvoyAddressInstance(self_address()); + + Network::Address::InstanceConstSharedPtr remote_addr = + quicAddressToEnvoyAddressInstance(peer_address()); + connection_socket_ = std::make_unique( + // Wraps the real IoHandle instance so that if this socket gets closed, + // the real IoHandle won't be affected. + std::make_unique(listener_config_.socket().ioHandle()), + std::move(local_addr), std::move(remote_addr)); + connection_socket_->setDetectedTransportProtocol( + Extensions::TransportSockets::TransportSocketNames::get().Quic); + + filter_chain_ = listener_config_.filterChainManager().findFilterChain(*connection_socket_); + if (filter_chain_ == nullptr) { + listener_stats_.no_filter_chain_match_.inc(); + CloseConnection(quic::QUIC_CRYPTO_INTERNAL_ERROR, + "closing connection: no matching filter chain found for handshake", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return false; + } + const bool empty_filter_chain = !listener_config_.filterChainFactory().createNetworkFilterChain( + *envoy_connection_, filter_chain_->networkFilterFactories()); + if (empty_filter_chain) { + CloseConnection(quic::QUIC_CRYPTO_INTERNAL_ERROR, "closing connection: filter chain is empty", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); + return false; + } + } + return true; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection.h b/source/extensions/quic_listeners/quiche/envoy_quic_connection.h new file mode 100644 index 0000000000..04381bf383 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection.h @@ -0,0 +1,71 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_connection.h" + +#pragma GCC diagnostic pop + +#include + +#include "common/common/logger.h" +#include "envoy/network/connection.h" +#include "envoy/network/listener.h" +#include "server/connection_handler_impl.h" + +namespace Envoy { +namespace Quic { + +// Derived for network filter chain, stats and QoS. This is used on both client +// and server side. +class EnvoyQuicConnection : public quic::QuicConnection, + protected Logger::Loggable { +public: + EnvoyQuicConnection(const quic::QuicConnectionId& server_connection_id, + quic::QuicSocketAddress initial_peer_address, + quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter& writer, + bool owns_writer, quic::Perspective perspective, + const quic::ParsedQuicVersionVector& supported_versions, + Network::ListenerConfig& listener_config, + Server::ListenerStats& listener_stats); + + ~EnvoyQuicConnection() override = default; + + // Called by EnvoyQuicSession::setConnectionStats(). + void setConnectionStats(const Network::Connection::ConnectionStats& stats) { + connection_stats_ = std::make_unique(stats); + } + + // quic::QuicConnection + // Overridden to retrieve filter chain with initialized self address. + bool OnPacketHeader(const quic::QuicPacketHeader& header) override; + + // Called in session Initialize(). + void setEnvoyConnection(Network::Connection& connection) { envoy_connection_ = &connection; } + + const Network::ConnectionSocketPtr& connectionSocket() const { return connection_socket_; } + +protected: + Network::Connection::ConnectionStats& connectionStats() const { return *connection_stats_; } + +private: + // TODO(danzh): populate stats. + std::unique_ptr connection_stats_; + // Only initialized after self address is known. Must not own the underlying + // socket because UDP socket is shared among all connections. + Network::ConnectionSocketPtr connection_socket_; + Network::Connection* envoy_connection_{nullptr}; + Network::ListenerConfig& listener_config_; + Server::ListenerStats& listener_stats_; + // Latched to the corresponding quic FilterChain after connection_socket_ is + // initialized. + const Network::FilterChain* filter_chain_{nullptr}; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_connection_helper.h b/source/extensions/quic_listeners/quiche/envoy_quic_connection_helper.h new file mode 100644 index 0000000000..6af08fdece --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_connection_helper.h @@ -0,0 +1,42 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wtype-limits" + +#include "quiche/quic/core/crypto/quic_random.h" +#include "quiche/quic/core/quic_connection.h" +#include "quiche/quic/core/quic_simple_buffer_allocator.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/platform/envoy_quic_clock.h" + +namespace Envoy { +namespace Quic { + +// Derived to provide EnvoyQuicClock and default random generator and buffer +// allocator. +class EnvoyQuicConnectionHelper : public quic::QuicConnectionHelperInterface { +public: + EnvoyQuicConnectionHelper(Event::Dispatcher& dispatcher) + : clock_(dispatcher), random_generator_(quic::QuicRandom::GetInstance()) {} + + ~EnvoyQuicConnectionHelper() override = default; + + // QuicConnectionHelperInterface + const quic::QuicClock* GetClock() const override { return &clock_; } + quic::QuicRandom* GetRandomGenerator() override { return random_generator_; } + quic::QuicBufferAllocator* GetStreamSendBufferAllocator() override { return &buffer_allocator_; } + +private: + EnvoyQuicClock clock_; + quic::QuicRandom* random_generator_ = nullptr; + quic::SimpleBufferAllocator buffer_allocator_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc new file mode 100644 index 0000000000..e344eebc19 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.cc @@ -0,0 +1,59 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_dispatcher.h" + +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicDispatcher::EnvoyQuicDispatcher( + const quic::QuicCryptoServerConfig* crypto_config, const quic::QuicConfig& quic_config, + quic::QuicVersionManager* version_manager, + std::unique_ptr helper, + std::unique_ptr alarm_factory, + uint8_t expected_server_connection_id_length, Network::ConnectionHandler& connection_handler, + Network::ListenerConfig& listener_config, Server::ListenerStats& listener_stats, + Event::Dispatcher& dispatcher) + : quic::QuicDispatcher(&quic_config, crypto_config, version_manager, std::move(helper), + std::make_unique(), + std::move(alarm_factory), expected_server_connection_id_length), + connection_handler_(connection_handler), listener_config_(listener_config), + listener_stats_(listener_stats), dispatcher_(dispatcher) { + // Turn off chlo buffering in QuicDispatcher because per event loop clean + // up is not implemented. + // TODO(danzh): Add a per event loop callback to + // Network::UdpListenerCallbacks which should be called at the beginning + // of HandleReadEvent(). And this callback should call quic::Dispatcher::ProcessBufferedChlos(). + SetQuicFlag(FLAGS_quic_allow_chlo_buffering, false); +} + +void EnvoyQuicDispatcher::OnConnectionClosed(quic::QuicConnectionId connection_id, + quic::QuicErrorCode error, + const std::string& error_details, + quic::ConnectionCloseSource source) { + quic::QuicDispatcher::OnConnectionClosed(connection_id, error, error_details, source); + connection_handler_.decNumConnections(); +} + +quic::QuicSession* EnvoyQuicDispatcher::CreateQuicSession( + quic::QuicConnectionId server_connection_id, const quic::QuicSocketAddress& peer_address, + quic::QuicStringPiece /*alpn*/, const quic::ParsedQuicVersion& version) { + auto quic_connection = std::make_unique( + server_connection_id, peer_address, *helper(), *alarm_factory(), *writer(), + /*owns_writer=*/false, quic::Perspective::IS_SERVER, quic::ParsedQuicVersionVector{version}, + listener_config_, listener_stats_); + auto quic_session = new EnvoyQuicServerSession( + config(), quic::ParsedQuicVersionVector{version}, std::move(quic_connection), this, + session_helper(), crypto_config(), compressed_certs_cache(), dispatcher_); + quic_session->Initialize(); + // Filter chain can't be retrieved here as self address is unknown at this + // point. + // TODO(danzh): change QUIC interface to pass in self address as it is already + // known. In this way, filter chain can be retrieved at this point. But one + // thing to pay attention is that if the retrival fails, connection needs to + // be closed, and it should be added to time wait list instead of session map. + connection_handler_.incNumConnections(); + return quic_session; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h new file mode 100644 index 0000000000..bdc5808026 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_dispatcher.h @@ -0,0 +1,78 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wtype-limits" + +#include "quiche/quic/core/quic_dispatcher.h" +#include "quiche/quic/core/quic_utils.h" + +#pragma GCC diagnostic pop + +#include + +#include "envoy/network/listener.h" +#include "server/connection_handler_impl.h" + +namespace Envoy { +namespace Quic { + +// Envoy specific provider of server connection id and decision maker of +// accepting new connection or not. +class EnvoyQuicCryptoServerStreamHelper : public quic::QuicCryptoServerStream::Helper { +public: + ~EnvoyQuicCryptoServerStreamHelper() override = default; + + // quic::QuicCryptoServerStream::Helper + quic::QuicConnectionId + GenerateConnectionIdForReject(quic::QuicTransportVersion /*version*/, + quic::QuicConnectionId /*connection_id*/) const override { + // TODO(danzh): create reject connection id based on given connection_id. + return quic::QuicUtils::CreateRandomConnectionId(); + } + + bool CanAcceptClientHello(const quic::CryptoHandshakeMessage& /*message*/, + const quic::QuicSocketAddress& /*client_address*/, + const quic::QuicSocketAddress& /*peer_address*/, + const quic::QuicSocketAddress& /*self_address*/, + std::string* /*error_details*/) const override { + // TODO(danzh): decide to accept or not based on information from given handshake message, i.e. + // user agent and SNI. + return true; + } +}; + +class EnvoyQuicDispatcher : public quic::QuicDispatcher { +public: + EnvoyQuicDispatcher(const quic::QuicCryptoServerConfig* crypto_config, + const quic::QuicConfig& quic_config, + quic::QuicVersionManager* version_manager, + std::unique_ptr helper, + std::unique_ptr alarm_factory, + uint8_t expected_server_connection_id_length, + Network::ConnectionHandler& connection_handler, + Network::ListenerConfig& listener_config, + Server::ListenerStats& listener_stats, Event::Dispatcher& dispatcher); + + void OnConnectionClosed(quic::QuicConnectionId connection_id, quic::QuicErrorCode error, + const std::string& error_details, + quic::ConnectionCloseSource source) override; + +protected: + quic::QuicSession* CreateQuicSession(quic::QuicConnectionId server_connection_id, + const quic::QuicSocketAddress& peer_address, + quic::QuicStringPiece alpn, + const quic::ParsedQuicVersion& version) override; + +private: + Network::ConnectionHandler& connection_handler_; + Network::ListenerConfig& listener_config_; + Server::ListenerStats& listener_stats_; + Event::Dispatcher& dispatcher_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h index 455c76aebc..f55ae96562 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h @@ -49,9 +49,9 @@ class EnvoyQuicFakeProofSource : public quic::ProofSource { // Returns a certs chain with a fake certificate "Fake cert from [host_name]". quic::QuicReferenceCountedPointer GetCertChain(const quic::QuicSocketAddress& /*server_address*/, - const std::string& hostname) override { + const std::string& /*hostname*/) override { std::vector certs; - certs.push_back(absl::StrCat("Fake cert from ", hostname)); + certs.push_back(absl::StrCat("Fake cert")); return quic::QuicReferenceCountedPointer( new quic::ProofSource::Chain(certs)); } diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h index 09cbf73b4a..0861e09fb4 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_fake_proof_verifier.h @@ -43,13 +43,12 @@ class EnvoyQuicFakeProofVerifier : public quic::ProofVerifier { // Return success if the certs chain has only one fake certificate "Fake cert from [host_name]" // and its SCT is "Fake timestamp". Otherwise failure. quic::QuicAsyncStatus - VerifyCertChain(const std::string& hostname, const std::vector& certs, + VerifyCertChain(const std::string& /*hostname*/, const std::vector& certs, const std::string& /*ocsp_response*/, const std::string& cert_sct, const quic::ProofVerifyContext* /*context*/, std::string* /*error_details*/, std::unique_ptr* /*details*/, std::unique_ptr /*callback*/) override { - std::string cert = absl::StrCat("Fake cert from ", hostname); - if (cert_sct == "Fake timestamp" && certs.size() == 1 && certs[0] == cert) { + if (cert_sct == "Fake timestamp" && certs.size() == 1 && certs[0] == "Fake cert") { return quic::QUIC_SUCCESS; } return quic::QUIC_FAILURE; diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc new file mode 100644 index 0000000000..a6452fca1b --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.cc @@ -0,0 +1,190 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_crypto_server_stream.h" +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" + +namespace Envoy { +namespace Quic { + +EnvoyQuicServerSession::EnvoyQuicServerSession( + const quic::QuicConfig& config, const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, quic::QuicSession::Visitor* visitor, + quic::QuicCryptoServerStream::Helper* helper, const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache, Event::Dispatcher& dispatcher) + : quic::QuicServerSessionBase(config, supported_versions, connection.get(), visitor, helper, + crypto_config, compressed_certs_cache), + quic_connection_(std::move(connection)), filter_manager_(*this), dispatcher_(dispatcher), + stream_info_(dispatcher.timeSource()) { + // TODO(danzh): Use QUIC specific enum value. + stream_info_.protocol(Http::Protocol::Http2); +} + +quic::QuicCryptoServerStreamBase* EnvoyQuicServerSession::CreateQuicCryptoServerStream( + const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache) { + return new quic::QuicCryptoServerStream(crypto_config, compressed_certs_cache, this, + stream_helper()); +} + +quic::QuicSpdyStream* EnvoyQuicServerSession::CreateIncomingStream(quic::QuicStreamId id) { + if (!ShouldCreateIncomingStream(id)) { + return nullptr; + } + auto stream = new EnvoyQuicServerStream(id, this, quic::BIDIRECTIONAL); + ActivateStream(absl::WrapUnique(stream)); + setUpRequestDecoder(*stream); + return stream; +} + +quic::QuicSpdyStream* +EnvoyQuicServerSession::CreateIncomingStream(quic::PendingStream* /*pending*/) { + // Only client side server push stream should trigger this call. + NOT_REACHED_GCOVR_EXCL_LINE; +} + +quic::QuicSpdyStream* EnvoyQuicServerSession::CreateOutgoingBidirectionalStream() { + // Disallow server initiated stream. + NOT_REACHED_GCOVR_EXCL_LINE; +} + +quic::QuicSpdyStream* EnvoyQuicServerSession::CreateOutgoingUnidirectionalStream() { + NOT_REACHED_GCOVR_EXCL_LINE; +} + +void EnvoyQuicServerSession::setUpRequestDecoder(EnvoyQuicStream& stream) { + ASSERT(http_connection_callbacks_ != nullptr); + Http::StreamDecoder& decoder = http_connection_callbacks_->newStream(stream); + stream.setDecoder(decoder); +} + +void EnvoyQuicServerSession::OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, + quic::ConnectionCloseSource source) { + quic::QuicServerSessionBase::OnConnectionClosed(frame, source); + for (auto callback : network_connection_callbacks_) { + // Tell filters about connection close. + callback->onEvent(source == quic::ConnectionCloseSource::FROM_PEER + ? Network::ConnectionEvent::RemoteClose + : Network::ConnectionEvent::LocalClose); + } + transport_failure_reason_ = absl::StrCat(quic::QuicErrorCodeToString(frame.quic_error_code), + " with details: ", frame.error_details); +} + +void EnvoyQuicServerSession::Initialize() { + quic::QuicServerSessionBase::Initialize(); + quic_connection_->setEnvoyConnection(*this); +} + +void EnvoyQuicServerSession::SendGoAway(quic::QuicErrorCode error_code, const std::string& reason) { + if (transport_version() < quic::QUIC_VERSION_99) { + quic::QuicServerSessionBase::SendGoAway(error_code, reason); + } +} + +void EnvoyQuicServerSession::addWriteFilter(Network::WriteFilterSharedPtr filter) { + filter_manager_.addWriteFilter(filter); +} + +void EnvoyQuicServerSession::addFilter(Network::FilterSharedPtr filter) { + filter_manager_.addFilter(filter); +} + +void EnvoyQuicServerSession::addReadFilter(Network::ReadFilterSharedPtr filter) { + filter_manager_.addReadFilter(filter); +} + +bool EnvoyQuicServerSession::initializeReadFilters() { + return filter_manager_.initializeReadFilters(); +} + +void EnvoyQuicServerSession::addConnectionCallbacks(Network::ConnectionCallbacks& cb) { + network_connection_callbacks_.push_back(&cb); +} + +void EnvoyQuicServerSession::addBytesSentCallback(Network::Connection::BytesSentCb /*cb*/) { + // TODO(danzh): implement to support proxy. This interface is only called from + // TCP proxy code. + ASSERT(false, "addBytesSentCallback is not implemented for QUIC"); +} + +void EnvoyQuicServerSession::enableHalfClose(bool enabled) { + ASSERT(!enabled, "Quic connection doesn't support half close."); +} + +void EnvoyQuicServerSession::setBufferLimits(uint32_t /*limit*/) { + // TODO(danzh): add interface to quic for connection level buffer throttling. + // Currently read buffer is capped by connection level flow control. And + // write buffer is not capped. + ENVOY_CONN_LOG(error, "Quic manages its own buffer currently.", *this); +} + +uint32_t EnvoyQuicServerSession::bufferLimit() const { + // As quic connection is not HTTP1.1, this method shouldn't be called by HCM. + NOT_REACHED_GCOVR_EXCL_LINE; +} + +void EnvoyQuicServerSession::close(Network::ConnectionCloseType type) { + if (type != Network::ConnectionCloseType::NoFlush) { + // TODO(danzh): Implement FlushWrite and FlushWriteAndDelay mode. + ENVOY_CONN_LOG(error, "Flush write is not implemented for QUIC.", *this); + } + connection()->CloseConnection(quic::QUIC_NO_ERROR, "Closed by application", + quic::ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); +} + +void EnvoyQuicServerSession::setDelayedCloseTimeout(std::chrono::milliseconds timeout) { + ASSERT(timeout == std::chrono::milliseconds::zero(), + "Delayed close of connection is not supported"); +} + +std::chrono::milliseconds EnvoyQuicServerSession::delayedCloseTimeout() const { + // Not called outside of Network::ConnectionImpl. Maybe remove this interface + // from Network::Connection. + NOT_REACHED_GCOVR_EXCL_LINE; +} + +const Network::ConnectionSocket::OptionsSharedPtr& EnvoyQuicServerSession::socketOptions() const { + ENVOY_CONN_LOG( + error, + "QUIC connection socket is merely a wrapper, and doesn't have any specific socket options.", + *this); + return quic_connection_->connectionSocket()->options(); +} + +absl::string_view EnvoyQuicServerSession::requestedServerName() const { + return {GetCryptoStream()->crypto_negotiated_params().sni}; +} + +const Network::Address::InstanceConstSharedPtr& EnvoyQuicServerSession::remoteAddress() const { + ASSERT(quic_connection_->connectionSocket() != nullptr, + "remoteAddress() should only be called after OnPacketHeader"); + return quic_connection_->connectionSocket()->remoteAddress(); +} + +const Network::Address::InstanceConstSharedPtr& EnvoyQuicServerSession::localAddress() const { + ASSERT(quic_connection_->connectionSocket() != nullptr, + "localAddress() should only be called after OnPacketHeader"); + return quic_connection_->connectionSocket()->localAddress(); +} + +Ssl::ConnectionInfoConstSharedPtr EnvoyQuicServerSession::ssl() const { + // TODO(danzh): construct Ssl::ConnectionInfo from crypto stream + ENVOY_CONN_LOG(error, "Ssl::ConnectionInfo instance is not populated.", *this); + return nullptr; +} + +void EnvoyQuicServerSession::rawWrite(Buffer::Instance& /*data*/, bool /*end_stream*/) { + // Network filter should stop iteration. + NOT_REACHED_GCOVR_EXCL_LINE; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h new file mode 100644 index 0000000000..0cb2d0e84d --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_session.h @@ -0,0 +1,163 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#pragma GCC diagnostic ignored "-Wtype-limits" + +#include "quiche/quic/core/http/quic_server_session_base.h" + +#pragma GCC diagnostic pop + +#include + +#include "common/common/logger.h" +#include "common/common/empty_string.h" +#include "common/network/filter_manager_impl.h" +#include "common/stream_info/stream_info_impl.h" +#include "envoy/network/connection.h" +#include "envoy/event/dispatcher.h" +#include "extensions/quic_listeners/quiche/envoy_quic_stream.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection.h" + +namespace Envoy { +namespace Quic { + +// Act as a Network::Connection to HCM and a FilterManager to FilterFactoryCb. +class EnvoyQuicServerSession : public quic::QuicServerSessionBase, + public Network::FilterManagerConnection, + protected Logger::Loggable { +public: + EnvoyQuicServerSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + std::unique_ptr connection, + quic::QuicSession::Visitor* visitor, + quic::QuicCryptoServerStream::Helper* helper, + const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache, + Event::Dispatcher& dispatcher); + + // Network::FilterManager + // Overridden to delegate calls to filter_manager_. + void addWriteFilter(Network::WriteFilterSharedPtr filter) override; + void addFilter(Network::FilterSharedPtr filter) override; + void addReadFilter(Network::ReadFilterSharedPtr filter) override; + bool initializeReadFilters() override; + + // Network::Connection + void addConnectionCallbacks(Network::ConnectionCallbacks& cb) override; + void addBytesSentCallback(Network::Connection::BytesSentCb /*cb*/) override; + void enableHalfClose(bool enabled) override; + void close(Network::ConnectionCloseType type) override; + Event::Dispatcher& dispatcher() override { return dispatcher_; } + uint64_t id() const override { + // QUIC connection id can be 18 types. It's easier to use hash value instead + // of trying to map it into a 64-bit space. + return connection_id().Hash(); + } + std::string nextProtocol() const override { return EMPTY_STRING; } + void noDelay(bool /*enable*/) override { + // No-op. TCP_NODELAY doesn't apply to UDP. + } + void setDelayedCloseTimeout(std::chrono::milliseconds timeout) override; + std::chrono::milliseconds delayedCloseTimeout() const override; + void readDisable(bool disable) override { + ASSERT(!disable, "Quic connection should be able to read through out its life time."); + } + void detectEarlyCloseWhenReadDisabled(bool /*value*/) override { NOT_REACHED_GCOVR_EXCL_LINE; } + bool readEnabled() const override { return true; } + const Network::Address::InstanceConstSharedPtr& remoteAddress() const override; + const Network::Address::InstanceConstSharedPtr& localAddress() const override; + absl::optional + unixSocketPeerCredentials() const override { + ASSERT(false, "Unix domain socket is not supported."); + return absl::nullopt; + } + void setConnectionStats(const Network::Connection::ConnectionStats& stats) override { + stats_ = std::make_unique(stats); + quic_connection_->setConnectionStats(stats); + } + Ssl::ConnectionInfoConstSharedPtr ssl() const override; + Network::Connection::State state() const override { + return connection()->connected() ? Network::Connection::State::Open + : Network::Connection::State::Closed; + } + void write(Buffer::Instance& /*data*/, bool /*end_stream*/) override { + // All writes should be handled by Quic internally. + NOT_REACHED_GCOVR_EXCL_LINE; + } + void setBufferLimits(uint32_t limit) override; + uint32_t bufferLimit() const override; + bool localAddressRestored() const override { + // SO_ORIGINAL_DST not supported by QUIC. + return false; + } + bool aboveHighWatermark() const override { + ENVOY_CONN_LOG(error, "QUIC doesn't have connection level write buffer limit.", *this); + return false; + } + const Network::ConnectionSocket::OptionsSharedPtr& socketOptions() const override; + absl::string_view requestedServerName() const override; + StreamInfo::StreamInfo& streamInfo() override { return stream_info_; } + const StreamInfo::StreamInfo& streamInfo() const override { return stream_info_; } + absl::string_view transportFailureReason() const override { return transport_failure_reason_; } + + // Network::FilterManagerConnection + void rawWrite(Buffer::Instance& data, bool end_stream) override; + + // Network::ReadBufferSource + Network::StreamBuffer getReadBuffer() override { + // Network filter has to stop iteration to prevent hitting this line. + NOT_REACHED_GCOVR_EXCL_LINE; + } + // Network::WriteBufferSource + Network::StreamBuffer getWriteBuffer() override { NOT_REACHED_GCOVR_EXCL_LINE; } + + // Called by QuicHttpServerConnectionImpl before creating data streams. + void setHttpConnectionCallbacks(Http::ServerConnectionCallbacks& callbacks) { + http_connection_callbacks_ = &callbacks; + } + + // quic::QuicSession + void OnConnectionClosed(const quic::QuicConnectionCloseFrame& frame, + quic::ConnectionCloseSource source) override; + void Initialize() override; + void SendGoAway(quic::QuicErrorCode error_code, const std::string& reason) override; + +protected: + // quic::QuicServerSessionBase + quic::QuicCryptoServerStreamBase* + CreateQuicCryptoServerStream(const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache) override; + + // quic::QuicSession + // Overridden to create stream as encoder and associate it with an decoder. + quic::QuicSpdyStream* CreateIncomingStream(quic::QuicStreamId id) override; + quic::QuicSpdyStream* CreateIncomingStream(quic::PendingStream* pending) override; + quic::QuicSpdyStream* CreateOutgoingBidirectionalStream() override; + quic::QuicSpdyStream* CreateOutgoingUnidirectionalStream() override; + +private: + void setUpRequestDecoder(EnvoyQuicStream& stream); + + std::unique_ptr quic_connection_; + // Currently ConnectionManagerImpl is the one and only filter. If more network + // filters are added, ConnectionManagerImpl should always be the last one. + // Its onRead() is only called once to trigger ReadFilter::onNewConnection() + // and the rest incoming data bypasses these filters. + Network::FilterManagerImpl filter_manager_; + Event::Dispatcher& dispatcher_; + StreamInfo::StreamInfoImpl stream_info_; + std::string transport_failure_reason_; + // TODO(danzh): populate stats. + std::unique_ptr stats_; + // These callbacks are owned by network filters and quic session should out live + // them. + Http::ServerConnectionCallbacks* http_connection_callbacks_{nullptr}; + std::list network_connection_callbacks_; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc new file mode 100644 index 0000000000..35bd63f811 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.cc @@ -0,0 +1,132 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" + +#include +#include + +#include + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/http/quic_header_list.h" +#include "quiche/quic/core/quic_session.h" +#include "quiche/spdy/core/spdy_header_block.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "common/buffer/buffer_impl.h" +#include "common/http/header_map_impl.h" +#include "common/common/assert.h" + +namespace Envoy { +namespace Quic { + +void EnvoyQuicServerStream::encode100ContinueHeaders(const Http::HeaderMap& headers) { + ASSERT(headers.Status()->value() == "100"); + encodeHeaders(headers, false); +} +void EnvoyQuicServerStream::encodeHeaders(const Http::HeaderMap& /*headers*/, bool /*end_stream*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} +void EnvoyQuicServerStream::encodeData(Buffer::Instance& /*data*/, bool /*end_stream*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} +void EnvoyQuicServerStream::encodeTrailers(const Http::HeaderMap& /*trailers*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} +void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} + +void EnvoyQuicServerStream::resetStream(Http::StreamResetReason /*reason*/) { + NOT_IMPLEMENTED_GCOVR_EXCL_LINE; +} + +void EnvoyQuicServerStream::readDisable(bool /*disable*/) { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + +void EnvoyQuicServerStream::OnInitialHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) { + quic::QuicSpdyServerStreamBase::OnInitialHeadersComplete(fin, frame_len, header_list); + ASSERT(decoder() != nullptr); + ASSERT(headers_decompressed()); + decoder()->decodeHeaders(quicHeadersToEnvoyHeaders(header_list), /*end_stream=*/fin); + ConsumeHeaderList(); +} + +void EnvoyQuicServerStream::OnBodyAvailable() { + Buffer::InstancePtr buffer = std::make_unique(); + // TODO(danzh): check Envoy per stream buffer limit. + // Currently read out all the data. + while (HasBytesToRead()) { + struct iovec iov; + int num_regions = GetReadableRegions(&iov, 1); + ASSERT(num_regions > 0); + size_t bytes_read = iov.iov_len; + Buffer::RawSlice slice; + buffer->reserve(bytes_read, &slice, 1); + ASSERT(slice.len_ >= bytes_read); + slice.len_ = bytes_read; + memcpy(slice.mem_, iov.iov_base, iov.iov_len); + buffer->commit(&slice, 1); + MarkConsumed(bytes_read); + } + + // True if no trailer and FIN read. + bool finished_reading = IsDoneReading(); + // If this is the last stream data, set end_stream if there is no + // trailers. + ASSERT(decoder() != nullptr); + decoder()->decodeData(*buffer, finished_reading); + if (!quic::VersionUsesQpack(transport_version()) && sequencer()->IsClosed() && + !FinishedReadingTrailers()) { + // For Google QUIC implementation, trailers may arrived earlier and wait to + // be consumed after reading all the body. Consume it here. + // IETF QUIC shouldn't reach here because trailers are sent on same stream. + decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); + MarkTrailersConsumed(); + } +} + +void EnvoyQuicServerStream::OnTrailingHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) { + quic::QuicSpdyServerStreamBase::OnTrailingHeadersComplete(fin, frame_len, header_list); + if (session()->connection()->connected() && + (quic::VersionUsesQpack(transport_version()) || sequencer()->IsClosed()) && + !FinishedReadingTrailers()) { + // Before QPack trailers can arrive before body. Only decode trailers after finishing decoding + // body. + ASSERT(decoder() != nullptr); + decoder()->decodeTrailers(spdyHeaderBlockToEnvoyHeaders(received_trailers())); + MarkTrailersConsumed(); + } +} + +void EnvoyQuicServerStream::OnStreamReset(const quic::QuicRstStreamFrame& frame) { + quic::QuicSpdyServerStreamBase::OnStreamReset(frame); + Http::StreamResetReason reason; + if (frame.error_code == quic::QUIC_REFUSED_STREAM) { + reason = Http::StreamResetReason::RemoteRefusedStreamReset; + } else { + reason = Http::StreamResetReason::RemoteReset; + } + runResetCallbacks(reason); +} + +void EnvoyQuicServerStream::OnConnectionClosed(quic::QuicErrorCode error, + quic::ConnectionCloseSource source) { + quic::QuicSpdyServerStreamBase::OnConnectionClosed(error, source); + Http::StreamResetReason reason; + if (error == quic::QUIC_NO_ERROR) { + reason = Http::StreamResetReason::ConnectionTermination; + } else { + reason = Http::StreamResetReason::ConnectionFailure; + } + runResetCallbacks(reason); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h new file mode 100644 index 0000000000..047970f4fd --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_server_stream.h @@ -0,0 +1,52 @@ +#pragma once + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" +#include "quiche/quic/core/http/quic_spdy_server_stream_base.h" + +#pragma GCC diagnostic pop + +#include "extensions/quic_listeners/quiche/envoy_quic_stream.h" + +namespace Envoy { +namespace Quic { + +// This class is a quic stream and also a response encoder. +class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, public EnvoyQuicStream { +public: + EnvoyQuicServerStream(quic::QuicStreamId id, quic::QuicSpdySession* session, + quic::StreamType type) + : quic::QuicSpdyServerStreamBase(id, session, type) {} + EnvoyQuicServerStream(quic::PendingStream* pending, quic::QuicSpdySession* session, + quic::StreamType type) + : quic::QuicSpdyServerStreamBase(pending, session, type) {} + + // Http::StreamEncoder + void encode100ContinueHeaders(const Http::HeaderMap& headers) override; + void encodeHeaders(const Http::HeaderMap& headers, bool end_stream) override; + void encodeData(Buffer::Instance& data, bool end_stream) override; + void encodeTrailers(const Http::HeaderMap& trailers) override; + void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; + + // Http::Stream + void resetStream(Http::StreamResetReason reason) override; + void readDisable(bool disable) override; + // quic::QuicSpdyStream + void OnBodyAvailable() override; + void OnStreamReset(const quic::QuicRstStreamFrame& frame) override; + // quic::QuicServerSessionBase + void OnConnectionClosed(quic::QuicErrorCode error, quic::ConnectionCloseSource source) override; + +protected: + // quic::QuicSpdyStream + void OnInitialHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) override; + void OnTrailingHeadersComplete(bool fin, size_t frame_len, + const quic::QuicHeaderList& header_list) override; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_stream.h b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h new file mode 100644 index 0000000000..a6126224cc --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_stream.h @@ -0,0 +1,42 @@ +#pragma once + +#include "envoy/http/codec.h" + +#include "common/http/codec_helper.h" + +namespace Envoy { +namespace Quic { + +// Base class for EnvoyQuicServer|ClientStream. +class EnvoyQuicStream : public Http::StreamEncoder, + public Http::Stream, + public Http::StreamCallbackHelper { +public: + // Http::StreamEncoder + Stream& getStream() override { return *this; } + + // Http::Stream + void addCallbacks(Http::StreamCallbacks& callbacks) override { + ASSERT(!local_end_stream_); + addCallbacks_(callbacks); + } + void removeCallbacks(Http::StreamCallbacks& callbacks) override { removeCallbacks_(callbacks); } + uint32_t bufferLimit() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + + // Needs to be called during quic stream creation before the stream receives + // any headers and data. + void setDecoder(Http::StreamDecoder& decoder) { decoder_ = &decoder; } + +protected: + Http::StreamDecoder* decoder() { + ASSERT(decoder_ != nullptr); + return decoder_; + } + +private: + // Not owned. + Http::StreamDecoder* decoder_{nullptr}; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc new file mode 100644 index 0000000000..33e0c43fc0 --- /dev/null +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.cc @@ -0,0 +1,66 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + +#include + +namespace Envoy { +namespace Quic { + +// TODO(danzh): this is called on each write. Consider to return an address instance on the stack if +// the heap allocation is too expensive. +Network::Address::InstanceConstSharedPtr +quicAddressToEnvoyAddressInstance(const quic::QuicSocketAddress& quic_address) { + return quic_address.IsInitialized() + ? Network::Address::addressFromSockAddr(quic_address.generic_address(), + quic_address.host().address_family() == + quic::IpAddressFamily::IP_V4 + ? sizeof(sockaddr_in) + : sizeof(sockaddr_in6), + false) + : nullptr; +} + +quic::QuicSocketAddress envoyAddressInstanceToQuicSocketAddress( + const Network::Address::InstanceConstSharedPtr& envoy_address) { + ASSERT(envoy_address != nullptr && envoy_address->type() == Network::Address::Type::Ip); + uint32_t port = envoy_address->ip()->port(); + sockaddr_storage ss; + if (envoy_address->ip()->version() == Network::Address::IpVersion::v4) { + auto ipv4_addr = reinterpret_cast(&ss); + memset(ipv4_addr, 0, sizeof(sockaddr_in)); + ipv4_addr->sin_family = AF_INET; + ipv4_addr->sin_port = htons(port); + ipv4_addr->sin_addr.s_addr = envoy_address->ip()->ipv4()->address(); + } else { + auto ipv6_addr = reinterpret_cast(&ss); + memset(ipv6_addr, 0, sizeof(sockaddr_in6)); + ipv6_addr->sin6_family = AF_INET6; + ipv6_addr->sin6_port = htons(port); + ASSERT(sizeof(ipv6_addr->sin6_addr.s6_addr) == 16u); + *reinterpret_cast(ipv6_addr->sin6_addr.s6_addr) = + envoy_address->ip()->ipv6()->address(); + } + return quic::QuicSocketAddress(ss); +} + +Http::HeaderMapImplPtr quicHeadersToEnvoyHeaders(const quic::QuicHeaderList& header_list) { + Http::HeaderMapImplPtr headers = std::make_unique(); + for (const auto& entry : header_list) { + // TODO(danzh): Avoid copy by referencing entry as header_list is already validated by QUIC. + headers->addCopy(Http::LowerCaseString(entry.first), entry.second); + } + return headers; +} + +Http::HeaderMapImplPtr spdyHeaderBlockToEnvoyHeaders(const spdy::SpdyHeaderBlock& header_block) { + Http::HeaderMapImplPtr headers = std::make_unique(); + for (auto entry : header_block) { + // TODO(danzh): Avoid temporary strings and addCopy() with std::string_view. + std::string key(entry.first); + std::string value(entry.second); + headers->addCopy(Http::LowerCaseString(key), value); + } + return headers; +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h index 2d411aa8dc..54b1bf07f6 100644 --- a/source/extensions/quic_listeners/quiche/envoy_quic_utils.h +++ b/source/extensions/quic_listeners/quiche/envoy_quic_utils.h @@ -1,8 +1,11 @@ -#include +#include "envoy/http/codec.h" #include "common/common/assert.h" +#include "common/http/header_map_impl.h" #include "common/network/address_impl.h" +#include "quiche/quic/core/http/quic_header_list.h" +#include "quiche/quic/core/quic_error_codes.h" #include "quiche/quic/platform/api/quic_ip_address.h" #include "quiche/quic/platform/api/quic_socket_address.h" @@ -11,18 +14,16 @@ namespace Quic { // TODO(danzh): this is called on each write. Consider to return an address instance on the stack if // the heap allocation is too expensive. -inline Network::Address::InstanceConstSharedPtr -quicAddressToEnvoyAddressInstance(const quic::QuicSocketAddress& quic_address) { - ASSERT(quic_address.host().address_family() != quic::IpAddressFamily::IP_UNSPEC); - return quic_address.IsInitialized() - ? Network::Address::addressFromSockAddr(quic_address.generic_address(), - quic_address.host().address_family() == - quic::IpAddressFamily::IP_V4 - ? sizeof(sockaddr_in) - : sizeof(sockaddr_in6), - false) - : nullptr; -} +Network::Address::InstanceConstSharedPtr +quicAddressToEnvoyAddressInstance(const quic::QuicSocketAddress& quic_address); + +quic::QuicSocketAddress envoyAddressInstanceToQuicSocketAddress( + const Network::Address::InstanceConstSharedPtr& envoy_address); + +// The returned header map has all keys in lower case. +Http::HeaderMapImplPtr quicHeadersToEnvoyHeaders(const quic::QuicHeaderList& header_list); + +Http::HeaderMapImplPtr spdyHeaderBlockToEnvoyHeaders(const spdy::SpdyHeaderBlock& header_block); } // namespace Quic } // namespace Envoy diff --git a/source/extensions/quic_listeners/quiche/platform/BUILD b/source/extensions/quic_listeners/quiche/platform/BUILD index 08c42a1364..8fc764386a 100644 --- a/source/extensions/quic_listeners/quiche/platform/BUILD +++ b/source/extensions/quic_listeners/quiche/platform/BUILD @@ -94,6 +94,7 @@ envoy_cc_library( envoy_cc_library( name = "quic_platform_export_impl_lib", hdrs = ["quic_export_impl.h"], + tags = ["nofips"], visibility = ["//visibility:public"], ) @@ -104,6 +105,7 @@ envoy_cc_library( "quic_bug_tracker_impl.h", "quic_logging_impl.h", ], + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ "//source/common/common:assert_lib", @@ -153,6 +155,7 @@ envoy_cc_library( "abseil_optional", "googletest", ], + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ ":flags_impl_lib", @@ -191,6 +194,7 @@ envoy_cc_library( "abseil_time", "ssl", ], + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ "//source/common/common:assert_lib", @@ -205,6 +209,7 @@ envoy_cc_library( srcs = ["quic_mem_slice_span_impl.cc"], hdrs = ["quic_mem_slice_span_impl.h"], copts = ["-Wno-unused-parameter"], + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ "//include/envoy/buffer:buffer_interface", @@ -222,6 +227,7 @@ envoy_cc_library( "-Wno-error=invalid-offsetof", "-Wno-unused-parameter", ], + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ "@com_googlesource_quiche//:quic_core_buffer_allocator_lib", @@ -233,6 +239,7 @@ envoy_cc_library( envoy_cc_library( name = "quic_platform_bbr2_sender_impl_lib", hdrs = ["quic_bbr2_sender_impl.h"], + tags = ["nofips"], visibility = ["//visibility:public"], deps = ["@com_googlesource_quiche//:quic_core_congestion_control_bbr_lib"], ) @@ -241,6 +248,7 @@ envoy_cc_library( name = "envoy_quic_clock_lib", srcs = ["envoy_quic_clock.cc"], hdrs = ["envoy_quic_clock.h"], + tags = ["nofips"], visibility = ["//visibility:public"], deps = [ "//include/envoy/event:dispatcher_interface", @@ -277,6 +285,7 @@ envoy_cc_library( "spdy_flags_impl.h", "spdy_logging_impl.h", "spdy_macros_impl.h", + "spdy_map_util_impl.h", "spdy_mem_slice_impl.h", "spdy_ptr_util_impl.h", "spdy_string_impl.h", diff --git a/source/extensions/quic_listeners/quiche/platform/flags_list.h b/source/extensions/quic_listeners/quiche/platform/flags_list.h index 31ba6c3686..1b0defc682 100644 --- a/source/extensions/quic_listeners/quiche/platform/flags_list.h +++ b/source/extensions/quic_listeners/quiche/platform/flags_list.h @@ -1,5 +1,5 @@ // This file intentionally does not have header guards, it's intended to be -// included multiple times, each time with a different definition of QUIC_FLAG. +// included multiple times, each time with a different definition of QUICHE_FLAG. // NOLINT(namespace-envoy) @@ -18,24 +18,33 @@ QUICHE_FLAG(bool, quic_reloadable_flag_advertise_quic_for_https_for_debugips, fa QUICHE_FLAG(bool, quic_reloadable_flag_advertise_quic_for_https_for_external_users, false, "") -QUICHE_FLAG(bool, quic_reloadable_flag_enable_quic_stateless_reject_support, true, - "Enables server-side support for QUIC stateless rejects.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_active_streams_never_negative, false, + "If true, static streams should never be closed before QuicSession " + "destruction.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_add_upper_limit_of_buffered_control_frames, false, + "If true, close connection if there are too many (> 1000) buffered " + "control frames.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_aggressive_connection_aliveness, false, + "If true, QuicSession::ShouldKeepConnectionAlive() will not consider " + "locally closed streams whose highest byte offset is not received yet.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_allow_backend_set_stream_ttl, false, "If true, check backend response header for X-Response-Ttl. If it is " "provided, the stream TTL is set. A QUIC stream will be immediately " "canceled when tries to write data if this TTL expired.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_allow_client_enabled_bbr_v2, false, + "If true, allow client to enable BBRv2 on server via connection " + "option 'B2ON'.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_alpn_dispatch, false, "Support different QUIC sessions, as indicated by ALPN. Used for QBONE.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_always_reset_short_header_packets, true, - "If true, instead of send encryption none termination packets, send " - "stateless reset in response to short headers.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr_app_limited_recovery, false, - "When you're app-limited entering recovery, stay app-limited until " - "you exit recovery in QUIC BBR.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_avoid_empty_frame_after_empty_headers, true, + "If enabled, do not call OnStreamFrame() with empty frame after " + "receiving empty or too large headers with FIN.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr_flexible_app_limited, false, "When true and the BBR9 connection option is present, BBR only considers " @@ -60,15 +69,29 @@ QUICHE_FLAG(bool, quic_reloadable_flag_quic_bbr_startup_rate_reduction, false, "When true, enables the BBS4 and BBS5 connection options, which reduce " "BBR's pacing rate in STARTUP as more losses occur as a fraction of CWND.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_change_default_lumpy_pacing_size_to_two, false, + "If true and --quic_lumpy_pacing_size is 1, QUIC will use a lumpy " + "size of two for pacing.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_clear_queued_packets_on_connection_close, false, + "Calls ClearQueuedPackets after sending a connection close packet") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_conservative_bursts, false, + "If true, set burst token to 2 in cwnd bootstrapping experiment.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false, + "If true, uses conservative cwnd gain and pacing gain when cwnd gets " + "bootstrapped.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_debug_wrong_qos, false, "If true, consider getting QoS after stream has been detached as GFE bug.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_to_bbr, true, "When true, defaults to BBR congestion control instead of Cubic.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_deprecate_ack_bundling_mode, false, - "If true, stop using AckBundling mode to send ACK, also deprecate " - "ack_queued from QuicConnection.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_default_to_bbr_v2, false, + "If true, use BBRv2 as the default congestion controller. Takes " + "precedence over --quic_default_to_bbr.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_connection_migration_for_udp_proxying, true, "If true, GFE disables connection migration in connection option for " @@ -77,9 +100,21 @@ QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_connection_migration_for_udp QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_39, false, "If true, disable QUIC version 39.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_44, true, + "If true, disable QUIC version 44.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_do_not_accept_stop_waiting, false, + "In v44 and above, where STOP_WAITING is never sent, close the " + "connection if it's received.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_donot_reset_ideal_next_packet_send_time, false, "If true, stop resetting ideal_next_packet_send_time_ in pacing sender.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_drop_invalid_small_initial_connection_id, true, + "When true, QuicDispatcher will drop packets that have an initial " + "destination connection ID that is too short, instead of responding " + "with a Version Negotiation packet to reject it.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_eighth_rtt_loss_detection, false, "When true, the LOSS connection option allows for 1/8 RTT of " "reording instead of the current 1/8th threshold which has been " @@ -89,49 +124,57 @@ QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_ack_decimation, false, "Default enables QUIC ack decimation and adds a connection option to " "disable it.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_pcc3, false, - "If true, enable experiment for testing PCC congestion-control.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_version_43, true, - "If true, enable QUIC version 43.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_fifo_write_scheduler, false, + "If true and FIFO connection option is received, write_blocked_streams " + "uses FIFO(stream with smallest ID has highest priority) write scheduler.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_version_44, true, - "If true, enable version 44 which uses IETF header format.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_lifo_write_scheduler, false, + "If true and LIFO connection option is received, write_blocked_streams " + "uses LIFO(stream with largest ID has highest priority) write scheduler.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_version_46, true, - "If true, enable QUIC version 46.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_pcc3, false, + "If true, enable experiment for testing PCC congestion-control.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_version_47, false, "If true, enable QUIC version 47 which adds support for variable " "length connection IDs.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_version_48, false, + "If true, enable QUIC version 48.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_version_99, false, "If true, enable version 99.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_enabled, false, "") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_faster_interval_add_in_sequence_buffer, false, - "If true, QuicStreamSequencerBuffer will switch to a new " - "QuicIntervalSet::AddOptimizedForAppend method in OnStreamData().") - QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_adaptive_time_loss, false, - "Simplify QUIC's adaptive time loss detection to measure the " + "Simplify QUICHE's adaptive time loss detection to measure the " "necessary reordering window for every spurious retransmit.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_has_pending_crypto_data, false, - "If true, QuicSession::HasPendingCryptoData checks whether the " - "crypto stream's send buffer is empty. This flag fixes a bug where " - "the retransmission alarm mode is wrong for the first CHLO packet.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_bbr_cwnd_in_bandwidth_resumption, true, + "If true, adjust congestion window when doing bandwidth resumption in BBR.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_get_packet_header_size, false, + "Fixes quic::GetPacketHeaderSize and callsites when " + "QuicVersionHasLongHeaderLengths is false.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_packets_acked, true, + "If true, when detecting losses, use packets_acked of corresponding " + "packet number space.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_spurious_ack_alarm, false, - "If true, do not schedule ack alarm if should_send_ack is set in the " - "generator.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_rto_retransmission2, false, + "If true, when RTO fires and there is no packet to be RTOed, let " + "connection send.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_termination_packets, false, - "If true, GFE time wait list will send termination packets based on " - "current packet's encryption level.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_handle_staticness_for_spdy_stream, false, + "If true, QuicSpdySession::GetSpdyDataStream() will close the " + "connection if the returned stream is static.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_ignore_tlpr_if_no_pending_stream_data, false, + "If true, ignore TLPR if there is no pending stream data") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_limit_window_updates_in_traces, false, - "Limits the number of window update events recorded in Tracegraf logs.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_inline_getorcreatedynamicstream, false, + "If true, QuicSession::GetOrCreateDynamicStream() is deprecated, and " + "its contents are moved to GetOrCreateStream().") QUICHE_FLAG(bool, quic_reloadable_flag_quic_listener_never_fake_epollout, false, "If true, QuicListener::OnSocketIsWritable will always return false, " @@ -141,20 +184,16 @@ QUICHE_FLAG(bool, quic_reloadable_flag_quic_listener_never_fake_epollout, false, QUICHE_FLAG(bool, quic_reloadable_flag_quic_log_cert_name_for_empty_sct, true, "If true, log leaf cert subject name into warning log.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_log_is_proxy_in_tcs, false, - "If true, log whether a GFE QUIC server session is UDP proxied and whether " - "it is a health check connection, in transport connection stats.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_logging_frames_in_tracegraf, false, - "If true, populate frames info when logging tracegraf.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_loss_removes_from_inflight, true, + "When true, remove packets from inflight where they're declared " + "lost, rather than in MarkForRetransmission. Also no longer marks " + "handshake packets as no longer inflight when they're retransmitted.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_monotonic_epoll_clock, false, "If true, QuicEpollClock::Now() will monotonically increase.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_client_conn_ver_negotiation, false, - "If true, a client connection would be closed when a version " - "negotiation packet is received. It would be the higher layer's " - "responsibility to do the reconnection.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_negotiate_ack_delay_time, false, + "If true, will negotiate the ACK delay time.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_cloud_domain_sni_lookup_on_missing_sni, false, "Do not attempt to match an empty Server Name Indication (SNI) " @@ -164,68 +203,66 @@ QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_dup_experiment_id_2, false, "If true, transport connection stats doesn't report duplicated " "experiments for same connection.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_goaway_for_proxied_port_change, false, - "If true, for proxied quic sessions, GFE will not send a GOAWAY in " - "response to a client port change.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_stream_data_after_reset, false, + "If true, QuicStreamSequencer will not take in new data if the stream is reset.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_v2_scaling_factor, false, "When true, don't use an extra scaling factor when reading packets " - "from QUIC's RX_RING with TPACKET_V2.") + "from QUICHE's RX_RING with TPACKET_V2.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_optimize_inflight_check, false, - "Stop checking QuicUnackedPacketMap::HasUnackedRetransmittableFrames " - "and instead rely on the existing check that bytes_in_flight > 0") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_window_update_on_read_only_stream, false, + "If true, QuicConnection will be closed if a WindowUpdate frame is " + "received on a READ_UNIDIRECTIONAL stream.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_proxy_check_toss_on_insertion_failure, false, "If true, enable the code that fixes a race condition for quic udp " "proxying in L0. See b/70036019.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_proxy_munge_response_for_healthcheck, true, - "If true, for udp proxy, the health check packets from L1 to L0 will " - "be munged.") - QUICHE_FLAG(bool, quic_reloadable_flag_quic_proxy_read_packed_strings, true, "If true, QuicProxyDispatcher will prefer to extract client_address " "and server_vip from packed_client_address and packed_server_vip, " "respectively.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_proxy_supports_length_prefix, false, + "When true, QuicProxyUtils::GetConnectionId supports length prefixed " + "connection IDs.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_proxy_write_packed_strings, false, "If true, QuicProxyDispatcher will write packed_client_address and " "packed_server_vip in TcpProxyHeaderProto.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_length_prefix_from_packet_info, false, + "When true, QuicDispatcher::MaybeDispatchPacket will use packet_info.use_length_prefix " + "instead of an incorrect local computation.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_record_frontend_service_vip_mapping, false, "If true, for L1 GFE, as requests come in, record frontend service to VIP " "mapping which is used to announce VIP in SHLO for proxied sessions. ") QUICHE_FLAG(bool, quic_reloadable_flag_quic_reject_all_traffic, false, "") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_reject_unprocessable_packets_statelessly, false, + "If true, do not add connection ID of packets with unknown connection ID " + "and no version to time wait list, instead, send appropriate responses " + "depending on the packets' sizes and drop them.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_require_handshake_confirmation, false, "If true, require handshake confirmation for QUIC connections, " "functionally disabling 0-rtt handshakes.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_rpm_decides_when_to_send_acks, false, - "If both this flag and " - "gfe2_reloadable_flag_quic_deprecate_ack_bundling_mode are true, " - "QuicReceivedPacketManager decides when to send ACKs.") - QUICHE_FLAG(bool, quic_reloadable_flag_quic_send_timestamps, false, "When the STMP connection option is sent by the client, timestamps " "in the QUIC ACK frame are sent and processed.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_server_push, true, - "If true, enable server push feature on QUIC.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_set_transmission_type_for_next_frame, true, - "If true, QuicPacketCreator::SetTransmissionType will set the " - "transmission type of the next successfully added frame.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_sent_packet_manager_cleanup, false, + "When true, remove obsolete functionality intended to test IETF QUIC " + "recovery.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_simplify_build_connectivity_probing_packet, true, - "If true, simplifies the implementation of " - "QuicFramer::BuildConnectivityProbingPacket().") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_server_push, true, + "If true, enable server push feature on QUICHE.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_stateless_proxy, false, - "If true, UDP proxy will not drop versionless packets, in other " - "words, it will proxy all packets from client.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_simplify_stop_waiting, true, + "If true, do not send STOP_WAITING if no_stop_waiting_frame_.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_stop_reading_when_level_triggered, false, "When true, calling StopReading() on a level-triggered QUIC stream " @@ -237,31 +274,37 @@ QUICHE_FLAG(bool, quic_reloadable_flag_quic_testonly_default_false, false, QUICHE_FLAG(bool, quic_reloadable_flag_quic_testonly_default_true, true, "A testonly reloadable flag that will always default to true.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_tolerate_reneging, false, - "If true, do not close connection if received largest acked decreases.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_tracegraf_populate_ack_packet_number, false, + "If true, populate packet_number of received ACK in tracegraf.") + +QUICHE_FLAG(bool, quic_reloadable_flag_quic_track_ack_height_in_bandwidth_sampler, false, + "If true, QUIC will track max ack height in BandwidthSampler.") QUICHE_FLAG(bool, quic_reloadable_flag_quic_unified_iw_options, false, "When true, set the initial congestion control window from connection " "options in QuicSentPacketManager rather than TcpCubicSenderBytes.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_cheap_stateless_rejects, true, - "If true, QUIC will use cheap stateless rejects without creating a full " - "connection. Prerequisite: --quic_allow_chlo_buffering has to be true.") - QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_common_stream_check, false, "If true, use common code for checking whether a new stream ID may " "be allocated.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_connection_clock_for_last_ack_time, false, + "If true, QuicFasterStatsGatherer will use a GFEConnectionClock to " + "get the time when the last ack is received.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_header_stage_idle_list2, false, "If true, use header stage idle list for QUIC connections in GFE.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_http2_priority_write_scheduler, false, + "If true and H2PR connection option is received, write_blocked_streams_ " + "uses HTTP2 (tree-style) priority write scheduler.") + QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_leto_key_exchange, false, "If true, QUIC will attempt to use the Leto key exchange service and " "only fall back to local key exchange if that fails.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_new_append_connection_id, false, - "When true QuicFramer will use AppendIetfConnectionIdsNew instead of " - "AppendIetfConnectionId.") +QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_parse_public_header, false, + "When true, QuicDispatcher will always use QuicFramer::ParsePublicHeader") QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_pigeon_sockets, false, "Use USPS Direct Path for QUIC egress.") @@ -270,103 +313,40 @@ QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_quic_time_for_received_timestamp "If true, use QuicClock::Now() for the fallback source of packet " "received time instead of WallNow().") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_uber_loss_algorithm, false, - "If true, use one loss algorithm per encryption level.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_use_uber_received_packet_manager, false, - "If this flag and quic_rpm_decides_when_to_send_acks is true, use uber " - "received packet manager instead of the single received packet manager.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_validate_packet_number_post_decryption, false, - "If true, a QUIC endpoint will valid a received packet number after " - "successfully decrypting the packet.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_eliminate_static_stream_map_3, false, - "If true, static streams in a QuicSession will be stored inside dynamic stream map. " - "static_stream_map will no longer be used.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_simplify_stop_waiting, false, - "Do not send STOP_WAITING if no_stop_waiting_frame_ is true.") - -QUICHE_FLAG(bool, quic_reloadable_flag_send_quic_fallback_server_config_on_leto_error, false, - "If true and using Leto for QUIC shared-key calculations, GFE will react to a failure " - "to contact Leto by sending a REJ containing a fallback ServerConfig, allowing the " - "client to continue the handshake.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_fix_bbr_cwnd_in_bandwidth_resumption, true, - " If true, adjust congestion window when doing bandwidth resumption in BBR.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_conservative_cwnd_and_pacing_gains, false, - "If true, uses conservative cwnd gain and pacing gain.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_do_not_accept_stop_waiting, false, - "In v44 and above, where STOP_WAITING is never sent, close the connection if it's received.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_loss_removes_from_inflight, false, - "When true, remove packets from inflight where they're declared lost, rather than in " - "MarkForRetransmission. Also no longer marks handshake packets as no longer inflight " - "when they're retransmitted.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_conservative_bursts, false, - "If true, set burst token to 2 in cwnd bootstrapping experiment.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_deprecate_queued_control_frames, false, - "If true, deprecate queued_control_frames_ from QuicPacketGenerator.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_check_connected_before_flush, false, - "If true, check whether connection is connected before flush.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_ignore_tlpr_if_sending_ping, false, - "If true, ignore TLPR for retransmission delay when sending pings from ping alarm.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_terminate_gquic_connection_as_ietf, false, - "If true, terminate Google QUIC connections similarly as IETF QUIC.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_disable_version_44, false, - "If true, disable QUIC version 44.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_fix_packets_acked, false, - "If true, when detecting losses, use packets_acked of corresponding packet number space.") - -QUICHE_FLAG(bool, quic_reloadable_flag_quic_ignore_tlpr_if_no_pending_stream_data, false, - "If true, ignore TLPR if there is no pending stream data") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_drop_invalid_small_initial_connection_id, false, - "When true, QuicDispatcher will drop packets that have an initial destination connection ID " - "that is too short, instead of responding with a Version Negotiation packet to reject it.") - QUICHE_FLAG(bool, quic_reloadable_flag_quic_version_negotiation_grease, false, - "When true, QUIC Version Negotiation packets will randomly include fake versions.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_fix_get_packet_header_size, false, - "Fixes quic::GetPacketHeaderSize and callsites when QuicVersionHasLongHeaderLengths is false.") - -QUICHE_FLAG( - bool, quic_reloadable_flag_quic_change_default_lumpy_pacing_size_to_two, false, - "If true and --quic_lumpy_pacing_size is 1, QUIC will use a lumpy size of two for pacing.") + "When true, QUIC Version Negotiation packets will randomly include " + "fake versions.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_no_window_update_on_read_only_stream, false, - "If true, QuicConnection will be closed if a WindowUpdate frame is received on a " - "READ_UNIDIRECTIONAL stream.") +QUICHE_FLAG(bool, quic_reloadable_flag_send_quic_fallback_server_config_on_leto_error, false, + "If true and using Leto for QUIC shared-key calculations, GFE will react " + "to a failure to contact Leto by sending a REJ containing a fallback " + "ServerConfig, allowing the client to continue the handshake.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_clear_queued_packets_on_connection_close, false, - "Calls ClearQueuedPackets after sending a connection close packet") +QUICHE_FLAG(bool, quic_reloadable_flag_simplify_spdy_quic_https_scheme_detection, false, + "If true, simplify the logic for detecting REQUEST_HAS_HTTPS_SCHEME in " + "NetSpdyRequester::SetRequestUrlAndHost and " + "NetQuicRequester::SetRequestUrlAndHost. Fixes a bug where internal " + "redirects for QUIC connections would be treated as having an http scheme.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_enable_version_48, false, - "If true, enable QUIC version 48.") +QUICHE_FLAG(bool, quic_restart_flag_do_not_create_raw_socket_selector_if_quic_enabled, false, + "If true, do not create the RawSocketSelector in " + "QuicListener::Initialize() if QUIC is disabled by flag.") -QUICHE_FLAG(bool, quic_reloadable_flag_quic_avoid_empty_frame_after_empty_headers, false, - "If enabled, do not call OnStreamFrame() with empty frame after receiving empty or too " - "large headers with FIN.") +QUICHE_FLAG(bool, quic_restart_flag_dont_fetch_quic_private_keys_from_leto, false, + "If true, GFE will not request private keys when fetching QUIC " + "ServerConfigs from Leto.") QUICHE_FLAG(bool, quic_restart_flag_quic_allow_loas_multipacket_chlo, false, "If true, inspects QUIC CHLOs for kLOAS and early creates sessions " "to allow multi-packet CHLOs") +QUICHE_FLAG(bool, quic_restart_flag_quic_connection_id_use_siphash, false, + "When true, QuicConnectionId::Hash uses SipHash instead of XOR.") + +QUICHE_FLAG(bool, quic_restart_flag_quic_dispatcher_hands_chlo_extractor_one_version, false, + "When true, QuicDispatcher will pass the version from the packet to " + "the ChloExtractor instead of all supported versions.") + QUICHE_FLAG(bool, quic_restart_flag_quic_enable_gso_for_udp_egress, false, "If 1) flag is true, 2) UDP egress_method is used in GFE config, and " "3) UDP GSO is supported by the kernel, GFE will use UDP GSO for " @@ -378,9 +358,8 @@ QUICHE_FLAG(bool, quic_restart_flag_quic_enable_sendmmsg_for_udp_egress, false, "gso is not supported by kernel, GFE will use sendmmsg for egress, " "except for UDP proxy.") -QUICHE_FLAG(bool, quic_restart_flag_quic_no_server_conn_ver_negotiation2, false, - "If true, dispatcher passes in a single version when creating a server " - "connection, such that version negotiation is not supported in connection.") +QUICHE_FLAG(bool, quic_restart_flag_quic_no_fallback_for_pigeon_socket, false, + "If true, GFEs using USPS egress will not fallback to raw ip socket.") QUICHE_FLAG(bool, quic_restart_flag_quic_offload_pacing_to_usps2, false, "If true, QUIC offload pacing when using USPS as egress method.") @@ -402,6 +381,10 @@ QUICHE_FLAG(bool, quic_restart_flag_quic_testonly_default_false, false, QUICHE_FLAG(bool, quic_restart_flag_quic_testonly_default_true, true, "A testonly restart flag that will always default to true.") +QUICHE_FLAG(bool, quic_restart_flag_quic_use_allocated_connection_ids, true, + "When true, QuicConnectionId will allocate long connection IDs on " + "the heap instead of inline in the object.") + QUICHE_FLAG(bool, quic_restart_flag_quic_use_leto_for_quic_configs, false, "If true, use Leto to fetch QUIC server configs instead of using the " "seeds from Memento.") @@ -410,27 +393,12 @@ QUICHE_FLAG(bool, quic_restart_flag_quic_use_pigeon_socket_to_backend, false, "If true, create a shared pigeon socket for all quic to backend " "connections and switch to use it after successful handshake.") -QUICHE_FLAG(bool, quic_restart_flag_quic_do_not_override_connection_id, false, - " When true, QuicFramer will not override connection IDs in headers and will instead " - "respect the source/destination direction as expected by IETF QUIC.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_no_framer_object_in_dispatcher, false, - "If true, make QuicDispatcher no longer have an instance of QuicFramer.") - -QUICHE_FLAG( - bool, quic_restart_flag_dont_fetch_quic_private_keys_from_leto, false, - "If true, GFE will not request private keys when fetching QUIC ServerConfigs from Leto.") - -QUICHE_FLAG(bool, quic_restart_flag_quic_use_allocated_connection_ids, false, - "When true, QuicConnectionId will allocate long connection IDs on the heap instead of " - "inline in the object.") - QUICHE_FLAG(bool, quic_allow_chlo_buffering, true, "If true, allows packets to be buffered in anticipation of a " "future CHLO, and allow CHLO packets to be buffered until next " "iteration of the event loop.") -QUICHE_FLAG(bool, quic_disable_pacing_for_perf_tests, false, "If true, disable pacing in QUIC") +QUICHE_FLAG(bool, quic_disable_pacing_for_perf_tests, false, "If true, disable pacing in QUICHE") QUICHE_FLAG(bool, quic_enforce_single_packet_chlo, true, "If true, enforce that QUIC CHLOs fit in one packet") @@ -440,54 +408,86 @@ QUICHE_FLAG(bool, quic_enforce_single_packet_chlo, true, // 200 seconds * 1000 qps = 200000. // Of course, there are usually many queries per QUIC connection, so we allow a // factor of 3 leeway. -QUICHE_FLAG(int64_t, quic_time_wait_list_max_connections, 600000, +QUICHE_FLAG(int64_t, // allow-non-std-int + quic_time_wait_list_max_connections, 600000, "Maximum number of connections on the time-wait list. " "A negative value implies no configured limit.") -QUICHE_FLAG(int64_t, quic_time_wait_list_seconds, 200, +QUICHE_FLAG(int64_t, // allow-non-std-int + quic_time_wait_list_seconds, 200, "Time period for which a given connection_id should live in " "the time-wait state.") QUICHE_FLAG(double, quic_bbr_cwnd_gain, 2.0f, "Congestion window gain for QUIC BBR during PROBE_BW phase.") -QUICHE_FLAG(int32_t, quic_buffered_data_threshold, 8 * 1024, +QUICHE_FLAG(int32_t, // allow-non-std-int + quic_buffered_data_threshold, 8 * 1024, "If buffered data in QUIC stream is less than this " "threshold, buffers all provided data or asks upper layer for more data") -QUICHE_FLAG(int32_t, quic_ietf_draft_version, 0, - "Mechanism to override version label and ALPN for IETF interop.") - -QUICHE_FLAG(int32_t, quic_send_buffer_max_data_slice_size, 4 * 1024, +QUICHE_FLAG(int32_t, // allow-non-std-int + quic_send_buffer_max_data_slice_size, 4 * 1024, "Max size of data slice in bytes for QUIC stream send buffer.") QUICHE_FLAG(bool, quic_supports_tls_handshake, false, "If true, QUIC supports both QUIC Crypto and TLS 1.3 for the " "handshake protocol") -QUICHE_FLAG(int32_t, quic_lumpy_pacing_size, 1, +QUICHE_FLAG(int32_t, // allow-non-std-int + quic_lumpy_pacing_size, 1, "Number of packets that the pacing sender allows in bursts during pacing.") QUICHE_FLAG(double, quic_lumpy_pacing_cwnd_fraction, 0.25f, "Congestion window fraction that the pacing sender allows in bursts " "during pacing.") -QUICHE_FLAG(int32_t, quic_max_pace_time_into_future_ms, 10, +QUICHE_FLAG(int32_t, // allow-non-std-int + quic_max_pace_time_into_future_ms, 10, "Max time that QUIC can pace packets into the future in ms.") QUICHE_FLAG(double, quic_pace_time_into_future_srtt_fraction, 0.125f, // One-eighth smoothed RTT "Smoothed RTT fraction that a connection can pace packets into the future.") +QUICHE_FLAG(int32_t, // allow-non-std-int + quic_ietf_draft_version, 0, + "Mechanism to override version label and ALPN for IETF interop.") + QUICHE_FLAG(bool, quic_export_server_num_packets_per_write_histogram, false, "If true, export number of packets written per write operation histogram.") -QUICHE_FLAG(int64_t, quic_headers_stream_id_in_v99, 0, - "QUIC version 99 will use this stream ID for the headers stream.") - QUICHE_FLAG(bool, quic_disable_version_negotiation_grease_randomness, false, "If true, use predictable version negotiation versions.") +QUICHE_FLAG(int64_t, // allow-non-std-int + quic_max_tracked_packet_count, 10000, "Maximum number of tracked packets.") + +QUICHE_FLAG(bool, quic_prober_uses_length_prefixed_connection_ids, false, + "If true, QuicFramer::WriteClientVersionNegotiationProbePacket uses " + "length-prefixed connection IDs.") + +QUICHE_FLAG(bool, quic_client_convert_http_header_name_to_lowercase, true, + "If true, HTTP request header names sent from QuicSpdyClientBase(and " + "descendents) will be automatically converted to lower case.") + +QUICHE_FLAG(int32_t, // allow-non-std-int + quic_bbr2_default_probe_bw_base_duration_ms, 2000, + "The default minimum duration for BBRv2-native probes, in milliseconds.") + +QUICHE_FLAG(int32_t, // allow-non-std-int + quic_bbr2_default_probe_bw_max_rand_duration_ms, 1000, + "The default upper bound of the random amount of BBRv2-native " + "probes, in milliseconds.") + +QUICHE_FLAG(int32_t, // allow-non-std-int + quic_bbr2_default_probe_rtt_period_ms, 10000, + "The default period for entering PROBE_RTT, in milliseconds.") + +QUICHE_FLAG(double, quic_bbr2_default_loss_threshold, 0.02, + "The default loss threshold for QUIC BBRv2, should be a value " + "between 0 and 1.") + QUICHE_FLAG(bool, http2_reloadable_flag_http2_testonly_default_false, false, "A testonly reloadable flag that will always default to false.") diff --git a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_storage_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_storage_impl.h index 90c59db242..3a43ec7316 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_storage_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_mem_slice_storage_impl.h @@ -36,6 +36,8 @@ class QuicMemSliceStorageImpl { QuicMemSliceSpan ToSpan() { return QuicMemSliceSpan(QuicMemSliceSpanImpl(buffer_)); } + void Append(QuicMemSliceImpl mem_slice) { buffer_.move(mem_slice.single_slice_buffer()); } + private: Envoy::Buffer::OwnedImpl buffer_; }; diff --git a/source/extensions/quic_listeners/quiche/platform/quic_pcc_sender_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_pcc_sender_impl.h index 011120b1c2..1e0f2bcbaf 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_pcc_sender_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_pcc_sender_impl.h @@ -20,11 +20,11 @@ class RttStats; class SendAlgorithmInterface; // Interface for creating a PCC SendAlgorithmInterface. -SendAlgorithmInterface* CreatePccSenderImpl(const QuicClock* clock, const RttStats* rtt_stats, - const QuicUnackedPacketMap* unacked_packets, - QuicRandom* random, QuicConnectionStats* stats, - QuicPacketCount initial_congestion_window, - QuicPacketCount max_congestion_window) { +inline SendAlgorithmInterface* +CreatePccSenderImpl(const QuicClock* /*clock*/, const RttStats* /*rtt_stats*/, + const QuicUnackedPacketMap* /*unacked_packets*/, QuicRandom* /*random*/, + QuicConnectionStats* /*stats*/, QuicPacketCount /*initial_congestion_window*/, + QuicPacketCount /*max_congestion_window*/) { PANIC("PccSender is not supported."); return nullptr; } diff --git a/source/extensions/quic_listeners/quiche/platform/quic_text_utils_impl.h b/source/extensions/quic_listeners/quiche/platform/quic_text_utils_impl.h index 42bb24e682..e39508adbb 100644 --- a/source/extensions/quic_listeners/quiche/platform/quic_text_utils_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/quic_text_utils_impl.h @@ -69,6 +69,10 @@ class QuicTextUtilsImpl { return std::any_of(data.begin(), data.end(), absl::ascii_isupper); } + static bool IsAllDigits(QuicStringPieceImpl data) { + return std::all_of(data.begin(), data.end(), absl::ascii_isdigit); + } + static std::vector Split(QuicStringPieceImpl data, char delim) { return absl::StrSplit(data, delim); } diff --git a/source/extensions/quic_listeners/quiche/platform/spdy_containers_impl.h b/source/extensions/quic_listeners/quiche/platform/spdy_containers_impl.h index 57d953c939..35d08c6183 100644 --- a/source/extensions/quic_listeners/quiche/platform/spdy_containers_impl.h +++ b/source/extensions/quic_listeners/quiche/platform/spdy_containers_impl.h @@ -37,4 +37,6 @@ inline size_t SpdyHashStringPairImpl(SpdyStringPieceImpl a, SpdyStringPieceImpl return absl::Hash>()(std::make_pair(a, b)); } +template +using SpdySmallMapImpl = absl::flat_hash_map; } // namespace spdy diff --git a/source/extensions/quic_listeners/quiche/platform/spdy_map_util_impl.h b/source/extensions/quic_listeners/quiche/platform/spdy_map_util_impl.h new file mode 100644 index 0000000000..befd8c7f8c --- /dev/null +++ b/source/extensions/quic_listeners/quiche/platform/spdy_map_util_impl.h @@ -0,0 +1,18 @@ +#pragma once + +// NOLINT(namespace-envoy) + +// This file is part of the QUICHE platform implementation, and is not to be +// consumed or referenced directly by other Envoy code. It serves purely as a +// porting layer for QUICHE. + +#include + +namespace spdy { + +template +bool SpdyContainsKeyImpl(const Collection& collection, const Key& key) { + return collection.find(key) != collection.end(); +} + +} // namespace spdy diff --git a/source/extensions/quic_listeners/quiche/quic_io_handle_wrapper.h b/source/extensions/quic_listeners/quiche/quic_io_handle_wrapper.h new file mode 100644 index 0000000000..231f1bf08b --- /dev/null +++ b/source/extensions/quic_listeners/quiche/quic_io_handle_wrapper.h @@ -0,0 +1,68 @@ +#include "envoy/network/io_handle.h" + +#include "common/network/io_socket_error_impl.h" + +namespace Envoy { +namespace Quic { + +// A wrapper class around IoHandle object which doesn't close() upon destruction. It is used to +// create ConnectionSocket as the actual IoHandle instance should out live connection socket. +class QuicIoHandleWrapper : public Network::IoHandle { +public: + QuicIoHandleWrapper(Network::IoHandle& io_handle) : io_handle_(io_handle) {} + + // Network::IoHandle + int fd() const override { return io_handle_.fd(); } + Api::IoCallUint64Result close() override { + closed_ = true; + return Api::ioCallUint64ResultNoError(); + } + bool isOpen() const override { return !closed_; } + Api::IoCallUint64Result readv(uint64_t max_length, Buffer::RawSlice* slices, + uint64_t num_slice) override { + if (closed_) { + return Api::IoCallUint64Result(0, Api::IoErrorPtr(new Network::IoSocketError(EBADF), + Network::IoSocketError::deleteIoError)); + } + return io_handle_.readv(max_length, slices, num_slice); + } + Api::IoCallUint64Result writev(const Buffer::RawSlice* slices, uint64_t num_slice) override { + if (closed_) { + return Api::IoCallUint64Result(0, Api::IoErrorPtr(new Network::IoSocketError(EBADF), + Network::IoSocketError::deleteIoError)); + } + return io_handle_.writev(slices, num_slice); + } + Api::IoCallUint64Result sendto(const Buffer::RawSlice& slice, int flags, + const Network::Address::Instance& address) override { + if (closed_) { + return Api::IoCallUint64Result(0, Api::IoErrorPtr(new Network::IoSocketError(EBADF), + Network::IoSocketError::deleteIoError)); + } + return io_handle_.sendto(slice, flags, address); + } + Api::IoCallUint64Result sendmsg(const Buffer::RawSlice* slices, uint64_t num_slice, int flags, + const Envoy::Network::Address::Ip* self_ip, + const Network::Address::Instance& peer_address) override { + if (closed_) { + return Api::IoCallUint64Result(0, Api::IoErrorPtr(new Network::IoSocketError(EBADF), + Network::IoSocketError::deleteIoError)); + } + return io_handle_.sendmsg(slices, num_slice, flags, self_ip, peer_address); + } + Api::IoCallUint64Result recvmsg(Buffer::RawSlice* slices, const uint64_t num_slice, + uint32_t self_port, RecvMsgOutput& output) override { + if (closed_) { + return Api::IoCallUint64Result(0, Api::IoErrorPtr(new Network::IoSocketError(EBADF), + Network::IoSocketError::deleteIoError)); + } + return io_handle_.recvmsg(slices, num_slice, self_port, output); + } + +private: + Network::IoHandle& io_handle_; + bool closed_{false}; +}; + +} // namespace Quic +} // namespace Envoy diff --git a/source/extensions/resource_monitors/common/factory_base.h b/source/extensions/resource_monitors/common/factory_base.h index 124367132d..65350bc695 100644 --- a/source/extensions/resource_monitors/common/factory_base.h +++ b/source/extensions/resource_monitors/common/factory_base.h @@ -15,8 +15,9 @@ class FactoryBase : public Server::Configuration::ResourceMonitorFactory { Server::ResourceMonitorPtr createResourceMonitor(const Protobuf::Message& config, Server::Configuration::ResourceMonitorFactoryContext& context) override { - return createResourceMonitorFromProtoTyped( - MessageUtil::downcastAndValidate(config), context); + return createResourceMonitorFromProtoTyped(MessageUtil::downcastAndValidate( + config, context.messageValidationVisitor()), + context); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/source/extensions/retry/priority/previous_priorities/config.cc b/source/extensions/retry/priority/previous_priorities/config.cc index 5eaf923967..d528442359 100644 --- a/source/extensions/retry/priority/previous_priorities/config.cc +++ b/source/extensions/retry/priority/previous_priorities/config.cc @@ -9,12 +9,14 @@ namespace Extensions { namespace Retry { namespace Priority { -Upstream::RetryPrioritySharedPtr -PreviousPrioritiesRetryPriorityFactory::createRetryPriority(const Protobuf::Message& config, - uint32_t max_retries) { +Upstream::RetryPrioritySharedPtr PreviousPrioritiesRetryPriorityFactory::createRetryPriority( + const Protobuf::Message& config, ProtobufMessage::ValidationVisitor& validation_visitor, + + uint32_t max_retries) { return std::make_shared( MessageUtil::downcastAndValidate< - const envoy::config::retry::previous_priorities::PreviousPrioritiesConfig&>(config) + const envoy::config::retry::previous_priorities::PreviousPrioritiesConfig&>( + config, validation_visitor) .update_frequency(), max_retries); } diff --git a/source/extensions/retry/priority/previous_priorities/config.h b/source/extensions/retry/priority/previous_priorities/config.h index b985552352..8fe761b3fe 100644 --- a/source/extensions/retry/priority/previous_priorities/config.h +++ b/source/extensions/retry/priority/previous_priorities/config.h @@ -15,8 +15,10 @@ namespace Priority { class PreviousPrioritiesRetryPriorityFactory : public Upstream::RetryPriorityFactory { public: - Upstream::RetryPrioritySharedPtr createRetryPriority(const Protobuf::Message& config, - uint32_t max_retries) override; + Upstream::RetryPrioritySharedPtr + createRetryPriority(const Protobuf::Message& config, + ProtobufMessage::ValidationVisitor& validation_visitor, + uint32_t max_retries) override; std::string name() const override { return RetryPriorityValues::get().PreviousPrioritiesRetryPriority; diff --git a/source/extensions/stat_sinks/common/statsd/statsd.cc b/source/extensions/stat_sinks/common/statsd/statsd.cc index 64d7238bba..4dc3a5dae8 100644 --- a/source/extensions/stat_sinks/common/statsd/statsd.cc +++ b/source/extensions/stat_sinks/common/statsd/statsd.cc @@ -13,6 +13,7 @@ #include "common/common/fmt.h" #include "common/common/utility.h" #include "common/config/utility.h" +#include "common/stats/symbol_table_impl.h" namespace Envoy { namespace Extensions { @@ -99,8 +100,9 @@ TcpStatsdSink::TcpStatsdSink(const LocalInfo::LocalInfo& local_info, Upstream::ClusterManager& cluster_manager, Stats::Scope& scope, const std::string& prefix) : prefix_(prefix.empty() ? Statsd::getDefaultPrefix() : prefix), tls_(tls.allocateSlot()), - cluster_manager_(cluster_manager), cx_overflow_stat_(scope.counter("statsd.cx_overflow")) { - + cluster_manager_(cluster_manager), + cx_overflow_stat_(scope.counterFromStatName( + Stats::StatNameManagedStorage("statsd.cx_overflow", scope.symbolTable()).statName())) { Config::Utility::checkClusterAndLocalInfo("tcp statsd", cluster_name, cluster_manager, local_info); cluster_info_ = cluster_manager.get(cluster_name)->info(); diff --git a/source/extensions/stat_sinks/dog_statsd/config.cc b/source/extensions/stat_sinks/dog_statsd/config.cc index 9902d35344..6b7ce0182b 100644 --- a/source/extensions/stat_sinks/dog_statsd/config.cc +++ b/source/extensions/stat_sinks/dog_statsd/config.cc @@ -19,7 +19,8 @@ namespace DogStatsd { Stats::SinkPtr DogStatsdSinkFactory::createStatsSink(const Protobuf::Message& config, Server::Instance& server) { const auto& sink_config = - MessageUtil::downcastAndValidate(config); + MessageUtil::downcastAndValidate( + config, server.messageValidationContext().staticValidationVisitor()); Network::Address::InstanceConstSharedPtr address = Network::Address::resolveProtoAddress(sink_config.address()); ENVOY_LOG(debug, "dog_statsd UDP ip address: {}", address->asString()); diff --git a/source/extensions/stat_sinks/hystrix/config.cc b/source/extensions/stat_sinks/hystrix/config.cc index 3034efad99..a9fc2d697d 100644 --- a/source/extensions/stat_sinks/hystrix/config.cc +++ b/source/extensions/stat_sinks/hystrix/config.cc @@ -19,7 +19,8 @@ namespace Hystrix { Stats::SinkPtr HystrixSinkFactory::createStatsSink(const Protobuf::Message& config, Server::Instance& server) { const auto& hystrix_sink = - MessageUtil::downcastAndValidate(config); + MessageUtil::downcastAndValidate( + config, server.messageValidationContext().staticValidationVisitor()); return std::make_unique(server, hystrix_sink.num_buckets()); } diff --git a/source/extensions/stat_sinks/metrics_service/config.cc b/source/extensions/stat_sinks/metrics_service/config.cc index 78bad75879..80e74ea992 100644 --- a/source/extensions/stat_sinks/metrics_service/config.cc +++ b/source/extensions/stat_sinks/metrics_service/config.cc @@ -23,7 +23,7 @@ Stats::SinkPtr MetricsServiceSinkFactory::createStatsSink(const Protobuf::Messag const auto& sink_config = MessageUtil::downcastAndValidate( - config); + config, server.messageValidationContext().staticValidationVisitor()); const auto& grpc_service = sink_config.grpc_service(); ENVOY_LOG(debug, "Metrics Service gRPC service configuration: {}", grpc_service.DebugString()); diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc index 3ecd4d2c28..f37d2c984f 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.cc @@ -60,21 +60,43 @@ void MetricsServiceSink::flushGauge(const Stats::Gauge& gauge) { gauage_metric->set_value(gauge.value()); } -void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& histogram) { - io::prometheus::client::MetricFamily* metrics_family = message_.add_envoy_metrics(); - metrics_family->set_type(io::prometheus::client::MetricType::SUMMARY); - metrics_family->set_name(histogram.name()); - auto* metric = metrics_family->add_metric(); - metric->set_timestamp_ms(std::chrono::duration_cast( - time_source_.systemTime().time_since_epoch()) - .count()); - auto* summary_metric = metric->mutable_summary(); - const Stats::HistogramStatistics& hist_stats = histogram.intervalStatistics(); +void MetricsServiceSink::flushHistogram(const Stats::ParentHistogram& envoy_histogram) { + // TODO(ramaraochavali): Currently we are sending both quantile information and bucket + // information. We should make this configurable if it turns out that sending both affects + // performance. + + // Add summary information for histograms. + io::prometheus::client::MetricFamily* summary_metrics_family = message_.add_envoy_metrics(); + summary_metrics_family->set_type(io::prometheus::client::MetricType::SUMMARY); + summary_metrics_family->set_name(envoy_histogram.name()); + auto* summary_metric = summary_metrics_family->add_metric(); + summary_metric->set_timestamp_ms(std::chrono::duration_cast( + time_source_.systemTime().time_since_epoch()) + .count()); + auto* summary = summary_metric->mutable_summary(); + const Stats::HistogramStatistics& hist_stats = envoy_histogram.intervalStatistics(); for (size_t i = 0; i < hist_stats.supportedQuantiles().size(); i++) { - auto* quantile = summary_metric->add_quantile(); + auto* quantile = summary->add_quantile(); quantile->set_quantile(hist_stats.supportedQuantiles()[i]); quantile->set_value(hist_stats.computedQuantiles()[i]); } + + // Add bucket information for histograms. + io::prometheus::client::MetricFamily* histogram_metrics_family = message_.add_envoy_metrics(); + histogram_metrics_family->set_type(io::prometheus::client::MetricType::HISTOGRAM); + histogram_metrics_family->set_name(envoy_histogram.name()); + auto* histogram_metric = histogram_metrics_family->add_metric(); + histogram_metric->set_timestamp_ms(std::chrono::duration_cast( + time_source_.systemTime().time_since_epoch()) + .count()); + auto* histogram = histogram_metric->mutable_histogram(); + histogram->set_sample_count(hist_stats.sampleCount()); + histogram->set_sample_sum(hist_stats.sampleSum()); + for (size_t i = 0; i < hist_stats.supportedBuckets().size(); i++) { + auto* bucket = histogram->add_bucket(); + bucket->set_upper_bound(hist_stats.supportedBuckets()[i]); + bucket->set_cumulative_count(hist_stats.computedBuckets()[i]); + } } void MetricsServiceSink::flush(Stats::MetricSnapshot& snapshot) { diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h index 14a99e92ac..5c0473b084 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h @@ -80,7 +80,7 @@ class MetricsServiceSink : public Stats::Sink { void flushCounter(const Stats::Counter& counter); void flushGauge(const Stats::Gauge& gauge); - void flushHistogram(const Stats::ParentHistogram& histogram); + void flushHistogram(const Stats::ParentHistogram& envoy_histogram); private: GrpcMetricsStreamerSharedPtr grpc_metrics_streamer_; diff --git a/source/extensions/stat_sinks/statsd/config.cc b/source/extensions/stat_sinks/statsd/config.cc index 6fe112882a..f7e352e30b 100644 --- a/source/extensions/stat_sinks/statsd/config.cc +++ b/source/extensions/stat_sinks/statsd/config.cc @@ -20,7 +20,8 @@ Stats::SinkPtr StatsdSinkFactory::createStatsSink(const Protobuf::Message& confi Server::Instance& server) { const auto& statsd_sink = - MessageUtil::downcastAndValidate(config); + MessageUtil::downcastAndValidate( + config, server.messageValidationContext().staticValidationVisitor()); switch (statsd_sink.statsd_specifier_case()) { case envoy::config::metrics::v2::StatsdSink::kAddress: { Network::Address::InstanceConstSharedPtr address = diff --git a/source/extensions/tracers/common/factory_base.h b/source/extensions/tracers/common/factory_base.h index 6b57de7b3c..b32975a74e 100644 --- a/source/extensions/tracers/common/factory_base.h +++ b/source/extensions/tracers/common/factory_base.h @@ -17,8 +17,10 @@ template class FactoryBase : public Server::Configuration::T // Server::Configuration::TracerFactory Tracing::HttpTracerPtr createHttpTracer(const Protobuf::Message& config, Server::Instance& server) override { - return createHttpTracerTyped(MessageUtil::downcastAndValidate(config), - server); + return createHttpTracerTyped( + MessageUtil::downcastAndValidate( + config, server.messageValidationContext().staticValidationVisitor()), + server); } ProtobufTypes::MessagePtr createEmptyConfigProto() override { diff --git a/source/extensions/tracers/opencensus/BUILD b/source/extensions/tracers/opencensus/BUILD index 492648fdf0..0e7c9085fc 100644 --- a/source/extensions/tracers/opencensus/BUILD +++ b/source/extensions/tracers/opencensus/BUILD @@ -32,6 +32,7 @@ envoy_cc_library( "opencensus_trace_cloud_trace_context", "opencensus_trace_grpc_trace_bin", "opencensus_trace_trace_context", + "opencensus_exporter_ocagent", "opencensus_exporter_stdout", "opencensus_exporter_stackdriver", "opencensus_exporter_zipkin", @@ -39,5 +40,6 @@ envoy_cc_library( deps = [ "//source/common/config:utility_lib", "//source/common/tracing:http_tracer_lib", + "@envoy_api//envoy/config/trace/v2:trace_cc", ], ) diff --git a/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc b/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc index 9be9d34848..a5562d20c8 100644 --- a/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc +++ b/source/extensions/tracers/opencensus/opencensus_tracer_impl.cc @@ -8,6 +8,7 @@ #include "absl/strings/str_cat.h" #include "google/devtools/cloudtrace/v2/tracing.grpc.pb.h" +#include "opencensus/exporters/trace/ocagent/ocagent_exporter.h" #include "opencensus/exporters/trace/stackdriver/stackdriver_exporter.h" #include "opencensus/exporters/trace/stdout/stdout_exporter.h" #include "opencensus/exporters/trace/zipkin/zipkin_exporter.h" @@ -257,6 +258,11 @@ Driver::Driver(const envoy::config::trace::v2::OpenCensusConfig& oc_config, opts.service_name = local_info_.clusterName(); ::opencensus::exporters::trace::ZipkinExporter::Register(opts); } + if (oc_config.ocagent_exporter_enabled()) { + ::opencensus::exporters::trace::OcAgentOptions opts; + opts.address = oc_config.ocagent_address(); + ::opencensus::exporters::trace::OcAgentExporter::Register(std::move(opts)); + } } void Driver::applyTraceConfig(const opencensus::proto::trace::v1::TraceConfig& config) { diff --git a/source/extensions/tracers/zipkin/BUILD b/source/extensions/tracers/zipkin/BUILD index 77937e6dbb..6524969001 100644 --- a/source/extensions/tracers/zipkin/BUILD +++ b/source/extensions/tracers/zipkin/BUILD @@ -57,6 +57,7 @@ envoy_cc_library( "//source/common/singleton:const_singleton", "//source/common/tracing:http_tracer_lib", "//source/extensions/tracers:well_known_names", + "@com_github_openzipkin_zipkinapi//:zipkin_cc", ], ) diff --git a/source/extensions/tracers/zipkin/span_buffer.cc b/source/extensions/tracers/zipkin/span_buffer.cc index 387d851a9f..66bb96a946 100644 --- a/source/extensions/tracers/zipkin/span_buffer.cc +++ b/source/extensions/tracers/zipkin/span_buffer.cc @@ -1,35 +1,224 @@ #include "extensions/tracers/zipkin/span_buffer.h" +#include "common/protobuf/protobuf.h" + +#include "extensions/tracers/zipkin/util.h" +#include "extensions/tracers/zipkin/zipkin_core_constants.h" + +#include "absl/strings/str_join.h" + namespace Envoy { namespace Extensions { namespace Tracers { namespace Zipkin { -// TODO(fabolive): Need to avoid the copy to improve performance. -bool SpanBuffer::addSpan(const Span& span) { - if (span_buffer_.size() == span_buffer_.capacity()) { - // Buffer full +SpanBuffer::SpanBuffer( + const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version, + const bool shared_span_context) + : serializer_{makeSerializer(version, shared_span_context)} {} + +SpanBuffer::SpanBuffer( + const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version, + const bool shared_span_context, uint64_t size) + : serializer_{makeSerializer(version, shared_span_context)} { + allocateBuffer(size); +} + +bool SpanBuffer::addSpan(Span&& span) { + const auto& annotations = span.annotations(); + if (span_buffer_.size() == span_buffer_.capacity() || annotations.empty() || + annotations.end() == + std::find_if(annotations.begin(), annotations.end(), [](const auto& annotation) { + return annotation.value() == ZipkinCoreConstants::get().CLIENT_SEND || + annotation.value() == ZipkinCoreConstants::get().SERVER_RECV; + })) { + + // Buffer full or invalid span. return false; } + span_buffer_.push_back(std::move(span)); return true; } -std::string SpanBuffer::toStringifiedJsonArray() { - std::string stringified_json_array = "["; +SerializerPtr SpanBuffer::makeSerializer( + const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version, + const bool shared_span_context) { + switch (version) { + case envoy::config::trace::v2::ZipkinConfig::HTTP_JSON_V1: + return std::make_unique(); + case envoy::config::trace::v2::ZipkinConfig::HTTP_JSON: + return std::make_unique(shared_span_context); + case envoy::config::trace::v2::ZipkinConfig::HTTP_PROTO: + return std::make_unique(shared_span_context); + default: + NOT_REACHED_GCOVR_EXCL_LINE; + } +} + +std::string JsonV1Serializer::serialize(const std::vector& zipkin_spans) { + const std::string serialized_elements = + absl::StrJoin(zipkin_spans, ",", [](std::string* element, Span zipkin_span) { + absl::StrAppend(element, zipkin_span.toJson()); + }); + return absl::StrCat("[", serialized_elements, "]"); +} + +JsonV2Serializer::JsonV2Serializer(const bool shared_span_context) + : shared_span_context_{shared_span_context} {} + +std::string JsonV2Serializer::serialize(const std::vector& zipkin_spans) { + const std::string serialized_elements = + absl::StrJoin(zipkin_spans, ",", [this](std::string* out, const Span& zipkin_span) { + absl::StrAppend(out, + absl::StrJoin(toListOfSpans(zipkin_span), ",", + [](std::string* element, const zipkin::jsonv2::Span& span) { + std::string entry; + Protobuf::util::MessageToJsonString(span, &entry); + absl::StrAppend(element, entry); + })); + }); + return absl::StrCat("[", serialized_elements, "]"); +} - if (pendingSpans()) { - stringified_json_array += span_buffer_[0].toJson(); - const uint64_t size = span_buffer_.size(); - for (uint64_t i = 1; i < size; i++) { - stringified_json_array += ","; - stringified_json_array += span_buffer_[i].toJson(); +const std::vector +JsonV2Serializer::toListOfSpans(const Span& zipkin_span) const { + std::vector spans; + spans.reserve(zipkin_span.annotations().size()); + for (const auto& annotation : zipkin_span.annotations()) { + zipkin::jsonv2::Span span; + + if (annotation.value() == ZipkinCoreConstants::get().CLIENT_SEND) { + span.set_kind(ZipkinCoreConstants::get().KIND_CLIENT); + } else if (annotation.value() == ZipkinCoreConstants::get().SERVER_RECV) { + span.set_shared(shared_span_context_ && zipkin_span.annotations().size() > 1); + span.set_kind(ZipkinCoreConstants::get().KIND_SERVER); + } else { + continue; + } + + if (annotation.isSetEndpoint()) { + span.set_timestamp(annotation.timestamp()); + span.mutable_local_endpoint()->MergeFrom(toProtoEndpoint(annotation.endpoint())); + } + + span.set_trace_id(zipkin_span.traceIdAsHexString()); + if (zipkin_span.isSetParentId()) { + span.set_parent_id(zipkin_span.parentIdAsHexString()); + } + + span.set_id(zipkin_span.idAsHexString()); + span.set_name(zipkin_span.name()); + + if (zipkin_span.isSetDuration()) { + span.set_duration(zipkin_span.duration()); + } + + auto& tags = *span.mutable_tags(); + for (const auto& binary_annotation : zipkin_span.binaryAnnotations()) { + tags[binary_annotation.key()] = binary_annotation.value(); } + + spans.push_back(std::move(span)); + } + return spans; +} + +const zipkin::jsonv2::Endpoint +JsonV2Serializer::toProtoEndpoint(const Endpoint& zipkin_endpoint) const { + zipkin::jsonv2::Endpoint endpoint; + Network::Address::InstanceConstSharedPtr address = zipkin_endpoint.address(); + if (address) { + if (address->ip()->version() == Network::Address::IpVersion::v4) { + endpoint.set_ipv4(address->ip()->addressAsString()); + } else { + endpoint.set_ipv6(address->ip()->addressAsString()); + } + endpoint.set_port(address->ip()->port()); + } + + const std::string& service_name = zipkin_endpoint.serviceName(); + if (!service_name.empty()) { + endpoint.set_service_name(service_name); + } + + return endpoint; +} + +ProtobufSerializer::ProtobufSerializer(const bool shared_span_context) + : shared_span_context_{shared_span_context} {} + +std::string ProtobufSerializer::serialize(const std::vector& zipkin_spans) { + zipkin::proto3::ListOfSpans spans; + for (const Span& zipkin_span : zipkin_spans) { + spans.MergeFrom(toListOfSpans(zipkin_span)); + } + std::string serialized; + spans.SerializeToString(&serialized); + return serialized; +} + +const zipkin::proto3::ListOfSpans ProtobufSerializer::toListOfSpans(const Span& zipkin_span) const { + zipkin::proto3::ListOfSpans spans; + for (const auto& annotation : zipkin_span.annotations()) { + zipkin::proto3::Span span; + if (annotation.value() == ZipkinCoreConstants::get().CLIENT_SEND) { + span.set_kind(zipkin::proto3::Span::CLIENT); + } else if (annotation.value() == ZipkinCoreConstants::get().SERVER_RECV) { + span.set_shared(shared_span_context_ && zipkin_span.annotations().size() > 1); + span.set_kind(zipkin::proto3::Span::SERVER); + } else { + continue; + } + + if (annotation.isSetEndpoint()) { + span.set_timestamp(annotation.timestamp()); + span.mutable_local_endpoint()->MergeFrom(toProtoEndpoint(annotation.endpoint())); + } + + span.set_trace_id(zipkin_span.traceIdAsByteString()); + if (zipkin_span.isSetParentId()) { + span.set_parent_id(zipkin_span.parentIdAsByteString()); + } + + span.set_id(zipkin_span.idAsByteString()); + span.set_name(zipkin_span.name()); + + if (zipkin_span.isSetDuration()) { + span.set_duration(zipkin_span.duration()); + } + + auto& tags = *span.mutable_tags(); + for (const auto& binary_annotation : zipkin_span.binaryAnnotations()) { + tags[binary_annotation.key()] = binary_annotation.value(); + } + + auto* mutable_span = spans.add_spans(); + mutable_span->MergeFrom(span); + } + return spans; +} + +const zipkin::proto3::Endpoint +ProtobufSerializer::toProtoEndpoint(const Endpoint& zipkin_endpoint) const { + zipkin::proto3::Endpoint endpoint; + Network::Address::InstanceConstSharedPtr address = zipkin_endpoint.address(); + if (address) { + if (address->ip()->version() == Network::Address::IpVersion::v4) { + endpoint.set_ipv4(Util::toByteString(address->ip()->ipv4()->address())); + } else { + endpoint.set_ipv6(Util::toByteString(address->ip()->ipv6()->address())); + } + endpoint.set_port(address->ip()->port()); + } + + const std::string& service_name = zipkin_endpoint.serviceName(); + if (!service_name.empty()) { + endpoint.set_service_name(service_name); } - stringified_json_array += "]"; - return stringified_json_array; + return endpoint; } } // namespace Zipkin diff --git a/source/extensions/tracers/zipkin/span_buffer.h b/source/extensions/tracers/zipkin/span_buffer.h index a67479c644..a571860012 100644 --- a/source/extensions/tracers/zipkin/span_buffer.h +++ b/source/extensions/tracers/zipkin/span_buffer.h @@ -1,7 +1,13 @@ #pragma once +#include "envoy/config/trace/v2/trace.pb.h" + +#include "extensions/tracers/zipkin/tracer_interface.h" #include "extensions/tracers/zipkin/zipkin_core_types.h" +#include "zipkin-jsonv2.pb.h" +#include "zipkin.pb.h" + namespace Envoy { namespace Extensions { namespace Tracers { @@ -16,15 +22,26 @@ class SpanBuffer { /** * Constructor that creates an empty buffer. Space needs to be allocated by invoking * the method allocateBuffer(size). + * + * @param version The selected Zipkin collector version. @see + * api/envoy/config/trace/v2/trace.proto. + * @param shared_span_context To determine whether client and server spans will share the same + * span context. */ - SpanBuffer() = default; + SpanBuffer(const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version, + bool shared_span_context); /** * Constructor that initializes a buffer with the given size. * + * @param version The selected Zipkin collector version. @see + * api/envoy/config/trace/v2/trace.proto. + * @param shared_span_context To determine whether client and server spans will share the same + * span context. * @param size The desired buffer size. */ - SpanBuffer(uint64_t size) { allocateBuffer(size); } + SpanBuffer(const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version, + bool shared_span_context, uint64_t size); /** * Allocates space for an empty buffer or resizes a previously-allocated one. @@ -40,7 +57,7 @@ class SpanBuffer { * * @return true if the span was successfully added, or false if the buffer was full. */ - bool addSpan(const Span& span); + bool addSpan(Span&& span); /** * Empties the buffer. This method is supposed to be called when all buffered spans @@ -54,14 +71,82 @@ class SpanBuffer { uint64_t pendingSpans() { return span_buffer_.size(); } /** - * @return the contents of the buffer as a stringified array of JSONs, where - * each JSON in the array corresponds to one Zipkin span. + * Serializes std::vector span_buffer_ to std::string as payload for the reporter when the + * reporter does spans flushing. This function does only serialization and does not clear + * span_buffer_. + * + * @return std::string the contents of the buffer, a collection of serialized pending Zipkin + * spans. */ - std::string toStringifiedJsonArray(); + std::string serialize() const { return serializer_->serialize(span_buffer_); } private: + SerializerPtr + makeSerializer(const envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion& version, + bool shared_span_context); + // We use a pre-allocated vector to improve performance std::vector span_buffer_; + SerializerPtr serializer_; +}; + +using SpanBufferPtr = std::unique_ptr; + +/** + * JsonV1Serializer implements Zipkin::Serializer that serializes list of Zipkin spans into JSON + * Zipkin v1 array. + */ +class JsonV1Serializer : public Serializer { +public: + JsonV1Serializer() = default; + + /** + * Serialize list of Zipkin spans into Zipkin v1 JSON array. + * @return std::string serialized pending spans as Zipkin v1 JSON array. + */ + std::string serialize(const std::vector& pending_spans) override; +}; + +/** + * JsonV2Serializer implements Zipkin::Serializer that serializes list of Zipkin spans into JSON + * Zipkin v2 array. + */ +class JsonV2Serializer : public Serializer { +public: + JsonV2Serializer(bool shared_span_context); + + /** + * Serialize list of Zipkin spans into Zipkin v2 JSON array. + * @return std::string serialized pending spans as Zipkin v2 JSON array. + */ + std::string serialize(const std::vector& pending_spans) override; + +private: + const std::vector toListOfSpans(const Span& zipkin_span) const; + const zipkin::jsonv2::Endpoint toProtoEndpoint(const Endpoint& zipkin_endpoint) const; + + const bool shared_span_context_; +}; + +/** + * ProtobufSerializer implements Zipkin::Serializer that serializes list of Zipkin spans into + * stringified (SerializeToString) protobuf message. + */ +class ProtobufSerializer : public Serializer { +public: + ProtobufSerializer(bool shared_span_context); + + /** + * Serialize list of Zipkin spans into Zipkin v2 zipkin::proto3::ListOfSpans. + * @return std::string serialized pending spans as Zipkin zipkin::proto3::ListOfSpans. + */ + std::string serialize(const std::vector& pending_spans) override; + +private: + const zipkin::proto3::ListOfSpans toListOfSpans(const Span& zipkin_span) const; + const zipkin::proto3::Endpoint toProtoEndpoint(const Endpoint& zipkin_endpoint) const; + + const bool shared_span_context_; }; } // namespace Zipkin diff --git a/source/extensions/tracers/zipkin/tracer.cc b/source/extensions/tracers/zipkin/tracer.cc index 8b21bef8f2..aff1d659c1 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(time_source_)); + SpanPtr span_ptr = std::make_unique(time_source_); span_ptr->setName(span_name); uint64_t random_number = random_generator_.random(); span_ptr->setId(random_number); @@ -56,8 +56,8 @@ 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(time_source_)); + SystemTime timestamp, const SpanContext& previous_context) { + SpanPtr span_ptr = std::make_unique(time_source_); Annotation annotation; uint64_t timestamp_micro; diff --git a/source/extensions/tracers/zipkin/tracer.h b/source/extensions/tracers/zipkin/tracer.h index 190b68631b..d51e064584 100644 --- a/source/extensions/tracers/zipkin/tracer.h +++ b/source/extensions/tracers/zipkin/tracer.h @@ -33,7 +33,7 @@ class Reporter { * * @param span The span that needs action. */ - virtual void reportSpan(const Span& span) PURE; + virtual void reportSpan(Span&& span) PURE; }; using ReporterPtr = std::unique_ptr; @@ -72,6 +72,7 @@ class Tracer : public TracerInterface { * @param config The tracing configuration * @param span_name Name of the new span. * @param start_time The time indicating the beginning of the span. + * @return SpanPtr The root span. */ SpanPtr startSpan(const Tracing::Config&, const std::string& span_name, SystemTime timestamp); @@ -82,12 +83,15 @@ class Tracer : public TracerInterface { * @param span_name Name of the new span. * @param start_time The time indicating the beginning of the span. * @param previous_context The context of the span preceding the one to be created. + * @return SpanPtr The child span. */ SpanPtr startSpan(const Tracing::Config&, const std::string& span_name, SystemTime timestamp, - SpanContext& previous_context); + const SpanContext& previous_context); /** * TracerInterface::reportSpan. + * + * @param span The span to be reported. */ void reportSpan(Span&& span) override; @@ -103,6 +107,8 @@ class Tracer : public TracerInterface { /** * Associates a Reporter object with this Tracer. + * + * @param The span reporter. */ void setReporter(ReporterPtr reporter); diff --git a/source/extensions/tracers/zipkin/tracer_interface.h b/source/extensions/tracers/zipkin/tracer_interface.h index 4c55ab9098..c56e130e28 100644 --- a/source/extensions/tracers/zipkin/tracer_interface.h +++ b/source/extensions/tracers/zipkin/tracer_interface.h @@ -1,5 +1,9 @@ #pragma once +#include +#include +#include + #include "envoy/common/pure.h" namespace Envoy { @@ -31,6 +35,23 @@ class TracerInterface { virtual void reportSpan(Span&& span) PURE; }; +/** + * Buffered pending spans serializer. + */ +class Serializer { +public: + virtual ~Serializer() = default; + + /** + * Serialize buffered pending spans. + * + * @return std::string serialized buffered pending spans. + */ + virtual std::string serialize(const std::vector& spans) PURE; +}; + +using SerializerPtr = std::unique_ptr; + } // namespace Zipkin } // namespace Tracers } // namespace Extensions diff --git a/source/extensions/tracers/zipkin/util.cc b/source/extensions/tracers/zipkin/util.cc index d18eff673b..18eea42daf 100644 --- a/source/extensions/tracers/zipkin/util.cc +++ b/source/extensions/tracers/zipkin/util.cc @@ -7,6 +7,7 @@ #include "common/common/hex.h" #include "common/common/utility.h" +#include "absl/strings/str_join.h" #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" @@ -33,18 +34,7 @@ void Util::mergeJsons(std::string& target, const std::string& source, void Util::addArrayToJson(std::string& target, const std::vector& json_array, const std::string& field_name) { - std::string stringified_json_array = "["; - - if (!json_array.empty()) { - stringified_json_array += json_array[0]; - for (auto it = json_array.begin() + 1; it != json_array.end(); it++) { - stringified_json_array += ","; - stringified_json_array += *it; - } - } - stringified_json_array += "]"; - - mergeJsons(target, stringified_json_array, field_name); + mergeJsons(target, absl::StrCat("[", absl::StrJoin(json_array, ","), "]"), field_name); } uint64_t Util::generateRandom64(TimeSource& time_source) { diff --git a/source/extensions/tracers/zipkin/util.h b/source/extensions/tracers/zipkin/util.h index ce86f73080..8b30a155ae 100644 --- a/source/extensions/tracers/zipkin/util.h +++ b/source/extensions/tracers/zipkin/util.h @@ -5,6 +5,8 @@ #include "envoy/common/time.h" +#include "common/common/byte_order.h" + namespace Envoy { namespace Extensions { namespace Tracers { @@ -48,6 +50,28 @@ class Util { * Returns a randomly-generated 64-bit integer number. */ static uint64_t generateRandom64(TimeSource& time_source); + + /** + * Returns byte string representation of a number. + * + * @param value Number that will be represented in byte string. + * @return std::string byte string representation of a number. + */ + template static std::string toByteString(Type value) { + return std::string(reinterpret_cast(&value), sizeof(Type)); + } + + /** + * Returns big endian byte string representation of a number. + * + * @param value Number that will be represented in byte string. + * @param flip indicates to flip order or not. + * @return std::string byte string representation of a number. + */ + template static std::string toBigEndianByteString(Type value) { + auto bytes = toEndianness(value); + return std::string(reinterpret_cast(&bytes), sizeof(Type)); + } }; } // namespace Zipkin diff --git a/source/extensions/tracers/zipkin/zipkin_core_constants.h b/source/extensions/tracers/zipkin/zipkin_core_constants.h index 7384df786a..0180aeb8f2 100644 --- a/source/extensions/tracers/zipkin/zipkin_core_constants.h +++ b/source/extensions/tracers/zipkin/zipkin_core_constants.h @@ -13,6 +13,9 @@ namespace Zipkin { class ZipkinCoreConstantValues { public: + const std::string KIND_CLIENT = "CLIENT"; + const std::string KIND_SERVER = "SERVER"; + const std::string CLIENT_SEND = "cs"; const std::string CLIENT_RECV = "cr"; const std::string SERVER_SEND = "ss"; diff --git a/source/extensions/tracers/zipkin/zipkin_core_types.cc b/source/extensions/tracers/zipkin/zipkin_core_types.cc index 1730e3cd75..16c9d688a4 100644 --- a/source/extensions/tracers/zipkin/zipkin_core_types.cc +++ b/source/extensions/tracers/zipkin/zipkin_core_types.cc @@ -241,6 +241,9 @@ void Span::finish() { cr.setTimestamp(stop_timestamp); cr.setValue(ZipkinCoreConstants::get().CLIENT_RECV); annotations_.push_back(std::move(cr)); + } + + if (monotonic_start_time_) { const int64_t monotonic_stop_time = std::chrono::duration_cast( time_source_.monotonicTime().time_since_epoch()) .count(); diff --git a/source/extensions/tracers/zipkin/zipkin_core_types.h b/source/extensions/tracers/zipkin/zipkin_core_types.h index 8c9ff90924..9de9f48716 100644 --- a/source/extensions/tracers/zipkin/zipkin_core_types.h +++ b/source/extensions/tracers/zipkin/zipkin_core_types.h @@ -6,11 +6,13 @@ #include "envoy/common/time.h" #include "envoy/network/address.h" +#include "common/common/assert.h" #include "common/common/hex.h" #include "extensions/tracers/zipkin/tracer_interface.h" #include "extensions/tracers/zipkin/util.h" +#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" @@ -443,6 +445,11 @@ class Span : public ZipkinBase { */ const std::string idAsHexString() const { return Hex::uint64ToHex(id_); } + /** + * @return the span's id as a byte string. + */ + const std::string idAsByteString() const { return Util::toByteString(id_); } + /** * @return the span's name. */ @@ -460,6 +467,14 @@ class Span : public ZipkinBase { return parent_id_ ? Hex::uint64ToHex(parent_id_.value()) : EMPTY_HEX_STRING_; } + /** + * @return the span's parent id as a byte string. + */ + const std::string parentIdAsByteString() const { + ASSERT(parent_id_); + return Util::toByteString(parent_id_.value()); + } + /** * @return whether or not the debug attribute is set */ @@ -490,10 +505,21 @@ class Span : public ZipkinBase { */ const std::string traceIdAsHexString() const { return trace_id_high_.has_value() - ? Hex::uint64ToHex(trace_id_high_.value()) + Hex::uint64ToHex(trace_id_) + ? absl::StrCat(Hex::uint64ToHex(trace_id_high_.value()), Hex::uint64ToHex(trace_id_)) : Hex::uint64ToHex(trace_id_); } + /** + * @return the span's trace id as a byte string. + */ + const std::string traceIdAsByteString() const { + // https://github.com/openzipkin/zipkin-api/blob/v0.2.1/zipkin.proto#L60-L61. + return trace_id_high_.has_value() + ? absl::StrCat(Util::toBigEndianByteString(trace_id_high_.value()), + Util::toBigEndianByteString(trace_id_)) + : Util::toBigEndianByteString(trace_id_); + } + /** * @return the span's start time (monotonic, used to calculate duration). */ diff --git a/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc b/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc index fbe84ff786..3b269f742c 100644 --- a/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc +++ b/source/extensions/tracers/zipkin/zipkin_tracer_impl.cc @@ -56,9 +56,9 @@ void ZipkinSpan::setSampled(bool sampled) { span_.setSampled(sampled); } Tracing::SpanPtr ZipkinSpan::spawnChild(const Tracing::Config& config, const std::string& name, SystemTime start_time) { - SpanContext context(span_); - return Tracing::SpanPtr{ - new ZipkinSpan(*tracer_.startSpan(config, name, start_time, context), tracer_)}; + SpanContext previous_context(span_); + return std::make_unique( + *tracer_.startSpan(config, name, start_time, previous_context), tracer_); } Driver::TlsTracer::TlsTracer(TracerPtr&& tracer, Driver& driver) @@ -76,23 +76,26 @@ Driver::Driver(const envoy::config::trace::v2::ZipkinConfig& zipkin_config, Config::Utility::checkCluster(TracerNames::get().Zipkin, zipkin_config.collector_cluster(), cm_); cluster_ = cm_.get(zipkin_config.collector_cluster())->info(); - std::string collector_endpoint = ZipkinCoreConstants::get().DEFAULT_COLLECTOR_ENDPOINT; + CollectorInfo collector; if (!zipkin_config.collector_endpoint().empty()) { - collector_endpoint = zipkin_config.collector_endpoint(); + collector.endpoint_ = zipkin_config.collector_endpoint(); } - + // The current default version of collector_endpoint_version is HTTP_JSON_V1. + collector.version_ = zipkin_config.collector_endpoint_version(); const bool trace_id_128bit = zipkin_config.trace_id_128bit(); const bool shared_span_context = PROTOBUF_GET_WRAPPED_OR_DEFAULT( zipkin_config, shared_span_context, ZipkinCoreConstants::get().DEFAULT_SHARED_SPAN_CONTEXT); + collector.shared_span_context_ = shared_span_context; - tls_->set([this, collector_endpoint, &random_generator, trace_id_128bit, shared_span_context]( + tls_->set([this, collector, &random_generator, trace_id_128bit, shared_span_context]( Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { - TracerPtr tracer(new Tracer(local_info_.clusterName(), local_info_.address(), random_generator, - trace_id_128bit, shared_span_context, time_source_)); + TracerPtr tracer = + std::make_unique(local_info_.clusterName(), local_info_.address(), random_generator, + trace_id_128bit, shared_span_context, time_source_); tracer->setReporter( - ReporterImpl::NewInstance(std::ref(*this), std::ref(dispatcher), collector_endpoint)); - return ThreadLocal::ThreadLocalObjectSharedPtr{new TlsTracer(std::move(tracer), *this)}; + ReporterImpl::NewInstance(std::ref(*this), std::ref(dispatcher), collector)); + return std::make_shared(std::move(tracer), *this); }); } @@ -117,16 +120,18 @@ Tracing::SpanPtr Driver::startSpan(const Tracing::Config& config, Http::HeaderMa } } catch (const ExtractorException& e) { - return Tracing::SpanPtr(new Tracing::NullSpan()); + return std::make_unique(); } - ZipkinSpanPtr active_span(new ZipkinSpan(*new_zipkin_span, tracer)); - return active_span; + // Return the active Zipkin span. + return std::make_unique(*new_zipkin_span, tracer); } ReporterImpl::ReporterImpl(Driver& driver, Event::Dispatcher& dispatcher, - const std::string& collector_endpoint) - : driver_(driver), collector_endpoint_(collector_endpoint) { + const CollectorInfo& collector) + : driver_(driver), + collector_(collector), span_buffer_{std::make_unique( + collector.version_, collector.shared_span_context_)} { flush_timer_ = dispatcher.createTimer([this]() -> void { driver_.tracerStats().timer_flushed_.inc(); flushSpans(); @@ -135,24 +140,23 @@ ReporterImpl::ReporterImpl(Driver& driver, Event::Dispatcher& dispatcher, const uint64_t min_flush_spans = driver_.runtime().snapshot().getInteger("tracing.zipkin.min_flush_spans", 5U); - span_buffer_.allocateBuffer(min_flush_spans); + span_buffer_->allocateBuffer(min_flush_spans); enableTimer(); } ReporterPtr ReporterImpl::NewInstance(Driver& driver, Event::Dispatcher& dispatcher, - const std::string& collector_endpoint) { - return ReporterPtr(new ReporterImpl(driver, dispatcher, collector_endpoint)); + const CollectorInfo& collector) { + return std::make_unique(driver, dispatcher, collector); } -// TODO(fabolive): Need to avoid the copy to improve performance. -void ReporterImpl::reportSpan(const Span& span) { - span_buffer_.addSpan(span); +void ReporterImpl::reportSpan(Span&& span) { + span_buffer_->addSpan(std::move(span)); const uint64_t min_flush_spans = driver_.runtime().snapshot().getInteger("tracing.zipkin.min_flush_spans", 5U); - if (span_buffer_.pendingSpans() == min_flush_spans) { + if (span_buffer_->pendingSpans() == min_flush_spans) { flushSpans(); } } @@ -164,18 +168,19 @@ void ReporterImpl::enableTimer() { } void ReporterImpl::flushSpans() { - if (span_buffer_.pendingSpans()) { - driver_.tracerStats().spans_sent_.add(span_buffer_.pendingSpans()); - - const std::string request_body = span_buffer_.toStringifiedJsonArray(); - Http::MessagePtr message(new Http::RequestMessageImpl()); + if (span_buffer_->pendingSpans()) { + driver_.tracerStats().spans_sent_.add(span_buffer_->pendingSpans()); + const std::string request_body = span_buffer_->serialize(); + Http::MessagePtr message = std::make_unique(); message->headers().insertMethod().value().setReference(Http::Headers::get().MethodValues.Post); - message->headers().insertPath().value(collector_endpoint_); + message->headers().insertPath().value(collector_.endpoint_); message->headers().insertHost().value(driver_.cluster()->name()); message->headers().insertContentType().value().setReference( - Http::Headers::get().ContentTypeValues.Json); + collector_.version_ == envoy::config::trace::v2::ZipkinConfig::HTTP_PROTO + ? Http::Headers::get().ContentTypeValues.Protobuf + : Http::Headers::get().ContentTypeValues.Json); - Buffer::InstancePtr body(new Buffer::OwnedImpl()); + Buffer::InstancePtr body = std::make_unique(); body->add(request_body); message->body() = std::move(body); @@ -186,7 +191,7 @@ void ReporterImpl::flushSpans() { .send(std::move(message), *this, Http::AsyncClient::RequestOptions().setTimeout(std::chrono::milliseconds(timeout))); - span_buffer_.clear(); + span_buffer_->clear(); } } diff --git a/source/extensions/tracers/zipkin/zipkin_tracer_impl.h b/source/extensions/tracers/zipkin/zipkin_tracer_impl.h index 048b37cd18..4cecd015d6 100644 --- a/source/extensions/tracers/zipkin/zipkin_tracer_impl.h +++ b/source/extensions/tracers/zipkin/zipkin_tracer_impl.h @@ -11,6 +11,7 @@ #include "extensions/tracers/zipkin/span_buffer.h" #include "extensions/tracers/zipkin/tracer.h" +#include "extensions/tracers/zipkin/zipkin_core_constants.h" namespace Envoy { namespace Extensions { @@ -137,6 +138,23 @@ class Driver : public Tracing::Driver { TimeSource& time_source_; }; +/** + * Information about the Zipkin collector. + */ +struct CollectorInfo { + // The Zipkin collector endpoint/path to receive the collected trace data. e.g. /api/v1/spans if + // HTTP_JSON_V1 or /api/v2/spans otherwise. + std::string endpoint_{ZipkinCoreConstants::get().DEFAULT_COLLECTOR_ENDPOINT}; + + // The version of the collector. This is related to endpoint's supported payload specification and + // transport. Currently it defaults to envoy::config::trace::v2::ZipkinConfig::HTTP_JSON_V1. In + // the future, we will throw when collector_endpoint_version is not specified. + envoy::config::trace::v2::ZipkinConfig::CollectorEndpointVersion version_{ + envoy::config::trace::v2::ZipkinConfig::HTTP_JSON_V1}; + + bool shared_span_context_{ZipkinCoreConstants::get().DEFAULT_SHARED_SPAN_CONTEXT}; +}; + /** * This class derives from the abstract Zipkin::Reporter. * It buffers spans and relies on Http::AsyncClient to send spans to @@ -158,12 +176,11 @@ class ReporterImpl : public Reporter, Http::AsyncClient::Callbacks { * * @param driver ZipkinDriver to be associated with the reporter. * @param dispatcher Controls the timer used to flush buffered spans. - * @param collector_endpoint String representing the Zipkin endpoint to be used + * @param collector holds the endpoint version and path information. * when making HTTP POST requests carrying spans. This value comes from the * Zipkin-related tracing configuration. */ - ReporterImpl(Driver& driver, Event::Dispatcher& dispatcher, - const std::string& collector_endpoint); + ReporterImpl(Driver& driver, Event::Dispatcher& dispatcher, const CollectorInfo& collector); /** * Implementation of Zipkin::Reporter::reportSpan(). @@ -172,7 +189,7 @@ class ReporterImpl : public Reporter, Http::AsyncClient::Callbacks { * * @param span The span to be buffered. */ - void reportSpan(const Span& span) override; + void reportSpan(Span&& span) override; // Http::AsyncClient::Callbacks. // The callbacks below record Zipkin-span-related stats. @@ -184,14 +201,14 @@ class ReporterImpl : public Reporter, Http::AsyncClient::Callbacks { * * @param driver ZipkinDriver to be associated with the reporter. * @param dispatcher Controls the timer used to flush buffered spans. - * @param collector_endpoint String representing the Zipkin endpoint to be used + * @param collector holds the endpoint version and path information. * when making HTTP POST requests carrying spans. This value comes from the * Zipkin-related tracing configuration. * * @return Pointer to the newly-created ZipkinReporter. */ static ReporterPtr NewInstance(Driver& driver, Event::Dispatcher& dispatcher, - const std::string& collector_endpoint); + const CollectorInfo& collector); private: /** @@ -206,8 +223,8 @@ class ReporterImpl : public Reporter, Http::AsyncClient::Callbacks { Driver& driver_; Event::TimerPtr flush_timer_; - SpanBuffer span_buffer_; - const std::string collector_endpoint_; + const CollectorInfo collector_; + SpanBufferPtr span_buffer_; }; } // namespace Zipkin } // namespace Tracers diff --git a/source/extensions/transport_sockets/alts/config.cc b/source/extensions/transport_sockets/alts/config.cc index a85bb9d7fa..712f692820 100644 --- a/source/extensions/transport_sockets/alts/config.cc +++ b/source/extensions/transport_sockets/alts/config.cc @@ -79,7 +79,7 @@ Network::TransportSocketFactoryPtr createTransportSocketFactoryHelper( [] { return std::make_shared(); }); auto config = MessageUtil::downcastAndValidate( - message); + message, factory_ctxt.messageValidationVisitor()); HandshakeValidator validator = createHandshakeValidator(config); const std::string& handshaker_service = config.handshaker_service(); diff --git a/source/extensions/transport_sockets/alts/tsi_socket.h b/source/extensions/transport_sockets/alts/tsi_socket.h index bbd7b19fad..0acba40502 100644 --- a/source/extensions/transport_sockets/alts/tsi_socket.h +++ b/source/extensions/transport_sockets/alts/tsi_socket.h @@ -58,7 +58,7 @@ class TsiSocket : public Network::TransportSocket, std::string protocol() const override; absl::string_view failureReason() const override; bool canFlushClose() override { return handshake_complete_; } - const Envoy::Ssl::ConnectionInfo* ssl() const override { return nullptr; } + Envoy::Ssl::ConnectionInfoConstSharedPtr ssl() const override { return nullptr; } Network::IoResult doWrite(Buffer::Instance& buffer, bool end_stream) override; void closeSocket(Network::ConnectionEvent event) override; Network::IoResult doRead(Buffer::Instance& buffer) override; diff --git a/source/extensions/transport_sockets/tap/config.cc b/source/extensions/transport_sockets/tap/config.cc index c376fe7cec..fb04ed511b 100644 --- a/source/extensions/transport_sockets/tap/config.cc +++ b/source/extensions/transport_sockets/tap/config.cc @@ -36,7 +36,7 @@ Network::TransportSocketFactoryPtr UpstreamTapSocketConfigFactory::createTranspo Server::Configuration::TransportSocketFactoryContext& context) { const auto& outer_config = MessageUtil::downcastAndValidate( - message); + message, context.messageValidationVisitor()); auto& inner_config_factory = Config::Utility::getAndCheckFactory< Server::Configuration::UpstreamTransportSocketConfigFactory>( outer_config.transport_socket().name()); @@ -55,7 +55,7 @@ Network::TransportSocketFactoryPtr DownstreamTapSocketConfigFactory::createTrans const std::vector& server_names) { const auto& outer_config = MessageUtil::downcastAndValidate( - message); + message, context.messageValidationVisitor()); auto& inner_config_factory = Config::Utility::getAndCheckFactory< Server::Configuration::DownstreamTransportSocketConfigFactory>( outer_config.transport_socket().name()); diff --git a/source/extensions/transport_sockets/tap/tap.cc b/source/extensions/transport_sockets/tap/tap.cc index fc34b5ee2c..634bfb6759 100644 --- a/source/extensions/transport_sockets/tap/tap.cc +++ b/source/extensions/transport_sockets/tap/tap.cc @@ -50,7 +50,7 @@ Network::IoResult TapSocket::doWrite(Buffer::Instance& buffer, bool end_stream) void TapSocket::onConnected() { transport_socket_->onConnected(); } -const Ssl::ConnectionInfo* TapSocket::ssl() const { return transport_socket_->ssl(); } +Ssl::ConnectionInfoConstSharedPtr TapSocket::ssl() const { return transport_socket_->ssl(); } TapSocketFactory::TapSocketFactory( const envoy::config::transport_socket::tap::v2alpha::Tap& proto_config, diff --git a/source/extensions/transport_sockets/tap/tap.h b/source/extensions/transport_sockets/tap/tap.h index eb5f76959d..6e1e6a98cd 100644 --- a/source/extensions/transport_sockets/tap/tap.h +++ b/source/extensions/transport_sockets/tap/tap.h @@ -26,7 +26,7 @@ class TapSocket : public Network::TransportSocket { Network::IoResult doRead(Buffer::Instance& buffer) override; Network::IoResult doWrite(Buffer::Instance& buffer, bool end_stream) override; void onConnected() override; - const Ssl::ConnectionInfo* ssl() const override; + Ssl::ConnectionInfoConstSharedPtr ssl() const override; private: SocketTapConfigSharedPtr config_; diff --git a/source/extensions/transport_sockets/tls/BUILD b/source/extensions/transport_sockets/tls/BUILD index 2d9526cfcd..3345a9d874 100644 --- a/source/extensions/transport_sockets/tls/BUILD +++ b/source/extensions/transport_sockets/tls/BUILD @@ -38,6 +38,8 @@ envoy_cc_library( ":utility_lib", "//include/envoy/network:connection_interface", "//include/envoy/network:transport_socket_interface", + "//include/envoy/ssl/private_key:private_key_callbacks_interface", + "//include/envoy/ssl/private_key:private_key_interface", "//include/envoy/stats:stats_macros", "//source/common/common:assert_lib", "//source/common/common:empty_string", @@ -90,6 +92,7 @@ envoy_cc_library( "//include/envoy/ssl:context_config_interface", "//include/envoy/ssl:context_interface", "//include/envoy/ssl:context_manager_interface", + "//include/envoy/ssl/private_key:private_key_interface", "//include/envoy/stats:stats_interface", "//include/envoy/stats:stats_macros", "//source/common/common:assert_lib", @@ -98,6 +101,8 @@ envoy_cc_library( "//source/common/common:utility_lib", "//source/common/network:address_lib", "//source/common/protobuf:utility_lib", + "//source/common/stats:symbol_table_lib", + "//source/extensions/transport_sockets/tls/private_key:private_key_manager_lib", "@envoy_api//envoy/admin/v2alpha:certs_cc", ], ) diff --git a/source/extensions/transport_sockets/tls/config.cc b/source/extensions/transport_sockets/tls/config.cc index 15854a3698..4d04b0f2c3 100644 --- a/source/extensions/transport_sockets/tls/config.cc +++ b/source/extensions/transport_sockets/tls/config.cc @@ -17,7 +17,8 @@ Network::TransportSocketFactoryPtr UpstreamSslSocketFactory::createTransportSock const Protobuf::Message& message, Server::Configuration::TransportSocketFactoryContext& context) { auto client_config = std::make_unique( - MessageUtil::downcastAndValidate(message), + MessageUtil::downcastAndValidate( + message, context.messageValidationVisitor()), context); return std::make_unique( std::move(client_config), context.sslContextManager(), context.statsScope()); @@ -34,7 +35,8 @@ Network::TransportSocketFactoryPtr DownstreamSslSocketFactory::createTransportSo const Protobuf::Message& message, Server::Configuration::TransportSocketFactoryContext& context, const std::vector& server_names) { auto server_config = std::make_unique( - MessageUtil::downcastAndValidate(message), + MessageUtil::downcastAndValidate( + message, context.messageValidationVisitor()), context); return std::make_unique( std::move(server_config), context.sslContextManager(), context.statsScope(), server_names); diff --git a/source/extensions/transport_sockets/tls/context_config_impl.cc b/source/extensions/transport_sockets/tls/context_config_impl.cc index 424e08ae2d..5978e136c8 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.cc +++ b/source/extensions/transport_sockets/tls/context_config_impl.cc @@ -25,7 +25,8 @@ std::vector getTlsCertificateConf if (!config.tls_certificates().empty()) { std::vector providers; for (const auto& tls_certificate : config.tls_certificates()) { - if (!tls_certificate.has_certificate_chain() && !tls_certificate.has_private_key()) { + if (!tls_certificate.has_private_key_provider() && !tls_certificate.has_certificate_chain() && + !tls_certificate.has_private_key()) { continue; } providers.push_back( @@ -143,7 +144,7 @@ ContextConfigImpl::ContextConfigImpl( if (!tls_certificate_providers_.empty()) { for (auto& provider : tls_certificate_providers_) { if (provider->secret() != nullptr) { - tls_certificate_configs_.emplace_back(*provider->secret(), api_); + tls_certificate_configs_.emplace_back(*provider->secret(), &factory_context, api_); } } } @@ -174,7 +175,8 @@ void ContextConfigImpl::setSecretUpdateCallback(std::function callback) // This breaks multiple certificate support, but today SDS is only single cert. // TODO(htuch): Fix this when SDS goes multi-cert. tls_certificate_configs_.clear(); - tls_certificate_configs_.emplace_back(*tls_certificate_providers_[0]->secret(), api_); + tls_certificate_configs_.emplace_back(*tls_certificate_providers_[0]->secret(), nullptr, + api_); callback(); }); } diff --git a/source/extensions/transport_sockets/tls/context_impl.cc b/source/extensions/transport_sockets/tls/context_impl.cc index 03d247c489..8592817261 100644 --- a/source/extensions/transport_sockets/tls/context_impl.cc +++ b/source/extensions/transport_sockets/tls/context_impl.cc @@ -51,7 +51,11 @@ bool cbsContainsU16(CBS& cbs, uint16_t n) { ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source) : scope_(scope), stats_(generateStats(scope)), time_source_(time_source), - tls_max_version_(config.maxProtocolVersion()) { + tls_max_version_(config.maxProtocolVersion()), stat_name_set_(scope.symbolTable()), + ssl_ciphers_(stat_name_set_.add("ssl.ciphers")), + ssl_versions_(stat_name_set_.add("ssl.versions")), + ssl_curves_(stat_name_set_.add("ssl.curves")), + ssl_sigalgs_(stat_name_set_.add("ssl.sigalgs")) { const auto tls_certificates = config.tlsCertificates(); tls_contexts_.resize(std::max(static_cast(1), tls_certificates.size())); @@ -305,40 +309,62 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c #endif } - // Load private key. - bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), - tls_certificate.privateKey().size())); - RELEASE_ASSERT(bio != nullptr, ""); - bssl::UniquePtr pkey(PEM_read_bio_PrivateKey( - bio.get(), nullptr, nullptr, - !tls_certificate.password().empty() ? const_cast(tls_certificate.password().c_str()) - : nullptr)); - if (pkey == nullptr || !SSL_CTX_use_PrivateKey(ctx.ssl_ctx_.get(), pkey.get())) { - throw EnvoyException( - fmt::format("Failed to load private key from {}", tls_certificate.privateKeyPath())); - } - + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider = + tls_certificate.privateKeyMethod(); + // We either have a private key or a BoringSSL private key method provider. + if (private_key_method_provider) { + ctx.private_key_method_provider_ = private_key_method_provider; + // The provider has a reference to the private key method for the context lifetime. + Ssl::BoringSslPrivateKeyMethodSharedPtr private_key_method = + private_key_method_provider->getBoringSslPrivateKeyMethod(); + if (private_key_method == nullptr) { + throw EnvoyException( + fmt::format("Failed to get BoringSSL private key method from provider")); + } #ifdef BORINGSSL_FIPS - // Verify that private keys are passing FIPS pairwise consistency tests. - switch (pkey_id) { - case EVP_PKEY_EC: { - const EC_KEY* ecdsa_private_key = EVP_PKEY_get0_EC_KEY(pkey.get()); - if (!EC_KEY_check_fips(ecdsa_private_key)) { - throw EnvoyException(fmt::format("Failed to load private key from {}, ECDSA key failed " - "pairwise consistency test required in FIPS mode", - tls_certificate.privateKeyPath())); + if (!ctx.private_key_method_provider_->checkFips()) { + throw EnvoyException( + fmt::format("Private key method doesn't support FIPS mode with current parameters")); } - } break; - case EVP_PKEY_RSA: { - RSA* rsa_private_key = EVP_PKEY_get0_RSA(pkey.get()); - if (!RSA_check_fips(rsa_private_key)) { - throw EnvoyException(fmt::format("Failed to load private key from {}, RSA key failed " - "pairwise consistency test required in FIPS mode", - tls_certificate.privateKeyPath())); +#endif + SSL_CTX_set_private_key_method(ctx.ssl_ctx_.get(), private_key_method.get()); + } else { + // Load private key. + bio.reset(BIO_new_mem_buf(const_cast(tls_certificate.privateKey().data()), + tls_certificate.privateKey().size())); + RELEASE_ASSERT(bio != nullptr, ""); + bssl::UniquePtr pkey( + PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, + !tls_certificate.password().empty() + ? const_cast(tls_certificate.password().c_str()) + : nullptr)); + if (pkey == nullptr || !SSL_CTX_use_PrivateKey(ctx.ssl_ctx_.get(), pkey.get())) { + throw EnvoyException( + fmt::format("Failed to load private key from {}", tls_certificate.privateKeyPath())); + } + +#ifdef BORINGSSL_FIPS + // Verify that private keys are passing FIPS pairwise consistency tests. + switch (pkey_id) { + case EVP_PKEY_EC: { + const EC_KEY* ecdsa_private_key = EVP_PKEY_get0_EC_KEY(pkey.get()); + if (!EC_KEY_check_fips(ecdsa_private_key)) { + throw EnvoyException(fmt::format("Failed to load private key from {}, ECDSA key failed " + "pairwise consistency test required in FIPS mode", + tls_certificate.privateKeyPath())); + } + } break; + case EVP_PKEY_RSA: { + RSA* rsa_private_key = EVP_PKEY_get0_RSA(pkey.get()); + if (!RSA_check_fips(rsa_private_key)) { + throw EnvoyException(fmt::format("Failed to load private key from {}, RSA key failed " + "pairwise consistency test required in FIPS mode", + tls_certificate.privateKeyPath())); + } + } break; } - } break; - } #endif + } } // use the server's cipher list preferences @@ -347,6 +373,35 @@ ContextImpl::ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& c } parsed_alpn_protocols_ = parseAlpnProtocols(config.alpnProtocols()); + + // To enumerate the required builtin ciphers, curves, algorithms, and + // versions, uncomment '#define LOG_BUILTIN_STAT_NAMES' below, and run + // bazel test //test/extensions/transport_sockets/tls/... --test_output=streamed + // | grep " Builtin ssl." | sort | uniq + // #define LOG_BUILTIN_STAT_NAMES + // + // TODO(#8035): improve tooling to find any other built-ins needed to avoid + // contention. + + // Ciphers + stat_name_set_.rememberBuiltin("AEAD-AES128-GCM-SHA256"); + stat_name_set_.rememberBuiltin("ECDHE-ECDSA-AES128-GCM-SHA256"); + stat_name_set_.rememberBuiltin("ECDHE-RSA-AES128-GCM-SHA256"); + stat_name_set_.rememberBuiltin("ECDHE-RSA-AES128-SHA"); + stat_name_set_.rememberBuiltin("ECDHE-RSA-CHACHA20-POLY1305"); + + // Curves + stat_name_set_.rememberBuiltin("X25519"); + + // Algorithms + stat_name_set_.rememberBuiltin("ecdsa_secp256r1_sha256"); + stat_name_set_.rememberBuiltin("rsa_pss_rsae_sha256"); + + // Versions + stat_name_set_.rememberBuiltin("TLSv1"); + stat_name_set_.rememberBuiltin("TLSv1.1"); + stat_name_set_.rememberBuiltin("TLSv1.2"); + stat_name_set_.rememberBuiltin("TLSv1.3"); } int ServerContextImpl::alpnSelectCallback(const unsigned char** out, unsigned char* outlen, @@ -455,6 +510,18 @@ int ContextImpl::verifyCertificate(X509* cert, const std::vector& v return 1; } +void ContextImpl::incCounter(const Stats::StatName name, absl::string_view value) const { + Stats::SymbolTable& symbol_table = scope_.symbolTable(); + Stats::SymbolTable::StoragePtr storage = + symbol_table.join({name, stat_name_set_.getStatName(value)}); + scope_.counterFromStatName(Stats::StatName(storage.get())).inc(); + +#ifdef LOG_BUILTIN_STAT_NAMES + std::cerr << absl::StrCat("Builtin ", symbol_table.toString(name), ": ", value, "\n") + << std::flush; +#endif +} + void ContextImpl::logHandshake(SSL* ssl) const { stats_.handshake_.inc(); @@ -462,22 +529,19 @@ void ContextImpl::logHandshake(SSL* ssl) const { stats_.session_reused_.inc(); } - const char* cipher = SSL_get_cipher_name(ssl); - scope_.counter(fmt::format("ssl.ciphers.{}", std::string{cipher})).inc(); - - const char* version = SSL_get_version(ssl); - scope_.counter(fmt::format("ssl.versions.{}", std::string{version})).inc(); + incCounter(ssl_ciphers_, SSL_get_cipher_name(ssl)); + incCounter(ssl_versions_, SSL_get_version(ssl)); uint16_t curve_id = SSL_get_curve_id(ssl); if (curve_id) { - const char* curve = SSL_get_curve_name(curve_id); - scope_.counter(fmt::format("ssl.curves.{}", std::string{curve})).inc(); + // Note: in the unit tests, this curve name is always literal "X25519" + incCounter(ssl_curves_, SSL_get_curve_name(curve_id)); } uint16_t sigalg_id = SSL_get_peer_signature_algorithm(ssl); if (sigalg_id) { const char* sigalg = SSL_get_signature_algorithm_name(sigalg_id, 1 /* include curve */); - scope_.counter(fmt::format("ssl.sigalgs.{}", std::string{sigalg})).inc(); + incCounter(ssl_sigalgs_, sigalg); } bssl::UniquePtr cert(SSL_get_peer_certificate(ssl)); @@ -486,6 +550,19 @@ void ContextImpl::logHandshake(SSL* ssl) const { } } +std::vector ContextImpl::getPrivateKeyMethodProviders() { + std::vector providers; + + for (auto& tls_context : tls_contexts_) { + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr provider = + tls_context.getPrivateKeyMethodProvider(); + if (provider) { + providers.push_back(provider); + } + } + return providers; +} + bool ContextImpl::verifySubjectAltName(X509* cert, const std::vector& subject_alt_names) { bssl::UniquePtr san_names( diff --git a/source/extensions/transport_sockets/tls/context_impl.h b/source/extensions/transport_sockets/tls/context_impl.h index c4cf67d6cf..a730187bcd 100644 --- a/source/extensions/transport_sockets/tls/context_impl.h +++ b/source/extensions/transport_sockets/tls/context_impl.h @@ -8,9 +8,12 @@ #include "envoy/network/transport_socket.h" #include "envoy/ssl/context.h" #include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" +#include "common/stats/symbol_table_impl.h" + #include "extensions/transport_sockets/tls/context_manager_impl.h" #include "absl/synchronization/mutex.h" @@ -79,6 +82,8 @@ class ContextImpl : public virtual Envoy::Ssl::Context { Envoy::Ssl::CertificateDetailsPtr getCaCertInformation() const override; std::vector getCertChainInformation() const override; + std::vector getPrivateKeyMethodProviders(); + protected: ContextImpl(Stats::Scope& scope, const Envoy::Ssl::ContextConfig& config, TimeSource& time_source); @@ -123,6 +128,7 @@ class ContextImpl : public virtual Envoy::Ssl::Context { static SslStats generateStats(Stats::Scope& scope); std::string getCaFileName() const { return ca_file_path_; }; + void incCounter(const Stats::StatName name, absl::string_view value) const; Envoy::Ssl::CertificateDetailsPtr certificateDetails(X509* cert, const std::string& path) const; @@ -135,11 +141,15 @@ class ContextImpl : public virtual Envoy::Ssl::Context { bssl::UniquePtr cert_chain_; std::string cert_chain_file_path_; bool is_ecdsa_{}; + Ssl::PrivateKeyMethodProviderSharedPtr private_key_method_provider_{}; std::string getCertChainFileName() const { return cert_chain_file_path_; }; void addClientValidationContext(const Envoy::Ssl::CertificateValidationContextConfig& config, bool require_client_cert); bool isCipherEnabled(uint16_t cipher_id, uint16_t client_version); + Envoy::Ssl::PrivateKeyMethodProviderSharedPtr getPrivateKeyMethodProvider() { + return private_key_method_provider_; + } }; // This is always non-empty, with the first context used for all new SSL @@ -160,6 +170,11 @@ class ContextImpl : public virtual Envoy::Ssl::Context { std::string cert_chain_file_path_; TimeSource& time_source_; const unsigned tls_max_version_; + mutable Stats::StatNameSet stat_name_set_; + const Stats::StatName ssl_ciphers_; + const Stats::StatName ssl_versions_; + const Stats::StatName ssl_curves_; + const Stats::StatName ssl_sigalgs_; }; using ContextImplSharedPtr = std::shared_ptr; @@ -179,7 +194,7 @@ class ClientContextImpl : public ContextImpl, public Envoy::Ssl::ClientContext { const bool allow_renegotiation_; const size_t max_session_keys_; absl::Mutex session_keys_mu_; - std::deque> session_keys_ GUARDED_BY(session_keys_mu_); + std::deque> session_keys_ ABSL_GUARDED_BY(session_keys_mu_); bool session_keys_single_use_{false}; }; diff --git a/source/extensions/transport_sockets/tls/context_manager_impl.h b/source/extensions/transport_sockets/tls/context_manager_impl.h index 88ef9e67d5..d08e12e974 100644 --- a/source/extensions/transport_sockets/tls/context_manager_impl.h +++ b/source/extensions/transport_sockets/tls/context_manager_impl.h @@ -5,8 +5,11 @@ #include "envoy/common/time.h" #include "envoy/ssl/context_manager.h" +#include "envoy/ssl/private_key/private_key.h" #include "envoy/stats/scope.h" +#include "extensions/transport_sockets/tls/private_key/private_key_manager_impl.h" + namespace Envoy { namespace Extensions { namespace TransportSockets { @@ -33,11 +36,15 @@ class ContextManagerImpl final : public Envoy::Ssl::ContextManager { const std::vector& server_names) override; size_t daysUntilFirstCertExpires() const override; void iterateContexts(std::function callback) override; + Ssl::PrivateKeyMethodManager& privateKeyMethodManager() override { + return private_key_method_manager_; + }; private: void removeEmptyContexts(); TimeSource& time_source_; std::list> contexts_; + PrivateKeyMethodManagerImpl private_key_method_manager_{}; }; } // namespace Tls diff --git a/source/extensions/transport_sockets/tls/private_key/BUILD b/source/extensions/transport_sockets/tls/private_key/BUILD new file mode 100644 index 0000000000..2c181249b5 --- /dev/null +++ b/source/extensions/transport_sockets/tls/private_key/BUILD @@ -0,0 +1,26 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_library( + name = "private_key_manager_lib", + srcs = [ + "private_key_manager_impl.cc", + ], + hdrs = [ + "private_key_manager_impl.h", + ], + deps = [ + "//include/envoy/event:dispatcher_interface", + "//include/envoy/registry", + "//include/envoy/ssl/private_key:private_key_config_interface", + "//include/envoy/ssl/private_key:private_key_interface", + "@envoy_api//envoy/api/v2/auth:cert_cc", + ], +) diff --git a/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.cc b/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.cc new file mode 100644 index 0000000000..817b9d3626 --- /dev/null +++ b/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.cc @@ -0,0 +1,30 @@ +#include "extensions/transport_sockets/tls/private_key/private_key_manager_impl.h" + +#include "envoy/registry/registry.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +Envoy::Ssl::PrivateKeyMethodProviderSharedPtr +PrivateKeyMethodManagerImpl::createPrivateKeyMethodProvider( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) { + + Ssl::PrivateKeyMethodProviderInstanceFactory* factory = + Registry::FactoryRegistry::getFactory( + config.provider_name()); + + // Create a new provider instance with the configuration. + if (factory) { + return factory->createPrivateKeyMethodProviderInstance(config, factory_context); + } + + return nullptr; +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.h b/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.h new file mode 100644 index 0000000000..1ae42d1916 --- /dev/null +++ b/source/extensions/transport_sockets/tls/private_key/private_key_manager_impl.h @@ -0,0 +1,23 @@ +#pragma once + +#include "envoy/api/v2/auth/cert.pb.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/private_key/private_key_config.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class PrivateKeyMethodManagerImpl : public virtual Ssl::PrivateKeyMethodManager { +public: + // Ssl::PrivateKeyMethodManager + Ssl::PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProvider( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) override; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/ssl_socket.cc b/source/extensions/transport_sockets/tls/ssl_socket.cc index d6b63be7d0..7737d36b16 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.cc +++ b/source/extensions/transport_sockets/tls/ssl_socket.cc @@ -38,20 +38,22 @@ class NotReadySslSocket : public Network::TransportSocket { return {PostIoAction::Close, 0, false}; } void onConnected() override {} - const Ssl::ConnectionInfo* ssl() const override { return nullptr; } + Ssl::ConnectionInfoConstSharedPtr ssl() const override { return nullptr; } }; } // namespace SslSocket::SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, const Network::TransportSocketOptionsSharedPtr& transport_socket_options) : transport_socket_options_(transport_socket_options), - ctx_(std::dynamic_pointer_cast(ctx)), - ssl_(ctx_->newSsl(transport_socket_options_.get())) { + ctx_(std::dynamic_pointer_cast(ctx)), state_(SocketState::PreHandshake) { + bssl::UniquePtr ssl = ctx_->newSsl(transport_socket_options_.get()); + ssl_ = ssl.get(); + info_ = std::make_shared(std::move(ssl)); if (state == InitialState::Client) { - SSL_set_connect_state(ssl_.get()); + SSL_set_connect_state(ssl_); } else { ASSERT(state == InitialState::Server); - SSL_set_accept_state(ssl_.get()); + SSL_set_accept_state(ssl_); } } @@ -59,8 +61,14 @@ void SslSocket::setTransportSocketCallbacks(Network::TransportSocketCallbacks& c ASSERT(!callbacks_); callbacks_ = &callbacks; + // Associate this SSL connection with all the certificates (with their potentially different + // private key methods). + for (auto const& provider : ctx_->getPrivateKeyMethodProviders()) { + provider->registerPrivateKeyMethod(ssl_, *this, callbacks_->connection().dispatcher()); + } + BIO* bio = BIO_new_socket(callbacks_->ioHandle().fd(), 0); - SSL_set_bio(ssl_.get(), bio, bio); + SSL_set_bio(ssl_, bio, bio); } SslSocket::ReadResult SslSocket::sslReadIntoSlice(Buffer::RawSlice& slice) { @@ -68,7 +76,7 @@ SslSocket::ReadResult SslSocket::sslReadIntoSlice(Buffer::RawSlice& slice) { uint8_t* mem = static_cast(slice.mem_); size_t remaining = slice.len_; while (remaining > 0) { - int rc = SSL_read(ssl_.get(), mem, remaining); + int rc = SSL_read(ssl_, mem, remaining); ENVOY_CONN_LOG(trace, "ssl read returns: {}", callbacks_->connection(), rc); if (rc > 0) { ASSERT(static_cast(rc) <= remaining); @@ -88,9 +96,9 @@ SslSocket::ReadResult SslSocket::sslReadIntoSlice(Buffer::RawSlice& slice) { } Network::IoResult SslSocket::doRead(Buffer::Instance& read_buffer) { - if (!handshake_complete_) { + if (state_ != SocketState::HandshakeComplete && state_ != SocketState::ShutdownSent) { PostIoAction action = doHandshake(); - if (action == PostIoAction::Close || !handshake_complete_) { + if (action == PostIoAction::Close || state_ != SocketState::HandshakeComplete) { // end_stream is false because either a hard error occurred (action == Close) or // the handshake isn't complete, so a half-close cannot occur yet. return {action, 0, false}; @@ -115,7 +123,7 @@ Network::IoResult SslSocket::doRead(Buffer::Instance& read_buffer) { } if (result.error_.has_value()) { keep_reading = false; - int err = SSL_get_error(ssl_.get(), result.error_.value()); + int err = SSL_get_error(ssl_, result.error_.value()); switch (err) { case SSL_ERROR_WANT_READ: break; @@ -149,13 +157,25 @@ Network::IoResult SslSocket::doRead(Buffer::Instance& read_buffer) { return {action, bytes_read, end_stream}; } +void SslSocket::onPrivateKeyMethodComplete() { + ASSERT(isThreadSafe()); + ASSERT(state_ == SocketState::HandshakeInProgress); + + // Resume handshake. + PostIoAction action = doHandshake(); + if (action == PostIoAction::Close) { + ENVOY_CONN_LOG(debug, "async handshake completion error", callbacks_->connection()); + callbacks_->connection().close(Network::ConnectionCloseType::FlushWrite); + } +} + PostIoAction SslSocket::doHandshake() { - ASSERT(!handshake_complete_); - int rc = SSL_do_handshake(ssl_.get()); + ASSERT(state_ != SocketState::HandshakeComplete && state_ != SocketState::ShutdownSent); + int rc = SSL_do_handshake(ssl_); if (rc == 1) { ENVOY_CONN_LOG(debug, "handshake complete", callbacks_->connection()); - handshake_complete_ = true; - ctx_->logHandshake(ssl_.get()); + state_ = SocketState::HandshakeComplete; + ctx_->logHandshake(ssl_); callbacks_->raiseEvent(Network::ConnectionEvent::Connected); // It's possible that we closed during the handshake callback. @@ -163,13 +183,19 @@ PostIoAction SslSocket::doHandshake() { ? PostIoAction::KeepOpen : PostIoAction::Close; } else { - int err = SSL_get_error(ssl_.get(), rc); - ENVOY_CONN_LOG(debug, "handshake error: {}", callbacks_->connection(), err); + int err = SSL_get_error(ssl_, rc); switch (err) { case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: + ENVOY_CONN_LOG(debug, "handshake expecting {}", callbacks_->connection(), + err == SSL_ERROR_WANT_READ ? "read" : "write"); + return PostIoAction::KeepOpen; + case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION: + ENVOY_CONN_LOG(debug, "handshake continued asynchronously", callbacks_->connection()); + state_ = SocketState::HandshakeInProgress; return PostIoAction::KeepOpen; default: + ENVOY_CONN_LOG(debug, "handshake error: {}", callbacks_->connection(), err); drainErrorQueue(); return PostIoAction::Close; } @@ -204,10 +230,10 @@ void SslSocket::drainErrorQueue() { } Network::IoResult SslSocket::doWrite(Buffer::Instance& write_buffer, bool end_stream) { - ASSERT(!shutdown_sent_ || write_buffer.length() == 0); - if (!handshake_complete_) { + ASSERT(state_ != SocketState::ShutdownSent || write_buffer.length() == 0); + if (state_ != SocketState::HandshakeComplete && state_ != SocketState::ShutdownSent) { PostIoAction action = doHandshake(); - if (action == PostIoAction::Close || !handshake_complete_) { + if (action == PostIoAction::Close || state_ != SocketState::HandshakeComplete) { return {action, 0, false}; } } @@ -229,7 +255,7 @@ Network::IoResult SslSocket::doWrite(Buffer::Instance& write_buffer, bool end_st // it again with the same parameters. This is done by tracking last write size, but not write // data, since linearize() will return the same undrained data anyway. ASSERT(bytes_to_write <= write_buffer.length()); - int rc = SSL_write(ssl_.get(), write_buffer.linearize(bytes_to_write), bytes_to_write); + int rc = SSL_write(ssl_, write_buffer.linearize(bytes_to_write), bytes_to_write); ENVOY_CONN_LOG(trace, "ssl write returns: {}", callbacks_->connection(), rc); if (rc > 0) { ASSERT(rc == static_cast(bytes_to_write)); @@ -237,7 +263,7 @@ Network::IoResult SslSocket::doWrite(Buffer::Instance& write_buffer, bool end_st write_buffer.drain(rc); bytes_to_write = std::min(write_buffer.length(), static_cast(16384)); } else { - int err = SSL_get_error(ssl_.get(), rc); + int err = SSL_get_error(ssl_, rc); switch (err) { case SSL_ERROR_WANT_WRITE: bytes_to_retry_ = bytes_to_write; @@ -260,41 +286,56 @@ Network::IoResult SslSocket::doWrite(Buffer::Instance& write_buffer, bool end_st return {PostIoAction::KeepOpen, total_bytes_written, false}; } -void SslSocket::onConnected() { ASSERT(!handshake_complete_); } +void SslSocket::onConnected() { ASSERT(state_ == SocketState::PreHandshake); } + +Ssl::ConnectionInfoConstSharedPtr SslSocket::ssl() const { return info_; } void SslSocket::shutdownSsl() { - ASSERT(handshake_complete_); - if (!shutdown_sent_ && callbacks_->connection().state() != Network::Connection::State::Closed) { - int rc = SSL_shutdown(ssl_.get()); + ASSERT(state_ != SocketState::PreHandshake); + if (state_ != SocketState::ShutdownSent && + callbacks_->connection().state() != Network::Connection::State::Closed) { + int rc = SSL_shutdown(ssl_); ENVOY_CONN_LOG(debug, "SSL shutdown: rc={}", callbacks_->connection(), rc); drainErrorQueue(); - shutdown_sent_ = true; + state_ = SocketState::ShutdownSent; } } -bool SslSocket::peerCertificatePresented() const { +bool SslSocketInfo::peerCertificatePresented() const { bssl::UniquePtr cert(SSL_get_peer_certificate(ssl_.get())); return cert != nullptr; } -std::vector SslSocket::uriSanLocalCertificate() const { +std::vector SslSocketInfo::uriSanLocalCertificate() const { + if (!cached_uri_san_local_certificate_.empty()) { + return cached_uri_san_local_certificate_; + } + // The cert object is not owned. X509* cert = SSL_get_certificate(ssl_.get()); if (!cert) { - return {}; + ASSERT(cached_uri_san_local_certificate_.empty()); + return cached_uri_san_local_certificate_; } - return Utility::getSubjectAltNames(*cert, GEN_URI); + cached_uri_san_local_certificate_ = Utility::getSubjectAltNames(*cert, GEN_URI); + return cached_uri_san_local_certificate_; } -std::vector SslSocket::dnsSansLocalCertificate() const { +std::vector SslSocketInfo::dnsSansLocalCertificate() const { + if (!cached_dns_san_local_certificate_.empty()) { + return cached_dns_san_local_certificate_; + } + X509* cert = SSL_get_certificate(ssl_.get()); if (!cert) { - return {}; + ASSERT(cached_dns_san_local_certificate_.empty()); + return cached_dns_san_local_certificate_; } - return Utility::getSubjectAltNames(*cert, GEN_DNS); + cached_dns_san_local_certificate_ = Utility::getSubjectAltNames(*cert, GEN_DNS); + return cached_dns_san_local_certificate_; } -const std::string& SslSocket::sha256PeerCertificateDigest() const { +const std::string& SslSocketInfo::sha256PeerCertificateDigest() const { if (!cached_sha_256_peer_certificate_digest_.empty()) { return cached_sha_256_peer_certificate_digest_; } @@ -312,7 +353,7 @@ const std::string& SslSocket::sha256PeerCertificateDigest() const { return cached_sha_256_peer_certificate_digest_; } -const std::string& SslSocket::urlEncodedPemEncodedPeerCertificate() const { +const std::string& SslSocketInfo::urlEncodedPemEncodedPeerCertificate() const { if (!cached_url_encoded_pem_encoded_peer_certificate_.empty()) { return cached_url_encoded_pem_encoded_peer_certificate_; } @@ -334,7 +375,7 @@ const std::string& SslSocket::urlEncodedPemEncodedPeerCertificate() const { return cached_url_encoded_pem_encoded_peer_certificate_; } -const std::string& SslSocket::urlEncodedPemEncodedPeerCertificateChain() const { +const std::string& SslSocketInfo::urlEncodedPemEncodedPeerCertificateChain() const { if (!cached_url_encoded_pem_encoded_peer_cert_chain_.empty()) { return cached_url_encoded_pem_encoded_peer_cert_chain_; } @@ -364,27 +405,44 @@ const std::string& SslSocket::urlEncodedPemEncodedPeerCertificateChain() const { return cached_url_encoded_pem_encoded_peer_cert_chain_; } -std::vector SslSocket::uriSanPeerCertificate() const { +std::vector SslSocketInfo::uriSanPeerCertificate() const { + if (!cached_uri_san_peer_certificate_.empty()) { + return cached_uri_san_peer_certificate_; + } + bssl::UniquePtr cert(SSL_get_peer_certificate(ssl_.get())); if (!cert) { - return {}; + ASSERT(cached_uri_san_peer_certificate_.empty()); + return cached_uri_san_peer_certificate_; } - return Utility::getSubjectAltNames(*cert, GEN_URI); + cached_uri_san_peer_certificate_ = Utility::getSubjectAltNames(*cert, GEN_URI); + return cached_uri_san_peer_certificate_; } -std::vector SslSocket::dnsSansPeerCertificate() const { +std::vector SslSocketInfo::dnsSansPeerCertificate() const { + if (!cached_dns_san_peer_certificate_.empty()) { + return cached_dns_san_peer_certificate_; + } + bssl::UniquePtr cert(SSL_get_peer_certificate(ssl_.get())); if (!cert) { - return {}; + ASSERT(cached_dns_san_peer_certificate_.empty()); + return cached_dns_san_peer_certificate_; } - return Utility::getSubjectAltNames(*cert, GEN_DNS); + cached_dns_san_peer_certificate_ = Utility::getSubjectAltNames(*cert, GEN_DNS); + return cached_dns_san_peer_certificate_; } void SslSocket::closeSocket(Network::ConnectionEvent) { + // Unregister the SSL connection object from private key method providers. + for (auto const& provider : ctx_->getPrivateKeyMethodProviders()) { + provider->unregisterPrivateKeyMethod(ssl_); + } + // Attempt to send a shutdown before closing the socket. It's possible this won't go out if // there is no room on the socket. We can extend the state machine to handle this at some point // if needed. - if (handshake_complete_) { + if (state_ == SocketState::HandshakeInProgress || state_ == SocketState::HandshakeComplete) { shutdownSsl(); } } @@ -392,11 +450,11 @@ void SslSocket::closeSocket(Network::ConnectionEvent) { std::string SslSocket::protocol() const { const unsigned char* proto; unsigned int proto_len; - SSL_get0_alpn_selected(ssl_.get(), &proto, &proto_len); + SSL_get0_alpn_selected(ssl_, &proto, &proto_len); return std::string(reinterpret_cast(proto), proto_len); } -uint16_t SslSocket::ciphersuiteId() const { +uint16_t SslSocketInfo::ciphersuiteId() const { const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl_.get()); if (cipher == nullptr) { return 0xffff; @@ -408,52 +466,78 @@ uint16_t SslSocket::ciphersuiteId() const { return static_cast(SSL_CIPHER_get_id(cipher)); } -std::string SslSocket::ciphersuiteString() const { +std::string SslSocketInfo::ciphersuiteString() const { const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl_.get()); if (cipher == nullptr) { - return std::string(); + return {}; } - return std::string(SSL_CIPHER_get_name(cipher)); + return SSL_CIPHER_get_name(cipher); } -std::string SslSocket::tlsVersion() const { return std::string(SSL_get_version(ssl_.get())); } +const std::string& SslSocketInfo::tlsVersion() const { + if (!cached_tls_version_.empty()) { + return cached_tls_version_; + } + cached_tls_version_ = SSL_get_version(ssl_.get()); + return cached_tls_version_; +} absl::string_view SslSocket::failureReason() const { return failure_reason_; } -std::string SslSocket::serialNumberPeerCertificate() const { +const std::string& SslSocketInfo::serialNumberPeerCertificate() const { + if (!cached_serial_number_peer_certificate_.empty()) { + return cached_serial_number_peer_certificate_; + } bssl::UniquePtr cert(SSL_get_peer_certificate(ssl_.get())); if (!cert) { - return ""; + ASSERT(cached_serial_number_peer_certificate_.empty()); + return cached_serial_number_peer_certificate_; } - return Utility::getSerialNumberFromCertificate(*cert.get()); + cached_serial_number_peer_certificate_ = Utility::getSerialNumberFromCertificate(*cert.get()); + return cached_serial_number_peer_certificate_; } -std::string SslSocket::issuerPeerCertificate() const { +const std::string& SslSocketInfo::issuerPeerCertificate() const { + if (!cached_issuer_peer_certificate_.empty()) { + return cached_issuer_peer_certificate_; + } bssl::UniquePtr cert(SSL_get_peer_certificate(ssl_.get())); if (!cert) { - return ""; + ASSERT(cached_issuer_peer_certificate_.empty()); + return cached_issuer_peer_certificate_; } - return Utility::getIssuerFromCertificate(*cert); + cached_issuer_peer_certificate_ = Utility::getIssuerFromCertificate(*cert); + return cached_issuer_peer_certificate_; } -std::string SslSocket::subjectPeerCertificate() const { +const std::string& SslSocketInfo::subjectPeerCertificate() const { + if (!cached_subject_peer_certificate_.empty()) { + return cached_subject_peer_certificate_; + } bssl::UniquePtr cert(SSL_get_peer_certificate(ssl_.get())); if (!cert) { - return ""; + ASSERT(cached_subject_peer_certificate_.empty()); + return cached_subject_peer_certificate_; } - return Utility::getSubjectFromCertificate(*cert); + cached_subject_peer_certificate_ = Utility::getSubjectFromCertificate(*cert); + return cached_subject_peer_certificate_; } -std::string SslSocket::subjectLocalCertificate() const { +const std::string& SslSocketInfo::subjectLocalCertificate() const { + if (!cached_subject_local_certificate_.empty()) { + return cached_subject_local_certificate_; + } X509* cert = SSL_get_certificate(ssl_.get()); if (!cert) { - return ""; + ASSERT(cached_subject_local_certificate_.empty()); + return cached_subject_local_certificate_; } - return Utility::getSubjectFromCertificate(*cert); + cached_subject_local_certificate_ = Utility::getSubjectFromCertificate(*cert); + return cached_subject_local_certificate_; } -absl::optional SslSocket::validFromPeerCertificate() const { +absl::optional SslSocketInfo::validFromPeerCertificate() const { bssl::UniquePtr cert(SSL_get_peer_certificate(ssl_.get())); if (!cert) { return absl::nullopt; @@ -461,7 +545,7 @@ absl::optional SslSocket::validFromPeerCertificate() const { return Utility::getValidFrom(*cert); } -absl::optional SslSocket::expirationPeerCertificate() const { +absl::optional SslSocketInfo::expirationPeerCertificate() const { bssl::UniquePtr cert(SSL_get_peer_certificate(ssl_.get())); if (!cert) { return absl::nullopt; @@ -469,15 +553,20 @@ absl::optional SslSocket::expirationPeerCertificate() const { return Utility::getExpirationTime(*cert); } -std::string SslSocket::sessionId() const { +const std::string& SslSocketInfo::sessionId() const { + if (!cached_session_id_.empty()) { + return cached_session_id_; + } SSL_SESSION* session = SSL_get_session(ssl_.get()); if (session == nullptr) { - return ""; + ASSERT(cached_session_id_.empty()); + return cached_session_id_; } unsigned int session_id_length = 0; const uint8_t* session_id = SSL_SESSION_get_id(session, &session_id_length); - return Hex::encode(session_id, session_id_length); + cached_session_id_ = Hex::encode(session_id, session_id_length); + return cached_session_id_; } namespace { diff --git a/source/extensions/transport_sockets/tls/ssl_socket.h b/source/extensions/transport_sockets/tls/ssl_socket.h index 549ffbf836..4f7660717b 100644 --- a/source/extensions/transport_sockets/tls/ssl_socket.h +++ b/source/extensions/transport_sockets/tls/ssl_socket.h @@ -6,6 +6,7 @@ #include "envoy/network/connection.h" #include "envoy/network/transport_socket.h" #include "envoy/secret/secret_callbacks.h" +#include "envoy/ssl/private_key/private_key_callbacks.h" #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" @@ -38,22 +39,20 @@ struct SslSocketFactoryStats { }; enum class InitialState { Client, Server }; +enum class SocketState { PreHandshake, HandshakeInProgress, HandshakeComplete, ShutdownSent }; -class SslSocket : public Network::TransportSocket, - public Envoy::Ssl::ConnectionInfo, - protected Logger::Loggable { +class SslSocketInfo : public Envoy::Ssl::ConnectionInfo { public: - SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, - const Network::TransportSocketOptionsSharedPtr& transport_socket_options); + SslSocketInfo(bssl::UniquePtr ssl) : ssl_(std::move(ssl)) {} // Ssl::ConnectionInfo bool peerCertificatePresented() const override; std::vector uriSanLocalCertificate() const override; const std::string& sha256PeerCertificateDigest() const override; - std::string serialNumberPeerCertificate() const override; - std::string issuerPeerCertificate() const override; - std::string subjectPeerCertificate() const override; - std::string subjectLocalCertificate() const override; + const std::string& serialNumberPeerCertificate() const override; + const std::string& issuerPeerCertificate() const override; + const std::string& subjectPeerCertificate() const override; + const std::string& subjectLocalCertificate() const override; std::vector uriSanPeerCertificate() const override; const std::string& urlEncodedPemEncodedPeerCertificate() const override; const std::string& urlEncodedPemEncodedPeerCertificateChain() const override; @@ -61,23 +60,52 @@ class SslSocket : public Network::TransportSocket, std::vector dnsSansLocalCertificate() const override; absl::optional validFromPeerCertificate() const override; absl::optional expirationPeerCertificate() const override; - std::string sessionId() const override; + const std::string& sessionId() const override; uint16_t ciphersuiteId() const override; std::string ciphersuiteString() const override; - std::string tlsVersion() const override; + const std::string& tlsVersion() const override; + + SSL* rawSslForTest() const { return ssl_.get(); } + + bssl::UniquePtr ssl_; + +private: + mutable std::vector cached_uri_san_local_certificate_; + mutable std::string cached_sha_256_peer_certificate_digest_; + mutable std::string cached_serial_number_peer_certificate_; + mutable std::string cached_issuer_peer_certificate_; + mutable std::string cached_subject_peer_certificate_; + mutable std::string cached_subject_local_certificate_; + mutable std::vector cached_uri_san_peer_certificate_; + mutable std::string cached_url_encoded_pem_encoded_peer_certificate_; + mutable std::string cached_url_encoded_pem_encoded_peer_cert_chain_; + mutable std::vector cached_dns_san_peer_certificate_; + mutable std::vector cached_dns_san_local_certificate_; + mutable std::string cached_session_id_; + mutable std::string cached_tls_version_; +}; + +class SslSocket : public Network::TransportSocket, + public Envoy::Ssl::PrivateKeyConnectionCallbacks, + protected Logger::Loggable { +public: + SslSocket(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, + const Network::TransportSocketOptionsSharedPtr& transport_socket_options); // Network::TransportSocket void setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) override; std::string protocol() const override; absl::string_view failureReason() const override; - bool canFlushClose() override { return handshake_complete_; } + bool canFlushClose() override { return state_ == SocketState::HandshakeComplete; } void closeSocket(Network::ConnectionEvent close_type) override; Network::IoResult doRead(Buffer::Instance& read_buffer) override; Network::IoResult doWrite(Buffer::Instance& write_buffer, bool end_stream) override; void onConnected() override; - const Ssl::ConnectionInfo* ssl() const override { return this; } + Ssl::ConnectionInfoConstSharedPtr ssl() const override; + // Ssl::PrivateKeyConnectionCallbacks + void onPrivateKeyMethodComplete() override; - SSL* rawSslForTest() const { return ssl_.get(); } + SSL* rawSslForTest() const { return ssl_; } private: struct ReadResult { @@ -89,18 +117,19 @@ class SslSocket : public Network::TransportSocket, Network::PostIoAction doHandshake(); void drainErrorQueue(); void shutdownSsl(); + bool isThreadSafe() const { + return callbacks_ != nullptr && callbacks_->connection().dispatcher().isThreadSafe(); + } const Network::TransportSocketOptionsSharedPtr transport_socket_options_; Network::TransportSocketCallbacks* callbacks_{}; ContextImplSharedPtr ctx_; - bssl::UniquePtr ssl_; - bool handshake_complete_{}; - bool shutdown_sent_{}; uint64_t bytes_to_retry_{}; std::string failure_reason_; - mutable std::string cached_sha_256_peer_certificate_digest_; - mutable std::string cached_url_encoded_pem_encoded_peer_certificate_; - mutable std::string cached_url_encoded_pem_encoded_peer_cert_chain_; + SocketState state_; + + SSL* ssl_; + Ssl::ConnectionInfoConstSharedPtr info_; }; class ClientSslSocketFactory : public Network::TransportSocketFactory, @@ -123,7 +152,7 @@ class ClientSslSocketFactory : public Network::TransportSocketFactory, SslSocketFactoryStats stats_; Envoy::Ssl::ClientContextConfigPtr config_; mutable absl::Mutex ssl_ctx_mu_; - Envoy::Ssl::ClientContextSharedPtr ssl_ctx_ GUARDED_BY(ssl_ctx_mu_); + Envoy::Ssl::ClientContextSharedPtr ssl_ctx_ ABSL_GUARDED_BY(ssl_ctx_mu_); }; class ServerSslSocketFactory : public Network::TransportSocketFactory, @@ -148,7 +177,7 @@ class ServerSslSocketFactory : public Network::TransportSocketFactory, Envoy::Ssl::ServerContextConfigPtr config_; const std::vector server_names_; mutable absl::Mutex ssl_ctx_mu_; - Envoy::Ssl::ServerContextSharedPtr ssl_ctx_ GUARDED_BY(ssl_ctx_mu_); + Envoy::Ssl::ServerContextSharedPtr ssl_ctx_ ABSL_GUARDED_BY(ssl_ctx_mu_); }; } // namespace Tls diff --git a/source/extensions/transport_sockets/well_known_names.h b/source/extensions/transport_sockets/well_known_names.h index 3d2270a632..078337c256 100644 --- a/source/extensions/transport_sockets/well_known_names.h +++ b/source/extensions/transport_sockets/well_known_names.h @@ -18,6 +18,7 @@ class TransportSocketNameValues { const std::string Tap = "envoy.transport_sockets.tap"; const std::string RawBuffer = "raw_buffer"; const std::string Tls = "tls"; + const std::string Quic = "quic"; }; using TransportSocketNames = ConstSingleton; diff --git a/source/server/BUILD b/source/server/BUILD index daaa42b030..f7bb84c80f 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -65,6 +65,7 @@ envoy_cc_library( "//include/envoy/network:filter_interface", "//include/envoy/network:listen_socket_interface", "//include/envoy/network:listener_interface", + "//include/envoy/server:active_udp_listener_config_interface", "//include/envoy/server:listener_manager_interface", "//include/envoy/stats:timespan", "//source/common/common:linked_object", @@ -107,6 +108,7 @@ envoy_cc_library( "//source/common/common:minimal_logger_lib", "//source/common/common:thread_lib", "//source/common/event:libevent_lib", + "//source/common/stats:symbol_table_lib", ], ) @@ -228,6 +230,7 @@ envoy_cc_library( "//include/envoy/thread_local:thread_local_interface", "//source/common/common:logger_lib", "//source/common/config:utility_lib", + "//source/common/stats:symbol_table_lib", "//source/server:resource_monitor_config_lib", "@envoy_api//envoy/config/overload/v2alpha:overload_cc", ], @@ -261,6 +264,8 @@ envoy_cc_library( ":filter_chain_manager_lib", ":lds_api_lib", ":transport_socket_config_lib", + ":well_known_names_lib", + "//include/envoy/server:active_udp_listener_config_interface", "//include/envoy/server:filter_config_interface", "//include/envoy/server:listener_manager_interface", "//include/envoy/server:transport_socket_config_interface", @@ -390,6 +395,7 @@ envoy_cc_library( "//source/common/runtime:runtime_lib", "//source/common/secret:secret_manager_impl_lib", "//source/common/singleton:manager_impl_lib", + "//source/common/stats:symbol_table_creator_lib", "//source/common/stats:thread_local_store_lib", "//source/common/upstream:cluster_manager_lib", "//source/common/upstream:health_discovery_service_lib", @@ -452,3 +458,21 @@ envoy_cc_library( "//include/envoy/server:transport_socket_config_interface", ], ) + +envoy_cc_library( + name = "well_known_names_lib", + hdrs = ["well_known_names.h"], + deps = ["//source/common/singleton:const_singleton"], +) + +envoy_cc_library( + name = "active_raw_udp_listener_config", + srcs = ["active_raw_udp_listener_config.cc"], + hdrs = ["active_raw_udp_listener_config.h"], + deps = [ + ":connection_handler_lib", + ":well_known_names_lib", + "//include/envoy/registry", + "//include/envoy/server:active_udp_listener_config_interface", + ], +) diff --git a/source/server/active_raw_udp_listener_config.cc b/source/server/active_raw_udp_listener_config.cc new file mode 100644 index 0000000000..0d6cbb196c --- /dev/null +++ b/source/server/active_raw_udp_listener_config.cc @@ -0,0 +1,30 @@ +#include "server/active_raw_udp_listener_config.h" + +#include "server/connection_handler_impl.h" +#include "server/well_known_names.h" + +namespace Envoy { +namespace Server { + +Network::ConnectionHandler::ActiveListenerPtr ActiveRawUdpListenerFactory::createActiveUdpListener( + Network::ConnectionHandler& /*parent*/, Event::Dispatcher& dispatcher, + spdlog::logger& /*logger*/, Network::ListenerConfig& config) const { + return std::make_unique(dispatcher, config); +} + +ProtobufTypes::MessagePtr ActiveRawUdpListenerConfigFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +Network::ActiveUdpListenerFactoryPtr +ActiveRawUdpListenerConfigFactory::createActiveUdpListenerFactory( + const Protobuf::Message& /*message*/) { + return std::make_unique(); +} + +std::string ActiveRawUdpListenerConfigFactory::name() { return UdpListenerNames::get().RawUdp; } + +REGISTER_FACTORY(ActiveRawUdpListenerConfigFactory, Server::ActiveUdpListenerConfigFactory); + +} // namespace Server +} // namespace Envoy diff --git a/source/server/active_raw_udp_listener_config.h b/source/server/active_raw_udp_listener_config.h new file mode 100644 index 0000000000..b58554c9c3 --- /dev/null +++ b/source/server/active_raw_udp_listener_config.h @@ -0,0 +1,33 @@ +#pragma once + +#include "envoy/network/connection_handler.h" +#include "envoy/registry/registry.h" +#include "envoy/server/active_udp_listener_config.h" + +namespace Envoy { +namespace Server { + +class ActiveRawUdpListenerFactory : public Network::ActiveUdpListenerFactory { +public: + Network::ConnectionHandler::ActiveListenerPtr + createActiveUdpListener(Network::ConnectionHandler& parent, Event::Dispatcher& disptacher, + spdlog::logger& logger, Network::ListenerConfig& config) const override; +}; + +// This class uses a protobuf config to create a UDP listener factory which +// creates a Server::ConnectionHandlerImpl::ActiveUdpListener. +// This is the default UDP listener if not specified in config. +class ActiveRawUdpListenerConfigFactory : public ActiveUdpListenerConfigFactory { +public: + ProtobufTypes::MessagePtr createEmptyConfigProto() override; + + Network::ActiveUdpListenerFactoryPtr + createActiveUdpListenerFactory(const Protobuf::Message&) override; + + std::string name() override; +}; + +DECLARE_FACTORY(ActiveRawUdpListenerConfigFactory); + +} // namespace Server +} // namespace Envoy diff --git a/source/server/config_validation/server.h b/source/server/config_validation/server.h index bbd350b325..8a59c4a046 100644 --- a/source/server/config_validation/server.h +++ b/source/server/config_validation/server.h @@ -94,7 +94,9 @@ class ValidationInstance : Logger::Loggable, Stats::Store& stats() override { return stats_store_; } Grpc::Context& grpcContext() override { return grpc_context_; } Http::Context& httpContext() override { return http_context_; } - ProcessContext& processContext() override { NOT_IMPLEMENTED_GCOVR_EXCL_LINE; } + absl::optional> processContext() override { + return absl::nullopt; + } ThreadLocal::Instance& threadLocal() override { return thread_local_; } const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } TimeSource& timeSource() override { return api_->timeSource(); } diff --git a/source/server/connection_handler_impl.cc b/source/server/connection_handler_impl.cc index b7e08a3144..398cb88fd7 100644 --- a/source/server/connection_handler_impl.cc +++ b/source/server/connection_handler_impl.cc @@ -17,29 +17,35 @@ namespace Server { ConnectionHandlerImpl::ConnectionHandlerImpl(spdlog::logger& logger, Event::Dispatcher& dispatcher) : logger_(logger), dispatcher_(dispatcher), disable_listeners_(false) {} +void ConnectionHandlerImpl::incNumConnections() { ++num_connections_; } + +void ConnectionHandlerImpl::decNumConnections() { + ASSERT(num_connections_ > 0); + --num_connections_; +} + void ConnectionHandlerImpl::addListener(Network::ListenerConfig& config) { - ActiveListenerBasePtr listener; + Network::ConnectionHandler::ActiveListenerPtr listener; Network::Address::SocketType socket_type = config.socket().socketType(); if (socket_type == Network::Address::SocketType::Stream) { - ActiveTcpListenerPtr tcp(new ActiveTcpListener(*this, config)); - listener = std::move(tcp); + listener = std::make_unique(*this, config); } else { ASSERT(socket_type == Network::Address::SocketType::Datagram, "Only datagram/stream listener supported"); - ActiveUdpListenerPtr udp(new ActiveUdpListener(*this, config)); - listener = std::move(udp); + listener = + config.udpListenerFactory()->createActiveUdpListener(*this, dispatcher_, logger_, config); } if (disable_listeners_) { - listener->listener_->disable(); + listener->listener()->disable(); } listeners_.emplace_back(config.socket().localAddress(), std::move(listener)); } void ConnectionHandlerImpl::removeListeners(uint64_t listener_tag) { for (auto listener = listeners_.begin(); listener != listeners_.end();) { - if (listener->second->listener_tag_ == listener_tag) { + if (listener->second->listenerTag() == listener_tag) { listener = listeners_.erase(listener); } else { ++listener; @@ -49,29 +55,29 @@ void ConnectionHandlerImpl::removeListeners(uint64_t listener_tag) { void ConnectionHandlerImpl::stopListeners(uint64_t listener_tag) { for (auto& listener : listeners_) { - if (listener.second->listener_tag_ == listener_tag) { - listener.second->listener_.reset(); + if (listener.second->listenerTag() == listener_tag) { + listener.second->destroy(); } } } void ConnectionHandlerImpl::stopListeners() { for (auto& listener : listeners_) { - listener.second->listener_.reset(); + listener.second->destroy(); } } void ConnectionHandlerImpl::disableListeners() { disable_listeners_ = true; for (auto& listener : listeners_) { - listener.second->listener_->disable(); + listener.second->listener()->disable(); } } void ConnectionHandlerImpl::enableListeners() { disable_listeners_ = false; for (auto& listener : listeners_) { - listener.second->listener_->enable(); + listener.second->listener()->enable(); } } @@ -84,11 +90,9 @@ void ConnectionHandlerImpl::ActiveTcpListener::removeConnection(ActiveConnection parent_.num_connections_--; } -ConnectionHandlerImpl::ActiveListenerBase::ActiveListenerBase(ConnectionHandlerImpl& parent, - Network::ListenerPtr&& listener, - Network::ListenerConfig& config) - : parent_(parent), listener_(std::move(listener)), - stats_(generateStats(config.listenerScope())), +ConnectionHandlerImpl::ActiveListenerImplBase::ActiveListenerImplBase( + Network::ListenerPtr&& listener, Network::ListenerConfig& config) + : listener_(std::move(listener)), stats_(generateStats(config.listenerScope())), listener_filters_timeout_(config.listenerFiltersTimeout()), continue_on_listener_filters_timeout_(config.continueOnListenerFiltersTimeout()), listener_tag_(config.listenerTag()), config_(config) {} @@ -104,7 +108,7 @@ ConnectionHandlerImpl::ActiveTcpListener::ActiveTcpListener(ConnectionHandlerImp ConnectionHandlerImpl::ActiveTcpListener::ActiveTcpListener(ConnectionHandlerImpl& parent, Network::ListenerPtr&& listener, Network::ListenerConfig& config) - : ConnectionHandlerImpl::ActiveListenerBase(parent, std::move(listener), config) {} + : ConnectionHandlerImpl::ActiveListenerImplBase(std::move(listener), config), parent_(parent) {} ConnectionHandlerImpl::ActiveTcpListener::~ActiveTcpListener() { // Purge sockets that have not progressed to connections. This should only happen when @@ -123,22 +127,22 @@ ConnectionHandlerImpl::ActiveTcpListener::~ActiveTcpListener() { Network::Listener* ConnectionHandlerImpl::findListenerByAddress(const Network::Address::Instance& address) { - ActiveListenerBase* listener = findActiveListenerByAddress(address); - return listener ? listener->listener_.get() : nullptr; + Network::ConnectionHandler::ActiveListener* listener = findActiveListenerByAddress(address); + return listener ? listener->listener() : nullptr; } -ConnectionHandlerImpl::ActiveListenerBase* +Network::ConnectionHandler::ActiveListener* ConnectionHandlerImpl::findActiveListenerByAddress(const Network::Address::Instance& address) { // This is a linear operation, may need to add a map to improve performance. // However, linear performance might be adequate since the number of listeners is small. // We do not return stopped listeners. - auto listener_it = std::find_if( - listeners_.begin(), listeners_.end(), - [&address]( - const std::pair& p) { - return p.second->listener_ != nullptr && p.first->type() == Network::Address::Type::Ip && - *(p.first) == address; - }); + auto listener_it = + std::find_if(listeners_.begin(), listeners_.end(), + [&address](const std::pair& p) { + return p.second->listener() != nullptr && + p.first->type() == Network::Address::Type::Ip && *(p.first) == address; + }); // If there is exact address match, return the corresponding listener. if (listener_it != listeners_.end()) { @@ -150,9 +154,9 @@ ConnectionHandlerImpl::findActiveListenerByAddress(const Network::Address::Insta // TODO(wattli): consolidate with previous search for more efficiency. listener_it = std::find_if( listeners_.begin(), listeners_.end(), - [&address]( - const std::pair& p) { - return p.second->listener_ != nullptr && p.first->type() == Network::Address::Type::Ip && + [&address](const std::pair& p) { + return p.second->listener() != nullptr && p.first->type() == Network::Address::Type::Ip && p.first->ip()->port() == address.ip()->port() && p.first->ip()->isAnyAddress(); }); return (listener_it != listeners_.end()) ? listener_it->second.get() : nullptr; @@ -161,7 +165,11 @@ ConnectionHandlerImpl::findActiveListenerByAddress(const Network::Address::Insta void ConnectionHandlerImpl::ActiveSocket::onTimeout() { listener_.stats_.downstream_pre_cx_timeout_.inc(); ASSERT(inserted()); + ENVOY_LOG_TO_LOGGER(listener_.parent_.logger_, debug, "listener filter times out after {} ms", + listener_.listener_filters_timeout_.count()); + if (listener_.continue_on_listener_filters_timeout_) { + ENVOY_LOG_TO_LOGGER(listener_.parent_.logger_, debug, "fallback to default listener filter"); newConnection(); } unlink(); @@ -184,6 +192,7 @@ void ConnectionHandlerImpl::ActiveSocket::unlink() { void ConnectionHandlerImpl::ActiveSocket::continueFilterChain(bool success) { if (success) { + bool no_error = true; if (iter_ == accept_filters_.end()) { iter_ = accept_filters_.begin(); } else { @@ -195,11 +204,23 @@ void ConnectionHandlerImpl::ActiveSocket::continueFilterChain(bool success) { if (status == Network::FilterStatus::StopIteration) { // The filter is responsible for calling us again at a later time to continue the filter // chain from the next filter. - return; + if (!socket().ioHandle().isOpen()) { + // break the loop but should not create new connection + no_error = false; + break; + } else { + // Blocking at the filter but no error + return; + } } } // Successfully ran all the accept filters. - newConnection(); + if (no_error) { + newConnection(); + } else { + // Signal the caller that no extra filter chain iteration is needed. + iter_ = accept_filters_.end(); + } } // Filter execution concluded, unlink and delete this ActiveSocket if it was linked. @@ -210,7 +231,7 @@ void ConnectionHandlerImpl::ActiveSocket::continueFilterChain(bool success) { void ConnectionHandlerImpl::ActiveSocket::newConnection() { // Check if the socket may need to be redirected to another listener. - ActiveListenerBase* new_listener = nullptr; + ConnectionHandler::ActiveListener* new_listener = nullptr; if (hand_off_restored_destination_connections_ && socket_->localAddressRestored()) { // Find a listener associated with the original destination address. @@ -318,15 +339,12 @@ ListenerStats ConnectionHandlerImpl::generateStats(Stats::Scope& scope) { return {ALL_LISTENER_STATS(POOL_COUNTER(scope), POOL_GAUGE(scope), POOL_HISTOGRAM(scope))}; } -ConnectionHandlerImpl::ActiveUdpListener::ActiveUdpListener(ConnectionHandlerImpl& parent, - Network::ListenerConfig& config) - : ActiveUdpListener(parent, parent.dispatcher_.createUdpListener(config.socket(), *this), - config) {} +ActiveUdpListener::ActiveUdpListener(Event::Dispatcher& dispatcher, Network::ListenerConfig& config) + : ActiveUdpListener(dispatcher.createUdpListener(config.socket(), *this), config) {} -ConnectionHandlerImpl::ActiveUdpListener::ActiveUdpListener(ConnectionHandlerImpl& parent, - Network::ListenerPtr&& listener, - Network::ListenerConfig& config) - : ConnectionHandlerImpl::ActiveListenerBase(parent, std::move(listener), config), +ActiveUdpListener::ActiveUdpListener(Network::ListenerPtr&& listener, + Network::ListenerConfig& config) + : ConnectionHandlerImpl::ActiveListenerImplBase(std::move(listener), config), udp_listener_(dynamic_cast(listener_.get())), read_filter_(nullptr) { // TODO(sumukhs): Try to avoid dynamic_cast by coming up with a better interface design ASSERT(udp_listener_ != nullptr, ""); @@ -342,32 +360,27 @@ ConnectionHandlerImpl::ActiveUdpListener::ActiveUdpListener(ConnectionHandlerImp } } -void ConnectionHandlerImpl::ActiveUdpListener::onData(Network::UdpRecvData& data) { - read_filter_->onData(data); -} +void ActiveUdpListener::onData(Network::UdpRecvData& data) { read_filter_->onData(data); } -void ConnectionHandlerImpl::ActiveUdpListener::onWriteReady(const Network::Socket&) { +void ActiveUdpListener::onWriteReady(const Network::Socket&) { // TODO(sumukhs): This is not used now. When write filters are implemented, this is a // trigger to invoke the on write ready API on the filters which is when they can write // data } -void ConnectionHandlerImpl::ActiveUdpListener::onReceiveError( - const Network::UdpListenerCallbacks::ErrorCode&, Api::IoError::IoErrorCode) { +void ActiveUdpListener::onReceiveError(const Network::UdpListenerCallbacks::ErrorCode&, + Api::IoError::IoErrorCode) { // TODO(sumukhs): Determine what to do on receive error. // Would the filters need to know on error? Can't foresee a scenario where they // would take an action } -void ConnectionHandlerImpl::ActiveUdpListener::addReadFilter( - Network::UdpListenerReadFilterPtr&& filter) { +void ActiveUdpListener::addReadFilter(Network::UdpListenerReadFilterPtr&& filter) { ASSERT(read_filter_ == nullptr, "Cannot add a 2nd UDP read filter"); read_filter_ = std::move(filter); } -Network::UdpListener& ConnectionHandlerImpl::ActiveUdpListener::udpListener() { - return *udp_listener_; -} +Network::UdpListener& ActiveUdpListener::udpListener() { return *udp_listener_; } } // namespace Server } // namespace Envoy diff --git a/source/server/connection_handler_impl.h b/source/server/connection_handler_impl.h index 7ca0088abe..ecf1946bf2 100644 --- a/source/server/connection_handler_impl.h +++ b/source/server/connection_handler_impl.h @@ -12,6 +12,7 @@ #include "envoy/network/filter.h" #include "envoy/network/listen_socket.h" #include "envoy/network/listener.h" +#include "envoy/server/active_udp_listener_config.h" #include "envoy/server/listener_manager.h" #include "envoy/stats/scope.h" #include "envoy/stats/timespan.h" @@ -22,6 +23,12 @@ #include "spdlog/spdlog.h" namespace Envoy { + +namespace Quic { +class ActiveQuicListener; +class EnvoyQuicDispatcher; +} // namespace Quic + namespace Server { #define ALL_LISTENER_STATS(COUNTER, GAUGE, HISTOGRAM) \ @@ -50,6 +57,8 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, NonCopyable { // Network::ConnectionHandler uint64_t numConnections() override { return num_connections_; } + void incNumConnections() override; + void decNumConnections() override; void addListener(Network::ListenerConfig& config) override; void removeListeners(uint64_t listener_tag) override; void stopListeners(uint64_t listener_tag) override; @@ -59,33 +68,21 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, NonCopyable { Network::Listener* findListenerByAddress(const Network::Address::Instance& address) override; -private: - struct ActiveListenerBase; - using ActiveListenerBasePtr = std::unique_ptr; - - struct ActiveTcpListener; - using ActiveTcpListenerPtr = std::unique_ptr; - - struct ActiveUdpListener; - using ActiveUdpListenerPtr = std::unique_ptr; - - ActiveListenerBase* findActiveListenerByAddress(const Network::Address::Instance& address); - - struct ActiveConnection; - using ActiveConnectionPtr = std::unique_ptr; - struct ActiveSocket; - using ActiveSocketPtr = std::unique_ptr; + Network::ConnectionHandler::ActiveListener* + findActiveListenerByAddress(const Network::Address::Instance& address); /** * Wrapper for an active listener owned by this handler. */ - struct ActiveListenerBase { - ActiveListenerBase(ConnectionHandlerImpl& parent, Network::ListenerPtr&& listener, - Network::ListenerConfig& config); + class ActiveListenerImplBase : public Network::ConnectionHandler::ActiveListener { + public: + ActiveListenerImplBase(Network::ListenerPtr&& listener, Network::ListenerConfig& config); - virtual ~ActiveListenerBase() = default; + // Network::ConnectionHandler::ActiveListener. + uint64_t listenerTag() override { return listener_tag_; } + Network::Listener* listener() override { return listener_.get(); } + void destroy() override { listener_.reset(); } - ConnectionHandlerImpl& parent_; Network::ListenerPtr listener_; ListenerStats stats_; const std::chrono::milliseconds listener_filters_timeout_; @@ -94,38 +91,24 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, NonCopyable { Network::ListenerConfig& config_; }; - /** - * Wrapper for an active udp listener owned by this handler. - */ - struct ActiveUdpListener : public Network::UdpListenerCallbacks, - public ActiveListenerBase, - public Network::UdpListenerFilterManager, - public Network::UdpReadFilterCallbacks { - ActiveUdpListener(ConnectionHandlerImpl& parent, Network::ListenerConfig& config); - - ActiveUdpListener(ConnectionHandlerImpl& parent, Network::ListenerPtr&& listener, - Network::ListenerConfig& config); - - // Network::UdpListenerCallbacks - void onData(Network::UdpRecvData& data) override; - void onWriteReady(const Network::Socket& socket) override; - void onReceiveError(const Network::UdpListenerCallbacks::ErrorCode& error_code, - Api::IoError::IoErrorCode err) override; - - // Network::UdpListenerFilterManager - void addReadFilter(Network::UdpListenerReadFilterPtr&& filter) override; - - // Network::UdpReadFilterCallbacks - Network::UdpListener& udpListener() override; +private: + class ActiveUdpListener; + using ActiveUdpListenerPtr = std::unique_ptr; + class ActiveTcpListener; + using ActiveTcpListenerPtr = std::unique_ptr; + struct ActiveConnection; + using ActiveConnectionPtr = std::unique_ptr; + struct ActiveSocket; + using ActiveSocketPtr = std::unique_ptr; - Network::UdpListener* udp_listener_; - Network::UdpListenerReadFilterPtr read_filter_; - }; + friend class Quic::ActiveQuicListener; + friend class Quic::EnvoyQuicDispatcher; /** * Wrapper for an active tcp listener owned by this handler. */ - struct ActiveTcpListener : public Network::ListenerCallbacks, public ActiveListenerBase { + class ActiveTcpListener : public Network::ListenerCallbacks, public ActiveListenerImplBase { + public: ActiveTcpListener(ConnectionHandlerImpl& parent, Network::ListenerConfig& config); ActiveTcpListener(ConnectionHandlerImpl& parent, Network::ListenerPtr&& listener, @@ -149,6 +132,7 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, NonCopyable { */ void newConnection(Network::ConnectionSocketPtr&& socket); + ConnectionHandlerImpl& parent_; std::list sockets_; std::list connections_; }; @@ -225,10 +209,42 @@ class ConnectionHandlerImpl : public Network::ConnectionHandler, NonCopyable { spdlog::logger& logger_; Event::Dispatcher& dispatcher_; - std::list> listeners_; + std::list> + listeners_; std::atomic num_connections_{}; bool disable_listeners_; }; +/** + * Wrapper for an active udp listener owned by this handler. + * TODO(danzh): rename to ActiveRawUdpListener. + */ +class ActiveUdpListener : public Network::UdpListenerCallbacks, + public ConnectionHandlerImpl::ActiveListenerImplBase, + public Network::UdpListenerFilterManager, + public Network::UdpReadFilterCallbacks { +public: + ActiveUdpListener(Event::Dispatcher& dispatcher, Network::ListenerConfig& config); + + ActiveUdpListener(Network::ListenerPtr&& listener, Network::ListenerConfig& config); + + // Network::UdpListenerCallbacks + void onData(Network::UdpRecvData& data) override; + void onWriteReady(const Network::Socket& socket) override; + void onReceiveError(const Network::UdpListenerCallbacks::ErrorCode& error_code, + Api::IoError::IoErrorCode err) override; + + // Network::UdpListenerFilterManager + void addReadFilter(Network::UdpListenerReadFilterPtr&& filter) override; + + // Network::UdpReadFilterCallbacks + Network::UdpListener& udpListener() override; + +private: + Network::UdpListener* udp_listener_; + Network::UdpListenerReadFilterPtr read_filter_; +}; + } // namespace Server } // namespace Envoy diff --git a/source/server/guarddog_impl.cc b/source/server/guarddog_impl.cc index a9e3e583e3..5b0b801634 100644 --- a/source/server/guarddog_impl.cc +++ b/source/server/guarddog_impl.cc @@ -8,6 +8,7 @@ #include "common/common/assert.h" #include "common/common/fmt.h" #include "common/common/lock_guard.h" +#include "common/stats/symbol_table_impl.h" #include "server/watchdog_impl.h" @@ -30,8 +31,12 @@ GuardDogImpl::GuardDogImpl(Stats::Scope& stats_scope, const Server::Configuratio multikillEnabled() ? multi_kill_timeout_ : min_of_nonfatal, min_of_nonfatal}); }()), - watchdog_miss_counter_(stats_scope.counter("server.watchdog_miss")), - watchdog_megamiss_counter_(stats_scope.counter("server.watchdog_mega_miss")), + watchdog_miss_counter_(stats_scope.counterFromStatName( + Stats::StatNameManagedStorage("server.watchdog_miss", stats_scope.symbolTable()) + .statName())), + watchdog_megamiss_counter_(stats_scope.counterFromStatName( + Stats::StatNameManagedStorage("server.watchdog_mega_miss", stats_scope.symbolTable()) + .statName())), dispatcher_(api.allocateDispatcher()), loop_timer_(dispatcher_->createTimer([this]() { step(); })), run_thread_(true) { start(api); diff --git a/source/server/guarddog_impl.h b/source/server/guarddog_impl.h index 5c0906ea55..6d50a2e23f 100644 --- a/source/server/guarddog_impl.h +++ b/source/server/guarddog_impl.h @@ -115,13 +115,13 @@ class GuardDogImpl : public GuardDog { const std::chrono::milliseconds loop_interval_; Stats::Counter& watchdog_miss_counter_; Stats::Counter& watchdog_megamiss_counter_; - std::vector watched_dogs_ GUARDED_BY(wd_lock_); + std::vector watched_dogs_ ABSL_GUARDED_BY(wd_lock_); Thread::MutexBasicLockable wd_lock_; Thread::ThreadPtr thread_; Event::DispatcherPtr dispatcher_; Event::TimerPtr loop_timer_; Thread::MutexBasicLockable mutex_; - bool run_thread_ GUARDED_BY(mutex_); + bool run_thread_ ABSL_GUARDED_BY(mutex_); }; } // namespace Server diff --git a/source/server/http/admin.cc b/source/server/http/admin.cc index f2389c7390..174f3a3b7f 100644 --- a/source/server/http/admin.cc +++ b/source/server/http/admin.cc @@ -719,6 +719,7 @@ Http::Code AdminImpl::handlerServerInfo(absl::string_view, Http::HeaderMap& head time_t current_time = time(nullptr); envoy::admin::v2alpha::ServerInfo server_info; server_info.set_version(VersionInfo::version()); + server_info.set_hot_restart_version(server_.hotRestart().version()); server_info.set_state( Utility::serverState(server_.initManager().state(), server_.healthCheckFailed())); diff --git a/source/server/http/admin.h b/source/server/http/admin.h index e3806f58e1..f54d72fee7 100644 --- a/source/server/http/admin.h +++ b/source/server/http/admin.h @@ -121,6 +121,9 @@ class AdminImpl : public Admin, return &scoped_route_config_provider_; } const std::string& serverName() override { return Http::DefaultServerString::get(); } + HttpConnectionManagerProto::ServerHeaderTransformation serverHeaderTransformation() override { + return HttpConnectionManagerProto::OVERWRITE; + } Http::ConnectionManagerStats& stats() override { return stats_; } Http::ConnectionManagerTracingStats& tracingStats() override { return tracing_stats_; } bool useRemoteAddress() override { return true; } @@ -172,6 +175,7 @@ class AdminImpl : public Admin, absl::optional configInfo() const override { return {}; } SystemTime lastUpdated() const override { return time_source_.systemTime(); } void onConfigUpdate() override {} + void validateConfig(const envoy::api::v2::RouteConfiguration&) const override {} Router::ConfigConstSharedPtr config_; TimeSource& time_source_; @@ -319,6 +323,9 @@ class AdminImpl : public Admin, Stats::Scope& listenerScope() override { return *scope_; } uint64_t listenerTag() const override { return 0; } const std::string& name() const override { return name_; } + const Network::ActiveUdpListenerFactory* udpListenerFactory() override { + NOT_REACHED_GCOVR_EXCL_LINE; + } AdminImpl& parent_; const std::string name_; diff --git a/source/server/lds_api.cc b/source/server/lds_api.cc index 581abba24b..957603de82 100644 --- a/source/server/lds_api.cc +++ b/source/server/lds_api.cc @@ -49,9 +49,8 @@ void LdsApiImpl::onConfigUpdate( for (const auto& resource : added_resources) { envoy::api::v2::Listener listener; try { - listener = MessageUtil::anyConvert(resource.resource(), - validation_visitor_); - MessageUtil::validate(listener); + listener = MessageUtil::anyConvert(resource.resource()); + MessageUtil::validate(listener, validation_visitor_); if (!listener_names.insert(listener.name()).second) { // NOTE: at this point, the first of these duplicates has already been successfully applied. throw EnvoyException(fmt::format("duplicate listener {} found", listener.name())); @@ -91,8 +90,7 @@ void LdsApiImpl::onConfigUpdate(const Protobuf::RepeatedPtrField(listener_blob, validation_visitor_) - .name(); + MessageUtil::anyConvert(listener_blob).name(); to_add->set_name(listener_name); to_add->set_version(version_info); to_add->mutable_resource()->MergeFrom(listener_blob); diff --git a/source/server/lds_api.h b/source/server/lds_api.h index 199bd84132..24ca66cfe7 100644 --- a/source/server/lds_api.h +++ b/source/server/lds_api.h @@ -39,7 +39,7 @@ class LdsApiImpl : public LdsApi, void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason reason, const EnvoyException* e) override; std::string resourceName(const ProtobufWkt::Any& resource) override { - return MessageUtil::anyConvert(resource, validation_visitor_).name(); + return MessageUtil::anyConvert(resource).name(); } std::unique_ptr subscription_; diff --git a/source/server/listener_manager_impl.cc b/source/server/listener_manager_impl.cc index 0f2ec74ee6..2aa454b16d 100644 --- a/source/server/listener_manager_impl.cc +++ b/source/server/listener_manager_impl.cc @@ -4,6 +4,7 @@ #include "envoy/admin/v2alpha/config_dump.pb.h" #include "envoy/registry/registry.h" +#include "envoy/server/active_udp_listener_config.h" #include "envoy/server/transport_socket_config.h" #include "envoy/stats/scope.h" @@ -23,6 +24,7 @@ #include "server/drain_manager_impl.h" #include "server/filter_chain_manager_impl.h" #include "server/transport_socket_config_impl.h" +#include "server/well_known_names.h" #include "extensions/filters/listener/well_known_names.h" #include "extensions/transport_sockets/well_known_names.h" @@ -226,6 +228,15 @@ ListenerImpl::ListenerImpl(const envoy::api::v2::Listener& config, const std::st addListenSocketOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); // Needed to return receive buffer overflown indicator. addListenSocketOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + auto udp_config = config.udp_listener_config(); + if (udp_config.udp_listener_name().empty()) { + udp_config.set_udp_listener_name(UdpListenerNames::get().RawUdp); + } + auto& config_factory = Config::Utility::getAndCheckFactory( + udp_config.udp_listener_name()); + ProtobufTypes::MessagePtr message = + Config::Utility::translateToFactoryConfig(udp_config, validation_visitor_, config_factory); + udp_listener_factory_ = config_factory.createActiveUdpListenerFactory(*message); } if (!config.listener_filters().empty()) { diff --git a/source/server/listener_manager_impl.h b/source/server/listener_manager_impl.h index 1197ac82a9..35bd7ddfee 100644 --- a/source/server/listener_manager_impl.h +++ b/source/server/listener_manager_impl.h @@ -279,6 +279,9 @@ class ListenerImpl : public Network::ListenerConfig, Stats::Scope& listenerScope() override { return *listener_scope_; } uint64_t listenerTag() const override { return listener_tag_; } const std::string& name() const override { return name_; } + const Network::ActiveUdpListenerFactory* udpListenerFactory() override { + return udp_listener_factory_.get(); + } // Server::Configuration::ListenerFactoryContext AccessLog::AccessLogManager& accessLogManager() override { @@ -321,7 +324,9 @@ class ListenerImpl : public Network::ListenerConfig, ServerLifecycleNotifier& lifecycleNotifier() override { return parent_.server_.lifecycleNotifier(); } - ProcessContext& processContext() override { return parent_.server_.processContext(); } + absl::optional> processContext() override { + return parent_.server_.processContext(); + } // Network::DrainDecision bool drainClose() const override; @@ -379,6 +384,7 @@ class ListenerImpl : public Network::ListenerConfig, Network::Socket::OptionsSharedPtr listen_socket_options_; const std::chrono::milliseconds listener_filters_timeout_; const bool continue_on_listener_filters_timeout_; + Network::ActiveUdpListenerFactoryPtr udp_listener_factory_; // to access ListenerManagerImpl::factory_. friend class ListenerFilterChainFactoryBuilder; }; diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index c3fd287341..2beca59d6f 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -112,7 +112,9 @@ OptionsImpl::OptionsImpl(int argc, const char* const* argv, TCLAP::ValueArg use_libevent_buffer("", "use-libevent-buffers", "Use the original libevent buffer implementation", false, false, "bool", cmd); - + TCLAP::ValueArg use_fake_symbol_table("", "use-fake-symbol-table", + "Use fake symbol table implementation", false, true, + "bool", cmd); cmd.setExceptionHandling(false); try { cmd.parse(argc, argv); @@ -136,6 +138,7 @@ OptionsImpl::OptionsImpl(int argc, const char* const* argv, mutex_tracing_enabled_ = enable_mutex_tracing.getValue(); libevent_buffer_enabled_ = use_libevent_buffer.getValue(); + fake_symbol_table_enabled_ = use_fake_symbol_table.getValue(); cpuset_threads_ = cpuset_threads.getValue(); log_level_ = default_log_level; @@ -300,6 +303,7 @@ OptionsImpl::OptionsImpl(const std::string& service_cluster, const std::string& service_cluster_(service_cluster), service_node_(service_node), service_zone_(service_zone), file_flush_interval_msec_(10000), drain_time_(600), parent_shutdown_time_(900), mode_(Server::Mode::Serve), hot_restart_disabled_(false), signal_handling_enabled_(true), - mutex_tracing_enabled_(false), cpuset_threads_(false), libevent_buffer_enabled_(false) {} + mutex_tracing_enabled_(false), cpuset_threads_(false), libevent_buffer_enabled_(false), + fake_symbol_table_enabled_(false) {} } // namespace Envoy diff --git a/source/server/options_impl.h b/source/server/options_impl.h index 7fea3a2546..a06365c64e 100644 --- a/source/server/options_impl.h +++ b/source/server/options_impl.h @@ -81,6 +81,9 @@ class OptionsImpl : public Server::Options, protected Logger::Loggable value_; }; -std::string StatsName(const std::string& a, const std::string& b) { - return absl::StrCat("overload.", a, ".", b); +Stats::Counter& makeCounter(Stats::Scope& scope, absl::string_view a, absl::string_view b) { + Stats::StatNameManagedStorage stat_name(absl::StrCat("overload.", a, ".", b), + scope.symbolTable()); + return scope.counterFromStatName(stat_name.statName()); +} + +Stats::Gauge& makeGauge(Stats::Scope& scope, absl::string_view a, absl::string_view b, + Stats::Gauge::ImportMode import_mode) { + Stats::StatNameManagedStorage stat_name(absl::StrCat("overload.", a, ".", b), + scope.symbolTable()); + return scope.gaugeFromStatName(stat_name.statName(), import_mode); } } // namespace OverloadAction::OverloadAction(const envoy::config::overload::v2alpha::OverloadAction& config, Stats::Scope& stats_scope) - : active_gauge_(stats_scope.gauge(StatsName(config.name(), "active"), - Stats::Gauge::ImportMode::Accumulate)) { + : active_gauge_( + makeGauge(stats_scope, config.name(), "active", Stats::Gauge::ImportMode::Accumulate)) { for (const auto& trigger_config : config.triggers()) { TriggerPtr trigger; @@ -93,7 +103,7 @@ OverloadManagerImpl::OverloadManagerImpl( : started_(false), dispatcher_(dispatcher), tls_(slot_allocator.allocateSlot()), refresh_interval_( std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(config, refresh_interval, 1000))) { - Configuration::ResourceMonitorFactoryContextImpl context(dispatcher, api); + Configuration::ResourceMonitorFactoryContextImpl context(dispatcher, api, validation_visitor); for (const auto& resource : config.resource_monitors()) { const auto& name = resource.name(); ENVOY_LOG(debug, "Adding resource monitor for {}", name); @@ -213,9 +223,9 @@ OverloadManagerImpl::Resource::Resource(const std::string& name, ResourceMonitor OverloadManagerImpl& manager, Stats::Scope& stats_scope) : name_(name), monitor_(std::move(monitor)), manager_(manager), pending_update_(false), pressure_gauge_( - stats_scope.gauge(StatsName(name, "pressure"), Stats::Gauge::ImportMode::NeverImport)), - failed_updates_counter_(stats_scope.counter(StatsName(name, "failed_updates"))), - skipped_updates_counter_(stats_scope.counter(StatsName(name, "skipped_updates"))) {} + makeGauge(stats_scope, name, "pressure", Stats::Gauge::ImportMode::NeverImport)), + failed_updates_counter_(makeCounter(stats_scope, name, "failed_updates")), + skipped_updates_counter_(makeCounter(stats_scope, name, "skipped_updates")) {} void OverloadManagerImpl::Resource::update() { if (!pending_update_) { diff --git a/source/server/resource_monitor_config_impl.h b/source/server/resource_monitor_config_impl.h index bab2f6c3a6..e565f6fd78 100644 --- a/source/server/resource_monitor_config_impl.h +++ b/source/server/resource_monitor_config_impl.h @@ -8,16 +8,22 @@ namespace Configuration { class ResourceMonitorFactoryContextImpl : public ResourceMonitorFactoryContext { public: - ResourceMonitorFactoryContextImpl(Event::Dispatcher& dispatcher, Api::Api& api) - : dispatcher_(dispatcher), api_(api) {} + ResourceMonitorFactoryContextImpl(Event::Dispatcher& dispatcher, Api::Api& api, + ProtobufMessage::ValidationVisitor& validation_visitor) + : dispatcher_(dispatcher), api_(api), validation_visitor_(validation_visitor) {} Event::Dispatcher& dispatcher() override { return dispatcher_; } Api::Api& api() override { return api_; } + ProtobufMessage::ValidationVisitor& messageValidationVisitor() override { + return validation_visitor_; + } + private: Event::Dispatcher& dispatcher_; Api::Api& api_; + ProtobufMessage::ValidationVisitor& validation_visitor_; }; } // namespace Configuration diff --git a/source/server/server.cc b/source/server/server.cc index 0c41302bb6..9a742671b6 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -233,7 +233,7 @@ InstanceUtil::BootstrapVersion InstanceUtil::loadBootstrapConfig( if (config_proto.ByteSize() != 0) { bootstrap.MergeFrom(config_proto); } - MessageUtil::validate(bootstrap); + MessageUtil::validate(bootstrap, validation_visitor); return BootstrapVersion::V2; } @@ -492,10 +492,10 @@ void InstanceImpl::loadServerFlags(const absl::optional& flags_path RunHelper::RunHelper(Instance& instance, const Options& options, Event::Dispatcher& dispatcher, Upstream::ClusterManager& cm, AccessLog::AccessLogManager& access_log_manager, Init::Manager& init_manager, OverloadManager& overload_manager, - std::function workers_start_cb) - : init_watcher_("RunHelper", [&instance, workers_start_cb]() { + std::function post_init_cb) + : init_watcher_("RunHelper", [&instance, post_init_cb]() { if (!instance.isShutdown()) { - workers_start_cb(); + post_init_cb(); } }) { // Setup signals. @@ -550,9 +550,11 @@ RunHelper::RunHelper(Instance& instance, const Options& options, Event::Dispatch void InstanceImpl::run() { // RunHelper exists primarily to facilitate testing of how we respond to early shutdown during // startup (see RunHelperTest in server_test.cc). - const auto run_helper = - RunHelper(*this, options_, *dispatcher_, clusterManager(), access_log_manager_, init_manager_, - overloadManager(), [this] { startWorkers(); }); + const auto run_helper = RunHelper(*this, options_, *dispatcher_, clusterManager(), + access_log_manager_, init_manager_, overloadManager(), [this] { + notifyCallbacksForStage(Stage::PostInit); + startWorkers(); + }); // Run the main dispatch loop waiting to exit. ENVOY_LOG(info, "starting main dispatch loop"); diff --git a/source/server/server.h b/source/server/server.h index 29d63c8db5..cb325dd752 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -195,7 +195,9 @@ class InstanceImpl : Logger::Loggable, Stats::Store& stats() override { return stats_store_; } Grpc::Context& grpcContext() override { return grpc_context_; } Http::Context& httpContext() override { return http_context_; } - ProcessContext& processContext() override { return *process_context_; } + absl::optional> processContext() override { + return *process_context_; + } ThreadLocal::Instance& threadLocal() override { return thread_local_; } const LocalInfo::LocalInfo& localInfo() override { return *local_info_; } TimeSource& timeSource() override { return time_source_; } diff --git a/source/server/ssl_context_manager.cc b/source/server/ssl_context_manager.cc index 3e422643ed..4573cdf6de 100644 --- a/source/server/ssl_context_manager.cc +++ b/source/server/ssl_context_manager.cc @@ -29,6 +29,8 @@ class SslContextManagerNoTlsStub final : public Envoy::Ssl::ContextManager { void iterateContexts(std::function /* callback */) override{}; + Ssl::PrivateKeyMethodManager& privateKeyMethodManager() override { throwException(); } + private: [[noreturn]] void throwException() { throw EnvoyException("SSL is not supported in this configuration"); diff --git a/source/server/well_known_names.h b/source/server/well_known_names.h new file mode 100644 index 0000000000..23d202d996 --- /dev/null +++ b/source/server/well_known_names.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include "common/singleton/const_singleton.h" + +namespace Envoy { +namespace Server { + +/** + * Well-known active UDP listener names. + */ +class UdpListenerNameValues { +public: + const std::string RawUdp = "raw_udp_listener"; +}; + +using UdpListenerNames = ConstSingleton; + +} // namespace Server +} // namespace Envoy diff --git a/test/common/access_log/access_log_formatter_fuzz_test.cc b/test/common/access_log/access_log_formatter_fuzz_test.cc index 09d6b43225..1df65dd173 100644 --- a/test/common/access_log/access_log_formatter_fuzz_test.cc +++ b/test/common/access_log/access_log_formatter_fuzz_test.cc @@ -10,14 +10,12 @@ namespace { DEFINE_PROTO_FUZZER(const test::common::access_log::TestCase& input) { try { - NiceMock connection_info; std::vector formatters = AccessLog::AccessLogFormatParser::parse(input.format()); for (const auto& it : formatters) { - it->format(Fuzz::fromHeaders(input.request_headers()), - Fuzz::fromHeaders(input.response_headers()), - Fuzz::fromHeaders(input.response_trailers()), - Fuzz::fromStreamInfo(input.stream_info(), &connection_info)); + it->format( + Fuzz::fromHeaders(input.request_headers()), Fuzz::fromHeaders(input.response_headers()), + Fuzz::fromHeaders(input.response_trailers()), Fuzz::fromStreamInfo(input.stream_info())); } ENVOY_LOG_MISC(trace, "Success"); } catch (const EnvoyException& e) { diff --git a/test/common/access_log/access_log_formatter_test.cc b/test/common/access_log/access_log_formatter_test.cc index d10665a034..e05f0911a1 100644 --- a/test/common/access_log/access_log_formatter_test.cc +++ b/test/common/access_log/access_log_formatter_test.cc @@ -17,7 +17,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::Const; using testing::NiceMock; using testing::Return; @@ -220,26 +219,27 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_URI_SAN"); - NiceMock connection_info; + auto connection_info = std::make_shared(); const std::vector sans{"san"}; - ON_CALL(connection_info, uriSanPeerCertificate()).WillByDefault(Return(sans)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, uriSanPeerCertificate()).WillRepeatedly(Return(sans)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("san", upstream_format.format(header, header, header, stream_info)); } + { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_URI_SAN"); - NiceMock connection_info; + auto connection_info = std::make_shared(); const std::vector sans{"san1", "san2"}; - ON_CALL(connection_info, uriSanPeerCertificate()).WillByDefault(Return(sans)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, uriSanPeerCertificate()).WillRepeatedly(Return(sans)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("san1,san2", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_URI_SAN"); - NiceMock connection_info; - ON_CALL(connection_info, uriSanPeerCertificate()) - .WillByDefault(Return(std::vector())); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, uriSanPeerCertificate()) + .WillRepeatedly(Return(std::vector())); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -249,26 +249,26 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_LOCAL_URI_SAN"); - NiceMock connection_info; + auto connection_info = std::make_shared(); const std::vector sans{"san"}; - ON_CALL(connection_info, uriSanLocalCertificate()).WillByDefault(Return(sans)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, uriSanLocalCertificate()).WillRepeatedly(Return(sans)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("san", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_LOCAL_URI_SAN"); - NiceMock connection_info; + auto connection_info = std::make_shared(); const std::vector sans{"san1", "san2"}; - ON_CALL(connection_info, uriSanLocalCertificate()).WillByDefault(Return(sans)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, uriSanLocalCertificate()).WillRepeatedly(Return(sans)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("san1,san2", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_LOCAL_URI_SAN"); - NiceMock connection_info; - ON_CALL(connection_info, uriSanLocalCertificate()) - .WillByDefault(Return(std::vector())); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, uriSanLocalCertificate()) + .WillRepeatedly(Return(std::vector())); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -278,16 +278,19 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_LOCAL_SUBJECT"); - NiceMock connection_info; - ON_CALL(connection_info, subjectLocalCertificate()).WillByDefault(Return("subject")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + const std::string subject_local = "subject"; + EXPECT_CALL(*connection_info, subjectLocalCertificate()) + .WillRepeatedly(ReturnRef(subject_local)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("subject", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_LOCAL_SUBJECT"); - NiceMock connection_info; - ON_CALL(connection_info, subjectLocalCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, subjectLocalCertificate()) + .WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -297,16 +300,17 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_SUBJECT"); - NiceMock connection_info; - ON_CALL(connection_info, subjectPeerCertificate()).WillByDefault(Return("subject")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + const std::string subject_peer = "subject"; + EXPECT_CALL(*connection_info, subjectPeerCertificate()).WillRepeatedly(ReturnRef(subject_peer)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("subject", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_SUBJECT"); - NiceMock connection_info; - ON_CALL(connection_info, subjectPeerCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, subjectPeerCertificate()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -316,16 +320,17 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_SESSION_ID"); - NiceMock connection_info; - ON_CALL(connection_info, sessionId()).WillByDefault(Return("deadbeef")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + const std::string session_id = "deadbeef"; + EXPECT_CALL(*connection_info, sessionId()).WillRepeatedly(ReturnRef(session_id)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("deadbeef", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_SESSION_ID"); - NiceMock connection_info; - ON_CALL(connection_info, sessionId()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, sessionId()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -335,18 +340,18 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_CIPHER"); - NiceMock connection_info; - ON_CALL(connection_info, ciphersuiteString()) - .WillByDefault(Return("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, ciphersuiteString()) + .WillRepeatedly(Return("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384")); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_CIPHER"); - NiceMock connection_info; - ON_CALL(connection_info, ciphersuiteString()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, ciphersuiteString()).WillRepeatedly(Return("")); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -356,16 +361,17 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_VERSION"); - NiceMock connection_info; - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("TLSv1.2")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + std::string tlsVersion = "TLSv1.2"; + EXPECT_CALL(*connection_info, tlsVersion()).WillRepeatedly(ReturnRef(tlsVersion)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("TLSv1.2", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_TLS_VERSION"); - NiceMock connection_info; - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, tlsVersion()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -375,18 +381,20 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_FINGERPRINT_256"); - NiceMock connection_info; + auto connection_info = std::make_shared(); std::string expected_sha = "685a2db593d5f86d346cb1a297009c3b467ad77f1944aa799039a2fb3d531f3f"; - ON_CALL(connection_info, sha256PeerCertificateDigest()).WillByDefault(ReturnRef(expected_sha)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, sha256PeerCertificateDigest()) + .WillRepeatedly(ReturnRef(expected_sha)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ(expected_sha, upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_FINGERPRINT_256"); - NiceMock connection_info; + auto connection_info = std::make_shared(); std::string expected_sha; - ON_CALL(connection_info, sha256PeerCertificateDigest()).WillByDefault(ReturnRef(expected_sha)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, sha256PeerCertificateDigest()) + .WillRepeatedly(ReturnRef(expected_sha)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -396,17 +404,19 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_SERIAL"); - NiceMock connection_info; - ON_CALL(connection_info, serialNumberPeerCertificate()) - .WillByDefault(Return("b8b5ecc898f2124a")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + const std::string serial_number = "b8b5ecc898f2124a"; + EXPECT_CALL(*connection_info, serialNumberPeerCertificate()) + .WillRepeatedly(ReturnRef(serial_number)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("b8b5ecc898f2124a", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_SERIAL"); - NiceMock connection_info; - ON_CALL(connection_info, serialNumberPeerCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, serialNumberPeerCertificate()) + .WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -416,19 +426,19 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_ISSUER"); - NiceMock connection_info; - ON_CALL(connection_info, issuerPeerCertificate()) - .WillByDefault( - Return("CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + const std::string issuer_peer = + "CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US"; + EXPECT_CALL(*connection_info, issuerPeerCertificate()).WillRepeatedly(ReturnRef(issuer_peer)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_ISSUER"); - NiceMock connection_info; - ON_CALL(connection_info, issuerPeerCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, issuerPeerCertificate()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -438,19 +448,19 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_SUBJECT"); - NiceMock connection_info; - ON_CALL(connection_info, subjectPeerCertificate()) - .WillByDefault( - Return("CN=Test Server,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + const std::string subject_peer = + "CN=Test Server,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US"; + EXPECT_CALL(*connection_info, subjectPeerCertificate()).WillRepeatedly(ReturnRef(subject_peer)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("CN=Test Server,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_SUBJECT"); - NiceMock connection_info; - ON_CALL(connection_info, subjectPeerCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, subjectPeerCertificate()).WillRepeatedly(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -460,20 +470,20 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT"); - NiceMock connection_info; + auto connection_info = std::make_shared(); std::string expected_cert = ""; - ON_CALL(connection_info, urlEncodedPemEncodedPeerCertificate()) - .WillByDefault(ReturnRef(expected_cert)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, urlEncodedPemEncodedPeerCertificate()) + .WillRepeatedly(ReturnRef(expected_cert)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ(expected_cert, upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT"); - NiceMock connection_info; + auto connection_info = std::make_shared(); std::string expected_cert = ""; - ON_CALL(connection_info, urlEncodedPemEncodedPeerCertificate()) - .WillByDefault(ReturnRef(expected_cert)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, urlEncodedPemEncodedPeerCertificate()) + .WillRepeatedly(ReturnRef(expected_cert)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -483,20 +493,20 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_START"); - NiceMock connection_info; + auto connection_info = std::make_shared(); absl::Time abslStartTime = TestUtility::parseTime("Dec 18 01:50:34 2018 GMT", "%b %e %H:%M:%S %Y GMT"); SystemTime startTime = absl::ToChronoTime(abslStartTime); - ON_CALL(connection_info, validFromPeerCertificate()).WillByDefault(Return(startTime)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(startTime)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("2018-12-18T01:50:34.000Z", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_START"); - NiceMock connection_info; - ON_CALL(connection_info, validFromPeerCertificate()).WillByDefault(Return(absl::nullopt)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, validFromPeerCertificate()).WillRepeatedly(Return(absl::nullopt)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { @@ -506,20 +516,21 @@ TEST(AccessLogFormatterTest, streamInfoFormatter) { } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_END"); - NiceMock connection_info; + auto connection_info = std::make_shared(); absl::Time abslEndTime = TestUtility::parseTime("Dec 17 01:50:34 2020 GMT", "%b %e %H:%M:%S %Y GMT"); SystemTime endTime = absl::ToChronoTime(abslEndTime); - ON_CALL(connection_info, expirationPeerCertificate()).WillByDefault(Return(endTime)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(*connection_info, expirationPeerCertificate()).WillRepeatedly(Return(endTime)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("2020-12-17T01:50:34.000Z", upstream_format.format(header, header, header, stream_info)); } { StreamInfoFormatter upstream_format("DOWNSTREAM_PEER_CERT_V_END"); - NiceMock connection_info; - ON_CALL(connection_info, expirationPeerCertificate()).WillByDefault(Return(absl::nullopt)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared(); + EXPECT_CALL(*connection_info, expirationPeerCertificate()) + .WillRepeatedly(Return(absl::nullopt)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); EXPECT_EQ("-", upstream_format.format(header, header, header, stream_info)); } { diff --git a/test/common/access_log/access_log_impl_test.cc b/test/common/access_log/access_log_impl_test.cc index fe29b78394..02677fc783 100644 --- a/test/common/access_log/access_log_impl_test.cc +++ b/test/common/access_log/access_log_impl_test.cc @@ -1143,7 +1143,7 @@ class TestHeaderFilterFactory : public ExtensionFilterFactory { auto factory_config = Config::Utility::translateToFactoryConfig( config, Envoy::ProtobufMessage::getNullValidationVisitor(), *this); const auto& header_config = - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( *factory_config); return std::make_unique(header_config); } diff --git a/test/common/access_log/access_log_manager_impl_test.cc b/test/common/access_log/access_log_manager_impl_test.cc index 151ac7544b..3b28ee6526 100644 --- a/test/common/access_log/access_log_manager_impl_test.cc +++ b/test/common/access_log/access_log_manager_impl_test.cc @@ -8,12 +8,15 @@ #include "test/mocks/api/mocks.h" #include "test/mocks/event/mocks.h" #include "test/mocks/filesystem/mocks.h" +#include "test/test_common/test_time.h" +#include "test/test_common/utility.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; using testing::ByMove; +using testing::Invoke; using testing::NiceMock; using testing::Return; using testing::ReturnNew; @@ -36,6 +39,14 @@ class AccessLogManagerImplTest : public testing::Test { EXPECT_CALL(api_, threadFactory()).WillRepeatedly(ReturnRef(thread_factory_)); } + void waitForCounterEq(const std::string& name, uint64_t value) { + TestUtility::waitForCounterEq(store_, name, value, time_system_); + } + + void waitForGaugeEq(const std::string& name, uint64_t value) { + TestUtility::waitForGaugeEq(store_, name, value, time_system_); + } + NiceMock api_; NiceMock file_system_; NiceMock* file_; @@ -45,23 +56,47 @@ class AccessLogManagerImplTest : public testing::Test { NiceMock dispatcher_; Thread::MutexBasicLockable lock_; AccessLogManagerImpl access_log_manager_; + Event::TestRealTimeSystem time_system_; }; TEST_F(AccessLogManagerImplTest, BadFile) { EXPECT_CALL(dispatcher_, createTimer_(_)); - EXPECT_CALL(*file_, open_()).WillOnce(Return(ByMove(Filesystem::resultFailure(false, 0)))); + EXPECT_CALL(*file_, open_(_)).WillOnce(Return(ByMove(Filesystem::resultFailure(false, 0)))); EXPECT_THROW(access_log_manager_.createAccessLog("foo"), EnvoyException); } +TEST_F(AccessLogManagerImplTest, OpenFileWithRightFlags) { + EXPECT_CALL(dispatcher_, createTimer_(_)); + EXPECT_CALL(*file_, open_(_)) + .WillOnce(Invoke([](Filesystem::FlagSet flags) -> Api::IoCallBoolResult { + EXPECT_FALSE(flags[Filesystem::File::Operation::Read]); + EXPECT_TRUE(flags[Filesystem::File::Operation::Write]); + EXPECT_TRUE(flags[Filesystem::File::Operation::Append]); + EXPECT_TRUE(flags[Filesystem::File::Operation::Create]); + return Filesystem::resultSuccess(true); + })); + EXPECT_NE(nullptr, access_log_manager_.createAccessLog("foo")); + EXPECT_CALL(*file_, close_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); +} + TEST_F(AccessLogManagerImplTest, flushToLogFilePeriodically) { NiceMock* timer = new NiceMock(&dispatcher_); - EXPECT_CALL(*file_, open_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); + EXPECT_CALL(*file_, open_(_)).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); AccessLogFileSharedPtr log_file = access_log_manager_.createAccessLog("foo"); - EXPECT_CALL(*timer, enableTimer(timeout_40ms_)); + EXPECT_EQ(0UL, store_.counter("filesystem.write_failed").value()); + EXPECT_EQ(0UL, store_.counter("filesystem.write_completed").value()); + EXPECT_EQ(0UL, store_.counter("filesystem.flushed_by_timer").value()); + EXPECT_EQ(0UL, store_.counter("filesystem.write_buffered").value()); + + EXPECT_CALL(*timer, enableTimer(timeout_40ms_, _)); EXPECT_CALL(*file_, write_(_)) - .WillOnce(Invoke([](absl::string_view data) -> Api::IoCallSizeResult { + .WillOnce(Invoke([&](absl::string_view data) -> Api::IoCallSizeResult { + EXPECT_EQ( + 4UL, + store_.gauge("filesystem.write_total_buffered", Stats::Gauge::ImportMode::Accumulate) + .value()); EXPECT_EQ(0, data.compare("test")); return Filesystem::resultSuccess(static_cast(data.length())); })); @@ -75,15 +110,26 @@ TEST_F(AccessLogManagerImplTest, flushToLogFilePeriodically) { } } + waitForCounterEq("filesystem.write_completed", 1); + EXPECT_EQ(1UL, store_.counter("filesystem.write_buffered").value()); + EXPECT_EQ(0UL, store_.counter("filesystem.flushed_by_timer").value()); + waitForGaugeEq("filesystem.write_total_buffered", 0); + EXPECT_CALL(*file_, write_(_)) - .WillOnce(Invoke([](absl::string_view data) -> Api::IoCallSizeResult { + .WillOnce(Invoke([&](absl::string_view data) -> Api::IoCallSizeResult { + EXPECT_EQ( + 5UL, + store_.gauge("filesystem.write_total_buffered", Stats::Gauge::ImportMode::Accumulate) + .value()); EXPECT_EQ(0, data.compare("test2")); return Filesystem::resultSuccess(static_cast(data.length())); })); - // make sure timer is re-enabled on callback call log_file->write("test2"); - EXPECT_CALL(*timer, enableTimer(timeout_40ms_)); + EXPECT_EQ(2UL, store_.counter("filesystem.write_buffered").value()); + + // make sure timer is re-enabled on callback call + EXPECT_CALL(*timer, enableTimer(timeout_40ms_, _)); timer->invokeCallback(); { @@ -92,30 +138,40 @@ TEST_F(AccessLogManagerImplTest, flushToLogFilePeriodically) { file_->write_event_.wait(file_->write_mutex_); } } + + waitForCounterEq("filesystem.write_completed", 2); + EXPECT_EQ(0UL, store_.counter("filesystem.write_failed").value()); + EXPECT_EQ(1UL, store_.counter("filesystem.flushed_by_timer").value()); + EXPECT_EQ(2UL, store_.counter("filesystem.write_buffered").value()); + waitForGaugeEq("filesystem.write_total_buffered", 0); + EXPECT_CALL(*file_, close_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); } TEST_F(AccessLogManagerImplTest, flushToLogFileOnDemand) { NiceMock* timer = new NiceMock(&dispatcher_); - EXPECT_CALL(*file_, open_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); + EXPECT_CALL(*file_, open_(_)).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); AccessLogFileSharedPtr log_file = access_log_manager_.createAccessLog("foo"); - EXPECT_CALL(*timer, enableTimer(timeout_40ms_)); + EXPECT_EQ(0UL, store_.counter("filesystem.flushed_by_timer").value()); - // The first write to a given file will start the flush thread, which can flush - // immediately (race on whether it will or not). So do a write and flush to - // get that state out of the way, then test that small writes don't trigger a flush. + EXPECT_CALL(*timer, enableTimer(timeout_40ms_, _)); + + // The first write to a given file will start the flush thread. Because AccessManagerImpl::write + // holds the write_lock_ when the thread is started, the thread will flush on its first loop, once + // it obtains the write_lock_. Perform a write to get all that out of the way. EXPECT_CALL(*file_, write_(_)) .WillOnce(Invoke([](absl::string_view data) -> Api::IoCallSizeResult { return Filesystem::resultSuccess(static_cast(data.length())); })); log_file->write("prime-it"); - log_file->flush(); uint32_t expected_writes = 1; { Thread::LockGuard lock(file_->write_mutex_); - EXPECT_EQ(expected_writes, file_->num_writes_); + while (file_->num_writes_ != expected_writes) { + file_->write_event_.wait(file_->write_mutex_); + } } EXPECT_CALL(*file_, write_(_)) @@ -135,9 +191,14 @@ TEST_F(AccessLogManagerImplTest, flushToLogFileOnDemand) { expected_writes++; { Thread::LockGuard lock(file_->write_mutex_); - EXPECT_EQ(expected_writes, file_->num_writes_); + while (file_->num_writes_ != expected_writes) { + file_->write_event_.wait(file_->write_mutex_); + } } + waitForCounterEq("filesystem.write_completed", 2); + EXPECT_EQ(0UL, store_.counter("filesystem.flushed_by_timer").value()); + EXPECT_CALL(*file_, write_(_)) .WillOnce(Invoke([](absl::string_view data) -> Api::IoCallSizeResult { EXPECT_EQ(0, data.compare("test2")); @@ -146,7 +207,7 @@ TEST_F(AccessLogManagerImplTest, flushToLogFileOnDemand) { // make sure timer is re-enabled on callback call log_file->write("test2"); - EXPECT_CALL(*timer, enableTimer(timeout_40ms_)); + EXPECT_CALL(*timer, enableTimer(timeout_40ms_, _)); timer->invokeCallback(); expected_writes++; @@ -159,11 +220,41 @@ TEST_F(AccessLogManagerImplTest, flushToLogFileOnDemand) { EXPECT_CALL(*file_, close_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); } +TEST_F(AccessLogManagerImplTest, flushCountsIOErrors) { + NiceMock* timer = new NiceMock(&dispatcher_); + + EXPECT_CALL(*file_, open_(_)).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); + AccessLogFileSharedPtr log_file = access_log_manager_.createAccessLog("foo"); + + EXPECT_EQ(0UL, store_.counter("filesystem.write_failed").value()); + + EXPECT_CALL(*timer, enableTimer(timeout_40ms_, _)); + EXPECT_CALL(*file_, write_(_)) + .WillOnce(Invoke([](absl::string_view data) -> Api::IoCallSizeResult { + EXPECT_EQ(0, data.compare("test")); + return Filesystem::resultFailure(2UL, ENOSPC); + })); + + log_file->write("test"); + + { + Thread::LockGuard lock(file_->write_mutex_); + while (file_->num_writes_ != 1) { + file_->write_event_.wait(file_->write_mutex_); + } + } + + waitForCounterEq("filesystem.write_failed", 1); + EXPECT_EQ(0UL, store_.counter("filesystem.write_completed").value()); + + EXPECT_CALL(*file_, close_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); +} + TEST_F(AccessLogManagerImplTest, reopenFile) { NiceMock* timer = new NiceMock(&dispatcher_); Sequence sq; - EXPECT_CALL(*file_, open_()) + EXPECT_CALL(*file_, open_(_)) .InSequence(sq) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); AccessLogFileSharedPtr log_file = access_log_manager_.createAccessLog("foo"); @@ -188,7 +279,7 @@ TEST_F(AccessLogManagerImplTest, reopenFile) { EXPECT_CALL(*file_, close_()) .InSequence(sq) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); - EXPECT_CALL(*file_, open_()) + EXPECT_CALL(*file_, open_(_)) .InSequence(sq) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); @@ -224,7 +315,7 @@ TEST_F(AccessLogManagerImplTest, reopenThrows) { })); Sequence sq; - EXPECT_CALL(*file_, open_()) + EXPECT_CALL(*file_, open_(_)) .InSequence(sq) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); @@ -232,7 +323,7 @@ TEST_F(AccessLogManagerImplTest, reopenThrows) { EXPECT_CALL(*file_, close_()) .InSequence(sq) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); - EXPECT_CALL(*file_, open_()) + EXPECT_CALL(*file_, open_(_)) .InSequence(sq) .WillOnce(Return(ByMove(Filesystem::resultFailure(false, 0)))); @@ -259,10 +350,12 @@ TEST_F(AccessLogManagerImplTest, reopenThrows) { // write call should not cause any exceptions log_file->write("random data"); timer->invokeCallback(); + + waitForCounterEq("filesystem.reopen_failed", 1); } TEST_F(AccessLogManagerImplTest, bigDataChunkShouldBeFlushedWithoutTimer) { - EXPECT_CALL(*file_, open_()).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); + EXPECT_CALL(*file_, open_(_)).WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); AccessLogFileSharedPtr log_file = access_log_manager_.createAccessLog("foo"); EXPECT_CALL(*file_, write_(_)) @@ -305,7 +398,7 @@ TEST_F(AccessLogManagerImplTest, reopenAllFiles) { EXPECT_CALL(dispatcher_, createTimer_(_)).WillRepeatedly(ReturnNew>()); Sequence sq; - EXPECT_CALL(*file_, open_()) + EXPECT_CALL(*file_, open_(_)) .InSequence(sq) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); AccessLogFileSharedPtr log = access_log_manager_.createAccessLog("foo"); @@ -315,7 +408,7 @@ TEST_F(AccessLogManagerImplTest, reopenAllFiles) { .WillOnce(Return(ByMove(std::unique_ptr>(file2)))); Sequence sq2; - EXPECT_CALL(*file2, open_()) + EXPECT_CALL(*file2, open_(_)) .InSequence(sq2) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); AccessLogFileSharedPtr log2 = access_log_manager_.createAccessLog("bar"); @@ -342,10 +435,10 @@ TEST_F(AccessLogManagerImplTest, reopenAllFiles) { .InSequence(sq2) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); - EXPECT_CALL(*file_, open_()) + EXPECT_CALL(*file_, open_(_)) .InSequence(sq) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); - EXPECT_CALL(*file2, open_()) + EXPECT_CALL(*file2, open_(_)) .InSequence(sq2) .WillOnce(Return(ByMove(Filesystem::resultSuccess(true)))); diff --git a/test/common/common/BUILD b/test/common/common/BUILD index 4714f1ff5c..dd9b768e0a 100644 --- a/test/common/common/BUILD +++ b/test/common/common/BUILD @@ -143,6 +143,15 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "regex_test", + srcs = ["regex_test.cc"], + deps = [ + "//source/common/common:regex_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "perf_annotation_test", srcs = ["perf_annotation_test.cc"], diff --git a/test/common/common/cleanup_test.cc b/test/common/common/cleanup_test.cc index 7b666c163c..98a5903087 100644 --- a/test/common/common/cleanup_test.cc +++ b/test/common/common/cleanup_test.cc @@ -13,6 +13,18 @@ TEST(CleanupTest, ScopeExitCallback) { EXPECT_TRUE(callback_fired); } +TEST(CleanupTest, Cancel) { + bool callback_fired = false; + { + Cleanup cleanup([&callback_fired] { callback_fired = true; }); + EXPECT_FALSE(cleanup.cancelled()); + cleanup.cancel(); + EXPECT_FALSE(callback_fired); + EXPECT_TRUE(cleanup.cancelled()); + } + EXPECT_FALSE(callback_fired); +} + TEST(RaiiListElementTest, DeleteOnDestruction) { std::list l; diff --git a/test/common/common/lock_guard_test.cc b/test/common/common/lock_guard_test.cc index 163b535378..8cc8eb8b83 100644 --- a/test/common/common/lock_guard_test.cc +++ b/test/common/common/lock_guard_test.cc @@ -9,7 +9,7 @@ namespace Thread { class ThreadTest : public testing::Test { protected: ThreadTest() = default; - int a_ GUARDED_BY(a_mutex_){0}; + int a_ ABSL_GUARDED_BY(a_mutex_){0}; MutexBasicLockable a_mutex_; int b_{0}; }; diff --git a/test/common/common/matchers_test.cc b/test/common/common/matchers_test.cc index d5b509a890..3c0a64f4ca 100644 --- a/test/common/common/matchers_test.cc +++ b/test/common/common/matchers_test.cc @@ -257,6 +257,15 @@ TEST(MetadataTest, MatchDoubleListValue) { metadataValue.Clear(); } +TEST(StringMatcher, SafeRegexValue) { + envoy::type::matcher::StringMatcher matcher; + matcher.mutable_safe_regex()->mutable_google_re2(); + matcher.mutable_safe_regex()->set_regex("foo.*"); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("foo")); + EXPECT_TRUE(Matchers::StringMatcherImpl(matcher).match("foobar")); + EXPECT_FALSE(Matchers::StringMatcherImpl(matcher).match("bar")); +} + TEST(LowerCaseStringMatcher, MatchExactValue) { envoy::type::matcher::StringMatcher matcher; matcher.set_exact("Foo"); diff --git a/test/common/common/regex_test.cc b/test/common/common/regex_test.cc new file mode 100644 index 0000000000..b12454d7cd --- /dev/null +++ b/test/common/common/regex_test.cc @@ -0,0 +1,61 @@ +#include "envoy/common/exception.h" + +#include "common/common/regex.h" + +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Regex { +namespace { + +TEST(Utility, ParseStdRegex) { + EXPECT_THROW_WITH_REGEX(Utility::parseStdRegex("(+invalid)"), EnvoyException, + "Invalid regex '\\(\\+invalid\\)': .+"); + + { + std::regex regex = Utility::parseStdRegex("x*"); + EXPECT_NE(0, regex.flags() & std::regex::optimize); + } + + { + std::regex regex = Utility::parseStdRegex("x*", std::regex::icase); + EXPECT_NE(0, regex.flags() & std::regex::icase); + EXPECT_EQ(0, regex.flags() & std::regex::optimize); + } +} + +TEST(Utility, ParseRegex) { + { + envoy::type::matcher::RegexMatcher matcher; + matcher.mutable_google_re2(); + matcher.set_regex("(+invalid)"); + EXPECT_THROW_WITH_MESSAGE(Utility::parseRegex(matcher), EnvoyException, + "no argument for repetition operator: +"); + } + + // Regression test for https://github.com/envoyproxy/envoy/issues/7728 + { + envoy::type::matcher::RegexMatcher matcher; + matcher.mutable_google_re2(); + matcher.set_regex("/asdf/.*"); + const auto compiled_matcher = Utility::parseRegex(matcher); + const std::string long_string = "/asdf/" + std::string(50 * 1024, 'a'); + EXPECT_TRUE(compiled_matcher->match(long_string)); + } + + // Verify max program size. + { + envoy::type::matcher::RegexMatcher matcher; + matcher.mutable_google_re2()->mutable_max_program_size()->set_value(1); + matcher.set_regex("/asdf/.*"); + EXPECT_THROW_WITH_MESSAGE(Utility::parseRegex(matcher), EnvoyException, + "regex '/asdf/.*' RE2 program size of 24 > max program size of 1. " + "Increase configured max program size if necessary."); + } +} + +} // namespace +} // namespace Regex +} // namespace Envoy diff --git a/test/common/common/utility_speed_test.cc b/test/common/common/utility_speed_test.cc index 69ed1b41ff..6e262c08bf 100644 --- a/test/common/common/utility_speed_test.cc +++ b/test/common/common/utility_speed_test.cc @@ -206,6 +206,28 @@ static void BM_FindTokenValueNoSplit(benchmark::State& state) { } BENCHMARK(BM_FindTokenValueNoSplit); +static void BM_RemoveTokensLong(benchmark::State& state) { + auto size = state.range(0); + std::string input(size, ','); + std::vector to_remove; + StringUtil::CaseUnorderedSet to_remove_set; + for (decltype(size) i = 0; i < size; i++) { + to_remove.push_back(std::to_string(i)); + } + for (int i = 0; i < size; i++) { + if (i & 1) { + to_remove_set.insert(to_remove[i]); + } + input.append(","); + input.append(to_remove[i]); + } + for (auto _ : state) { + Envoy::StringUtil::removeTokens(input, ",", to_remove_set, ","); + state.SetBytesProcessed(static_cast(state.iterations()) * input.size()); + } +} +BENCHMARK(BM_RemoveTokensLong)->Range(8, 8 << 10); + static void BM_IntervalSetInsert17(benchmark::State& state) { for (auto _ : state) { Envoy::IntervalSetImpl interval_set; diff --git a/test/common/common/utility_test.cc b/test/common/common/utility_test.cc index 3ad89010ac..d4ea1e1f41 100644 --- a/test/common/common/utility_test.cc +++ b/test/common/common/utility_test.cc @@ -368,6 +368,28 @@ TEST(StringUtil, StringViewSplit) { } } +TEST(StringUtil, StringViewRemoveTokens) { + // Basic cases. + EXPECT_EQ(StringUtil::removeTokens("", ",", {"two"}, ","), ""); + EXPECT_EQ(StringUtil::removeTokens("one", ",", {"two"}, ","), "one"); + EXPECT_EQ(StringUtil::removeTokens("one,two ", ",", {"two"}, ","), "one"); + EXPECT_EQ(StringUtil::removeTokens("one,two ", ",", {"two", "one"}, ","), ""); + EXPECT_EQ(StringUtil::removeTokens("one,two ", ",", {"one"}, ","), "two"); + EXPECT_EQ(StringUtil::removeTokens("one,two,three ", ",", {"two"}, ","), "one,three"); + EXPECT_EQ(StringUtil::removeTokens(" one , two , three ", ",", {"two"}, ","), "one,three"); + EXPECT_EQ(StringUtil::removeTokens(" one , two , three ", ",", {"three"}, ","), "one,two"); + EXPECT_EQ(StringUtil::removeTokens(" one , two , three ", ",", {"three"}, ", "), "one, two"); + EXPECT_EQ(StringUtil::removeTokens("one,two,three", ",", {"two", "three"}, ","), "one"); + EXPECT_EQ(StringUtil::removeTokens("one,two,three,four", ",", {"two", "three"}, ","), "one,four"); + // Ignore case. + EXPECT_EQ(StringUtil::removeTokens("One,Two,Three,Four", ",", {"two", "three"}, ","), "One,Four"); + // Longer joiner. + EXPECT_EQ(StringUtil::removeTokens("one,two,three,four", ",", {"two", "three"}, " , "), + "one , four"); + // Delimiters. + EXPECT_EQ(StringUtil::removeTokens("one,two;three ", ",;", {"two"}, ","), "one,three"); +} + TEST(StringUtil, removeCharacters) { IntervalSetImpl removals; removals.insert(3, 5); @@ -401,22 +423,6 @@ TEST(Primes, findPrimeLargerThan) { EXPECT_EQ(10007, Primes::findPrimeLargerThan(9991)); } -TEST(RegexUtil, parseRegex) { - EXPECT_THROW_WITH_REGEX(RegexUtil::parseRegex("(+invalid)"), EnvoyException, - "Invalid regex '\\(\\+invalid\\)': .+"); - - { - std::regex regex = RegexUtil::parseRegex("x*"); - EXPECT_NE(0, regex.flags() & std::regex::optimize); - } - - { - std::regex regex = RegexUtil::parseRegex("x*", std::regex::icase); - EXPECT_NE(0, regex.flags() & std::regex::icase); - EXPECT_EQ(0, regex.flags() & std::regex::optimize); - } -} - class WeightedClusterEntry { public: WeightedClusterEntry(const std::string name, const uint64_t weight) diff --git a/test/common/config/config_provider_impl_test.cc b/test/common/config/config_provider_impl_test.cc index 5e7e65eeec..6bf5e0f9ce 100644 --- a/test/common/config/config_provider_impl_test.cc +++ b/test/common/config/config_provider_impl_test.cc @@ -629,6 +629,7 @@ class DeltaDummyConfigProviderManager : public ConfigProviderManagerImplBase { const std::string&, const Envoy::Config::ConfigProviderManager::OptionalArg&) override { DeltaDummyConfigSubscriptionSharedPtr subscription = + getSubscription( config_source_proto, factory_context.initManager(), [&factory_context](const uint64_t manager_identifier, @@ -709,6 +710,8 @@ TEST_F(DeltaConfigProviderImplTest, MultipleDeltaSubscriptions) { subscription.onConfigUpdate(untyped_dummy_configs, "2"); // NOTE: the config implementation is append only and _does not_ track updates/removals to the // config proto set, so the expectation is to double the size of the set. + EXPECT_EQ(provider1->config().get(), + provider2->config().get()); EXPECT_EQ(provider1->config()->numProtos(), 4); EXPECT_EQ(provider1->configProtoInfoVector().value().version_, "2"); diff --git a/test/common/config/delta_subscription_test_harness.h b/test/common/config/delta_subscription_test_harness.h index cae4cebbf2..4414aa7748 100644 --- a/test/common/config/delta_subscription_test_harness.h +++ b/test/common/config/delta_subscription_test_harness.h @@ -157,7 +157,7 @@ class DeltaSubscriptionTestHarness : public SubscriptionTestHarness { void expectEnableInitFetchTimeoutTimer(std::chrono::milliseconds timeout) override { init_timeout_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*init_timeout_timer_, enableTimer(std::chrono::milliseconds(timeout))); + EXPECT_CALL(*init_timeout_timer_, enableTimer(std::chrono::milliseconds(timeout), _)); } void expectDisableInitFetchTimeoutTimer() override { diff --git a/test/common/config/filesystem_subscription_impl_test.cc b/test/common/config/filesystem_subscription_impl_test.cc index 5249143464..8c0f19775c 100644 --- a/test/common/config/filesystem_subscription_impl_test.cc +++ b/test/common/config/filesystem_subscription_impl_test.cc @@ -6,7 +6,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using ::testing::Throw; +using testing::Return; +using testing::Throw; namespace Envoy { namespace Config { diff --git a/test/common/config/filesystem_subscription_test_harness.h b/test/common/config/filesystem_subscription_test_harness.h index 5bed6e9b19..901b5cf29b 100644 --- a/test/common/config/filesystem_subscription_test_harness.h +++ b/test/common/config/filesystem_subscription_test_harness.h @@ -20,7 +20,6 @@ using testing::_; using testing::NiceMock; -using testing::Return; namespace Envoy { namespace Config { diff --git a/test/common/config/grpc_mux_impl_test.cc b/test/common/config/grpc_mux_impl_test.cc index 72b556277a..c2493c0934 100644 --- a/test/common/config/grpc_mux_impl_test.cc +++ b/test/common/config/grpc_mux_impl_test.cc @@ -154,7 +154,7 @@ TEST_F(GrpcMuxImplTest, ResetStream) { .Times(3); EXPECT_CALL(random_, random()); ASSERT_TRUE(timer != nullptr); // initialized from dispatcher mock. - EXPECT_CALL(*timer, enableTimer(_)); + EXPECT_CALL(*timer, enableTimer(_, _)); grpc_mux_->grpcStreamForTest().onRemoteClose(Grpc::Status::GrpcStatus::Canceled, ""); EXPECT_EQ(0, control_plane_connected_state_.value()); EXPECT_CALL(*async_client_, startRaw(_, _, _)).WillOnce(Return(&async_stream_)); @@ -481,7 +481,7 @@ TEST_F(GrpcMuxImplTestWithMockTimeSystem, TooManyRequestsWithEmptyRateLimitSetti grpc_mux_->start(); // Validate that drain_request_timer is enabled when there are no tokens. - EXPECT_CALL(*drain_request_timer, enableTimer(std::chrono::milliseconds(100))); + EXPECT_CALL(*drain_request_timer, enableTimer(std::chrono::milliseconds(100), _)); onReceiveMessage(99); EXPECT_EQ(1, stats_.counter("control_plane.rate_limit_enforced").value()); EXPECT_EQ( @@ -541,7 +541,8 @@ TEST_F(GrpcMuxImplTest, TooManyRequestsWithCustomRateLimitSettings) { EXPECT_EQ(0, stats_.counter("control_plane.rate_limit_enforced").value()); // Validate that drain_request_timer is enabled when there are no tokens. - EXPECT_CALL(*drain_request_timer, enableTimer(std::chrono::milliseconds(500))).Times(AtLeast(1)); + EXPECT_CALL(*drain_request_timer, enableTimer(std::chrono::milliseconds(500), _)) + .Times(AtLeast(1)); onReceiveMessage(160); EXPECT_EQ(12, stats_.counter("control_plane.rate_limit_enforced").value()); Stats::Gauge& pending_requests = diff --git a/test/common/config/grpc_subscription_impl_test.cc b/test/common/config/grpc_subscription_impl_test.cc index ea773b24f1..7a1ca43598 100644 --- a/test/common/config/grpc_subscription_impl_test.cc +++ b/test/common/config/grpc_subscription_impl_test.cc @@ -18,7 +18,7 @@ TEST_F(GrpcSubscriptionImplTest, StreamCreationFailure) { EXPECT_CALL(callbacks_, onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _)); EXPECT_CALL(random_, random()); - EXPECT_CALL(*timer_, enableTimer(_)); + EXPECT_CALL(*timer_, enableTimer(_, _)); subscription_->start({"cluster0", "cluster1"}); EXPECT_TRUE(statsAre(2, 0, 0, 1, 0, 0)); // Ensure this doesn't cause an issue by sending a request, since we don't @@ -40,7 +40,7 @@ TEST_F(GrpcSubscriptionImplTest, RemoteStreamClose) { EXPECT_TRUE(statsAre(1, 0, 0, 0, 0, 0)); EXPECT_CALL(callbacks_, onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _)); - EXPECT_CALL(*timer_, enableTimer(_)); + EXPECT_CALL(*timer_, enableTimer(_, _)); EXPECT_CALL(random_, random()); subscription_->grpcMux().grpcStreamForTest().onRemoteClose(Grpc::Status::GrpcStatus::Canceled, ""); diff --git a/test/common/config/grpc_subscription_test_harness.h b/test/common/config/grpc_subscription_test_harness.h index 3031c2e947..9e68838ee6 100644 --- a/test/common/config/grpc_subscription_test_harness.h +++ b/test/common/config/grpc_subscription_test_harness.h @@ -142,7 +142,7 @@ class GrpcSubscriptionTestHarness : public SubscriptionTestHarness { void expectEnableInitFetchTimeoutTimer(std::chrono::milliseconds timeout) override { init_timeout_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*init_timeout_timer_, enableTimer(std::chrono::milliseconds(timeout))); + EXPECT_CALL(*init_timeout_timer_, enableTimer(std::chrono::milliseconds(timeout), _)); } void expectDisableInitFetchTimeoutTimer() override { diff --git a/test/common/config/http_subscription_impl_test.cc b/test/common/config/http_subscription_impl_test.cc index 258357452c..afd592c022 100644 --- a/test/common/config/http_subscription_impl_test.cc +++ b/test/common/config/http_subscription_impl_test.cc @@ -14,7 +14,7 @@ class HttpSubscriptionImplTest : public testing::Test, public HttpSubscriptionTe TEST_F(HttpSubscriptionImplTest, OnRequestReset) { startSubscription({"cluster0", "cluster1"}); EXPECT_CALL(random_gen_, random()).WillOnce(Return(0)); - EXPECT_CALL(*timer_, enableTimer(_)); + EXPECT_CALL(*timer_, enableTimer(_, _)); EXPECT_CALL(callbacks_, onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _)); http_callbacks_->onFailure(Http::AsyncClient::FailureReason::Reset); @@ -32,7 +32,7 @@ TEST_F(HttpSubscriptionImplTest, BadJsonRecovery) { Http::MessagePtr message{new Http::ResponseMessageImpl(std::move(response_headers))}; message->body() = std::make_unique(";!@#badjso n"); EXPECT_CALL(random_gen_, random()).WillOnce(Return(0)); - EXPECT_CALL(*timer_, enableTimer(_)); + EXPECT_CALL(*timer_, enableTimer(_, _)); EXPECT_CALL(callbacks_, onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::ConnectionFailure, _)); http_callbacks_->onSuccess(std::move(message)); diff --git a/test/common/config/http_subscription_test_harness.h b/test/common/config/http_subscription_test_harness.h index 95d1123be5..1d41ee3801 100644 --- a/test/common/config/http_subscription_test_harness.h +++ b/test/common/config/http_subscription_test_harness.h @@ -144,7 +144,7 @@ class HttpSubscriptionTestHarness : public SubscriptionTestHarness { Envoy::Config::ConfigUpdateFailureReason::UpdateRejected, _)); } EXPECT_CALL(random_gen_, random()).WillOnce(Return(0)); - EXPECT_CALL(*timer_, enableTimer(_)); + EXPECT_CALL(*timer_, enableTimer(_, _)); http_callbacks_->onSuccess(std::move(message)); if (accept) { version_ = version; @@ -159,7 +159,7 @@ class HttpSubscriptionTestHarness : public SubscriptionTestHarness { void expectEnableInitFetchTimeoutTimer(std::chrono::milliseconds timeout) override { init_timeout_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*init_timeout_timer_, enableTimer(std::chrono::milliseconds(timeout))); + EXPECT_CALL(*init_timeout_timer_, enableTimer(std::chrono::milliseconds(timeout), _)); } void expectDisableInitFetchTimeoutTimer() override { diff --git a/test/common/config/rds_json_test.cc b/test/common/config/rds_json_test.cc index 190220b84f..6a0ed6717e 100644 --- a/test/common/config/rds_json_test.cc +++ b/test/common/config/rds_json_test.cc @@ -6,8 +6,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; - namespace Envoy { namespace Config { namespace { diff --git a/test/common/config/utility_test.cc b/test/common/config/utility_test.cc index dea04ddb1f..a84513f036 100644 --- a/test/common/config/utility_test.cc +++ b/test/common/config/utility_test.cc @@ -18,10 +18,8 @@ #include "gtest/gtest.h" using testing::_; -using testing::AtLeast; using testing::Ref; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Config { diff --git a/test/common/event/dispatcher_impl_test.cc b/test/common/event/dispatcher_impl_test.cc index c0605a70c3..58e750b293 100644 --- a/test/common/event/dispatcher_impl_test.cc +++ b/test/common/event/dispatcher_impl_test.cc @@ -18,8 +18,6 @@ using testing::_; using testing::InSequence; using testing::NiceMock; -using testing::Return; -using testing::StartsWith; namespace Envoy { namespace Event { @@ -185,6 +183,40 @@ TEST_F(DispatcherImplTest, Timer) { } } +TEST_F(DispatcherImplTest, TimerWithScope) { + TimerPtr timer; + MockScopedTrackedObject scope; + dispatcher_->post([this, &timer, &scope]() { + { + // Expect a call to dumpState. The timer will call onFatalError during + // the alarm interval, and if the scope is tracked correctly this will + // result in a dumpState call. + EXPECT_CALL(scope, dumpState(_, _)); + Thread::LockGuard lock(mu_); + timer = dispatcher_->createTimer([this]() { + { + Thread::LockGuard lock(mu_); + static_cast(dispatcher_.get())->onFatalError(); + work_finished_ = true; + } + cv_.notifyOne(); + }); + EXPECT_FALSE(timer->enabled()); + } + cv_.notifyOne(); + }); + + Thread::LockGuard lock(mu_); + while (timer == nullptr) { + cv_.wait(mu_); + } + timer->enableTimer(std::chrono::milliseconds(50), &scope); + + while (!work_finished_) { + cv_.wait(mu_); + } +} + TEST_F(DispatcherImplTest, IsThreadSafe) { dispatcher_->post([this]() { { diff --git a/test/common/grpc/async_client_impl_test.cc b/test/common/grpc/async_client_impl_test.cc index 1a33686075..469b23bfe6 100644 --- a/test/common/grpc/async_client_impl_test.cc +++ b/test/common/grpc/async_client_impl_test.cc @@ -14,7 +14,6 @@ using testing::Eq; using testing::Invoke; using testing::Return; using testing::ReturnRef; -using testing::Throw; namespace Envoy { namespace Grpc { diff --git a/test/common/grpc/context_impl_test.cc b/test/common/grpc/context_impl_test.cc index ace45801ca..d1d8079653 100644 --- a/test/common/grpc/context_impl_test.cc +++ b/test/common/grpc/context_impl_test.cc @@ -18,7 +18,7 @@ namespace Grpc { TEST(GrpcContextTest, ChargeStats) { NiceMock cluster; - Envoy::Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Stats::StatNamePool pool(*symbol_table_); const Stats::StatName service = pool.add("service"); const Stats::StatName method = pool.add("method"); @@ -58,7 +58,7 @@ TEST(GrpcContextTest, ResolveServiceAndMethod) { Http::HeaderMapImpl headers; Http::HeaderEntry& path = headers.insertPath(); path.value(std::string("/service_name/method_name")); - Envoy::Test::Global symbol_table; + Stats::TestSymbolTable symbol_table; ContextImpl context(*symbol_table); absl::optional request_names = context.resolveServiceAndMethod(&path); EXPECT_TRUE(request_names); diff --git a/test/common/grpc/grpc_client_integration.h b/test/common/grpc/grpc_client_integration.h index ff6d4d3a7b..bdfc0c6ae1 100644 --- a/test/common/grpc/grpc_client_integration.h +++ b/test/common/grpc/grpc_client_integration.h @@ -43,7 +43,6 @@ class GrpcClientIntegrationParamTest : public BaseGrpcClientIntegrationParamTest, public testing::TestWithParam> { public: - ~GrpcClientIntegrationParamTest() override = default; static std::string protocolTestParamsToString( const ::testing::TestParamInfo>& p) { return fmt::format("{}_{}", @@ -54,10 +53,26 @@ class GrpcClientIntegrationParamTest ClientType clientType() const override { return std::get<1>(GetParam()); } }; +class DeltaSotwGrpcClientIntegrationParamTest + : public BaseGrpcClientIntegrationParamTest, + public testing::TestWithParam> { +public: + static std::string protocolTestParamsToString( + const ::testing::TestParamInfo>& + p) { + return fmt::format("{}_{}", + std::get<0>(p.param) == Network::Address::IpVersion::v4 ? "IPv4" : "IPv6", + std::get<1>(p.param) == ClientType::GoogleGrpc ? "GoogleGrpc" : "EnvoyGrpc", + std::get<2>(p.param) ? "Delta" : "StateOfTheWorld"); + } + Network::Address::IpVersion ipVersion() const override { return std::get<0>(GetParam()); } + ClientType clientType() const override { return std::get<1>(GetParam()); } + bool isDelta() { return std::get<2>(GetParam()); } +}; + class DeltaSotwIntegrationParamTest : public testing::TestWithParam> { public: - ~DeltaSotwIntegrationParamTest() override = default; static std::string protocolTestParamsToString( const ::testing::TestParamInfo>& p) { return fmt::format("{}_{}_{}", @@ -84,10 +99,17 @@ class DeltaSotwIntegrationParamTest #define GRPC_CLIENT_INTEGRATION_PARAMS \ testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ testing::Values(Grpc::ClientType::EnvoyGrpc, Grpc::ClientType::GoogleGrpc)) +#define DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS \ + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ + testing::Values(Grpc::ClientType::EnvoyGrpc, Grpc::ClientType::GoogleGrpc), \ + testing::Bool()) #else #define GRPC_CLIENT_INTEGRATION_PARAMS \ testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ testing::Values(Grpc::ClientType::EnvoyGrpc)) +#define DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS \ + testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), \ + testing::Values(Grpc::ClientType::EnvoyGrpc), testing::Bool()) #endif // ENVOY_GOOGLE_GRPC #define DELTA_INTEGRATION_PARAMS \ diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index 997bae8b6b..1d02a8e81d 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -416,7 +416,7 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { FakeHttpConnectionPtr fake_connection_; std::vector fake_streams_; const Protobuf::MethodDescriptor* method_descriptor_; - Envoy::Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Stats::IsolatedStoreImpl* stats_store_ = new Stats::IsolatedStoreImpl(*symbol_table_); Api::ApiPtr api_; Event::DispatcherPtr dispatcher_; diff --git a/test/common/http/BUILD b/test/common/http/BUILD index 56eefc2b25..5c6a91624f 100644 --- a/test/common/http/BUILD +++ b/test/common/http/BUILD @@ -51,6 +51,7 @@ envoy_cc_test( "//test/mocks/event:event_mocks", "//test/mocks/http:http_mocks", "//test/mocks/network:network_mocks", + "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", @@ -129,17 +130,6 @@ envoy_cc_test_library( ], ) -envoy_cc_test_library( - name = "conn_manager_impl_common_lib", - hdrs = ["conn_manager_impl_common.h"], - deps = [ - "//include/envoy/common:time_interface", - "//include/envoy/config:config_provider_interface", - "//include/envoy/router:rds_interface", - "//test/mocks/router:router_mocks", - ], -) - envoy_proto_library( name = "conn_manager_impl_fuzz_proto", srcs = ["conn_manager_impl_fuzz.proto"], @@ -153,7 +143,6 @@ envoy_cc_fuzz_test( srcs = ["conn_manager_impl_fuzz_test.cc"], corpus = "conn_manager_impl_corpus", deps = [ - ":conn_manager_impl_common_lib", ":conn_manager_impl_fuzz_proto_cc", "//source/common/common:empty_string", "//source/common/http:conn_manager_lib", @@ -161,11 +150,13 @@ envoy_cc_fuzz_test( "//source/common/http:date_provider_lib", "//source/common/network:address_lib", "//source/common/network:utility_lib", + "//source/common/stats:symbol_table_creator_lib", "//test/fuzz:utility_lib", "//test/mocks/access_log:access_log_mocks", "//test/mocks/http:http_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", + "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/tracing:tracing_mocks", @@ -179,7 +170,6 @@ envoy_cc_test( name = "conn_manager_impl_test", srcs = ["conn_manager_impl_test.cc"], deps = [ - ":conn_manager_impl_common_lib", "//include/envoy/access_log:access_log_interface", "//include/envoy/buffer:buffer_interface", "//include/envoy/event:dispatcher_interface", @@ -206,6 +196,7 @@ envoy_cc_test( "//test/mocks/http:http_mocks", "//test/mocks/local_info:local_info_mocks", "//test/mocks/network:network_mocks", + "//test/mocks/router:router_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", "//test/mocks/ssl:ssl_mocks", @@ -233,6 +224,7 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/ssl:ssl_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_runtime_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 ae5b7cdbb6..bac7b1c2f1 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -26,7 +26,6 @@ using testing::_; using testing::Invoke; using testing::NiceMock; -using testing::Ref; using testing::Return; using testing::ReturnRef; @@ -76,6 +75,7 @@ class AsyncClientImplTest : public testing::Test { NiceMock local_info_; Http::ContextImpl http_context_; AsyncClientImpl client_; + NiceMock stream_info_; }; TEST_F(AsyncClientImplTest, BasicStream) { @@ -84,7 +84,7 @@ TEST_F(AsyncClientImplTest, BasicStream) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -126,7 +126,7 @@ TEST_F(AsyncClientImplTest, Basic) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -165,7 +165,7 @@ TEST_F(AsyncClientImplTest, Retry) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -185,7 +185,7 @@ TEST_F(AsyncClientImplTest, Retry) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -208,7 +208,7 @@ TEST_F(AsyncClientImplTest, RetryWithStream) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -233,7 +233,7 @@ TEST_F(AsyncClientImplTest, RetryWithStream) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -256,7 +256,7 @@ TEST_F(AsyncClientImplTest, MultipleStreams) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -282,7 +282,7 @@ TEST_F(AsyncClientImplTest, MultipleStreams) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder2, cm_.conn_pool_.host_, stream_info_); response_decoder2 = &decoder; return nullptr; })); @@ -316,7 +316,7 @@ TEST_F(AsyncClientImplTest, MultipleRequests) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -335,7 +335,7 @@ TEST_F(AsyncClientImplTest, MultipleRequests) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder2, cm_.conn_pool_.host_, stream_info_); response_decoder2 = &decoder; return nullptr; })); @@ -362,7 +362,7 @@ TEST_F(AsyncClientImplTest, StreamAndRequest) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -380,7 +380,7 @@ TEST_F(AsyncClientImplTest, StreamAndRequest) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder2, cm_.conn_pool_.host_, stream_info_); response_decoder2 = &decoder; return nullptr; })); @@ -419,7 +419,7 @@ TEST_F(AsyncClientImplTest, StreamWithTrailers) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -452,7 +452,7 @@ TEST_F(AsyncClientImplTest, Trailers) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -472,7 +472,7 @@ TEST_F(AsyncClientImplTest, ImmediateReset) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); @@ -493,7 +493,7 @@ TEST_F(AsyncClientImplTest, LocalResetAfterStreamStart) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -530,7 +530,7 @@ TEST_F(AsyncClientImplTest, ResetInOnHeaders) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); @@ -565,7 +565,7 @@ TEST_F(AsyncClientImplTest, RemoteResetAfterStreamStart) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -598,7 +598,7 @@ TEST_F(AsyncClientImplTest, ResetAfterResponseStart) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); @@ -616,7 +616,7 @@ TEST_F(AsyncClientImplTest, ResetStream) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); @@ -633,7 +633,7 @@ TEST_F(AsyncClientImplTest, CancelRequest) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); @@ -649,7 +649,7 @@ TEST_F(AsyncClientImplTest, DestroyWithActiveStream) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); @@ -664,7 +664,7 @@ TEST_F(AsyncClientImplTest, DestroyWithActiveRequest) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); @@ -713,13 +713,13 @@ TEST_F(AsyncClientImplTest, StreamTimeout) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&message_->headers()), true)); timer_ = new NiceMock(&dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40), _)); EXPECT_CALL(stream_encoder_.stream_, resetStream(_)); TestHeaderMapImpl expected_timeout{ @@ -746,7 +746,7 @@ 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_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); @@ -754,7 +754,7 @@ TEST_F(AsyncClientImplTest, StreamTimeoutHeadReply) { 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(*timer_, enableTimer(std::chrono::milliseconds(40), _)); EXPECT_CALL(stream_encoder_.stream_, resetStream(_)); TestHeaderMapImpl expected_timeout{ @@ -772,14 +772,14 @@ TEST_F(AsyncClientImplTest, RequestTimeout) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&message_->headers()), true)); expectSuccess(504); timer_ = new NiceMock(&dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40), _)); EXPECT_CALL(stream_encoder_.stream_, resetStream(_)); client_.send(std::move(message_), callbacks_, AsyncClient::RequestOptions().setTimeout(std::chrono::milliseconds(40))); @@ -798,13 +798,13 @@ TEST_F(AsyncClientImplTest, DisableTimer) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&message_->headers()), true)); timer_ = new NiceMock(&dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(200))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(200), _)); EXPECT_CALL(*timer_, disableTimer()); EXPECT_CALL(stream_encoder_.stream_, resetStream(_)); AsyncClient::Request* request = @@ -817,13 +817,13 @@ TEST_F(AsyncClientImplTest, DisableTimerWithStream) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder&, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); return nullptr; })); EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&message_->headers()), true)); timer_ = new NiceMock(&dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(40), _)); EXPECT_CALL(*timer_, disableTimer()); EXPECT_CALL(stream_encoder_.stream_, resetStream(_)); EXPECT_CALL(stream_callbacks_, onReset()); @@ -841,7 +841,7 @@ TEST_F(AsyncClientImplTest, MultipleDataStream) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](StreamDecoder& decoder, ConnectionPool::Callbacks& callbacks) -> ConnectionPool::Cancellable* { - callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(stream_encoder_, cm_.conn_pool_.host_, stream_info_); response_decoder_ = &decoder; return nullptr; })); diff --git a/test/common/http/codec_client_test.cc b/test/common/http/codec_client_test.cc index 7fc083eba0..1de3c04cb4 100644 --- a/test/common/http/codec_client_test.cc +++ b/test/common/http/codec_client_test.cc @@ -14,6 +14,7 @@ #include "test/mocks/event/mocks.h" #include "test/mocks/http/mocks.h" #include "test/mocks/network/mocks.h" +#include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" @@ -31,7 +32,7 @@ using testing::NiceMock; using testing::Pointee; using testing::Ref; using testing::Return; -using testing::SaveArg; +using testing::ReturnRef; using testing::Throw; namespace Envoy { @@ -56,6 +57,7 @@ class CodecClientTest : public testing::Test { EXPECT_CALL(dispatcher_, createTimer_(_)); client_ = std::make_unique(std::move(connection), codec_, nullptr, host_, dispatcher_); + ON_CALL(*connection_, streamInfo()).WillByDefault(ReturnRef(stream_info_)); } ~CodecClientTest() override { EXPECT_EQ(0U, client_->numActiveRequests()); } @@ -70,6 +72,7 @@ class CodecClientTest : public testing::Test { new NiceMock()}; Upstream::HostDescriptionConstSharedPtr host_{ Upstream::makeTestHostDescription(cluster_, "tcp://127.0.0.1:80")}; + NiceMock stream_info_; }; TEST_F(CodecClientTest, BasicHeaderOnlyResponse) { @@ -257,6 +260,16 @@ TEST_F(CodecClientTest, WatermarkPassthrough) { connection_cb_->onBelowWriteBufferLowWatermark(); } +TEST_F(CodecClientTest, SSLConnectionInfo) { + std::string session_id = "D62A523A65695219D46FE1FFE285A4C371425ACE421B110B5B8D11D3EB4D5F0B"; + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(session_id)); + EXPECT_CALL(*connection_, ssl()).WillRepeatedly(Return(connection_info)); + connection_cb_->onEvent(Network::ConnectionEvent::Connected); + EXPECT_NE(nullptr, stream_info_.downstreamSslConnection()); + EXPECT_EQ(session_id, stream_info_.downstreamSslConnection()->sessionId()); +} + // Test the codec getting input from a real TCP connection. class CodecNetworkTest : public testing::TestWithParam { public: diff --git a/test/common/http/codec_impl_corpus/clusterfuzz-testcase-codec_impl_fuzz_test-5687788200001536 b/test/common/http/codec_impl_corpus/clusterfuzz-testcase-codec_impl_fuzz_test-5687788200001536 new file mode 100644 index 0000000000..eee0fb76bd --- /dev/null +++ b/test/common/http/codec_impl_corpus/clusterfuzz-testcase-codec_impl_fuzz_test-5687788200001536 @@ -0,0 +1,11962 @@ +h2_settings { + client { + hpack_table_size: 35072 + initial_connection_window_size: 35072 + } + server { + hpack_table_size: 257 + initial_stream_window_size: 4294836216 + initial_connection_window_size: 4294835968 + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\000\000\000]" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\177H" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "YY" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\000\000\000]" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + request_headers { + headers { + value: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" + } + } + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "BB" + } + } + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "````````````````````````````````````````````````````````yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\000\225yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy````````````````````````````" + } + } + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 34 + value: 1545 + server: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "````````````````````````````````````````````````````````yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\000\225yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy````````````````````````````" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 34 + value: 1545 + server: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + quiesce_drain { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\000\000\000]" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + } + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + value: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\177\177\177" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "````````````````````````````````````````````````````````yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\000\225yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy````````````````````````````" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + request_headers { + headers { + key: "\177H" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\000\000\000]" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\000\000\000]" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "````````````````````````````````````````````````````````yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\000\225yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy````````````````````````````" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "````````````````````````````````````````````````````````yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\000\225yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy````````````````````````````" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + quiesce_drain { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\177H" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + server_drain { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "YY" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "YY" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + request_headers { + headers { + key: "\177H" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 34 + value: 1 + server: true + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 34 + value: 1545 + server: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "transfer-encodinG" + value: "YY" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 34 + value: 1545 + server: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + request_headers { + headers { + value: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + server_drain { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + end_stream: true + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: "\177\177\177" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + } + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { +} +actions { + new_stream { + request_headers { + headers { + value: "\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + new_stream { + } +} +actions { + mutate { + buffer: 1 + offset: 1 + value: 63 + } +} diff --git a/test/common/http/codec_impl_fuzz.proto b/test/common/http/codec_impl_fuzz.proto index 249e38eb64..f5d39f9ded 100644 --- a/test/common/http/codec_impl_fuzz.proto +++ b/test/common/http/codec_impl_fuzz.proto @@ -4,17 +4,19 @@ package test.common.http; import "google/protobuf/empty.proto"; +import "validate/validate.proto"; import "test/fuzz/common.proto"; // Structured input for H2 codec_impl_fuzz_test. message NewStream { - test.fuzz.Headers request_headers = 1; + test.fuzz.Headers request_headers = 1 [(validate.rules).message.required = true]; bool end_stream = 2; } message DirectionalAction { oneof directional_action_selector { + option (validate.required) = true; test.fuzz.Headers continue_headers = 1; test.fuzz.Headers headers = 2; uint32 data = 3; @@ -30,6 +32,7 @@ message StreamAction { // Index into list of created streams (not HTTP/2 level stream ID). uint32 stream_id = 1; oneof stream_action_selector { + option (validate.required) = true; DirectionalAction request = 2; DirectionalAction response = 3; } @@ -56,6 +59,7 @@ message SwapBufferAction { message Action { oneof action_selector { + option (validate.required) = true; // Create new stream. NewStream new_stream = 1; // Perform an action on an existing stream. diff --git a/test/common/http/codec_impl_fuzz_test.cc b/test/common/http/codec_impl_fuzz_test.cc index e25f56194e..f411565fa4 100644 --- a/test/common/http/codec_impl_fuzz_test.cc +++ b/test/common/http/codec_impl_fuzz_test.cc @@ -16,7 +16,7 @@ #include "common/http/http1/codec_impl.h" #include "common/http/http2/codec_impl.h" -#include "test/common/http/codec_impl_fuzz.pb.h" +#include "test/common/http/codec_impl_fuzz.pb.validate.h" #include "test/common/http/http2/codec_impl_test_util.h" #include "test/fuzz/fuzz_runner.h" #include "test/fuzz/utility.h" @@ -420,8 +420,10 @@ void codecFuzz(const test::common::http::CodecImplFuzzTestCase& input, HttpVersi } }; + constexpr auto max_actions = 1024; try { - for (const auto& action : input.actions()) { + for (int i = 0; i < std::min(max_actions, input.actions().size()); ++i) { + const auto& action = input.actions(i); ENVOY_LOG_MISC(trace, "action {} with {} streams", action.DebugString(), streams.size()); switch (action.action_selector_case()) { case test::common::http::Action::kNewStream: { @@ -502,8 +504,14 @@ void codecFuzz(const test::common::http::CodecImplFuzzTestCase& input, HttpVersi // Fuzz the H1/H2 codec implementations. DEFINE_PROTO_FUZZER(const test::common::http::CodecImplFuzzTestCase& input) { - codecFuzz(input, HttpVersion::Http1); - codecFuzz(input, HttpVersion::Http2); + try { + // Validate input early. + TestUtility::validate(input); + codecFuzz(input, HttpVersion::Http1); + codecFuzz(input, HttpVersion::Http2); + } catch (const EnvoyException& e) { + ENVOY_LOG_MISC(debug, "EnvoyException: {}", e.what()); + } } } // namespace Http diff --git a/test/common/http/codes_test.cc b/test/common/http/codes_test.cc index 1bdd1f3202..4f7766fc2d 100644 --- a/test/common/http/codes_test.cc +++ b/test/common/http/codes_test.cc @@ -8,6 +8,7 @@ #include "common/common/empty_string.h" #include "common/http/codes.h" #include "common/http/header_map_impl.h" +#include "common/stats/symbol_table_creator.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/printers.h" @@ -16,7 +17,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::Property; namespace Envoy { @@ -45,7 +45,7 @@ class CodeUtilityTest : public testing::Test { code_stats_.chargeResponseStat(info); } - Envoy::Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Stats::IsolatedStoreImpl global_store_; Stats::IsolatedStoreImpl cluster_scope_; Http::CodeStatsImpl code_stats_; @@ -276,13 +276,14 @@ TEST_F(CodeUtilityTest, ResponseTimingTest) { class CodeStatsTest : public testing::Test { protected: - CodeStatsTest() : code_stats_(symbol_table_) {} + CodeStatsTest() + : symbol_table_(Stats::SymbolTableCreator::makeSymbolTable()), code_stats_(*symbol_table_) {} absl::string_view stripTrailingDot(absl::string_view prefix) { return CodeStatsImpl::stripTrailingDot(prefix); } - Stats::FakeSymbolTableImpl symbol_table_; + Stats::SymbolTablePtr symbol_table_; CodeStatsImpl code_stats_; }; diff --git a/test/common/http/common.h b/test/common/http/common.h index 9aa57ef87c..d2c6f60747 100644 --- a/test/common/http/common.h +++ b/test/common/http/common.h @@ -39,8 +39,8 @@ class CodecClientForTest : public Http::CodecClient { * Mock callbacks used for conn pool testing. */ struct ConnPoolCallbacks : public Http::ConnectionPool::Callbacks { - void onPoolReady(Http::StreamEncoder& encoder, - Upstream::HostDescriptionConstSharedPtr host) override { + void onPoolReady(Http::StreamEncoder& encoder, Upstream::HostDescriptionConstSharedPtr host, + const StreamInfo::StreamInfo&) override { outer_encoder_ = &encoder; host_ = host; pool_ready_.ready(); diff --git a/test/common/http/conn_manager_impl_common.h b/test/common/http/conn_manager_impl_common.h index 1f68cc5941..f7b8134dbb 100644 --- a/test/common/http/conn_manager_impl_common.h +++ b/test/common/http/conn_manager_impl_common.h @@ -25,6 +25,7 @@ struct RouteConfigProvider : public Router::RouteConfigProvider { absl::optional configInfo() const override { return {}; } SystemTime lastUpdated() const override { return time_source_.systemTime(); } void onConfigUpdate() override {} + void validateConfig(const envoy::api::v2::RouteConfiguration&) const override {} TimeSource& time_source_; std::shared_ptr route_config_{new NiceMock()}; diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 5c2bf01fe1..3ddb49f3db 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -19,8 +19,8 @@ #include "common/http/exception.h" #include "common/network/address_impl.h" #include "common/network/utility.h" +#include "common/stats/symbol_table_creator.h" -#include "test/common/http/conn_manager_impl_common.h" #include "test/common/http/conn_manager_impl_fuzz.pb.h" #include "test/fuzz/fuzz_runner.h" #include "test/fuzz/utility.h" @@ -29,6 +29,7 @@ #include "test/mocks/http/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" +#include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/tracing/mocks.h" @@ -46,13 +47,15 @@ namespace Http { class FuzzConfig : public ConnectionManagerConfig { public: FuzzConfig() - : route_config_provider_(time_system_), scoped_route_config_provider_(time_system_), - stats_{{ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(fake_stats_), POOL_GAUGE(fake_stats_), + : stats_{{ALL_HTTP_CONN_MAN_STATS(POOL_COUNTER(fake_stats_), POOL_GAUGE(fake_stats_), POOL_HISTOGRAM(fake_stats_))}, "", fake_stats_}, tracing_stats_{CONN_MAN_TRACING_STATS(POOL_COUNTER(fake_stats_))}, listener_stats_{CONN_MAN_LISTENER_STATS(POOL_COUNTER(fake_stats_))} { + ON_CALL(route_config_provider_, lastUpdated()).WillByDefault(Return(time_system_.systemTime())); + ON_CALL(scoped_route_config_provider_, lastUpdated()) + .WillByDefault(Return(time_system_.systemTime())); access_logs_.emplace_back(std::make_shared>()); } @@ -85,11 +88,22 @@ class FuzzConfig : public ConnectionManagerConfig { std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } - Router::RouteConfigProvider* routeConfigProvider() override { return &route_config_provider_; } + Router::RouteConfigProvider* routeConfigProvider() override { + if (use_srds_) { + return nullptr; + } + return &route_config_provider_; + } Config::ConfigProvider* scopedRouteConfigProvider() override { - return &scoped_route_config_provider_; + if (use_srds_) { + return &scoped_route_config_provider_; + } + return nullptr; } const std::string& serverName() override { return server_name_; } + HttpConnectionManagerProto::ServerHeaderTransformation serverHeaderTransformation() override { + return server_transformation_; + } ConnectionManagerStats& stats() override { return stats_; } ConnectionManagerTracingStats& tracingStats() override { return tracing_stats_; } bool useRemoteAddress() override { return use_remote_address_; } @@ -120,9 +134,12 @@ class FuzzConfig : public ConnectionManagerConfig { NiceMock filter_factory_; Event::SimulatedTimeSystem time_system_; SlowDateProviderImpl date_provider_{time_system_}; - ConnectionManagerImplHelper::RouteConfigProvider route_config_provider_; - ConnectionManagerImplHelper::ScopedRouteConfigProvider scoped_route_config_provider_; + bool use_srds_{}; + Router::MockRouteConfigProvider route_config_provider_; + Router::MockScopedRouteConfigProvider scoped_route_config_provider_; std::string server_name_; + HttpConnectionManagerProto::ServerHeaderTransformation server_transformation_{ + HttpConnectionManagerProto::OVERWRITE}; Stats::IsolatedStoreImpl fake_stats_; ConnectionManagerStats stats_; ConnectionManagerTracingStats tracing_stats_; @@ -382,17 +399,17 @@ DEFINE_PROTO_FUZZER(const test::common::http::ConnManagerImplTestCase& input) { FuzzConfig config; NiceMock drain_close; NiceMock random; - Stats::FakeSymbolTableImpl symbol_table; - Http::ContextImpl http_context(symbol_table); + Stats::SymbolTablePtr symbol_table(Stats::SymbolTableCreator::makeSymbolTable()); + Http::ContextImpl http_context(*symbol_table); NiceMock runtime; NiceMock local_info; NiceMock cluster_manager; NiceMock filter_callbacks; - std::unique_ptr ssl_connection; + auto ssl_connection = std::make_shared(); bool connection_alive = true; - ON_CALL(filter_callbacks.connection_, ssl()).WillByDefault(Return(ssl_connection.get())); - ON_CALL(Const(filter_callbacks.connection_), ssl()).WillByDefault(Return(ssl_connection.get())); + ON_CALL(filter_callbacks.connection_, ssl()).WillByDefault(Return(ssl_connection)); + ON_CALL(Const(filter_callbacks.connection_), ssl()).WillByDefault(Return(ssl_connection)); ON_CALL(filter_callbacks.connection_, close(_)) .WillByDefault(InvokeWithoutArgs([&connection_alive] { connection_alive = false; })); filter_callbacks.connection_.local_address_ = diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 083bbbe737..00d80d4c6d 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -26,13 +26,13 @@ #include "extensions/access_loggers/file/file_access_log_impl.h" -#include "test/common/http/conn_manager_impl_common.h" #include "test/mocks/access_log/mocks.h" #include "test/mocks/buffer/mocks.h" #include "test/mocks/common.h" #include "test/mocks/http/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" +#include "test/mocks/router/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/mocks.h" #include "test/mocks/ssl/mocks.h" @@ -50,7 +50,6 @@ using testing::_; using testing::An; using testing::AnyNumber; using testing::AtLeast; -using testing::DoAll; using testing::Eq; using testing::HasSubstr; using testing::InSequence; @@ -61,7 +60,6 @@ using testing::NiceMock; using testing::Ref; using testing::Return; using testing::ReturnRef; -using testing::Sequence; namespace Envoy { namespace Http { @@ -69,9 +67,7 @@ namespace Http { class HttpConnectionManagerImplTest : public testing::Test, public ConnectionManagerConfig { public: HttpConnectionManagerImplTest() - : route_config_provider_(test_time_.timeSystem()), - scoped_route_config_provider_(test_time_.timeSystem()), - http_context_(fake_stats_.symbolTable()), access_log_path_("dummy_path"), + : http_context_(fake_stats_.symbolTable()), access_log_path_("dummy_path"), access_logs_{ AccessLog::InstanceSharedPtr{new Extensions::AccessLoggers::File::FileAccessLog( access_log_path_, {}, AccessLog::AccessLogFormatUtils::defaultAccessLogFormatter(), @@ -86,6 +82,10 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan http_context_.setTracer(tracer_); + ON_CALL(route_config_provider_, lastUpdated()) + .WillByDefault(Return(test_time_.timeSystem().systemTime())); + ON_CALL(scoped_route_config_provider_, lastUpdated()) + .WillByDefault(Return(test_time_.timeSystem().systemTime())); // response_encoder_ is not a NiceMock on purpose. This prevents complaining about this // method only. EXPECT_CALL(response_encoder_, getStream()).Times(AtLeast(0)); @@ -95,15 +95,15 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); } - void setup(bool ssl, const std::string& server_name, bool tracing = true) { + void setup(bool ssl, const std::string& server_name, bool tracing = true, bool use_srds = false) { + use_srds_ = use_srds; if (ssl) { - ssl_connection_ = std::make_unique(); + ssl_connection_ = std::make_shared(); } server_name_ = server_name; - ON_CALL(filter_callbacks_.connection_, ssl()).WillByDefault(Return(ssl_connection_.get())); - ON_CALL(Const(filter_callbacks_.connection_), ssl()) - .WillByDefault(Return(ssl_connection_.get())); + ON_CALL(filter_callbacks_.connection_, ssl()).WillByDefault(Return(ssl_connection_)); + ON_CALL(Const(filter_callbacks_.connection_), ssl()).WillByDefault(Return(ssl_connection_)); filter_callbacks_.connection_.local_address_ = std::make_shared("127.0.0.1"); filter_callbacks_.connection_.remote_address_ = @@ -125,7 +125,8 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan percent1, percent2, percent1, - false}); + false, + 256}); } } @@ -227,6 +228,21 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan conn_manager_->onData(fake_input, false); } + HeaderMap* sendResponseHeaders(HeaderMapPtr&& response_headers) { + HeaderMap* altered_response_headers = nullptr; + + EXPECT_CALL(*encoder_filters_[0], encodeHeaders(_, _)) + .WillOnce(Invoke([&](HeaderMap& headers, bool) -> FilterHeadersStatus { + altered_response_headers = &headers; + return FilterHeadersStatus::Continue; + })); + EXPECT_CALL(*encoder_filters_[1], encodeHeaders(_, false)) + .WillOnce(Return(FilterHeadersStatus::Continue)); + EXPECT_CALL(response_encoder_, encodeHeaders(_, false)); + decoder_filters_[0]->callbacks_->encodeHeaders(std::move(response_headers), false); + return altered_response_headers; + } + void expectOnDestroy() { for (auto filter : decoder_filters_) { EXPECT_CALL(*filter, onDestroy()); @@ -256,11 +272,23 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan std::chrono::milliseconds streamIdleTimeout() const override { return stream_idle_timeout_; } std::chrono::milliseconds requestTimeout() const override { return request_timeout_; } std::chrono::milliseconds delayedCloseTimeout() const override { return delayed_close_timeout_; } - Router::RouteConfigProvider* routeConfigProvider() override { return &route_config_provider_; } + bool use_srds_{}; + Router::RouteConfigProvider* routeConfigProvider() override { + if (use_srds_) { + return nullptr; + } + return &route_config_provider_; + } Config::ConfigProvider* scopedRouteConfigProvider() override { - return &scoped_route_config_provider_; + if (use_srds_) { + return &scoped_route_config_provider_; + } + return nullptr; } const std::string& serverName() override { return server_name_; } + HttpConnectionManagerProto::ServerHeaderTransformation serverHeaderTransformation() override { + return server_transformation_; + } ConnectionManagerStats& stats() override { return stats_; } ConnectionManagerTracingStats& tracingStats() override { return tracing_stats_; } bool useRemoteAddress() override { return use_remote_address_; } @@ -284,8 +312,9 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan bool shouldMergeSlashes() const override { return merge_slashes_; } DangerousDeprecatedTestTime test_time_; - ConnectionManagerImplHelper::RouteConfigProvider route_config_provider_; - ConnectionManagerImplHelper::ScopedRouteConfigProvider scoped_route_config_provider_; + NiceMock route_config_provider_; + std::shared_ptr route_config_{new NiceMock()}; + NiceMock scoped_route_config_provider_; NiceMock tracer_; Stats::IsolatedStoreImpl fake_stats_; Http::ContextImpl http_context_; @@ -301,6 +330,8 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan NiceMock drain_close_; std::unique_ptr conn_manager_; std::string server_name_; + HttpConnectionManagerProto::ServerHeaderTransformation server_transformation_{ + HttpConnectionManagerProto::OVERWRITE}; Network::Address::Ipv4Instance local_address_{"127.0.0.1"}; bool use_remote_address_{true}; Http::DefaultInternalAddressConfig internal_address_config_; @@ -315,7 +346,7 @@ class HttpConnectionManagerImplTest : public testing::Test, public ConnectionMan NiceMock random_; NiceMock local_info_; NiceMock factory_context_; - std::unique_ptr ssl_connection_; + std::shared_ptr ssl_connection_; TracingConnectionManagerConfigPtr tracing_config_; SlowDateProviderImpl date_provider_{test_time_.timeSystem()}; MockStream stream_; @@ -537,6 +568,65 @@ TEST_F(HttpConnectionManagerImplTest, PauseResume100Continue) { decoder_filters_[1]->callbacks_->encodeHeaders(std::move(response_headers), false); } +// By default, Envoy will set the server header to the server name, here "custom-value" +TEST_F(HttpConnectionManagerImplTest, ServerHeaderOverwritten) { + setup(false, "custom-value", false); + setUpEncoderAndDecoder(false, false); + + sendRequestHeadersAndData(); + const HeaderMap* altered_headers = sendResponseHeaders( + HeaderMapPtr{new TestHeaderMapImpl{{":status", "200"}, {"server", "foo"}}}); + EXPECT_EQ("custom-value", altered_headers->Server()->value().getStringView()); +} + +// When configured APPEND_IF_ABSENT if the server header is present it will be retained. +TEST_F(HttpConnectionManagerImplTest, ServerHeaderAppendPresent) { + server_transformation_ = HttpConnectionManagerProto::APPEND_IF_ABSENT; + setup(false, "custom-value", false); + setUpEncoderAndDecoder(false, false); + + sendRequestHeadersAndData(); + const HeaderMap* altered_headers = sendResponseHeaders( + HeaderMapPtr{new TestHeaderMapImpl{{":status", "200"}, {"server", "foo"}}}); + EXPECT_EQ("foo", altered_headers->Server()->value().getStringView()); +} + +// When configured APPEND_IF_ABSENT if the server header is absent the server name will be set. +TEST_F(HttpConnectionManagerImplTest, ServerHeaderAppendAbsent) { + server_transformation_ = HttpConnectionManagerProto::APPEND_IF_ABSENT; + setup(false, "custom-value", false); + setUpEncoderAndDecoder(false, false); + + sendRequestHeadersAndData(); + const HeaderMap* altered_headers = + sendResponseHeaders(HeaderMapPtr{new TestHeaderMapImpl{{":status", "200"}}}); + EXPECT_EQ("custom-value", altered_headers->Server()->value().getStringView()); +} + +// When configured PASS_THROUGH, the server name will pass through. +TEST_F(HttpConnectionManagerImplTest, ServerHeaderPassthroughPresent) { + server_transformation_ = HttpConnectionManagerProto::PASS_THROUGH; + setup(false, "custom-value", false); + setUpEncoderAndDecoder(false, false); + + sendRequestHeadersAndData(); + const HeaderMap* altered_headers = sendResponseHeaders( + HeaderMapPtr{new TestHeaderMapImpl{{":status", "200"}, {"server", "foo"}}}); + EXPECT_EQ("foo", altered_headers->Server()->value().getStringView()); +} + +// When configured PASS_THROUGH, the server header will not be added if absent. +TEST_F(HttpConnectionManagerImplTest, ServerHeaderPassthroughAbsent) { + server_transformation_ = HttpConnectionManagerProto::PASS_THROUGH; + setup(false, "custom-value", false); + setUpEncoderAndDecoder(false, false); + + sendRequestHeadersAndData(); + const HeaderMap* altered_headers = + sendResponseHeaders(HeaderMapPtr{new TestHeaderMapImpl{{":status", "200"}}}); + EXPECT_TRUE(altered_headers->Server() == nullptr); +} + TEST_F(HttpConnectionManagerImplTest, InvalidPathWithDualFilter) { InSequence s; setup(false, ""); @@ -892,7 +982,8 @@ TEST_F(HttpConnectionManagerImplTest, StartAndFinishSpanNormalFlowEgressDecorato percent1, percent2, percent1, - false}); + false, + 256}); auto* span = new NiceMock(); EXPECT_CALL(tracer_, startSpan_(_, _, _, _)) @@ -970,7 +1061,8 @@ TEST_F(HttpConnectionManagerImplTest, StartAndFinishSpanNormalFlowEgressDecorato percent1, percent2, percent1, - false}); + false, + 256}); auto* span = new NiceMock(); EXPECT_CALL(tracer_, startSpan_(_, _, _, _)) @@ -1043,7 +1135,8 @@ TEST_F(HttpConnectionManagerImplTest, percent1, percent2, percent1, - false}); + false, + 256}); EXPECT_CALL(runtime_.snapshot_, featureEnabled("tracing.global_enabled", An(), _)) @@ -1403,12 +1496,12 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutGlobal) { EXPECT_CALL(*codec_, dispatch(_)).WillRepeatedly(Invoke([&](Buffer::Instance&) -> void { Event::MockTimer* idle_timer = setUpTimer(); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10), _)); conn_manager_->newStream(response_encoder_); // Expect resetIdleTimer() to be called for the response // encodeHeaders()/encodeData(). - EXPECT_CALL(*idle_timer, enableTimer(_)).Times(2); + EXPECT_CALL(*idle_timer, enableTimer(_, _)).Times(2); EXPECT_CALL(*idle_timer, disableTimer()); idle_timer->invokeCallback(); })); @@ -1441,12 +1534,12 @@ TEST_F(HttpConnectionManagerImplTest, AccessEncoderRouteBeforeHeadersArriveOnIdl EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { Event::MockTimer* idle_timer = setUpTimer(); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10), _)); conn_manager_->newStream(response_encoder_); // Expect resetIdleTimer() to be called for the response // encodeHeaders()/encodeData(). - EXPECT_CALL(*idle_timer, enableTimer(_)).Times(2); + EXPECT_CALL(*idle_timer, enableTimer(_, _)).Times(2); EXPECT_CALL(*idle_timer, disableTimer()); // Simulate and idle timeout so that the filter chain gets created. idle_timer->invokeCallback(); @@ -1482,12 +1575,12 @@ TEST_F(HttpConnectionManagerImplTest, TestStreamIdleAccessLog) { NiceMock encoder; EXPECT_CALL(*codec_, dispatch(_)).WillRepeatedly(Invoke([&](Buffer::Instance&) -> void { Event::MockTimer* idle_timer = setUpTimer(); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10), _)); conn_manager_->newStream(response_encoder_); // Expect resetIdleTimer() to be called for the response // encodeHeaders()/encodeData(). - EXPECT_CALL(*idle_timer, enableTimer(_)).Times(2); + EXPECT_CALL(*idle_timer, enableTimer(_, _)).Times(2); EXPECT_CALL(*idle_timer, disableTimer()); idle_timer->invokeCallback(); })); @@ -1534,12 +1627,12 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutRouteOverride) { EXPECT_CALL(*codec_, dispatch(_)).WillRepeatedly(Invoke([&](Buffer::Instance& data) -> void { Event::MockTimer* idle_timer = setUpTimer(); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10), _)); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(30))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(30), _)); decoder->decodeHeaders(std::move(headers), false); data.drain(4); @@ -1560,7 +1653,7 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutRouteZeroOverride) { EXPECT_CALL(*codec_, dispatch(_)).WillRepeatedly(Invoke([&](Buffer::Instance& data) -> void { Event::MockTimer* idle_timer = setUpTimer(); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(10), _)); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ @@ -1590,12 +1683,12 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders Event::MockTimer* idle_timer = setUpTimer(); HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); decoder->decodeHeaders(std::move(headers), false); // Expect resetIdleTimer() to be called for the response // encodeHeaders()/encodeData(). - EXPECT_CALL(*idle_timer, enableTimer(_)).Times(2); + EXPECT_CALL(*idle_timer, enableTimer(_, _)).Times(2); EXPECT_CALL(*idle_timer, disableTimer()); idle_timer->invokeCallback(); @@ -1630,7 +1723,7 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutNormalTermination) { HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); decoder->decodeHeaders(std::move(headers), false); data.drain(4); @@ -1659,15 +1752,15 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterDownstreamHeaders Event::MockTimer* idle_timer = setUpTimer(); HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); decoder->decodeHeaders(std::move(headers), false); - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); decoder->decodeData(data, false); // Expect resetIdleTimer() to be called for the response // encodeHeaders()/encodeData(). - EXPECT_CALL(*idle_timer, enableTimer(_)).Times(2); + EXPECT_CALL(*idle_timer, enableTimer(_, _)).Times(2); EXPECT_CALL(*idle_timer, disableTimer()); idle_timer->invokeCallback(); @@ -1712,11 +1805,11 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterUpstreamHeaders) Event::MockTimer* idle_timer = setUpTimer(); HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); decoder->decodeHeaders(std::move(headers), false); HeaderMapPtr response_headers{new TestHeaderMapImpl{{":status", "200"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); filter->callbacks_->encodeHeaders(std::move(response_headers), false); EXPECT_CALL(*idle_timer, disableTimer()); @@ -1761,26 +1854,26 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterBidiData) { decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); decoder->decodeHeaders(std::move(headers), false); HeaderMapPtr response_continue_headers{new TestHeaderMapImpl{{":status", "100"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); filter->callbacks_->encode100ContinueHeaders(std::move(response_continue_headers)); HeaderMapPtr response_headers{new TestHeaderMapImpl{{":status", "200"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); filter->callbacks_->encodeHeaders(std::move(response_headers), false); - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); decoder->decodeData(data, false); HeaderMapPtr trailers{new TestHeaderMapImpl{{"foo", "bar"}}}; - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); decoder->decodeTrailers(std::move(trailers)); Buffer::OwnedImpl fake_response("world"); - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); filter->callbacks_->encodeData(fake_response, false); EXPECT_CALL(*idle_timer, disableTimer()); @@ -1811,7 +1904,7 @@ TEST_F(HttpConnectionManagerImplTest, PerStreamIdleTimeoutAfterBidiData) { TEST_F(HttpConnectionManagerImplTest, RequestTimeoutDisabledByDefault) { setup(false, ""); - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, createTimer_).Times(0); conn_manager_->newStream(response_encoder_); })); @@ -1824,7 +1917,7 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutDisabledIfSetToZero) { request_timeout_ = std::chrono::milliseconds(0); setup(false, ""); - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, createTimer_).Times(0); conn_manager_->newStream(response_encoder_); })); @@ -1837,9 +1930,9 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutValidlyConfigured) { request_timeout_ = std::chrono::milliseconds(10); setup(false, ""); - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { Event::MockTimer* request_timer = setUpTimer(); - EXPECT_CALL(*request_timer, enableTimer(request_timeout_)); + EXPECT_CALL(*request_timer, enableTimer(request_timeout_, _)); conn_manager_->newStream(response_encoder_); })); @@ -1853,9 +1946,9 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutCallbackDisarmsAndReturns408 setup(false, ""); std::string response_body; - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { Event::MockTimer* request_timer = setUpTimer(); - EXPECT_CALL(*request_timer, enableTimer(request_timeout_)).Times(1); + EXPECT_CALL(*request_timer, enableTimer(request_timeout_, _)).Times(1); EXPECT_CALL(*request_timer, disableTimer()).Times(AtLeast(1)); EXPECT_CALL(response_encoder_, encodeHeaders(_, false)) @@ -1865,6 +1958,7 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutCallbackDisarmsAndReturns408 EXPECT_CALL(response_encoder_, encodeData(_, true)).WillOnce(AddBufferToString(&response_body)); conn_manager_->newStream(response_encoder_); + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, setTrackedObject(_)).Times(2); request_timer->invokeCallback(); })); @@ -1879,9 +1973,9 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsNotDisarmedOnIncompleteReq request_timeout_ = std::chrono::milliseconds(10); setup(false, ""); - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { Event::MockTimer* request_timer = setUpTimer(); - EXPECT_CALL(*request_timer, enableTimer(request_timeout_)).Times(1); + EXPECT_CALL(*request_timer, enableTimer(request_timeout_, _)).Times(1); EXPECT_CALL(*request_timer, disableTimer()).Times(0); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); @@ -1902,9 +1996,9 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestW request_timeout_ = std::chrono::milliseconds(10); setup(false, ""); - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { Event::MockTimer* request_timer = setUpTimer(); - EXPECT_CALL(*request_timer, enableTimer(request_timeout_)).Times(1); + EXPECT_CALL(*request_timer, enableTimer(request_timeout_, _)).Times(1); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ @@ -1926,7 +2020,7 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestW EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { Event::MockTimer* request_timer = setUpTimer(); - EXPECT_CALL(*request_timer, enableTimer(request_timeout_)).Times(1); + EXPECT_CALL(*request_timer, enableTimer(request_timeout_, _)).Times(1); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ @@ -1949,7 +2043,7 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnCompleteRequestW EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { Event::MockTimer* request_timer = setUpTimer(); - EXPECT_CALL(*request_timer, enableTimer(request_timeout_)).Times(1); + EXPECT_CALL(*request_timer, enableTimer(request_timeout_, _)).Times(1); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ @@ -1978,9 +2072,9 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnEncodeHeaders) { })); EXPECT_CALL(response_encoder_, encodeHeaders(_, _)); - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { Event::MockTimer* request_timer = setUpTimer(); - EXPECT_CALL(*request_timer, enableTimer(request_timeout_)).Times(1); + EXPECT_CALL(*request_timer, enableTimer(request_timeout_, _)).Times(1); StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ @@ -2004,7 +2098,7 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnConnectionTermin setup(false, ""); Event::MockTimer* request_timer = setUpTimer(); - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; @@ -2014,7 +2108,7 @@ TEST_F(HttpConnectionManagerImplTest, RequestTimeoutIsDisarmedOnConnectionTermin Buffer::OwnedImpl fake_input("1234"); - EXPECT_CALL(*request_timer, enableTimer(request_timeout_)).Times(1); + EXPECT_CALL(*request_timer, enableTimer(request_timeout_, _)).Times(1); conn_manager_->onData(fake_input, false); // kick off request EXPECT_CALL(*request_timer, disableTimer()).Times(1); @@ -2138,11 +2232,11 @@ TEST_F(HttpConnectionManagerImplTest, DrainClose) { HeaderMapPtr response_headers{new TestHeaderMapImpl{{":status", "300"}}}; Event::MockTimer* drain_timer = setUpTimer(); - EXPECT_CALL(*drain_timer, enableTimer(_)); + EXPECT_CALL(*drain_timer, enableTimer(_, _)); EXPECT_CALL(drain_close_, drainClose()).WillOnce(Return(true)); EXPECT_CALL(*codec_, shutdownNotice()); filter->callbacks_->encodeHeaders(std::move(response_headers), true); - EXPECT_EQ(ssl_connection_.get(), filter->callbacks_->connection()->ssl()); + EXPECT_EQ(ssl_connection_.get(), filter->callbacks_->connection()->ssl().get()); EXPECT_CALL(*codec_, goAway()); EXPECT_CALL(filter_callbacks_.connection_, @@ -2376,7 +2470,7 @@ TEST_F(HttpConnectionManagerImplTest, IdleTimeoutNoCodec) { idle_timeout_ = (std::chrono::milliseconds(10)); Event::MockTimer* idle_timer = setUpTimer(); - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); setup(false, ""); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); @@ -2389,7 +2483,7 @@ TEST_F(HttpConnectionManagerImplTest, IdleTimeoutNoCodec) { TEST_F(HttpConnectionManagerImplTest, IdleTimeout) { idle_timeout_ = (std::chrono::milliseconds(10)); Event::MockTimer* idle_timer = setUpTimer(); - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); setup(false, ""); MockStreamDecoderFilter* filter = new NiceMock(); @@ -2420,12 +2514,12 @@ TEST_F(HttpConnectionManagerImplTest, IdleTimeout) { Buffer::OwnedImpl fake_input("1234"); conn_manager_->onData(fake_input, false); - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); HeaderMapPtr response_headers{new TestHeaderMapImpl{{":status", "200"}}}; filter->callbacks_->encodeHeaders(std::move(response_headers), true); Event::MockTimer* drain_timer = setUpTimer(); - EXPECT_CALL(*drain_timer, enableTimer(_)); + EXPECT_CALL(*drain_timer, enableTimer(_, _)); idle_timer->invokeCallback(); EXPECT_CALL(*codec_, goAway()); @@ -4044,7 +4138,8 @@ TEST_F(HttpConnectionManagerImplTest, MultipleFilters) { .WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus { EXPECT_EQ(route_config_provider_.route_config_->route_, decoder_filters_[0]->callbacks_->route()); - EXPECT_EQ(ssl_connection_.get(), decoder_filters_[0]->callbacks_->connection()->ssl()); + EXPECT_EQ(ssl_connection_.get(), + decoder_filters_[0]->callbacks_->connection()->ssl().get()); return FilterHeadersStatus::StopIteration; })); @@ -4064,7 +4159,8 @@ TEST_F(HttpConnectionManagerImplTest, MultipleFilters) { .WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus { EXPECT_EQ(route_config_provider_.route_config_->route_, decoder_filters_[1]->callbacks_->route()); - EXPECT_EQ(ssl_connection_.get(), decoder_filters_[1]->callbacks_->connection()->ssl()); + EXPECT_EQ(ssl_connection_.get(), + decoder_filters_[1]->callbacks_->connection()->ssl().get()); return FilterHeadersStatus::StopIteration; })); EXPECT_CALL(*decoder_filters_[1], decodeData(_, true)) @@ -4085,14 +4181,14 @@ TEST_F(HttpConnectionManagerImplTest, MultipleFilters) { EXPECT_CALL(*encoder_filters_[1], encodeTrailers(_)) .WillOnce(Return(FilterTrailersStatus::StopIteration)); EXPECT_CALL(*encoder_filters_[1], encodeComplete()); - EXPECT_EQ(ssl_connection_.get(), encoder_filters_[1]->callbacks_->connection()->ssl()); + EXPECT_EQ(ssl_connection_.get(), encoder_filters_[1]->callbacks_->connection()->ssl().get()); decoder_filters_[2]->callbacks_->encodeHeaders( HeaderMapPtr{new TestHeaderMapImpl{{":status", "200"}}}, false); Buffer::OwnedImpl response_body("response"); decoder_filters_[2]->callbacks_->encodeData(response_body, false); decoder_filters_[2]->callbacks_->encodeTrailers( HeaderMapPtr{new TestHeaderMapImpl{{"some", "trailer"}}}); - EXPECT_EQ(ssl_connection_.get(), decoder_filters_[2]->callbacks_->connection()->ssl()); + EXPECT_EQ(ssl_connection_.get(), decoder_filters_[2]->callbacks_->connection()->ssl().get()); // Now finish the encode. EXPECT_CALL(*encoder_filters_[0], encodeHeaders(_, false)) @@ -4108,7 +4204,7 @@ TEST_F(HttpConnectionManagerImplTest, MultipleFilters) { expectOnDestroy(); encoder_filters_[1]->callbacks_->continueEncoding(); - EXPECT_EQ(ssl_connection_.get(), encoder_filters_[0]->callbacks_->connection()->ssl()); + EXPECT_EQ(ssl_connection_.get(), encoder_filters_[0]->callbacks_->connection()->ssl().get()); } TEST(HttpConnectionManagerTracingStatsTest, verifyTracingStats) { @@ -4194,7 +4290,7 @@ TEST_F(HttpConnectionManagerImplTest, OverlyLongHeadersRejected) { std::string response_code; std::string response_body; - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; @@ -4219,7 +4315,7 @@ TEST_F(HttpConnectionManagerImplTest, OverlyLongHeadersAcceptedIfConfigured) { max_request_headers_kb_ = 62; setup(false, ""); - EXPECT_CALL(*codec_, dispatch(_)).Times(1).WillOnce(Invoke([&](Buffer::Instance&) -> void { + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); HeaderMapPtr headers{ new TestHeaderMapImpl{{":authority", "host"}, {":path", "/"}, {":method", "GET"}}}; @@ -4413,5 +4509,231 @@ TEST_F(HttpConnectionManagerImplTest, TestSessionTrace) { } } +// SRDS no scope found. +TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteNotFound) { + setup(false, "", true, true); + setupFilterChain(1, 0); // Recreate the chain for second stream. + + EXPECT_CALL(*static_cast( + scopedRouteConfigProvider()->config().get()), + getRouteConfig(_)) + .Times(2) + .WillRepeatedly(Return(nullptr)); + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { + StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":method", "GET"}, {":path", "/foo"}}}; + decoder->decodeHeaders(std::move(headers), true); + data.drain(4); + })); + + EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true)) + .WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus { + EXPECT_EQ(nullptr, decoder_filters_[0]->callbacks_->route()); + return FilterHeadersStatus::StopIteration; + })); + EXPECT_CALL(*decoder_filters_[0], decodeComplete()); // end_stream=true. + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + +// SRDS updating scopes affects routing. +TEST_F(HttpConnectionManagerImplTest, TestSrdsUpdate) { + setup(false, "", true, true); + + EXPECT_CALL(*static_cast( + scopedRouteConfigProvider()->config().get()), + getRouteConfig(_)) + .Times(3) + .WillOnce(Return(nullptr)) + .WillOnce(Return(nullptr)) // refreshCachedRoute first time. + .WillOnce(Return(route_config_)); // triggered by callbacks_->route(), SRDS now updated. + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { + StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":method", "GET"}, {":path", "/foo"}}}; + decoder->decodeHeaders(std::move(headers), true); + data.drain(4); + })); + const std::string fake_cluster1_name = "fake_cluster1"; + std::shared_ptr route1 = std::make_shared>(); + EXPECT_CALL(route1->route_entry_, clusterName()).WillRepeatedly(ReturnRef(fake_cluster1_name)); + std::shared_ptr fake_cluster1 = + std::make_shared>(); + EXPECT_CALL(cluster_manager_, get(_)).WillOnce(Return(fake_cluster1.get())); + EXPECT_CALL(*route_config_, route(_, _)).WillOnce(Return(route1)); + // First no-scope-found request will be handled by decoder_filters_[0]. + setupFilterChain(1, 0); + EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true)) + .WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus { + EXPECT_EQ(nullptr, decoder_filters_[0]->callbacks_->route()); + + // Clear route and next call on callbacks_->route() will trigger a re-snapping of the + // snapped_route_config_. + decoder_filters_[0]->callbacks_->clearRouteCache(); + + // Now route config provider returns something. + EXPECT_EQ(route1, decoder_filters_[0]->callbacks_->route()); + EXPECT_EQ(route1->routeEntry(), decoder_filters_[0]->callbacks_->streamInfo().routeEntry()); + EXPECT_EQ(fake_cluster1->info(), decoder_filters_[0]->callbacks_->clusterInfo()); + return FilterHeadersStatus::StopIteration; + + return FilterHeadersStatus::StopIteration; + })); + EXPECT_CALL(*decoder_filters_[0], decodeComplete()); // end_stream=true. + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + +// SRDS Scope header update cause cross-scope reroute. +TEST_F(HttpConnectionManagerImplTest, TestSrdsCrossScopeReroute) { + setup(false, "", true, true); + + std::shared_ptr route_config1 = + std::make_shared>(); + std::shared_ptr route_config2 = + std::make_shared>(); + std::shared_ptr route1 = std::make_shared>(); + std::shared_ptr route2 = std::make_shared>(); + EXPECT_CALL(*route_config1, route(_, _)).WillRepeatedly(Return(route1)); + EXPECT_CALL(*route_config2, route(_, _)).WillRepeatedly(Return(route2)); + EXPECT_CALL(*static_cast( + scopedRouteConfigProvider()->config().get()), + getRouteConfig(_)) + // 1. Snap scoped route config; + // 2. refreshCachedRoute (both in decodeHeaders(headers,end_stream); + // 3. then refreshCachedRoute triggered by decoder_filters_[1]->callbacks_->route(). + .Times(3) + .WillRepeatedly(Invoke([&](const HeaderMap& headers) -> Router::ConfigConstSharedPtr { + auto& test_headers = static_cast(headers); + if (test_headers.get_("scope_key") == "foo") { + return route_config1; + } + return route_config2; + })); + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { + StreamDecoder* decoder = &conn_manager_->newStream(response_encoder_); + HeaderMapPtr headers{new TestHeaderMapImpl{ + {":authority", "host"}, {":method", "GET"}, {"scope_key", "foo"}, {":path", "/foo"}}}; + decoder->decodeHeaders(std::move(headers), false); + data.drain(4); + })); + setupFilterChain(2, 0); + EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, false)) + .WillOnce(Invoke([&](Http::HeaderMap& headers, bool) -> FilterHeadersStatus { + EXPECT_EQ(route1, decoder_filters_[0]->callbacks_->route()); + auto& test_headers = static_cast(headers); + // Clear cached route and change scope key to "bar". + decoder_filters_[0]->callbacks_->clearRouteCache(); + test_headers.remove("scope_key"); + test_headers.addCopy("scope_key", "bar"); + return FilterHeadersStatus::Continue; + })); + EXPECT_CALL(*decoder_filters_[1], decodeHeaders(_, false)) + .WillOnce(Invoke([&](Http::HeaderMap& headers, bool) -> FilterHeadersStatus { + auto& test_headers = static_cast(headers); + EXPECT_EQ(test_headers.get_("scope_key"), "bar"); + // Route now switched to route2 as header "scope_key" has changed. + EXPECT_EQ(route2, decoder_filters_[1]->callbacks_->route()); + EXPECT_EQ(route2->routeEntry(), decoder_filters_[1]->callbacks_->streamInfo().routeEntry()); + return FilterHeadersStatus::StopIteration; + })); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + +// SRDS scoped RouteConfiguration found and route found. +TEST_F(HttpConnectionManagerImplTest, TestSrdsRouteFound) { + setup(false, "", true, true); + setupFilterChain(1, 0); + + const std::string fake_cluster1_name = "fake_cluster1"; + std::shared_ptr route1 = std::make_shared>(); + EXPECT_CALL(route1->route_entry_, clusterName()).WillRepeatedly(ReturnRef(fake_cluster1_name)); + std::shared_ptr fake_cluster1 = + std::make_shared>(); + EXPECT_CALL(cluster_manager_, get(_)).WillOnce(Return(fake_cluster1.get())); + EXPECT_CALL(*scopedRouteConfigProvider()->config(), getRouteConfig(_)) + // 1. decodeHeaders() snaping route config. + // 2. refreshCachedRoute() later in the same decodeHeaders(). + .Times(2); + EXPECT_CALL( + *static_cast( + scopedRouteConfigProvider()->config()->route_config_.get()), + route(_, _)) + .WillOnce(Return(route1)); + StreamDecoder* decoder = nullptr; + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> void { + decoder = &conn_manager_->newStream(response_encoder_); + HeaderMapPtr headers{ + new TestHeaderMapImpl{{":authority", "host"}, {":method", "GET"}, {":path", "/foo"}}}; + decoder->decodeHeaders(std::move(headers), true); + data.drain(4); + })); + EXPECT_CALL(*decoder_filters_[0], decodeHeaders(_, true)) + .WillOnce(InvokeWithoutArgs([&]() -> FilterHeadersStatus { + EXPECT_EQ(route1, decoder_filters_[0]->callbacks_->route()); + EXPECT_EQ(route1->routeEntry(), decoder_filters_[0]->callbacks_->streamInfo().routeEntry()); + EXPECT_EQ(fake_cluster1->info(), decoder_filters_[0]->callbacks_->clusterInfo()); + return FilterHeadersStatus::StopIteration; + })); + EXPECT_CALL(*decoder_filters_[0], decodeComplete()); + + Buffer::OwnedImpl fake_input("1234"); + conn_manager_->onData(fake_input, false); +} + +class HttpConnectionManagerImplDeathTest : public HttpConnectionManagerImplTest { +public: + Router::RouteConfigProvider* routeConfigProvider() override { + return route_config_provider2_.get(); + } + Config::ConfigProvider* scopedRouteConfigProvider() override { + return scoped_route_config_provider2_.get(); + } + + std::shared_ptr route_config_provider2_; + std::shared_ptr scoped_route_config_provider2_; +}; + +// HCM config can only have either RouteConfigProvider or ScopedRoutesConfigProvider. +TEST_F(HttpConnectionManagerImplDeathTest, InvalidConnectionManagerConfig) { + setup(false, ""); + + Buffer::OwnedImpl fake_input("1234"); + EXPECT_CALL(*codec_, dispatch(_)).WillRepeatedly(Invoke([&](Buffer::Instance&) -> void { + conn_manager_->newStream(response_encoder_); + })); + // Either RDS or SRDS should be set. + EXPECT_DEBUG_DEATH(conn_manager_->onData(fake_input, false), + "Either routeConfigProvider or scopedRouteConfigProvider should be set in " + "ConnectionManagerImpl."); + + route_config_provider2_ = std::make_shared>(); + + // Only route config provider valid. + EXPECT_NO_THROW(conn_manager_->onData(fake_input, false)); + + scoped_route_config_provider2_ = + std::make_shared>(); + // Can't have RDS and SRDS provider in the same time. + EXPECT_DEBUG_DEATH(conn_manager_->onData(fake_input, false), + "Either routeConfigProvider or scopedRouteConfigProvider should be set in " + "ConnectionManagerImpl."); + + route_config_provider2_.reset(); + // Only scoped route config provider valid. + EXPECT_NO_THROW(conn_manager_->onData(fake_input, false)); + +#if !defined(NDEBUG) + EXPECT_CALL(*scoped_route_config_provider2_, getConfig()).WillRepeatedly(Return(nullptr)); + // ASSERT failure when SRDS provider returns a nullptr. + EXPECT_DEBUG_DEATH(conn_manager_->onData(fake_input, false), + "Scoped rds provider returns null for scoped routes config."); +#endif // !defined(NDEBUG) +} + } // namespace Http } // namespace Envoy diff --git a/test/common/http/conn_manager_utility_test.cc b/test/common/http/conn_manager_utility_test.cc index c228f99e3c..2e591a77cf 100644 --- a/test/common/http/conn_manager_utility_test.cc +++ b/test/common/http/conn_manager_utility_test.cc @@ -13,6 +13,7 @@ #include "test/mocks/runtime/mocks.h" #include "test/mocks/ssl/mocks.h" #include "test/test_common/printers.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -20,7 +21,6 @@ using testing::_; using testing::An; -using testing::InSequence; using testing::Matcher; using testing::NiceMock; using testing::Return; @@ -63,6 +63,8 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD0(routeConfigProvider, Router::RouteConfigProvider*()); MOCK_METHOD0(scopedRouteConfigProvider, Config::ConfigProvider*()); MOCK_METHOD0(serverName, const std::string&()); + MOCK_METHOD0(serverHeaderTransformation, + HttpConnectionManagerProto::ServerHeaderTransformation()); MOCK_METHOD0(stats, ConnectionManagerStats&()); MOCK_METHOD0(tracingStats, ConnectionManagerTracingStats&()); MOCK_METHOD0(useRemoteAddress, bool()); @@ -99,7 +101,8 @@ class ConnectionManagerUtilityTest : public testing::Test { envoy::type::FractionalPercent percent2; percent2.set_numerator(10000); percent2.set_denominator(envoy::type::FractionalPercent::TEN_THOUSAND); - tracing_config_ = {Tracing::OperationName::Ingress, {}, percent1, percent2, percent1, false}; + tracing_config_ = { + Tracing::OperationName::Ingress, {}, percent1, percent2, percent1, false, 256}; ON_CALL(config_, tracingConfig()).WillByDefault(Return(&tracing_config_)); ON_CALL(config_, via()).WillByDefault(ReturnRef(via_)); @@ -226,6 +229,50 @@ TEST_F(ConnectionManagerUtilityTest, SkipXffAppendPassThruUseRemoteAddress) { EXPECT_EQ("198.51.100.1", headers.ForwardedFor()->value().getStringView()); } +TEST_F(ConnectionManagerUtilityTest, ForwardedProtoLegacyBehavior) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.trusted_forwarded_proto", "false"}}); + + ON_CALL(config_, useRemoteAddress()).WillByDefault(Return(true)); + ON_CALL(config_, xffNumTrustedHops()).WillByDefault(Return(1)); + EXPECT_CALL(config_, skipXffAppend()).WillOnce(Return(true)); + connection_.remote_address_ = std::make_shared("12.12.12.12"); + ON_CALL(config_, useRemoteAddress()).WillByDefault(Return(true)); + TestHeaderMapImpl headers{{"x-forwarded-proto", "https"}}; + + callMutateRequestHeaders(headers, Protocol::Http2); + EXPECT_EQ("http", headers.ForwardedProto()->value().getStringView()); +} + +TEST_F(ConnectionManagerUtilityTest, PreserveForwardedProtoWhenInternal) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.trusted_forwarded_proto", "true"}}); + + ON_CALL(config_, useRemoteAddress()).WillByDefault(Return(true)); + ON_CALL(config_, xffNumTrustedHops()).WillByDefault(Return(1)); + EXPECT_CALL(config_, skipXffAppend()).WillOnce(Return(true)); + connection_.remote_address_ = std::make_shared("12.12.12.12"); + ON_CALL(config_, useRemoteAddress()).WillByDefault(Return(true)); + TestHeaderMapImpl headers{{"x-forwarded-proto", "https"}}; + + callMutateRequestHeaders(headers, Protocol::Http2); + EXPECT_EQ("https", headers.ForwardedProto()->value().getStringView()); +} + +TEST_F(ConnectionManagerUtilityTest, OverwriteForwardedProtoWhenExternal) { + ON_CALL(config_, useRemoteAddress()).WillByDefault(Return(true)); + ON_CALL(config_, xffNumTrustedHops()).WillByDefault(Return(0)); + connection_.remote_address_ = std::make_shared("127.0.0.1"); + TestHeaderMapImpl headers{{"x-forwarded-proto", "https"}}; + Network::Address::Ipv4Instance local_address("10.3.2.1"); + ON_CALL(config_, localAddress()).WillByDefault(ReturnRef(local_address)); + + callMutateRequestHeaders(headers, Protocol::Http2); + EXPECT_EQ("http", headers.ForwardedProto()->value().getStringView()); +} + // Verify internal request and XFF is set when we are using remote address and the address is // internal according to user configuration. TEST_F(ConnectionManagerUtilityTest, UseRemoteAddressWhenUserConfiguredRemoteAddress) { @@ -729,9 +776,9 @@ TEST_F(ConnectionManagerUtilityTest, MutateResponseHeadersReturnXRequestId) { // Test full sanitization of x-forwarded-client-cert. TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeClientCert) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true)); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::Sanitize)); std::vector details; @@ -745,9 +792,9 @@ TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeClientCert) { // Test that we sanitize and set x-forwarded-client-cert. TEST_F(ConnectionManagerUtilityTest, MtlsForwardOnlyClientCert) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true)); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::ForwardOnly)); std::vector details; @@ -764,22 +811,22 @@ TEST_F(ConnectionManagerUtilityTest, MtlsForwardOnlyClientCert) { // The server (local) identity is foo.com/be. The client does not set XFCC. TEST_F(ConnectionManagerUtilityTest, MtlsSetForwardClientCert) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true)); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); const std::vector local_uri_sans{"test://foo.com/be"}; - EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return(local_uri_sans)); + EXPECT_CALL(*ssl, uriSanLocalCertificate()).WillOnce(Return(local_uri_sans)); std::string expected_sha("abcdefg"); - EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); + EXPECT_CALL(*ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); const std::vector peer_uri_sans{"test://foo.com/fe"}; - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(peer_uri_sans)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(peer_uri_sans)); std::string expected_pem("%3D%3Dabc%0Ade%3D"); - EXPECT_CALL(ssl, urlEncodedPemEncodedPeerCertificate()).WillOnce(ReturnRef(expected_pem)); + EXPECT_CALL(*ssl, urlEncodedPemEncodedPeerCertificate()).WillOnce(ReturnRef(expected_pem)); std::string expected_chain_pem(expected_pem + "%3D%3Dlmn%0Aop%3D"); - EXPECT_CALL(ssl, urlEncodedPemEncodedPeerCertificateChain()) + EXPECT_CALL(*ssl, urlEncodedPemEncodedPeerCertificateChain()) .WillOnce(ReturnRef(expected_chain_pem)); std::vector expected_dns = {"www.example.com"}; - EXPECT_CALL(ssl, dnsSansPeerCertificate()).WillOnce(Return(expected_dns)); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + EXPECT_CALL(*ssl, dnsSansPeerCertificate()).WillOnce(Return(expected_dns)); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::AppendForward)); std::vector details = std::vector(); @@ -807,22 +854,22 @@ TEST_F(ConnectionManagerUtilityTest, MtlsSetForwardClientCert) { // also sends the XFCC header with the authentication result of the previous hop, (bar.com/be // calling foo.com/fe). TEST_F(ConnectionManagerUtilityTest, MtlsAppendForwardClientCert) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true)); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); const std::vector local_uri_sans{"test://foo.com/be"}; - EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return(local_uri_sans)); + EXPECT_CALL(*ssl, uriSanLocalCertificate()).WillOnce(Return(local_uri_sans)); std::string expected_sha("abcdefg"); - EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); + EXPECT_CALL(*ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); const std::vector peer_uri_sans{"test://foo.com/fe"}; - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(peer_uri_sans)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(peer_uri_sans)); std::string expected_pem("%3D%3Dabc%0Ade%3D"); - EXPECT_CALL(ssl, urlEncodedPemEncodedPeerCertificate()).WillOnce(ReturnRef(expected_pem)); + EXPECT_CALL(*ssl, urlEncodedPemEncodedPeerCertificate()).WillOnce(ReturnRef(expected_pem)); std::string expected_chain_pem(expected_pem + "%3D%3Dlmn%0Aop%3D"); - EXPECT_CALL(ssl, urlEncodedPemEncodedPeerCertificateChain()) + EXPECT_CALL(*ssl, urlEncodedPemEncodedPeerCertificateChain()) .WillOnce(ReturnRef(expected_chain_pem)); std::vector expected_dns = {"www.example.com"}; - EXPECT_CALL(ssl, dnsSansPeerCertificate()).WillOnce(Return(expected_dns)); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + EXPECT_CALL(*ssl, dnsSansPeerCertificate()).WillOnce(Return(expected_dns)); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::AppendForward)); std::vector details = std::vector(); @@ -850,14 +897,14 @@ TEST_F(ConnectionManagerUtilityTest, MtlsAppendForwardClientCert) { // also sends the XFCC header with the authentication result of the previous hop, (bar.com/be // calling foo.com/fe). TEST_F(ConnectionManagerUtilityTest, MtlsAppendForwardClientCertLocalSanEmpty) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true)); - EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return(std::vector())); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); + EXPECT_CALL(*ssl, uriSanLocalCertificate()).WillOnce(Return(std::vector())); std::string expected_sha("abcdefg"); - EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); + EXPECT_CALL(*ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); const std::vector peer_uri_sans{"test://foo.com/fe"}; - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(peer_uri_sans)); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(peer_uri_sans)); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::AppendForward)); std::vector details = std::vector(); @@ -879,22 +926,22 @@ TEST_F(ConnectionManagerUtilityTest, MtlsAppendForwardClientCertLocalSanEmpty) { // also sends the XFCC header with the authentication result of the previous hop, (bar.com/be // calling foo.com/fe). TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeSetClientCert) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true)); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); const std::vector local_uri_sans{"test://foo.com/be"}; - EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return(local_uri_sans)); + EXPECT_CALL(*ssl, uriSanLocalCertificate()).WillOnce(Return(local_uri_sans)); std::string expected_sha("abcdefg"); - EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); - EXPECT_CALL(ssl, subjectPeerCertificate()) - .WillOnce(Return("/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=test.lyft.com")); + EXPECT_CALL(*ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); + std::string peer_subject("/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=test.lyft.com"); + EXPECT_CALL(*ssl, subjectPeerCertificate()).WillOnce(ReturnRef(peer_subject)); const std::vector peer_uri_sans{"test://foo.com/fe"}; - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(peer_uri_sans)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(peer_uri_sans)); std::string expected_pem("abcde="); - EXPECT_CALL(ssl, urlEncodedPemEncodedPeerCertificate()).WillOnce(ReturnRef(expected_pem)); + EXPECT_CALL(*ssl, urlEncodedPemEncodedPeerCertificate()).WillOnce(ReturnRef(expected_pem)); std::string expected_chain_pem(expected_pem + "lmnop="); - EXPECT_CALL(ssl, urlEncodedPemEncodedPeerCertificateChain()) + EXPECT_CALL(*ssl, urlEncodedPemEncodedPeerCertificateChain()) .WillOnce(ReturnRef(expected_chain_pem)); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::SanitizeSet)); std::vector details = std::vector(); @@ -920,16 +967,16 @@ TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeSetClientCert) { // also sends the XFCC header with the authentication result of the previous hop, (bar.com/be // calling foo.com/fe). TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeSetClientCertPeerSanEmpty) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(true)); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(true)); const std::vector local_uri_sans{"test://foo.com/be"}; - EXPECT_CALL(ssl, uriSanLocalCertificate()).WillOnce(Return(local_uri_sans)); + EXPECT_CALL(*ssl, uriSanLocalCertificate()).WillOnce(Return(local_uri_sans)); std::string expected_sha("abcdefg"); - EXPECT_CALL(ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); - EXPECT_CALL(ssl, subjectPeerCertificate()) - .WillOnce(Return("/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=test.lyft.com")); - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(std::vector())); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + EXPECT_CALL(*ssl, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha)); + std::string peer_subject = "/C=US/ST=CA/L=San Francisco/OU=Lyft/CN=test.lyft.com"; + EXPECT_CALL(*ssl, subjectPeerCertificate()).WillOnce(ReturnRef(peer_subject)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(std::vector())); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::SanitizeSet)); std::vector details = std::vector(); @@ -949,9 +996,9 @@ TEST_F(ConnectionManagerUtilityTest, MtlsSanitizeSetClientCertPeerSanEmpty) { // forward_only, append_forward and sanitize_set are only effective in mTLS connection. TEST_F(ConnectionManagerUtilityTest, TlsSanitizeClientCertWhenForward) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(false)); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(false)); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::ForwardOnly)); std::vector details; @@ -965,9 +1012,9 @@ TEST_F(ConnectionManagerUtilityTest, TlsSanitizeClientCertWhenForward) { // always_forward_only works regardless whether the connection is TLS/mTLS. TEST_F(ConnectionManagerUtilityTest, TlsAlwaysForwardOnlyClientCert) { - NiceMock ssl; - ON_CALL(ssl, peerCertificatePresented()).WillByDefault(Return(false)); - ON_CALL(connection_, ssl()).WillByDefault(Return(&ssl)); + auto ssl = std::make_shared>(); + ON_CALL(*ssl, peerCertificatePresented()).WillByDefault(Return(false)); + ON_CALL(connection_, ssl()).WillByDefault(Return(ssl)); ON_CALL(config_, forwardClientCert()) .WillByDefault(Return(Http::ForwardClientCertType::AlwaysForwardOnly)); std::vector details; diff --git a/test/common/http/date_provider_impl_test.cc b/test/common/http/date_provider_impl_test.cc index 619a5f9a96..41594a30a6 100644 --- a/test/common/http/date_provider_impl_test.cc +++ b/test/common/http/date_provider_impl_test.cc @@ -19,14 +19,14 @@ TEST(DateProviderImplTest, All) { Event::MockDispatcher dispatcher; NiceMock tls; Event::MockTimer* timer = new Event::MockTimer(&dispatcher); - EXPECT_CALL(*timer, enableTimer(std::chrono::milliseconds(500))); + EXPECT_CALL(*timer, enableTimer(std::chrono::milliseconds(500), _)); TlsCachingDateProviderImpl provider(dispatcher, tls); HeaderMapImpl headers; provider.setDateHeader(headers); EXPECT_NE(nullptr, headers.Date()); - EXPECT_CALL(*timer, enableTimer(std::chrono::milliseconds(500))); + EXPECT_CALL(*timer, enableTimer(std::chrono::milliseconds(500), _)); timer->invokeCallback(); headers.removeDate(); diff --git a/test/common/http/header_map_impl_corpus/appendheader b/test/common/http/header_map_impl_corpus/appendheader new file mode 100644 index 0000000000..bb772dcb6e --- /dev/null +++ b/test/common/http/header_map_impl_corpus/appendheader @@ -0,0 +1,5377 @@ +actions { + set_reference_key { + key: ":method" + value: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + set_reference_key { + key: ":method" + value: "baz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + set_reference_key { + key: ":method" + value: "baz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + set_reference_key { + key: ":method" + value: "baz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} +actions { + get_and_mutate { + key: ":method" + append: "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + } +} + diff --git a/test/common/http/header_utility_test.cc b/test/common/http/header_utility_test.cc index 97f6b95b34..c61443feec 100644 --- a/test/common/http/header_utility_test.cc +++ b/test/common/http/header_utility_test.cc @@ -175,8 +175,9 @@ name: match-header regex_match: (a|b) )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_FALSE(HeaderUtility::matchHeaders(headers, header_data)); headers.addCopy("match-header", "a"); @@ -202,9 +203,11 @@ name: match-header-A name: match-header-B )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yamlA))); - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yamlB))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yamlA))); + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yamlB))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers_1, header_data)); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers_2, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers_1, header_data)); @@ -220,8 +223,9 @@ TEST(MatchHeadersTest, HeaderPresence) { name: match-header )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -235,8 +239,9 @@ name: match-header exact_match: match-value )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -252,8 +257,9 @@ exact_match: match-value invert_match: true )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -266,8 +272,26 @@ name: match-header regex_match: \d{3} )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); + EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); + EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); +} + +TEST(MatchHeadersTest, HeaderSafeRegexMatch) { + TestHeaderMapImpl matching_headers{{"match-header", "123"}}; + TestHeaderMapImpl unmatching_headers{{"match-header", "1234"}, {"match-header", "123.456"}}; + const std::string yaml = R"EOF( +name: match-header +safe_regex_match: + google_re2: {} + regex: \d{3} + )EOF"; + + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -282,8 +306,9 @@ regex_match: \d{3} invert_match: true )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -301,8 +326,9 @@ name: match-header end: 0 )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -322,8 +348,9 @@ name: match-header invert_match: true )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -338,8 +365,9 @@ name: match-header present_match: true )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -355,8 +383,9 @@ present_match: true invert_match: true )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -370,8 +399,9 @@ name: match-header prefix_match: value )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -386,8 +416,9 @@ prefix_match: value invert_match: true )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -401,8 +432,9 @@ name: match-header suffix_match: value )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } @@ -417,8 +449,9 @@ suffix_match: value invert_match: true )EOF"; - std::vector header_data; - header_data.push_back(HeaderUtility::HeaderData(parseHeaderMatcherFromYaml(yaml))); + std::vector header_data; + header_data.push_back( + std::make_unique(parseHeaderMatcherFromYaml(yaml))); EXPECT_TRUE(HeaderUtility::matchHeaders(matching_headers, header_data)); EXPECT_FALSE(HeaderUtility::matchHeaders(unmatching_headers, header_data)); } diff --git a/test/common/http/http1/BUILD b/test/common/http/http1/BUILD index bbc1464177..b59485efb6 100644 --- a/test/common/http/http1/BUILD +++ b/test/common/http/http1/BUILD @@ -28,6 +28,7 @@ envoy_cc_test( "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", "//test/test_common:logging_lib", + "//test/test_common:test_runtime_lib", ], ) diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index f2b04b6e08..0303cfee5b 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -12,15 +12,10 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/printers.h" -#include "test/test_common/utility.h" +#include "test/test_common/test_runtime.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -38,17 +33,6 @@ namespace Http1 { class Http1ServerConnectionImplTest : public testing::Test { public: - Http1ServerConnectionImplTest() : api_(Api::createApiForTest()) { - envoy::config::bootstrap::v2::LayeredRuntime config; - config.add_layers()->mutable_admin_layer(); - - // Create a runtime loader, so that tests can manually manipulate runtime - // guarded features. - loader_ = std::make_unique(Runtime::LoaderPtr{ - new Runtime::LoaderImpl(dispatcher_, tls_, config, local_info_, init_manager_, store_, - generator_, validation_visitor_, *api_)}); - } - void initialize() { codec_ = std::make_unique(connection_, store_, callbacks_, codec_settings_, max_request_headers_kb_); @@ -65,15 +49,7 @@ class Http1ServerConnectionImplTest : public testing::Test { protected: uint32_t max_request_headers_kb_{Http::DEFAULT_MAX_REQUEST_HEADERS_KB}; - Event::MockDispatcher dispatcher_; - NiceMock tls_; Stats::IsolatedStoreImpl store_; - Runtime::MockRandomGenerator generator_; - Api::ApiPtr api_; - NiceMock local_info_; - Init::MockManager init_manager_; - NiceMock validation_visitor_; - std::unique_ptr loader_; }; void Http1ServerConnectionImplTest::expect400(Protocol p, bool allow_absolute_url, @@ -366,6 +342,7 @@ TEST_F(Http1ServerConnectionImplTest, HostHeaderTranslation) { // Ensures that requests with invalid HTTP header values are not rejected // when the runtime guard is not enabled for the feature. TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRuntimeGuard) { + TestScopedRuntime scoped_runtime; // When the runtime-guarded feature is NOT enabled, invalid header values // should be accepted by the codec. Runtime::LoaderSingleton::getExisting()->mergeValues( @@ -384,6 +361,7 @@ TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRuntimeGuard) { // Ensures that requests with invalid HTTP header values are properly rejected // when the runtime guard is enabled for the feature. TEST_F(Http1ServerConnectionImplTest, HeaderInvalidCharsRejection) { + TestScopedRuntime scoped_runtime; // When the runtime-guarded feature is enabled, invalid header values // should result in a rejection. Runtime::LoaderSingleton::getExisting()->mergeValues( @@ -742,6 +720,43 @@ TEST_F(Http1ServerConnectionImplTest, RequestWithTrailers) { EXPECT_EQ(0U, buffer.length()); } +TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2c) { + initialize(); + + TestHeaderMapImpl expected_headers{ + {":authority", "www.somewhere.com"}, {":path", "/"}, {":method", "GET"}}; + Buffer::OwnedImpl buffer( + "GET http://www.somewhere.com/ HTTP/1.1\r\nConnection: " + "Upgrade, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: token64\r\nHost: bah\r\n\r\n"); + expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); +} + +TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cClose) { + initialize(); + + TestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"}, + {":path", "/"}, + {":method", "GET"}, + {"connection", "Close"}}; + Buffer::OwnedImpl buffer("GET http://www.somewhere.com/ HTTP/1.1\r\nConnection: " + "Upgrade, Close, HTTP2-Settings\r\nUpgrade: h2c\r\nHTTP2-Settings: " + "token64\r\nHost: bah\r\n\r\n"); + expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); +} + +TEST_F(Http1ServerConnectionImplTest, IgnoreUpgradeH2cCloseEtc) { + initialize(); + + TestHeaderMapImpl expected_headers{{":authority", "www.somewhere.com"}, + {":path", "/"}, + {":method", "GET"}, + {"connection", "Close,Etc"}}; + Buffer::OwnedImpl buffer("GET http://www.somewhere.com/ HTTP/1.1\r\nConnection: " + "Upgrade, Close, HTTP2-Settings, Etc\r\nUpgrade: h2c\r\nHTTP2-Settings: " + "token64\r\nHost: bah\r\n\r\n"); + expectHeadersTest(Protocol::Http11, true, buffer, expected_headers); +} + TEST_F(Http1ServerConnectionImplTest, UpgradeRequest) { initialize(); @@ -850,16 +865,6 @@ TEST_F(Http1ServerConnectionImplTest, WatermarkTest) { class Http1ClientConnectionImplTest : public testing::Test { public: - Http1ClientConnectionImplTest() : api_(Api::createApiForTest()) { - envoy::config::bootstrap::v2::LayeredRuntime config; - - // Create a runtime loader, so that tests can manually manipulate runtime - // guarded features. - loader_ = std::make_unique(Runtime::LoaderPtr{ - new Runtime::LoaderImpl(dispatcher_, tls_, config, local_info_, init_manager_, store_, - generator_, validation_visitor_, *api_)}); - } - void initialize() { codec_ = std::make_unique(connection_, store_, callbacks_); } @@ -869,15 +874,7 @@ class Http1ClientConnectionImplTest : public testing::Test { std::unique_ptr codec_; protected: - Event::MockDispatcher dispatcher_; - NiceMock tls_; Stats::IsolatedStoreImpl store_; - Runtime::MockRandomGenerator generator_; - Api::ApiPtr api_; - NiceMock local_info_; - Init::MockManager init_manager_; - NiceMock validation_visitor_; - std::unique_ptr loader_; }; TEST_F(Http1ClientConnectionImplTest, SimpleGet) { diff --git a/test/common/http/http1/conn_pool_test.cc b/test/common/http/http1/conn_pool_test.cc index d91c9cd862..a667ea4938 100644 --- a/test/common/http/http1/conn_pool_test.cc +++ b/test/common/http/http1/conn_pool_test.cc @@ -30,7 +30,6 @@ using testing::NiceMock; using testing::Property; using testing::Return; using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Http { @@ -98,13 +97,13 @@ class ConnPoolImplForTest : public ConnPoolImpl { EXPECT_CALL(mock_dispatcher_, createClientConnection_(_, _, _, _)) .WillOnce(Return(test_client.connection_)); EXPECT_CALL(*this, createCodecClient_()).WillOnce(Return(test_client.codec_client_)); - EXPECT_CALL(*test_client.connect_timer_, enableTimer(_)); + EXPECT_CALL(*test_client.connect_timer_, enableTimer(_, _)); ON_CALL(*test_client.codec_, protocol()).WillByDefault(Return(protocol)); } void expectEnableUpstreamReady() { EXPECT_FALSE(upstream_ready_enabled_); - EXPECT_CALL(*mock_upstream_ready_timer_, enableTimer(_)).Times(1).RetiresOnSaturation(); + EXPECT_CALL(*mock_upstream_ready_timer_, enableTimer(_, _)).Times(1).RetiresOnSaturation(); } void expectAndRunUpstreamReady() { diff --git a/test/common/http/http2/BUILD b/test/common/http/http2/BUILD index ae51864b72..3a93f99146 100644 --- a/test/common/http/http2/BUILD +++ b/test/common/http/http2/BUILD @@ -30,6 +30,7 @@ envoy_cc_test( "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/common/http/http2/codec_impl_test.cc b/test/common/http/http2/codec_impl_test.cc index e738c140af..7592ec0985 100644 --- a/test/common/http/http2/codec_impl_test.cc +++ b/test/common/http/http2/codec_impl_test.cc @@ -11,12 +11,9 @@ #include "test/common/http/common.h" #include "test/common/http/http2/http2_frame.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" #include "test/mocks/network/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/printers.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "codec_impl_test_util.h" @@ -169,17 +166,7 @@ class Http2CodecImplTest : public ::testing::TestWithParam(GetParam()), ::testing::get<1>(GetParam())), - api_(Api::createApiForTest()) { - envoy::config::bootstrap::v2::LayeredRuntime config; - config.add_layers()->mutable_admin_layer(); - - // Create a runtime loader, so that tests can manually manipulate runtime - // guarded features. - loader_ = std::make_unique( - std::make_unique(dispatcher_, tls_, config, local_info_, init_manager_, - store_, generator_, validation_visitor_, *api_)); - } + : Http2CodecImplTestFixture(::testing::get<0>(GetParam()), ::testing::get<1>(GetParam())) {} protected: void priorityFlood() { @@ -242,16 +229,9 @@ class Http2CodecImplTest : public ::testing::TestWithParam tls_; - Stats::IsolatedStoreImpl store_; - Runtime::MockRandomGenerator generator_; - Api::ApiPtr api_; - NiceMock local_info_; - Init::MockManager init_manager_; - NiceMock validation_visitor_; - std::unique_ptr loader_; + // Make sure the test fixture has a fake runtime, for the tests which use + // Runtime::LoaderSingleton::getExisting()->mergeValues(...) + TestScopedRuntime scoped_runtime_; }; TEST_P(Http2CodecImplTest, ShutdownNotice) { diff --git a/test/common/http/http2/conn_pool_test.cc b/test/common/http/http2/conn_pool_test.cc index 6490da7337..8ac3f66a31 100644 --- a/test/common/http/http2/conn_pool_test.cc +++ b/test/common/http/http2/conn_pool_test.cc @@ -27,7 +27,6 @@ using testing::NiceMock; using testing::Property; using testing::Return; using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Http { @@ -96,7 +95,7 @@ class Http2ConnPoolImplTest : public testing::Test { .WillOnce(Invoke([this](Upstream::Host::CreateConnectionData&) -> CodecClient* { return test_clients_.back().codec_client_; })); - EXPECT_CALL(*test_client.connect_timer_, enableTimer(_)); + EXPECT_CALL(*test_client.connect_timer_, enableTimer(_, _)); } // Connects a pending connection for client with the given index, asserting diff --git a/test/common/init/manager_impl_test.cc b/test/common/init/manager_impl_test.cc index 8a479b0c19..28465b1d2a 100644 --- a/test/common/init/manager_impl_test.cc +++ b/test/common/init/manager_impl_test.cc @@ -5,7 +5,6 @@ #include "gtest/gtest.h" using ::testing::InSequence; -using ::testing::InvokeWithoutArgs; namespace Envoy { namespace Init { diff --git a/test/common/json/config_schemas_test.cc b/test/common/json/config_schemas_test.cc index 29585f2e65..1eefa90648 100644 --- a/test/common/json/config_schemas_test.cc +++ b/test/common/json/config_schemas_test.cc @@ -12,8 +12,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; - namespace Envoy { namespace Json { namespace { diff --git a/test/common/network/addr_family_aware_socket_option_impl_test.cc b/test/common/network/addr_family_aware_socket_option_impl_test.cc index b208faac77..379029dc2d 100644 --- a/test/common/network/addr_family_aware_socket_option_impl_test.cc +++ b/test/common/network/addr_family_aware_socket_option_impl_test.cc @@ -17,6 +17,7 @@ class AddrFamilyAwareSocketOptionImplTest : public SocketOptionTest { .WillRepeatedly(Invoke([](int domain, int type, int protocol) { return Api::SysCallIntResult{::socket(domain, type, protocol), 0}; })); + EXPECT_CALL(os_sys_calls_, close(_)).Times(testing::AnyNumber()); } }; diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index 734734f09b..7e22ba77e9 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -1224,7 +1224,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimerResetWithPendingWriteBufferFlushes) Buffer::OwnedImpl data("data"); server_connection->write(data, false); - EXPECT_CALL(*mocks.timer_, enableTimer(timeout)).Times(1); + EXPECT_CALL(*mocks.timer_, enableTimer(timeout, _)).Times(1); server_connection->close(ConnectionCloseType::FlushWriteAndDelay); // The write ready event cb (ConnectionImpl::onWriteReady()) will reset the timer to its original @@ -1234,7 +1234,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimerResetWithPendingWriteBufferFlushes) // Partial flush. return IoResult{PostIoAction::KeepOpen, 1, false}; })); - EXPECT_CALL(*mocks.timer_, enableTimer(timeout)).Times(1); + EXPECT_CALL(*mocks.timer_, enableTimer(timeout, _)).Times(1); (*mocks.file_ready_cb_)(Event::FileReadyType::Write); EXPECT_CALL(*transport_socket, doWrite(BufferStringEqual("data"), _)) @@ -1243,7 +1243,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimerResetWithPendingWriteBufferFlushes) buffer.drain(buffer.length()); return IoResult{PostIoAction::KeepOpen, buffer.length(), false}; })); - EXPECT_CALL(*mocks.timer_, enableTimer(timeout)).Times(1); + EXPECT_CALL(*mocks.timer_, enableTimer(timeout, _)).Times(1); (*mocks.file_ready_cb_)(Event::FileReadyType::Write); // Force the delayed close timeout to trigger so the connection is cleaned up. @@ -1277,7 +1277,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimeoutDisableOnSocketClose) { return IoResult{PostIoAction::KeepOpen, buffer.length(), false}; })); server_connection->write(data, false); - EXPECT_CALL(*mocks.timer_, enableTimer(_)).Times(1); + EXPECT_CALL(*mocks.timer_, enableTimer(_, _)).Times(1); // Enable the delayed close timer. server_connection->close(ConnectionCloseType::FlushWriteAndDelay); EXPECT_CALL(*mocks.timer_, disableTimer()).Times(1); @@ -1318,7 +1318,7 @@ TEST_P(ConnectionImplTest, DelayedCloseTimeoutNullStats) { })); server_connection->write(data, false); - EXPECT_CALL(*mocks.timer_, enableTimer(_)).Times(1); + EXPECT_CALL(*mocks.timer_, enableTimer(_, _)).Times(1); server_connection->close(ConnectionCloseType::FlushWriteAndDelay); EXPECT_CALL(*mocks.timer_, disableTimer()).Times(1); // The following close() will call closeSocket() and reset internal data structures such as diff --git a/test/common/network/dns_impl_test.cc b/test/common/network/dns_impl_test.cc index eaa925b9e0..a5b1cee0bc 100644 --- a/test/common/network/dns_impl_test.cc +++ b/test/common/network/dns_impl_test.cc @@ -34,7 +34,6 @@ using testing::_; using testing::InSequence; -using testing::Mock; using testing::NiceMock; using testing::Return; @@ -857,7 +856,7 @@ TEST(DnsImplUnitTest, PendingTimerEnable) { DnsResolverImpl resolver(dispatcher, {}); Event::FileEvent* file_event = new NiceMock(); EXPECT_CALL(dispatcher, createFileEvent_(_, _, _, _)).WillOnce(Return(file_event)); - EXPECT_CALL(*timer, enableTimer(_)); + EXPECT_CALL(*timer, enableTimer(_, _)); EXPECT_NE(nullptr, resolver.resolve("some.bad.domain.invalid", DnsLookupFamily::V4Only, [&](std::list&& results) { UNREFERENCED_PARAMETER(results); diff --git a/test/common/network/socket_option_factory_test.cc b/test/common/network/socket_option_factory_test.cc index 6ec4767ab3..ca5e25cc36 100644 --- a/test/common/network/socket_option_factory_test.cc +++ b/test/common/network/socket_option_factory_test.cc @@ -154,20 +154,21 @@ TEST_F(SocketOptionFactoryTest, TestBuildLiteralOptions) { EXPECT_TRUE(option_details.has_value()); EXPECT_EQ(SOL_SOCKET, option_details->name_.level()); EXPECT_EQ(SO_LINGER, option_details->name_.option()); - EXPECT_EQ(sizeof(struct linger), option_details->value_.size()); - const struct linger* linger_ptr = - reinterpret_cast(option_details->value_.data()); - EXPECT_EQ(1, linger_ptr->l_onoff); - EXPECT_EQ(3456, linger_ptr->l_linger); + struct linger expected_linger; + expected_linger.l_onoff = 1; + expected_linger.l_linger = 3456; + absl::string_view linger_bstr{reinterpret_cast(&expected_linger), + sizeof(struct linger)}; + EXPECT_EQ(linger_bstr, option_details->value_); option_details = socket_options->at(1)->getOptionDetails( socket_mock_, envoy::api::v2::core::SocketOption::STATE_PREBIND); EXPECT_TRUE(option_details.has_value()); EXPECT_EQ(SOL_SOCKET, option_details->name_.level()); EXPECT_EQ(SO_KEEPALIVE, option_details->name_.option()); - EXPECT_EQ(sizeof(int), option_details->value_.size()); - const int* flag_ptr = reinterpret_cast(option_details->value_.data()); - EXPECT_EQ(1, *flag_ptr); + int value = 1; + absl::string_view value_bstr{reinterpret_cast(&value), sizeof(int)}; + EXPECT_EQ(value_bstr, option_details->value_); } } // namespace diff --git a/test/common/network/socket_option_impl_test.cc b/test/common/network/socket_option_impl_test.cc index 8d9e55ae2d..d979d8a010 100644 --- a/test/common/network/socket_option_impl_test.cc +++ b/test/common/network/socket_option_impl_test.cc @@ -8,7 +8,8 @@ class SocketOptionImplTest : public SocketOptionTest {}; TEST_F(SocketOptionImplTest, BadFd) { absl::string_view zero("\0\0\0\0", 4); - Api::SysCallIntResult result = SocketOptionImpl::setSocketOption(socket_, {}, zero); + Api::SysCallIntResult result = + SocketOptionImpl::setSocketOption(socket_, {}, zero.data(), zero.size()); EXPECT_EQ(-1, result.rc_); EXPECT_EQ(ENOTSUP, result.errno_); } diff --git a/test/common/network/socket_option_test.h b/test/common/network/socket_option_test.h index 2fe7a59d2c..c4d5759120 100644 --- a/test/common/network/socket_option_test.h +++ b/test/common/network/socket_option_test.h @@ -13,7 +13,6 @@ using testing::_; using testing::Invoke; using testing::NiceMock; -using testing::Return; namespace Envoy { namespace Network { diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index 04fb200fc0..945bec99d5 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -1,8 +1,10 @@ #include +#include "envoy/api/v2/cds.pb.validate.h" #include "envoy/config/bootstrap/v2/bootstrap.pb.h" #include "envoy/config/bootstrap/v2/bootstrap.pb.validate.h" +#include "common/protobuf/message_validator_impl.h" #include "common/protobuf/protobuf.h" #include "common/protobuf/utility.h" #include "common/runtime/runtime_impl.h" @@ -35,6 +37,8 @@ TEST_F(ProtobufUtilityTest, convertPercentNaN) { EXPECT_THROW(PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config_, healthy_panic_threshold, 100, 50), EnvoyException); + EXPECT_THROW(PROTOBUF_PERCENT_TO_DOUBLE_OR_DEFAULT(common_config_, healthy_panic_threshold, 0.5), + EnvoyException); } namespace ProtobufPercentHelper { @@ -123,15 +127,41 @@ TEST_F(ProtobufUtilityTest, RepeatedPtrUtilDebugString) { EXPECT_EQ("[value: 10\n, value: 20\n]", RepeatedPtrUtil::debugString(repeated)); } -TEST_F(ProtobufUtilityTest, DowncastAndValidate) { +// Validated exception thrown when downcastAndValidate observes a PGV failures. +TEST_F(ProtobufUtilityTest, DowncastAndValidateFailedValidation) { envoy::config::bootstrap::v2::Bootstrap bootstrap; bootstrap.mutable_static_resources()->add_clusters(); - EXPECT_THROW(MessageUtil::validate(bootstrap), ProtoValidationException); + EXPECT_THROW(TestUtility::validate(bootstrap), ProtoValidationException); EXPECT_THROW( - MessageUtil::downcastAndValidate(bootstrap), + TestUtility::downcastAndValidate(bootstrap), ProtoValidationException); } +// Validated exception thrown when downcastAndValidate observes a unknown field. +TEST_F(ProtobufUtilityTest, DowncastAndValidateUnknownFields) { + envoy::config::bootstrap::v2::Bootstrap bootstrap; + bootstrap.GetReflection()->MutableUnknownFields(&bootstrap)->AddVarint(1, 0); + EXPECT_THROW_WITH_MESSAGE(TestUtility::validate(bootstrap), EnvoyException, + "Protobuf message (type envoy.config.bootstrap.v2.Bootstrap with " + "unknown field set {1}) has unknown fields"); + EXPECT_THROW_WITH_MESSAGE(TestUtility::validate(bootstrap), EnvoyException, + "Protobuf message (type envoy.config.bootstrap.v2.Bootstrap with " + "unknown field set {1}) has unknown fields"); +} + +// Validated exception thrown when downcastAndValidate observes a nested unknown field. +TEST_F(ProtobufUtilityTest, DowncastAndValidateUnknownFieldsNested) { + envoy::config::bootstrap::v2::Bootstrap bootstrap; + auto* cluster = bootstrap.mutable_static_resources()->add_clusters(); + cluster->GetReflection()->MutableUnknownFields(cluster)->AddVarint(1, 0); + EXPECT_THROW_WITH_MESSAGE(TestUtility::validate(*cluster), EnvoyException, + "Protobuf message (type envoy.api.v2.Cluster with " + "unknown field set {1}) has unknown fields"); + EXPECT_THROW_WITH_MESSAGE(TestUtility::validate(bootstrap), EnvoyException, + "Protobuf message (type envoy.api.v2.Cluster with " + "unknown field set {1}) has unknown fields"); +} + TEST_F(ProtobufUtilityTest, LoadBinaryProtoFromFile) { envoy::config::bootstrap::v2::Bootstrap bootstrap; bootstrap.mutable_cluster_manager() @@ -147,15 +177,31 @@ TEST_F(ProtobufUtilityTest, LoadBinaryProtoFromFile) { EXPECT_TRUE(TestUtility::protoEqual(bootstrap, proto_from_file)); } +// An unknown field (or with wrong type) in a message is rejected. TEST_F(ProtobufUtilityTest, LoadBinaryProtoUnknownFieldFromFile) { ProtobufWkt::Duration source_duration; source_duration.set_seconds(42); const std::string filename = TestEnvironment::writeStringToFileForTest("proto.pb", source_duration.SerializeAsString()); envoy::config::bootstrap::v2::Bootstrap proto_from_file; - EXPECT_THROW_WITH_MESSAGE( - TestUtility::loadFromFile(filename, proto_from_file, *api_), EnvoyException, - "Protobuf message (type envoy.config.bootstrap.v2.Bootstrap) has unknown fields"); + EXPECT_THROW_WITH_MESSAGE(TestUtility::loadFromFile(filename, proto_from_file, *api_), + EnvoyException, + "Protobuf message (type envoy.config.bootstrap.v2.Bootstrap with " + "unknown field set {1}) has unknown fields"); +} + +// Multiple unknown fields (or with wrong type) in a message are rejected. +TEST_F(ProtobufUtilityTest, LoadBinaryProtoUnknownMultipleFieldsFromFile) { + ProtobufWkt::Duration source_duration; + source_duration.set_seconds(42); + source_duration.set_nanos(42); + const std::string filename = + TestEnvironment::writeStringToFileForTest("proto.pb", source_duration.SerializeAsString()); + envoy::config::bootstrap::v2::Bootstrap proto_from_file; + EXPECT_THROW_WITH_MESSAGE(TestUtility::loadFromFile(filename, proto_from_file, *api_), + EnvoyException, + "Protobuf message (type envoy.config.bootstrap.v2.Bootstrap with " + "unknown field set {1, 2}) has unknown fields"); } TEST_F(ProtobufUtilityTest, LoadTextProtoFromFile) { @@ -326,16 +372,6 @@ TEST_F(ProtobufUtilityTest, AnyConvertWrongType) { EnvoyException, "Unable to unpack .*"); } -TEST_F(ProtobufUtilityTest, AnyConvertWrongFields) { - const ProtobufWkt::Struct obj = MessageUtil::keyValueStruct("test_key", "test_value"); - ProtobufWkt::Any source_any; - source_any.PackFrom(obj); - source_any.set_type_url("type.google.com/google.protobuf.Timestamp"); - EXPECT_THROW_WITH_MESSAGE(TestUtility::anyConvert(source_any), - EnvoyException, - "Protobuf message (type google.protobuf.Timestamp) has unknown fields"); -} - TEST_F(ProtobufUtilityTest, JsonConvertSuccess) { envoy::config::bootstrap::v2::Bootstrap source; source.set_flags_path("foo"); @@ -475,49 +511,54 @@ class DeprecatedFieldsTest : public testing::Test { NiceMock validation_visitor_; }; +void checkForDeprecation(const Protobuf::Message& message) { + MessageUtil::checkForUnexpectedFields(message, ProtobufMessage::getStrictValidationVisitor()); +} + TEST_F(DeprecatedFieldsTest, NoCrashIfRuntimeMissing) { loader_.reset(); envoy::test::deprecation_test::Base base; base.set_not_deprecated("foo"); // Fatal checks for a non-deprecated field should cause no problem. - MessageUtil::checkForDeprecation(base); + checkForDeprecation(base); } TEST_F(DeprecatedFieldsTest, NoErrorWhenDeprecatedFieldsUnused) { envoy::test::deprecation_test::Base base; base.set_not_deprecated("foo"); // Fatal checks for a non-deprecated field should cause no problem. - MessageUtil::checkForDeprecation(base); + checkForDeprecation(base); EXPECT_EQ(0, runtime_deprecated_feature_use_.value()); } -TEST_F(DeprecatedFieldsTest, IndividualFieldDeprecated) { +TEST_F(DeprecatedFieldsTest, DEPRECATED_FEATURE_TEST(IndividualFieldDeprecated)) { envoy::test::deprecation_test::Base base; base.set_is_deprecated("foo"); // Non-fatal checks for a deprecated field should log rather than throw an exception. EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated'", - MessageUtil::checkForDeprecation(base)); + checkForDeprecation(base)); EXPECT_EQ(1, runtime_deprecated_feature_use_.value()); } // Use of a deprecated and disallowed field should result in an exception. -TEST_F(DeprecatedFieldsTest, IndividualFieldDisallowed) { +TEST_F(DeprecatedFieldsTest, DEPRECATED_FEATURE_TEST(IndividualFieldDisallowed)) { envoy::test::deprecation_test::Base base; base.set_is_deprecated_fatal("foo"); EXPECT_THROW_WITH_REGEX( - MessageUtil::checkForDeprecation(base), ProtoValidationException, + checkForDeprecation(base), ProtoValidationException, "Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated_fatal'"); } -TEST_F(DeprecatedFieldsTest, IndividualFieldDisallowedWithRuntimeOverride) { +TEST_F(DeprecatedFieldsTest, + DEPRECATED_FEATURE_TEST(IndividualFieldDisallowedWithRuntimeOverride)) { envoy::test::deprecation_test::Base base; base.set_is_deprecated_fatal("foo"); // Make sure this is set up right. EXPECT_THROW_WITH_REGEX( - MessageUtil::checkForDeprecation(base), ProtoValidationException, + checkForDeprecation(base), ProtoValidationException, "Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated_fatal'"); // The config will be rejected, so the feature will not be used. EXPECT_EQ(0, runtime_deprecated_feature_use_.value()); @@ -529,17 +570,17 @@ TEST_F(DeprecatedFieldsTest, IndividualFieldDisallowedWithRuntimeOverride) { // Now the same deprecation check should only trigger a warning. EXPECT_LOG_CONTAINS( "warning", "Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated_fatal'", - MessageUtil::checkForDeprecation(base)); + checkForDeprecation(base)); EXPECT_EQ(1, runtime_deprecated_feature_use_.value()); } -TEST_F(DeprecatedFieldsTest, DisallowViaRuntime) { +TEST_F(DeprecatedFieldsTest, DEPRECATED_FEATURE_TEST(DisallowViaRuntime)) { envoy::test::deprecation_test::Base base; base.set_is_deprecated("foo"); EXPECT_LOG_CONTAINS("warning", "Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated'", - MessageUtil::checkForDeprecation(base)); + checkForDeprecation(base)); EXPECT_EQ(1, runtime_deprecated_feature_use_.value()); // Now create a new snapshot with this feature disallowed. @@ -547,7 +588,7 @@ TEST_F(DeprecatedFieldsTest, DisallowViaRuntime) { {{"envoy.deprecated_features.deprecated.proto:is_deprecated", " false"}}); EXPECT_THROW_WITH_REGEX( - MessageUtil::checkForDeprecation(base), ProtoValidationException, + checkForDeprecation(base), ProtoValidationException, "Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated'"); EXPECT_EQ(1, runtime_deprecated_feature_use_.value()); } @@ -555,45 +596,44 @@ TEST_F(DeprecatedFieldsTest, DisallowViaRuntime) { // Note that given how Envoy config parsing works, the first time we hit a // 'fatal' error and throw, we won't log future warnings. That said, this tests // the case of the warning occurring before the fatal error. -TEST_F(DeprecatedFieldsTest, MixOfFatalAndWarnings) { +TEST_F(DeprecatedFieldsTest, DEPRECATED_FEATURE_TEST(MixOfFatalAndWarnings)) { envoy::test::deprecation_test::Base base; base.set_is_deprecated("foo"); base.set_is_deprecated_fatal("foo"); EXPECT_LOG_CONTAINS( "warning", "Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated'", { EXPECT_THROW_WITH_REGEX( - MessageUtil::checkForDeprecation(base), ProtoValidationException, + checkForDeprecation(base), ProtoValidationException, "Using deprecated option 'envoy.test.deprecation_test.Base.is_deprecated_fatal'"); }); } // Present (unused) deprecated messages should be detected as deprecated. -TEST_F(DeprecatedFieldsTest, MessageDeprecated) { +TEST_F(DeprecatedFieldsTest, DEPRECATED_FEATURE_TEST(MessageDeprecated)) { envoy::test::deprecation_test::Base base; base.mutable_deprecated_message(); EXPECT_LOG_CONTAINS( "warning", "Using deprecated option 'envoy.test.deprecation_test.Base.deprecated_message'", - MessageUtil::checkForDeprecation(base)); + checkForDeprecation(base)); EXPECT_EQ(1, runtime_deprecated_feature_use_.value()); } -TEST_F(DeprecatedFieldsTest, InnerMessageDeprecated) { +TEST_F(DeprecatedFieldsTest, DEPRECATED_FEATURE_TEST(InnerMessageDeprecated)) { envoy::test::deprecation_test::Base base; base.mutable_not_deprecated_message()->set_inner_not_deprecated("foo"); // Checks for a non-deprecated field shouldn't trigger warnings - EXPECT_LOG_NOT_CONTAINS("warning", "Using deprecated option", - MessageUtil::checkForDeprecation(base)); + EXPECT_LOG_NOT_CONTAINS("warning", "Using deprecated option", checkForDeprecation(base)); base.mutable_not_deprecated_message()->set_inner_deprecated("bar"); // Checks for a deprecated sub-message should result in a warning. EXPECT_LOG_CONTAINS( "warning", "Using deprecated option 'envoy.test.deprecation_test.Base.InnerMessage.inner_deprecated'", - MessageUtil::checkForDeprecation(base)); + checkForDeprecation(base)); } // Check that repeated sub-messages get validated. -TEST_F(DeprecatedFieldsTest, SubMessageDeprecated) { +TEST_F(DeprecatedFieldsTest, DEPRECATED_FEATURE_TEST(SubMessageDeprecated)) { envoy::test::deprecation_test::Base base; base.add_repeated_message(); base.add_repeated_message()->set_inner_deprecated("foo"); @@ -603,11 +643,11 @@ TEST_F(DeprecatedFieldsTest, SubMessageDeprecated) { EXPECT_LOG_CONTAINS("warning", "Using deprecated option " "'envoy.test.deprecation_test.Base.InnerMessage.inner_deprecated'", - MessageUtil::checkForDeprecation(base)); + checkForDeprecation(base)); } // Check that deprecated repeated messages trigger -TEST_F(DeprecatedFieldsTest, RepeatedMessageDeprecated) { +TEST_F(DeprecatedFieldsTest, DEPRECATED_FEATURE_TEST(RepeatedMessageDeprecated)) { envoy::test::deprecation_test::Base base; base.add_deprecated_repeated_message(); @@ -615,7 +655,7 @@ TEST_F(DeprecatedFieldsTest, RepeatedMessageDeprecated) { EXPECT_LOG_CONTAINS("warning", "Using deprecated option " "'envoy.test.deprecation_test.Base.deprecated_repeated_message'", - MessageUtil::checkForDeprecation(base)); + checkForDeprecation(base)); } class TimestampUtilTest : public testing::Test, public ::testing::WithParamInterface {}; diff --git a/test/common/router/BUILD b/test/common/router/BUILD index eb07c65589..48304edb71 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -85,6 +85,7 @@ envoy_cc_test( ], deps = [ "//source/common/router:scoped_config_lib", + "//test/mocks/router:router_mocks", "//test/test_common:utility_lib", ], ) @@ -96,12 +97,16 @@ envoy_cc_test( "abseil_strings", ], deps = [ + "//include/envoy/config:subscription_interface", + "//include/envoy/init:manager_interface", "//source/common/config:utility_lib", "//source/common/http:message_lib", "//source/common/json:json_loader_lib", "//source/common/router:scoped_rds_lib", "//source/server/http:admin_lib", + "//test/mocks/config:config_mocks", "//test/mocks/init:init_mocks", + "//test/mocks/router:router_mocks", "//test/mocks/server:server_mocks", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", @@ -242,6 +247,7 @@ envoy_cc_test( "//test/mocks/upstream:upstream_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", + "//test/test_common:test_runtime_lib", "//test/test_common:utility_lib", ], ) diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 5af81cbc1f..bc50040980 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -31,14 +31,12 @@ using testing::_; using testing::ContainerEq; -using testing::ElementsAreArray; using testing::Eq; using testing::Matcher; using testing::MockFunction; using testing::NiceMock; using testing::Return; using testing::ReturnRef; -using testing::StrNe; namespace Envoy { namespace Router { @@ -89,7 +87,7 @@ Http::TestHeaderMapImpl genHeaders(const std::string& host, const std::string& p envoy::api::v2::RouteConfiguration parseRouteConfigurationFromV2Yaml(const std::string& yaml) { envoy::api::v2::RouteConfiguration route_config; TestUtility::loadFromYaml(yaml, route_config); - MessageUtil::validate(route_config); + TestUtility::validate(route_config); return route_config; } @@ -109,13 +107,175 @@ class ConfigImplTestBase { return factory_context_.scope().symbolTable().toString(name); } - Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Api::ApiPtr api_; NiceMock factory_context_; }; class RouteMatcherTest : public testing::Test, public ConfigImplTestBase {}; +// When removing legacy fields this test can be removed. +TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(TestLegacyRoutes)) { + const std::string yaml = R"EOF( +virtual_hosts: +- name: regex + domains: + - bat.com + routes: + - match: + regex: "/t[io]c" + route: + cluster: clock + - match: + safe_regex: + google_re2: {} + regex: "/baa+" + route: + cluster: sheep + - match: + regex: ".*/\\d{3}$" + route: + cluster: three_numbers + prefix_rewrite: "/rewrote" + - match: + regex: ".*" + route: + cluster: regex_default +- name: regex2 + domains: + - bat2.com + routes: + - match: + regex: '' + route: + cluster: nothingness + - match: + regex: ".*" + route: + cluster: regex_default +- name: default + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: instant-server + timeout: 30s + 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 + )EOF"; + + NiceMock stream_info; + TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); + + // Regular Expression matching + EXPECT_EQ("clock", + config.route(genHeaders("bat.com", "/tic", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("clock", + config.route(genHeaders("bat.com", "/toc", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("regex_default", + config.route(genHeaders("bat.com", "/tac", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("regex_default", + config.route(genHeaders("bat.com", "", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("regex_default", + config.route(genHeaders("bat.com", "/tick", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("regex_default", + config.route(genHeaders("bat.com", "/tic/toc", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("sheep", + config.route(genHeaders("bat.com", "/baa", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ( + "sheep", + config.route(genHeaders("bat.com", "/baaaaaaaaaaaa", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("regex_default", + config.route(genHeaders("bat.com", "/ba", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("nothingness", + config.route(genHeaders("bat2.com", "", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("regex_default", + config.route(genHeaders("bat2.com", "/foo", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ("regex_default", + config.route(genHeaders("bat2.com", " ", "GET"), 0)->routeEntry()->clusterName()); + + // Regular Expression matching with query string params + EXPECT_EQ( + "clock", + config.route(genHeaders("bat.com", "/tic?tac=true", "GET"), 0)->routeEntry()->clusterName()); + EXPECT_EQ( + "regex_default", + config.route(genHeaders("bat.com", "/tac?tic=true", "GET"), 0)->routeEntry()->clusterName()); + + // Virtual cluster testing. + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/rides", "GET"); + EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/rides/blah", "POST"); + EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/rides", "POST"); + EXPECT_EQ("ride_request", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/rides/123", "PUT"); + EXPECT_EQ("update_ride", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/rides/123/456", "POST"); + EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = + genHeaders("api.lyft.com", "/users/123/chargeaccounts", "POST"); + EXPECT_EQ("cc_add", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = + genHeaders("api.lyft.com", "/users/123/chargeaccounts/hello123", "PUT"); + EXPECT_EQ("cc_add", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = + genHeaders("api.lyft.com", "/users/123/chargeaccounts/validate", "PUT"); + EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/foo/bar", "PUT"); + EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/users", "POST"); + EXPECT_EQ("create_user_login", + virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/users/123", "PUT"); + EXPECT_EQ("update_user", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } + { + Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/something/else", "GET"); + EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); + } +} + TEST_F(RouteMatcherTest, TestRoutes) { const std::string yaml = R"EOF( virtual_hosts: @@ -171,20 +331,28 @@ TEST_F(RouteMatcherTest, TestRoutes) { - bat.com routes: - match: - regex: "/t[io]c" + safe_regex: + google_re2: {} + regex: "/t[io]c" route: cluster: clock - match: - regex: "/baa+" + safe_regex: + google_re2: {} + regex: "/baa+" route: cluster: sheep - match: - regex: ".*/\\d{3}$" + safe_regex: + google_re2: {} + regex: ".*/\\d{3}$" route: cluster: three_numbers prefix_rewrite: "/rewrote" - match: - regex: ".*" + safe_regex: + google_re2: {} + regex: ".*" route: cluster: regex_default - name: regex2 @@ -192,11 +360,9 @@ TEST_F(RouteMatcherTest, TestRoutes) { - bat2.com routes: - match: - regex: '' - route: - cluster: nothingness - - match: - regex: ".*" + safe_regex: + google_re2: {} + regex: ".*" route: cluster: regex_default - name: default @@ -277,26 +443,53 @@ TEST_F(RouteMatcherTest, TestRoutes) { cluster: instant-server timeout: 30s virtual_clusters: - - pattern: "^/rides$" - method: POST + - headers: + - name: ":path" + safe_regex_match: + google_re2: {} + regex: "^/rides$" + - name: ":method" + exact_match: POST name: ride_request - - pattern: "^/rides/\\d+$" - method: PUT + - headers: + - name: ":path" + safe_regex_match: + google_re2: {} + regex: "^/rides/\\d+$" + - name: ":method" + exact_match: PUT name: update_ride - - pattern: "^/users/\\d+/chargeaccounts$" - method: POST - name: cc_add - - pattern: "^/users/\\d+/chargeaccounts/(?!validate)\\w+$" - method: PUT + - headers: + - name: ":path" + safe_regex_match: + google_re2: {} + regex: "^/users/\\d+/chargeaccounts$" + - name: ":method" + exact_match: POST name: cc_add - - pattern: "^/users$" - method: POST + - headers: + - name: ":path" + safe_regex_match: + google_re2: {} + regex: "^/users$" + - name: ":method" + exact_match: POST name: create_user_login - - pattern: "^/users/\\d+$" - method: PUT + - headers: + - name: ":path" + safe_regex_match: + google_re2: {} + regex: "^/users/\\d+$" + - name: ":method" + exact_match: PUT name: update_user - - pattern: "^/users/\\d+/location$" - method: POST + - headers: + - name: ":path" + safe_regex_match: + google_re2: {} + regex: "^/users/\\d+/location$" + - name: ":method" + exact_match: POST name: ulu )EOF"; @@ -364,8 +557,6 @@ TEST_F(RouteMatcherTest, TestRoutes) { config.route(genHeaders("bat.com", "/baaaaaaaaaaaa", "GET"), 0)->routeEntry()->clusterName()); EXPECT_EQ("regex_default", config.route(genHeaders("bat.com", "/ba", "GET"), 0)->routeEntry()->clusterName()); - EXPECT_EQ("nothingness", - config.route(genHeaders("bat2.com", "", "GET"), 0)->routeEntry()->clusterName()); EXPECT_EQ("regex_default", config.route(genHeaders("bat2.com", "/foo", "GET"), 0)->routeEntry()->clusterName()); EXPECT_EQ("regex_default", @@ -548,21 +739,6 @@ TEST_F(RouteMatcherTest, TestRoutes) { Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/rides/123/456", "POST"); EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); } - { - Http::TestHeaderMapImpl headers = - genHeaders("api.lyft.com", "/users/123/chargeaccounts", "POST"); - EXPECT_EQ("cc_add", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); - } - { - Http::TestHeaderMapImpl headers = - genHeaders("api.lyft.com", "/users/123/chargeaccounts/hello123", "PUT"); - EXPECT_EQ("cc_add", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); - } - { - Http::TestHeaderMapImpl headers = - genHeaders("api.lyft.com", "/users/123/chargeaccounts/validate", "PUT"); - EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); - } { Http::TestHeaderMapImpl headers = genHeaders("api.lyft.com", "/foo/bar", "PUT"); EXPECT_EQ("other", virtualClusterName(config.route(headers, 0)->routeEntry(), headers)); @@ -610,7 +786,8 @@ TEST_F(RouteMatcherTest, TestRoutesWithWildcardAndDefaultOnly) { config.route(genHeaders("example.com", "/", "GET"), 0)->routeEntry()->clusterName()); } -TEST_F(RouteMatcherTest, TestRoutesWithInvalidRegex) { +// When deprecating regex: this test can be removed. +TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(TestRoutesWithInvalidRegexLegacy)) { std::string invalid_route = R"EOF( virtual_hosts: - name: regex @@ -643,6 +820,66 @@ TEST_F(RouteMatcherTest, TestRoutesWithInvalidRegex) { EnvoyException, "Invalid regex '\\^/\\(\\+invalid\\)':"); } +TEST_F(RouteMatcherTest, TestRoutesWithInvalidRegex) { + std::string invalid_route = R"EOF( +virtual_hosts: + - name: regex + domains: ["*"] + routes: + - match: + safe_regex: + google_re2: {} + regex: "/(+invalid)" + route: { cluster: "regex" } + )EOF"; + + std::string invalid_virtual_cluster = R"EOF( +virtual_hosts: + - name: regex + domains: ["*"] + routes: + - match: { prefix: "/" } + route: { cluster: "regex" } + virtual_clusters: + name: "invalid" + headers: + name: "invalid" + safe_regex_match: + google_re2: {} + regex: "^/(+invalid)" + )EOF"; + + NiceMock stream_info; + + EXPECT_THROW_WITH_REGEX( + TestConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_route), factory_context_, true), + EnvoyException, "no argument for repetition operator:"); + + EXPECT_THROW_WITH_REGEX(TestConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_virtual_cluster), + factory_context_, true), + EnvoyException, "no argument for repetition operator"); +} + +// Virtual cluster that contains neither pattern nor regex. This must be checked while pattern is +// deprecated. +TEST_F(RouteMatcherTest, TestRoutesWithInvalidVirtualCluster) { + const std::string invalid_virtual_cluster = R"EOF( +virtual_hosts: + - name: regex + domains: ["*"] + routes: + - match: { prefix: "/" } + route: { cluster: "regex" } + virtual_clusters: + - name: "invalid" + )EOF"; + + EXPECT_THROW_WITH_REGEX(TestConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_virtual_cluster), + factory_context_, true), + EnvoyException, + "virtual clusters must define either 'pattern' or 'headers'"); +} + // Validates behavior of request_headers_to_add at router, vhost, and route levels. TEST_F(RouteMatcherTest, TestAddRemoveRequestHeaders) { const std::string yaml = R"EOF( @@ -1066,10 +1303,6 @@ TEST_F(RouteMatcherTest, Priority) { prefix: "/bar" route: cluster: local_service_grpc - virtual_clusters: - - pattern: "^/bar$" - method: POST - name: foo )EOF"; TestConfigImpl config(parseRouteConfigurationFromV2Yaml(yaml), factory_context_, true); @@ -1171,7 +1404,9 @@ TEST_F(RouteMatcherTest, HeaderMatchedRouting) { prefix: "/" headers: - name: test_header_pattern - regex_match: "^user=test-\\d+$" + safe_regex_match: + google_re2: {} + regex: "^user=test-\\d+$" route: cluster: local_service_with_header_pattern_set_regex - match: @@ -1261,7 +1496,8 @@ TEST_F(RouteMatcherTest, HeaderMatchedRouting) { } // Verify the fixes for https://github.com/envoyproxy/envoy/issues/2406 -TEST_F(RouteMatcherTest, InvalidHeaderMatchedRoutingConfig) { +// When removing regex_match this test can be removed entirely. +TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(InvalidHeaderMatchedRoutingConfigLegacy)) { std::string value_with_regex_chars = R"EOF( virtual_hosts: - name: local_service @@ -1296,7 +1532,46 @@ TEST_F(RouteMatcherTest, InvalidHeaderMatchedRoutingConfig) { EnvoyException, "Invalid regex"); } -TEST_F(RouteMatcherTest, QueryParamMatchedRouting) { +// Verify the fixes for https://github.com/envoyproxy/envoy/issues/2406 +TEST_F(RouteMatcherTest, InvalidHeaderMatchedRoutingConfig) { + std::string value_with_regex_chars = R"EOF( +virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + headers: + - name: test_header + exact_match: "(+not a regex)" + route: { cluster: "local_service" } + )EOF"; + + std::string invalid_regex = R"EOF( +virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + headers: + - name: test_header + safe_regex_match: + google_re2: {} + regex: "(+invalid regex)" + route: { cluster: "local_service" } + )EOF"; + + EXPECT_NO_THROW(TestConfigImpl(parseRouteConfigurationFromV2Yaml(value_with_regex_chars), + factory_context_, true)); + + EXPECT_THROW_WITH_REGEX( + TestConfigImpl(parseRouteConfigurationFromV2Yaml(invalid_regex), factory_context_, true), + EnvoyException, "no argument for repetition operator"); +} + +// When removing value: simply remove that section of the config and the relevant test. +TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(QueryParamMatchedRouting)) { const std::string yaml = R"EOF( virtual_hosts: - name: local_service @@ -1325,6 +1600,21 @@ TEST_F(RouteMatcherTest, QueryParamMatchedRouting) { - name: debug route: cluster: local_service_with_valueless_query_parameter + - match: + prefix: "/" + query_parameters: + - name: debug2 + present_match: true + route: + cluster: local_service_with_present_match_query_parameter + - match: + prefix: "/" + query_parameters: + - name: debug3 + string_match: + exact: foo + route: + cluster: local_service_with_string_match_query_parameter - match: prefix: "/" route: @@ -1364,6 +1654,18 @@ TEST_F(RouteMatcherTest, QueryParamMatchedRouting) { config.route(headers, 0)->routeEntry()->clusterName()); } + { + Http::TestHeaderMapImpl headers = genHeaders("example.com", "/?debug2", "GET"); + EXPECT_EQ("local_service_with_present_match_query_parameter", + config.route(headers, 0)->routeEntry()->clusterName()); + } + + { + Http::TestHeaderMapImpl headers = genHeaders("example.com", "/?debug3=foo", "GET"); + EXPECT_EQ("local_service_with_string_match_query_parameter", + config.route(headers, 0)->routeEntry()->clusterName()); + } + { Http::TestHeaderMapImpl headers = genHeaders("example.com", "/?debug=2", "GET"); EXPECT_EQ("local_service_with_valueless_query_parameter", @@ -1383,8 +1685,8 @@ TEST_F(RouteMatcherTest, QueryParamMatchedRouting) { } } -// Verify the fixes for https://github.com/envoyproxy/envoy/issues/2406 -TEST_F(RouteMatcherTest, InvalidQueryParamMatchedRoutingConfig) { +// When removing value: this test can be removed. +TEST_F(RouteMatcherTest, DEPRECATED_FEATURE_TEST(InvalidQueryParamMatchedRoutingConfig)) { std::string value_with_regex_chars = R"EOF( virtual_hosts: - name: local_service @@ -2213,7 +2515,11 @@ TEST_F(RouteMatcherTest, Shadow) { route: request_mirror_policy: cluster: some_cluster2 - runtime_key: foo + runtime_fraction: + default_value: + numerator: 20 + denominator: HUNDRED + runtime_key: foo cluster: www2 - match: prefix: "/baz" @@ -2253,7 +2559,8 @@ TEST_F(RouteMatcherTest, Shadow) { class RouteConfigurationV2 : public testing::Test, public ConfigImplTestBase {}; -TEST_F(RouteConfigurationV2, RequestMirrorPolicy) { +// When removing runtime_key: this test can be removed. +TEST_F(RouteConfigurationV2, DEPRECATED_FEATURE_TEST(RequestMirrorPolicy)) { const std::string yaml = R"EOF( name: foo virtual_hosts: @@ -4210,13 +4517,21 @@ TEST_F(RoutePropertyTest, excludeVHRateLimits) { EXPECT_TRUE(config_ptr->route(headers, 0)->routeEntry()->includeVirtualHostRateLimits()); } -TEST_F(RoutePropertyTest, TestVHostCorsConfig) { +// When allow_origin: and allow_origin_regex: are removed, simply remove them +// and the relevant checks below. +TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestVHostCorsConfig)) { const std::string yaml = R"EOF( virtual_hosts: - name: "default" domains: ["*"] cors: allow_origin: ["test-origin"] + allow_origin_regex: + - .*\.envoyproxy\.io + allow_origin_string_match: + - safe_regex: + google_re2: {} + regex: .*\.envoyproxy\.io allow_methods: "test-methods" allow_headers: "test-headers" expose_headers: "test-expose-headers" @@ -4258,7 +4573,7 @@ TEST_F(RoutePropertyTest, TestVHostCorsConfig) { EXPECT_EQ(cors_policy->enabled(), false); EXPECT_EQ(cors_policy->shadowEnabled(), true); - EXPECT_THAT(cors_policy->allowOrigins(), ElementsAreArray({"test-origin"})); + EXPECT_EQ(3, cors_policy->allowOrigins().size()); EXPECT_EQ(cors_policy->allowMethods(), "test-methods"); EXPECT_EQ(cors_policy->allowHeaders(), "test-headers"); EXPECT_EQ(cors_policy->exposeHeaders(), "test-expose-headers"); @@ -4277,7 +4592,8 @@ TEST_F(RoutePropertyTest, TestRouteCorsConfig) { route: cluster: "ats" cors: - allow_origin: ["test-origin"] + allow_origin_string_match: + - exact: "test-origin" allow_methods: "test-methods" allow_headers: "test-headers" expose_headers: "test-expose-headers" @@ -4311,7 +4627,7 @@ TEST_F(RoutePropertyTest, TestRouteCorsConfig) { EXPECT_EQ(cors_policy->enabled(), false); EXPECT_EQ(cors_policy->shadowEnabled(), true); - EXPECT_THAT(cors_policy->allowOrigins(), ElementsAreArray({"test-origin"})); + EXPECT_EQ(1, cors_policy->allowOrigins().size()); EXPECT_EQ(cors_policy->allowMethods(), "test-methods"); EXPECT_EQ(cors_policy->allowHeaders(), "test-headers"); EXPECT_EQ(cors_policy->exposeHeaders(), "test-expose-headers"); @@ -4319,7 +4635,8 @@ TEST_F(RoutePropertyTest, TestRouteCorsConfig) { EXPECT_EQ(cors_policy->allowCredentials(), true); } -TEST_F(RoutePropertyTest, TestVHostCorsLegacyConfig) { +// When allow-origin: is removed, this test can be removed. +TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TTestVHostCorsLegacyConfig)) { const std::string yaml = R"EOF( virtual_hosts: - name: default @@ -4350,7 +4667,7 @@ TEST_F(RoutePropertyTest, TestVHostCorsLegacyConfig) { EXPECT_EQ(cors_policy->enabled(), true); EXPECT_EQ(cors_policy->shadowEnabled(), false); - EXPECT_THAT(cors_policy->allowOrigins(), ElementsAreArray({"test-origin"})); + EXPECT_EQ(1, cors_policy->allowOrigins().size()); EXPECT_EQ(cors_policy->allowMethods(), "test-methods"); EXPECT_EQ(cors_policy->allowHeaders(), "test-headers"); EXPECT_EQ(cors_policy->exposeHeaders(), "test-expose-headers"); @@ -4358,7 +4675,8 @@ TEST_F(RoutePropertyTest, TestVHostCorsLegacyConfig) { EXPECT_EQ(cors_policy->allowCredentials(), true); } -TEST_F(RoutePropertyTest, TestRouteCorsLegacyConfig) { +// When allow-origin: is removed, this test can be removed. +TEST_F(RoutePropertyTest, DEPRECATED_FEATURE_TEST(TestRouteCorsLegacyConfig)) { const std::string yaml = R"EOF( virtual_hosts: - name: default @@ -4386,7 +4704,7 @@ TEST_F(RoutePropertyTest, TestRouteCorsLegacyConfig) { EXPECT_EQ(cors_policy->enabled(), true); EXPECT_EQ(cors_policy->shadowEnabled(), false); - EXPECT_THAT(cors_policy->allowOrigins(), ElementsAreArray({"test-origin"})); + EXPECT_EQ(1, cors_policy->allowOrigins().size()); EXPECT_EQ(cors_policy->allowMethods(), "test-methods"); EXPECT_EQ(cors_policy->allowHeaders(), "test-headers"); EXPECT_EQ(cors_policy->exposeHeaders(), "test-expose-headers"); @@ -4856,7 +5174,10 @@ name: foo - name: bar domains: ["*"] routes: - - match: { regex: "/rege[xy]" } + - match: + safe_regex: + google_re2: {} + regex: "/rege[xy]" route: { cluster: ww2 } - match: { path: "/exact-path" } route: { cluster: ww2 } @@ -4895,12 +5216,18 @@ name: foo - name: bar domains: ["*"] routes: - - match: { regex: "/first" } + - match: + safe_regex: + google_re2: {} + regex: "/first" tracing: client_sampling: numerator: 1 route: { cluster: ww2 } - - match: { regex: "/second" } + - match: + safe_regex: + google_re2: {} + regex: "/second" tracing: overall_sampling: numerator: 1 @@ -4956,7 +5283,10 @@ name: AllRedirects redirect: { prefix_rewrite: "/new/path/" } - match: { prefix: "/host/prefix" } redirect: { host_redirect: new.lyft.com, prefix_rewrite: "/new/prefix"} - - match: { regex: "/[r][e][g][e][x].*"} + - match: + safe_regex: + google_re2: {} + regex: "/[r][e][g][e][x].*" redirect: { prefix_rewrite: "/new/regex-prefix/" } - match: { prefix: "/http/prefix"} redirect: { prefix_rewrite: "/https/prefix" , https_redirect: true } @@ -5163,7 +5493,9 @@ name: foo prefix: "/" headers: - name: test_header_pattern - regex_match: "^user=test-\\d+$" + safe_regex_match: + google_re2: {} + regex: "^user=test-\\d+$" route: cluster: local_service_with_header_pattern_set_regex - match: @@ -5315,7 +5647,10 @@ name: RegexNoMatch - name: regex domains: [regex.lyft.com] routes: - - match: { regex: "/regex"} + - match: + safe_regex: + google_re2: {} + regex: "/regex" route: { cluster: some-cluster } )EOF"; @@ -5343,7 +5678,10 @@ name: NoIdleTimeout - name: regex domains: [idle.lyft.com] routes: - - match: { regex: "/regex"} + - match: + safe_regex: + google_re2: {} + regex: "/regex" route: cluster: some-cluster )EOF"; @@ -5361,7 +5699,10 @@ name: ZeroIdleTimeout - name: regex domains: [idle.lyft.com] routes: - - match: { regex: "/regex"} + - match: + safe_regex: + google_re2: {} + regex: "/regex" route: cluster: some-cluster idle_timeout: 0s @@ -5380,7 +5721,10 @@ name: ExplicitIdleTimeout - name: regex domains: [idle.lyft.com] routes: - - match: { regex: "/regex"} + - match: + safe_regex: + google_re2: {} + regex: "/regex" route: cluster: some-cluster idle_timeout: 7s @@ -5400,7 +5744,10 @@ name: RetriableStatusCodes - name: regex domains: [idle.lyft.com] routes: - - match: { regex: "/regex"} + - match: + safe_regex: + google_re2: {} + regex: "/regex" route: cluster: some-cluster retry_policy: @@ -5422,7 +5769,10 @@ name: RetriableStatusCodes - name: regex domains: [idle.lyft.com] routes: - - match: { regex: "/regex"} + - match: + safe_regex: + google_re2: {} + regex: "/regex" route: cluster: some-cluster upgrade_configs: @@ -5446,7 +5796,10 @@ name: RetriableStatusCodes - name: regex domains: [idle.lyft.com] routes: - - match: { regex: "/regex"} + - match: + safe_regex: + google_re2: {} + regex: "/regex" route: cluster: some-cluster upgrade_configs: @@ -5469,7 +5822,10 @@ name: RetriableStatusCodes - name: regex domains: [idle.lyft.com] routes: - - match: { regex: "/regex"} + - match: + safe_regex: + google_re2: {} + regex: "/regex" route: cluster: some-cluster retry_policy: diff --git a/test/common/router/header_formatter_test.cc b/test/common/router/header_formatter_test.cc index 3ab5ead740..34584a4420 100644 --- a/test/common/router/header_formatter_test.cc +++ b/test/common/router/header_formatter_test.cc @@ -89,28 +89,28 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithProtocolVariable) { TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerUriSanVariableSingleSan) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); const std::vector sans{"san"}; - ON_CALL(connection_info, uriSanPeerCertificate()).WillByDefault(Return(sans)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + ON_CALL(*connection_info, uriSanPeerCertificate()).WillByDefault(Return(sans)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_URI_SAN", "san"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerUriSanVariableMultipleSans) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); const std::vector sans{"san1", "san2"}; - ON_CALL(connection_info, uriSanPeerCertificate()).WillByDefault(Return(sans)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + ON_CALL(*connection_info, uriSanPeerCertificate()).WillByDefault(Return(sans)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_URI_SAN", "san1,san2"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerUriSanEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, uriSanPeerCertificate()) + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, uriSanPeerCertificate()) .WillByDefault(Return(std::vector())); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_URI_SAN", EMPTY_STRING); } @@ -122,28 +122,28 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerNoTls) { TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamLocalUriSanVariableSingleSan) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); const std::vector sans{"san"}; - ON_CALL(connection_info, uriSanLocalCertificate()).WillByDefault(Return(sans)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + ON_CALL(*connection_info, uriSanLocalCertificate()).WillByDefault(Return(sans)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_LOCAL_URI_SAN", "san"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamLocalUriSanVariableMultipleSans) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); const std::vector sans{"san1", "san2"}; - ON_CALL(connection_info, uriSanLocalCertificate()).WillByDefault(Return(sans)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + ON_CALL(*connection_info, uriSanLocalCertificate()).WillByDefault(Return(sans)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_LOCAL_URI_SAN", "san1,san2"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamLocalUriSanVariableNoSans) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, uriSanLocalCertificate()) + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, uriSanLocalCertificate()) .WillByDefault(Return(std::vector())); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_LOCAL_URI_SAN", EMPTY_STRING); } @@ -155,17 +155,19 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamLocalUriSanNoTls) TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamLocalSubject) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, subjectLocalCertificate()).WillByDefault(Return("subject")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + std::string subject = "subject"; + ON_CALL(*connection_info, subjectLocalCertificate()).WillByDefault(ReturnRef(subject)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_LOCAL_SUBJECT", "subject"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamLocalSubjectEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, subjectLocalCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + std::string subject; + ON_CALL(*connection_info, subjectLocalCertificate()).WillByDefault(ReturnRef(subject)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_LOCAL_SUBJECT", EMPTY_STRING); } @@ -177,17 +179,19 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamLocalSubjectNoTls) TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsSessionId) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, sessionId()).WillByDefault(Return("deadbeef")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + std::string session_id = "deadbeef"; + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(session_id)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_TLS_SESSION_ID", "deadbeef"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsSessionIdEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, sessionId()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + std::string session_id; + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(session_id)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_TLS_SESSION_ID", EMPTY_STRING); } @@ -199,18 +203,18 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsSessionIdNoTls) TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsCipher) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, ciphersuiteString()) + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, ciphersuiteString()) .WillByDefault(Return("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_TLS_CIPHER", "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsCipherEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, ciphersuiteString()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, ciphersuiteString()).WillByDefault(Return("")); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_TLS_CIPHER", EMPTY_STRING); } @@ -222,17 +226,18 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsCipherNoTls) { TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsVersion) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("TLSv1.2")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + std::string tls_version = "TLSv1.2"; + ON_CALL(*connection_info, tlsVersion()).WillByDefault(ReturnRef(tls_version)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_TLS_VERSION", "TLSv1.2"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsVersionEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, tlsVersion()).WillByDefault(ReturnRef(EMPTY_STRING)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_TLS_VERSION", EMPTY_STRING); } @@ -244,20 +249,20 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamTlsVersionNoTls) { TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerFingerprint) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); std::string expected_sha = "685a2db593d5f86d346cb1a297009c3b467ad77f1944aa799039a2fb3d531f3f"; - ON_CALL(connection_info, sha256PeerCertificateDigest()).WillByDefault(ReturnRef(expected_sha)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + ON_CALL(*connection_info, sha256PeerCertificateDigest()).WillByDefault(ReturnRef(expected_sha)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_FINGERPRINT_256", "685a2db593d5f86d346cb1a297009c3b467ad77f1944aa799039a2fb3d531f3f"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerFingerprintEmpty) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); std::string expected_sha; - ON_CALL(connection_info, sha256PeerCertificateDigest()).WillByDefault(ReturnRef(expected_sha)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + ON_CALL(*connection_info, sha256PeerCertificateDigest()).WillByDefault(ReturnRef(expected_sha)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_FINGERPRINT_256", EMPTY_STRING); } @@ -269,17 +274,19 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerFingerprintNoT TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerSerial) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, serialNumberPeerCertificate()).WillByDefault(Return("b8b5ecc898f2124a")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + const std::string serial_number = "b8b5ecc898f2124a"; + ON_CALL(*connection_info, serialNumberPeerCertificate()).WillByDefault(ReturnRef(serial_number)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_SERIAL", "b8b5ecc898f2124a"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerSerialEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, serialNumberPeerCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + const std::string serial_number; + ON_CALL(*connection_info, serialNumberPeerCertificate()).WillByDefault(ReturnRef(serial_number)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_SERIAL", EMPTY_STRING); } @@ -291,20 +298,21 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerSerialNoTls) { TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerIssuer) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, issuerPeerCertificate()) - .WillByDefault( - Return("CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + const std::string issuer_peer = + "CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US"; + ON_CALL(*connection_info, issuerPeerCertificate()).WillByDefault(ReturnRef(issuer_peer)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_ISSUER", "CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerIssuerEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, issuerPeerCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + const std::string issuer_peer; + ON_CALL(*connection_info, issuerPeerCertificate()).WillByDefault(ReturnRef(issuer_peer)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_ISSUER", EMPTY_STRING); } @@ -316,20 +324,21 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerIssuerNoTls) { TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerSubject) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, subjectPeerCertificate()) - .WillByDefault( - Return("CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + const std::string subject_peer = + "CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US"; + ON_CALL(*connection_info, subjectPeerCertificate()).WillByDefault(ReturnRef(subject_peer)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_SUBJECT", "CN=Test CA,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerSubjectEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, subjectPeerCertificate()).WillByDefault(Return("")); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + const std::string subject_peer; + ON_CALL(*connection_info, subjectPeerCertificate()).WillByDefault(ReturnRef(subject_peer)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_SUBJECT", EMPTY_STRING); } @@ -341,21 +350,21 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerSubjectNoTls) TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerCert) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); std::string expected_cert = ""; - ON_CALL(connection_info, urlEncodedPemEncodedPeerCertificate()) + ON_CALL(*connection_info, urlEncodedPemEncodedPeerCertificate()) .WillByDefault(ReturnRef(expected_cert)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_CERT", expected_cert); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerCertEmpty) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); std::string expected_cert; - ON_CALL(connection_info, urlEncodedPemEncodedPeerCertificate()) + ON_CALL(*connection_info, urlEncodedPemEncodedPeerCertificate()) .WillByDefault(ReturnRef(expected_cert)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_CERT", EMPTY_STRING); } @@ -367,20 +376,20 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerCertNoTls) { TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerCertVStart) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); absl::Time abslStartTime = TestUtility::parseTime("Dec 18 01:50:34 2018 GMT", "%b %e %H:%M:%S %Y GMT"); SystemTime startTime = absl::ToChronoTime(abslStartTime); - ON_CALL(connection_info, validFromPeerCertificate()).WillByDefault(Return(startTime)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + ON_CALL(*connection_info, validFromPeerCertificate()).WillByDefault(Return(startTime)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_CERT_V_START", "2018-12-18T01:50:34.000Z"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerCertVStartEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, validFromPeerCertificate()).WillByDefault(Return(absl::nullopt)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, validFromPeerCertificate()).WillByDefault(Return(absl::nullopt)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_CERT_V_START", EMPTY_STRING); } @@ -392,20 +401,20 @@ TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerCertVStartNoTl TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerCertVEnd) { NiceMock stream_info; - NiceMock connection_info; + auto connection_info = std::make_shared>(); absl::Time abslStartTime = TestUtility::parseTime("Dec 17 01:50:34 2020 GMT", "%b %e %H:%M:%S %Y GMT"); SystemTime startTime = absl::ToChronoTime(abslStartTime); - ON_CALL(connection_info, expirationPeerCertificate()).WillByDefault(Return(startTime)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + ON_CALL(*connection_info, expirationPeerCertificate()).WillByDefault(Return(startTime)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_CERT_V_END", "2020-12-17T01:50:34.000Z"); } TEST_F(StreamInfoHeaderFormatterTest, TestFormatWithDownstreamPeerCertVEndEmpty) { NiceMock stream_info; - NiceMock connection_info; - ON_CALL(connection_info, expirationPeerCertificate()).WillByDefault(Return(absl::nullopt)); - EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, expirationPeerCertificate()).WillByDefault(Return(absl::nullopt)); + EXPECT_CALL(stream_info, downstreamSslConnection()).WillRepeatedly(Return(connection_info)); testFormatting(stream_info, "DOWNSTREAM_PEER_CERT_V_END", EMPTY_STRING); } @@ -505,7 +514,7 @@ TEST_F(StreamInfoHeaderFormatterTest, ValidateLimitsOnUserDefinedHeaders) { header->mutable_header()->set_key("header_name"); header->mutable_header()->set_value(long_string); header->mutable_append()->set_value(true); - EXPECT_THROW_WITH_REGEX(MessageUtil::validate(route), ProtoValidationException, + EXPECT_THROW_WITH_REGEX(TestUtility::validate(route), ProtoValidationException, "Proto constraint validation failed.*"); } { @@ -516,7 +525,7 @@ TEST_F(StreamInfoHeaderFormatterTest, ValidateLimitsOnUserDefinedHeaders) { header->mutable_header()->set_key("header_name"); header->mutable_header()->set_value("value"); } - EXPECT_THROW_WITH_REGEX(MessageUtil::validate(route), ProtoValidationException, + EXPECT_THROW_WITH_REGEX(TestUtility::validate(route), ProtoValidationException, "Proto constraint validation failed.*"); } } diff --git a/test/common/router/header_parser_fuzz_test.cc b/test/common/router/header_parser_fuzz_test.cc index 451aae702c..f2ca239bae 100644 --- a/test/common/router/header_parser_fuzz_test.cc +++ b/test/common/router/header_parser_fuzz_test.cc @@ -11,7 +11,7 @@ namespace { DEFINE_PROTO_FUZZER(const test::common::router::TestCase& input) { try { - MessageUtil::validate(input); + TestUtility::validate(input); auto headers_to_add = replaceInvalidHeaders(input.headers_to_add()); Protobuf::RepeatedPtrField headers_to_remove; for (const auto& s : input.headers_to_remove()) { @@ -20,8 +20,7 @@ DEFINE_PROTO_FUZZER(const test::common::router::TestCase& input) { Router::HeaderParserPtr parser = Router::HeaderParser::configure(headers_to_add, headers_to_remove); Http::HeaderMapImpl header_map; - NiceMock connection_info; - TestStreamInfo test_stream_info = fromStreamInfo(input.stream_info(), &connection_info); + TestStreamInfo test_stream_info = fromStreamInfo(input.stream_info()); parser->evaluateHeaders(header_map, test_stream_info); ENVOY_LOG_MISC(trace, "Success"); } catch (const EnvoyException& e) { diff --git a/test/common/router/rds_impl_test.cc b/test/common/router/rds_impl_test.cc index e84bb925b3..b581017f3f 100644 --- a/test/common/router/rds_impl_test.cc +++ b/test/common/router/rds_impl_test.cc @@ -29,9 +29,6 @@ using testing::_; using testing::Eq; using testing::InSequence; using testing::Invoke; -using testing::Return; -using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Router { @@ -266,8 +263,8 @@ class RouteConfigProviderManagerImplTest : public RdsTestBase { // Get a RouteConfigProvider. This one should create an entry in the RouteConfigProviderManager. rds_.set_route_config_name("foo_route_config"); rds_.mutable_config_source()->set_path("foo_path"); - provider_ = route_config_provider_manager_->createRdsRouteConfigProvider(rds_, factory_context_, - "foo_prefix."); + provider_ = route_config_provider_manager_->createRdsRouteConfigProvider( + rds_, factory_context_, "foo_prefix.", factory_context_.initManager()); rds_callbacks_ = factory_context_.cluster_manager_.subscription_factory_.callbacks_; } @@ -295,7 +292,7 @@ envoy::api::v2::RouteConfiguration parseRouteConfigurationFromV2Yaml(const std:: TEST_F(RouteConfigProviderManagerImplTest, ConfigDump) { auto message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"](); const auto& route_config_dump = - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( *message_ptr); // No routes at all, no last_updated timestamp @@ -325,7 +322,7 @@ name: foo parseRouteConfigurationFromV2Yaml(config_yaml), factory_context_); message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"](); const auto& route_config_dump2 = - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( *message_ptr); TestUtility::loadFromYaml(R"EOF( static_route_configs: @@ -368,7 +365,7 @@ name: foo rds_callbacks_->onConfigUpdate(response1.resources(), response1.version_info()); message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"](); const auto& route_config_dump3 = - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( *message_ptr); TestUtility::loadFromYaml(R"EOF( static_route_configs: @@ -419,7 +416,7 @@ name: foo_route_config "1"); RouteConfigProviderPtr provider2 = route_config_provider_manager_->createRdsRouteConfigProvider( - rds_, factory_context_, "foo_prefix"); + rds_, factory_context_, "foo_prefix", factory_context_.initManager()); // provider2 should have route config immediately after create EXPECT_TRUE(provider2->configInfo().has_value()); @@ -433,7 +430,7 @@ name: foo_route_config rds2.set_route_config_name("foo_route_config"); rds2.mutable_config_source()->set_path("bar_path"); RouteConfigProviderPtr provider3 = route_config_provider_manager_->createRdsRouteConfigProvider( - rds2, factory_context_, "foo_prefix"); + rds2, factory_context_, "foo_prefix", factory_context_.initManager()); EXPECT_NE(provider3, provider_); factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate(route_configs, "provider3"); @@ -493,6 +490,72 @@ TEST_F(RouteConfigProviderManagerImplTest, onConfigUpdateWrongSize) { EnvoyException, "Unexpected RDS resource length: 2"); } +// Regression test for https://github.com/envoyproxy/envoy/issues/7939 +TEST_F(RouteConfigProviderManagerImplTest, ConfigDumpAfterConfigRejected) { + auto message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"](); + const auto& route_config_dump = + TestUtility::downcastAndValidate( + *message_ptr); + + // No routes at all, no last_updated timestamp + envoy::admin::v2alpha::RoutesConfigDump expected_route_config_dump; + TestUtility::loadFromYaml(R"EOF( +static_route_configs: +dynamic_route_configs: +)EOF", + expected_route_config_dump); + EXPECT_EQ(expected_route_config_dump.DebugString(), route_config_dump.DebugString()); + + timeSystem().setSystemTime(std::chrono::milliseconds(1234567891234)); + + // dynamic. + setup(); + EXPECT_CALL(*factory_context_.cluster_manager_.subscription_factory_.subscription_, start(_)); + factory_context_.init_manager_.initialize(init_watcher_); + + const std::string response1_yaml = R"EOF( +version_info: '1' +resources: +- "@type": type.googleapis.com/envoy.api.v2.RouteConfiguration + name: foo_route_config + virtual_hosts: + - name: integration + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + cluster_header: ":authority" + - name: duplicate + domains: + - "*" + routes: + - match: + prefix: "/foo" + route: + cluster_header: ":authority" +)EOF"; + auto response1 = TestUtility::parseYaml(response1_yaml); + + EXPECT_CALL(init_watcher_, ready()); + + EXPECT_THROW_WITH_MESSAGE( + rds_callbacks_->onConfigUpdate(response1.resources(), response1.version_info()), + EnvoyException, "Only a single wildcard domain is permitted"); + + message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["routes"](); + const auto& route_config_dump3 = + TestUtility::downcastAndValidate( + *message_ptr); + TestUtility::loadFromYaml(R"EOF( +static_route_configs: +dynamic_route_configs: +)EOF", + expected_route_config_dump); + EXPECT_EQ(expected_route_config_dump.DebugString(), route_config_dump3.DebugString()); +} + } // namespace } // namespace Router } // namespace Envoy diff --git a/test/common/router/retry_state_impl_test.cc b/test/common/router/retry_state_impl_test.cc index 6120470db1..64de11d021 100644 --- a/test/common/router/retry_state_impl_test.cc +++ b/test/common/router/retry_state_impl_test.cc @@ -43,7 +43,7 @@ class RouterRetryStateImplTest : public testing::Test { void expectTimerCreateAndEnable() { retry_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*retry_timer_, enableTimer(_)); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)); } NiceMock policy_; @@ -466,12 +466,12 @@ TEST_F(RouterRetryStateImplTest, MaxRetriesHeader) { EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); - EXPECT_CALL(*retry_timer_, enableTimer(_)); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); - EXPECT_CALL(*retry_timer_, enableTimer(_)); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); @@ -493,19 +493,19 @@ TEST_F(RouterRetryStateImplTest, Backoff) { EXPECT_CALL(random_, random()).WillOnce(Return(49)); retry_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(24))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(24), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); EXPECT_CALL(random_, random()).WillOnce(Return(149)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(74))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(74), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); EXPECT_CALL(random_, random()).WillOnce(Return(349)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(174))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(174), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); @@ -530,25 +530,25 @@ TEST_F(RouterRetryStateImplTest, CustomBackOffInterval) { EXPECT_CALL(random_, random()).WillOnce(Return(149)); retry_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(49))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(49), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); EXPECT_CALL(random_, random()).WillOnce(Return(350)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(50))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); EXPECT_CALL(random_, random()).WillOnce(Return(751)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(51))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(51), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); EXPECT_CALL(random_, random()).WillOnce(Return(1499)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1200))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1200), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); @@ -565,25 +565,25 @@ TEST_F(RouterRetryStateImplTest, CustomBackOffIntervalDefaultMax) { EXPECT_CALL(random_, random()).WillOnce(Return(149)); retry_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(49))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(49), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); EXPECT_CALL(random_, random()).WillOnce(Return(350)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(50))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(50), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); EXPECT_CALL(random_, random()).WillOnce(Return(751)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(51))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(51), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); EXPECT_CALL(random_, random()).WillOnce(Return(1499)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_EQ(RetryStatus::Yes, state_->shouldRetryReset(connect_failure_, callback_)); EXPECT_CALL(callback_ready_, ready()); retry_timer_->invokeCallback(); diff --git a/test/common/router/route_fuzz_test.cc b/test/common/router/route_fuzz_test.cc index 865c30f3f8..b2cecf913b 100644 --- a/test/common/router/route_fuzz_test.cc +++ b/test/common/router/route_fuzz_test.cc @@ -72,7 +72,7 @@ DEFINE_PROTO_FUZZER(const test::common::router::RouteTestCase& input) { try { NiceMock stream_info; NiceMock factory_context; - MessageUtil::validate(input.config()); + TestUtility::validate(input.config()); ConfigImpl config(cleanRouteConfig(input.config()), factory_context, true); Http::TestHeaderMapImpl headers = Fuzz::fromHeaders(input.headers()); // It's a precondition of routing that {:authority, :path, x-forwarded-proto} headers exists, diff --git a/test/common/router/router_ratelimit_test.cc b/test/common/router/router_ratelimit_test.cc index f49f4e943d..c5cd117dda 100644 --- a/test/common/router/router_ratelimit_test.cc +++ b/test/common/router/router_ratelimit_test.cc @@ -20,9 +20,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; -using testing::ReturnRef; namespace Envoy { namespace Router { @@ -31,7 +29,7 @@ namespace { envoy::api::v2::route::RateLimit parseRateLimitFromV2Yaml(const std::string& yaml_string) { envoy::api::v2::route::RateLimit rate_limit; TestUtility::loadFromYaml(yaml_string, rate_limit); - MessageUtil::validate(rate_limit); + TestUtility::validate(rate_limit); return rate_limit; } diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 770ce8bef6..3292f4ce9e 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -27,6 +27,7 @@ #include "test/test_common/environment.h" #include "test/test_common/printers.h" #include "test/test_common/simulated_time_system.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -44,11 +45,9 @@ using testing::Invoke; using testing::Matcher; using testing::MockFunction; using testing::NiceMock; -using testing::Ref; using testing::Return; using testing::ReturnPointee; using testing::ReturnRef; -using testing::SaveArg; using testing::StartsWith; namespace Envoy { @@ -106,13 +105,13 @@ class RouterTestBase : public testing::Test { void expectResponseTimerCreate() { response_timeout_ = new Event::MockTimer(&callbacks_.dispatcher_); - EXPECT_CALL(*response_timeout_, enableTimer(_)); + EXPECT_CALL(*response_timeout_, enableTimer(_, _)); EXPECT_CALL(*response_timeout_, disableTimer()); } void expectPerTryTimerCreate() { per_try_timeout_ = new Event::MockTimer(&callbacks_.dispatcher_); - EXPECT_CALL(*per_try_timeout_, enableTimer(_)); + EXPECT_CALL(*per_try_timeout_, enableTimer(_, _)); EXPECT_CALL(*per_try_timeout_, disableTimer()); } @@ -205,7 +204,8 @@ class RouterTestBase : public testing::Test { [&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder_ = &decoder; - callbacks.onPoolReady(original_encoder_, cm_.conn_pool_.host_); + EXPECT_CALL(callbacks_.dispatcher_, setTrackedObject(_)).Times(testing::AtLeast(2)); + callbacks.onPoolReady(original_encoder_, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); HttpTestUtility::addDefaultHeaders(default_request_headers_); @@ -258,6 +258,7 @@ class RouterTestBase : public testing::Test { Http::HeaderMapPtr redirect_headers_{ new Http::TestHeaderMapImpl{{":status", "302"}, {"location", "http://www.foo.com"}}}; NiceMock span_; + NiceMock upstream_stream_info_; }; class RouterTest : public RouterTestBase { @@ -496,7 +497,7 @@ TEST_F(RouterTest, AddCookie) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return &cancellable_; })); @@ -545,7 +546,7 @@ TEST_F(RouterTest, AddCookieNoDuplicate) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return &cancellable_; })); @@ -592,7 +593,7 @@ TEST_F(RouterTest, AddMultipleCookies) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return &cancellable_; })); @@ -776,7 +777,7 @@ TEST_F(RouterTest, ResponseCodeDetailsSetByUpstream) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -801,7 +802,7 @@ TEST_F(RouterTest, EnvoyUpstreamServiceTime) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -839,7 +840,7 @@ void RouterTestBase::testAppendCluster(absl::optional clu .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -891,7 +892,7 @@ void RouterTestBase::testAppendUpstreamHost( .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1012,7 +1013,7 @@ TEST_F(RouterTestSuppressEnvoyHeaders, EnvoyUpstreamServiceTime) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1040,7 +1041,7 @@ TEST_F(RouterTest, NoRetriesOverflow) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1063,7 +1064,7 @@ TEST_F(RouterTest, NoRetriesOverflow) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); router_.retry_state_->callback_(); @@ -1086,7 +1087,7 @@ TEST_F(RouterTest, ResetDuringEncodeHeaders) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -1116,7 +1117,7 @@ TEST_F(RouterTest, UpstreamTimeout) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) @@ -1159,7 +1160,7 @@ TEST_F(RouterTest, GrpcOkTrailersOnly) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1176,14 +1177,17 @@ TEST_F(RouterTest, GrpcOkTrailersOnly) { } // Validate gRPC AlreadyExists response stats are sane when response is trailers only. -TEST_F(RouterTest, GrpcAlreadyExistsTrailersOnly) { +TEST_F(RouterTest, GrpcAlreadyExistsTrailersOnlyRuntimeGuard) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.outlier_detection_support_for_grpc_status", "false"}}); NiceMock encoder1; Http::StreamDecoder* response_decoder = nullptr; EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1199,15 +1203,101 @@ TEST_F(RouterTest, GrpcAlreadyExistsTrailersOnly) { EXPECT_TRUE(verifyHostUpstreamStats(1, 0)); } +// Validate gRPC AlreadyExists response stats are sane when response is trailers only. +TEST_F(RouterTest, GrpcAlreadyExistsTrailersOnly) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.outlier_detection_support_for_grpc_status", "true"}}); + NiceMock encoder1; + Http::StreamDecoder* response_decoder = nullptr; + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + expectResponseTimerCreate(); + + Http::TestHeaderMapImpl headers{{"content-type", "application/grpc"}, {"grpc-timeout", "20S"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_.decodeHeaders(headers, true); + + Http::HeaderMapPtr response_headers( + new Http::TestHeaderMapImpl{{":status", "200"}, {"grpc-status", "6"}}); + EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, putHttpResponseCode(409)); + response_decoder->decodeHeaders(std::move(response_headers), true); + EXPECT_TRUE(verifyHostUpstreamStats(1, 0)); +} + +// Validate gRPC Unavailable response stats are sane when response is trailers only. +TEST_F(RouterTest, GrpcOutlierDetectionUnavailableStatusCodeRuntimeGuard) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.outlier_detection_support_for_grpc_status", "false"}}); + NiceMock encoder1; + Http::StreamDecoder* response_decoder = nullptr; + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + expectResponseTimerCreate(); + + Http::TestHeaderMapImpl headers{{"content-type", "application/grpc"}, {"grpc-timeout", "20S"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_.decodeHeaders(headers, true); + + Http::HeaderMapPtr response_headers( + new Http::TestHeaderMapImpl{{":status", "200"}, {"grpc-status", "14"}}); + // Outlier detector will use the gRPC response status code. + EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, putHttpResponseCode(200)); + response_decoder->decodeHeaders(std::move(response_headers), true); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); +} + +// Validate gRPC Unavailable response stats are sane when response is trailers only. +TEST_F(RouterTest, GrpcOutlierDetectionUnavailableStatusCode) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.outlier_detection_support_for_grpc_status", "true"}}); + NiceMock encoder1; + Http::StreamDecoder* response_decoder = nullptr; + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + expectResponseTimerCreate(); + + Http::TestHeaderMapImpl headers{{"content-type", "application/grpc"}, {"grpc-timeout", "20S"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_.decodeHeaders(headers, true); + + Http::HeaderMapPtr response_headers( + new Http::TestHeaderMapImpl{{":status", "200"}, {"grpc-status", "14"}}); + // Outlier detector will use the gRPC response status code. + EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, putHttpResponseCode(503)); + response_decoder->decodeHeaders(std::move(response_headers), true); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); +} + // Validate gRPC Internal response stats are sane when response is trailers only. -TEST_F(RouterTest, GrpcInternalTrailersOnly) { +TEST_F(RouterTest, GrpcInternalTrailersOnlyRuntimeGuard) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.outlier_detection_support_for_grpc_status", "false"}}); NiceMock encoder1; Http::StreamDecoder* response_decoder = nullptr; EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1223,6 +1313,33 @@ TEST_F(RouterTest, GrpcInternalTrailersOnly) { EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); } +// Validate gRPC Internal response stats are sane when response is trailers only. +TEST_F(RouterTest, GrpcInternalTrailersOnly) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.outlier_detection_support_for_grpc_status", "true"}}); + NiceMock encoder1; + Http::StreamDecoder* response_decoder = nullptr; + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + expectResponseTimerCreate(); + + Http::TestHeaderMapImpl headers{{"content-type", "application/grpc"}, {"grpc-timeout", "20S"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_.decodeHeaders(headers, true); + + Http::HeaderMapPtr response_headers( + new Http::TestHeaderMapImpl{{":status", "200"}, {"grpc-status", "13"}}); + EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, putHttpResponseCode(500)); + response_decoder->decodeHeaders(std::move(response_headers), true); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); +} + // Validate gRPC response stats are sane when response is ended in a DATA // frame. TEST_F(RouterTest, GrpcDataEndStream) { @@ -1232,7 +1349,7 @@ TEST_F(RouterTest, GrpcDataEndStream) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1259,7 +1376,7 @@ TEST_F(RouterTest, GrpcReset) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1287,7 +1404,7 @@ TEST_F(RouterTest, GrpcOk) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1316,7 +1433,7 @@ TEST_F(RouterTest, GrpcInternal) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1341,7 +1458,7 @@ TEST_F(RouterTest, UpstreamTimeoutWithAltResponse) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) @@ -1384,7 +1501,7 @@ TEST_F(RouterTest, UpstreamPerTryTimeout) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) @@ -1455,7 +1572,7 @@ TEST_F(RouterTest, UpstreamPerTryTimeoutDelayedPoolReady) { EXPECT_EQ(host_address_, host->address()); })); - pool_callbacks->onPoolReady(encoder, cm_.conn_pool_.host_); + pool_callbacks->onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); EXPECT_CALL(callbacks_.stream_info_, setResponseFlag(StreamInfo::ResponseFlag::UpstreamRequestTimeout)); @@ -1491,7 +1608,7 @@ TEST_F(RouterTest, UpstreamPerTryTimeoutExcludesNewStream) { })); response_timeout_ = new Event::MockTimer(&callbacks_.dispatcher_); - EXPECT_CALL(*response_timeout_, enableTimer(_)); + EXPECT_CALL(*response_timeout_, enableTimer(_, _)); EXPECT_CALL(callbacks_.stream_info_, onUpstreamHostSelected(_)) .WillOnce(Invoke([&](const Upstream::HostDescriptionConstSharedPtr host) -> void { @@ -1506,9 +1623,9 @@ TEST_F(RouterTest, UpstreamPerTryTimeoutExcludesNewStream) { router_.decodeData(data, true); per_try_timeout_ = new Event::MockTimer(&callbacks_.dispatcher_); - EXPECT_CALL(*per_try_timeout_, enableTimer(_)); + EXPECT_CALL(*per_try_timeout_, enableTimer(_, _)); // The per try timeout timer should not be started yet. - pool_callbacks->onPoolReady(encoder, cm_.conn_pool_.host_); + pool_callbacks->onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); EXPECT_CALL(encoder.stream_, resetStream(Http::StreamResetReason::LocalReset)); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -1543,7 +1660,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutFirstRequestSucceeds) { -> Http::ConnectionPool::Cancellable* { response_decoder1 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -1571,7 +1688,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutFirstRequestSucceeds) { -> Http::ConnectionPool::Cancellable* { response_decoder2 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -1616,7 +1733,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutResetsOnBadHeaders) { -> Http::ConnectionPool::Cancellable* { response_decoder1 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -1644,7 +1761,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutResetsOnBadHeaders) { -> Http::ConnectionPool::Cancellable* { response_decoder2 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -1694,7 +1811,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutThirdRequestSucceeds) { -> Http::ConnectionPool::Cancellable* { response_decoder1 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -1725,7 +1842,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutThirdRequestSucceeds) { -> Http::ConnectionPool::Cancellable* { response_decoder2 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -1745,7 +1862,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutThirdRequestSucceeds) { -> Http::ConnectionPool::Cancellable* { response_decoder3 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder3, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder3, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -1787,7 +1904,7 @@ TEST_F(RouterTest, RetryOnlyOnceForSameUpstreamRequest) { -> Http::ConnectionPool::Cancellable* { response_decoder1 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -1816,7 +1933,7 @@ TEST_F(RouterTest, RetryOnlyOnceForSameUpstreamRequest) { -> Http::ConnectionPool::Cancellable* { response_decoder2 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -1851,7 +1968,7 @@ TEST_F(RouterTest, BadHeadersDroppedIfPreviousRetryScheduled) { -> Http::ConnectionPool::Cancellable* { response_decoder1 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -1892,7 +2009,7 @@ TEST_F(RouterTest, BadHeadersDroppedIfPreviousRetryScheduled) { -> Http::ConnectionPool::Cancellable* { response_decoder2 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); router_.retry_state_->callback_(); @@ -1915,7 +2032,7 @@ TEST_F(RouterTest, RetryRequestNotComplete) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(callbacks_.stream_info_, @@ -1949,7 +2066,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutGlobalTimeout) { -> Http::ConnectionPool::Cancellable* { response_decoder1 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -1978,7 +2095,7 @@ TEST_F(RouterTest, HedgedPerTryTimeoutGlobalTimeout) { -> Http::ConnectionPool::Cancellable* { response_decoder2 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -2015,7 +2132,7 @@ TEST_F(RouterTest, HedgingRetriesExhaustedBadResponse) { -> Http::ConnectionPool::Cancellable* { response_decoder1 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -2044,7 +2161,7 @@ TEST_F(RouterTest, HedgingRetriesExhaustedBadResponse) { -> Http::ConnectionPool::Cancellable* { response_decoder2 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -2095,7 +2212,7 @@ TEST_F(RouterTest, HedgingRetriesProceedAfterReset) { -> Http::ConnectionPool::Cancellable* { response_decoder1 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); // First is reset @@ -2128,7 +2245,7 @@ TEST_F(RouterTest, HedgingRetriesProceedAfterReset) { -> Http::ConnectionPool::Cancellable* { response_decoder2 = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -2174,7 +2291,7 @@ TEST_F(RouterTest, HedgingRetryImmediatelyReset) { -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, @@ -2242,7 +2359,7 @@ TEST_F(RouterTest, RetryNoneHealthy) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -2279,7 +2396,7 @@ TEST_F(RouterTest, RetryUpstreamReset) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2302,7 +2419,7 @@ TEST_F(RouterTest, RetryUpstreamReset) { EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, putResult(Upstream::Outlier::Result::LOCAL_ORIGIN_CONNECT_SUCCESS, absl::optional(absl::nullopt))); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); router_.retry_state_->callback_(); @@ -2327,7 +2444,7 @@ TEST_F(RouterTest, RetryUpstreamPerTryTimeout) { -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -2355,7 +2472,7 @@ TEST_F(RouterTest, RetryUpstreamPerTryTimeout) { EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, putResult(Upstream::Outlier::Result::LOCAL_ORIGIN_CONNECT_SUCCESS, absl::optional(absl::nullopt))); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -2400,7 +2517,7 @@ TEST_F(RouterTest, RetryUpstreamConnectionFailure) { -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; EXPECT_CALL(*router_.retry_state_, onHostAttempted(_)); - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); router_.retry_state_->callback_(); @@ -2420,7 +2537,7 @@ TEST_F(RouterTest, DontResetStartedResponseOnUpstreamPerTryTimeout) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -2454,7 +2571,7 @@ TEST_F(RouterTest, RetryUpstreamResetResponseStarted) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2486,7 +2603,7 @@ TEST_F(RouterTest, RetryUpstreamReset100ContinueResponseStarted) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2517,7 +2634,7 @@ TEST_F(RouterTest, RetryUpstream5xx) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2540,7 +2657,7 @@ TEST_F(RouterTest, RetryUpstream5xx) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); router_.retry_state_->callback_(); @@ -2561,7 +2678,7 @@ TEST_F(RouterTest, RetryTimeoutDuringRetryDelay) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2597,7 +2714,7 @@ TEST_F(RouterTest, RetryTimeoutDuringRetryDelayWithUpstreamRequestNoHost) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2644,7 +2761,7 @@ TEST_F(RouterTest, RetryTimeoutDuringRetryDelayWithUpstreamRequestNoHostAltRespo .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2690,7 +2807,7 @@ TEST_F(RouterTest, RetryUpstream5xxNotComplete) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2721,7 +2838,7 @@ TEST_F(RouterTest, RetryUpstream5xxNotComplete) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); ON_CALL(callbacks_, decodingBuffer()).WillByDefault(Return(body_data.get())); @@ -2754,14 +2871,18 @@ TEST_F(RouterTest, RetryUpstream5xxNotComplete) { .value()); } -TEST_F(RouterTest, RetryUpstreamGrpcCancelled) { +// Validate gRPC Cancelled response stats are sane when retry is taking effect. +TEST_F(RouterTest, RetryUpstreamGrpcCancelledRuntimeGuard) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.outlier_detection_support_for_grpc_status", "false"}}); NiceMock encoder1; Http::StreamDecoder* response_decoder = nullptr; EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2788,7 +2909,59 @@ TEST_F(RouterTest, RetryUpstreamGrpcCancelled) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + router_.retry_state_->callback_(); + + // Normal response. + EXPECT_CALL(*router_.retry_state_, shouldRetryHeaders(_, _)).WillOnce(Return(RetryStatus::No)); + Http::HeaderMapPtr response_headers( + new Http::TestHeaderMapImpl{{":status", "200"}, {"grpc-status", "0"}}); + EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, putHttpResponseCode(200)); + response_decoder->decodeHeaders(std::move(response_headers), true); + EXPECT_TRUE(verifyHostUpstreamStats(1, 1)); +} + +// Validate gRPC Cancelled response stats are sane when retry is taking effect. +TEST_F(RouterTest, RetryUpstreamGrpcCancelled) { + TestScopedRuntime scoped_runtime; + Runtime::LoaderSingleton::getExisting()->mergeValues( + {{"envoy.reloadable_features.outlier_detection_support_for_grpc_status", "true"}}); + NiceMock encoder1; + Http::StreamDecoder* response_decoder = nullptr; + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + expectResponseTimerCreate(); + + Http::TestHeaderMapImpl headers{{"x-envoy-retry-grpc-on", "cancelled"}, + {"x-envoy-internal", "true"}, + {"content-type", "application/grpc"}, + {"grpc-timeout", "20S"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_.decodeHeaders(headers, true); + + // gRPC with status "cancelled" (1) + router_.retry_state_->expectHeadersRetry(); + Http::HeaderMapPtr response_headers1( + new Http::TestHeaderMapImpl{{":status", "200"}, {"grpc-status", "1"}}); + EXPECT_CALL(cm_.conn_pool_.host_->outlier_detector_, putHttpResponseCode(499)); + response_decoder->decodeHeaders(std::move(response_headers1), true); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); + + // We expect the grpc-status to result in a retried request. + EXPECT_CALL(encoder1.stream_, resetStream(_)).Times(0); + NiceMock encoder2; + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); router_.retry_state_->callback_(); @@ -2813,7 +2986,7 @@ TEST_F(RouterTest, RetryRespsectsMaxHostSelectionCount) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2848,7 +3021,7 @@ TEST_F(RouterTest, RetryRespsectsMaxHostSelectionCount) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); ON_CALL(callbacks_, decodingBuffer()).WillByDefault(Return(body_data.get())); @@ -2880,7 +3053,7 @@ TEST_F(RouterTest, RetryRespectsRetryHostPredicate) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -2915,7 +3088,7 @@ TEST_F(RouterTest, RetryRespectsRetryHostPredicate) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); ON_CALL(callbacks_, decodingBuffer()).WillByDefault(Return(body_data.get())); @@ -3049,13 +3222,13 @@ TEST_F(RouterTest, HttpInternalRedirectSucceeded) { } TEST_F(RouterTest, HttpsInternalRedirectSucceeded) { - Ssl::MockConnectionInfo ssl_connection; + auto ssl_connection = std::make_shared(); enableRedirects(); sendRequest(); redirect_headers_->insertLocation().value(std::string("https://www.foo.com")); - EXPECT_CALL(connection_, ssl()).Times(1).WillOnce(Return(&ssl_connection)); + EXPECT_CALL(connection_, ssl()).Times(1).WillOnce(Return(ssl_connection)); EXPECT_CALL(callbacks_, decodingBuffer()).Times(1); EXPECT_CALL(callbacks_, recreateStream()).Times(1).WillOnce(Return(true)); response_decoder_->decodeHeaders(std::move(redirect_headers_), false); @@ -3078,7 +3251,7 @@ TEST_F(RouterTest, Shadow) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -3122,7 +3295,7 @@ TEST_F(RouterTest, AltStatName) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -3242,6 +3415,36 @@ TEST_F(RouterTest, DirectResponseWithBody) { EXPECT_EQ(1UL, config_.stats_.rq_direct_response_.value()); } +TEST_F(RouterTest, UpstreamSSLConnection) { + NiceMock encoder; + Http::StreamDecoder* response_decoder = nullptr; + + std::string session_id = "D62A523A65695219D46FE1FFE285A4C371425ACE421B110B5B8D11D3EB4D5F0B"; + auto connection_info = std::make_shared>(); + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(session_id)); + upstream_stream_info_.setDownstreamSslConnection(connection_info); + + expectResponseTimerCreate(); + EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) + .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) + -> Http::ConnectionPool::Cancellable* { + response_decoder = &decoder; + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); + return nullptr; + })); + + Http::TestHeaderMapImpl headers{}; + HttpTestUtility::addDefaultHeaders(headers); + router_.decodeHeaders(headers, true); + + Http::HeaderMapPtr response_headers(new Http::TestHeaderMapImpl{{":status", "200"}}); + response_decoder->decodeHeaders(std::move(response_headers), true); + EXPECT_TRUE(verifyHostUpstreamStats(1, 0)); + + ASSERT_NE(nullptr, callbacks_.streamInfo().upstreamSslConnection()); + EXPECT_EQ(session_id, callbacks_.streamInfo().upstreamSslConnection()->sessionId()); +} + // Verify that upstream timing information is set into the StreamInfo after the upstream // request completes. TEST_F(RouterTest, UpstreamTimingSingleRequest) { @@ -3251,7 +3454,7 @@ TEST_F(RouterTest, UpstreamTimingSingleRequest) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -3308,7 +3511,7 @@ TEST_F(RouterTest, UpstreamTimingRetry) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -3333,7 +3536,7 @@ TEST_F(RouterTest, UpstreamTimingRetry) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -3388,7 +3591,7 @@ TEST_F(RouterTest, UpstreamTimingTimeout) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -3889,7 +4092,7 @@ TEST_F(RouterTest, CanaryStatusTrue) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -3922,7 +4125,7 @@ TEST_F(RouterTest, CanaryStatusFalse) { .WillOnce(Invoke([&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -3962,7 +4165,7 @@ TEST_F(RouterTest, AutoHostRewriteEnabled) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](Http::StreamDecoder&, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -3997,7 +4200,7 @@ TEST_F(RouterTest, AutoHostRewriteDisabled) { EXPECT_CALL(cm_.conn_pool_, newStream(_, _)) .WillOnce(Invoke([&](Http::StreamDecoder&, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -4053,7 +4256,7 @@ class WatermarkTest : public RouterTest { response_decoder_ = &decoder; pool_callbacks_ = &callbacks; if (pool_ready) { - callbacks.onPoolReady(encoder_, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder_, cm_.conn_pool_.host_, upstream_stream_info_); } return nullptr; })); @@ -4144,7 +4347,7 @@ TEST_F(WatermarkTest, FilterWatermarks) { .value()); EXPECT_CALL(encoder_, encodeData(_, true)) .WillOnce(Invoke([&](Buffer::Instance& data, bool) -> void { data.drain(data.length()); })); - pool_callbacks_->onPoolReady(encoder_, cm_.conn_pool_.host_); + pool_callbacks_->onPoolReady(encoder_, cm_.conn_pool_.host_, upstream_stream_info_); EXPECT_EQ(1U, cm_.thread_local_cluster_.cluster_.info_->stats_store_ .counter("upstream_flow_control_drained_total") .value()); @@ -4164,7 +4367,7 @@ TEST_F(WatermarkTest, RetryRequestNotComplete) { [&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); EXPECT_CALL(callbacks_.stream_info_, @@ -4210,7 +4413,7 @@ TEST_F(RouterTestChildSpan, BasicFlow) { -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; EXPECT_CALL(*child_span, injectContext(_)); - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); @@ -4242,7 +4445,7 @@ TEST_F(RouterTestChildSpan, ResetFlow) { -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; EXPECT_CALL(*child_span, injectContext(_)); - callbacks.onPoolReady(encoder, cm_.conn_pool_.host_); + callbacks.onPoolReady(encoder, cm_.conn_pool_.host_, upstream_stream_info_); return nullptr; })); diff --git a/test/common/router/router_upstream_log_test.cc b/test/common/router/router_upstream_log_test.cc index 581db1087f..0b74b3e068 100644 --- a/test/common/router/router_upstream_log_test.cc +++ b/test/common/router/router_upstream_log_test.cc @@ -106,13 +106,13 @@ class RouterUpstreamLogTest : public testing::Test { void expectResponseTimerCreate() { response_timeout_ = new Event::MockTimer(&callbacks_.dispatcher_); - EXPECT_CALL(*response_timeout_, enableTimer(_)); + EXPECT_CALL(*response_timeout_, enableTimer(_, _)); EXPECT_CALL(*response_timeout_, disableTimer()); } void expectPerTryTimerCreate() { per_try_timeout_ = new Event::MockTimer(&callbacks_.dispatcher_); - EXPECT_CALL(*per_try_timeout_, enableTimer(_)); + EXPECT_CALL(*per_try_timeout_, enableTimer(_, _)); EXPECT_CALL(*per_try_timeout_, disableTimer()); } @@ -129,7 +129,8 @@ class RouterUpstreamLogTest : public testing::Test { [&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder, context_.cluster_manager_.conn_pool_.host_); + callbacks.onPoolReady(encoder, context_.cluster_manager_.conn_pool_.host_, + stream_info_); return nullptr; })); expectResponseTimerCreate(); @@ -161,7 +162,8 @@ class RouterUpstreamLogTest : public testing::Test { [&](Http::StreamDecoder& decoder, Http::ConnectionPool::Callbacks& callbacks) -> Http::ConnectionPool::Cancellable* { response_decoder = &decoder; - callbacks.onPoolReady(encoder1, context_.cluster_manager_.conn_pool_.host_); + callbacks.onPoolReady(encoder1, context_.cluster_manager_.conn_pool_.host_, + stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -187,7 +189,8 @@ class RouterUpstreamLogTest : public testing::Test { response_decoder = &decoder; EXPECT_CALL(context_.cluster_manager_.conn_pool_.host_->outlier_detector_, putResult(Upstream::Outlier::Result::LOCAL_ORIGIN_CONNECT_SUCCESS, _)); - callbacks.onPoolReady(encoder2, context_.cluster_manager_.conn_pool_.host_); + callbacks.onPoolReady(encoder2, context_.cluster_manager_.conn_pool_.host_, + stream_info_); return nullptr; })); expectPerTryTimerCreate(); @@ -214,6 +217,7 @@ class RouterUpstreamLogTest : public testing::Test { NiceMock callbacks_; std::shared_ptr config_; std::shared_ptr router_; + NiceMock stream_info_; }; TEST_F(RouterUpstreamLogTest, NoLogConfigured) { diff --git a/test/common/router/scoped_config_impl_test.cc b/test/common/router/scoped_config_impl_test.cc index 9c9a06a259..0decf3b2e7 100644 --- a/test/common/router/scoped_config_impl_test.cc +++ b/test/common/router/scoped_config_impl_test.cc @@ -2,6 +2,7 @@ #include "common/router/scoped_config_impl.h" +#include "test/mocks/router/mocks.h" #include "test/test_common/utility.h" #include "gtest/gtest.h" @@ -11,10 +12,11 @@ namespace Router { namespace { using ::Envoy::Http::TestHeaderMapImpl; +using ::testing::NiceMock; class FooFragment : public ScopeKeyFragmentBase { -private: - bool equals(const ScopeKeyFragmentBase&) const override { return true; }; +public: + uint64_t hash() const override { return 1; } }; TEST(ScopeKeyFragmentBaseTest, EqualSign) { @@ -24,14 +26,33 @@ TEST(ScopeKeyFragmentBaseTest, EqualSign) { EXPECT_NE(foo, bar); } +TEST(ScopeKeyFragmentBaseTest, HashStable) { + FooFragment foo1; + FooFragment foo2; + + // Two FooFragments equal because their hash equals. + EXPECT_EQ(foo1, foo2); + EXPECT_EQ(foo1.hash(), foo2.hash()); + + // Hash value doesn't change. + StringKeyFragment a("abcdefg"); + auto hash_value = a.hash(); + for (int i = 0; i < 100; ++i) { + EXPECT_EQ(hash_value, a.hash()); + EXPECT_EQ(StringKeyFragment("abcdefg").hash(), hash_value); + } +} + TEST(StringKeyFragmentTest, Empty) { StringKeyFragment a(""); StringKeyFragment b(""); EXPECT_EQ(a, b); + EXPECT_EQ(a.hash(), b.hash()); StringKeyFragment non_empty("ABC"); EXPECT_NE(a, non_empty); + EXPECT_NE(a.hash(), non_empty.hash()); } TEST(StringKeyFragmentTest, Normal) { @@ -217,7 +238,9 @@ ScopeKey makeKey(const std::vector& parts) { TEST(ScopeKeyDeathTest, AddNullFragment) { ScopeKey key; +#if !defined(NDEBUG) EXPECT_DEBUG_DEATH(key.addFragment(nullptr), "null fragment not allowed in ScopeKey."); +#endif } TEST(ScopeKeyTest, Unmatches) { @@ -231,6 +254,10 @@ TEST(ScopeKeyTest, Unmatches) { EXPECT_EQ(makeKey({"a", "b", "c"}), makeKey({"a", "b", "c"})); + // Order matters. + EXPECT_EQ(makeKey({"a", "b", "c"}), makeKey({"a", "b", "c"})); + EXPECT_NE(makeKey({"a", "c", "b"}), makeKey({"a", "b", "c"})); + // Two keys of different length won't match. EXPECT_NE(makeKey({"a", "b"}), makeKey({"a", "b", "c"})); @@ -243,7 +270,7 @@ TEST(ScopeKeyTest, Matches) { EXPECT_EQ(makeKey({"", ""}), makeKey({"", ""})); EXPECT_EQ(makeKey({"a", "", ""}), makeKey({"a", "", ""})); - // Non empty fragments comparison. + // Non empty fragments comparison. EXPECT_EQ(makeKey({"A", "b"}), makeKey({"A", "b"})); } @@ -317,6 +344,170 @@ TEST(ScopeKeyBuilderImplTest, Parse) { EXPECT_EQ(key, nullptr); } +class ScopedRouteInfoTest : public testing::Test { +public: + void SetUp() override { + std::string yaml_plain = R"EOF( + name: foo_scope + route_configuration_name: foo_route + key: + fragments: + - string_key: foo + - string_key: bar +)EOF"; + TestUtility::loadFromYaml(yaml_plain, scoped_route_config_); + + route_config_ = std::make_shared>(); + route_config_->name_ = "foo_route"; + } + + envoy::api::v2::RouteConfiguration route_configuration_; + envoy::api::v2::ScopedRouteConfiguration scoped_route_config_; + std::shared_ptr route_config_; + std::unique_ptr info_; +}; + +TEST_F(ScopedRouteInfoTest, Creation) { + envoy::api::v2::ScopedRouteConfiguration config_copy = scoped_route_config_; + info_ = std::make_unique(std::move(scoped_route_config_), route_config_); + EXPECT_EQ(info_->routeConfig().get(), route_config_.get()); + EXPECT_TRUE(TestUtility::protoEqual(info_->configProto(), config_copy)); + EXPECT_EQ(info_->scopeName(), "foo_scope"); + EXPECT_EQ(info_->scopeKey(), makeKey({"foo", "bar"})); +} + +class ScopedConfigImplTest : public testing::Test { +public: + void SetUp() override { + std::string yaml_plain = R"EOF( + fragments: + - header_value_extractor: + name: 'foo_header' + element_separator: ',' + element: + key: 'bar' + separator: '=' + - header_value_extractor: + name: 'bar_header' + element_separator: ';' + index: 2 +)EOF"; + TestUtility::loadFromYaml(yaml_plain, key_builder_config_); + + scope_info_a_ = makeScopedRouteInfo(R"EOF( + name: foo_scope + route_configuration_name: foo_route + key: + fragments: + - string_key: foo + - string_key: bar +)EOF"); + scope_info_a_v2_ = makeScopedRouteInfo(R"EOF( + name: foo_scope + route_configuration_name: foo_route + key: + fragments: + - string_key: xyz + - string_key: xyz +)EOF"); + scope_info_b_ = makeScopedRouteInfo(R"EOF( + name: bar_scope + route_configuration_name: bar_route + key: + fragments: + - string_key: bar + - string_key: baz +)EOF"); + } + std::shared_ptr makeScopedRouteInfo(const std::string& route_config_yaml) { + envoy::api::v2::ScopedRouteConfiguration scoped_route_config; + TestUtility::loadFromYaml(route_config_yaml, scoped_route_config); + + std::shared_ptr route_config = std::make_shared>(); + route_config->name_ = scoped_route_config.route_configuration_name(); + return std::make_shared(std::move(scoped_route_config), + std::move(route_config)); + } + + std::shared_ptr scope_info_a_; + std::shared_ptr scope_info_a_v2_; + std::shared_ptr scope_info_b_; + ScopedRoutes::ScopeKeyBuilder key_builder_config_; + std::unique_ptr scoped_config_impl_; +}; + +// Test a ScopedConfigImpl returns the correct route Config. +TEST_F(ScopedConfigImplTest, PickRoute) { + scoped_config_impl_ = std::make_unique(std::move(key_builder_config_)); + scoped_config_impl_->addOrUpdateRoutingScope(scope_info_a_); + scoped_config_impl_->addOrUpdateRoutingScope(scope_info_b_); + + // Key (foo, bar) maps to scope_info_a_. + ConfigConstSharedPtr route_config = scoped_config_impl_->getRouteConfig(TestHeaderMapImpl{ + {"foo_header", ",,key=value,bar=foo,"}, + {"bar_header", ";val1;bar;val3"}, + }); + EXPECT_EQ(route_config, scope_info_a_->routeConfig()); + + // Key (bar, baz) maps to scope_info_b_. + route_config = scoped_config_impl_->getRouteConfig(TestHeaderMapImpl{ + {"foo_header", ",,key=value,bar=bar,"}, + {"bar_header", ";val1;baz;val3"}, + }); + EXPECT_EQ(route_config, scope_info_b_->routeConfig()); + + // No such key (bar, NOT_BAZ). + route_config = scoped_config_impl_->getRouteConfig(TestHeaderMapImpl{ + {"foo_header", ",key=value,bar=bar,"}, + {"bar_header", ";val1;NOT_BAZ;val3"}, + }); + EXPECT_EQ(route_config, nullptr); +} + +// Test a ScopedConfigImpl returns the correct route Config before and after scope config update. +TEST_F(ScopedConfigImplTest, Update) { + scoped_config_impl_ = std::make_unique(std::move(key_builder_config_)); + + TestHeaderMapImpl headers{ + {"foo_header", ",,key=value,bar=foo,"}, + {"bar_header", ";val1;bar;val3"}, + }; + // Empty ScopeConfig. + EXPECT_EQ(scoped_config_impl_->getRouteConfig(headers), nullptr); + + // Add scope_key (bar, baz). + scoped_config_impl_->addOrUpdateRoutingScope(scope_info_b_); + EXPECT_EQ(scoped_config_impl_->getRouteConfig(headers), nullptr); + EXPECT_EQ(scoped_config_impl_->getRouteConfig( + TestHeaderMapImpl{{"foo_header", ",,key=v,bar=bar,"}, {"bar_header", ";val1;baz"}}), + scope_info_b_->routeConfig()); + + // Add scope_key (foo, bar). + scoped_config_impl_->addOrUpdateRoutingScope(scope_info_a_); + // Found scope_info_a_. + EXPECT_EQ(scoped_config_impl_->getRouteConfig(headers), scope_info_a_->routeConfig()); + + // Update scope foo_scope. + scoped_config_impl_->addOrUpdateRoutingScope(scope_info_a_v2_); + EXPECT_EQ(scoped_config_impl_->getRouteConfig(headers), nullptr); + + // foo_scope now is keyed by (xyz, xyz). + EXPECT_EQ(scoped_config_impl_->getRouteConfig( + TestHeaderMapImpl{{"foo_header", ",bar=xyz,foo=bar"}, {"bar_header", ";;xyz"}}), + scope_info_a_v2_->routeConfig()); + + // Remove scope "foo_scope". + scoped_config_impl_->removeRoutingScope("foo_scope"); + // scope_info_a_ is gone. + EXPECT_EQ(scoped_config_impl_->getRouteConfig(headers), nullptr); + + // Now delete some non-existent scopes. + EXPECT_NO_THROW(scoped_config_impl_->removeRoutingScope("foo_scope1")); + EXPECT_NO_THROW(scoped_config_impl_->removeRoutingScope("base_scope")); + EXPECT_NO_THROW(scoped_config_impl_->removeRoutingScope("bluh_scope")); + EXPECT_NO_THROW(scoped_config_impl_->removeRoutingScope("xyz_scope")); +} + } // namespace } // namespace Router } // namespace Envoy diff --git a/test/common/router/scoped_rds_test.cc b/test/common/router/scoped_rds_test.cc index 30d3a671ed..3967703c41 100644 --- a/test/common/router/scoped_rds_test.cc +++ b/test/common/router/scoped_rds_test.cc @@ -2,25 +2,37 @@ #include "envoy/admin/v2alpha/config_dump.pb.h" #include "envoy/admin/v2alpha/config_dump.pb.validate.h" +#include "envoy/config/subscription.h" +#include "envoy/init/manager.h" #include "envoy/stats/scope.h" #include "common/router/scoped_rds.h" +#include "test/mocks/config/mocks.h" +#include "test/mocks/router/mocks.h" #include "test/mocks/server/mocks.h" #include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" #include "gmock/gmock.h" #include "gtest/gtest.h" +using testing::AnyNumber; +using testing::Eq; using testing::InSequence; +using testing::Invoke; +using testing::IsNull; +using testing::NiceMock; using testing::Return; namespace Envoy { namespace Router { namespace { +using ::Envoy::Http::TestHeaderMapImpl; + envoy::api::v2::ScopedRouteConfiguration parseScopedRouteConfigurationFromYaml(const std::string& yaml) { envoy::api::v2::ScopedRouteConfiguration scoped_route_config; @@ -44,21 +56,39 @@ parseHttpConnectionManagerFromYaml(const std::string& config_yaml) { class ScopedRoutesTestBase : public testing::Test { protected: ScopedRoutesTestBase() { + EXPECT_CALL(factory_context_.admin_.config_tracker_, add_("routes", _)); + route_config_provider_manager_ = + std::make_unique(factory_context_.admin_); + EXPECT_CALL(factory_context_.admin_.config_tracker_, add_("route_scopes", _)); - config_provider_manager_ = - std::make_unique(factory_context_.admin_); + config_provider_manager_ = std::make_unique( + factory_context_.admin_, *route_config_provider_manager_); } ~ScopedRoutesTestBase() override { factory_context_.thread_local_.shutdownThread(); } + // The delta style API helper. + Protobuf::RepeatedPtrField + anyToResource(Protobuf::RepeatedPtrField& resources, + const std::string& version) { + Protobuf::RepeatedPtrField added_resources; + for (const auto& resource_any : resources) { + auto config = TestUtility::anyConvert(resource_any); + auto* to_add = added_resources.Add(); + to_add->set_name(config.name()); + to_add->set_version(version); + to_add->mutable_resource()->PackFrom(config); + } + return added_resources; + } + Event::SimulatedTimeSystem& timeSystem() { return time_system_; } NiceMock factory_context_; - Upstream::ClusterManager::ClusterInfoMap cluster_map_; - Upstream::MockClusterMockPrioritySet cluster_; + std::unique_ptr route_config_provider_manager_; std::unique_ptr config_provider_manager_; + Event::SimulatedTimeSystem time_system_; - envoy::api::v2::core::ConfigSource rds_config_source_; }; class ScopedRdsTest : public ScopedRoutesTestBase { @@ -66,11 +96,56 @@ class ScopedRdsTest : public ScopedRoutesTestBase { void setup() { InSequence s; + // Since factory_context_.cluster_manager_.subscription_factory_.callbacks_ is taken by the SRDS + // subscription. We need to return a different MockSubscription here for each RDS subscription. + // To build the map from RDS route_config_name to the RDS subscription, we need to get the + // route_config_name by mocking start() on the Config::Subscription. + EXPECT_CALL(factory_context_.cluster_manager_.subscription_factory_, + subscriptionFromConfigSource(_, _, _, _)) + .Times(AnyNumber()); + EXPECT_CALL(factory_context_.cluster_manager_.subscription_factory_, + subscriptionFromConfigSource( + _, + Eq(Grpc::Common::typeUrl( + envoy::api::v2::RouteConfiguration().GetDescriptor()->full_name())), + _, _)) + .Times(AnyNumber()) + .WillRepeatedly(Invoke([this](const envoy::api::v2::core::ConfigSource&, absl::string_view, + Stats::Scope&, + Envoy::Config::SubscriptionCallbacks& callbacks) { + auto ret = std::make_unique>(); + rds_subscription_by_config_subscription_[ret.get()] = &callbacks; + EXPECT_CALL(*ret, start(_)) + .WillOnce(Invoke( + [this, config_sub_addr = ret.get()](const std::set& resource_names) { + EXPECT_EQ(resource_names.size(), 1); + auto iter = rds_subscription_by_config_subscription_.find(config_sub_addr); + EXPECT_NE(iter, rds_subscription_by_config_subscription_.end()); + rds_subscription_by_name_[*resource_names.begin()] = iter->second; + })); + return ret; + })); + + ON_CALL(factory_context_.init_manager_, add(_)) + .WillByDefault(Invoke([this](const Init::Target& target) { + target_handles_.push_back(target.createHandle("test")); + })); + ON_CALL(factory_context_.init_manager_, initialize(_)) + .WillByDefault(Invoke([this](const Init::Watcher& watcher) { + for (auto& handle_ : target_handles_) { + handle_->initialize(watcher); + } + })); + const std::string config_yaml = R"EOF( name: foo_scoped_routes scope_key_builder: fragments: - - header_value_extractor: { name: X-Google-VIP } + - header_value_extractor: + name: Addr + element: + key: x-foo-key + separator: ; )EOF"; envoy::config::filter::network::http_connection_manager::v2::ScopedRoutes scoped_routes_config; TestUtility::loadFromYaml(config_yaml, scoped_routes_config); @@ -79,11 +154,44 @@ name: foo_scoped_routes ScopedRoutesConfigProviderManagerOptArg(scoped_routes_config.name(), scoped_routes_config.rds_config_source(), scoped_routes_config.scope_key_builder())); - subscription_callbacks_ = factory_context_.cluster_manager_.subscription_factory_.callbacks_; + srds_subscription_ = factory_context_.cluster_manager_.subscription_factory_.callbacks_; + } + + // Helper function which pushes an update to given RDS subscription, the start(_) of the + // subscription must have been called. + void pushRdsConfig(const std::string& route_config_name, const std::string& version) { + const std::string route_config_tmpl = R"EOF( + name: {} + virtual_hosts: + - name: test + domains: ["*"] + routes: + - match: {{ prefix: "/" }} + route: {{ cluster: bluh }} +)EOF"; + Protobuf::RepeatedPtrField resources; + resources.Add()->PackFrom(TestUtility::parseYaml( + fmt::format(route_config_tmpl, route_config_name))); + rds_subscription_by_name_[route_config_name]->onConfigUpdate(resources, version); + } + + ScopedRdsConfigProvider* getScopedRdsProvider() const { + return dynamic_cast(provider_.get()); + } + // Helper function which returns the ScopedRouteMap of the subscription. + const ScopedRouteMap& getScopedRouteMap() const { + return getScopedRdsProvider()->subscription().scopedRouteMap(); } - Envoy::Config::SubscriptionCallbacks* subscription_callbacks_{}; + Envoy::Config::SubscriptionCallbacks* srds_subscription_{}; Envoy::Config::ConfigProviderPtr provider_; + std::list target_handles_; + Init::ExpectableWatcherImpl init_watcher_; + + // RDS mocks. + absl::flat_hash_map + rds_subscription_by_config_subscription_; + absl::flat_hash_map rds_subscription_by_name_; }; TEST_F(ScopedRdsTest, ValidateFail) { @@ -99,7 +207,11 @@ route_configuration_name: foo_routes )EOF"; Protobuf::RepeatedPtrField resources; parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml); - EXPECT_THROW(subscription_callbacks_->onConfigUpdate(resources, "1"), ProtoValidationException); + EXPECT_THROW(srds_subscription_->onConfigUpdate(resources, "1"), ProtoValidationException); + + EXPECT_THROW_WITH_REGEX( + srds_subscription_->onConfigUpdate(anyToResource(resources, "1"), {}, "1"), EnvoyException, + "Error adding/updating scoped route\\(s\\): Proto constraint validation failed.*"); // 'route_configuration_name' validation: value must be > 1 byte. const std::string config_yaml2 = R"EOF( @@ -111,7 +223,10 @@ name: foo_scope )EOF"; Protobuf::RepeatedPtrField resources2; parseScopedRouteConfigurationFromYaml(*resources2.Add(), config_yaml2); - EXPECT_THROW(subscription_callbacks_->onConfigUpdate(resources2, "1"), ProtoValidationException); + EXPECT_THROW(srds_subscription_->onConfigUpdate(resources2, "1"), ProtoValidationException); + EXPECT_THROW_WITH_REGEX( + srds_subscription_->onConfigUpdate(anyToResource(resources2, "1"), {}, "1"), EnvoyException, + "Error adding/updating scoped route\\(s\\): Proto constraint validation failed.*"); // 'key' validation: must define at least 1 fragment. const std::string config_yaml3 = R"EOF( @@ -121,11 +236,15 @@ route_configuration_name: foo_routes )EOF"; Protobuf::RepeatedPtrField resources3; parseScopedRouteConfigurationFromYaml(*resources3.Add(), config_yaml3); - EXPECT_THROW(subscription_callbacks_->onConfigUpdate(resources3, "1"), ProtoValidationException); + EXPECT_THROW(srds_subscription_->onConfigUpdate(resources3, "1"), ProtoValidationException); + EXPECT_THROW_WITH_REGEX( + srds_subscription_->onConfigUpdate(anyToResource(resources3, "1"), {}, "1"), EnvoyException, + "Error adding/updating scoped route\\(s\\): Proto constraint validation failed .*value is " + "required.*"); } -// Tests that multiple uniquely named resources are allowed in config updates. -TEST_F(ScopedRdsTest, MultipleResources) { +// Tests that multiple uniquely named non-conflict resources are allowed in config updates. +TEST_F(ScopedRdsTest, MultipleResourcesSotw) { setup(); const std::string config_yaml = R"EOF( @@ -140,20 +259,210 @@ route_configuration_name: foo_routes const std::string config_yaml2 = R"EOF( name: foo_scope2 route_configuration_name: foo_routes +key: + fragments: + - string_key: x-bar-key +)EOF"; + parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml2); + EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(resources, "1")); + factory_context_.init_manager_.initialize(init_watcher_); + init_watcher_.expectReady().Times(2); // SRDS and RDS "foo_routes" + EXPECT_EQ( + 1UL, + factory_context_.scope_.counter("foo.scoped_rds.foo_scoped_routes.config_reload").value()); + + // Verify the config is a ScopedConfigImpl instance, both scopes point to "" as RDS hasn't kicked + // in yet(NullConfigImpl returned). + EXPECT_NE(getScopedRdsProvider(), nullptr); + EXPECT_NE(getScopedRdsProvider()->config(), nullptr); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}) + ->name(), + ""); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-bar-key"}}) + ->name(), + ""); + // RDS updates foo_routes. + pushRdsConfig("foo_routes", "111"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}) + ->name(), + "foo_routes"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-bar-key"}}) + ->name(), + "foo_routes"); + + // Delete foo_scope2. + resources.RemoveLast(); + EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(resources, "3")); + EXPECT_EQ(getScopedRouteMap().size(), 1); + EXPECT_EQ(getScopedRouteMap().count("foo_scope"), 1); + EXPECT_EQ( + 2UL, + factory_context_.scope_.counter("foo.scoped_rds.foo_scoped_routes.config_reload").value()); + // now scope key "x-bar-key" points to nowhere. + EXPECT_THAT(getScopedRdsProvider()->config()->getRouteConfig( + TestHeaderMapImpl{{"Addr", "x-foo-key;x-bar-key"}}), + IsNull()); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}) + ->name(), + "foo_routes"); +} + +// Tests that multiple uniquely named non-conflict resources are allowed in config updates. +TEST_F(ScopedRdsTest, MultipleResourcesDelta) { + setup(); + init_watcher_.expectReady().Times(2); // SRDS and RDS "foo_routes" + + const std::string config_yaml = R"EOF( +name: foo_scope +route_configuration_name: foo_routes key: fragments: - string_key: x-foo-key +)EOF"; + Protobuf::RepeatedPtrField resources; + parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml); + const std::string config_yaml2 = R"EOF( +name: foo_scope2 +route_configuration_name: foo_routes +key: + fragments: + - string_key: x-bar-key )EOF"; parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml2); - EXPECT_NO_THROW(subscription_callbacks_->onConfigUpdate(resources, "1")); + + // Delta API. + EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(anyToResource(resources, "2"), {}, "1")); + factory_context_.init_manager_.initialize(init_watcher_); EXPECT_EQ( 1UL, factory_context_.scope_.counter("foo.scoped_rds.foo_scoped_routes.config_reload").value()); + EXPECT_EQ(getScopedRouteMap().size(), 2); + + // Verify the config is a ScopedConfigImpl instance, both scopes point to "" as RDS hasn't kicked + // in yet(NullConfigImpl returned). + EXPECT_NE(getScopedRdsProvider(), nullptr); + EXPECT_NE(getScopedRdsProvider()->config(), nullptr); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}) + ->name(), + ""); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-bar-key"}}) + ->name(), + ""); + // RDS updates foo_routes. + pushRdsConfig("foo_routes", "111"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}) + ->name(), + "foo_routes"); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-bar-key"}}) + ->name(), + "foo_routes"); + + // Delete foo_scope2. + resources.RemoveLast(); + Protobuf::RepeatedPtrField deletes; + *deletes.Add() = "foo_scope2"; + EXPECT_NO_THROW(srds_subscription_->onConfigUpdate(anyToResource(resources, "4"), deletes, "2")); + EXPECT_EQ(getScopedRouteMap().size(), 1); + EXPECT_EQ(getScopedRouteMap().count("foo_scope"), 1); + EXPECT_EQ( + 2UL, + factory_context_.scope_.counter("foo.scoped_rds.foo_scoped_routes.config_reload").value()); + // now scope key "x-bar-key" points to nowhere. + EXPECT_THAT(getScopedRdsProvider()->config()->getRouteConfig( + TestHeaderMapImpl{{"Addr", "x-foo-key;x-bar-key"}}), + IsNull()); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}) + ->name(), + "foo_routes"); +} + +// Tests that conflict resources are detected. +TEST_F(ScopedRdsTest, MultipleResourcesWithKeyConflict) { + setup(); + + const std::string config_yaml = R"EOF( +name: foo_scope +route_configuration_name: foo_routes +key: + fragments: + - string_key: x-foo-key +)EOF"; + Protobuf::RepeatedPtrField resources; + parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml); + const std::string config_yaml2 = R"EOF( +name: foo_scope2 +route_configuration_name: foo_routes +key: + fragments: + - string_key: x-foo-key +)EOF"; + parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml2); + EXPECT_THROW_WITH_REGEX( + srds_subscription_->onConfigUpdate(resources, "1"), EnvoyException, + ".*scope key conflict found, first scope is 'foo_scope', second scope is 'foo_scope2'"); + EXPECT_EQ( + // Fully rejected. + 0UL, + factory_context_.scope_.counter("foo.scoped_rds.foo_scoped_routes.config_reload").value()); + // Scope key "x-foo-key" points to nowhere. + EXPECT_NE(getScopedRdsProvider(), nullptr); + EXPECT_NE(getScopedRdsProvider()->config(), nullptr); + EXPECT_THAT(getScopedRdsProvider()->config()->getRouteConfig( + TestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}), + IsNull()); + factory_context_.init_manager_.initialize(init_watcher_); + init_watcher_.expectReady().Times( + 1); // Just SRDS, RDS "foo_routes" will initialized by the noop init-manager. + EXPECT_EQ(factory_context_.scope_.counter("foo.rds.foo_routes.config_reload").value(), 0UL); + + // Delta API. + EXPECT_CALL(factory_context_.init_manager_, state()) + .WillOnce(Return(Init::Manager::State::Initialized)); + EXPECT_THROW_WITH_REGEX( + srds_subscription_->onConfigUpdate(anyToResource(resources, "2"), {}, "2"), EnvoyException, + ".*scope key conflict found, first scope is 'foo_scope', second scope is 'foo_scope2'"); + EXPECT_EQ( + // Partially reject. + 1UL, + factory_context_.scope_.counter("foo.scoped_rds.foo_scoped_routes.config_reload").value()); + // foo_scope update is applied. + EXPECT_EQ(getScopedRouteMap().size(), 1UL); + EXPECT_EQ(getScopedRouteMap().count("foo_scope"), 1); + // Scope key "x-foo-key" points to foo_routes due to partial rejection. + pushRdsConfig("foo_routes", "111"); // Push some real route configuration. + EXPECT_EQ(1UL, factory_context_.scope_.counter("foo.rds.foo_routes.config_reload").value()); + EXPECT_EQ(getScopedRdsProvider() + ->config() + ->getRouteConfig(TestHeaderMapImpl{{"Addr", "x-foo-key;x-foo-key"}}) + ->name(), + "foo_routes"); } // Tests that only one resource is provided during a config update. -TEST_F(ScopedRdsTest, InvalidDuplicateResource) { +TEST_F(ScopedRdsTest, InvalidDuplicateResourceSotw) { setup(); + factory_context_.init_manager_.initialize(init_watcher_); + init_watcher_.expectReady().Times(0); const std::string config_yaml = R"EOF( name: foo_scope @@ -165,8 +474,42 @@ route_configuration_name: foo_routes Protobuf::RepeatedPtrField resources; parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml); parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml); - EXPECT_THROW_WITH_MESSAGE(subscription_callbacks_->onConfigUpdate(resources, "1"), EnvoyException, - "duplicate scoped route configuration foo_scope found"); + EXPECT_THROW_WITH_MESSAGE(srds_subscription_->onConfigUpdate(resources, "1"), EnvoyException, + "duplicate scoped route configuration 'foo_scope' found"); +} + +// Tests that only one resource is provided during a config update. +TEST_F(ScopedRdsTest, InvalidDuplicateResourceDelta) { + setup(); + factory_context_.init_manager_.initialize(init_watcher_); + // After the above initialize, the default init_manager should return "Initialized". + EXPECT_CALL(factory_context_.init_manager_, state()) + .WillOnce(Return(Init::Manager::State::Initialized)); + init_watcher_.expectReady().Times( + 1); // SRDS onConfigUpdate breaks, but first foo_routes will + // kick start if it's initialized post-Server/LDS initialization. + + const std::string config_yaml = R"EOF( +name: foo_scope +route_configuration_name: foo_routes +key: + fragments: + - string_key: x-foo-key +)EOF"; + Protobuf::RepeatedPtrField resources; + parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml); + parseScopedRouteConfigurationFromYaml(*resources.Add(), config_yaml); + EXPECT_THROW_WITH_MESSAGE( + srds_subscription_->onConfigUpdate(anyToResource(resources, "1"), {}, "1"), EnvoyException, + "Error adding/updating scoped route(s): duplicate scoped route configuration 'foo_scope' " + "found"); + EXPECT_EQ( + // Partially reject. + 1UL, + factory_context_.scope_.counter("foo.scoped_rds.foo_scoped_routes.config_reload").value()); + // foo_scope update is applied. + EXPECT_EQ(getScopedRouteMap().size(), 1UL); + EXPECT_EQ(getScopedRouteMap().count("foo_scope"), 1); } // Tests a config update failure. @@ -177,31 +520,37 @@ TEST_F(ScopedRdsTest, ConfigUpdateFailure) { timeSystem().setSystemTime(time); const EnvoyException ex(fmt::format("config failure")); // Verify the failure updates the lastUpdated() timestamp. - subscription_callbacks_->onConfigUpdateFailed( - Envoy::Config::ConfigUpdateFailureReason::UpdateRejected, &ex); + srds_subscription_->onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason::UpdateRejected, + &ex); EXPECT_EQ(std::chrono::time_point_cast(provider_->lastUpdated()) .time_since_epoch(), time); } -using ScopedRoutesConfigProviderManagerTest = ScopedRoutesTestBase; +// Tests that the /config_dump handler returns the corresponding scoped routing +// config. +TEST_F(ScopedRdsTest, ConfigDump) { + setup(); + factory_context_.init_manager_.initialize(init_watcher_); + EXPECT_CALL(factory_context_.init_manager_, state()) + .Times(2) // There are two SRDS pushes. + .WillRepeatedly(Return(Init::Manager::State::Initialized)); + init_watcher_.expectReady().Times(1); // SRDS only, no RDS push. -// Tests that the /config_dump handler returns the corresponding scoped routing config. -TEST_F(ScopedRoutesConfigProviderManagerTest, ConfigDump) { auto message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["route_scopes"](); const auto& scoped_routes_config_dump = - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( *message_ptr); - // No routes at all, no last_updated timestamp + // No routes at all(no SRDS push yet), no last_updated timestamp envoy::admin::v2alpha::ScopedRoutesConfigDump expected_config_dump; TestUtility::loadFromYaml(R"EOF( inline_scoped_route_configs: dynamic_scoped_route_configs: )EOF", expected_config_dump); - EXPECT_EQ(expected_config_dump.DebugString(), scoped_routes_config_dump.DebugString()); + EXPECT_TRUE(TestUtility::protoEqual(expected_config_dump, scoped_routes_config_dump)); timeSystem().setSystemTime(std::chrono::milliseconds(1234567891234)); @@ -215,7 +564,9 @@ stat_prefix: foo name: $0 scope_key_builder: fragments: - - header_value_extractor: { name: X-Google-VIP } + - header_value_extractor: + name: Addr + index: 0 $1 )EOF"; const std::string inline_scoped_route_configs_yaml = R"EOF( @@ -237,7 +588,7 @@ stat_prefix: foo factory_context_, "foo.", *config_provider_manager_); message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["route_scopes"](); const auto& scoped_routes_config_dump2 = - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( *message_ptr); TestUtility::loadFromYaml(R"EOF( inline_scoped_route_configs: @@ -257,17 +608,9 @@ stat_prefix: foo dynamic_scoped_route_configs: )EOF", expected_config_dump); - EXPECT_EQ(expected_config_dump.DebugString(), scoped_routes_config_dump2.DebugString()); - - const std::string scoped_rds_config_yaml = R"EOF( - scoped_rds: - scoped_rds_config_source: -)EOF"; - Envoy::Config::ConfigProviderPtr dynamic_provider = ScopedRoutesConfigProviderUtil::create( - parseHttpConnectionManagerFromYaml(absl::Substitute( - hcm_base_config_yaml, "foo-dynamic-scoped-routes", scoped_rds_config_yaml)), - factory_context_, "foo.", *config_provider_manager_); + EXPECT_TRUE(TestUtility::protoEqual(expected_config_dump, scoped_routes_config_dump2)); + // Now SRDS kicks off. Protobuf::RepeatedPtrField resources; resources.Add()->PackFrom(parseScopedRouteConfigurationFromYaml(R"EOF( name: dynamic-foo @@ -277,8 +620,7 @@ route_configuration_name: dynamic-foo-route-config )EOF")); timeSystem().setSystemTime(std::chrono::milliseconds(1234567891567)); - factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate(resources, - "1"); + srds_subscription_->onConfigUpdate(resources, "1"); TestUtility::loadFromYaml(R"EOF( inline_scoped_route_configs: @@ -296,7 +638,7 @@ route_configuration_name: dynamic-foo-route-config seconds: 1234567891 nanos: 234000000 dynamic_scoped_route_configs: - - name: foo-dynamic-scoped-routes + - name: foo_scoped_routes scoped_route_configs: - name: dynamic-foo route_configuration_name: dynamic-foo-route-config @@ -310,13 +652,12 @@ route_configuration_name: dynamic-foo-route-config expected_config_dump); message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["route_scopes"](); const auto& scoped_routes_config_dump3 = - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( *message_ptr); - EXPECT_EQ(expected_config_dump.DebugString(), scoped_routes_config_dump3.DebugString()); + EXPECT_TRUE(TestUtility::protoEqual(expected_config_dump, scoped_routes_config_dump3)); resources.Clear(); - factory_context_.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate(resources, - "2"); + srds_subscription_->onConfigUpdate(resources, "2"); TestUtility::loadFromYaml(R"EOF( inline_scoped_route_configs: - name: foo-scoped-routes @@ -333,7 +674,7 @@ route_configuration_name: dynamic-foo-route-config seconds: 1234567891 nanos: 234000000 dynamic_scoped_route_configs: - - name: foo-dynamic-scoped-routes + - name: foo_scoped_routes last_updated: seconds: 1234567891 nanos: 567000000 @@ -342,16 +683,15 @@ route_configuration_name: dynamic-foo-route-config expected_config_dump); message_ptr = factory_context_.admin_.config_tracker_.config_tracker_callbacks_["route_scopes"](); const auto& scoped_routes_config_dump4 = - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( *message_ptr); - EXPECT_EQ(expected_config_dump.DebugString(), scoped_routes_config_dump4.DebugString()); + EXPECT_TRUE(TestUtility::protoEqual(expected_config_dump, scoped_routes_config_dump4)); } -using ScopedRoutesConfigProviderManagerDeathTest = ScopedRoutesConfigProviderManagerTest; - // Tests that SRDS only allows creation of delta static config providers. -TEST_F(ScopedRoutesConfigProviderManagerDeathTest, DeltaStaticConfigProviderOnly) { - // Use match all regex due to lack of distinctive matchable output for coverage test. +TEST_F(ScopedRdsTest, DeltaStaticConfigProviderOnly) { + // Use match all regex due to lack of distinctive matchable output for + // coverage test. EXPECT_DEATH(config_provider_manager_->createStaticConfigProvider( parseScopedRouteConfigurationFromYaml(R"EOF( name: dynamic-foo diff --git a/test/common/router/vhds_test.cc b/test/common/router/vhds_test.cc index 01d3287421..4bac9088b2 100644 --- a/test/common/router/vhds_test.cc +++ b/test/common/router/vhds_test.cc @@ -24,13 +24,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::InSequence; -using testing::Invoke; -using testing::Return; -using testing::ReturnRef; -using testing::SaveArg; - namespace Envoy { namespace Router { namespace { diff --git a/test/common/runtime/runtime_impl_test.cc b/test/common/runtime/runtime_impl_test.cc index 82fedad731..11d0293094 100644 --- a/test/common/runtime/runtime_impl_test.cc +++ b/test/common/runtime/runtime_impl_test.cc @@ -23,8 +23,6 @@ using testing::Invoke; using testing::InvokeWithoutArgs; using testing::NiceMock; using testing::Return; -using testing::ReturnNew; -using testing::ReturnRef; namespace Envoy { namespace Runtime { @@ -179,6 +177,15 @@ TEST_F(DiskLoaderImplTest, All) { // test_feature_false is not in runtime_features.cc and so is false by default. EXPECT_EQ(false, snapshot->runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); + // Deprecation +#ifdef ENVOY_DISABLE_DEPRECATED_FEATURES + EXPECT_EQ(false, snapshot->deprecatedFeatureEnabled("random_string_should_be_enabled")); +#else + EXPECT_EQ(true, snapshot->deprecatedFeatureEnabled("random_string_should_be_enabled")); +#endif + EXPECT_EQ(false, snapshot->deprecatedFeatureEnabled( + "envoy.deprecated_features.deprecated.proto:is_deprecated_fatal")); + // Feature defaults via helper function. EXPECT_EQ(false, runtimeFeatureEnabled("envoy.reloadable_features.test_feature_false")); EXPECT_EQ(true, runtimeFeatureEnabled("envoy.reloadable_features.test_feature_true")); diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index 7802118643..bcedd9d363 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -21,7 +21,6 @@ using ::testing::_; using ::testing::Invoke; -using ::testing::Return; namespace Envoy { namespace Secret { @@ -87,7 +86,7 @@ TEST_F(SdsApiTest, DynamicTlsCertificateUpdateSuccess) { EXPECT_CALL(secret_callback, onAddOrUpdateSecret()); subscription_factory_.callbacks_->onConfigUpdate(secret_resources, ""); - Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), *api_); + Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), nullptr, *api_); const std::string cert_pem = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), @@ -182,7 +181,7 @@ TEST_F(SdsApiTest, DeltaUpdateSuccess) { initialize(); subscription_factory_.callbacks_->onConfigUpdate(secret_resources, {}, ""); - Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), *api_); + Ssl::TlsCertificateConfigImpl tls_config(*sds_api.secret(), nullptr, *api_); const std::string cert_pem = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), diff --git a/test/common/secret/secret_manager_impl_test.cc b/test/common/secret/secret_manager_impl_test.cc index b3a6105fb1..69e3051ce8 100644 --- a/test/common/secret/secret_manager_impl_test.cc +++ b/test/common/secret/secret_manager_impl_test.cc @@ -66,7 +66,7 @@ name: "abc.com" ASSERT_NE(secret_manager->findStaticTlsCertificateProvider("abc.com"), nullptr); Ssl::TlsCertificateConfigImpl tls_config( - *secret_manager->findStaticTlsCertificateProvider("abc.com")->secret(), *api_); + *secret_manager->findStaticTlsCertificateProvider("abc.com")->secret(), nullptr, *api_); const std::string cert_pem = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), @@ -206,7 +206,7 @@ name: "abc.com" init_target_handle->initialize(init_watcher); secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate(secret_resources, ""); - Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), *api_); + Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), nullptr, *api_); const std::string cert_pem = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(cert_pem)), @@ -261,7 +261,7 @@ name: "abc.com" init_target_handle->initialize(init_watcher); secret_context.cluster_manager_.subscription_factory_.callbacks_->onConfigUpdate(secret_resources, "keycert-v1"); - Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), *api_); + Ssl::TlsCertificateConfigImpl tls_config(*secret_provider->secret(), nullptr, *api_); EXPECT_EQ("DUMMY_INLINE_BYTES_FOR_CERT_CHAIN", tls_config.certificateChain()); EXPECT_EQ("DUMMY_INLINE_BYTES_FOR_PRIVATE_KEY", tls_config.privateKey()); EXPECT_EQ("DUMMY_PASSWORD", tls_config.password()); diff --git a/test/common/stats/BUILD b/test/common/stats/BUILD index 6ab96a754d..b4b5281941 100644 --- a/test/common/stats/BUILD +++ b/test/common/stats/BUILD @@ -15,7 +15,7 @@ envoy_cc_test( srcs = ["allocator_impl_test.cc"], deps = [ "//source/common/stats:allocator_lib", - "//source/common/stats:fake_symbol_table_lib", + "//source/common/stats:symbol_table_creator_lib", "//test/test_common:logging_lib", ], ) @@ -33,7 +33,7 @@ envoy_cc_test( srcs = ["metric_impl_test.cc"], deps = [ "//source/common/stats:allocator_lib", - "//source/common/stats:fake_symbol_table_lib", + "//source/common/stats:symbol_table_creator_lib", "//source/common/stats:utility_lib", "//test/test_common:logging_lib", ], @@ -43,9 +43,9 @@ envoy_cc_test( name = "stat_merger_test", srcs = ["stat_merger_test.cc"], deps = [ - "//source/common/stats:fake_symbol_table_lib", "//source/common/stats:isolated_store_lib", "//source/common/stats:stat_merger_lib", + "//source/common/stats:symbol_table_creator_lib", "//source/common/stats:thread_local_store_lib", "//test/test_common:utility_lib", ], @@ -61,6 +61,7 @@ envoy_cc_test_library( deps = [ "//source/common/common:assert_lib", "//source/common/memory:stats_lib", + "//source/common/stats:symbol_table_creator_lib", ], ) @@ -83,6 +84,7 @@ envoy_cc_test( envoy_cc_test( name = "symbol_table_impl_test", srcs = ["symbol_table_impl_test.cc"], + external_deps = ["abseil_hash_testing"], deps = [ ":stat_test_utility_lib", "//source/common/common:mutex_tracer_lib", diff --git a/test/common/stats/allocator_impl_test.cc b/test/common/stats/allocator_impl_test.cc index 37339b1384..f1b646a46b 100644 --- a/test/common/stats/allocator_impl_test.cc +++ b/test/common/stats/allocator_impl_test.cc @@ -1,7 +1,7 @@ #include #include "common/stats/allocator_impl.h" -#include "common/stats/fake_symbol_table_impl.h" +#include "common/stats/symbol_table_creator.h" #include "test/test_common/logging.h" @@ -13,21 +13,23 @@ namespace { class AllocatorImplTest : public testing::Test { protected: - AllocatorImplTest() : alloc_(symbol_table_), pool_(symbol_table_) {} + AllocatorImplTest() + : symbol_table_(SymbolTableCreator::makeSymbolTable()), alloc_(*symbol_table_), + pool_(*symbol_table_) {} ~AllocatorImplTest() override { clearStorage(); } StatNameStorage makeStatStorage(absl::string_view name) { - return StatNameStorage(name, symbol_table_); + return StatNameStorage(name, *symbol_table_); } StatName makeStat(absl::string_view name) { return pool_.add(name); } void clearStorage() { pool_.clear(); - EXPECT_EQ(0, symbol_table_.numSymbols()); + EXPECT_EQ(0, symbol_table_->numSymbols()); } - FakeSymbolTableImpl symbol_table_; + SymbolTablePtr symbol_table_; AllocatorImpl alloc_; StatNamePool pool_; }; diff --git a/test/common/stats/metric_impl_test.cc b/test/common/stats/metric_impl_test.cc index 16e90a2201..b178842fa8 100644 --- a/test/common/stats/metric_impl_test.cc +++ b/test/common/stats/metric_impl_test.cc @@ -1,7 +1,7 @@ #include #include "common/stats/allocator_impl.h" -#include "common/stats/fake_symbol_table_impl.h" +#include "common/stats/symbol_table_creator.h" #include "common/stats/utility.h" #include "test/test_common/logging.h" @@ -14,17 +14,19 @@ namespace { class MetricImplTest : public testing::Test { protected: - MetricImplTest() : alloc_(symbol_table_), pool_(symbol_table_) {} + MetricImplTest() + : symbol_table_(SymbolTableCreator::makeSymbolTable()), alloc_(*symbol_table_), + pool_(*symbol_table_) {} ~MetricImplTest() override { clearStorage(); } StatName makeStat(absl::string_view name) { return pool_.add(name); } void clearStorage() { pool_.clear(); - EXPECT_EQ(0, symbol_table_.numSymbols()); + EXPECT_EQ(0, symbol_table_->numSymbols()); } - FakeSymbolTableImpl symbol_table_; + SymbolTablePtr symbol_table_; AllocatorImpl alloc_; StatNamePool pool_; }; diff --git a/test/common/stats/stat_merger_test.cc b/test/common/stats/stat_merger_test.cc index f56e8d98c7..4168bef221 100644 --- a/test/common/stats/stat_merger_test.cc +++ b/test/common/stats/stat_merger_test.cc @@ -1,8 +1,8 @@ #include -#include "common/stats/fake_symbol_table_impl.h" #include "common/stats/isolated_store_impl.h" #include "common/stats/stat_merger.h" +#include "common/stats/symbol_table_creator.h" #include "common/stats/thread_local_store.h" #include "test/test_common/utility.h" @@ -182,8 +182,8 @@ TEST_F(StatMergerTest, gaugeMergeImportMode) { class StatMergerThreadLocalTest : public testing::Test { protected: - FakeSymbolTableImpl symbol_table_; - AllocatorImpl alloc_{symbol_table_}; + SymbolTablePtr symbol_table_{SymbolTableCreator::makeSymbolTable()}; + AllocatorImpl alloc_{*symbol_table_}; ThreadLocalStoreImpl store_{alloc_}; }; diff --git a/test/common/stats/stat_test_utility.h b/test/common/stats/stat_test_utility.h index afc97bb9c6..c10fe40320 100644 --- a/test/common/stats/stat_test_utility.h +++ b/test/common/stats/stat_test_utility.h @@ -2,6 +2,7 @@ #include "common/common/logger.h" #include "common/memory/stats.h" +#include "common/stats/symbol_table_creator.h" #include "absl/strings/str_join.h" #include "absl/strings/string_view.h" @@ -104,6 +105,13 @@ class MemoryTest { } \ } while (false) +class SymbolTableCreatorTestPeer { +public: + static void setUseFakeSymbolTables(bool use_fakes) { + SymbolTableCreator::setUseFakeSymbolTables(use_fakes); + } +}; + } // namespace TestUtil } // namespace Stats } // namespace Envoy diff --git a/test/common/stats/stats_matcher_impl_test.cc b/test/common/stats/stats_matcher_impl_test.cc index 485560f2b7..9277a545e1 100644 --- a/test/common/stats/stats_matcher_impl_test.cc +++ b/test/common/stats/stats_matcher_impl_test.cc @@ -6,9 +6,6 @@ #include "gtest/gtest.h" -using testing::IsFalse; -using testing::IsTrue; - namespace Envoy { namespace Stats { diff --git a/test/common/stats/symbol_table_impl_test.cc b/test/common/stats/symbol_table_impl_test.cc index d6a533b092..4917caeca9 100644 --- a/test/common/stats/symbol_table_impl_test.cc +++ b/test/common/stats/symbol_table_impl_test.cc @@ -10,6 +10,7 @@ #include "test/test_common/logging.h" #include "test/test_common/utility.h" +#include "absl/hash/hash_testing.h" #include "absl/synchronization/blocking_counter.h" #include "gtest/gtest.h" @@ -565,6 +566,26 @@ TEST_P(StatNameTest, StatNameSet) { EXPECT_NE(dynamic2.data(), dynamic.data()); } +TEST_P(StatNameTest, StatNameEmptyEquivalent) { + StatName empty1; + StatName empty2 = makeStat(""); + StatName non_empty = makeStat("a"); + EXPECT_EQ(empty1, empty2); + EXPECT_EQ(empty1.hash(), empty2.hash()); + EXPECT_NE(empty1, non_empty); + EXPECT_NE(empty2, non_empty); + EXPECT_NE(empty1.hash(), non_empty.hash()); + EXPECT_NE(empty2.hash(), non_empty.hash()); +} + +TEST_P(StatNameTest, SupportsAbslHash) { + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ + StatName(), + makeStat(""), + makeStat("hello.world"), + })); +} + // Tests the memory savings realized from using symbol tables with 1k // clusters. This test shows the memory drops from almost 8M to less than // 2M. Note that only SymbolTableImpl is tested for memory consumption, diff --git a/test/common/stats/thread_local_store_speed_test.cc b/test/common/stats/thread_local_store_speed_test.cc index 877c6bc7bb..6ea30cb61e 100644 --- a/test/common/stats/thread_local_store_speed_test.cc +++ b/test/common/stats/thread_local_store_speed_test.cc @@ -22,18 +22,18 @@ namespace Envoy { class ThreadLocalStorePerf { public: ThreadLocalStorePerf() - : heap_alloc_(symbol_table_), store_(heap_alloc_), - api_(Api::createApiForTest(store_, time_system_)) { + : symbol_table_(Stats::SymbolTableCreator::makeSymbolTable()), heap_alloc_(*symbol_table_), + store_(heap_alloc_), api_(Api::createApiForTest(store_, time_system_)) { store_.setTagProducer(std::make_unique(stats_config_)); Stats::TestUtil::forEachSampleStat(1000, [this](absl::string_view name) { - stat_names_.push_back(std::make_unique(name, symbol_table_)); + stat_names_.push_back(std::make_unique(name, *symbol_table_)); }); } ~ThreadLocalStorePerf() { for (auto& stat_name_storage : stat_names_) { - stat_name_storage->free(symbol_table_); + stat_name_storage->free(*symbol_table_); } store_.shutdownThreading(); if (tls_) { @@ -54,7 +54,7 @@ class ThreadLocalStorePerf { } private: - Stats::FakeSymbolTableImpl symbol_table_; + Stats::SymbolTablePtr symbol_table_; Event::SimulatedTimeSystem time_system_; Stats::AllocatorImpl heap_alloc_; Stats::ThreadLocalStoreImpl store_; diff --git a/test/common/stats/thread_local_store_test.cc b/test/common/stats/thread_local_store_test.cc index 711d1c459e..baba626c31 100644 --- a/test/common/stats/thread_local_store_test.cc +++ b/test/common/stats/thread_local_store_test.cc @@ -26,7 +26,6 @@ using testing::_; using testing::InSequence; -using testing::Invoke; using testing::NiceMock; using testing::Ref; using testing::Return; @@ -39,7 +38,8 @@ const uint64_t MaxStatNameLength = 127; class StatsThreadLocalStoreTest : public testing::Test { public: StatsThreadLocalStoreTest() - : alloc_(symbol_table_), store_(std::make_unique(alloc_)) { + : symbol_table_(SymbolTableCreator::makeSymbolTable()), alloc_(*symbol_table_), + store_(std::make_unique(alloc_)) { store_->addSink(sink_); } @@ -48,7 +48,7 @@ class StatsThreadLocalStoreTest : public testing::Test { store_->addSink(sink_); } - Stats::FakeSymbolTableImpl symbol_table_; + SymbolTablePtr symbol_table_; NiceMock main_thread_dispatcher_; NiceMock tls_; AllocatorImpl alloc_; @@ -78,7 +78,7 @@ class HistogramTest : public testing::Test { public: using NameHistogramMap = std::map; - HistogramTest() : alloc_(symbol_table_) {} + HistogramTest() : symbol_table_(SymbolTableCreator::makeSymbolTable()), alloc_(*symbol_table_) {} void SetUp() override { store_ = std::make_unique(alloc_); @@ -166,7 +166,7 @@ class HistogramTest : public testing::Test { } } - FakeSymbolTableImpl symbol_table_; + SymbolTablePtr symbol_table_; NiceMock main_thread_dispatcher_; NiceMock tls_; AllocatorImpl alloc_; @@ -182,7 +182,7 @@ TEST_F(StatsThreadLocalStoreTest, NoTls) { Counter& c1 = store_->counter("c1"); EXPECT_EQ(&c1, &store_->counter("c1")); - StatNameManagedStorage c1_name("c1", symbol_table_); + StatNameManagedStorage c1_name("c1", *symbol_table_); c1.add(100); auto found_counter = store_->findCounter(c1_name.statName()); ASSERT_TRUE(found_counter.has_value()); @@ -193,7 +193,7 @@ TEST_F(StatsThreadLocalStoreTest, NoTls) { Gauge& g1 = store_->gauge("g1", Gauge::ImportMode::Accumulate); EXPECT_EQ(&g1, &store_->gauge("g1", Gauge::ImportMode::Accumulate)); - StatNameManagedStorage g1_name("g1", symbol_table_); + StatNameManagedStorage g1_name("g1", *symbol_table_); g1.set(100); auto found_gauge = store_->findGauge(g1_name.statName()); ASSERT_TRUE(found_gauge.has_value()); @@ -204,7 +204,7 @@ TEST_F(StatsThreadLocalStoreTest, NoTls) { Histogram& h1 = store_->histogram("h1"); EXPECT_EQ(&h1, &store_->histogram("h1")); - StatNameManagedStorage h1_name("h1", symbol_table_); + StatNameManagedStorage h1_name("h1", *symbol_table_); auto found_histogram = store_->findHistogram(h1_name.statName()); ASSERT_TRUE(found_histogram.has_value()); EXPECT_EQ(&h1, &found_histogram->get()); @@ -230,7 +230,7 @@ TEST_F(StatsThreadLocalStoreTest, Tls) { Counter& c1 = store_->counter("c1"); EXPECT_EQ(&c1, &store_->counter("c1")); - StatNameManagedStorage c1_name("c1", symbol_table_); + StatNameManagedStorage c1_name("c1", *symbol_table_); c1.add(100); auto found_counter = store_->findCounter(c1_name.statName()); ASSERT_TRUE(found_counter.has_value()); @@ -241,7 +241,7 @@ TEST_F(StatsThreadLocalStoreTest, Tls) { Gauge& g1 = store_->gauge("g1", Gauge::ImportMode::Accumulate); EXPECT_EQ(&g1, &store_->gauge("g1", Gauge::ImportMode::Accumulate)); - StatNameManagedStorage g1_name("g1", symbol_table_); + StatNameManagedStorage g1_name("g1", *symbol_table_); g1.set(100); auto found_gauge = store_->findGauge(g1_name.statName()); ASSERT_TRUE(found_gauge.has_value()); @@ -252,7 +252,7 @@ TEST_F(StatsThreadLocalStoreTest, Tls) { Histogram& h1 = store_->histogram("h1"); EXPECT_EQ(&h1, &store_->histogram("h1")); - StatNameManagedStorage h1_name("h1", symbol_table_); + StatNameManagedStorage h1_name("h1", *symbol_table_); auto found_histogram = store_->findHistogram(h1_name.statName()); ASSERT_TRUE(found_histogram.has_value()); EXPECT_EQ(&h1, &found_histogram->get()); @@ -284,11 +284,11 @@ TEST_F(StatsThreadLocalStoreTest, BasicScope) { Counter& c2 = scope1->counter("c2"); EXPECT_EQ("c1", c1.name()); EXPECT_EQ("scope1.c2", c2.name()); - StatNameManagedStorage c1_name("c1", symbol_table_); + StatNameManagedStorage c1_name("c1", *symbol_table_); auto found_counter = store_->findCounter(c1_name.statName()); ASSERT_TRUE(found_counter.has_value()); EXPECT_EQ(&c1, &found_counter->get()); - StatNameManagedStorage c2_name("scope1.c2", symbol_table_); + StatNameManagedStorage c2_name("scope1.c2", *symbol_table_); auto found_counter2 = store_->findCounter(c2_name.statName()); ASSERT_TRUE(found_counter2.has_value()); EXPECT_EQ(&c2, &found_counter2->get()); @@ -297,11 +297,11 @@ TEST_F(StatsThreadLocalStoreTest, BasicScope) { Gauge& g2 = scope1->gauge("g2", Gauge::ImportMode::Accumulate); EXPECT_EQ("g1", g1.name()); EXPECT_EQ("scope1.g2", g2.name()); - StatNameManagedStorage g1_name("g1", symbol_table_); + StatNameManagedStorage g1_name("g1", *symbol_table_); auto found_gauge = store_->findGauge(g1_name.statName()); ASSERT_TRUE(found_gauge.has_value()); EXPECT_EQ(&g1, &found_gauge->get()); - StatNameManagedStorage g2_name("scope1.g2", symbol_table_); + StatNameManagedStorage g2_name("scope1.g2", *symbol_table_); auto found_gauge2 = store_->findGauge(g2_name.statName()); ASSERT_TRUE(found_gauge2.has_value()); EXPECT_EQ(&g2, &found_gauge2->get()); @@ -314,11 +314,11 @@ TEST_F(StatsThreadLocalStoreTest, BasicScope) { h1.recordValue(100); EXPECT_CALL(sink_, onHistogramComplete(Ref(h2), 200)); h2.recordValue(200); - StatNameManagedStorage h1_name("h1", symbol_table_); + StatNameManagedStorage h1_name("h1", *symbol_table_); auto found_histogram = store_->findHistogram(h1_name.statName()); ASSERT_TRUE(found_histogram.has_value()); EXPECT_EQ(&h1, &found_histogram->get()); - StatNameManagedStorage h2_name("scope1.h2", symbol_table_); + StatNameManagedStorage h2_name("scope1.h2", *symbol_table_); auto found_histogram2 = store_->findHistogram(h2_name.statName()); ASSERT_TRUE(found_histogram2.has_value()); EXPECT_EQ(&h2, &found_histogram2->get()); @@ -380,7 +380,7 @@ TEST_F(StatsThreadLocalStoreTest, NestedScopes) { ScopePtr scope1 = store_->createScope("scope1."); Counter& c1 = scope1->counter("foo.bar"); EXPECT_EQ("scope1.foo.bar", c1.name()); - StatNameManagedStorage c1_name("scope1.foo.bar", symbol_table_); + StatNameManagedStorage c1_name("scope1.foo.bar", *symbol_table_); auto found_counter = store_->findCounter(c1_name.statName()); ASSERT_TRUE(found_counter.has_value()); EXPECT_EQ(&c1, &found_counter->get()); @@ -389,7 +389,7 @@ TEST_F(StatsThreadLocalStoreTest, NestedScopes) { Counter& c2 = scope2->counter("bar"); EXPECT_EQ(&c1, &c2); EXPECT_EQ("scope1.foo.bar", c2.name()); - StatNameManagedStorage c2_name("scope1.foo.bar", symbol_table_); + StatNameManagedStorage c2_name("scope1.foo.bar", *symbol_table_); auto found_counter2 = store_->findCounter(c2_name.statName()); ASSERT_TRUE(found_counter2.has_value()); @@ -455,12 +455,14 @@ TEST_F(StatsThreadLocalStoreTest, OverlappingScopes) { class LookupWithStatNameTest : public testing::Test { public: - LookupWithStatNameTest() : alloc_(symbol_table_), store_(alloc_), pool_(symbol_table_) {} + LookupWithStatNameTest() + : symbol_table_(SymbolTableCreator::makeSymbolTable()), alloc_(*symbol_table_), + store_(alloc_), pool_(*symbol_table_) {} ~LookupWithStatNameTest() override { store_.shutdownThreading(); } StatName makeStatName(absl::string_view name) { return pool_.add(name); } - Stats::FakeSymbolTableImpl symbol_table_; + SymbolTablePtr symbol_table_; AllocatorImpl alloc_; ThreadLocalStoreImpl store_; StatNamePool pool_; @@ -664,7 +666,8 @@ TEST_F(StatsMatcherTLSTest, TestExclusionRegex) { class RememberStatsMatcherTest : public testing::TestWithParam { public: RememberStatsMatcherTest() - : heap_alloc_(symbol_table_), store_(heap_alloc_), scope_(store_.createScope("scope.")) { + : symbol_table_(SymbolTableCreator::makeSymbolTable()), heap_alloc_(*symbol_table_), + store_(heap_alloc_), scope_(store_.createScope("scope.")) { if (GetParam()) { store_.initializeThreading(main_thread_dispatcher_, tls_); } @@ -755,7 +758,7 @@ class RememberStatsMatcherTest : public testing::TestWithParam { }; } - Stats::FakeSymbolTableImpl symbol_table_; + Stats::SymbolTablePtr symbol_table_; NiceMock main_thread_dispatcher_; NiceMock tls_; AllocatorImpl heap_alloc_; @@ -845,46 +848,85 @@ TEST_F(StatsThreadLocalStoreTest, NonHotRestartNoTruncation) { tls_.shutdownThread(); } -// Tests how much memory is consumed allocating 100k stats. -TEST(StatsThreadLocalStoreTestNoFixture, MemoryWithoutTls) { - MockSink sink; - Stats::FakeSymbolTableImpl symbol_table; - AllocatorImpl alloc(symbol_table); - ThreadLocalStoreImpl store(alloc); - store.addSink(sink); +class StatsThreadLocalStoreTestNoFixture : public testing::Test { +protected: + StatsThreadLocalStoreTestNoFixture() + : save_use_fakes_(SymbolTableCreator::useFakeSymbolTables()) {} + ~StatsThreadLocalStoreTestNoFixture() override { + TestUtil::SymbolTableCreatorTestPeer::setUseFakeSymbolTables(save_use_fakes_); + if (threading_enabled_) { + store_->shutdownThreading(); + tls_.shutdownThread(); + } + } - // Use a tag producer that will produce tags. - envoy::config::metrics::v2::StatsConfig stats_config; - store.setTagProducer(std::make_unique(stats_config)); + void init(bool use_fakes) { + TestUtil::SymbolTableCreatorTestPeer::setUseFakeSymbolTables(use_fakes); + symbol_table_ = SymbolTableCreator::makeSymbolTable(); + alloc_ = std::make_unique(*symbol_table_); + store_ = std::make_unique(*alloc_); + store_->addSink(sink_); + + // Use a tag producer that will produce tags. + envoy::config::metrics::v2::StatsConfig stats_config; + store_->setTagProducer(std::make_unique(stats_config)); + } + + void initThreading() { + threading_enabled_ = true; + store_->initializeThreading(main_thread_dispatcher_, tls_); + } + + static constexpr size_t million_ = 1000 * 1000; + + MockSink sink_; + SymbolTablePtr symbol_table_; + std::unique_ptr alloc_; + std::unique_ptr store_; + NiceMock main_thread_dispatcher_; + NiceMock tls_; + const bool save_use_fakes_; + bool threading_enabled_{false}; +}; +// Tests how much memory is consumed allocating 100k stats. +TEST_F(StatsThreadLocalStoreTestNoFixture, MemoryWithoutTlsFakeSymbolTable) { + init(true); TestUtil::MemoryTest memory_test; TestUtil::forEachSampleStat( - 1000, [&store](absl::string_view name) { store.counter(std::string(name)); }); - const size_t million = 1000 * 1000; + 1000, [this](absl::string_view name) { store_->counter(std::string(name)); }); EXPECT_MEMORY_EQ(memory_test.consumedBytes(), 15268336); // June 30, 2019 - EXPECT_MEMORY_LE(memory_test.consumedBytes(), 16 * million); + EXPECT_MEMORY_LE(memory_test.consumedBytes(), 16 * million_); } -TEST(StatsThreadLocalStoreTestNoFixture, MemoryWithTls) { - Stats::FakeSymbolTableImpl symbol_table; - AllocatorImpl alloc(symbol_table); - NiceMock main_thread_dispatcher; - NiceMock tls; - ThreadLocalStoreImpl store(alloc); +TEST_F(StatsThreadLocalStoreTestNoFixture, MemoryWithTlsFakeSymbolTable) { + init(true); + initThreading(); + TestUtil::MemoryTest memory_test; + TestUtil::forEachSampleStat( + 1000, [this](absl::string_view name) { store_->counter(std::string(name)); }); + EXPECT_MEMORY_EQ(memory_test.consumedBytes(), 17496848); // June 30, 2019 + EXPECT_MEMORY_LE(memory_test.consumedBytes(), 18 * million_); +} - // Use a tag producer that will produce tags. - envoy::config::metrics::v2::StatsConfig stats_config; - store.setTagProducer(std::make_unique(stats_config)); +// Tests how much memory is consumed allocating 100k stats. +TEST_F(StatsThreadLocalStoreTestNoFixture, MemoryWithoutTlsRealSymbolTable) { + init(false); + TestUtil::MemoryTest memory_test; + TestUtil::forEachSampleStat( + 1000, [this](absl::string_view name) { store_->counter(std::string(name)); }); + EXPECT_MEMORY_EQ(memory_test.consumedBytes(), 9139120); // Aug 9, 2019 + EXPECT_MEMORY_LE(memory_test.consumedBytes(), 10 * million_); +} - store.initializeThreading(main_thread_dispatcher, tls); +TEST_F(StatsThreadLocalStoreTestNoFixture, MemoryWithTlsRealSymbolTable) { + init(false); + initThreading(); TestUtil::MemoryTest memory_test; TestUtil::forEachSampleStat( - 1000, [&store](absl::string_view name) { store.counter(std::string(name)); }); - const size_t million = 1000 * 1000; - EXPECT_MEMORY_EQ(memory_test.consumedBytes(), 17496848); // June 30, 2019 - EXPECT_MEMORY_LE(memory_test.consumedBytes(), 18 * million); - store.shutdownThreading(); - tls.shutdownThread(); + 1000, [this](absl::string_view name) { store_->counter(std::string(name)); }); + EXPECT_MEMORY_EQ(memory_test.consumedBytes(), 11367632); // Aug 9, 2019 + EXPECT_MEMORY_LE(memory_test.consumedBytes(), 12 * million_); } TEST_F(StatsThreadLocalStoreTest, ShuttingDown) { @@ -929,11 +971,11 @@ TEST_F(StatsThreadLocalStoreTest, MergeDuringShutDown) { } TEST(ThreadLocalStoreThreadTest, ConstructDestruct) { - Stats::FakeSymbolTableImpl symbol_table; + SymbolTablePtr symbol_table(SymbolTableCreator::makeSymbolTable()); Api::ApiPtr api = Api::createApiForTest(); Event::DispatcherPtr dispatcher = api->allocateDispatcher(); NiceMock tls; - AllocatorImpl alloc(symbol_table); + AllocatorImpl alloc(*symbol_table); ThreadLocalStoreImpl store(alloc); store.initializeThreading(*dispatcher, tls); diff --git a/test/common/stream_info/test_util.h b/test/common/stream_info/test_util.h index a532ba1a8d..83627789c3 100644 --- a/test/common/stream_info/test_util.h +++ b/test/common/stream_info/test_util.h @@ -85,13 +85,22 @@ class TestStreamInfo : public StreamInfo::StreamInfo { return downstream_remote_address_; } - void setDownstreamSslConnection(const Ssl::ConnectionInfo* connection_info) override { + void + setDownstreamSslConnection(const Ssl::ConnectionInfoConstSharedPtr& connection_info) override { downstream_connection_info_ = connection_info; } - const Ssl::ConnectionInfo* downstreamSslConnection() const override { + Ssl::ConnectionInfoConstSharedPtr downstreamSslConnection() const override { return downstream_connection_info_; } + + void setUpstreamSslConnection(const Ssl::ConnectionInfoConstSharedPtr& connection_info) override { + upstream_connection_info_ = connection_info; + } + + Ssl::ConnectionInfoConstSharedPtr upstreamSslConnection() const override { + return upstream_connection_info_; + } void setRouteName(absl::string_view route_name) override { route_name_ = std::string(route_name); } @@ -207,7 +216,8 @@ class TestStreamInfo : public StreamInfo::StreamInfo { Network::Address::InstanceConstSharedPtr downstream_local_address_; Network::Address::InstanceConstSharedPtr downstream_direct_remote_address_; Network::Address::InstanceConstSharedPtr downstream_remote_address_; - const Ssl::ConnectionInfo* downstream_connection_info_{}; + Ssl::ConnectionInfoConstSharedPtr downstream_connection_info_; + Ssl::ConnectionInfoConstSharedPtr upstream_connection_info_; const Router::RouteEntry* route_entry_{}; envoy::api::v2::core::Metadata metadata_{}; Envoy::StreamInfo::FilterStateImpl filter_state_{}; diff --git a/test/common/tcp/conn_pool_test.cc b/test/common/tcp/conn_pool_test.cc index ec42cf60e6..9877d44377 100644 --- a/test/common/tcp/conn_pool_test.cc +++ b/test/common/tcp/conn_pool_test.cc @@ -18,14 +18,11 @@ #include "gtest/gtest.h" using testing::_; -using testing::DoAll; using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Property; using testing::Return; -using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Tcp { @@ -106,12 +103,12 @@ class ConnPoolImplForTest : public ConnPoolImpl { .WillOnce(Invoke( [&](Network::ReadFilterSharedPtr filter) -> void { test_conn.filter_ = filter; })); EXPECT_CALL(*test_conn.connection_, connect()); - EXPECT_CALL(*test_conn.connect_timer_, enableTimer(_)); + EXPECT_CALL(*test_conn.connect_timer_, enableTimer(_, _)); } void expectEnableUpstreamReady() { EXPECT_FALSE(upstream_ready_enabled_); - EXPECT_CALL(*mock_upstream_ready_timer_, enableTimer(_)).Times(1).RetiresOnSaturation(); + EXPECT_CALL(*mock_upstream_ready_timer_, enableTimer(_, _)).Times(1).RetiresOnSaturation(); } void expectAndRunUpstreamReady() { @@ -186,7 +183,7 @@ class TcpConnPoolImplDestructorTest : public testing::Test { connection_ = new NiceMock(); connect_timer_ = new NiceMock(&dispatcher_); EXPECT_CALL(dispatcher_, createClientConnection_(_, _, _, _)).WillOnce(Return(connection_)); - EXPECT_CALL(*connect_timer_, enableTimer(_)); + EXPECT_CALL(*connect_timer_, enableTimer(_, _)); callbacks_ = std::make_unique(); ConnectionPool::Cancellable* handle = conn_pool_->newConnection(*callbacks_); @@ -921,7 +918,7 @@ TEST_F(TcpConnPoolImplDestructorTest, TestPendingConnectionsAreClosed) { connection_ = new NiceMock(); connect_timer_ = new NiceMock(&dispatcher_); EXPECT_CALL(dispatcher_, createClientConnection_(_, _, _, _)).WillOnce(Return(connection_)); - EXPECT_CALL(*connect_timer_, enableTimer(_)); + EXPECT_CALL(*connect_timer_, enableTimer(_, _)); callbacks_ = std::make_unique(); ConnectionPool::Cancellable* handle = conn_pool_->newConnection(*callbacks_); diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 903249d9a0..07c505c2e9 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -821,21 +821,21 @@ TEST_F(TcpProxyTest, IdleTimeout) { setup(1, config); Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); raiseEventUpstreamConnected(0); Buffer::OwnedImpl buffer("hello"); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); filter_->onData(buffer, false); buffer.add("hello2"); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); upstream_callbacks_->onUpstreamData(buffer, false); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); filter_callbacks_.connection_.raiseBytesSentCallbacks(1); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); upstream_connections_.at(0)->raiseBytesSentCallbacks(2); EXPECT_CALL(*upstream_connections_.at(0), close(Network::ConnectionCloseType::NoFlush)); @@ -851,7 +851,7 @@ TEST_F(TcpProxyTest, IdleTimerDisabledDownstreamClose) { setup(1, config); Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); raiseEventUpstreamConnected(0); EXPECT_CALL(*idle_timer, disableTimer()); @@ -865,7 +865,7 @@ TEST_F(TcpProxyTest, IdleTimerDisabledUpstreamClose) { setup(1, config); Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); raiseEventUpstreamConnected(0); EXPECT_CALL(*idle_timer, disableTimer()); @@ -879,21 +879,21 @@ TEST_F(TcpProxyTest, IdleTimeoutWithOutstandingDataFlushed) { setup(1, config); Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); raiseEventUpstreamConnected(0); Buffer::OwnedImpl buffer("hello"); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); filter_->onData(buffer, false); buffer.add("hello2"); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); upstream_callbacks_->onUpstreamData(buffer, false); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); filter_callbacks_.connection_.raiseBytesSentCallbacks(1); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); upstream_connections_.at(0)->raiseBytesSentCallbacks(2); // Mark the upstream connection as blocked. @@ -947,9 +947,9 @@ TEST_F(TcpProxyTest, AccessLogPeerUriSan) { Network::Utility::resolveUrl("tcp://1.1.1.1:40000"); const std::vector uriSan{"someSan"}; - Ssl::MockConnectionInfo mockConnectionInfo; - EXPECT_CALL(mockConnectionInfo, uriSanPeerCertificate()).WillOnce(Return(uriSan)); - EXPECT_CALL(filter_callbacks_.connection_, ssl()).WillRepeatedly(Return(&mockConnectionInfo)); + auto mockConnectionInfo = std::make_shared(); + EXPECT_CALL(*mockConnectionInfo, uriSanPeerCertificate()).WillOnce(Return(uriSan)); + EXPECT_CALL(filter_callbacks_.connection_, ssl()).WillRepeatedly(Return(mockConnectionInfo)); setup(1, accessLogConfig("%DOWNSTREAM_PEER_URI_SAN%")); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -966,9 +966,9 @@ TEST_F(TcpProxyTest, AccessLogTlsSessionId) { const std::string tlsSessionId{ "D62A523A65695219D46FE1FFE285A4C371425ACE421B110B5B8D11D3EB4D5F0B"}; - Ssl::MockConnectionInfo mockConnectionInfo; - EXPECT_CALL(mockConnectionInfo, sessionId()).WillOnce(Return(tlsSessionId)); - EXPECT_CALL(filter_callbacks_.connection_, ssl()).WillRepeatedly(Return(&mockConnectionInfo)); + auto mockConnectionInfo = std::make_shared(); + EXPECT_CALL(*mockConnectionInfo, sessionId()).WillOnce(ReturnRef(tlsSessionId)); + EXPECT_CALL(filter_callbacks_.connection_, ssl()).WillRepeatedly(Return(mockConnectionInfo)); setup(1, accessLogConfig("%DOWNSTREAM_TLS_SESSION_ID%")); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -1010,6 +1010,21 @@ TEST_F(TcpProxyTest, AccessLogBytesRxTxDuration) { "bytesreceived=1 bytessent=2 datetime=[0-9-]+T[0-9:.]+Z nonzeronum=[1-9][0-9]*")); } +TEST_F(TcpProxyTest, AccessLogUpstreamSSLConnection) { + setup(1); + + NiceMock stream_info; + const std::string session_id = "D62A523A65695219D46FE1FFE285A4C371425ACE421B110B5B8D11D3EB4D5F0B"; + auto ssl_info = std::make_shared(); + EXPECT_CALL(*ssl_info, sessionId()).WillRepeatedly(ReturnRef(session_id)); + stream_info.setDownstreamSslConnection(ssl_info); + EXPECT_CALL(*upstream_connections_.at(0), streamInfo()).WillRepeatedly(ReturnRef(stream_info)); + + raiseEventUpstreamConnected(0); + ASSERT_NE(nullptr, filter_->getStreamInfo().upstreamSslConnection()); + EXPECT_EQ(session_id, filter_->getStreamInfo().upstreamSslConnection()->sessionId()); +} + // Tests that upstream flush works properly with no idle timeout configured. TEST_F(TcpProxyTest, UpstreamFlushNoTimeout) { setup(1); @@ -1043,7 +1058,7 @@ TEST_F(TcpProxyTest, UpstreamFlushTimeoutConfigured) { NiceMock* idle_timer = new NiceMock(&filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); raiseEventUpstreamConnected(0); EXPECT_CALL(*upstream_connections_.at(0), @@ -1056,7 +1071,7 @@ TEST_F(TcpProxyTest, UpstreamFlushTimeoutConfigured) { filter_.reset(); EXPECT_EQ(1U, config_->stats().upstream_flush_active_.value()); - EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(1000), _)); upstream_connections_.at(0)->raiseBytesSentCallbacks(1); // Simulate flush complete. @@ -1075,7 +1090,7 @@ TEST_F(TcpProxyTest, UpstreamFlushTimeoutExpired) { NiceMock* idle_timer = new NiceMock(&filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*idle_timer, enableTimer(_)); + EXPECT_CALL(*idle_timer, enableTimer(_, _)); raiseEventUpstreamConnected(0); EXPECT_CALL(*upstream_connections_.at(0), diff --git a/test/common/thread_local/thread_local_impl_test.cc b/test/common/thread_local/thread_local_impl_test.cc index 5a678c8393..fb3bd1cf39 100644 --- a/test/common/thread_local/thread_local_impl_test.cc +++ b/test/common/thread_local/thread_local_impl_test.cc @@ -85,6 +85,40 @@ TEST_F(ThreadLocalInstanceImplTest, All) { tls_.shutdownThread(); } +// Test that the config passed into the update callback is the previous version stored in the slot. +TEST_F(ThreadLocalInstanceImplTest, UpdateCallback) { + InSequence s; + + SlotPtr slot = tls_.allocateSlot(); + + auto newer_version = std::make_shared(); + bool update_called = false; + + TestThreadLocalObject& object_ref = setObject(*slot); + auto update_cb = [&object_ref, &update_called, + newer_version](ThreadLocalObjectSharedPtr obj) -> ThreadLocalObjectSharedPtr { + // The unit test setup have two dispatchers registered, but only one thread, this lambda will be + // called twice in the same thread. + if (!update_called) { + EXPECT_EQ(obj.get(), &object_ref); + update_called = true; + } else { + EXPECT_EQ(obj.get(), newer_version.get()); + } + + return newer_version; + }; + EXPECT_CALL(thread_dispatcher_, post(_)); + EXPECT_CALL(object_ref, onDestroy()); + EXPECT_CALL(*newer_version, onDestroy()); + slot->runOnAllThreads(update_cb); + + EXPECT_EQ(newer_version.get(), &slot->getTyped()); + + tls_.shutdownGlobalThreading(); + tls_.shutdownThread(); +} + // TODO(ramaraochavali): Run this test with real threads. The current issue in the unit // testing environment is, the post to main_dispatcher is not working as expected. diff --git a/test/common/tracing/http_tracer_impl_test.cc b/test/common/tracing/http_tracer_impl_test.cc index 4aeee65fc6..496da50b58 100644 --- a/test/common/tracing/http_tracer_impl_test.cc +++ b/test/common/tracing/http_tracer_impl_test.cc @@ -25,11 +25,9 @@ using testing::_; using testing::Eq; -using testing::Invoke; using testing::NiceMock; using testing::Return; using testing::ReturnPointee; -using testing::ReturnRef; namespace Envoy { namespace Tracing { @@ -122,12 +120,14 @@ TEST(HttpConnManFinalizerImpl, OriginalAndLongPath) { {"x-envoy-original-path", path}, {":method", "GET"}, {"x-forwarded-proto", "http"}}; + Http::TestHeaderMapImpl response_headers; + Http::TestHeaderMapImpl response_trailers; NiceMock stream_info; absl::optional protocol = Http::Protocol::Http2; EXPECT_CALL(stream_info, bytesReceived()).WillOnce(Return(10)); EXPECT_CALL(stream_info, bytesSent()).WillOnce(Return(11)); - EXPECT_CALL(stream_info, protocol()).WillOnce(ReturnPointee(&protocol)); + EXPECT_CALL(stream_info, protocol()).WillRepeatedly(ReturnPointee(&protocol)); absl::optional response_code; EXPECT_CALL(stream_info, responseCode()).WillRepeatedly(ReturnPointee(&response_code)); @@ -137,7 +137,8 @@ TEST(HttpConnManFinalizerImpl, OriginalAndLongPath) { EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/2"))); NiceMock config; - HttpTracerUtility::finalizeSpan(span, &request_headers, stream_info, config); + HttpTracerUtility::finalizeSpan(span, &request_headers, &response_headers, &response_trailers, + stream_info, config); } TEST(HttpConnManFinalizerImpl, NoGeneratedId) { @@ -148,12 +149,14 @@ TEST(HttpConnManFinalizerImpl, NoGeneratedId) { Http::TestHeaderMapImpl request_headers{ {"x-envoy-original-path", path}, {":method", "GET"}, {"x-forwarded-proto", "http"}}; + Http::TestHeaderMapImpl response_headers; + Http::TestHeaderMapImpl response_trailers; NiceMock stream_info; absl::optional protocol = Http::Protocol::Http2; EXPECT_CALL(stream_info, bytesReceived()).WillOnce(Return(10)); EXPECT_CALL(stream_info, bytesSent()).WillOnce(Return(11)); - EXPECT_CALL(stream_info, protocol()).WillOnce(ReturnPointee(&protocol)); + EXPECT_CALL(stream_info, protocol()).WillRepeatedly(ReturnPointee(&protocol)); absl::optional response_code; EXPECT_CALL(stream_info, responseCode()).WillRepeatedly(ReturnPointee(&response_code)); @@ -163,7 +166,8 @@ TEST(HttpConnManFinalizerImpl, NoGeneratedId) { EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/2"))); NiceMock config; - HttpTracerUtility::finalizeSpan(span, &request_headers, stream_info, config); + HttpTracerUtility::finalizeSpan(span, &request_headers, &response_headers, &response_trailers, + stream_info, config); } TEST(HttpConnManFinalizerImpl, NullRequestHeaders) { @@ -184,7 +188,7 @@ TEST(HttpConnManFinalizerImpl, NullRequestHeaders) { EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().UpstreamCluster), _)).Times(0); NiceMock config; - HttpTracerUtility::finalizeSpan(span, nullptr, stream_info, config); + HttpTracerUtility::finalizeSpan(span, nullptr, nullptr, nullptr, stream_info, config); } TEST(HttpConnManFinalizerImpl, StreamInfoLogs) { @@ -222,7 +226,7 @@ TEST(HttpConnManFinalizerImpl, StreamInfoLogs) { NiceMock config; EXPECT_CALL(config, verbose).WillOnce(Return(true)); - HttpTracerUtility::finalizeSpan(span, nullptr, stream_info, config); + HttpTracerUtility::finalizeSpan(span, nullptr, nullptr, nullptr, stream_info, config); } TEST(HttpConnManFinalizerImpl, UpstreamClusterTagSet) { @@ -244,7 +248,7 @@ TEST(HttpConnManFinalizerImpl, UpstreamClusterTagSet) { EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().RequestSize), Eq("10"))); NiceMock config; - HttpTracerUtility::finalizeSpan(span, nullptr, stream_info, config); + HttpTracerUtility::finalizeSpan(span, nullptr, nullptr, nullptr, stream_info, config); } TEST(HttpConnManFinalizerImpl, SpanOptionalHeaders) { @@ -254,11 +258,13 @@ TEST(HttpConnManFinalizerImpl, SpanOptionalHeaders) { {":path", "/test"}, {":method", "GET"}, {"x-forwarded-proto", "https"}}; + Http::TestHeaderMapImpl response_headers; + Http::TestHeaderMapImpl response_trailers; NiceMock stream_info; absl::optional protocol = Http::Protocol::Http10; EXPECT_CALL(stream_info, bytesReceived()).WillOnce(Return(10)); - EXPECT_CALL(stream_info, protocol()).WillOnce(ReturnPointee(&protocol)); + EXPECT_CALL(stream_info, protocol()).WillRepeatedly(ReturnPointee(&protocol)); const std::string service_node = "i-453"; // Check that span is populated correctly. @@ -282,7 +288,8 @@ TEST(HttpConnManFinalizerImpl, SpanOptionalHeaders) { EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().UpstreamCluster), _)).Times(0); NiceMock config; - HttpTracerUtility::finalizeSpan(span, &request_headers, stream_info, config); + HttpTracerUtility::finalizeSpan(span, &request_headers, &response_headers, &response_trailers, + stream_info, config); } TEST(HttpConnManFinalizerImpl, SpanPopulatedFailureResponse) { @@ -291,6 +298,8 @@ TEST(HttpConnManFinalizerImpl, SpanPopulatedFailureResponse) { {":path", "/test"}, {":method", "GET"}, {"x-forwarded-proto", "http"}}; + Http::TestHeaderMapImpl response_headers; + Http::TestHeaderMapImpl response_trailers; NiceMock stream_info; request_headers.insertHost().value(std::string("api")); @@ -299,7 +308,7 @@ TEST(HttpConnManFinalizerImpl, SpanPopulatedFailureResponse) { request_headers.insertClientTraceId().value(std::string("client_trace_id")); absl::optional protocol = Http::Protocol::Http10; - EXPECT_CALL(stream_info, protocol()).WillOnce(ReturnPointee(&protocol)); + EXPECT_CALL(stream_info, protocol()).WillRepeatedly(ReturnPointee(&protocol)); EXPECT_CALL(stream_info, bytesReceived()).WillOnce(Return(10)); const std::string service_node = "i-453"; @@ -325,6 +334,7 @@ TEST(HttpConnManFinalizerImpl, SpanPopulatedFailureResponse) { EXPECT_CALL(span, setTag(Eq("cc"), Eq("c"))); EXPECT_CALL(config, requestHeadersForTags()); EXPECT_CALL(config, verbose).WillOnce(Return(false)); + EXPECT_CALL(config, maxPathTagLength).WillOnce(Return(256)); absl::optional response_code(503); EXPECT_CALL(stream_info, responseCode()).WillRepeatedly(ReturnPointee(&response_code)); @@ -339,7 +349,118 @@ TEST(HttpConnManFinalizerImpl, SpanPopulatedFailureResponse) { EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().ResponseFlags), Eq("UT"))); EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().UpstreamCluster), _)).Times(0); - HttpTracerUtility::finalizeSpan(span, &request_headers, stream_info, config); + HttpTracerUtility::finalizeSpan(span, &request_headers, &response_headers, &response_trailers, + stream_info, config); +} + +TEST(HttpConnManFinalizerImpl, GrpcOkStatus) { + const std::string path_prefix = "http://"; + NiceMock span; + + Http::TestHeaderMapImpl request_headers{{":method", "POST"}, + {":scheme", "http"}, + {":path", "/pb.Foo/Bar"}, + {":authority", "example.com:80"}, + {"content-type", "application/grpc"}, + {"te", "trailers"}}; + + Http::TestHeaderMapImpl response_headers{{":status", "200"}, + {"content-type", "application/grpc"}}; + Http::TestHeaderMapImpl response_trailers{{"grpc-status", "0"}, {"grpc-message", ""}}; + NiceMock stream_info; + + absl::optional protocol = Http::Protocol::Http2; + absl::optional response_code(200); + EXPECT_CALL(stream_info, responseCode()).WillRepeatedly(ReturnPointee(&response_code)); + EXPECT_CALL(stream_info, bytesReceived()).WillOnce(Return(10)); + EXPECT_CALL(stream_info, bytesSent()).WillOnce(Return(11)); + EXPECT_CALL(stream_info, protocol()).WillRepeatedly(ReturnPointee(&protocol)); + + EXPECT_CALL(span, setTag(_, _)).Times(testing::AnyNumber()); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpMethod), Eq("POST"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/2"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("200"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("0"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq(""))); + + NiceMock config; + HttpTracerUtility::finalizeSpan(span, &request_headers, &response_headers, &response_trailers, + stream_info, config); +} + +TEST(HttpConnManFinalizerImpl, GrpcErrorTag) { + const std::string path_prefix = "http://"; + NiceMock span; + + Http::TestHeaderMapImpl request_headers{{":method", "POST"}, + {":scheme", "http"}, + {":path", "/pb.Foo/Bar"}, + {":authority", "example.com:80"}, + {"content-type", "application/grpc"}, + {"te", "trailers"}}; + + Http::TestHeaderMapImpl response_headers{{":status", "200"}, + {"content-type", "application/grpc"}}; + Http::TestHeaderMapImpl response_trailers{{"grpc-status", "7"}, + {"grpc-message", "permission denied"}}; + NiceMock stream_info; + + absl::optional protocol = Http::Protocol::Http2; + absl::optional response_code(200); + EXPECT_CALL(stream_info, responseCode()).WillRepeatedly(ReturnPointee(&response_code)); + EXPECT_CALL(stream_info, bytesReceived()).WillOnce(Return(10)); + EXPECT_CALL(stream_info, bytesSent()).WillOnce(Return(11)); + EXPECT_CALL(stream_info, protocol()).WillRepeatedly(ReturnPointee(&protocol)); + + EXPECT_CALL(span, setTag(_, _)).Times(testing::AnyNumber()); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpMethod), Eq("POST"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/2"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("200"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("7"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("permission denied"))); + + NiceMock config; + HttpTracerUtility::finalizeSpan(span, &request_headers, &response_headers, &response_trailers, + stream_info, config); +} + +TEST(HttpConnManFinalizerImpl, GrpcTrailersOnly) { + const std::string path_prefix = "http://"; + NiceMock span; + + Http::TestHeaderMapImpl request_headers{{":method", "POST"}, + {":scheme", "http"}, + {":path", "/pb.Foo/Bar"}, + {":authority", "example.com:80"}, + {"content-type", "application/grpc"}, + {"te", "trailers"}}; + + Http::TestHeaderMapImpl response_headers{{":status", "200"}, + {"content-type", "application/grpc"}, + {"grpc-status", "7"}, + {"grpc-message", "permission denied"}}; + Http::TestHeaderMapImpl response_trailers; + NiceMock stream_info; + + absl::optional protocol = Http::Protocol::Http2; + absl::optional response_code(200); + EXPECT_CALL(stream_info, responseCode()).WillRepeatedly(ReturnPointee(&response_code)); + EXPECT_CALL(stream_info, bytesReceived()).WillOnce(Return(10)); + EXPECT_CALL(stream_info, bytesSent()).WillOnce(Return(11)); + EXPECT_CALL(stream_info, protocol()).WillRepeatedly(ReturnPointee(&protocol)); + + EXPECT_CALL(span, setTag(_, _)).Times(testing::AnyNumber()); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpMethod), Eq("POST"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/2"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("200"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("7"))); + EXPECT_CALL(span, setTag(Eq(Tracing::Tags::get().GrpcMessage), Eq("permission denied"))); + + NiceMock config; + HttpTracerUtility::finalizeSpan(span, &request_headers, &response_headers, &response_trailers, + stream_info, config); } TEST(HttpTracerUtilityTest, operationTypeToString) { @@ -352,6 +473,8 @@ TEST(HttpNullTracerTest, BasicFunctionality) { MockConfig config; StreamInfo::MockStreamInfo stream_info; Http::TestHeaderMapImpl request_headers; + Http::TestHeaderMapImpl response_headers; + Http::TestHeaderMapImpl response_trailers; SpanPtr span_ptr = null_tracer.startSpan(config, request_headers, stream_info, {Reason::Sampling, true}); @@ -374,6 +497,8 @@ class HttpTracerImplTest : public testing::Test { Http::TestHeaderMapImpl request_headers_{ {":path", "/"}, {":method", "GET"}, {"x-request-id", "foo"}, {":authority", "test"}}; + Http::TestHeaderMapImpl response_headers; + Http::TestHeaderMapImpl response_trailers; StreamInfo::MockStreamInfo stream_info_; NiceMock local_info_; MockConfig config_; diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 4e5d8b4679..9543cadbe7 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -29,6 +29,9 @@ envoy_cc_test( envoy_cc_test( name = "cluster_manager_impl_test", srcs = ["cluster_manager_impl_test.cc"], + external_deps = [ + "abseil_optional", + ], deps = [ ":utility_lib", "//include/envoy/stats:stats_interface", @@ -43,8 +46,10 @@ envoy_cc_test( "//source/common/stats:stats_lib", "//source/common/upstream:cluster_factory_lib", "//source/common/upstream:cluster_manager_lib", + "//source/common/upstream:subset_lb_lib", "//source/extensions/transport_sockets/raw_buffer:config", "//source/extensions/transport_sockets/tls:context_lib", + "//test/integration/clusters:custom_static_cluster", "//test/mocks/access_log:access_log_mocks", "//test/mocks/api:api_mocks", "//test/mocks/http:http_mocks", @@ -61,6 +66,7 @@ envoy_cc_test( "//test/test_common:simulated_time_system_lib", "//test/test_common:threadsafe_singleton_injector_lib", "//test/test_common:utility_lib", + "@envoy_api//envoy/api/v2:cds_cc", ], ) @@ -119,6 +125,7 @@ envoy_cc_test( "//source/common/upstream:upstream_lib", "//test/common/http:common_lib", "//test/mocks/access_log:access_log_mocks", + "//test/mocks/api:api_mocks", "//test/mocks/network:network_mocks", "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/runtime:runtime_mocks", diff --git a/test/common/upstream/cds_api_impl_test.cc b/test/common/upstream/cds_api_impl_test.cc index 72c0aeb6fe..48101c0957 100644 --- a/test/common/upstream/cds_api_impl_test.cc +++ b/test/common/upstream/cds_api_impl_test.cc @@ -19,11 +19,8 @@ #include "gtest/gtest.h" using testing::_; -using testing::AnyNumber; using testing::InSequence; -using testing::Invoke; using testing::Return; -using testing::ReturnRef; using testing::StrEq; using testing::Throw; diff --git a/test/common/upstream/cluster_factory_impl_test.cc b/test/common/upstream/cluster_factory_impl_test.cc index 85ed32e327..6b4091db67 100644 --- a/test/common/upstream/cluster_factory_impl_test.cc +++ b/test/common/upstream/cluster_factory_impl_test.cc @@ -24,11 +24,7 @@ #include "test/mocks/server/mocks.h" #include "test/mocks/ssl/mocks.h" -using testing::_; -using testing::ContainerEq; -using testing::Invoke; using testing::NiceMock; -using testing::ReturnRef; namespace Envoy { namespace Upstream { diff --git a/test/common/upstream/cluster_manager_impl_test.cc b/test/common/upstream/cluster_manager_impl_test.cc index a7ada1599f..1534ba3f3b 100644 --- a/test/common/upstream/cluster_manager_impl_test.cc +++ b/test/common/upstream/cluster_manager_impl_test.cc @@ -2,6 +2,7 @@ #include #include "envoy/admin/v2alpha/config_dump.pb.h" +#include "envoy/api/v2/cds.pb.h" #include "envoy/api/v2/core/base.pb.h" #include "envoy/network/listen_socket.h" #include "envoy/upstream/upstream.h" @@ -17,10 +18,12 @@ #include "common/singleton/manager_impl.h" #include "common/upstream/cluster_factory_impl.h" #include "common/upstream/cluster_manager_impl.h" +#include "common/upstream/subset_lb.h" #include "extensions/transport_sockets/tls/context_manager_impl.h" #include "test/common/upstream/utility.h" +#include "test/integration/clusters/custom_static_cluster.h" #include "test/mocks/access_log/mocks.h" #include "test/mocks/api/mocks.h" #include "test/mocks/http/mocks.h" @@ -38,18 +41,19 @@ #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" +#include "absl/strings/str_replace.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using testing::_; +using testing::ByRef; +using testing::Eq; using testing::InSequence; using testing::Invoke; using testing::Mock; using testing::NiceMock; -using testing::Pointee; using testing::Return; using testing::ReturnNew; -using testing::ReturnRef; using testing::SaveArg; namespace Envoy { @@ -200,7 +204,7 @@ class MockedUpdatedClusterManagerImpl : public TestClusterManagerImpl { local_cluster_update_.post(priority, hosts_added, hosts_removed); } - void postThreadLocalHostRemoval(const Cluster&, const HostVector& hosts_removed) override { + void postThreadLocalDrainConnections(const Cluster&, const HostVector& hosts_removed) override { local_hosts_removed_.post(hosts_removed); } @@ -575,14 +579,48 @@ TEST_F(ClusterManagerImplTest, OriginalDstLbRestriction2) { "'ORIGINAL_DST_LB' is allowed only with cluster type 'ORIGINAL_DST'"); } -TEST_F(ClusterManagerImplTest, SubsetLoadBalancerInitialization) { - const std::string yaml = R"EOF( +class ClusterManagerSubsetInitializationTest + : public ClusterManagerImplTest, + public testing::WithParamInterface { +public: + ClusterManagerSubsetInitializationTest() = default; + + static std::vector lbPolicies() { + int first = static_cast(envoy::api::v2::Cluster_LbPolicy_LbPolicy_MIN); + int last = static_cast(envoy::api::v2::Cluster_LbPolicy_LbPolicy_MAX); + ASSERT(first < last); + + std::vector policies; + for (int i = first; i <= last; i++) { + if (envoy::api::v2::Cluster_LbPolicy_IsValid(i)) { + auto policy = static_cast(i); + if (policy != envoy::api::v2::Cluster_LbPolicy_LOAD_BALANCING_POLICY_CONFIG) { + policies.push_back(policy); + } + } + } + return policies; + } + + static std::string paramName(const testing::TestParamInfo& info) { + const std::string& name = envoy::api::v2::Cluster_LbPolicy_Name(info.param); + return absl::StrReplaceAll(name, {{"_", ""}}); + } +}; + +// Test initialization of subset load balancer with every possible load balancer policy. +TEST_P(ClusterManagerSubsetInitializationTest, SubsetLoadBalancerInitialization) { + const std::string yamlPattern = R"EOF( static_resources: clusters: - name: cluster_1 connect_timeout: 0.250s - type: static - lb_policy: round_robin + {} + lb_policy: "{}" + lb_subset_config: + fallback_policy: ANY_ENDPOINT + subset_selectors: + - keys: [ "x" ] load_assignment: endpoints: - lb_endpoints: @@ -598,19 +636,47 @@ TEST_F(ClusterManagerImplTest, SubsetLoadBalancerInitialization) { port_value: 8001 )EOF"; - envoy::config::bootstrap::v2::Bootstrap bootstrap = parseBootstrapFromV2Yaml(yaml); - envoy::api::v2::Cluster::LbSubsetConfig* subset_config = - bootstrap.mutable_static_resources()->mutable_clusters(0)->mutable_lb_subset_config(); - subset_config->set_fallback_policy(envoy::api::v2::Cluster::LbSubsetConfig::ANY_ENDPOINT); - subset_config->add_subset_selectors()->add_keys("x"); + const std::string& policy_name = envoy::api::v2::Cluster_LbPolicy_Name(GetParam()); - create(bootstrap); - checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 1 /*active*/, 0 /*warming*/); + std::string cluster_type = "type: STATIC"; + if (GetParam() == envoy::api::v2::Cluster_LbPolicy_ORIGINAL_DST_LB) { + cluster_type = "type: ORIGINAL_DST"; + } else if (GetParam() == envoy::api::v2::Cluster_LbPolicy_CLUSTER_PROVIDED) { + // This custom cluster type is registered by linking test/integration/custom/static_cluster.cc. + cluster_type = "cluster_type: { name: envoy.clusters.custom_static_with_lb }"; + } - factory_.tls_.shutdownThread(); + const std::string yaml = fmt::format(yamlPattern, cluster_type, policy_name); + + if (GetParam() == envoy::api::v2::Cluster_LbPolicy_ORIGINAL_DST_LB || + GetParam() == envoy::api::v2::Cluster_LbPolicy_CLUSTER_PROVIDED) { + EXPECT_THROW_WITH_MESSAGE( + create(parseBootstrapFromV2Yaml(yaml)), EnvoyException, + fmt::format("cluster: LB policy {} cannot be combined with lb_subset_config", + envoy::api::v2::Cluster_LbPolicy_Name(GetParam()))); + + } else { + create(parseBootstrapFromV2Yaml(yaml)); + checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 1 /*active*/, 0 /*warming*/); + + Upstream::ThreadLocalCluster* tlc = cluster_manager_->get("cluster_1"); + EXPECT_NE(nullptr, tlc); + + if (tlc) { + Upstream::LoadBalancer& lb = tlc->loadBalancer(); + EXPECT_NE(nullptr, dynamic_cast(&lb)); + } + + factory_.tls_.shutdownThread(); + } } -TEST_F(ClusterManagerImplTest, SubsetLoadBalancerRestriction) { +INSTANTIATE_TEST_SUITE_P(ClusterManagerSubsetInitializationTest, + ClusterManagerSubsetInitializationTest, + testing::ValuesIn(ClusterManagerSubsetInitializationTest::lbPolicies()), + ClusterManagerSubsetInitializationTest::paramName); + +TEST_F(ClusterManagerImplTest, SubsetLoadBalancerOriginalDstRestriction) { const std::string yaml = R"EOF( static_resources: clusters: @@ -618,17 +684,34 @@ TEST_F(ClusterManagerImplTest, SubsetLoadBalancerRestriction) { connect_timeout: 0.250s type: original_dst lb_policy: original_dst_lb + lb_subset_config: + fallback_policy: ANY_ENDPOINT + subset_selectors: + - keys: [ "x" ] )EOF"; - envoy::config::bootstrap::v2::Bootstrap bootstrap = parseBootstrapFromV2Yaml(yaml); - envoy::api::v2::Cluster::LbSubsetConfig* subset_config = - bootstrap.mutable_static_resources()->mutable_clusters(0)->mutable_lb_subset_config(); - subset_config->set_fallback_policy(envoy::api::v2::Cluster::LbSubsetConfig::ANY_ENDPOINT); - subset_config->add_subset_selectors()->add_keys("x"); + EXPECT_THROW_WITH_MESSAGE( + create(parseBootstrapFromV2Yaml(yaml)), EnvoyException, + "cluster: LB policy ORIGINAL_DST_LB cannot be combined with lb_subset_config"); +} + +TEST_F(ClusterManagerImplTest, SubsetLoadBalancerClusterProvidedLbRestriction) { + const std::string yaml = R"EOF( +static_resources: + clusters: + - name: cluster_1 + connect_timeout: 0.250s + type: static + lb_policy: cluster_provided + lb_subset_config: + fallback_policy: ANY_ENDPOINT + subset_selectors: + - keys: [ "x" ] + )EOF"; EXPECT_THROW_WITH_MESSAGE( - create(bootstrap), EnvoyException, - "cluster: cluster type 'original_dst' may not be used with lb_subset_config"); + create(parseBootstrapFromV2Yaml(yaml)), EnvoyException, + "cluster: LB policy CLUSTER_PROVIDED cannot be combined with lb_subset_config"); } TEST_F(ClusterManagerImplTest, SubsetLoadBalancerLocalityAware) { @@ -639,6 +722,11 @@ TEST_F(ClusterManagerImplTest, SubsetLoadBalancerLocalityAware) { connect_timeout: 0.250s type: STATIC lb_policy: ROUND_ROBIN + lb_subset_config: + fallback_policy: ANY_ENDPOINT + subset_selectors: + - keys: [ "x" ] + locality_weight_aware: true load_assignment: endpoints: - lb_endpoints: @@ -654,12 +742,7 @@ TEST_F(ClusterManagerImplTest, SubsetLoadBalancerLocalityAware) { port_value: 8001 )EOF"; - envoy::config::bootstrap::v2::Bootstrap bootstrap = parseBootstrapFromV2Yaml(yaml); - envoy::api::v2::Cluster::LbSubsetConfig* subset_config = - bootstrap.mutable_static_resources()->mutable_clusters(0)->mutable_lb_subset_config(); - subset_config->set_locality_weight_aware(true); - - EXPECT_THROW_WITH_MESSAGE(create(bootstrap), EnvoyException, + EXPECT_THROW_WITH_MESSAGE(create(parseBootstrapFromV2Yaml(yaml)), EnvoyException, "Locality weight aware subset LB requires that a " "locality_weighted_lb_config be set in cluster_1"); } @@ -1232,6 +1315,78 @@ TEST_F(ClusterManagerImplTest, RemoveWarmingCluster) { EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); } +TEST_F(ClusterManagerImplTest, ModifyWarmingCluster) { + time_system_.setSystemTime(std::chrono::milliseconds(1234567891234)); + create(defaultConfig()); + + InSequence s; + ReadyWatcher initialized; + EXPECT_CALL(initialized, ready()); + cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); + + // Add a "fake_cluster" in warming state. + std::shared_ptr cluster1 = + std::make_shared>(); + EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) + .WillOnce(Return(std::make_pair(cluster1, nullptr))); + EXPECT_CALL(*cluster1, initializePhase()).Times(0); + EXPECT_CALL(*cluster1, initialize(_)); + EXPECT_TRUE( + cluster_manager_->addOrUpdateCluster(defaultStaticCluster("fake_cluster"), "version1")); + checkStats(1 /*added*/, 0 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); + EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); + checkConfigDump(R"EOF( + dynamic_warming_clusters: + - version_info: "version1" + cluster: + name: "fake_cluster" + type: STATIC + connect_timeout: 0.25s + hosts: + - socket_address: + address: "127.0.0.1" + port_value: 11001 + last_updated: + seconds: 1234567891 + nanos: 234000000 + )EOF"); + + // Update the warming cluster that was just added. + std::shared_ptr cluster2 = + std::make_shared>(); + EXPECT_CALL(factory_, clusterFromProto_(_, _, _, _)) + .WillOnce(Return(std::make_pair(cluster2, nullptr))); + EXPECT_CALL(*cluster2, initializePhase()).Times(0); + EXPECT_CALL(*cluster2, initialize(_)); + EXPECT_TRUE(cluster_manager_->addOrUpdateCluster( + parseClusterFromV2Json(fmt::sprintf(kDefaultStaticClusterTmpl, "fake_cluster", + R"EOF( +"socket_address": { + "address": "127.0.0.1", + "port_value": 11002 +})EOF")), + "version2")); + checkStats(1 /*added*/, 1 /*modified*/, 0 /*removed*/, 0 /*active*/, 1 /*warming*/); + checkConfigDump(R"EOF( + dynamic_warming_clusters: + - version_info: "version2" + cluster: + name: "fake_cluster" + type: STATIC + connect_timeout: 0.25s + hosts: + - socket_address: + address: "127.0.0.1" + port_value: 11002 + last_updated: + seconds: 1234567891 + nanos: 234000000 + )EOF"); + + EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster1.get())); + EXPECT_TRUE(Mock::VerifyAndClearExpectations(cluster2.get())); +} + // Verify that shutting down the cluster manager destroys warming clusters. TEST_F(ClusterManagerImplTest, ShutdownWithWarming) { create(defaultConfig()); @@ -1330,9 +1485,9 @@ TEST_F(ClusterManagerImplTest, DynamicAddRemove) { // tcp connections. Http::ConnectionPool::Instance::DrainedCb drained_cb; Tcp::ConnectionPool::Instance::DrainedCb drained_cb2; + EXPECT_CALL(*callbacks, onClusterRemoval(_)).Times(1); EXPECT_CALL(*cp, addDrainedCallback(_)).WillOnce(SaveArg<0>(&drained_cb)); EXPECT_CALL(*cp2, addDrainedCallback(_)).WillOnce(SaveArg<0>(&drained_cb2)); - EXPECT_CALL(*callbacks, onClusterRemoval(_)).Times(1); EXPECT_TRUE(cluster_manager_->removeCluster("fake_cluster")); EXPECT_EQ(nullptr, cluster_manager_->get("fake_cluster")); EXPECT_EQ(0UL, cluster_manager_->clusters().size()); @@ -2752,7 +2907,8 @@ class ClusterManagerInitHelperTest : public testing::Test { public: MOCK_METHOD1(onClusterInit, void(Cluster& cluster)); - ClusterManagerInitHelper init_helper_{[this](Cluster& cluster) { onClusterInit(cluster); }}; + NiceMock cm_; + ClusterManagerInitHelper init_helper_{cm_, [this](Cluster& cluster) { onClusterInit(cluster); }}; }; TEST_F(ClusterManagerInitHelperTest, ImmediateInitialize) { @@ -2824,6 +2980,60 @@ TEST_F(ClusterManagerInitHelperTest, UpdateAlreadyInitialized) { cluster2.initialize_callback_(); } +// If secondary clusters initialization triggered outside of CdsApiImpl::onConfigUpdate()'s +// callback flows, sending ClusterLoadAssignment should not be paused before calling +// ClusterManagerInitHelper::maybeFinishInitialize(). This case tests that +// ClusterLoadAssignment request is paused and resumed properly. +TEST_F(ClusterManagerInitHelperTest, InitSecondaryWithoutEdsPaused) { + InSequence s; + + ReadyWatcher cm_initialized; + init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); + + NiceMock cluster1; + ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + init_helper_.addCluster(cluster1); + + const auto& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + EXPECT_CALL(cm_.ads_mux_, paused(Eq(ByRef(type_url)))).WillRepeatedly(Return(false)); + EXPECT_CALL(cm_.ads_mux_, pause(Eq(ByRef(type_url)))); + EXPECT_CALL(cluster1, initialize(_)); + EXPECT_CALL(cm_.ads_mux_, resume(Eq(ByRef(type_url)))); + + init_helper_.onStaticLoadComplete(); + + EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); + EXPECT_CALL(cm_initialized, ready()); + cluster1.initialize_callback_(); +} + +// If secondary clusters initialization triggered inside of CdsApiImpl::onConfigUpdate()'s +// callback flows, that's, the CDS response didn't have any primary cluster, sending +// ClusterLoadAssignment should be already paused by CdsApiImpl::onConfigUpdate(). +// This case tests that ClusterLoadAssignment request isn't paused again. +TEST_F(ClusterManagerInitHelperTest, InitSecondaryWithEdsPaused) { + InSequence s; + + ReadyWatcher cm_initialized; + init_helper_.setInitializedCb([&]() -> void { cm_initialized.ready(); }); + + NiceMock cluster1; + ON_CALL(cluster1, initializePhase()).WillByDefault(Return(Cluster::InitializePhase::Secondary)); + init_helper_.addCluster(cluster1); + + const auto& type_url = Config::TypeUrl::get().ClusterLoadAssignment; + EXPECT_CALL(cm_.ads_mux_, paused(Eq(ByRef(type_url)))).WillRepeatedly(Return(true)); + EXPECT_CALL(cm_.ads_mux_, pause(Eq(ByRef(type_url)))).Times(0); + EXPECT_CALL(cluster1, initialize(_)); + EXPECT_CALL(cm_.ads_mux_, resume(Eq(ByRef(type_url)))).Times(0); + + init_helper_.onStaticLoadComplete(); + + EXPECT_CALL(*this, onClusterInit(Ref(cluster1))); + EXPECT_CALL(cm_initialized, ready()); + cluster1.initialize_callback_(); +} + TEST_F(ClusterManagerInitHelperTest, AddSecondaryAfterSecondaryInit) { InSequence s; @@ -3324,6 +3534,195 @@ TEST_F(TcpKeepaliveTest, TcpKeepaliveWithAllOptions) { expectSetsockoptSoKeepalive(7, 4, 1); } +TEST_F(ClusterManagerImplTest, ConnPoolsDrainedOnHostSetChange) { + const std::string yaml = R"EOF( + static_resources: + clusters: + - name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + type: STATIC + common_lb_config: + close_connections_on_host_set_change: true + )EOF"; + + ReadyWatcher initialized; + EXPECT_CALL(initialized, ready()); + + create(parseBootstrapFromV2Yaml(yaml)); + + // Set up for an initialize callback. + cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); + + std::unique_ptr callbacks(new NiceMock()); + ClusterUpdateCallbacksHandlePtr cb = + cluster_manager_->addThreadLocalClusterUpdateCallbacks(*callbacks); + + EXPECT_FALSE(cluster_manager_->get("cluster_1")->info()->addedViaApi()); + + // Verify that we get no hosts when the HostSet is empty. + EXPECT_EQ(nullptr, cluster_manager_->httpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, Http::Protocol::Http11, nullptr)); + EXPECT_EQ(nullptr, cluster_manager_->tcpConnPoolForCluster("cluster_1", ResourcePriority::Default, + nullptr, nullptr)); + EXPECT_EQ(nullptr, + cluster_manager_->tcpConnForCluster("cluster_1", nullptr, nullptr).connection_); + + Cluster& cluster = cluster_manager_->activeClusters().begin()->second; + + // Set up the HostSet. + HostSharedPtr host1 = makeTestHost(cluster.info(), "tcp://127.0.0.1:80"); + HostSharedPtr host2 = makeTestHost(cluster.info(), "tcp://127.0.0.1:81"); + + HostVector hosts{host1, host2}; + auto hosts_ptr = std::make_shared(hosts); + + // Sending non-mergeable updates. + cluster.prioritySet().updateHosts( + 0, HostSetImpl::partitionHosts(hosts_ptr, HostsPerLocalityImpl::empty()), nullptr, hosts, {}, + 100); + + EXPECT_EQ(1, factory_.stats_.counter("cluster_manager.cluster_updated").value()); + EXPECT_EQ(0, factory_.stats_.counter("cluster_manager.cluster_updated_via_merge").value()); + EXPECT_EQ(0, factory_.stats_.counter("cluster_manager.update_merge_cancelled").value()); + + EXPECT_CALL(factory_, allocateConnPool_(_, _)) + .Times(3) + .WillRepeatedly(ReturnNew()); + + EXPECT_CALL(factory_, allocateTcpConnPool_(_)) + .Times(3) + .WillRepeatedly(ReturnNew()); + + // This should provide us a CP for each of the above hosts. + Http::ConnectionPool::MockInstance* cp1 = + dynamic_cast(cluster_manager_->httpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, Http::Protocol::Http11, nullptr)); + // Create persistent connection for host2. + Http::ConnectionPool::MockInstance* cp2 = + dynamic_cast(cluster_manager_->httpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, Http::Protocol::Http2, nullptr)); + + Tcp::ConnectionPool::MockInstance* tcp1 = + dynamic_cast(cluster_manager_->tcpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, nullptr, nullptr)); + + Tcp::ConnectionPool::MockInstance* tcp2 = + dynamic_cast(cluster_manager_->tcpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, nullptr, nullptr)); + + EXPECT_NE(cp1, cp2); + EXPECT_NE(tcp1, tcp2); + + EXPECT_CALL(*cp2, addDrainedCallback(_)) + .WillOnce(Invoke([](Http::ConnectionPool::Instance::DrainedCb cb) { cb(); })); + + EXPECT_CALL(*cp1, addDrainedCallback(_)) + .WillOnce(Invoke([](Http::ConnectionPool::Instance::DrainedCb cb) { cb(); })); + + EXPECT_CALL(*tcp1, addDrainedCallback(_)) + .WillOnce(Invoke([](Tcp::ConnectionPool::Instance::DrainedCb cb) { cb(); })); + + EXPECT_CALL(*tcp2, addDrainedCallback(_)) + .WillOnce(Invoke([](Tcp::ConnectionPool::Instance::DrainedCb cb) { cb(); })); + + HostVector hosts_removed; + hosts_removed.push_back(host2); + + // This update should drain all connection pools (host1, host2). + cluster.prioritySet().updateHosts( + 0, HostSetImpl::partitionHosts(hosts_ptr, HostsPerLocalityImpl::empty()), nullptr, {}, + hosts_removed, 100); + + // Recreate connection pool for host1. + cp1 = dynamic_cast(cluster_manager_->httpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, Http::Protocol::Http11, nullptr)); + + tcp1 = dynamic_cast(cluster_manager_->tcpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, nullptr, nullptr)); + + HostSharedPtr host3 = makeTestHost(cluster.info(), "tcp://127.0.0.1:82"); + + HostVector hosts_added; + hosts_added.push_back(host3); + + EXPECT_CALL(*cp1, addDrainedCallback(_)) + .WillOnce(Invoke([](Http::ConnectionPool::Instance::DrainedCb cb) { cb(); })); + + EXPECT_CALL(*tcp1, addDrainedCallback(_)) + .WillOnce(Invoke([](Tcp::ConnectionPool::Instance::DrainedCb cb) { cb(); })); + + // Adding host3 should drain connection pool for host1. + cluster.prioritySet().updateHosts( + 0, HostSetImpl::partitionHosts(hosts_ptr, HostsPerLocalityImpl::empty()), nullptr, + hosts_added, {}, 100); +} + +TEST_F(ClusterManagerImplTest, ConnPoolsNotDrainedOnHostSetChange) { + const std::string yaml = R"EOF( + static_resources: + clusters: + - name: cluster_1 + connect_timeout: 0.250s + lb_policy: ROUND_ROBIN + type: STATIC + )EOF"; + + ReadyWatcher initialized; + EXPECT_CALL(initialized, ready()); + create(parseBootstrapFromV2Yaml(yaml)); + + // Set up for an initialize callback. + cluster_manager_->setInitializedCb([&]() -> void { initialized.ready(); }); + + std::unique_ptr callbacks(new NiceMock()); + ClusterUpdateCallbacksHandlePtr cb = + cluster_manager_->addThreadLocalClusterUpdateCallbacks(*callbacks); + + Cluster& cluster = cluster_manager_->activeClusters().begin()->second; + + // Set up the HostSet. + HostSharedPtr host1 = makeTestHost(cluster.info(), "tcp://127.0.0.1:80"); + + HostVector hosts{host1}; + auto hosts_ptr = std::make_shared(hosts); + + // Sending non-mergeable updates. + cluster.prioritySet().updateHosts( + 0, HostSetImpl::partitionHosts(hosts_ptr, HostsPerLocalityImpl::empty()), nullptr, hosts, {}, + 100); + + EXPECT_CALL(factory_, allocateConnPool_(_, _)) + .Times(1) + .WillRepeatedly(ReturnNew()); + + EXPECT_CALL(factory_, allocateTcpConnPool_(_)) + .Times(1) + .WillRepeatedly(ReturnNew()); + + // This should provide us a CP for each of the above hosts. + Http::ConnectionPool::MockInstance* cp1 = + dynamic_cast(cluster_manager_->httpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, Http::Protocol::Http11, nullptr)); + + Tcp::ConnectionPool::MockInstance* tcp1 = + dynamic_cast(cluster_manager_->tcpConnPoolForCluster( + "cluster_1", ResourcePriority::Default, nullptr, nullptr)); + + HostSharedPtr host2 = makeTestHost(cluster.info(), "tcp://127.0.0.1:82"); + HostVector hosts_added; + hosts_added.push_back(host2); + + // No connection pools should be drained. + EXPECT_CALL(*cp1, drainConnections()).Times(0); + EXPECT_CALL(*tcp1, drainConnections()).Times(0); + + // No connection pools should be drained. + cluster.prioritySet().updateHosts( + 0, HostSetImpl::partitionHosts(hosts_ptr, HostsPerLocalityImpl::empty()), nullptr, + hosts_added, {}, 100); +} + } // namespace } // namespace Upstream } // namespace Envoy diff --git a/test/common/upstream/conn_pool_map_impl_test.cc b/test/common/upstream/conn_pool_map_impl_test.cc index 83839f4e76..059fec2ef0 100644 --- a/test/common/upstream/conn_pool_map_impl_test.cc +++ b/test/common/upstream/conn_pool_map_impl_test.cc @@ -16,7 +16,6 @@ using testing::AtLeast; using testing::Invoke; -using testing::InvokeArgument; using testing::NiceMock; using testing::Return; using testing::SaveArg; diff --git a/test/common/upstream/eds_test.cc b/test/common/upstream/eds_test.cc index 988df7fc7e..688c162807 100644 --- a/test/common/upstream/eds_test.cc +++ b/test/common/upstream/eds_test.cc @@ -22,9 +22,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::AtLeast; -using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Upstream { @@ -1713,9 +1710,9 @@ TEST_F(EdsAssignmentTimeoutTest, AssignmentTimeoutEnableDisable) { cluster_load_assignment_lease.mutable_policy()->mutable_endpoint_stale_after()->MergeFrom( Protobuf::util::TimeUtil::SecondsToDuration(1)); - EXPECT_CALL(*interval_timer_, enableTimer(_)).Times(2); // Timer enabled twice. - EXPECT_CALL(*interval_timer_, disableTimer()).Times(1); // Timer disabled once. - EXPECT_CALL(*interval_timer_, enabled()).Times(6); // Includes calls by test. + EXPECT_CALL(*interval_timer_, enableTimer(_, _)).Times(2); // Timer enabled twice. + EXPECT_CALL(*interval_timer_, disableTimer()).Times(1); // Timer disabled once. + EXPECT_CALL(*interval_timer_, enabled()).Times(6); // Includes calls by test. doOnConfigUpdateVerifyNoThrow(cluster_load_assignment_lease); // Check that the timer is enabled. EXPECT_EQ(interval_timer_->enabled(), true); @@ -1755,7 +1752,7 @@ TEST_F(EdsAssignmentTimeoutTest, AssignmentLeaseExpired) { add_endpoint(81); // Expect the timer to be enabled once. - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); // Expect the timer to be disabled when stale assignments are removed. EXPECT_CALL(*interval_timer_, disableTimer()); EXPECT_CALL(*interval_timer_, enabled()).Times(2); diff --git a/test/common/upstream/hds_test.cc b/test/common/upstream/hds_test.cc index c7fe3af8d7..3a442a1924 100644 --- a/test/common/upstream/hds_test.cc +++ b/test/common/upstream/hds_test.cc @@ -232,7 +232,7 @@ TEST_F(HdsTest, TestMinimalOnReceiveMessage) { message->mutable_interval()->set_seconds(1); // Process message - EXPECT_CALL(*server_response_timer_, enableTimer(_)).Times(AtLeast(1)); + EXPECT_CALL(*server_response_timer_, enableTimer(_, _)).Times(AtLeast(1)); hds_delegate_->onReceiveMessage(std::move(message)); } @@ -248,7 +248,7 @@ TEST_F(HdsTest, TestMinimalSendResponse) { message->mutable_interval()->set_seconds(1); // Process message and send 2 responses - EXPECT_CALL(*server_response_timer_, enableTimer(_)).Times(AtLeast(1)); + EXPECT_CALL(*server_response_timer_, enableTimer(_, _)).Times(AtLeast(1)); EXPECT_CALL(async_stream_, sendMessageRaw_(_, _)).Times(2); hds_delegate_->onReceiveMessage(std::move(message)); hds_delegate_->sendResponse(); @@ -265,11 +265,11 @@ TEST_F(HdsTest, TestStreamConnectionFailure) { .WillOnce(Return(&async_stream_)); EXPECT_CALL(random_, random()).WillOnce(Return(1000005)).WillRepeatedly(Return(1234567)); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(5))); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1567))); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(2567))); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(4567))); - EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(25567))); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(5), _)); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(1567), _)); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(2567), _)); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(4567), _)); + EXPECT_CALL(*retry_timer_, enableTimer(std::chrono::milliseconds(25567), _)); EXPECT_CALL(async_stream_, sendMessageRaw_(_, _)); // Test connection failure and retry @@ -297,7 +297,7 @@ TEST_F(HdsTest, TestSendResponseOneEndpointTimeout) { Network::MockClientConnection* connection_ = new NiceMock(); EXPECT_CALL(dispatcher_, createClientConnection_(_, _, _, _)).WillRepeatedly(Return(connection_)); - EXPECT_CALL(*server_response_timer_, enableTimer(_)).Times(2); + EXPECT_CALL(*server_response_timer_, enableTimer(_, _)).Times(2); EXPECT_CALL(async_stream_, sendMessageRaw_(_, false)); EXPECT_CALL(test_factory_, createClusterInfo(_)).WillOnce(Return(cluster_info_)); EXPECT_CALL(*connection_, setBufferLimits(_)); diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 7c8d7cfb57..b14b942a0d 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -18,6 +18,7 @@ #include "test/common/http/common.h" #include "test/common/upstream/utility.h" #include "test/mocks/access_log/mocks.h" +#include "test/mocks/api/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/protobuf/mocks.h" #include "test/mocks/runtime/mocks.h" @@ -35,11 +36,9 @@ using testing::InSequence; using testing::Invoke; using testing::InvokeWithoutArgs; using testing::NiceMock; -using testing::Ref; using testing::Return; using testing::ReturnRef; using testing::SaveArg; -using testing::WithArg; namespace Envoy { namespace Upstream { @@ -64,10 +63,11 @@ TEST(HealthCheckerFactoryTest, GrpcHealthCheckHTTP2NotConfiguredException) { Event::MockDispatcher dispatcher; AccessLog::MockAccessLogManager log_manager; NiceMock validation_visitor; + Api::MockApi api; EXPECT_THROW_WITH_MESSAGE( HealthCheckerFactory::create(createGrpcHealthCheckConfig(), cluster, runtime, random, - dispatcher, log_manager, validation_visitor), + dispatcher, log_manager, validation_visitor, api), EnvoyException, "fake_cluster cluster must support HTTP/2 for gRPC healthchecking"); } @@ -82,12 +82,13 @@ TEST(HealthCheckerFactoryTest, CreateGrpc) { Event::MockDispatcher dispatcher; AccessLog::MockAccessLogManager log_manager; NiceMock validation_visitor; + Api::MockApi api; - EXPECT_NE(nullptr, - dynamic_cast( - HealthCheckerFactory::create(createGrpcHealthCheckConfig(), cluster, runtime, - random, dispatcher, log_manager, validation_visitor) - .get())); + EXPECT_NE(nullptr, dynamic_cast( + HealthCheckerFactory::create(createGrpcHealthCheckConfig(), cluster, + runtime, random, dispatcher, log_manager, + validation_visitor, api) + .get())); } class TestHttpHealthCheckerImpl : public HttpHealthCheckerImpl { @@ -543,12 +544,12 @@ class HttpHealthCheckerImplTest : public testing::Test { Host::HealthFlag::FAILED_ACTIVE_HC); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); // Test that failing first disables fast success. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); respond(0, "503", false, false, false, false, health_checked_cluster); @@ -557,12 +558,12 @@ class HttpHealthCheckerImplTest : public testing::Test { EXPECT_EQ(Host::Health::Unhealthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, false, false, health_checked_cluster); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( @@ -570,13 +571,13 @@ class HttpHealthCheckerImplTest : public testing::Test { EXPECT_EQ(Host::Health::Unhealthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logAddHealthy(_, _, false)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, false, false, health_checked_cluster); EXPECT_EQ(Host::Health::Healthy, @@ -606,13 +607,14 @@ TEST_F(HttpHealthCheckerImplTest, Success) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -627,7 +629,7 @@ TEST_F(HttpHealthCheckerImplTest, Degraded) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); @@ -635,19 +637,19 @@ TEST_F(HttpHealthCheckerImplTest, Degraded) { .WillRepeatedly(Return(45000)); // We start off as healthy, and should go degraded after receiving the degraded health response. - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); EXPECT_CALL(*event_logger_, logDegraded(_, _)); respond(0, "200", false, false, true, false, {}, true); EXPECT_EQ(Host::Health::Degraded, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); // Then, after receiving a regular health check response we should go back to healthy. - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->interval_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*event_logger_, logNoLongerDegraded(_, _)); respond(0, "200", false, false, true, false, {}, false); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -661,22 +663,22 @@ TEST_F(HttpHealthCheckerImplTest, SuccessIntervalJitter) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); for (int i = 0; i < 50000; i += 239) { EXPECT_CALL(random_, random()).WillOnce(Return(i)); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // the jitter is 1000ms here EXPECT_CALL(*test_sessions_[0]->interval_timer_, - enableTimer(std::chrono::milliseconds(5000 + i % 1000))); + enableTimer(std::chrono::milliseconds(5000 + i % 1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); } @@ -690,24 +692,24 @@ TEST_F(HttpHealthCheckerImplTest, InitialJitterNoTraffic) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); test_sessions_[0]->interval_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); for (int i = 0; i < 2; i += 1) { EXPECT_CALL(random_, random()).WillOnce(Return(i)); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // the jitter is 40% of 5000, so should be 2000 EXPECT_CALL(*test_sessions_[0]->interval_timer_, - enableTimer(std::chrono::milliseconds(5000 + i % 2000))); + enableTimer(std::chrono::milliseconds(5000 + i % 2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); } @@ -721,22 +723,22 @@ TEST_F(HttpHealthCheckerImplTest, SuccessIntervalJitterPercentNoTraffic) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); for (int i = 0; i < 50000; i += 239) { EXPECT_CALL(random_, random()).WillOnce(Return(i)); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // the jitter is 40% of 5000, so should be 2000 EXPECT_CALL(*test_sessions_[0]->interval_timer_, - enableTimer(std::chrono::milliseconds(5000 + i % 2000))); + enableTimer(std::chrono::milliseconds(5000 + i % 2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); } @@ -751,22 +753,22 @@ TEST_F(HttpHealthCheckerImplTest, SuccessIntervalJitterPercent) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); for (int i = 0; i < 50000; i += 239) { EXPECT_CALL(random_, random()).WillOnce(Return(i)); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // the jitter is 40% of 1000, so should be 400 EXPECT_CALL(*test_sessions_[0]->interval_timer_, - enableTimer(std::chrono::milliseconds(1000 + i % 400))); + enableTimer(std::chrono::milliseconds(1000 + i % 400), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); } @@ -781,13 +783,14 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithSpurious100Continue) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); std::unique_ptr continue_headers( @@ -808,13 +811,14 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithSpuriousMetadata) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); std::unique_ptr metadata_map(new Http::MetadataMap()); @@ -837,19 +841,21 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithMultipleHosts) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectSessionCreate(); expectStreamCreate(1); - EXPECT_CALL(*test_sessions_[1]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[1]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)).Times(2); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .Times(2) .WillRepeatedly(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); - EXPECT_CALL(*test_sessions_[1]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[1]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[1]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true); respond(1, "200", false, false, true); @@ -870,19 +876,21 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithMultipleHostSets) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectSessionCreate(); expectStreamCreate(1); - EXPECT_CALL(*test_sessions_[1]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[1]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)).Times(2); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .Times(2) .WillRepeatedly(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); - EXPECT_CALL(*test_sessions_[1]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[1]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[1]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true); respond(1, "200", false, false, true); @@ -924,7 +932,7 @@ TEST_F(HttpHealthCheckerImplTest, ZeroRetryInterval) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) .WillOnce(Invoke([&](const Http::HeaderMap& headers, bool) { EXPECT_EQ(headers.Host()->value().getStringView(), host); @@ -936,7 +944,7 @@ TEST_F(HttpHealthCheckerImplTest, ZeroRetryInterval) { EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)).WillOnce(Return(0)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)).WillOnce(Return(0)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); absl::optional health_checked_cluster("locations-production-iad"); respond(0, "200", false, false, true, false, health_checked_cluster); @@ -957,7 +965,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheck) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) .WillOnce(Invoke([&](const Http::HeaderMap& headers, bool) { EXPECT_EQ(headers.Host()->value().getStringView(), host); @@ -970,7 +978,8 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheck) { EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); absl::optional health_checked_cluster("locations-production-iad"); respond(0, "200", false, false, true, false, health_checked_cluster); @@ -992,7 +1001,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValue) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) .WillOnce(Invoke([&](const Http::HeaderMap& headers, bool) { EXPECT_EQ(headers.Host()->value().getStringView(), host); @@ -1003,7 +1012,8 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithCustomHostValue) { EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); absl::optional health_checked_cluster("locations-production-iad"); respond(0, "200", false, false, true, false, health_checked_cluster); @@ -1053,7 +1063,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) .WillRepeatedly(Invoke([&](const Http::HeaderMap& headers, bool) { EXPECT_EQ(headers.get(header_ok)->value().getStringView(), value_ok); @@ -1079,13 +1089,14 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAdditionalHeaders) { EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); absl::optional health_checked_cluster("locations-production-iad"); respond(0, "200", false, false, true, false, health_checked_cluster); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); } @@ -1110,7 +1121,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithoutUserAgent) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) .WillRepeatedly(Invoke( [&](const Http::HeaderMap& headers, bool) { EXPECT_EQ(headers.UserAgent(), nullptr); })); @@ -1119,13 +1130,14 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithoutUserAgent) { EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); absl::optional health_checked_cluster("locations-production-iad"); respond(0, "200", false, false, true, false, health_checked_cluster); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); } @@ -1144,13 +1156,14 @@ TEST_F(HttpHealthCheckerImplTest, ServiceDoesNotMatchFail) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); absl::optional health_checked_cluster("api-production-iad"); respond(0, "200", false, false, true, false, health_checked_cluster); @@ -1174,13 +1187,14 @@ TEST_F(HttpHealthCheckerImplTest, ServiceNotPresentInResponseFail) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, false); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( @@ -1201,13 +1215,14 @@ TEST_F(HttpHealthCheckerImplTest, ServiceCheckRuntimeOff) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); absl::optional health_checked_cluster("api-production-iad"); respond(0, "200", false, false, true, false, health_checked_cluster); @@ -1230,10 +1245,10 @@ TEST_F(HttpHealthCheckerImplTest, SuccessNoTraffic) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(5000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(5000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true, true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1247,7 +1262,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessStartFailedSuccessFirst) { Host::HealthFlag::FAILED_ACTIVE_HC); expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); // Test fast success immediately moves us to healthy. @@ -1255,7 +1270,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessStartFailedSuccessFirst) { EXPECT_CALL(*event_logger_, logAddHealthy(_, _, true)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)).WillOnce(Return(500)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(500))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(500), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1278,7 +1293,7 @@ TEST_F(HttpHealthCheckerImplTest, HttpFailRemoveHostInCallbackNoClose) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)) @@ -1287,7 +1302,7 @@ TEST_F(HttpHealthCheckerImplTest, HttpFailRemoveHostInCallbackNoClose) { cluster_->prioritySet().runUpdateCallbacks(0, {}, {host}); })); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)).Times(0); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)).Times(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()).Times(0); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); respond(0, "503", false); @@ -1300,7 +1315,7 @@ TEST_F(HttpHealthCheckerImplTest, HttpFailRemoveHostInCallbackClose) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)) @@ -1309,7 +1324,7 @@ TEST_F(HttpHealthCheckerImplTest, HttpFailRemoveHostInCallbackClose) { cluster_->prioritySet().runUpdateCallbacks(0, {}, {host}); })); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)).Times(0); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)).Times(0); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()).Times(0); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); respond(0, "503", true); @@ -1321,12 +1336,12 @@ TEST_F(HttpHealthCheckerImplTest, HttpFail) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); respond(0, "503", false); @@ -1337,12 +1352,12 @@ TEST_F(HttpHealthCheckerImplTest, HttpFail) { EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), Host::ActiveHealthFailureType::UNHEALTHY); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( @@ -1350,13 +1365,13 @@ TEST_F(HttpHealthCheckerImplTest, HttpFail) { EXPECT_EQ(Host::Health::Unhealthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logAddHealthy(_, _, false)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1368,12 +1383,12 @@ TEST_F(HttpHealthCheckerImplTest, HttpFailLogError) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); respond(0, "503", false); @@ -1384,13 +1399,13 @@ TEST_F(HttpHealthCheckerImplTest, HttpFailLogError) { EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), Host::ActiveHealthFailureType::UNHEALTHY); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // logUnhealthy is called with first_check == false EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, false)); respond(0, "503", false); @@ -1401,12 +1416,12 @@ TEST_F(HttpHealthCheckerImplTest, HttpFailLogError) { EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), Host::ActiveHealthFailureType::UNHEALTHY); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( @@ -1414,13 +1429,13 @@ TEST_F(HttpHealthCheckerImplTest, HttpFailLogError) { EXPECT_EQ(Host::Health::Unhealthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logAddHealthy(_, _, false)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1435,23 +1450,23 @@ TEST_F(HttpHealthCheckerImplTest, Disconnect) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->client_connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); expectClientCreate(0); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(cluster_->prioritySet().getMockHostSet(0)->hosts_[0], HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->client_connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( @@ -1466,12 +1481,12 @@ TEST_F(HttpHealthCheckerImplTest, Timeout) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*test_sessions_[0]->client_connection_, close(_)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); @@ -1491,7 +1506,7 @@ TEST_F(HttpHealthCheckerImplTest, TimeoutThenSuccess) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); // Do a response that is not complete but includes headers. @@ -1502,18 +1517,18 @@ TEST_F(HttpHealthCheckerImplTest, TimeoutThenSuccess) { EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); EXPECT_CALL(*test_sessions_[0]->client_connection_, close(_)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); expectClientCreate(0); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1526,24 +1541,24 @@ TEST_F(HttpHealthCheckerImplTest, TimeoutThenRemoteClose) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); EXPECT_CALL(*test_sessions_[0]->client_connection_, close(_)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); expectClientCreate(0); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->client_connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( @@ -1562,11 +1577,11 @@ TEST_F(HttpHealthCheckerImplTest, TimeoutAfterDisconnect) { expectSessionCreate(); expectStreamCreate(0); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)).Times(2); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)).Times(2); health_checker_->start(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)).Times(1); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)).Times(2); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)).Times(2); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); for (auto& session : test_sessions_) { session->client_connection_->close(Network::ConnectionCloseType::NoFlush); @@ -1576,7 +1591,7 @@ TEST_F(HttpHealthCheckerImplTest, TimeoutAfterDisconnect) { EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); - test_sessions_[0]->timeout_timer_->enableTimer(std::chrono::seconds(10)); + test_sessions_[0]->timeout_timer_->enableTimer(std::chrono::seconds(10), nullptr); test_sessions_[0]->timeout_timer_->invokeCallback(); EXPECT_EQ(Host::Health::Unhealthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1590,7 +1605,7 @@ TEST_F(HttpHealthCheckerImplTest, DynamicAddAndRemove) { expectStreamCreate(0); cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); cluster_->prioritySet().getMockHostSet(0)->runCallbacks( {cluster_->prioritySet().getMockHostSet(0)->hosts_.back()}, {}); @@ -1608,17 +1623,17 @@ TEST_F(HttpHealthCheckerImplTest, ConnectionClose) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); expectClientCreate(0); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); test_sessions_[0]->interval_timer_->invokeCallback(); } @@ -1630,17 +1645,17 @@ TEST_F(HttpHealthCheckerImplTest, ProxyConnectionClose) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false, true); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); expectClientCreate(0); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); test_sessions_[0]->interval_timer_->invokeCallback(); } @@ -1650,39 +1665,39 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { makeTestHost(cluster_->info_, "tcp://128.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); // First check should respect no_traffic_interval setting. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(5000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(5000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); cluster_->info_->stats().upstream_cx_total_.inc(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Follow up successful checks should respect interval setting. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Follow up successful checks should respect interval setting. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -1692,33 +1707,33 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { // check respects "unhealthy_interval". EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "503", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Subsequent failing checks should respect unhealthy_interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "503", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Subsequent failing checks should respect unhealthy_interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "503", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -1726,21 +1741,21 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { // When transitioning to a successful state, checks should respect healthy_edge_interval. Health // state should be delayed pending healthy threshold. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -1749,22 +1764,22 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { // the default interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logAddHealthy(_, _, false)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Subsequent checks shouldn't change the state. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -1773,11 +1788,11 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { // timeout, being a network type failure, should respect unhealthy threshold before changing the // health state. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(3000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(3000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a network timeout. expectClientCreate(0); // Needed after a response is sent. @@ -1785,11 +1800,11 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(3000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(3000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a network timeout. expectClientCreate(0); // Needed after a response is sent. @@ -1800,11 +1815,11 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { // reached, health state should also change. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a network timeout. expectClientCreate(0); // Needed after a response is sent. @@ -1813,11 +1828,11 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { // Remaining failing checks shouldn't change the state. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a network timeout. expectClientCreate(0); // Needed after a response is sent. @@ -1826,21 +1841,21 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { // When transitioning to a successful state, checks should respect healthy_edge_interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -1849,18 +1864,18 @@ TEST_F(HttpHealthCheckerImplTest, HealthCheckIntervals) { // the default interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logAddHealthy(_, _, false)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Subsequent checks shouldn't change the state. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); } @@ -1873,10 +1888,10 @@ TEST_F(HttpHealthCheckerImplTest, RemoteCloseBetweenChecks) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1885,10 +1900,10 @@ TEST_F(HttpHealthCheckerImplTest, RemoteCloseBetweenChecks) { expectClientCreate(0); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); test_sessions_[0]->interval_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1903,10 +1918,10 @@ TEST_F(HttpHealthCheckerImplTest, DontReuseConnectionBetweenChecks) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1916,10 +1931,10 @@ TEST_F(HttpHealthCheckerImplTest, DontReuseConnectionBetweenChecks) { // closes the connection. expectClientCreate(0); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); test_sessions_[0]->interval_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respond(0, "200", false); EXPECT_EQ(Host::Health::Healthy, cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->health()); @@ -1933,10 +1948,10 @@ TEST_F(HttpHealthCheckerImplTest, StreamReachesWatermarkDuringCheck) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->request_encoder_.stream_.runHighWatermarkCallbacks(); @@ -1954,10 +1969,10 @@ TEST_F(HttpHealthCheckerImplTest, ConnectionReachesWatermarkDuringCheck) { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(_, _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->client_connection_->runHighWatermarkCallbacks(); @@ -1982,7 +1997,7 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAltPort) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(hosts); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); EXPECT_CALL(test_sessions_[0]->request_encoder_, encodeHeaders(_, true)) .WillOnce(Invoke([&](const Http::HeaderMap& headers, bool) { EXPECT_EQ(headers.Host()->value().getStringView(), host); @@ -1993,7 +2008,8 @@ TEST_F(HttpHealthCheckerImplTest, SuccessServiceCheckWithAltPort) { EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .WillOnce(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); absl::optional health_checked_cluster("locations-production-iad"); respond(0, "200", false, false, true, false, health_checked_cluster); @@ -2013,19 +2029,21 @@ TEST_F(HttpHealthCheckerImplTest, SuccessWithMultipleHostsAndAltPort) { cluster_->info_->stats().upstream_cx_total_.inc(); expectSessionCreate(hosts); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); expectSessionCreate(hosts); expectStreamCreate(1); - EXPECT_CALL(*test_sessions_[1]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[1]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.max_interval", _)).Times(2); EXPECT_CALL(runtime_.snapshot_, getInteger("health_check.min_interval", _)) .Times(2) .WillRepeatedly(Return(45000)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); - EXPECT_CALL(*test_sessions_[1]->interval_timer_, enableTimer(std::chrono::milliseconds(45000))); + EXPECT_CALL(*test_sessions_[1]->interval_timer_, + enableTimer(std::chrono::milliseconds(45000), _)); EXPECT_CALL(*test_sessions_[1]->timeout_timer_, disableTimer()); respond(0, "200", false, false, true); respond(1, "200", false, false, true); @@ -2410,7 +2428,7 @@ TEST_F(TcpHealthCheckerImplTest, Success) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); health_checker_->start(); connection_->runHighWatermarkCallbacks(); @@ -2418,7 +2436,7 @@ TEST_F(TcpHealthCheckerImplTest, Success) { connection_->raiseEvent(Network::ConnectionEvent::Connected); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); Buffer::OwnedImpl response; add_uint8(response, 2); read_filter_->onData(response, false); @@ -2434,14 +2452,14 @@ TEST_F(TcpHealthCheckerImplTest, DataWithoutReusingConnection) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)).Times(1); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); health_checker_->start(); connection_->raiseEvent(Network::ConnectionEvent::Connected); // Expected execution flow when a healthcheck is successful and reuse_connection is false. EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush)).Times(1); Buffer::OwnedImpl response; @@ -2463,7 +2481,7 @@ TEST_F(TcpHealthCheckerImplTest, WrongData) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)).Times(1); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); health_checker_->start(); connection_->raiseEvent(Network::ConnectionEvent::Connected); @@ -2492,7 +2510,7 @@ TEST_F(TcpHealthCheckerImplTest, TimeoutThenRemoteClose) { cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); cluster_->prioritySet().getMockHostSet(0)->runCallbacks( {cluster_->prioritySet().getMockHostSet(0)->hosts_.back()}, {}); @@ -2506,7 +2524,7 @@ TEST_F(TcpHealthCheckerImplTest, TimeoutThenRemoteClose) { EXPECT_CALL(*connection_, close(_)); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); timeout_timer_->invokeCallback(); EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), Host::ActiveHealthFailureType::TIMEOUT); @@ -2514,14 +2532,14 @@ TEST_F(TcpHealthCheckerImplTest, TimeoutThenRemoteClose) { expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); interval_timer_->invokeCallback(); connection_->raiseEvent(Network::ConnectionEvent::Connected); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( Host::HealthFlag::FAILED_ACTIVE_HC)); @@ -2530,7 +2548,7 @@ TEST_F(TcpHealthCheckerImplTest, TimeoutThenRemoteClose) { expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); interval_timer_->invokeCallback(); connection_->raiseEvent(Network::ConnectionEvent::Connected); @@ -2552,7 +2570,7 @@ TEST_F(TcpHealthCheckerImplTest, Timeout) { cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); cluster_->prioritySet().getMockHostSet(0)->runCallbacks( {cluster_->prioritySet().getMockHostSet(0)->hosts_.back()}, {}); @@ -2567,7 +2585,7 @@ TEST_F(TcpHealthCheckerImplTest, Timeout) { EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); timeout_timer_->invokeCallback(); EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), Host::ActiveHealthFailureType::TIMEOUT); @@ -2586,7 +2604,7 @@ TEST_F(TcpHealthCheckerImplTest, DoubleTimeout) { cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); cluster_->prioritySet().getMockHostSet(0)->runCallbacks( {cluster_->prioritySet().getMockHostSet(0)->hosts_.back()}, {}); @@ -2600,7 +2618,7 @@ TEST_F(TcpHealthCheckerImplTest, DoubleTimeout) { EXPECT_CALL(*connection_, close(_)); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); timeout_timer_->invokeCallback(); EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), Host::ActiveHealthFailureType::TIMEOUT); @@ -2608,7 +2626,7 @@ TEST_F(TcpHealthCheckerImplTest, DoubleTimeout) { expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); interval_timer_->invokeCallback(); connection_->raiseEvent(Network::ConnectionEvent::Connected); @@ -2616,7 +2634,7 @@ TEST_F(TcpHealthCheckerImplTest, DoubleTimeout) { EXPECT_CALL(*connection_, close(_)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); timeout_timer_->invokeCallback(); EXPECT_EQ(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->getActiveHealthFailureType(), Host::ActiveHealthFailureType::TIMEOUT); @@ -2625,7 +2643,7 @@ TEST_F(TcpHealthCheckerImplTest, DoubleTimeout) { expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); interval_timer_->invokeCallback(); connection_->raiseEvent(Network::ConnectionEvent::Connected); @@ -2646,14 +2664,14 @@ TEST_F(TcpHealthCheckerImplTest, TimeoutWithoutReusingConnection) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)).Times(1); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); health_checker_->start(); connection_->raiseEvent(Network::ConnectionEvent::Connected); // Expected flow when a healthcheck is successful and reuse_connection is false. EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); EXPECT_CALL(*connection_, close(Network::ConnectionCloseType::NoFlush)).Times(1); Buffer::OwnedImpl response; @@ -2666,14 +2684,14 @@ TEST_F(TcpHealthCheckerImplTest, TimeoutWithoutReusingConnection) { // The healthcheck will run again. expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); interval_timer_->invokeCallback(); connection_->raiseEvent(Network::ConnectionEvent::Connected); // Expected flow when a healthcheck times out. EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); // The healthcheck is not yet at the unhealthy threshold. EXPECT_FALSE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( @@ -2687,7 +2705,7 @@ TEST_F(TcpHealthCheckerImplTest, TimeoutWithoutReusingConnection) { // The healthcheck will run again, it should be failing after this attempt. expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); interval_timer_->invokeCallback(); connection_->raiseEvent(Network::ConnectionEvent::Connected); @@ -2695,7 +2713,7 @@ TEST_F(TcpHealthCheckerImplTest, TimeoutWithoutReusingConnection) { // Expected flow when a healthcheck times out. EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); connection_->raiseEvent(Network::ConnectionEvent::RemoteClose); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( Host::HealthFlag::FAILED_ACTIVE_HC)); @@ -2716,17 +2734,17 @@ TEST_F(TcpHealthCheckerImplTest, NoData) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)).Times(0); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); health_checker_->start(); EXPECT_CALL(*connection_, close(_)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); connection_->raiseEvent(Network::ConnectionEvent::Connected); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)).Times(0); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); interval_timer_->invokeCallback(); } @@ -2739,7 +2757,7 @@ TEST_F(TcpHealthCheckerImplTest, PassiveFailure) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)).Times(0); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); health_checker_->start(); @@ -2755,7 +2773,7 @@ TEST_F(TcpHealthCheckerImplTest, PassiveFailure) { // A single success should not bring us back to healthy. EXPECT_CALL(*connection_, close(_)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); connection_->raiseEvent(Network::ConnectionEvent::Connected); EXPECT_TRUE(cluster_->prioritySet().getMockHostSet(0)->hosts_[0]->healthFlagGet( Host::HealthFlag::FAILED_ACTIVE_HC)); @@ -2777,7 +2795,7 @@ TEST_F(TcpHealthCheckerImplTest, PassiveFailureCrossThreadRemoveHostRace) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)).Times(0); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); health_checker_->start(); // Do a passive failure. This will not reset the active HC timers. @@ -2806,7 +2824,7 @@ TEST_F(TcpHealthCheckerImplTest, PassiveFailureCrossThreadRemoveClusterRace) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)).Times(0); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); health_checker_->start(); // Do a passive failure. This will not reset the active HC timers. @@ -2834,13 +2852,13 @@ TEST_F(TcpHealthCheckerImplTest, ConnectionLocalFailure) { expectSessionCreate(); expectClientCreate(); EXPECT_CALL(*connection_, write(_, _)); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); health_checker_->start(); // Expect the LocalClose to be handled as a health check failure EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); // Raise a LocalClose that is not triggered by the health monitor itself. // e.g. a failure to setsockopt(). @@ -3092,16 +3110,16 @@ class GrpcHealthCheckerImplTestBase { // Hides timer/stream-related boilerplate of healthcheck start. void expectHealthcheckStart(size_t index) { expectStreamCreate(index); - EXPECT_CALL(*test_sessions_[index]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[index]->timeout_timer_, enableTimer(_, _)); } // Hides timer-related boilerplate of healthcheck stop. void expectHealthcheckStop(size_t index, int interval_ms = 0) { if (interval_ms > 0) { EXPECT_CALL(*test_sessions_[index]->interval_timer_, - enableTimer(std::chrono::milliseconds(interval_ms))); + enableTimer(std::chrono::milliseconds(interval_ms), _)); } else { - EXPECT_CALL(*test_sessions_[index]->interval_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[index]->interval_timer_, enableTimer(_, _)); } EXPECT_CALL(*test_sessions_[index]->timeout_timer_, disableTimer()); } @@ -3532,7 +3550,7 @@ TEST_F(GrpcHealthCheckerImplTest, DynamicAddAndRemove) { expectStreamCreate(0); cluster_->prioritySet().getMockHostSet(0)->hosts_ = { makeTestHost(cluster_->info_, "tcp://127.0.0.1:80")}; - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); cluster_->prioritySet().getMockHostSet(0)->runCallbacks( {cluster_->prioritySet().getMockHostSet(0)->hosts_.back()}, {}); @@ -3548,39 +3566,39 @@ TEST_F(GrpcHealthCheckerImplTest, HealthCheckIntervals) { makeTestHost(cluster_->info_, "tcp://128.0.0.1:80")}; expectSessionCreate(); expectStreamCreate(0); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); health_checker_->start(); // First check should respect no_traffic_interval setting. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(5000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(5000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); cluster_->info_->stats().upstream_cx_total_.inc(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Follow up successful checks should respect interval setting. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Follow up successful checks should respect interval setting. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -3590,33 +3608,33 @@ TEST_F(GrpcHealthCheckerImplTest, HealthCheckIntervals) { // check respects "unhealthy_interval". EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::NOT_SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Subsequent failing checks should respect unhealthy_interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::NOT_SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Subsequent failing checks should respect unhealthy_interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::NOT_SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -3624,21 +3642,21 @@ TEST_F(GrpcHealthCheckerImplTest, HealthCheckIntervals) { // When transitioning to a successful state, checks should respect healthy_edge_interval. Health // state should be delayed pending healthy threshold. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -3647,22 +3665,22 @@ TEST_F(GrpcHealthCheckerImplTest, HealthCheckIntervals) { // the default interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logAddHealthy(_, _, false)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Subsequent checks shouldn't change the state. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -3671,21 +3689,21 @@ TEST_F(GrpcHealthCheckerImplTest, HealthCheckIntervals) { // timeout, being a network type failure, should respect unhealthy threshold before changing the // health state. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(3000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(3000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(3000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(3000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -3694,43 +3712,43 @@ TEST_F(GrpcHealthCheckerImplTest, HealthCheckIntervals) { // reached, health state should also change. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Remaining failing checks shouldn't change the state. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(2000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); test_sessions_[0]->timeout_timer_->invokeCallback(); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // When transitioning to a successful state, checks should respect healthy_edge_interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); EXPECT_CALL(*this, onHostStatus(_, HealthTransition::ChangePending)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); @@ -3739,18 +3757,18 @@ TEST_F(GrpcHealthCheckerImplTest, HealthCheckIntervals) { // the default interval. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Changed)); EXPECT_CALL(*event_logger_, logAddHealthy(_, _, false)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); - EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_)); + EXPECT_CALL(*test_sessions_[0]->timeout_timer_, enableTimer(_, _)); // Needed after a response is sent. expectStreamCreate(0); test_sessions_[0]->interval_timer_->invokeCallback(); // Subsequent checks shouldn't change the state. EXPECT_CALL(*this, onHostStatus(_, HealthTransition::Unchanged)); - EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*test_sessions_[0]->interval_timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(*test_sessions_[0]->timeout_timer_, disableTimer()); respondServiceStatus(0, grpc::health::v1::HealthCheckResponse::SERVING); } @@ -4070,7 +4088,7 @@ TEST(HealthCheckProto, Validation) { service_name: locations path: /healthcheck )EOF"; - EXPECT_THROW_WITH_REGEX(MessageUtil::validate(parseHealthCheckFromV2Yaml(yaml)), EnvoyException, + EXPECT_THROW_WITH_REGEX(TestUtility::validate(parseHealthCheckFromV2Yaml(yaml)), EnvoyException, "Proto constraint validation failed.*value must be greater than.*"); } { @@ -4082,7 +4100,7 @@ TEST(HealthCheckProto, Validation) { service_name: locations path: /healthcheck )EOF"; - EXPECT_THROW_WITH_REGEX(MessageUtil::validate(parseHealthCheckFromV2Yaml(yaml)), EnvoyException, + EXPECT_THROW_WITH_REGEX(TestUtility::validate(parseHealthCheckFromV2Yaml(yaml)), EnvoyException, "Proto constraint validation failed.*value must be greater than.*"); } { @@ -4094,7 +4112,7 @@ TEST(HealthCheckProto, Validation) { service_name: locations path: /healthcheck )EOF"; - EXPECT_THROW_WITH_REGEX(MessageUtil::validate(parseHealthCheckFromV2Yaml(yaml)), EnvoyException, + EXPECT_THROW_WITH_REGEX(TestUtility::validate(parseHealthCheckFromV2Yaml(yaml)), EnvoyException, "Proto constraint validation failed.*value must be greater than.*"); } { @@ -4106,7 +4124,7 @@ TEST(HealthCheckProto, Validation) { service_name: locations path: /healthcheck )EOF"; - EXPECT_THROW_WITH_REGEX(MessageUtil::validate(parseHealthCheckFromV2Yaml(yaml)), EnvoyException, + EXPECT_THROW_WITH_REGEX(TestUtility::validate(parseHealthCheckFromV2Yaml(yaml)), EnvoyException, "Proto constraint validation failed.*value must be greater than.*"); } } diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc index e29dd9b0dc..a2e7366f5e 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/common/upstream/load_balancer_impl_test.cc @@ -539,6 +539,39 @@ TEST_P(FailoverTest, PriorityUpdatesWithLocalHostSet) { EXPECT_EQ(tertiary_host_set_.hosts_[0], lb_->chooseHost(nullptr)); } +// Test that extending the priority set with an existing LB causes the correct updates when the +// cluster is configured to disable on panic. +TEST_P(FailoverTest, PriorityUpdatesWithLocalHostSetDisableOnPanic) { + host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80")}; + failover_host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:81")}; + common_config_.mutable_zone_aware_lb_config()->set_fail_traffic_on_panic(true); + + init(false); + // With both the primary and failover hosts unhealthy, we should select no host. + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); + + // Update the priority set with a new priority level P=2 and ensure the host + // is chosen + MockHostSet& tertiary_host_set_ = *priority_set_.getMockHostSet(2); + HostVectorSharedPtr hosts(new HostVector({makeTestHost(info_, "tcp://127.0.0.1:82")})); + tertiary_host_set_.hosts_ = *hosts; + tertiary_host_set_.healthy_hosts_ = tertiary_host_set_.hosts_; + HostVector add_hosts; + add_hosts.push_back(tertiary_host_set_.hosts_[0]); + tertiary_host_set_.runCallbacks(add_hosts, {}); + EXPECT_EQ(tertiary_host_set_.hosts_[0], lb_->chooseHost(nullptr)); + + // Now add a healthy host in P=0 and make sure it is immediately selected. + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks(add_hosts, {}); + EXPECT_EQ(host_set_.hosts_[0], lb_->chooseHost(nullptr)); + + // Remove the healthy host and ensure we fail back over to tertiary_host_set_ + host_set_.healthy_hosts_ = {}; + host_set_.runCallbacks({}, {}); + EXPECT_EQ(tertiary_host_set_.hosts_[0], lb_->chooseHost(nullptr)); +} + // Test extending the priority set. TEST_P(FailoverTest, ExtendPrioritiesUpdatingPrioritySet) { host_set_.hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80")}; @@ -829,6 +862,32 @@ TEST_P(RoundRobinLoadBalancerTest, MaxUnhealthyPanic) { EXPECT_EQ(3UL, stats_.lb_healthy_panic_.value()); } +// Test that no hosts are selected when fail_traffic_on_panic is enabled. +TEST_P(RoundRobinLoadBalancerTest, MaxUnhealthyPanicDisableOnPanic) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80"), + makeTestHost(info_, "tcp://127.0.0.1:81")}; + hostSet().hosts_ = { + makeTestHost(info_, "tcp://127.0.0.1:80"), makeTestHost(info_, "tcp://127.0.0.1:81"), + makeTestHost(info_, "tcp://127.0.0.1:82"), makeTestHost(info_, "tcp://127.0.0.1:83"), + makeTestHost(info_, "tcp://127.0.0.1:84"), makeTestHost(info_, "tcp://127.0.0.1:85")}; + + common_config_.mutable_zone_aware_lb_config()->set_fail_traffic_on_panic(true); + + init(false); + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); + + // Take the threshold back above the panic threshold. + hostSet().healthy_hosts_ = { + makeTestHost(info_, "tcp://127.0.0.1:80"), makeTestHost(info_, "tcp://127.0.0.1:81"), + makeTestHost(info_, "tcp://127.0.0.1:82"), makeTestHost(info_, "tcp://127.0.0.1:83")}; + hostSet().runCallbacks({}, {}); + + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + + EXPECT_EQ(1UL, stats_.lb_healthy_panic_.value()); +} + // Ensure if the panic threshold is 0%, panic mode is disabled. TEST_P(RoundRobinLoadBalancerTest, DisablePanicMode) { hostSet().healthy_hosts_ = {}; @@ -1177,6 +1236,41 @@ TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingLocalEmpty) { EXPECT_EQ(1U, stats_.lb_local_cluster_not_ok_.value()); } +TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingLocalEmptyFailTrafficOnPanic) { + common_config_.mutable_zone_aware_lb_config()->set_fail_traffic_on_panic(true); + + if (&hostSet() == &failover_host_set_) { // P = 1 does not support zone-aware routing. + return; + } + HostVectorSharedPtr upstream_hosts(new HostVector( + {makeTestHost(info_, "tcp://127.0.0.1:80"), makeTestHost(info_, "tcp://127.0.0.1:81")})); + HostVectorSharedPtr local_hosts(new HostVector({}, {})); + + HostsPerLocalitySharedPtr upstream_hosts_per_locality = makeHostsPerLocality( + {{makeTestHost(info_, "tcp://127.0.0.1:80")}, {makeTestHost(info_, "tcp://127.0.0.1:81")}}); + HostsPerLocalitySharedPtr local_hosts_per_locality = makeHostsPerLocality({{}, {}}); + + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillOnce(Return(50)) + .WillOnce(Return(50)); + EXPECT_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillOnce(Return(true)); + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillOnce(Return(1)); + + hostSet().healthy_hosts_ = *upstream_hosts; + hostSet().hosts_ = *upstream_hosts; + hostSet().healthy_hosts_per_locality_ = upstream_hosts_per_locality; + init(true); + updateHosts(local_hosts, local_hosts_per_locality); + + // Local cluster is not OK, we'll do regular routing (and select no host, since we're in global + // panic). + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); + EXPECT_EQ(0U, stats_.lb_healthy_panic_.value()); + EXPECT_EQ(1U, stats_.lb_local_cluster_not_ok_.value()); +} + // Validate that if we have healthy host lists >= 2, but there is no local // locality included, that we skip zone aware routing and fallback. TEST_P(RoundRobinLoadBalancerTest, NoZoneAwareRoutingNoLocalLocality) { @@ -1388,20 +1482,40 @@ INSTANTIATE_TEST_SUITE_P(PrimaryOrFailover, LeastRequestLoadBalancerTest, class RandomLoadBalancerTest : public LoadBalancerTestBase { public: - RandomLoadBalancer lb_{priority_set_, nullptr, stats_, runtime_, random_, common_config_}; + void init() { + lb_.reset( + new RandomLoadBalancer(priority_set_, nullptr, stats_, runtime_, random_, common_config_)); + } + std::shared_ptr lb_; }; -TEST_P(RandomLoadBalancerTest, NoHosts) { EXPECT_EQ(nullptr, lb_.chooseHost(nullptr)); } +TEST_P(RandomLoadBalancerTest, NoHosts) { + init(); + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); +} TEST_P(RandomLoadBalancerTest, Normal) { + init(); + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80"), makeTestHost(info_, "tcp://127.0.0.1:81")}; hostSet().hosts_ = hostSet().healthy_hosts_; hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(3)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); +} + +TEST_P(RandomLoadBalancerTest, FailClusterOnPanic) { + common_config_.mutable_zone_aware_lb_config()->set_fail_traffic_on_panic(true); + init(); + + hostSet().healthy_hosts_ = {}; + hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80"), + makeTestHost(info_, "tcp://127.0.0.1:81")}; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); } INSTANTIATE_TEST_SUITE_P(PrimaryOrFailover, RandomLoadBalancerTest, ::testing::Values(true, false)); diff --git a/test/common/upstream/load_stats_reporter_test.cc b/test/common/upstream/load_stats_reporter_test.cc index 05dcdf9f22..11a2bdfc9b 100644 --- a/test/common/upstream/load_stats_reporter_test.cc +++ b/test/common/upstream/load_stats_reporter_test.cc @@ -63,7 +63,7 @@ class LoadStatsReporterTest : public testing::Test { std::copy(cluster_names.begin(), cluster_names.end(), Protobuf::RepeatedPtrFieldBackInserter(response->mutable_clusters())); - EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000))); + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); load_stats_reporter_->onReceiveMessage(std::move(response)); } @@ -84,7 +84,7 @@ class LoadStatsReporterTest : public testing::Test { // Validate that stream creation results in a timer based retry. TEST_F(LoadStatsReporterTest, StreamCreationFailure) { EXPECT_CALL(*async_client_, startRaw(_, _, _)).WillOnce(Return(nullptr)); - EXPECT_CALL(*retry_timer_, enableTimer(_)); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)); createLoadStatsReporter(); EXPECT_CALL(*async_client_, startRaw(_, _, _)).WillOnce(Return(&async_stream_)); expectSendMessage({}); @@ -98,13 +98,13 @@ TEST_F(LoadStatsReporterTest, TestPubSub) { deliverLoadStatsResponse({"foo"}); EXPECT_CALL(async_stream_, sendMessageRaw_(_, _)); - EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000))); + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); response_timer_cb_(); deliverLoadStatsResponse({"bar"}); EXPECT_CALL(async_stream_, sendMessageRaw_(_, _)); - EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000))); + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); response_timer_cb_(); } @@ -135,7 +135,7 @@ TEST_F(LoadStatsReporterTest, ExistingClusters) { Protobuf::util::TimeUtil::MicrosecondsToDuration(1)); expectSendMessage({foo_cluster_stats}); } - EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000))); + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); response_timer_cb_(); // Some traffic on foo/bar in between previous request and next response. @@ -163,7 +163,7 @@ TEST_F(LoadStatsReporterTest, ExistingClusters) { Protobuf::util::TimeUtil::MicrosecondsToDuration(22)); expectSendMessage({bar_cluster_stats, foo_cluster_stats}); } - EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000))); + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); response_timer_cb_(); // Some traffic on foo/bar in between previous request and next response. @@ -184,7 +184,7 @@ TEST_F(LoadStatsReporterTest, ExistingClusters) { Protobuf::util::TimeUtil::MicrosecondsToDuration(5)); expectSendMessage({bar_cluster_stats}); } - EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000))); + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); response_timer_cb_(); // Some traffic on foo/bar in between previous request and next response. @@ -212,7 +212,7 @@ TEST_F(LoadStatsReporterTest, ExistingClusters) { Protobuf::util::TimeUtil::MicrosecondsToDuration(14)); expectSendMessage({bar_cluster_stats, foo_cluster_stats}); } - EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000))); + EXPECT_CALL(*response_timer_, enableTimer(std::chrono::milliseconds(42000), _)); response_timer_cb_(); } @@ -222,7 +222,7 @@ TEST_F(LoadStatsReporterTest, RemoteStreamClose) { expectSendMessage({}); createLoadStatsReporter(); EXPECT_CALL(*response_timer_, disableTimer()); - EXPECT_CALL(*retry_timer_, enableTimer(_)); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)); load_stats_reporter_->onRemoteClose(Grpc::Status::GrpcStatus::Canceled, ""); EXPECT_CALL(*async_client_, startRaw(_, _, _)).WillOnce(Return(&async_stream_)); expectSendMessage({}); diff --git a/test/common/upstream/logical_dns_cluster_test.cc b/test/common/upstream/logical_dns_cluster_test.cc index e09890e76e..b3d36e7c43 100644 --- a/test/common/upstream/logical_dns_cluster_test.cc +++ b/test/common/upstream/logical_dns_cluster_test.cc @@ -75,7 +75,7 @@ class LogicalDnsClusterTest : public testing::Test { EXPECT_CALL(membership_updated_, ready()); EXPECT_CALL(initialized_, ready()); - EXPECT_CALL(*resolve_timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolve_timer_, enableTimer(std::chrono::milliseconds(4000), _)); dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); EXPECT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); @@ -104,7 +104,7 @@ class LogicalDnsClusterTest : public testing::Test { resolve_timer_->invokeCallback(); // Should not cause any changes. - EXPECT_CALL(*resolve_timer_, enableTimer(_)); + EXPECT_CALL(*resolve_timer_, enableTimer(_, _)); dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2", "127.0.0.3"})); EXPECT_EQ("127.0.0.1:" + std::to_string(expected_hc_port), @@ -136,7 +136,7 @@ class LogicalDnsClusterTest : public testing::Test { resolve_timer_->invokeCallback(); // Should cause a change. - EXPECT_CALL(*resolve_timer_, enableTimer(_)); + EXPECT_CALL(*resolve_timer_, enableTimer(_, _)); dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3", "127.0.0.1", "127.0.0.2"})); EXPECT_EQ("127.0.0.3:" + std::to_string(expected_hc_port), @@ -154,7 +154,7 @@ class LogicalDnsClusterTest : public testing::Test { resolve_timer_->invokeCallback(); // Empty should not cause any change. - EXPECT_CALL(*resolve_timer_, enableTimer(_)); + EXPECT_CALL(*resolve_timer_, enableTimer(_, _)); dns_callback_({}); EXPECT_EQ(logical_host, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]); @@ -255,7 +255,7 @@ TEST_P(LogicalDnsParamTest, ImmediateResolve) { EXPECT_CALL(*dns_resolver_, resolve("foo.bar.com", std::get<1>(GetParam()), _)) .WillOnce(Invoke([&](const std::string&, Network::DnsLookupFamily, Network::DnsResolver::ResolveCb cb) -> Network::ActiveDnsQuery* { - EXPECT_CALL(*resolve_timer_, enableTimer(_)); + EXPECT_CALL(*resolve_timer_, enableTimer(_, _)); cb(TestUtility::makeDnsResponse(std::get<2>(GetParam()))); return nullptr; })); diff --git a/test/common/upstream/original_dst_cluster_test.cc b/test/common/upstream/original_dst_cluster_test.cc index eccedcc943..660d76cc8f 100644 --- a/test/common/upstream/original_dst_cluster_test.cc +++ b/test/common/upstream/original_dst_cluster_test.cc @@ -29,10 +29,8 @@ #include "gtest/gtest.h" using testing::_; -using testing::Invoke; using testing::NiceMock; using testing::Return; -using testing::ReturnRef; using testing::SaveArg; namespace Envoy { @@ -167,7 +165,7 @@ TEST_F(OriginalDstClusterTest, CleanupInterval) { EXPECT_CALL(initialized_, ready()); EXPECT_CALL(membership_updated_, ready()).Times(0); - EXPECT_CALL(*cleanup_timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*cleanup_timer_, enableTimer(std::chrono::milliseconds(1000), _)); setupFromYaml(yaml); EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); @@ -184,7 +182,7 @@ TEST_F(OriginalDstClusterTest, NoContext) { EXPECT_CALL(initialized_, ready()); EXPECT_CALL(membership_updated_, ready()).Times(0); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); setupFromYaml(yaml); EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); @@ -241,7 +239,7 @@ TEST_F(OriginalDstClusterTest, Membership) { )EOF"; EXPECT_CALL(initialized_, ready()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); setupFromYaml(yaml); EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); @@ -289,7 +287,7 @@ TEST_F(OriginalDstClusterTest, Membership) { // Make host time out, no membership changes happen on the first timeout. ASSERT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); EXPECT_EQ(true, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->used()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); cleanup_timer_->invokeCallback(); EXPECT_EQ( cluster_hosts, @@ -299,7 +297,7 @@ TEST_F(OriginalDstClusterTest, Membership) { ASSERT_EQ(1UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); EXPECT_EQ(false, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->used()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); EXPECT_CALL(membership_updated_, ready()); cleanup_timer_->invokeCallback(); EXPECT_NE(cluster_hosts, @@ -332,7 +330,7 @@ TEST_F(OriginalDstClusterTest, Membership2) { )EOF"; EXPECT_CALL(initialized_, ready()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); setupFromYaml(yaml); EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); @@ -391,7 +389,7 @@ TEST_F(OriginalDstClusterTest, Membership2) { ASSERT_EQ(2UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); EXPECT_EQ(true, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->used()); EXPECT_EQ(true, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[1]->used()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); cleanup_timer_->invokeCallback(); EXPECT_EQ( cluster_hosts, @@ -402,7 +400,7 @@ TEST_F(OriginalDstClusterTest, Membership2) { EXPECT_EQ(false, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[0]->used()); EXPECT_EQ(false, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts()[1]->used()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); EXPECT_CALL(membership_updated_, ready()); cleanup_timer_->invokeCallback(); EXPECT_NE(cluster_hosts, @@ -420,7 +418,7 @@ TEST_F(OriginalDstClusterTest, Connection) { )EOF"; EXPECT_CALL(initialized_, ready()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); setupFromYaml(yaml); EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); @@ -460,7 +458,7 @@ TEST_F(OriginalDstClusterTest, MultipleClusters) { )EOF"; EXPECT_CALL(initialized_, ready()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); setupFromYaml(yaml); PrioritySetImpl second; @@ -514,7 +512,7 @@ TEST_F(OriginalDstClusterTest, UseHttpHeaderEnabled) { )EOF"; EXPECT_CALL(initialized_, ready()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); setupFromYaml(yaml); EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); @@ -585,7 +583,7 @@ TEST_F(OriginalDstClusterTest, UseHttpHeaderDisabled) { )EOF"; EXPECT_CALL(initialized_, ready()); - EXPECT_CALL(*cleanup_timer_, enableTimer(_)); + EXPECT_CALL(*cleanup_timer_, enableTimer(_, _)); setupFromYaml(yaml); EXPECT_EQ(0UL, cluster_->prioritySet().hostSetsPerPriority()[0]->hosts().size()); diff --git a/test/common/upstream/outlier_detection_impl_test.cc b/test/common/upstream/outlier_detection_impl_test.cc index 6de146f062..1df588abbc 100644 --- a/test/common/upstream/outlier_detection_impl_test.cc +++ b/test/common/upstream/outlier_detection_impl_test.cc @@ -129,11 +129,14 @@ enforcing_success_rate: 20 success_rate_minimum_hosts: 50 success_rate_request_volume: 200 success_rate_stdev_factor: 3000 +failure_percentage_minimum_hosts: 10 +failure_percentage_request_volume: 25 +failure_percentage_threshold: 70 )EOF"; envoy::api::v2::cluster::OutlierDetection outlier_detection; TestUtility::loadFromYaml(yaml, outlier_detection); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(100))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(100), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, outlier_detection, dispatcher_, runtime_, time_system_, event_logger_)); @@ -148,6 +151,11 @@ success_rate_stdev_factor: 3000 EXPECT_EQ(50UL, detector->config().successRateMinimumHosts()); EXPECT_EQ(200UL, detector->config().successRateRequestVolume()); EXPECT_EQ(3000UL, detector->config().successRateStdevFactor()); + EXPECT_EQ(0UL, detector->config().enforcingFailurePercentage()); + EXPECT_EQ(0UL, detector->config().enforcingFailurePercentageLocalOrigin()); + EXPECT_EQ(10UL, detector->config().failurePercentageMinimumHosts()); + EXPECT_EQ(25UL, detector->config().failurePercentageRequestVolume()); + EXPECT_EQ(70UL, detector->config().failurePercentageThreshold()); } TEST_F(OutlierDetectorImplTest, DestroyWithActive) { @@ -156,7 +164,7 @@ TEST_F(OutlierDetectorImplTest, DestroyWithActive) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}, true); addHosts({"tcp://127.0.0.1:81"}, false); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -188,7 +196,7 @@ TEST_F(OutlierDetectorImplTest, DestroyWithActive) { TEST_F(OutlierDetectorImplTest, DestroyHostInUse) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -205,7 +213,7 @@ TEST_F(OutlierDetectorImplTest, DestroyHostInUse) { TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaHttpCodes) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -231,7 +239,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaHttpCodes) { // Interval that doesn't bring the host back in. time_system_.setMonotonicTime(std::chrono::milliseconds(9999)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -240,7 +248,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaHttpCodes) { EXPECT_CALL(checker_, check(hosts_[0])); EXPECT_CALL(*event_logger_, logUneject(std::static_pointer_cast(hosts_[0]))); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_TRUE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -276,7 +284,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaHttpCodes) { TEST_F(OutlierDetectorImplTest, ConnectSuccessWithOptionalHTTP_OK) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -300,7 +308,7 @@ TEST_F(OutlierDetectorImplTest, ConnectSuccessWithOptionalHTTP_OK) { TEST_F(OutlierDetectorImplTest, ExternalOriginEventsNonSplit) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -327,7 +335,7 @@ TEST_F(OutlierDetectorImplTest, ExternalOriginEventsNonSplit) { TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaNonHttpCodes) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -358,7 +366,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaNonHttpCodes) { // Interval that doesn't bring the host back in. time_system_.setMonotonicTime(std::chrono::milliseconds(9999)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -367,7 +375,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaNonHttpCodes) { EXPECT_CALL(checker_, check(hosts_[0])); EXPECT_CALL(*event_logger_, logUneject(std::static_pointer_cast(hosts_[0]))); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_TRUE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -410,7 +418,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlow5xxViaNonHttpCodes) { TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailure) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); @@ -449,7 +457,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailure) { // Interval that doesn't bring the host back in. time_system_.setMonotonicTime(std::chrono::milliseconds(9999)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -458,7 +466,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailure) { EXPECT_CALL(checker_, check(hosts_[0])); EXPECT_CALL(*event_logger_, logUneject(std::static_pointer_cast(hosts_[0]))); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_TRUE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -512,7 +520,7 @@ TEST_F(OutlierDetectorImplTest, TimeoutWithHttpCode) { "tcp://127.0.0.1:84", }); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -536,7 +544,7 @@ TEST_F(OutlierDetectorImplTest, TimeoutWithHttpCode) { EXPECT_CALL(checker_, check(hosts_[0])); EXPECT_CALL(*event_logger_, logUneject(std::static_pointer_cast(hosts_[0]))); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); @@ -581,7 +589,7 @@ TEST_F(OutlierDetectorImplTest, TimeoutWithHttpCode) { TEST_F(OutlierDetectorImplTest, BasicFlowLocalOriginFailure) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}, true); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, outlier_detection_split_, dispatcher_, runtime_, time_system_, event_logger_)); @@ -610,7 +618,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowLocalOriginFailure) { // Wait short time - not enough to be unejected time_system_.setMonotonicTime(std::chrono::milliseconds(9999)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -619,7 +627,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowLocalOriginFailure) { EXPECT_CALL(checker_, check(hosts_[0])); EXPECT_CALL(*event_logger_, logUneject(std::static_pointer_cast(hosts_[0]))); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_TRUE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -665,7 +673,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowLocalOriginFailure) { TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailureAnd5xx) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); @@ -698,7 +706,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailureAnd5xx) { // Interval that doesn't bring the host back in. time_system_.setMonotonicTime(std::chrono::milliseconds(9999)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -707,7 +715,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailureAnd5xx) { EXPECT_CALL(checker_, check(hosts_[0])); EXPECT_CALL(*event_logger_, logUneject(std::static_pointer_cast(hosts_[0]))); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[0]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_TRUE(hosts_[0]->outlierDetector().lastUnejectionTime()); @@ -757,7 +765,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowGatewayFailureAnd5xx) { TEST_F(OutlierDetectorImplTest, BasicFlowNonHttpCodesExternalOrigin) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -812,7 +820,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateExternalOrigin) { "tcp://127.0.0.1:84", }); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -845,7 +853,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateExternalOrigin) { EXPECT_CALL(*event_logger_, logEject(std::static_pointer_cast(hosts_[4]), _, envoy::data::cluster::v2alpha::OutlierEjectionType::SUCCESS_RATE, true)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); ON_CALL(runtime_.snapshot_, getInteger("outlier_detection.success_rate_stdev_factor", 1900)) .WillByDefault(Return(1900)); interval_timer_->invokeCallback(); @@ -867,7 +875,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateExternalOrigin) { // Interval that doesn't bring the host back in. time_system_.setMonotonicTime(std::chrono::milliseconds(19999)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_TRUE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_EQ(1UL, outlier_detection_ejections_active_.value()); @@ -877,7 +885,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateExternalOrigin) { EXPECT_CALL(checker_, check(hosts_[4])); EXPECT_CALL(*event_logger_, logUneject(std::static_pointer_cast(hosts_[4]))); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); @@ -900,11 +908,13 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateExternalOrigin) { loadRq(hosts_[4], 25, 503); time_system_.setMonotonicTime(std::chrono::milliseconds(60001)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); + // The success rate should be *calculated* since the minimum request volume was met for failure + // percentage ejection, but the host should not be ejected. EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); - EXPECT_EQ(-1, hosts_[4]->outlierDetector().successRate( - DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); + EXPECT_EQ(50UL, hosts_[4]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); EXPECT_EQ(-1, detector->successRateAverage( DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); EXPECT_EQ(-1, detector->successRateEjectionThreshold( @@ -916,7 +926,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateExternalOrigin) { TEST_F(OutlierDetectorImplTest, ExternalOriginEventsWithSplit) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}, true); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, outlier_detection_split_, dispatcher_, runtime_, time_system_, event_logger_)); @@ -951,7 +961,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateLocalOrigin) { "tcp://127.0.0.1:84", }); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, outlier_detection_split_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -979,7 +989,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateLocalOrigin) { logEject(std::static_pointer_cast(hosts_[4]), _, envoy::data::cluster::v2alpha::OutlierEjectionType::SUCCESS_RATE_LOCAL_ORIGIN, true)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); ON_CALL(runtime_.snapshot_, getInteger("outlier_detection.success_rate_stdev_factor", 1900)) .WillByDefault(Return(1900)); interval_timer_->invokeCallback(); @@ -1001,7 +1011,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateLocalOrigin) { // Interval that doesn't bring the host back in. time_system_.setMonotonicTime(std::chrono::milliseconds(19999)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_TRUE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_EQ(1UL, outlier_detection_ejections_active_.value()); @@ -1011,7 +1021,7 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateLocalOrigin) { EXPECT_CALL(checker_, check(hosts_[4])); EXPECT_CALL(*event_logger_, logUneject(std::static_pointer_cast(hosts_[4]))); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); EXPECT_FALSE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); @@ -1030,11 +1040,13 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateLocalOrigin) { loadRq(hosts_[4], 25, Result::LOCAL_ORIGIN_CONNECT_FAILED); time_system_.setMonotonicTime(std::chrono::milliseconds(60001)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); + // The success rate should be *calculated* since the minimum request volume was met for failure + // percentage ejection, but the host should not be ejected. EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); - EXPECT_EQ(-1, hosts_[4]->outlierDetector().successRate( - DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + EXPECT_EQ(50UL, hosts_[4]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); EXPECT_EQ(-1, detector->successRateAverage(DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); EXPECT_EQ(-1, detector->successRateEjectionThreshold( @@ -1044,22 +1056,268 @@ TEST_F(OutlierDetectorImplTest, BasicFlowSuccessRateLocalOrigin) { // Validate that empty hosts doesn't crash success rate handling when success_rate_minimum_hosts is // zero. This is a regression test for earlier divide-by-zero behavior. TEST_F(OutlierDetectorImplTest, EmptySuccessRate) { - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); loadRq(hosts_, 200, 503); time_system_.setMonotonicTime(std::chrono::milliseconds(10000)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); ON_CALL(runtime_.snapshot_, getInteger("outlier_detection.success_rate_minimum_hosts", 5)) .WillByDefault(Return(0)); interval_timer_->invokeCallback(); } +TEST_F(OutlierDetectorImplTest, BasicFlowFailurePercentageExternalOrigin) { + EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); + addHosts({ + "tcp://127.0.0.1:80", + "tcp://127.0.0.1:81", + "tcp://127.0.0.1:82", + "tcp://127.0.0.1:83", + "tcp://127.0.0.1:84", + }); + + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + std::shared_ptr detector(DetectorImpl::create( + cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); + detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); + + // Turn off 5xx detection and SR detection to test failure percentage detection in isolation. + ON_CALL(runtime_.snapshot_, featureEnabled("outlier_detection.enforcing_consecutive_5xx", 100)) + .WillByDefault(Return(false)); + ON_CALL(runtime_.snapshot_, + featureEnabled("outlier_detection.enforcing_consecutive_gateway_failure", 100)) + .WillByDefault(Return(false)); + ON_CALL(runtime_.snapshot_, featureEnabled("outlier_detection.enforcing_success_rate", 100)) + .WillByDefault(Return(false)); + // Now turn on failure percentage detection. + ON_CALL(runtime_.snapshot_, featureEnabled("outlier_detection.enforcing_failure_percentage", 0)) + .WillByDefault(Return(true)); + // Expect non-enforcing logging to happen every time the consecutive_5xx_ counter + // gets saturated (every 5 times). + EXPECT_CALL(*event_logger_, + logEject(std::static_pointer_cast(hosts_[3]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::CONSECUTIVE_5XX, false)) + .Times(50); + EXPECT_CALL( + *event_logger_, + logEject(std::static_pointer_cast(hosts_[3]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::CONSECUTIVE_GATEWAY_FAILURE, + false)) + .Times(50); + EXPECT_CALL(*event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::CONSECUTIVE_5XX, false)) + .Times(60); + EXPECT_CALL( + *event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::CONSECUTIVE_GATEWAY_FAILURE, + false)) + .Times(60); + + // Cause a failure percentage error on one host. First 3 hosts have perfect failure percentage; + // fourth host has failure percentage slightly below threshold; fifth has failure percentage + // slightly above threshold. + loadRq(hosts_, 50, 200); + loadRq(hosts_[3], 250, 503); + loadRq(hosts_[4], 300, 503); + + time_system_.setMonotonicTime(std::chrono::milliseconds(10000)); + EXPECT_CALL(checker_, check(hosts_[4])); + EXPECT_CALL(*event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE, + true)); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + ON_CALL(runtime_.snapshot_, getInteger("outlier_detection.success_rate_stdev_factor", 1900)) + .WillByDefault(Return(1900)); + interval_timer_->invokeCallback(); + EXPECT_FLOAT_EQ(100.0 * (50.0 / 300.0), + hosts_[3]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); + EXPECT_FLOAT_EQ(100.0 * (50.0 / 350.0), + hosts_[4]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); + // Make sure that local origin success rate monitor is not affected + EXPECT_EQ(-1, hosts_[4]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + EXPECT_EQ(-1, + detector->successRateAverage(DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + EXPECT_EQ(-1, detector->successRateEjectionThreshold( + DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + EXPECT_FALSE(hosts_[3]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); + EXPECT_TRUE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); + EXPECT_EQ(1UL, outlier_detection_ejections_active_.value()); + + // Interval that doesn't bring the host back in. + time_system_.setMonotonicTime(std::chrono::milliseconds(19999)); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + interval_timer_->invokeCallback(); + EXPECT_TRUE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); + EXPECT_EQ(1UL, outlier_detection_ejections_active_.value()); + + // Interval that does bring the host back in. + time_system_.setMonotonicTime(std::chrono::milliseconds(50001)); + EXPECT_CALL(checker_, check(hosts_[4])); + EXPECT_CALL(*event_logger_, + logUneject(std::static_pointer_cast(hosts_[4]))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + interval_timer_->invokeCallback(); + EXPECT_FALSE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); + EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); + + // Expect non-enforcing logging to happen every time the consecutive_5xx_ counter + // gets saturated (every 5 times). + EXPECT_CALL(*event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::CONSECUTIVE_5XX, false)) + .Times(5); + EXPECT_CALL( + *event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::CONSECUTIVE_GATEWAY_FAILURE, + false)) + .Times(5); + + // Give 4 hosts enough request volume but not to the 5th. Should not cause an ejection. + loadRq(hosts_, 25, 200); + loadRq(hosts_[4], 25, 503); + + time_system_.setMonotonicTime(std::chrono::milliseconds(60001)); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + interval_timer_->invokeCallback(); + // The success rate should be *calculated* since the minimum request volume was met for failure + // percentage ejection, but the host should not be ejected. + EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); + EXPECT_EQ(50UL, hosts_[4]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); + EXPECT_EQ(-1, detector->successRateAverage( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); + EXPECT_EQ(-1, detector->successRateEjectionThreshold( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); +} + +TEST_F(OutlierDetectorImplTest, BasicFlowFailurePercentageLocalOrigin) { + EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); + addHosts({ + "tcp://127.0.0.1:80", + "tcp://127.0.0.1:81", + "tcp://127.0.0.1:82", + "tcp://127.0.0.1:83", + "tcp://127.0.0.1:84", + }); + + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + std::shared_ptr detector(DetectorImpl::create( + cluster_, outlier_detection_split_, dispatcher_, runtime_, time_system_, event_logger_)); + detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); + + // Turn off 5xx detection and SR detection to test failure percentage detection in isolation. + ON_CALL(runtime_.snapshot_, + featureEnabled("outlier_detection.enforcing_consecutive_local_origin_failure", 100)) + .WillByDefault(Return(false)); + ON_CALL(runtime_.snapshot_, + featureEnabled("outlier_detection.enforcing_local_origin_success_rate", 100)) + .WillByDefault(Return(false)); + // Now turn on failure percentage detection. + ON_CALL(runtime_.snapshot_, + featureEnabled("outlier_detection.enforcing_failure_percentage_local_origin", 0)) + .WillByDefault(Return(true)); + // Expect non-enforcing logging to happen every time the consecutive_ counter + // gets saturated (every 5 times). + EXPECT_CALL( + *event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::CONSECUTIVE_LOCAL_ORIGIN_FAILURE, + false)) + .Times(40); + // Cause a failure percentage error on one host. First 4 of the hosts have perfect failure + // percentage. + loadRq(hosts_, 200, Result::LOCAL_ORIGIN_CONNECT_SUCCESS); + loadRq(hosts_[4], 200, Result::LOCAL_ORIGIN_CONNECT_FAILED); + + time_system_.setMonotonicTime(std::chrono::milliseconds(10000)); + EXPECT_CALL(checker_, check(hosts_[4])); + EXPECT_CALL( + *event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE_LOCAL_ORIGIN, + true)); + EXPECT_CALL( + *event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::SUCCESS_RATE_LOCAL_ORIGIN, + false)); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + ON_CALL(runtime_.snapshot_, getInteger("outlier_detection.failure_percentage_threshold", 85)) + .WillByDefault(Return(40)); + interval_timer_->invokeCallback(); + EXPECT_EQ(50, hosts_[4]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + EXPECT_EQ(90, + detector->successRateAverage(DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + EXPECT_EQ(52, detector->successRateEjectionThreshold( + DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + // Make sure that external origin success rate monitor is not affected + EXPECT_EQ(-1, hosts_[4]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); + EXPECT_EQ(-1, detector->successRateAverage( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); + EXPECT_EQ(-1, detector->successRateEjectionThreshold( + DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)); + EXPECT_TRUE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); + EXPECT_EQ(1UL, outlier_detection_ejections_active_.value()); + + // Interval that doesn't bring the host back in. + time_system_.setMonotonicTime(std::chrono::milliseconds(19999)); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + interval_timer_->invokeCallback(); + EXPECT_TRUE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); + EXPECT_EQ(1UL, outlier_detection_ejections_active_.value()); + + // Interval that does bring the host back in. + time_system_.setMonotonicTime(std::chrono::milliseconds(50001)); + EXPECT_CALL(checker_, check(hosts_[4])); + EXPECT_CALL(*event_logger_, + logUneject(std::static_pointer_cast(hosts_[4]))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + interval_timer_->invokeCallback(); + EXPECT_FALSE(hosts_[4]->healthFlagGet(Host::HealthFlag::FAILED_OUTLIER_CHECK)); + EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); + + // Expect non-enforcing logging to happen every time the consecutive_ counter + // gets saturated (every 5 times). + EXPECT_CALL( + *event_logger_, + logEject(std::static_pointer_cast(hosts_[4]), _, + envoy::data::cluster::v2alpha::OutlierEjectionType::CONSECUTIVE_LOCAL_ORIGIN_FAILURE, + false)) + .Times(5); + + // Give 4 hosts enough request volume but not to the 5th. Should not cause an ejection. + loadRq(hosts_, 25, Result::LOCAL_ORIGIN_CONNECT_SUCCESS); + loadRq(hosts_[4], 25, Result::LOCAL_ORIGIN_CONNECT_FAILED); + + time_system_.setMonotonicTime(std::chrono::milliseconds(60001)); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); + interval_timer_->invokeCallback(); + // The success rate should be *calculated* since the minimum request volume was met for failure + // percentage ejection, but the host should not be ejected. + EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); + EXPECT_EQ(50UL, hosts_[4]->outlierDetector().successRate( + DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + EXPECT_EQ(-1, + detector->successRateAverage(DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); + EXPECT_EQ(-1, detector->successRateEjectionThreshold( + DetectorHostMonitor::SuccessRateMonitorType::LocalOrigin)); +} + TEST_F(OutlierDetectorImplTest, RemoveWhileEjected) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -1082,14 +1340,14 @@ TEST_F(OutlierDetectorImplTest, RemoveWhileEjected) { EXPECT_EQ(0UL, outlier_detection_ejections_active_.value()); time_system_.setMonotonicTime(std::chrono::milliseconds(9999)); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); interval_timer_->invokeCallback(); } TEST_F(OutlierDetectorImplTest, Overflow) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80", "tcp://127.0.0.1:81"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -1118,7 +1376,7 @@ TEST_F(OutlierDetectorImplTest, Overflow) { TEST_F(OutlierDetectorImplTest, NotEnforcing) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -1163,7 +1421,7 @@ TEST_F(OutlierDetectorImplTest, NotEnforcing) { TEST_F(OutlierDetectorImplTest, CrossThreadRemoveRace) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -1185,7 +1443,7 @@ TEST_F(OutlierDetectorImplTest, CrossThreadRemoveRace) { TEST_F(OutlierDetectorImplTest, CrossThreadDestroyRace) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -1208,7 +1466,7 @@ TEST_F(OutlierDetectorImplTest, CrossThreadDestroyRace) { TEST_F(OutlierDetectorImplTest, CrossThreadFailRace) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -1236,7 +1494,7 @@ TEST_F(OutlierDetectorImplTest, CrossThreadFailRace) { TEST_F(OutlierDetectorImplTest, Consecutive_5xxAlreadyEjected) { EXPECT_CALL(cluster_.prioritySet(), addMemberUpdateCb(_)); addHosts({"tcp://127.0.0.1:80"}); - EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000))); + EXPECT_CALL(*interval_timer_, enableTimer(std::chrono::milliseconds(10000), _)); std::shared_ptr detector(DetectorImpl::create( cluster_, empty_outlier_detection_, dispatcher_, runtime_, time_system_, event_logger_)); detector->addChangedStateCb([&](HostSharedPtr host) -> void { checker_.check(host); }); @@ -1345,6 +1603,36 @@ TEST(OutlierDetectionEventLoggerImplTest, All) { .WillOnce(SaveArg<0>(&log4)); event_logger.logUneject(host); Json::Factory::loadFromString(log4); + + StringViewSaver log5; + EXPECT_CALL(host->outlier_detector_, lastUnejectionTime()).WillOnce(ReturnRef(monotonic_time)); + EXPECT_CALL(host->outlier_detector_, + successRate(DetectorHostMonitor::SuccessRateMonitorType::ExternalOrigin)) + .WillOnce(Return(0)); + EXPECT_CALL(*file, + write(absl::string_view( + "{\"type\":\"FAILURE_PERCENTAGE\",\"cluster_name\":\"fake_cluster\"," + "\"upstream_url\":\"10.0.0.1:443\",\"action\":\"EJECT\"," + "\"num_ejections\":0,\"enforced\":false,\"eject_failure_percentage_event\":{" + "\"host_success_rate\":0},\"timestamp\":\"2018-12-18T09:00:00Z\"," + "\"secs_since_last_action\":\"30\"}\n"))) + .WillOnce(SaveArg<0>(&log5)); + event_logger.logEject(host, detector, + envoy::data::cluster::v2alpha::OutlierEjectionType::FAILURE_PERCENTAGE, + false); + Json::Factory::loadFromString(log5); + + StringViewSaver log6; + EXPECT_CALL(host->outlier_detector_, lastEjectionTime()).WillOnce(ReturnRef(monotonic_time)); + EXPECT_CALL(*file, + write(absl::string_view( + "{\"type\":\"CONSECUTIVE_5XX\",\"cluster_name\":\"fake_cluster\"," + "\"upstream_url\":\"10.0.0.1:443\",\"action\":\"UNEJECT\"," + "\"num_ejections\":0,\"enforced\":false,\"timestamp\":\"2018-12-18T09:00:00Z\"," + "\"secs_since_last_action\":\"30\"}\n"))) + .WillOnce(SaveArg<0>(&log6)); + event_logger.logUneject(host); + Json::Factory::loadFromString(log6); } TEST(OutlierUtility, SRThreshold) { diff --git a/test/common/upstream/ring_hash_lb_test.cc b/test/common/upstream/ring_hash_lb_test.cc index 243f297bf1..c2cb3c1c80 100644 --- a/test/common/upstream/ring_hash_lb_test.cc +++ b/test/common/upstream/ring_hash_lb_test.cc @@ -17,7 +17,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; using testing::Return; diff --git a/test/common/upstream/subset_lb_test.cc b/test/common/upstream/subset_lb_test.cc index c18dc9a4f8..ed96bf70f6 100644 --- a/test/common/upstream/subset_lb_test.cc +++ b/test/common/upstream/subset_lb_test.cc @@ -22,8 +22,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::EndsWith; using testing::NiceMock; using testing::Return; using testing::ReturnRef; diff --git a/test/common/upstream/upstream_impl_test.cc b/test/common/upstream/upstream_impl_test.cc index f55f288409..9934f68f7c 100644 --- a/test/common/upstream/upstream_impl_test.cc +++ b/test/common/upstream/upstream_impl_test.cc @@ -39,8 +39,6 @@ using testing::_; using testing::ContainerEq; using testing::Invoke; using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Upstream { @@ -221,7 +219,7 @@ TEST_F(StrictDnsClusterImplTest, ZeroHostsHealthChecker) { EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); EXPECT_CALL(initialized, ready()); - EXPECT_CALL(*resolver.timer_, enableTimer(_)); + EXPECT_CALL(*resolver.timer_, enableTimer(_, _)); resolver.dns_callback_({}); EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->hosts().size()); EXPECT_EQ(0UL, cluster.prioritySet().hostSetsPerPriority()[0]->healthyHosts().size()); @@ -301,7 +299,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { cluster.initialize([] {}); resolver1.expectResolve(*dns_resolver_); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); EXPECT_THAT( @@ -312,7 +310,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), @@ -320,14 +318,14 @@ TEST_F(StrictDnsClusterImplTest, Basic) { resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3"})); EXPECT_THAT( @@ -335,7 +333,7 @@ TEST_F(StrictDnsClusterImplTest, Basic) { ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); // Make sure we de-dup the same address. - EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver2.dns_callback_(TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); EXPECT_THAT( @@ -390,7 +388,7 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalActiveHealthSkipped) { cluster.initialize([&]() -> void {}); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); - EXPECT_CALL(*resolver.timer_, enableTimer(_)).Times(2); + EXPECT_CALL(*resolver.timer_, enableTimer(_, _)).Times(2); resolver.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); // Verify that both endpoints are initially marked with FAILED_ACTIVE_HC, then @@ -443,7 +441,7 @@ TEST_F(StrictDnsClusterImplTest, HostRemovalAfterHcFail) { cluster.initialize([&initialized]() { initialized.ready(); }); EXPECT_CALL(*health_checker, addHostCheckCompleteCb(_)); - EXPECT_CALL(*resolver.timer_, enableTimer(_)).Times(2); + EXPECT_CALL(*resolver.timer_, enableTimer(_, _)).Times(2); resolver.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); // Verify that both endpoints are initially marked with FAILED_ACTIVE_HC, then @@ -592,7 +590,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { cluster.initialize([] {}); resolver1.expectResolve(*dns_resolver_); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); EXPECT_THAT( @@ -611,7 +609,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), @@ -623,7 +621,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), @@ -633,7 +631,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { // Since no change for localhost1, we expect no rebuild. EXPECT_EQ(2UL, stats_.counter("cluster.name.update_no_rebuild").value()); - EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver2.dns_callback_(TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); @@ -642,14 +640,14 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); // We again received the same set as before for localhost1. No rebuild this time. EXPECT_EQ(3UL, stats_.counter("cluster.name.update_no_rebuild").value()); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3"})); EXPECT_THAT( @@ -657,7 +655,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); // Make sure we de-dup the same address. - EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver2.dns_callback_(TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.3:11001", "10.0.0.1:11002"}), @@ -670,7 +668,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { cluster.prioritySet().hostSetsPerPriority()[0]->healthyHostsPerLocality().get().size()); // Make sure that we *don't* de-dup between resolve targets. - EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver3.dns_callback_(TestUtility::makeDnsResponse({"10.0.0.1"})); @@ -704,11 +702,11 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasic) { } }); - EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver2.dns_callback_(TestUtility::makeDnsResponse({})); - EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver3.dns_callback_(TestUtility::makeDnsResponse({})); @@ -789,7 +787,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { cluster.initialize([] {}); resolver1.expectResolve(*dns_resolver_); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.1", "127.0.0.2"})); EXPECT_THAT( @@ -800,7 +798,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), @@ -808,14 +806,14 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { resolver1.expectResolve(*dns_resolver_); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.2", "127.0.0.1"})); EXPECT_THAT( std::list({"127.0.0.1:11001", "127.0.0.2:11001"}), ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); resolver1.timer_->invokeCallback(); - EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver1.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver1.dns_callback_(TestUtility::makeDnsResponse({"127.0.0.3"})); EXPECT_THAT( @@ -823,7 +821,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { ContainerEq(hostListToAddresses(cluster.prioritySet().hostSetsPerPriority()[0]->hosts()))); // Make sure we de-dup the same address. - EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver2.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver2.dns_callback_(TestUtility::makeDnsResponse({"10.0.0.1", "10.0.0.1"})); EXPECT_THAT( @@ -839,7 +837,7 @@ TEST_F(StrictDnsClusterImplTest, LoadAssignmentBasicMultiplePriorities) { EXPECT_EQ(cluster.info().get(), &host->cluster()); } - EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver3.timer_, enableTimer(std::chrono::milliseconds(4000), _)); EXPECT_CALL(membership_updated, ready()); resolver3.dns_callback_(TestUtility::makeDnsResponse({"192.168.1.1", "192.168.1.2"})); @@ -915,7 +913,7 @@ TEST_F(StrictDnsClusterImplTest, RecordTtlAsDnsRefreshRate) { EXPECT_CALL(membership_updated, ready()); - EXPECT_CALL(*resolver.timer_, enableTimer(std::chrono::milliseconds(5000))); + EXPECT_CALL(*resolver.timer_, enableTimer(std::chrono::milliseconds(5000), _)); resolver.dns_callback_( TestUtility::makeDnsResponse({"192.168.1.1", "192.168.1.2"}, std::chrono::seconds(5))); } @@ -944,7 +942,7 @@ TEST_F(StrictDnsClusterImplTest, DefaultTtlAsDnsRefreshRateWhenResponseEmpty) { std::move(scope), false); cluster.initialize([] {}); - EXPECT_CALL(*resolver.timer_, enableTimer(std::chrono::milliseconds(4000))); + EXPECT_CALL(*resolver.timer_, enableTimer(std::chrono::milliseconds(4000), _)); resolver.dns_callback_(TestUtility::makeDnsResponse({}, std::chrono::seconds(5))); } @@ -2094,7 +2092,8 @@ class TestNetworkFilterConfigFactory return parent_.createEmptyProtocolOptionsProto(); } Upstream::ProtocolOptionsConfigConstSharedPtr - createProtocolOptionsConfig(const Protobuf::Message& msg) override { + createProtocolOptionsConfig(const Protobuf::Message& msg, + ProtobufMessage::ValidationVisitor&) override { return parent_.createProtocolOptionsConfig(msg); } std::string name() override { CONSTRUCT_ON_FIRST_USE(std::string, "envoy.test.filter"); } @@ -2130,7 +2129,8 @@ class TestHttpFilterConfigFactory : public Server::Configuration::NamedHttpFilte return parent_.createEmptyProtocolOptionsProto(); } Upstream::ProtocolOptionsConfigConstSharedPtr - createProtocolOptionsConfig(const Protobuf::Message& msg) override { + createProtocolOptionsConfig(const Protobuf::Message& msg, + ProtobufMessage::ValidationVisitor&) override { return parent_.createProtocolOptionsConfig(msg); } std::string name() override { CONSTRUCT_ON_FIRST_USE(std::string, "envoy.test.filter"); } diff --git a/test/common/upstream/utility.h b/test/common/upstream/utility.h index b5da2071d0..b41b9cbfd2 100644 --- a/test/common/upstream/utility.h +++ b/test/common/upstream/utility.h @@ -15,8 +15,7 @@ namespace Envoy { namespace Upstream { namespace { -inline std::string defaultStaticClusterJson(const std::string& name) { - return fmt::sprintf(R"EOF( +constexpr static const char* kDefaultStaticClusterTmpl = R"EOF( { "name": "%s", "connect_timeout": "0.250s", @@ -24,15 +23,18 @@ inline std::string defaultStaticClusterJson(const std::string& name) { "lb_policy": "round_robin", "hosts": [ { - "socket_address": { - "address": "127.0.0.1", - "port_value": 11001 - } + %s, } ] } - )EOF", - name); + )EOF"; + +inline std::string defaultStaticClusterJson(const std::string& name) { + return fmt::sprintf(kDefaultStaticClusterTmpl, name, R"EOF( +"socket_address": { + "address": "127.0.0.1", + "port_value": 11001 +})EOF"); } inline envoy::config::bootstrap::v2::Bootstrap diff --git a/test/config/integration/server.yaml b/test/config/integration/server.yaml index 88d3404961..e26cc5cf5e 100644 --- a/test/config/integration/server.yaml +++ b/test/config/integration/server.yaml @@ -79,27 +79,6 @@ static_resources: catch_all_route: cluster: redis clusters: - - name: cds - connect_timeout: 5s - hosts: - - socket_address: - address: {{ ip_loopback_address }} - port_value: 4 - dns_lookup_family: "{{ dns_lookup_family }}" - - name: rds - connect_timeout: 5s - hosts: - - socket_address: - address: {{ ip_loopback_address }} - port_value: 4 - dns_lookup_family: "{{ dns_lookup_family }}" - - name: lds - connect_timeout: 5s - hosts: - - socket_address: - address: {{ ip_loopback_address }} - port_value: 4 - dns_lookup_family: "{{ dns_lookup_family }}" - name: cluster_1 connect_timeout: 5s hosts: @@ -141,19 +120,7 @@ static_resources: port_value: 4 dns_lookup_family: "{{ dns_lookup_family }}" outlier_detection: {} -dynamic_resources: - lds_config: - api_config_source: - api_type: REST - cluster_names: - - lds - refresh_delay: 30s - cds_config: - api_config_source: - api_type: REST - cluster_names: - - cds - refresh_delay: 30s +dynamic_resources: {} cluster_manager: {} flags_path: "/invalid_flags" stats_sinks: @@ -168,10 +135,19 @@ stats_sinks: "@type": type.googleapis.com/envoy.config.metrics.v2.StatsdSink tcp_cluster_name: statsd watchdog: {} -runtime: - symlink_root: "{{ test_tmpdir }}/test/common/runtime/test_data/current" - subdirectory: envoy - override_subdirectory: envoy_override +layered_runtime: + layers: + - name: root + disk_layer: + symlink_root: "{{ test_tmpdir }}/test/common/runtime/test_data/current" + subdirectory: envoy + - name: override + disk_layer: + symlink_root: "{{ test_tmpdir }}/test/common/runtime/test_data/current" + subdirectory: envoy_override + append_service_cluster: true + - name: admin + admin_layer: {} admin: access_log_path: "/dev/null" profile_path: "{{ test_tmpdir }}/envoy.prof" diff --git a/test/config/utility.cc b/test/config/utility.cc index 11efd7a79d..c230b4c772 100644 --- a/test/config/utility.cc +++ b/test/config/utility.cc @@ -365,8 +365,8 @@ void ConfigHelper::finalize(const std::vector& ports) { *cluster->mutable_transport_socket(), tls_config); } } - ASSERT(port_idx == ports.size() || eds_hosts || original_dst_cluster || custom_cluster || - bootstrap_.dynamic_resources().has_cds_config()); + ASSERT(skip_port_usage_validation_ || port_idx == ports.size() || eds_hosts || + original_dst_cluster || custom_cluster || bootstrap_.dynamic_resources().has_cds_config()); if (!connect_timeout_set_) { #ifdef __APPLE__ diff --git a/test/config/utility.h b/test/config/utility.h index 0abf01c3bf..574160651a 100644 --- a/test/config/utility.h +++ b/test/config/utility.h @@ -158,6 +158,10 @@ class ConfigHelper { // Allow a finalized configuration to be edited for generating xDS responses void applyConfigModifiers(); + // Skip validation that ensures that all upstream ports are referenced by the + // configuration generated in ConfigHelper::finalize. + void skipPortUsageValidation() { skip_port_usage_validation_ = true; } + private: // Load the first HCM struct from the first listener into a parsed proto. bool loadHttpConnectionManager( @@ -186,6 +190,11 @@ class ConfigHelper { // default). bool connect_timeout_set_{false}; + // Option to disable port usage validation for cases where the number of + // upstream ports created is expected to be larger than the number of + // upstreams in the config. + bool skip_port_usage_validation_{false}; + // A sanity check guard to make sure config is not modified after handing it to Envoy. bool finalized_{false}; }; diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index d7311ec8c1..72c86b235b 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -65,6 +65,7 @@ class ConfigTest { .WillByDefault(Invoke([&](const std::string& file) -> std::string { return api_->fileSystem().fileReadToEnd(file); })); + ON_CALL(os_sys_calls_, close(_)).WillByDefault(Return(Api::SysCallIntResult{0, 0})); // Here we setup runtime to mimic the actual deprecated feature list used in the // production code. Note that this test is actually more strict than production because diff --git a/test/config_test/example_configs_test.cc b/test/config_test/example_configs_test.cc index 6da6d5e55f..fef29e4411 100644 --- a/test/config_test/example_configs_test.cc +++ b/test/config_test/example_configs_test.cc @@ -5,7 +5,7 @@ #include "gtest/gtest.h" namespace Envoy { -TEST(ExampleConfigsTest, All) { +TEST(ExampleConfigsTest, DEPRECATED_FEATURE_TEST(All)) { TestEnvironment::exec( {TestEnvironment::runfilesPath("test/config_test/example_configs_test_setup.sh")}); diff --git a/test/dependencies/BUILD b/test/dependencies/BUILD new file mode 100644 index 0000000000..2e6ae296b7 --- /dev/null +++ b/test/dependencies/BUILD @@ -0,0 +1,17 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_package", +) + +envoy_package() + +envoy_cc_test( + name = "curl_test", + srcs = ["curl_test.cc"], + external_deps = [ + "curl", + ], +) diff --git a/test/dependencies/curl_test.cc b/test/dependencies/curl_test.cc new file mode 100644 index 0000000000..82fdeceeec --- /dev/null +++ b/test/dependencies/curl_test.cc @@ -0,0 +1,32 @@ +#include "curl/curl.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Dependencies { + +TEST(CurlTest, BuiltWithExpectedFeatures) { + // Ensure built with the expected features, flags from + // https://curl.haxx.se/libcurl/c/curl_version_info.html. + curl_version_info_data* info = curl_version_info(CURLVERSION_NOW); + + EXPECT_NE(0, info->features & CURL_VERSION_ASYNCHDNS); + EXPECT_NE(0, info->ares_num); + EXPECT_NE(0, info->features & CURL_VERSION_HTTP2); + EXPECT_NE(0, info->features & CURL_VERSION_LIBZ); + EXPECT_NE(0, info->features & CURL_VERSION_IPV6); + EXPECT_NE(0, info->features & CURL_VERSION_UNIX_SOCKETS); + + EXPECT_EQ(0, info->features & CURL_VERSION_BROTLI); + EXPECT_EQ(0, info->features & CURL_VERSION_GSSAPI); + EXPECT_EQ(0, info->features & CURL_VERSION_GSSNEGOTIATE); + EXPECT_EQ(0, info->features & CURL_VERSION_KERBEROS4); + EXPECT_EQ(0, info->features & CURL_VERSION_KERBEROS5); + EXPECT_EQ(0, info->features & CURL_VERSION_NTLM); + EXPECT_EQ(0, info->features & CURL_VERSION_NTLM_WB); + EXPECT_EQ(0, info->features & CURL_VERSION_SPNEGO); + EXPECT_EQ(0, info->features & CURL_VERSION_SSL); + EXPECT_EQ(0, info->features & CURL_VERSION_SSPI); +} + +} // namespace Dependencies +} // namespace Envoy diff --git a/test/exe/BUILD b/test/exe/BUILD index 12235850ca..8ed8e3334d 100644 --- a/test/exe/BUILD +++ b/test/exe/BUILD @@ -24,9 +24,8 @@ envoy_sh_test( srcs = ["envoy_static_test.sh"], coverage = False, data = ["//source/exe:envoy-static"], - # NOTE: In some environments, ASAN causes dynamic linking no matter what, so don't run this - # test when doing ASAN. - tags = ["no_asan"], + # Sanitizers doesn't like statically linked lib(std)c++ and libgcc, skip this test in that context. + tags = ["no_san"], ) envoy_sh_test( diff --git a/test/exe/main_common_test.cc b/test/exe/main_common_test.cc index 4dadcb3187..7af5aee32c 100644 --- a/test/exe/main_common_test.cc +++ b/test/exe/main_common_test.cc @@ -240,7 +240,7 @@ class AdminRequestTest : public MainCommonTest { TEST_P(AdminRequestTest, AdminRequestGetStatsAndQuit) { startEnvoy(); started_.WaitForNotification(); - EXPECT_THAT(adminRequest("/stats", "GET"), HasSubstr("access_log_file.reopen_failed")); + EXPECT_THAT(adminRequest("/stats", "GET"), HasSubstr("filesystem.reopen_failed")); adminRequest("/quitquitquit", "POST"); EXPECT_TRUE(waitForEnvoyToExit()); } @@ -253,7 +253,7 @@ TEST_P(AdminRequestTest, AdminRequestGetStatsAndKill) { // TODO(htuch): Remove when https://github.com/libevent/libevent/issues/779 is // fixed, started_ will then become our real synchronization point. waitForEnvoyRun(); - EXPECT_THAT(adminRequest("/stats", "GET"), HasSubstr("access_log_file.reopen_failed")); + EXPECT_THAT(adminRequest("/stats", "GET"), HasSubstr("filesystem.reopen_failed")); kill(getpid(), SIGTERM); EXPECT_TRUE(waitForEnvoyToExit()); } @@ -266,7 +266,7 @@ TEST_P(AdminRequestTest, AdminRequestGetStatsAndCtrlC) { // TODO(htuch): Remove when https://github.com/libevent/libevent/issues/779 is // fixed, started_ will then become our real synchronization point. waitForEnvoyRun(); - EXPECT_THAT(adminRequest("/stats", "GET"), HasSubstr("access_log_file.reopen_failed")); + EXPECT_THAT(adminRequest("/stats", "GET"), HasSubstr("filesystem.reopen_failed")); kill(getpid(), SIGINT); EXPECT_TRUE(waitForEnvoyToExit()); } @@ -335,7 +335,7 @@ TEST_P(AdminRequestTest, AdminRequestBeforeRun) { EXPECT_TRUE(admin_handler_was_called); // This just checks that some stat output was reported. We could pick any stat. - EXPECT_THAT(out, HasSubstr("access_log_file.reopen_failed")); + EXPECT_THAT(out, HasSubstr("filesystem.reopen_failed")); } // Class to track whether an object has been destroyed, which it does by bumping an atomic. diff --git a/test/extensions/access_loggers/file/BUILD b/test/extensions/access_loggers/file/BUILD index 3feb9c7509..461c4d5bda 100644 --- a/test/extensions/access_loggers/file/BUILD +++ b/test/extensions/access_loggers/file/BUILD @@ -18,5 +18,6 @@ envoy_extension_cc_test( deps = [ "//source/extensions/access_loggers/file:config", "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", ], ) diff --git a/test/extensions/access_loggers/grpc/BUILD b/test/extensions/access_loggers/grpc/BUILD index 9e2c7b4624..652fc010d5 100644 --- a/test/extensions/access_loggers/grpc/BUILD +++ b/test/extensions/access_loggers/grpc/BUILD @@ -77,3 +77,20 @@ envoy_extension_cc_test( "//test/test_common:utility_lib", ], ) + +envoy_extension_cc_test( + name = "tcp_grpc_access_log_integration_test", + srcs = ["tcp_grpc_access_log_integration_test.cc"], + extension_name = "envoy.access_loggers.http_grpc", + deps = [ + "//source/common/buffer:zero_copy_input_stream_lib", + "//source/common/grpc:codec_lib", + "//source/common/grpc:common_lib", + "//source/extensions/access_loggers/grpc:http_config", + "//source/extensions/access_loggers/grpc:tcp_config", + "//source/extensions/filters/network/tcp_proxy:config", + "//test/common/grpc:grpc_client_integration_lib", + "//test/integration:http_integration_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/access_loggers/grpc/grpc_access_log_impl_test.cc b/test/extensions/access_loggers/grpc/grpc_access_log_impl_test.cc index 571d29fe48..77c9f42554 100644 --- a/test/extensions/access_loggers/grpc/grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/grpc/grpc_access_log_impl_test.cc @@ -16,7 +16,6 @@ using testing::_; using testing::InSequence; using testing::Invoke; using testing::NiceMock; -using testing::Return; namespace Envoy { namespace Extensions { @@ -34,7 +33,7 @@ class GrpcAccessLoggerImplTest : public testing::Test { void initLogger(std::chrono::milliseconds buffer_flush_interval_msec, size_t buffer_size_bytes) { timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*timer_, enableTimer(buffer_flush_interval_msec)); + EXPECT_CALL(*timer_, enableTimer(buffer_flush_interval_msec, _)); logger_ = std::make_unique(Grpc::RawAsyncClientPtr{async_client_}, log_name_, buffer_flush_interval_msec, buffer_size_bytes, dispatcher_, local_info_); @@ -202,7 +201,7 @@ TEST_F(GrpcAccessLoggerImplTest, Flushing) { initLogger(FlushInterval, 100); // Nothing to do yet. - EXPECT_CALL(*timer_, enableTimer(FlushInterval)); + EXPECT_CALL(*timer_, enableTimer(FlushInterval, _)); timer_->invokeCallback(); envoy::data::accesslog::v2::HTTPAccessLogEntry entry; @@ -227,11 +226,11 @@ TEST_F(GrpcAccessLoggerImplTest, Flushing) { - request: path: /test/path1 )EOF")); - EXPECT_CALL(*timer_, enableTimer(FlushInterval)); + EXPECT_CALL(*timer_, enableTimer(FlushInterval, _)); timer_->invokeCallback(); // Flush on empty message does nothing. - EXPECT_CALL(*timer_, enableTimer(FlushInterval)); + EXPECT_CALL(*timer_, enableTimer(FlushInterval, _)); timer_->invokeCallback(); } @@ -271,21 +270,26 @@ TEST_F(GrpcAccessLoggerCacheImplTest, Deduplication) { config.mutable_grpc_service()->mutable_envoy_grpc()->set_cluster_name("cluster-1"); expectClientCreation(); - GrpcAccessLoggerSharedPtr logger1 = logger_cache_->getOrCreateLogger(config); - EXPECT_EQ(logger1, logger_cache_->getOrCreateLogger(config)); + GrpcAccessLoggerSharedPtr logger1 = + logger_cache_->getOrCreateLogger(config, GrpcAccessLoggerType::HTTP); + EXPECT_EQ(logger1, logger_cache_->getOrCreateLogger(config, GrpcAccessLoggerType::HTTP)); + + // Do not deduplicate different types of logger + expectClientCreation(); + EXPECT_NE(logger1, logger_cache_->getOrCreateLogger(config, GrpcAccessLoggerType::TCP)); // Changing log name leads to another logger. config.set_log_name("log-2"); expectClientCreation(); - EXPECT_NE(logger1, logger_cache_->getOrCreateLogger(config)); + EXPECT_NE(logger1, logger_cache_->getOrCreateLogger(config, GrpcAccessLoggerType::HTTP)); config.set_log_name("log-1"); - EXPECT_EQ(logger1, logger_cache_->getOrCreateLogger(config)); + EXPECT_EQ(logger1, logger_cache_->getOrCreateLogger(config, GrpcAccessLoggerType::HTTP)); // Changing cluster name leads to another logger. config.mutable_grpc_service()->mutable_envoy_grpc()->set_cluster_name("cluster-2"); expectClientCreation(); - EXPECT_NE(logger1, logger_cache_->getOrCreateLogger(config)); + EXPECT_NE(logger1, logger_cache_->getOrCreateLogger(config, GrpcAccessLoggerType::HTTP)); } } // namespace diff --git a/test/extensions/access_loggers/grpc/http_config_test.cc b/test/extensions/access_loggers/grpc/http_config_test.cc index 7cd5bbc00f..2a23658a91 100644 --- a/test/extensions/access_loggers/grpc/http_config_test.cc +++ b/test/extensions/access_loggers/grpc/http_config_test.cc @@ -12,7 +12,6 @@ using testing::_; using testing::Invoke; -using testing::Return; namespace Envoy { namespace Extensions { diff --git a/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc b/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc index 22e1f7cd59..cee4378e4a 100644 --- a/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc +++ b/test/extensions/access_loggers/grpc/http_grpc_access_log_impl_test.cc @@ -14,10 +14,12 @@ using namespace std::chrono_literals; using testing::_; +using testing::An; using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Return; +using testing::ReturnRef; namespace Envoy { namespace Extensions { @@ -25,18 +27,22 @@ namespace AccessLoggers { namespace HttpGrpc { namespace { +using envoy::data::accesslog::v2::HTTPAccessLogEntry; + class MockGrpcAccessLogger : public GrpcCommon::GrpcAccessLogger { public: // GrpcAccessLogger - MOCK_METHOD1(log, void(envoy::data::accesslog::v2::HTTPAccessLogEntry&& entry)); + MOCK_METHOD1(log, void(HTTPAccessLogEntry&& entry)); + MOCK_METHOD1(log, void(envoy::data::accesslog::v2::TCPAccessLogEntry&& entry)); }; class MockGrpcAccessLoggerCache : public GrpcCommon::GrpcAccessLoggerCache { public: // GrpcAccessLoggerCache - MOCK_METHOD1(getOrCreateLogger, + MOCK_METHOD2(getOrCreateLogger, GrpcCommon::GrpcAccessLoggerSharedPtr( - const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config)); + const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config, + GrpcCommon::GrpcAccessLoggerType logger_type)); }; class HttpGrpcAccessLogTest : public testing::Test { @@ -44,9 +50,11 @@ class HttpGrpcAccessLogTest : public testing::Test { void init() { ON_CALL(*filter_, evaluate(_, _, _, _)).WillByDefault(Return(true)); config_.mutable_common_config()->set_log_name("hello_log"); - EXPECT_CALL(*logger_cache_, getOrCreateLogger(_)) - .WillOnce([this](const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config) { + EXPECT_CALL(*logger_cache_, getOrCreateLogger(_, _)) + .WillOnce([this](const ::envoy::config::accesslog::v2::CommonGrpcAccessLogConfig& config, + GrpcCommon::GrpcAccessLoggerType logger_type) { EXPECT_EQ(config.DebugString(), config_.common_config().DebugString()); + EXPECT_EQ(GrpcCommon::GrpcAccessLoggerType::HTTP, logger_type); return logger_; }); access_log_ = std::make_unique(AccessLog::FilterPtr{filter_}, config_, tls_, @@ -58,9 +66,9 @@ class HttpGrpcAccessLogTest : public testing::Test { init(); } - envoy::data::accesslog::v2::HTTPAccessLogEntry expected_log_entry; + HTTPAccessLogEntry expected_log_entry; TestUtility::loadFromYaml(expected_log_entry_yaml, expected_log_entry); - EXPECT_CALL(*logger_, log(_)) + EXPECT_CALL(*logger_, log(An())) .WillOnce( Invoke([expected_log_entry](envoy::data::accesslog::v2::HTTPAccessLogEntry&& entry) { EXPECT_EQ(entry.DebugString(), expected_log_entry.DebugString()); @@ -298,18 +306,22 @@ response: {} stream_info.host_ = nullptr; stream_info.start_time_ = SystemTime(1h); - NiceMock connection_info; + auto connection_info = std::make_shared>(); const std::vector peerSans{"peerSan1", "peerSan2"}; - ON_CALL(connection_info, uriSanPeerCertificate()).WillByDefault(Return(peerSans)); + ON_CALL(*connection_info, uriSanPeerCertificate()).WillByDefault(Return(peerSans)); const std::vector localSans{"localSan1", "localSan2"}; - ON_CALL(connection_info, uriSanLocalCertificate()).WillByDefault(Return(localSans)); - ON_CALL(connection_info, subjectPeerCertificate()).WillByDefault(Return("peerSubject")); - ON_CALL(connection_info, subjectLocalCertificate()).WillByDefault(Return("localSubject")); - ON_CALL(connection_info, sessionId()) - .WillByDefault(Return("D62A523A65695219D46FE1FFE285A4C371425ACE421B110B5B8D11D3EB4D5F0B")); - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("TLSv1.3")); - ON_CALL(connection_info, ciphersuiteId()).WillByDefault(Return(0x2CC0)); - stream_info.setDownstreamSslConnection(&connection_info); + ON_CALL(*connection_info, uriSanLocalCertificate()).WillByDefault(Return(localSans)); + const std::string peerSubject = "peerSubject"; + ON_CALL(*connection_info, subjectPeerCertificate()).WillByDefault(ReturnRef(peerSubject)); + const std::string localSubject = "localSubject"; + ON_CALL(*connection_info, subjectLocalCertificate()).WillByDefault(ReturnRef(localSubject)); + const std::string sessionId = + "D62A523A65695219D46FE1FFE285A4C371425ACE421B110B5B8D11D3EB4D5F0B"; + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(sessionId)); + const std::string tlsVersion = "TLSv1.3"; + ON_CALL(*connection_info, tlsVersion()).WillByDefault(ReturnRef(tlsVersion)); + ON_CALL(*connection_info, ciphersuiteId()).WillByDefault(Return(0x2CC0)); + stream_info.setDownstreamSslConnection(connection_info); stream_info.requested_server_name_ = "sni"; Http::TestHeaderMapImpl request_headers{ @@ -357,10 +369,15 @@ response: {} stream_info.host_ = nullptr; stream_info.start_time_ = SystemTime(1h); - NiceMock connection_info; - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("TLSv1.2")); - ON_CALL(connection_info, ciphersuiteId()).WillByDefault(Return(0x2F)); - stream_info.setDownstreamSslConnection(&connection_info); + auto connection_info = std::make_shared>(); + const std::string empty; + ON_CALL(*connection_info, subjectPeerCertificate()).WillByDefault(ReturnRef(empty)); + ON_CALL(*connection_info, subjectLocalCertificate()).WillByDefault(ReturnRef(empty)); + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(empty)); + const std::string tlsVersion = "TLSv1.2"; + ON_CALL(*connection_info, tlsVersion()).WillByDefault(ReturnRef(tlsVersion)); + ON_CALL(*connection_info, ciphersuiteId()).WillByDefault(Return(0x2F)); + stream_info.setDownstreamSslConnection(connection_info); stream_info.requested_server_name_ = "sni"; Http::TestHeaderMapImpl request_headers{ @@ -398,10 +415,15 @@ response: {} stream_info.host_ = nullptr; stream_info.start_time_ = SystemTime(1h); - NiceMock connection_info; - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("TLSv1.1")); - ON_CALL(connection_info, ciphersuiteId()).WillByDefault(Return(0x2F)); - stream_info.setDownstreamSslConnection(&connection_info); + auto connection_info = std::make_shared>(); + const std::string empty; + ON_CALL(*connection_info, subjectPeerCertificate()).WillByDefault(ReturnRef(empty)); + ON_CALL(*connection_info, subjectLocalCertificate()).WillByDefault(ReturnRef(empty)); + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(empty)); + const std::string tlsVersion = "TLSv1.1"; + ON_CALL(*connection_info, tlsVersion()).WillByDefault(ReturnRef(tlsVersion)); + ON_CALL(*connection_info, ciphersuiteId()).WillByDefault(Return(0x2F)); + stream_info.setDownstreamSslConnection(connection_info); stream_info.requested_server_name_ = "sni"; Http::TestHeaderMapImpl request_headers{ @@ -439,10 +461,15 @@ response: {} stream_info.host_ = nullptr; stream_info.start_time_ = SystemTime(1h); - NiceMock connection_info; - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("TLSv1")); - ON_CALL(connection_info, ciphersuiteId()).WillByDefault(Return(0x2F)); - stream_info.setDownstreamSslConnection(&connection_info); + auto connection_info = std::make_shared>(); + const std::string empty; + ON_CALL(*connection_info, subjectPeerCertificate()).WillByDefault(ReturnRef(empty)); + ON_CALL(*connection_info, subjectLocalCertificate()).WillByDefault(ReturnRef(empty)); + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(empty)); + const std::string tlsVersion = "TLSv1"; + ON_CALL(*connection_info, tlsVersion()).WillByDefault(ReturnRef(tlsVersion)); + ON_CALL(*connection_info, ciphersuiteId()).WillByDefault(Return(0x2F)); + stream_info.setDownstreamSslConnection(connection_info); stream_info.requested_server_name_ = "sni"; Http::TestHeaderMapImpl request_headers{ @@ -480,10 +507,15 @@ response: {} stream_info.host_ = nullptr; stream_info.start_time_ = SystemTime(1h); - NiceMock connection_info; - ON_CALL(connection_info, tlsVersion()).WillByDefault(Return("TLSv1.4")); - ON_CALL(connection_info, ciphersuiteId()).WillByDefault(Return(0x2F)); - stream_info.setDownstreamSslConnection(&connection_info); + auto connection_info = std::make_shared>(); + const std::string empty; + ON_CALL(*connection_info, subjectPeerCertificate()).WillByDefault(ReturnRef(empty)); + ON_CALL(*connection_info, subjectLocalCertificate()).WillByDefault(ReturnRef(empty)); + ON_CALL(*connection_info, sessionId()).WillByDefault(ReturnRef(empty)); + const std::string tlsVersion = "TLSv1.4"; + ON_CALL(*connection_info, tlsVersion()).WillByDefault(ReturnRef(tlsVersion)); + ON_CALL(*connection_info, ciphersuiteId()).WillByDefault(Return(0x2F)); + stream_info.setDownstreamSslConnection(connection_info); stream_info.requested_server_name_ = "sni"; Http::TestHeaderMapImpl request_headers{ diff --git a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc new file mode 100644 index 0000000000..8ad6bbe7bb --- /dev/null +++ b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc @@ -0,0 +1,189 @@ +#include "envoy/config/accesslog/v2/als.pb.h" +#include "envoy/config/filter/network/tcp_proxy/v2/tcp_proxy.pb.validate.h" +#include "envoy/service/accesslog/v2/als.pb.h" + +#include "common/buffer/zero_copy_input_stream_impl.h" +#include "common/common/version.h" +#include "common/grpc/codec.h" +#include "common/grpc/common.h" + +#include "test/common/grpc/grpc_client_integration.h" +#include "test/integration/http_integration.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +using testing::AssertionResult; + +namespace Envoy { +namespace { + +void clearPort(envoy::api::v2::core::Address& address) { + address.mutable_socket_address()->clear_port_specifier(); +} + +class TcpGrpcAccessLogIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, + public BaseIntegrationTest { +public: + TcpGrpcAccessLogIntegrationTest() + : BaseIntegrationTest(ipVersion(), ConfigHelper::TCP_PROXY_CONFIG) { + enable_half_close_ = true; + } + + ~TcpGrpcAccessLogIntegrationTest() override { + test_server_.reset(); + fake_upstreams_.clear(); + } + + void createUpstreams() override { + BaseIntegrationTest::createUpstreams(); + fake_upstreams_.emplace_back( + new FakeUpstream(0, FakeHttpConnection::Type::HTTP2, version_, timeSystem())); + } + + void initialize() override { + config_helper_.renameListener("tcp_proxy"); + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* accesslog_cluster = bootstrap.mutable_static_resources()->add_clusters(); + accesslog_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + accesslog_cluster->set_name("accesslog"); + accesslog_cluster->mutable_http2_protocol_options(); + }); + + config_helper_.addConfigModifier([this](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* listener = bootstrap.mutable_static_resources()->mutable_listeners(0); + auto* filter_chain = listener->mutable_filter_chains(0); + auto* config_blob = filter_chain->mutable_filters(0)->mutable_config(); + + envoy::config::filter::network::tcp_proxy::v2::TcpProxy tcp_proxy_config; + TestUtility::jsonConvert(*config_blob, tcp_proxy_config); + + auto* access_log = tcp_proxy_config.add_access_log(); + access_log->set_name("envoy.tcp_grpc_access_log"); + envoy::config::accesslog::v2::TcpGrpcAccessLogConfig access_log_config; + auto* common_config = access_log_config.mutable_common_config(); + common_config->set_log_name("foo"); + setGrpcService(*common_config->mutable_grpc_service(), "accesslog", + fake_upstreams_.back()->localAddress()); + TestUtility::jsonConvert(access_log_config, *access_log->mutable_config()); + + TestUtility::jsonConvert(tcp_proxy_config, *config_blob); + }); + BaseIntegrationTest::initialize(); + } + + ABSL_MUST_USE_RESULT + AssertionResult waitForAccessLogConnection() { + return fake_upstreams_[1]->waitForHttpConnection(*dispatcher_, fake_access_log_connection_); + } + + ABSL_MUST_USE_RESULT + AssertionResult waitForAccessLogStream() { + return fake_access_log_connection_->waitForNewStream(*dispatcher_, access_log_request_); + } + + ABSL_MUST_USE_RESULT + AssertionResult waitForAccessLogRequest(const std::string& expected_request_msg_yaml) { + envoy::service::accesslog::v2::StreamAccessLogsMessage request_msg; + VERIFY_ASSERTION(access_log_request_->waitForGrpcMessage(*dispatcher_, request_msg)); + EXPECT_EQ("POST", access_log_request_->headers().Method()->value().getStringView()); + EXPECT_EQ("/envoy.service.accesslog.v2.AccessLogService/StreamAccessLogs", + access_log_request_->headers().Path()->value().getStringView()); + EXPECT_EQ("application/grpc", + access_log_request_->headers().ContentType()->value().getStringView()); + + envoy::service::accesslog::v2::StreamAccessLogsMessage expected_request_msg; + TestUtility::loadFromYaml(expected_request_msg_yaml, expected_request_msg); + + // Clear fields which are not deterministic. + auto* log_entry = request_msg.mutable_tcp_logs()->mutable_log_entry(0); + clearPort(*log_entry->mutable_common_properties()->mutable_downstream_remote_address()); + clearPort(*log_entry->mutable_common_properties()->mutable_downstream_local_address()); + clearPort(*log_entry->mutable_common_properties()->mutable_upstream_remote_address()); + clearPort(*log_entry->mutable_common_properties()->mutable_upstream_local_address()); + log_entry->mutable_common_properties()->clear_start_time(); + log_entry->mutable_common_properties()->clear_time_to_last_rx_byte(); + log_entry->mutable_common_properties()->clear_time_to_first_downstream_tx_byte(); + log_entry->mutable_common_properties()->clear_time_to_last_downstream_tx_byte(); + EXPECT_EQ(request_msg.DebugString(), expected_request_msg.DebugString()); + + return AssertionSuccess(); + } + + void cleanup() { + if (fake_access_log_connection_ != nullptr) { + AssertionResult result = fake_access_log_connection_->close(); + RELEASE_ASSERT(result, result.message()); + result = fake_access_log_connection_->waitForDisconnect(); + RELEASE_ASSERT(result, result.message()); + fake_access_log_connection_ = nullptr; + } + } + + FakeHttpConnectionPtr fake_access_log_connection_; + FakeStreamPtr access_log_request_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersionsCientType, TcpGrpcAccessLogIntegrationTest, + GRPC_CLIENT_INTEGRATION_PARAMS); + +// Test a basic full access logging flow. +TEST_P(TcpGrpcAccessLogIntegrationTest, BasicAccessLogFlow) { + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + + ASSERT_TRUE(fake_upstream_connection->write("hello")); + tcp_client->waitForData("hello"); + tcp_client->write("bar", false); + + ASSERT_TRUE(fake_upstream_connection->write("", true)); + tcp_client->waitForHalfClose(); + tcp_client->write("", true); + ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); + ASSERT_TRUE(fake_upstream_connection->waitForDisconnect()); + + ASSERT_TRUE(waitForAccessLogConnection()); + ASSERT_TRUE(waitForAccessLogStream()); + ASSERT_TRUE(waitForAccessLogRequest( + fmt::format(R"EOF( +identifier: + node: + id: node_name + cluster: cluster_name + locality: + zone: zone_name + build_version: {} + log_name: foo +tcp_logs: + log_entry: + common_properties: + downstream_remote_address: + socket_address: + address: {} + downstream_local_address: + socket_address: + address: {} + upstream_remote_address: + socket_address: + address: {} + upstream_local_address: + socket_address: + address: {} + upstream_cluster: cluster_0 + connection_properties: + received_bytes: 3 + sent_bytes: 5 +)EOF", + VersionInfo::version(), Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion()), + Network::Test::getLoopbackAddressString(ipVersion())))); + + cleanup(); +} + +} // namespace +} // namespace Envoy diff --git a/test/extensions/clusters/redis/mocks.cc b/test/extensions/clusters/redis/mocks.cc index b66ddd7c05..f0ae690f29 100644 --- a/test/extensions/clusters/redis/mocks.cc +++ b/test/extensions/clusters/redis/mocks.cc @@ -1,10 +1,7 @@ #include "test/extensions/clusters/redis/mocks.h" using testing::_; -using testing::Invoke; using testing::Return; -using testing::ReturnPointee; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/clusters/redis/redis_cluster_integration_test.cc b/test/extensions/clusters/redis/redis_cluster_integration_test.cc index 91aecc412a..bb1bfbd53d 100644 --- a/test/extensions/clusters/redis/redis_cluster_integration_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_integration_test.cc @@ -15,8 +15,7 @@ namespace { // This is a basic redis_proxy configuration with a single host // in the cluster. The load balancing policy must be set // to random for proper test operation. - -const std::string& testConfig() { +const std::string& listenerConfig() { CONSTRUCT_ON_FIRST_USE(std::string, R"EOF( admin: access_log_path: /dev/null @@ -36,9 +35,15 @@ const std::string& testConfig() { name: envoy.redis_proxy config: stat_prefix: redis_stats - cluster: cluster_0 + prefix_routes: + catch_all_route: + cluster: cluster_0 settings: - op_timeout: 5s + op_timeout: 5s)EOF"); +} + +const std::string& clusterConfig() { + CONSTRUCT_ON_FIRST_USE(std::string, R"EOF( clusters: - name: cluster_0 lb_policy: CLUSTER_PROVIDED @@ -56,6 +61,16 @@ const std::string& testConfig() { )EOF"); } +const std::string& testConfig() { + CONSTRUCT_ON_FIRST_USE(std::string, listenerConfig() + clusterConfig()); +} + +const std::string& testConfigWithReadPolicy() { + CONSTRUCT_ON_FIRST_USE(std::string, listenerConfig() + R"EOF( + read_policy: REPLICA +)EOF" + clusterConfig()); +} + // This is the basic redis_proxy configuration with an upstream // authentication password specified. @@ -276,6 +291,13 @@ class RedisClusterWithAuthIntegrationTest : public RedisClusterIntegrationTest { : RedisClusterIntegrationTest(config, num_upstreams) {} }; +class RedisClusterWithReadPolicyIntegrationTest : public RedisClusterIntegrationTest { +public: + RedisClusterWithReadPolicyIntegrationTest(const std::string& config = testConfigWithReadPolicy(), + int num_upstreams = 3) + : RedisClusterIntegrationTest(config, num_upstreams) {} +}; + INSTANTIATE_TEST_SUITE_P(IpVersions, RedisClusterIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); @@ -331,6 +353,29 @@ TEST_P(RedisClusterIntegrationTest, TwoSlot) { simpleRequestAndResponse(1, makeBulkStringArray({"get", "foo"}), "$3\r\nbar\r\n"); } +// This test sends simple "set foo" and "get foo" command from a fake +// downstream client through the proxy to a fake upstream +// Redis cluster with a single slot with master and replica. +// The envoy proxy is set with read_policy to read from replica, the expected result +// is that the set command will be sent to the master and the get command will be sent +// to the replica + +TEST_P(RedisClusterWithReadPolicyIntegrationTest, SingleSlotMasterReplicaReadReplica) { + random_index_ = 0; + + on_server_init_function_ = [this]() { + std::string cluster_slot_response = singleSlotMasterReplica( + fake_upstreams_[0]->localAddress()->ip(), fake_upstreams_[1]->localAddress()->ip()); + expectCallClusterSlot(random_index_, cluster_slot_response); + }; + + initialize(); + + // foo hashes to slot 12182 which has master node in upstream 0 and replica in upstream 1 + simpleRequestAndResponse(0, makeBulkStringArray({"set", "foo", "bar"}), ":1\r\n"); + simpleRequestAndResponse(1, makeBulkStringArray({"get", "foo"}), "$3\r\nbar\r\n"); +} + // This test sends a simple "get foo" command from a fake // downstream client through the proxy to a fake upstream // Redis cluster with a single slot with master and replica. diff --git a/test/extensions/clusters/redis/redis_cluster_lb_test.cc b/test/extensions/clusters/redis/redis_cluster_lb_test.cc index dedcf8e51e..5e0ae78de9 100644 --- a/test/extensions/clusters/redis/redis_cluster_lb_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_lb_test.cc @@ -44,6 +44,7 @@ class RedisClusterLoadBalancerTest : public testing::Test { factory_ = std::make_shared(random_); lb_ = std::make_unique(factory_); lb_->initialize(); + factory_->onHostHealthUpdate(); } void validateAssignment(Upstream::HostVector& hosts, diff --git a/test/extensions/clusters/redis/redis_cluster_test.cc b/test/extensions/clusters/redis/redis_cluster_test.cc index 7d6a4967cc..1f51829ced 100644 --- a/test/extensions/clusters/redis/redis_cluster_test.cc +++ b/test/extensions/clusters/redis/redis_cluster_test.cc @@ -21,15 +21,10 @@ using testing::_; using testing::ContainerEq; -using testing::DoAll; using testing::Eq; -using testing::InvokeWithoutArgs; using testing::NiceMock; using testing::Ref; using testing::Return; -using testing::ReturnRef; -using testing::SaveArg; -using testing::WithArg; namespace Envoy { namespace Extensions { @@ -55,13 +50,17 @@ const std::string BasicConfig = R"EOF( )EOF"; } +static const int ResponseFlagSize = 11; +static const int ResponseReplicaFlagSize = 4; class RedisClusterTest : public testing::Test, public Extensions::NetworkFilters::Common::Redis::Client::ClientFactory { public: // ClientFactory Extensions::NetworkFilters::Common::Redis::Client::ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher&, - const Extensions::NetworkFilters::Common::Redis::Client::Config&) override { + const Extensions::NetworkFilters::Common::Redis::Client::Config&, + const Extensions::NetworkFilters::Common::Redis::RedisCommandStatsSharedPtr&, + Stats::Scope&, const std::string&) override { EXPECT_EQ(22120, host->address()->ip()->port()); return Extensions::NetworkFilters::Common::Redis::Client::ClientPtr{ create_(host->address()->asString())}; @@ -99,7 +98,7 @@ class RedisClusterTest : public testing::Test, cluster_callback_ = std::make_shared>(); cluster_.reset(new RedisCluster( cluster_config, - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( config), *this, cm, runtime_, *api_, dns_resolver_, factory_context, std::move(scope), false, cluster_callback_)); @@ -168,12 +167,12 @@ class RedisClusterTest : public testing::Test, } void expectClusterSlotResponse(NetworkFilters::Common::Redis::RespValuePtr&& response) { - EXPECT_CALL(*resolve_timer_, enableTimer(_)); + EXPECT_CALL(*resolve_timer_, enableTimer(_, _)); pool_callbacks_->onResponse(std::move(response)); } void expectClusterSlotFailure() { - EXPECT_CALL(*resolve_timer_, enableTimer(_)); + EXPECT_CALL(*resolve_timer_, enableTimer(_, _)); pool_callbacks_->onFailure(); } @@ -192,7 +191,7 @@ class RedisClusterTest : public testing::Test, replica_1[1].type(NetworkFilters::Common::Redis::RespType::Integer); replica_1[1].asInteger() = port; - std::vector slot_1(4); + std::vector slot_1(ResponseReplicaFlagSize); slot_1[0].type(NetworkFilters::Common::Redis::RespType::Integer); slot_1[0].asInteger() = 0; slot_1[1].type(NetworkFilters::Common::Redis::RespType::Integer); @@ -280,7 +279,7 @@ class RedisClusterTest : public testing::Test, replica_2[1].type(NetworkFilters::Common::Redis::RespType::Integer); replica_2[1].asInteger() = 22120; - std::vector slot_1(4); + std::vector slot_1(ResponseReplicaFlagSize); slot_1[0].type(NetworkFilters::Common::Redis::RespType::Integer); slot_1[0].asInteger() = 0; slot_1[1].type(NetworkFilters::Common::Redis::RespType::Integer); @@ -290,7 +289,7 @@ class RedisClusterTest : public testing::Test, slot_1[3].type(NetworkFilters::Common::Redis::RespType::Array); slot_1[3].asArray().swap(replica_1); - std::vector slot_2(4); + std::vector slot_2(ResponseReplicaFlagSize); slot_2[0].type(NetworkFilters::Common::Redis::RespType::Integer); slot_2[0].asInteger() = 10000; slot_2[1].type(NetworkFilters::Common::Redis::RespType::Integer); @@ -321,7 +320,7 @@ class RedisClusterTest : public testing::Test, respValue.asString() = correct_value; } else { respValue.type(NetworkFilters::Common::Redis::RespType::Integer); - respValue.asInteger() = 10; + respValue.asInteger() = ResponseFlagSize; } return respValue; } @@ -355,8 +354,9 @@ class RedisClusterTest : public testing::Test, // Create a redis cluster slot response. If a bit is set in the bitset, then that part of // of the response is correct, otherwise it's incorrect. - NetworkFilters::Common::Redis::RespValuePtr createResponse(std::bitset<10> flags, - std::bitset<3> replica_flags) const { + NetworkFilters::Common::Redis::RespValuePtr + createResponse(std::bitset flags, + std::bitset replica_flags) const { int64_t idx(0); int64_t slots_type = idx++; int64_t slots_size = idx++; @@ -367,16 +367,22 @@ class RedisClusterTest : public testing::Test, int64_t master_type = idx++; int64_t master_size = idx++; int64_t master_ip_type = idx++; + int64_t master_ip_value = idx++; int64_t master_port_type = idx++; idx = 0; int64_t replica_size = idx++; int64_t replica_ip_type = idx++; + int64_t replica_ip_value = idx++; int64_t replica_port_type = idx++; std::vector master_1_array; if (flags.test(master_size)) { // Ip field. - master_1_array.push_back(createStringField(flags.test(master_ip_type), "127.0.0.1")); + if (flags.test(master_ip_value)) { + master_1_array.push_back(createStringField(flags.test(master_ip_type), "127.0.0.1")); + } else { + master_1_array.push_back(createStringField(flags.test(master_ip_type), "bad ip foo")); + } // Port field. master_1_array.push_back(createIntegerField(flags.test(master_port_type), 22120)); } @@ -384,8 +390,13 @@ class RedisClusterTest : public testing::Test, std::vector replica_1_array; if (replica_flags.any()) { // Ip field. - replica_1_array.push_back( - createStringField(replica_flags.test(replica_ip_type), "127.0.0.2")); + if (replica_flags.test(replica_ip_value)) { + replica_1_array.push_back( + createStringField(replica_flags.test(replica_ip_type), "127.0.0.2")); + } else { + replica_1_array.push_back( + createStringField(replica_flags.test(replica_ip_type), "bad ip bar")); + } // Port field. replica_1_array.push_back(createIntegerField(replica_flags.test(replica_port_type), 22120)); } @@ -632,7 +643,7 @@ TEST_F(RedisClusterTest, EmptyDnsResponse) { Event::MockTimer* dns_timer = new NiceMock(&dispatcher_); setupFromV2Yaml(BasicConfig); const std::list resolved_addresses{}; - EXPECT_CALL(*dns_timer, enableTimer(_)); + EXPECT_CALL(*dns_timer, enableTimer(_, _)); expectResolveDiscovery(Network::DnsLookupFamily::V4Only, "foo.bar.com", resolved_addresses); EXPECT_CALL(initialized_, ready()); @@ -643,7 +654,7 @@ TEST_F(RedisClusterTest, EmptyDnsResponse) { EXPECT_EQ(1U, cluster_->info()->stats().update_empty_.value()); // Does not recreate the timer on subsequent DNS resolve calls. - EXPECT_CALL(*dns_timer, enableTimer(_)); + EXPECT_CALL(*dns_timer, enableTimer(_, _)); expectResolveDiscovery(Network::DnsLookupFamily::V4Only, "foo.bar.com", resolved_addresses); dns_timer->invokeCallback(); @@ -771,8 +782,8 @@ TEST_F(RedisClusterTest, RedisErrorResponse) { EXPECT_CALL(membership_updated_, ready()); EXPECT_CALL(initialized_, ready()); EXPECT_CALL(*cluster_callback_, onClusterSlotUpdate(_, _)).Times(1); - std::bitset<10> single_slot_master(0x7ff); - std::bitset<3> no_replica(0); + std::bitset single_slot_master(0xfff); + std::bitset no_replica(0); expectClusterSlotResponse(createResponse(single_slot_master, no_replica)); expectHealthyHosts(std::list({"127.0.0.1:22120"})); @@ -780,8 +791,8 @@ TEST_F(RedisClusterTest, RedisErrorResponse) { uint64_t update_attempt = 2; uint64_t update_failure = 1; // Test every combination the cluster slots response. - for (uint64_t i = 0; i < (1 << 10); i++) { - std::bitset<10> flags(i); + for (uint64_t i = 0; i < (1 << ResponseFlagSize); i++) { + std::bitset flags(i); expectRedisResolve(); resolve_timer_->invokeCallback(); if (flags.all()) { @@ -807,8 +818,8 @@ TEST_F(RedisClusterTest, RedisReplicaErrorResponse) { EXPECT_CALL(membership_updated_, ready()); EXPECT_CALL(initialized_, ready()); EXPECT_CALL(*cluster_callback_, onClusterSlotUpdate(_, _)).Times(1); - std::bitset<10> single_slot_master(0x7ff); - std::bitset<3> no_replica(0); + std::bitset single_slot_master(0xfff); + std::bitset no_replica(0); expectClusterSlotResponse(createResponse(single_slot_master, no_replica)); expectHealthyHosts(std::list({"127.0.0.1:22120"})); @@ -816,8 +827,8 @@ TEST_F(RedisClusterTest, RedisReplicaErrorResponse) { uint64_t update_attempt = 1; uint64_t update_failure = 0; // Test every combination the replica error response. - for (uint64_t i = 1; i < (1 << 3); i++) { - std::bitset<3> replica_flags(i); + for (uint64_t i = 1; i < (1 << ResponseReplicaFlagSize); i++) { + std::bitset replica_flags(i); expectRedisResolve(); resolve_timer_->invokeCallback(); if (replica_flags.all()) { diff --git a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc index 7f69ca894a..0097b25432 100644 --- a/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc +++ b/test/extensions/common/dynamic_forward_proxy/dns_cache_impl_test.cc @@ -98,7 +98,7 @@ TEST_F(DnsCacheImplTest, ResolveSuccess) { EXPECT_CALL(update_callbacks_, onDnsHostAddOrUpdate("foo.com", DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); EXPECT_CALL(callbacks, onLoadDnsCacheComplete()); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"10.0.0.1"})); checkStats(1 /* attempt */, 1 /* success */, 0 /* failure */, 1 /* address changed */, @@ -113,7 +113,7 @@ TEST_F(DnsCacheImplTest, ResolveSuccess) { 1 /* added */, 0 /* removed */, 1 /* num hosts */); // Address does not change. - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"10.0.0.1"})); checkStats(2 /* attempt */, 2 /* success */, 0 /* failure */, 1 /* address changed */, @@ -130,7 +130,7 @@ TEST_F(DnsCacheImplTest, ResolveSuccess) { // Address does change. EXPECT_CALL(update_callbacks_, onDnsHostAddOrUpdate("foo.com", DnsHostInfoEquals("10.0.0.2:80", "foo.com", false))); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"10.0.0.2"})); checkStats(3 /* attempt */, 3 /* success */, 0 /* failure */, 2 /* address changed */, @@ -155,7 +155,7 @@ TEST_F(DnsCacheImplTest, Ipv4Address) { update_callbacks_, onDnsHostAddOrUpdate("127.0.0.1", DnsHostInfoEquals("127.0.0.1:80", "127.0.0.1", true))); EXPECT_CALL(callbacks, onLoadDnsCacheComplete()); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"127.0.0.1"})); } @@ -177,7 +177,7 @@ TEST_F(DnsCacheImplTest, Ipv4AddressWithPort) { onDnsHostAddOrUpdate("127.0.0.1:10000", DnsHostInfoEquals("127.0.0.1:10000", "127.0.0.1", true))); EXPECT_CALL(callbacks, onLoadDnsCacheComplete()); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"127.0.0.1"})); } @@ -198,7 +198,7 @@ TEST_F(DnsCacheImplTest, Ipv6Address) { EXPECT_CALL(update_callbacks_, onDnsHostAddOrUpdate("[::1]", DnsHostInfoEquals("[::1]:80", "::1", true))); EXPECT_CALL(callbacks, onLoadDnsCacheComplete()); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"::1"})); } @@ -219,7 +219,7 @@ TEST_F(DnsCacheImplTest, Ipv6AddressWithPort) { EXPECT_CALL(update_callbacks_, onDnsHostAddOrUpdate("[::1]:10000", DnsHostInfoEquals("[::1]:10000", "::1", true))); EXPECT_CALL(callbacks, onLoadDnsCacheComplete()); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"::1"})); } @@ -243,7 +243,7 @@ TEST_F(DnsCacheImplTest, TTL) { EXPECT_CALL(update_callbacks_, onDnsHostAddOrUpdate("foo.com", DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); EXPECT_CALL(callbacks, onLoadDnsCacheComplete()); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"10.0.0.1"}, std::chrono::seconds(0))); checkStats(1 /* attempt */, 1 /* success */, 0 /* failure */, 1 /* address changed */, @@ -257,7 +257,7 @@ TEST_F(DnsCacheImplTest, TTL) { checkStats(2 /* attempt */, 1 /* success */, 0 /* failure */, 1 /* address changed */, 1 /* added */, 0 /* removed */, 1 /* num hosts */); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); resolve_cb(TestUtility::makeDnsResponse({"10.0.0.1"})); checkStats(2 /* attempt */, 2 /* success */, 0 /* failure */, 1 /* address changed */, 1 /* added */, 0 /* removed */, 1 /* num hosts */); @@ -300,7 +300,7 @@ TEST_F(DnsCacheImplTest, TTLWithCustomParameters) { EXPECT_CALL(update_callbacks_, onDnsHostAddOrUpdate("foo.com", DnsHostInfoEquals("10.0.0.1:80", "foo.com", false))); EXPECT_CALL(callbacks, onLoadDnsCacheComplete()); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(30000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(30000), _)); resolve_cb(TestUtility::makeDnsResponse({"10.0.0.1"}, std::chrono::seconds(0))); // Re-resolve with ~30s passed. TTL should still be OK at 60s. @@ -308,7 +308,7 @@ TEST_F(DnsCacheImplTest, TTLWithCustomParameters) { EXPECT_CALL(*resolver_, resolve("foo.com", _, _)) .WillOnce(DoAll(SaveArg<2>(&resolve_cb), Return(&resolver_->active_query_))); resolve_timer->invokeCallback(); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(30000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(30000), _)); resolve_cb(TestUtility::makeDnsResponse({"10.0.0.1"})); // Re-resolve with ~30s passed. TTL should expire. @@ -340,7 +340,7 @@ TEST_F(DnsCacheImplTest, InlineResolve) { update_callbacks_, onDnsHostAddOrUpdate("localhost", DnsHostInfoEquals("127.0.0.1:80", "localhost", false))); EXPECT_CALL(callbacks, onLoadDnsCacheComplete()); - EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000))); + EXPECT_CALL(*resolve_timer, enableTimer(std::chrono::milliseconds(60000), _)); post_cb(); } diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD new file mode 100644 index 0000000000..490402dc17 --- /dev/null +++ b/test/extensions/common/wasm/BUILD @@ -0,0 +1,19 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test", + "envoy_cc_test_library", + "envoy_package", +) + +envoy_package() + +envoy_cc_test( + name = "wasm_vm_test", + srcs = ["wasm_vm_test.cc"], + deps = [ + "//source/extensions/common/wasm:wasm_vm_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/common/wasm/wasm_vm_test.cc b/test/extensions/common/wasm/wasm_vm_test.cc new file mode 100644 index 0000000000..e1ecd2590f --- /dev/null +++ b/test/extensions/common/wasm/wasm_vm_test.cc @@ -0,0 +1,103 @@ +#include "envoy/registry/registry.h" + +#include "extensions/common/wasm/null/null_vm_plugin.h" +#include "extensions/common/wasm/wasm_vm.h" + +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Common { +namespace Wasm { +namespace { + +class TestNullVmPlugin : public Null::NullVmPlugin { +public: + TestNullVmPlugin() = default; + ~TestNullVmPlugin() override = default; + + MOCK_METHOD0(start, void()); +}; + +class PluginFactory : public Null::NullVmPluginFactory { +public: + PluginFactory() = default; + + const std::string name() const override { return "test_null_vm_plugin"; } + std::unique_ptr create() const override; +}; + +TestNullVmPlugin* test_null_vm_plugin_ = nullptr; +Envoy::Registry::RegisterFactory register_; + +std::unique_ptr PluginFactory::create() const { + auto result = std::make_unique(); + test_null_vm_plugin_ = result.get(); + return result; +} + +TEST(WasmVmTest, BadVmType) { EXPECT_THROW(createWasmVm("bad.vm"), WasmException); } + +TEST(WasmVmTest, NullVmStartup) { + auto wasm_vm = createWasmVm("envoy.wasm.vm.null"); + EXPECT_TRUE(wasm_vm != nullptr); + EXPECT_TRUE(wasm_vm->cloneable()); + auto wasm_vm_clone = wasm_vm->clone(); + EXPECT_TRUE(wasm_vm_clone != nullptr); + EXPECT_TRUE(wasm_vm->getUserSection("user").empty()); +} + +TEST(WasmVmTest, NullVmMemory) { + auto wasm_vm = createWasmVm("envoy.wasm.vm.null"); + EXPECT_EQ(wasm_vm->getMemorySize(), std::numeric_limits::max()); + std::string d = "data"; + auto m = wasm_vm->getMemory(reinterpret_cast(d.data()), d.size()).value(); + EXPECT_EQ(m.data(), d.data()); + EXPECT_EQ(m.size(), d.size()); + uint64_t offset; + char l; + EXPECT_TRUE(wasm_vm->getMemoryOffset(&l, &offset)); + EXPECT_EQ(offset, reinterpret_cast(&l)); + char c; + char z = 'z'; + EXPECT_TRUE(wasm_vm->setMemory(reinterpret_cast(&c), 1, &z)); + EXPECT_EQ(c, z); + + Word w(13); + EXPECT_TRUE( + wasm_vm->setWord(reinterpret_cast(&w), std::numeric_limits::max())); + EXPECT_EQ(w.u64_, std::numeric_limits::max()); + + Word w2(0); + w.u64_ = 7; + EXPECT_TRUE(wasm_vm->getWord(reinterpret_cast(&w), &w2)); + EXPECT_EQ(w2.u64_, 7); +} + +TEST(WasmVmTest, NullVmStart) { + auto wasm_vm = createWasmVm("envoy.wasm.vm.null"); + EXPECT_TRUE(wasm_vm->load("test_null_vm_plugin", true)); + wasm_vm->link("test", false); + // Test that context argument to start is pushed and that the effective_context_id_ is reset. + // Test that the original values are restored. + Context* context1 = reinterpret_cast(1); + Context* context2 = reinterpret_cast(2); + current_context_ = context1; + effective_context_id_ = 1; + EXPECT_CALL(*test_null_vm_plugin_, start()).WillOnce(Invoke([context2]() { + EXPECT_EQ(current_context_, context2); + EXPECT_EQ(effective_context_id_, 0); + })); + wasm_vm->start(context2); + EXPECT_EQ(current_context_, context1); + EXPECT_EQ(effective_context_id_, 1); +} + +} // namespace +} // namespace Wasm +} // namespace Common +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/common/expr/context_test.cc b/test/extensions/filters/common/expr/context_test.cc index 0e79abe362..7f11fa75bb 100644 --- a/test/extensions/filters/common/expr/context_test.cc +++ b/test/extensions/filters/common/expr/context_test.cc @@ -10,8 +10,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::Const; using testing::Return; using testing::ReturnRef; @@ -256,10 +254,12 @@ TEST(Context, ResponseAttributes) { TEST(Context, ConnectionAttributes) { NiceMock info; - std::shared_ptr> host( + std::shared_ptr> upstream_host( new NiceMock()); - NiceMock connection_info; + auto downstream_ssl_info = std::make_shared>(); + auto upstream_ssl_info = std::make_shared>(); ConnectionWrapper connection(info); + UpstreamWrapper upstream(info); PeerWrapper source(info, false); PeerWrapper destination(info, true); @@ -267,16 +267,20 @@ TEST(Context, ConnectionAttributes) { Network::Utility::parseInternetAddress("1.2.3.4", 123, false); Network::Address::InstanceConstSharedPtr remote = Network::Utility::parseInternetAddress("10.20.30.40", 456, false); - Network::Address::InstanceConstSharedPtr upstream = + Network::Address::InstanceConstSharedPtr upstream_address = Network::Utility::parseInternetAddress("10.1.2.3", 679, false); const std::string sni_name = "kittens.com"; EXPECT_CALL(info, downstreamLocalAddress()).WillRepeatedly(ReturnRef(local)); EXPECT_CALL(info, downstreamRemoteAddress()).WillRepeatedly(ReturnRef(remote)); - EXPECT_CALL(info, downstreamSslConnection()).WillRepeatedly(Return(&connection_info)); - EXPECT_CALL(info, upstreamHost()).WillRepeatedly(Return(host)); + EXPECT_CALL(info, downstreamSslConnection()).WillRepeatedly(Return(downstream_ssl_info)); + EXPECT_CALL(info, upstreamSslConnection()).WillRepeatedly(Return(upstream_ssl_info)); + EXPECT_CALL(info, upstreamHost()).WillRepeatedly(Return(upstream_host)); EXPECT_CALL(info, requestedServerName()).WillRepeatedly(ReturnRef(sni_name)); - EXPECT_CALL(connection_info, peerCertificatePresented()).WillRepeatedly(Return(true)); - EXPECT_CALL(*host, address()).WillRepeatedly(Return(upstream)); + EXPECT_CALL(*downstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); + EXPECT_CALL(*upstream_ssl_info, peerCertificatePresented()).WillRepeatedly(Return(true)); + const std::string tls_version = "TLSv1"; + EXPECT_CALL(*downstream_ssl_info, tlsVersion()).WillRepeatedly(ReturnRef(tls_version)); + EXPECT_CALL(*upstream_host, address()).WillRepeatedly(Return(upstream_address)); { auto value = connection[CelValue::CreateString(Undefined)]; @@ -327,19 +331,26 @@ TEST(Context, ConnectionAttributes) { } { - auto value = connection[CelValue::CreateString(UpstreamAddress)]; + auto value = upstream[CelValue::CreateString(Address)]; EXPECT_TRUE(value.has_value()); ASSERT_TRUE(value.value().IsString()); EXPECT_EQ("10.1.2.3:679", value.value().StringOrDie().value()); } { - auto value = connection[CelValue::CreateString(UpstreamPort)]; + auto value = upstream[CelValue::CreateString(Port)]; EXPECT_TRUE(value.has_value()); ASSERT_TRUE(value.value().IsInt64()); EXPECT_EQ(679, value.value().Int64OrDie()); } + { + auto value = upstream[CelValue::CreateString(MTLS)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsBool()); + EXPECT_TRUE(value.value().BoolOrDie()); + } + { auto value = connection[CelValue::CreateString(MTLS)]; EXPECT_TRUE(value.has_value()); @@ -353,6 +364,13 @@ TEST(Context, ConnectionAttributes) { ASSERT_TRUE(value.value().IsString()); EXPECT_EQ(sni_name, value.value().StringOrDie().value()); } + + { + auto value = connection[CelValue::CreateString(TLSVersion)]; + EXPECT_TRUE(value.has_value()); + ASSERT_TRUE(value.value().IsString()); + EXPECT_EQ(tls_version, value.value().StringOrDie().value()); + } } } // namespace diff --git a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc index 7567a9a950..de4ef02578 100644 --- a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc +++ b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc @@ -28,19 +28,47 @@ class CheckRequestUtilsTest : public testing::Test { addr_ = std::make_shared("1.2.3.4", 1111); protocol_ = Envoy::Http::Protocol::Http10; buffer_ = CheckRequestUtilsTest::newTestBuffer(8192); + ssl_ = std::make_shared>(); }; - void ExpectBasicHttp() { + void expectBasicHttp() { EXPECT_CALL(callbacks_, connection()).Times(2).WillRepeatedly(Return(&connection_)); EXPECT_CALL(connection_, remoteAddress()).WillOnce(ReturnRef(addr_)); EXPECT_CALL(connection_, localAddress()).WillOnce(ReturnRef(addr_)); - EXPECT_CALL(Const(connection_), ssl()).Times(2).WillRepeatedly(Return(&ssl_)); + EXPECT_CALL(Const(connection_), ssl()).Times(2).WillRepeatedly(Return(ssl_)); EXPECT_CALL(callbacks_, streamId()).Times(1).WillOnce(Return(0)); EXPECT_CALL(callbacks_, decodingBuffer()).WillOnce(Return(buffer_.get())); EXPECT_CALL(callbacks_, streamInfo()).Times(3).WillRepeatedly(ReturnRef(req_info_)); EXPECT_CALL(req_info_, protocol()).Times(2).WillRepeatedly(ReturnPointee(&protocol_)); } + void callHttpCheckAndValidateRequestAttributes() { + Http::TestHeaderMapImpl request_headers{{"x-envoy-downstream-service-cluster", "foo"}, + {":path", "/bar"}}; + envoy::service::auth::v2::CheckRequest request; + Protobuf::Map context_extensions; + context_extensions["key"] = "value"; + + envoy::api::v2::core::Metadata metadata_context; + auto metadata_val = MessageUtil::keyValueStruct("foo", "bar"); + (*metadata_context.mutable_filter_metadata())["meta.key"] = metadata_val; + + CheckRequestUtils::createHttpCheck(&callbacks_, request_headers, std::move(context_extensions), + std::move(metadata_context), request, false); + + EXPECT_EQ("source", request.attributes().source().principal()); + EXPECT_EQ("destination", request.attributes().destination().principal()); + EXPECT_EQ("foo", request.attributes().source().service()); + EXPECT_EQ("value", request.attributes().context_extensions().at("key")); + EXPECT_EQ("bar", request.attributes() + .metadata_context() + .filter_metadata() + .at("meta.key") + .fields() + .at("foo") + .string_value()); + } + static Buffer::InstancePtr newTestBuffer(uint64_t size) { auto buffer = std::make_unique(); while (buffer->length() < size) { @@ -57,7 +85,7 @@ class CheckRequestUtilsTest : public testing::Test { NiceMock callbacks_; NiceMock net_callbacks_; NiceMock connection_; - NiceMock ssl_; + std::shared_ptr> ssl_; NiceMock req_info_; Buffer::InstancePtr buffer_; }; @@ -68,7 +96,10 @@ TEST_F(CheckRequestUtilsTest, BasicTcp) { EXPECT_CALL(net_callbacks_, connection()).Times(2).WillRepeatedly(ReturnRef(connection_)); EXPECT_CALL(connection_, remoteAddress()).WillOnce(ReturnRef(addr_)); EXPECT_CALL(connection_, localAddress()).WillOnce(ReturnRef(addr_)); - EXPECT_CALL(Const(connection_), ssl()).Times(2).WillRepeatedly(Return(&ssl_)); + EXPECT_CALL(Const(connection_), ssl()).Times(2).WillRepeatedly(Return(ssl_)); + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); CheckRequestUtils::createTcpCheck(&net_callbacks_, request); } @@ -84,9 +115,13 @@ TEST_F(CheckRequestUtilsTest, BasicHttp) { // A client supplied EnvoyAuthPartialBody header should be ignored. Http::TestHeaderMapImpl request_headers{{Http::Headers::get().EnvoyAuthPartialBody.get(), "1"}}; - ExpectBasicHttp(); + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); + expectBasicHttp(); CheckRequestUtils::createHttpCheck(&callbacks_, request_headers, - Protobuf::Map(), request_, size); + Protobuf::Map(), + envoy::api::v2::core::Metadata(), request_, size); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_EQ(request_.attributes().request().http().headers().end(), @@ -100,9 +135,13 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithPartialBody) { Http::HeaderMapImpl headers_; envoy::service::auth::v2::CheckRequest request_; - ExpectBasicHttp(); + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); + expectBasicHttp(); CheckRequestUtils::createHttpCheck(&callbacks_, headers_, - Protobuf::Map(), request_, size); + Protobuf::Map(), + envoy::api::v2::core::Metadata(), request_, size); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_EQ("true", request_.attributes().request().http().headers().at( @@ -114,10 +153,13 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBody) { Http::HeaderMapImpl headers_; envoy::service::auth::v2::CheckRequest request_; - ExpectBasicHttp(); + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); + expectBasicHttp(); CheckRequestUtils::createHttpCheck(&callbacks_, headers_, - Protobuf::Map(), request_, - buffer_->length()); + Protobuf::Map(), + envoy::api::v2::core::Metadata(), request_, buffer_->length()); ASSERT_EQ(buffer_->length(), request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, buffer_->length()), request_.attributes().request().http().body()); @@ -134,25 +176,64 @@ TEST_F(CheckRequestUtilsTest, CheckAttrContextPeer) { EXPECT_CALL(callbacks_, connection()).WillRepeatedly(Return(&connection_)); EXPECT_CALL(connection_, remoteAddress()).WillRepeatedly(ReturnRef(addr_)); EXPECT_CALL(connection_, localAddress()).WillRepeatedly(ReturnRef(addr_)); - EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(&ssl_)); + EXPECT_CALL(Const(connection_), ssl()).WillRepeatedly(Return(ssl_)); EXPECT_CALL(callbacks_, streamId()).WillRepeatedly(Return(0)); EXPECT_CALL(callbacks_, streamInfo()).WillRepeatedly(ReturnRef(req_info_)); EXPECT_CALL(callbacks_, decodingBuffer()).Times(1); EXPECT_CALL(req_info_, protocol()).WillRepeatedly(ReturnPointee(&protocol_)); - EXPECT_CALL(ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); - EXPECT_CALL(ssl_, uriSanLocalCertificate()) + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); + + callHttpCheckAndValidateRequestAttributes(); +} + +// Verify that createHttpCheck extract the attributes from the HTTP request into CheckRequest +// proto object and URI SAN is used as principal if present. +TEST_F(CheckRequestUtilsTest, CheckAttrContextPeerUriSans) { + expectBasicHttp(); + + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); + + callHttpCheckAndValidateRequestAttributes(); +} + +// Verify that createHttpCheck extract the attributes from the HTTP request into CheckRequest +// proto object and DNS SAN is used as principal if URI SAN is absent. +TEST_F(CheckRequestUtilsTest, CheckAttrContextPeerDnsSans) { + expectBasicHttp(); + + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{})); + EXPECT_CALL(*ssl_, dnsSansPeerCertificate()).WillOnce(Return(std::vector{"source"})); + + EXPECT_CALL(*ssl_, uriSanLocalCertificate()).WillOnce(Return(std::vector{})); + EXPECT_CALL(*ssl_, dnsSansLocalCertificate()) .WillOnce(Return(std::vector{"destination"})); Protobuf::Map context_extensions; context_extensions["key"] = "value"; - CheckRequestUtils::createHttpCheck(&callbacks_, request_headers, std::move(context_extensions), - request, false); + callHttpCheckAndValidateRequestAttributes(); +} + +// Verify that createHttpCheck extract the attributes from the HTTP request into CheckRequest +// proto object and Subject is used as principal if both URI SAN and DNS SAN are absent. +TEST_F(CheckRequestUtilsTest, CheckAttrContextSubject) { + expectBasicHttp(); + + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{})); + EXPECT_CALL(*ssl_, dnsSansPeerCertificate()).WillOnce(Return(std::vector{})); + std::string subject_peer = "source"; + EXPECT_CALL(*ssl_, subjectPeerCertificate()).WillOnce(ReturnRef(subject_peer)); + + EXPECT_CALL(*ssl_, uriSanLocalCertificate()).WillOnce(Return(std::vector{})); + EXPECT_CALL(*ssl_, dnsSansLocalCertificate()).WillOnce(Return(std::vector{})); + std::string subject_local = "destination"; + EXPECT_CALL(*ssl_, subjectLocalCertificate()).WillOnce(ReturnRef(subject_local)); - EXPECT_EQ("source", request.attributes().source().principal()); - EXPECT_EQ("destination", request.attributes().destination().principal()); - EXPECT_EQ("foo", request.attributes().source().service()); - EXPECT_EQ("value", request.attributes().context_extensions().at("key")); + callHttpCheckAndValidateRequestAttributes(); } } // namespace diff --git a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc index 62e89216af..6941c1b362 100644 --- a/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc +++ b/test/extensions/filters/common/ext_authz/ext_authz_http_impl_test.cc @@ -16,13 +16,12 @@ using testing::_; using testing::AllOf; +using testing::Eq; +using testing::InSequence; using testing::Invoke; -using testing::Ref; using testing::Return; -using testing::ReturnPointee; using testing::ReturnRef; using testing::WhenDynamicCastTo; -using testing::WithArg; namespace Envoy { namespace Extensions { @@ -377,6 +376,18 @@ TEST_F(ExtAuthzHttpClientTest, CancelledAuthorizationRequest) { client_.cancel(); } +// Test the client when the configured cluster is missing/removed. +TEST_F(ExtAuthzHttpClientTest, NoCluster) { + InSequence s; + + EXPECT_CALL(cm_, get(Eq("ext_authz"))).WillOnce(Return(nullptr)); + EXPECT_CALL(cm_, httpAsyncClientForCluster("ext_authz")).Times(0); + EXPECT_CALL(request_callbacks_, + onComplete_(WhenDynamicCastTo(AuthzErrorResponse(CheckStatus::Error)))); + client_.check(request_callbacks_, envoy::service::auth::v2::CheckRequest{}, + Tracing::NullSpan::instance()); +} + } // namespace } // namespace ExtAuthz } // namespace Common diff --git a/test/extensions/filters/common/lua/wrappers_test.cc b/test/extensions/filters/common/lua/wrappers_test.cc index 926db9ce3d..8d4a9af87e 100644 --- a/test/extensions/filters/common/lua/wrappers_test.cc +++ b/test/extensions/filters/common/lua/wrappers_test.cc @@ -35,6 +35,7 @@ class LuaConnectionWrapperTest : public LuaWrappersTestBase { void setup(const std::string& script) override { LuaWrappersTestBase::setup(script); state_->registerType(); + ssl_ = std::make_shared>(); } protected: @@ -53,17 +54,17 @@ class LuaConnectionWrapperTest : public LuaWrappersTestBase { setup(SCRIPT); // Setup secure connection if required. - EXPECT_CALL(Const(connection_), ssl()).WillOnce(Return(secure ? &ssl_ : nullptr)); + EXPECT_CALL(Const(connection_), ssl()).WillOnce(Return(secure ? ssl_ : nullptr)); ConnectionWrapper::create(coroutine_->luaState(), &connection_); EXPECT_CALL(*this, testPrint(secure ? "secure" : "plain")); - EXPECT_CALL(Const(connection_), ssl()).WillOnce(Return(secure ? &ssl_ : nullptr)); + EXPECT_CALL(Const(connection_), ssl()).WillOnce(Return(secure ? ssl_ : nullptr)); EXPECT_CALL(*this, testPrint(secure ? "userdata" : "nil")); start("callMe"); } NiceMock connection_; - NiceMock ssl_; + std::shared_ptr> ssl_; }; // Basic buffer wrapper methods test. diff --git a/test/extensions/filters/common/original_src/original_src_socket_option_test.cc b/test/extensions/filters/common/original_src/original_src_socket_option_test.cc index 9fc29b0aa5..c2e108cb66 100644 --- a/test/extensions/filters/common/original_src/original_src_socket_option_test.cc +++ b/test/extensions/filters/common/original_src/original_src_socket_option_test.cc @@ -12,7 +12,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::Eq; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/common/ratelimit/ratelimit_impl_test.cc b/test/extensions/filters/common/ratelimit/ratelimit_impl_test.cc index 97a5863c0b..37024b6790 100644 --- a/test/extensions/filters/common/ratelimit/ratelimit_impl_test.cc +++ b/test/extensions/filters/common/ratelimit/ratelimit_impl_test.cc @@ -20,12 +20,10 @@ #include "gtest/gtest.h" using testing::_; -using testing::AtLeast; using testing::Eq; using testing::Invoke; using testing::Ref; using testing::Return; -using testing::WithArg; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/common/rbac/engine_impl_test.cc b/test/extensions/filters/common/rbac/engine_impl_test.cc index 6d346dca62..7324099d20 100644 --- a/test/extensions/filters/common/rbac/engine_impl_test.cc +++ b/test/extensions/filters/common/rbac/engine_impl_test.cc @@ -10,9 +10,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::Const; -using testing::Return; using testing::ReturnRef; namespace Envoy { diff --git a/test/extensions/filters/common/rbac/matchers_test.cc b/test/extensions/filters/common/rbac/matchers_test.cc index 43012da47e..699b53d8b4 100644 --- a/test/extensions/filters/common/rbac/matchers_test.cc +++ b/test/extensions/filters/common/rbac/matchers_test.cc @@ -8,7 +8,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::Const; using testing::Return; using testing::ReturnRef; @@ -192,11 +191,11 @@ TEST(PortMatcher, PortMatcher) { TEST(AuthenticatedMatcher, uriSanPeerCertificate) { Envoy::Network::MockConnection conn; - Envoy::Ssl::MockConnectionInfo ssl; + auto ssl = std::make_shared(); const std::vector sans{"foo", "baz"}; - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(sans)); - EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(&ssl)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(sans)); + EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(ssl)); // We should get the first URI SAN. envoy::config::rbac::v2::Principal_Authenticated auth; @@ -209,16 +208,16 @@ TEST(AuthenticatedMatcher, uriSanPeerCertificate) { TEST(AuthenticatedMatcher, dnsSanPeerCertificate) { Envoy::Network::MockConnection conn; - Envoy::Ssl::MockConnectionInfo ssl; + auto ssl = std::make_shared(); const std::vector uri_sans; const std::vector dns_sans{"foo", "baz"}; - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(uri_sans)); - EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(&ssl)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(uri_sans)); + EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(ssl)); - EXPECT_CALL(ssl, dnsSansPeerCertificate()).WillRepeatedly(Return(dns_sans)); - EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(&ssl)); + EXPECT_CALL(*ssl, dnsSansPeerCertificate()).WillRepeatedly(Return(dns_sans)); + EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(ssl)); // We should get the first DNS SAN as URI SAN is not available. envoy::config::rbac::v2::Principal_Authenticated auth; @@ -231,13 +230,14 @@ TEST(AuthenticatedMatcher, dnsSanPeerCertificate) { TEST(AuthenticatedMatcher, subjectPeerCertificate) { Envoy::Network::MockConnection conn; - Envoy::Ssl::MockConnectionInfo ssl; + auto ssl = std::make_shared(); const std::vector sans; - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(sans)); - EXPECT_CALL(ssl, dnsSansPeerCertificate()).WillRepeatedly(Return(sans)); - EXPECT_CALL(ssl, subjectPeerCertificate()).WillRepeatedly(Return("bar")); - EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(&ssl)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(sans)); + EXPECT_CALL(*ssl, dnsSansPeerCertificate()).WillRepeatedly(Return(sans)); + std::string peer_subject = "bar"; + EXPECT_CALL(*ssl, subjectPeerCertificate()).WillRepeatedly(ReturnRef(peer_subject)); + EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(ssl)); envoy::config::rbac::v2::Principal_Authenticated auth; auth.mutable_principal_name()->set_exact("bar"); @@ -249,10 +249,10 @@ TEST(AuthenticatedMatcher, subjectPeerCertificate) { TEST(AuthenticatedMatcher, AnySSLSubject) { Envoy::Network::MockConnection conn; - Envoy::Ssl::MockConnectionInfo ssl; + auto ssl = std::make_shared(); const std::vector sans{"foo", "baz"}; - EXPECT_CALL(ssl, uriSanPeerCertificate()).WillRepeatedly(Return(sans)); - EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(&ssl)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).WillRepeatedly(Return(sans)); + EXPECT_CALL(Const(conn), ssl()).WillRepeatedly(Return(ssl)); envoy::config::rbac::v2::Principal_Authenticated auth; checkMatcher(AuthenticatedMatcher(auth), true, conn); @@ -298,13 +298,13 @@ TEST(PolicyMatcher, PolicyMatcher) { RBAC::PolicyMatcher matcher(policy, nullptr); Envoy::Network::MockConnection conn; - Envoy::Ssl::MockConnectionInfo ssl; + auto ssl = std::make_shared(); Envoy::Network::Address::InstanceConstSharedPtr addr = Envoy::Network::Utility::parseInternetAddress("1.2.3.4", 456, false); const std::vector sans{"bar", "baz"}; - EXPECT_CALL(ssl, uriSanPeerCertificate()).Times(2).WillRepeatedly(Return(sans)); - EXPECT_CALL(Const(conn), ssl()).Times(2).WillRepeatedly(Return(&ssl)); + EXPECT_CALL(*ssl, uriSanPeerCertificate()).Times(2).WillRepeatedly(Return(sans)); + EXPECT_CALL(Const(conn), ssl()).Times(2).WillRepeatedly(Return(ssl)); EXPECT_CALL(conn, localAddress()).Times(2).WillRepeatedly(ReturnRef(addr)); checkMatcher(matcher, true, conn); diff --git a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_test.cc b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_test.cc index 60ad871dc3..f859c031e6 100644 --- a/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_test.cc +++ b/test/extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter_test.cc @@ -24,28 +24,35 @@ using ConcurrencyController::RequestForwardingAction; class MockConcurrencyController : public ConcurrencyController::ConcurrencyController { public: MOCK_METHOD0(forwardingDecision, RequestForwardingAction()); - MOCK_METHOD1(recordLatencySample, void(const std::chrono::nanoseconds&)); + MOCK_METHOD0(cancelLatencySample, void()); + MOCK_METHOD1(recordLatencySample, void(std::chrono::nanoseconds)); + + uint32_t concurrencyLimit() const override { return 0; } }; class AdaptiveConcurrencyFilterTest : public testing::Test { public: - AdaptiveConcurrencyFilterTest() { - filter_.reset(); + AdaptiveConcurrencyFilterTest() = default; + void SetUp() override { const envoy::config::filter::http::adaptive_concurrency::v2alpha::AdaptiveConcurrency config; auto config_ptr = std::make_shared( config, runtime_, "testprefix.", stats_, time_system_); filter_ = std::make_unique(config_ptr, controller_); filter_->setDecoderFilterCallbacks(decoder_callbacks_); + filter_->setEncoderFilterCallbacks(encoder_callbacks_); } - std::unique_ptr filter_; + void TearDown() override { filter_.reset(); } + Event::SimulatedTimeSystem time_system_; Stats::IsolatedStoreImpl stats_; NiceMock runtime_; std::shared_ptr controller_{new MockConcurrencyController()}; NiceMock decoder_callbacks_; + NiceMock encoder_callbacks_; + std::unique_ptr filter_; }; TEST_F(AdaptiveConcurrencyFilterTest, DecodeHeadersTestForwarding) { @@ -53,6 +60,8 @@ TEST_F(AdaptiveConcurrencyFilterTest, DecodeHeadersTestForwarding) { EXPECT_CALL(*controller_, forwardingDecision()) .WillOnce(Return(RequestForwardingAction::Forward)); + EXPECT_CALL(*controller_, recordLatencySample(_)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, false)); Buffer::OwnedImpl request_body; @@ -71,9 +80,60 @@ TEST_F(AdaptiveConcurrencyFilterTest, DecodeHeadersTestBlock) { filter_->decodeHeaders(request_headers, true)); } +TEST_F(AdaptiveConcurrencyFilterTest, RecordSampleInDestructor) { + // Verify that the request latency is always sampled even if encodeComplete() is never called. + EXPECT_CALL(*controller_, forwardingDecision()) + .WillOnce(Return(RequestForwardingAction::Forward)); + Http::TestHeaderMapImpl request_headers; + filter_->decodeHeaders(request_headers, true); + + EXPECT_CALL(*controller_, recordLatencySample(_)); + filter_.reset(); +} + +TEST_F(AdaptiveConcurrencyFilterTest, RecordSampleOmission) { + // Verify that the request latency is not sampled if forwardingDecision blocks the request. + EXPECT_CALL(*controller_, forwardingDecision()).WillOnce(Return(RequestForwardingAction::Block)); + Http::TestHeaderMapImpl request_headers; + filter_->decodeHeaders(request_headers, true); + + filter_.reset(); +} + +TEST_F(AdaptiveConcurrencyFilterTest, OnDestroyCleanupResetTest) { + // Get the filter to record the request start time via decode. + Http::TestHeaderMapImpl request_headers; + EXPECT_CALL(*controller_, forwardingDecision()) + .WillOnce(Return(RequestForwardingAction::Forward)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + EXPECT_CALL(*controller_, cancelLatencySample()); + + // Encode step is not performed prior to destruction. + filter_->onDestroy(); +} + +TEST_F(AdaptiveConcurrencyFilterTest, OnDestroyCleanupTest) { + // Get the filter to record the request start time via decode. + Http::TestHeaderMapImpl request_headers; + EXPECT_CALL(*controller_, forwardingDecision()) + .WillOnce(Return(RequestForwardingAction::Forward)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); + + const auto advance_time = std::chrono::nanoseconds(42); + time_system_.sleep(advance_time); + + Http::TestHeaderMapImpl response_headers; + EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(response_headers, false)); + EXPECT_CALL(*controller_, recordLatencySample(advance_time)); + filter_->encodeComplete(); + + filter_->onDestroy(); +} + TEST_F(AdaptiveConcurrencyFilterTest, EncodeHeadersValidTest) { auto mt = time_system_.monotonicTime(); - time_system_.setMonotonicTime(mt + std::chrono::milliseconds(123)); + time_system_.setMonotonicTime(mt + std::chrono::nanoseconds(123)); // Get the filter to record the request start time via decode. Http::TestHeaderMapImpl request_headers; @@ -81,7 +141,7 @@ TEST_F(AdaptiveConcurrencyFilterTest, EncodeHeadersValidTest) { .WillOnce(Return(RequestForwardingAction::Forward)); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers, true)); - const std::chrono::nanoseconds advance_time = std::chrono::milliseconds(42); + const auto advance_time = std::chrono::nanoseconds(42); mt = time_system_.monotonicTime(); time_system_.setMonotonicTime(mt + advance_time); diff --git a/test/extensions/filters/http/adaptive_concurrency/concurrency_controller/BUILD b/test/extensions/filters/http/adaptive_concurrency/concurrency_controller/BUILD new file mode 100644 index 0000000000..eda772937c --- /dev/null +++ b/test/extensions/filters/http/adaptive_concurrency/concurrency_controller/BUILD @@ -0,0 +1,28 @@ +licenses(["notice"]) # Apache 2 + +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_test_library", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +envoy_package() + +envoy_extension_cc_test( + name = "gradient_controller_test", + srcs = ["gradient_controller_test.cc"], + extension_name = "envoy.filters.http.adaptive_concurrency", + deps = [ + "//source/common/stats:isolated_store_lib", + "//source/extensions/filters/http/adaptive_concurrency:adaptive_concurrency_filter_lib", + "//source/extensions/filters/http/adaptive_concurrency/concurrency_controller:concurrency_controller_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) diff --git a/test/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller_test.cc b/test/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller_test.cc new file mode 100644 index 0000000000..1a523df973 --- /dev/null +++ b/test/extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller_test.cc @@ -0,0 +1,497 @@ +#include +#include + +#include "envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.pb.h" +#include "envoy/config/filter/http/adaptive_concurrency/v2alpha/adaptive_concurrency.pb.validate.h" + +#include "common/stats/isolated_store_impl.h" + +#include "extensions/filters/http/adaptive_concurrency/adaptive_concurrency_filter.h" +#include "extensions/filters/http/adaptive_concurrency/concurrency_controller/concurrency_controller.h" +#include "extensions/filters/http/adaptive_concurrency/concurrency_controller/gradient_controller.h" + +#include "test/mocks/event/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::AllOf; +using testing::Ge; +using testing::Le; +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace AdaptiveConcurrency { +namespace ConcurrencyController { +namespace { + +class GradientControllerConfigTest : public testing::Test { +public: + GradientControllerConfigTest() = default; +}; + +class GradientControllerTest : public testing::Test { +public: + GradientControllerTest() + : api_(Api::createApiForTest(time_system_)), dispatcher_(api_->allocateDispatcher()) {} + + GradientControllerSharedPtr makeController(const std::string& yaml_config) { + return std::make_shared(makeConfig(yaml_config), *dispatcher_, runtime_, + "test_prefix.", stats_); + } + +protected: + GradientControllerConfigSharedPtr makeConfig(const std::string& yaml_config) { + envoy::config::filter::http::adaptive_concurrency::v2alpha::GradientControllerConfig proto = + TestUtility::parseYaml< + envoy::config::filter::http::adaptive_concurrency::v2alpha::GradientControllerConfig>( + yaml_config); + return std::make_shared(proto); + } + + // Helper function that will attempt to pull forwarding decisions. + void tryForward(const GradientControllerSharedPtr& controller, + const bool expect_forward_response) { + const auto expected_resp = + expect_forward_response ? RequestForwardingAction::Forward : RequestForwardingAction::Block; + EXPECT_EQ(expected_resp, controller->forwardingDecision()); + } + + // Gets the controller past the initial minRTT stage. + void advancePastMinRTTStage(const GradientControllerSharedPtr& controller, + const std::string& yaml_config, + std::chrono::milliseconds latency = std::chrono::milliseconds(5)) { + const auto config = makeConfig(yaml_config); + for (uint32_t ii = 0; ii <= config->minRTTAggregateRequestCount(); ++ii) { + tryForward(controller, true); + controller->recordLatencySample(latency); + } + } + + Event::SimulatedTimeSystem time_system_; + Stats::IsolatedStoreImpl stats_; + NiceMock runtime_; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; +}; + +TEST_F(GradientControllerConfigTest, BasicTest) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 42 +concurrency_limit_params: + max_gradient: 2.1 + max_concurrency_limit: 1337 + concurrency_update_interval: + nanos: 123000000 +min_rtt_calc_params: + interval: + seconds: 31 + request_count: 52 +)EOF"; + + envoy::config::filter::http::adaptive_concurrency::v2alpha::GradientControllerConfig proto = + TestUtility::parseYaml< + envoy::config::filter::http::adaptive_concurrency::v2alpha::GradientControllerConfig>( + yaml); + GradientControllerConfig config(proto); + + EXPECT_EQ(config.minRTTCalcInterval(), std::chrono::seconds(31)); + EXPECT_EQ(config.sampleRTTCalcInterval(), std::chrono::milliseconds(123)); + EXPECT_EQ(config.maxConcurrencyLimit(), 1337); + EXPECT_EQ(config.minRTTAggregateRequestCount(), 52); + EXPECT_EQ(config.maxGradient(), 2.1); + EXPECT_EQ(config.sampleAggregatePercentile(), 0.42); +} + +TEST_F(GradientControllerConfigTest, DefaultValuesTest) { + const std::string yaml = R"EOF( +concurrency_limit_params: + concurrency_update_interval: + nanos: 123000000 +min_rtt_calc_params: + interval: + seconds: 31 +)EOF"; + + envoy::config::filter::http::adaptive_concurrency::v2alpha::GradientControllerConfig proto = + TestUtility::parseYaml< + envoy::config::filter::http::adaptive_concurrency::v2alpha::GradientControllerConfig>( + yaml); + GradientControllerConfig config(proto); + + EXPECT_EQ(config.minRTTCalcInterval(), std::chrono::seconds(31)); + EXPECT_EQ(config.sampleRTTCalcInterval(), std::chrono::milliseconds(123)); + EXPECT_EQ(config.maxConcurrencyLimit(), 1000); + EXPECT_EQ(config.minRTTAggregateRequestCount(), 50); + EXPECT_EQ(config.maxGradient(), 2.0); + EXPECT_EQ(config.sampleAggregatePercentile(), 0.5); +} + +TEST_F(GradientControllerTest, MinRTTLogicTest) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 2.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 100000000 # 100ms +min_rtt_calc_params: + interval: + seconds: 30 + request_count: 50 +)EOF"; + + auto controller = makeController(yaml); + const auto min_rtt = std::chrono::milliseconds(13); + + // The controller should be measuring minRTT upon creation, so the concurrency window is 1. + EXPECT_EQ(controller->concurrencyLimit(), 1); + tryForward(controller, true); + tryForward(controller, false); + tryForward(controller, false); + controller->recordLatencySample(min_rtt); + + // 49 more requests should cause the minRTT to be done calculating. + for (int ii = 0; ii < 49; ++ii) { + EXPECT_EQ(controller->concurrencyLimit(), 1); + tryForward(controller, true); + tryForward(controller, false); + controller->recordLatencySample(min_rtt); + } + + // Verify the minRTT value measured is accurate. + EXPECT_EQ( + 13, stats_.gauge("test_prefix.min_rtt_msecs", Stats::Gauge::ImportMode::NeverImport).value()); +} + +TEST_F(GradientControllerTest, CancelLatencySample) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 2.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 100000000 # 100ms +min_rtt_calc_params: + interval: + seconds: 30 + request_count: 5 +)EOF"; + + auto controller = makeController(yaml); + + for (int ii = 1; ii <= 5; ++ii) { + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(ii)); + } + EXPECT_EQ( + 3, stats_.gauge("test_prefix.min_rtt_msecs", Stats::Gauge::ImportMode::NeverImport).value()); +} + +TEST_F(GradientControllerTest, SamplePercentileProcessTest) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 2.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 100000000 # 100ms +min_rtt_calc_params: + interval: + seconds: 30 + request_count: 5 +)EOF"; + + auto controller = makeController(yaml); + + tryForward(controller, true); + tryForward(controller, false); + controller->cancelLatencySample(); + tryForward(controller, true); + tryForward(controller, false); +} + +TEST_F(GradientControllerTest, ConcurrencyLimitBehaviorTestBasic) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 2.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 100000000 # 100ms +min_rtt_calc_params: + interval: + seconds: 30 + request_count: 5 +)EOF"; + + auto controller = makeController(yaml); + EXPECT_EQ(controller->concurrencyLimit(), 1); + + // Force a minRTT of 5ms. + advancePastMinRTTStage(controller, yaml, std::chrono::milliseconds(5)); + EXPECT_EQ( + 5, stats_.gauge("test_prefix.min_rtt_msecs", Stats::Gauge::ImportMode::NeverImport).value()); + + // Ensure that the concurrency window increases on its own due to the headroom calculation. + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + EXPECT_GT(controller->concurrencyLimit(), 1); + + // Make it seem as if the recorded latencies are consistently lower than the measured minRTT. + // Ensure that it grows. + for (int recalcs = 0; recalcs < 10; ++recalcs) { + const auto last_concurrency = controller->concurrencyLimit(); + for (int ii = 1; ii <= 5; ++ii) { + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(4)); + } + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + EXPECT_GT(controller->concurrencyLimit(), last_concurrency); + } + + // Verify that the concurrency limit can now shrink as necessary. + for (int recalcs = 0; recalcs < 10; ++recalcs) { + const auto last_concurrency = controller->concurrencyLimit(); + for (int ii = 1; ii <= 5; ++ii) { + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(6)); + } + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + EXPECT_LT(controller->concurrencyLimit(), last_concurrency); + } +} + +TEST_F(GradientControllerTest, MaxGradientTest) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 3.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 100000000 # 100ms +min_rtt_calc_params: + interval: + seconds: 30 + request_count: 5 +)EOF"; + + auto controller = makeController(yaml); + EXPECT_EQ(controller->concurrencyLimit(), 1); + + // Force a minRTT of 5 seconds. + advancePastMinRTTStage(controller, yaml, std::chrono::seconds(5)); + + // circllhist approximates the percentiles, so we can expect it to be within a certain range. + EXPECT_THAT( + stats_.gauge("test_prefix.min_rtt_msecs", Stats::Gauge::ImportMode::NeverImport).value(), + AllOf(Ge(4950), Le(5050))); + + // Now verify max gradient value by forcing dramatically faster latency measurements.. + for (int ii = 1; ii <= 5; ++ii) { + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(4)); + } + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + EXPECT_EQ(3.0, + stats_.gauge("test_prefix.gradient", Stats::Gauge::ImportMode::NeverImport).value()); +} + +TEST_F(GradientControllerTest, MinRTTReturnToPreviousLimit) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 3.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 100000000 # 100ms +min_rtt_calc_params: + interval: + seconds: 30 + request_count: 5 +)EOF"; + + auto controller = makeController(yaml); + EXPECT_EQ(controller->concurrencyLimit(), 1); + + // Get initial minRTT measurement out of the way. + advancePastMinRTTStage(controller, yaml, std::chrono::milliseconds(5)); + + // Force the limit calculation to run a few times from some measurements. + for (int sample_iters = 0; sample_iters < 5; ++sample_iters) { + const auto last_concurrency = controller->concurrencyLimit(); + for (int ii = 1; ii <= 5; ++ii) { + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(4)); + } + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + // Verify the value is growing. + EXPECT_GT(controller->concurrencyLimit(), last_concurrency); + } + + const auto limit_val = controller->concurrencyLimit(); + + // Wait until the minRTT recalculation is triggered again and verify the limit drops. + time_system_.sleep(std::chrono::seconds(31)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + EXPECT_EQ(controller->concurrencyLimit(), 1); + + // 49 more requests should cause the minRTT to be done calculating. + for (int ii = 0; ii < 5; ++ii) { + EXPECT_EQ(controller->concurrencyLimit(), 1); + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(13)); + } + + // Check that we restored the old concurrency limit value. + EXPECT_EQ(limit_val, controller->concurrencyLimit()); +} + +TEST_F(GradientControllerTest, MinRTTRescheduleTest) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 3.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 100000000 # 100ms +min_rtt_calc_params: + interval: + seconds: 30 + request_count: 5 +)EOF"; + + auto controller = makeController(yaml); + EXPECT_EQ(controller->concurrencyLimit(), 1); + + // Get initial minRTT measurement out of the way. + advancePastMinRTTStage(controller, yaml, std::chrono::milliseconds(5)); + + // Force the limit calculation to run a few times from some measurements. + for (int sample_iters = 0; sample_iters < 5; ++sample_iters) { + const auto last_concurrency = controller->concurrencyLimit(); + for (int ii = 1; ii <= 5; ++ii) { + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(4)); + } + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + // Verify the value is growing. + EXPECT_GT(controller->concurrencyLimit(), last_concurrency); + } + + // Wait until the minRTT recalculation is triggered again and verify the limit drops. + time_system_.sleep(std::chrono::seconds(31)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + EXPECT_EQ(controller->concurrencyLimit(), 1); + + // Verify sample recalculation doesn't occur during the minRTT window. + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + EXPECT_EQ(controller->concurrencyLimit(), 1); +} + +TEST_F(GradientControllerTest, NoSamplesTest) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 3.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 100000000 # 100ms +min_rtt_calc_params: + interval: + seconds: 30 + request_count: 5 +)EOF"; + + auto controller = makeController(yaml); + EXPECT_EQ(controller->concurrencyLimit(), 1); + + // Get minRTT measurement out of the way. + advancePastMinRTTStage(controller, yaml, std::chrono::milliseconds(5)); + + // Force the limit calculation to run a few times from some measurements. + for (int sample_iters = 0; sample_iters < 5; ++sample_iters) { + const auto last_concurrency = controller->concurrencyLimit(); + for (int ii = 1; ii <= 5; ++ii) { + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(4)); + } + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + // Verify the value is growing. + EXPECT_GT(controller->concurrencyLimit(), last_concurrency); + } + + // Now we make sure that the limit value doesn't change in the absence of samples. + for (int sample_iters = 0; sample_iters < 5; ++sample_iters) { + const auto old_limit = controller->concurrencyLimit(); + time_system_.sleep(std::chrono::milliseconds(101)); + dispatcher_->run(Event::Dispatcher::RunType::Block); + EXPECT_EQ(old_limit, controller->concurrencyLimit()); + } +} + +TEST_F(GradientControllerTest, TimerAccuracyTest) { + const std::string yaml = R"EOF( +sample_aggregate_percentile: + value: 50 +concurrency_limit_params: + max_gradient: 3.0 + max_concurrency_limit: + concurrency_update_interval: + nanos: 123000000 # 123ms +min_rtt_calc_params: + interval: + seconds: 45 + request_count: 5 +)EOF"; + + // Verify the configuration affects the timers that are kicked off. + NiceMock fake_dispatcher; + auto sample_timer = new NiceMock; + auto rtt_timer = new NiceMock; + + // Expect the sample timer to trigger start immediately upon controller creation. + EXPECT_CALL(fake_dispatcher, createTimer_(_)) + .Times(2) + .WillOnce(Return(rtt_timer)) + .WillOnce(Return(sample_timer)); + EXPECT_CALL(*sample_timer, enableTimer(std::chrono::milliseconds(123), _)); + auto controller = std::make_shared(makeConfig(yaml), fake_dispatcher, + runtime_, "test_prefix.", stats_); + + // Set the minRTT- this will trigger the timer for the next minRTT calculation. + EXPECT_CALL(*rtt_timer, enableTimer(std::chrono::milliseconds(45000), _)); + for (int ii = 1; ii <= 6; ++ii) { + tryForward(controller, true); + controller->recordLatencySample(std::chrono::milliseconds(5)); + } +} + +} // namespace +} // namespace ConcurrencyController +} // namespace AdaptiveConcurrency +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/filters/http/buffer/BUILD b/test/extensions/filters/http/buffer/BUILD index 0cbbdb99fe..64c60dcba9 100644 --- a/test/extensions/filters/http/buffer/BUILD +++ b/test/extensions/filters/http/buffer/BUILD @@ -27,6 +27,7 @@ envoy_extension_cc_test( "//test/mocks/protobuf:protobuf_mocks", "//test/mocks/thread_local:thread_local_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:test_runtime_lib", ], ) diff --git a/test/extensions/filters/http/buffer/buffer_filter_test.cc b/test/extensions/filters/http/buffer/buffer_filter_test.cc index 12b715f5d2..393ee47235 100644 --- a/test/extensions/filters/http/buffer/buffer_filter_test.cc +++ b/test/extensions/filters/http/buffer/buffer_filter_test.cc @@ -12,22 +12,15 @@ #include "test/mocks/buffer/mocks.h" #include "test/mocks/http/mocks.h" -#include "test/mocks/init/mocks.h" -#include "test/mocks/local_info/mocks.h" -#include "test/mocks/protobuf/mocks.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/thread_local/mocks.h" #include "test/test_common/printers.h" +#include "test/test_common/test_runtime.h" #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::DoAll; using testing::InSequence; using testing::NiceMock; using testing::Return; -using testing::SaveArg; namespace Envoy { namespace Extensions { @@ -42,16 +35,8 @@ class BufferFilterTest : public testing::Test { return std::make_shared(proto_config); } - BufferFilterTest() : config_(setupConfig()), filter_(config_), api_(Api::createApiForTest()) { + BufferFilterTest() : config_(setupConfig()), filter_(config_) { filter_.setDecoderFilterCallbacks(callbacks_); - - // Create a runtime loader, so that tests can manually manipulate runtime - // guarded features. - envoy::config::bootstrap::v2::LayeredRuntime config; - config.add_layers()->mutable_admin_layer(); - loader_ = std::make_unique(Runtime::LoaderPtr{ - new Runtime::LoaderImpl(dispatcher_, tls_, config, local_info_, init_manager_, store_, - generator_, validation_visitor_, *api_)}); } void routeLocalConfig(const Router::RouteSpecificFilterConfig* route_settings, @@ -66,15 +51,8 @@ class BufferFilterTest : public testing::Test { NiceMock callbacks_; BufferFilterConfigSharedPtr config_; BufferFilter filter_; - Event::MockDispatcher dispatcher_; - NiceMock tls_; - Stats::IsolatedStoreImpl store_; - Runtime::MockRandomGenerator generator_; - Api::ApiPtr api_; - NiceMock local_info_; - Init::MockManager init_manager_; - NiceMock validation_visitor_; - std::unique_ptr loader_; + // Create a runtime loader, so that tests can manually manipulate runtime guarded features. + TestScopedRuntime scoped_runtime; }; TEST_F(BufferFilterTest, HeaderOnlyRequest) { diff --git a/test/extensions/filters/http/common/jwks_fetcher_test.cc b/test/extensions/filters/http/common/jwks_fetcher_test.cc index 6932eee59f..feaea0e29b 100644 --- a/test/extensions/filters/http/common/jwks_fetcher_test.cc +++ b/test/extensions/filters/http/common/jwks_fetcher_test.cc @@ -11,7 +11,6 @@ #include "test/test_common/utility.h" using ::envoy::api::v2::core::HttpUri; -using ::google::jwt_verify::Status; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/http/cors/cors_filter_integration_test.cc b/test/extensions/filters/http/cors/cors_filter_integration_test.cc index bca2bcff0c..06a9120c8c 100644 --- a/test/extensions/filters/http/cors/cors_filter_integration_test.cc +++ b/test/extensions/filters/http/cors/cors_filter_integration_test.cc @@ -34,7 +34,11 @@ class CorsFilterIntegrationTest : public testing::TestWithParamadd_routes(); route->mutable_match()->set_prefix("/no-cors"); route->mutable_route()->set_cluster("cluster_0"); - route->mutable_route()->mutable_cors()->mutable_enabled()->set_value(false); + route->mutable_route() + ->mutable_cors() + ->mutable_filter_enabled() + ->mutable_default_value() + ->set_numerator(0); } { @@ -50,6 +54,8 @@ class CorsFilterIntegrationTest : public testing::TestWithParamadd_routes(); route->mutable_match()->set_prefix("/cors-credentials-allowed"); route->mutable_route()->set_cluster("cluster_0"); @@ -63,7 +69,10 @@ class CorsFilterIntegrationTest : public testing::TestWithParammutable_match()->set_prefix("/cors-allow-origin-regex"); route->mutable_route()->set_cluster("cluster_0"); auto* cors = route->mutable_route()->mutable_cors(); - cors->add_allow_origin_regex(".*\\.envoyproxy\\.io"); + auto* safe_regex = + cors->mutable_allow_origin_string_match()->Add()->mutable_safe_regex(); + safe_regex->mutable_google_re2(); + safe_regex->set_regex(".*\\.envoyproxy\\.io"); } { @@ -111,7 +120,7 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, CorsFilterIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); -TEST_P(CorsFilterIntegrationTest, TestVHostConfigSuccess) { +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestVHostConfigSuccess)) { testPreflight( Http::TestHeaderMapImpl{ {":method", "OPTIONS"}, @@ -131,7 +140,7 @@ TEST_P(CorsFilterIntegrationTest, TestVHostConfigSuccess) { }); } -TEST_P(CorsFilterIntegrationTest, TestRouteConfigSuccess) { +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestRouteConfigSuccess)) { testPreflight( Http::TestHeaderMapImpl{ {":method", "OPTIONS"}, @@ -152,7 +161,7 @@ TEST_P(CorsFilterIntegrationTest, TestRouteConfigSuccess) { }); } -TEST_P(CorsFilterIntegrationTest, TestRouteConfigBadOrigin) { +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestRouteConfigBadOrigin)) { testNormalRequest( Http::TestHeaderMapImpl{ {":method", "OPTIONS"}, @@ -169,7 +178,7 @@ TEST_P(CorsFilterIntegrationTest, TestRouteConfigBadOrigin) { }); } -TEST_P(CorsFilterIntegrationTest, TestCorsDisabled) { +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestCorsDisabled)) { testNormalRequest( Http::TestHeaderMapImpl{ {":method", "OPTIONS"}, @@ -186,7 +195,34 @@ TEST_P(CorsFilterIntegrationTest, TestCorsDisabled) { }); } -TEST_P(CorsFilterIntegrationTest, TestEncodeHeaders) { +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestLegacyCorsDisabled)) { + config_helper_.addConfigModifier( + [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& hcm) + -> void { + auto* route_config = hcm.mutable_route_config(); + auto* virtual_host = route_config->mutable_virtual_hosts(0); + auto* route = virtual_host->add_routes(); + route->mutable_match()->set_prefix("/legacy-no-cors"); + route->mutable_route()->set_cluster("cluster_0"); + route->mutable_route()->mutable_cors()->mutable_enabled()->set_value(false); + }); + testNormalRequest( + Http::TestHeaderMapImpl{ + {":method", "OPTIONS"}, + {":path", "/legacy-no-cors/test"}, + {":scheme", "http"}, + {":authority", "test-host"}, + {"access-control-request-method", "GET"}, + {"origin", "test-origin"}, + }, + Http::TestHeaderMapImpl{ + {"server", "envoy"}, + {"content-length", "0"}, + {":status", "200"}, + }); +} + +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestEncodeHeaders)) { testNormalRequest( Http::TestHeaderMapImpl{ {":method", "GET"}, @@ -203,7 +239,7 @@ TEST_P(CorsFilterIntegrationTest, TestEncodeHeaders) { }); } -TEST_P(CorsFilterIntegrationTest, TestEncodeHeadersCredentialsAllowed) { +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestEncodeHeadersCredentialsAllowed)) { testNormalRequest( Http::TestHeaderMapImpl{ {":method", "GET"}, @@ -221,7 +257,7 @@ TEST_P(CorsFilterIntegrationTest, TestEncodeHeadersCredentialsAllowed) { }); } -TEST_P(CorsFilterIntegrationTest, TestAllowedOriginRegex) { +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestAllowedOriginRegex)) { testNormalRequest( Http::TestHeaderMapImpl{ {":method", "GET"}, @@ -239,7 +275,7 @@ TEST_P(CorsFilterIntegrationTest, TestAllowedOriginRegex) { }); } -TEST_P(CorsFilterIntegrationTest, TestExposeHeaders) { +TEST_P(CorsFilterIntegrationTest, DEPRECATED_FEATURE_TEST(TestExposeHeaders)) { testNormalRequest( Http::TestHeaderMapImpl{ {":method", "GET"}, diff --git a/test/extensions/filters/http/cors/cors_filter_test.cc b/test/extensions/filters/http/cors/cors_filter_test.cc index d56cb8ac6a..503f624dd7 100644 --- a/test/extensions/filters/http/cors/cors_filter_test.cc +++ b/test/extensions/filters/http/cors/cors_filter_test.cc @@ -1,3 +1,4 @@ +#include "common/common/matchers.h" #include "common/http/header_map_impl.h" #include "extensions/filters/http/cors/cors_filter.h" @@ -11,18 +12,28 @@ #include "gtest/gtest.h" using testing::_; -using testing::DoAll; -using testing::InSequence; -using testing::Invoke; using testing::NiceMock; using testing::Return; -using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Extensions { namespace HttpFilters { namespace Cors { +namespace { + +Matchers::StringMatcherPtr makeExactStringMatcher(const std::string& exact_match) { + envoy::type::matcher::StringMatcher config; + config.set_exact(exact_match); + return std::make_unique(config); +} + +Matchers::StringMatcherPtr makeStdRegexStringMatcher(const std::string& regex) { + envoy::type::matcher::StringMatcher config; + config.set_regex(regex); + return std::make_unique(config); +} + +} // namespace class CorsFilterTest : public testing::Test { public: @@ -30,7 +41,7 @@ class CorsFilterTest : public testing::Test { cors_policy_ = std::make_unique(); cors_policy_->enabled_ = true; cors_policy_->shadow_enabled_ = false; - cors_policy_->allow_origin_.emplace_back("*"); + cors_policy_->allow_origins_.emplace_back(makeExactStringMatcher("*")); cors_policy_->allow_methods_ = "GET"; cors_policy_->allow_headers_ = "content-type"; cors_policy_->expose_headers_ = "content-type"; @@ -299,8 +310,8 @@ TEST_F(CorsFilterTest, OptionsRequestNotMatchingOrigin) { Http::TestHeaderMapImpl request_headers{ {":method", "OPTIONS"}, {"origin", "test-host"}, {"access-control-request-method", "GET"}}; - cors_policy_->allow_origin_.clear(); - cors_policy_->allow_origin_.emplace_back("localhost"); + cors_policy_->allow_origins_.clear(); + cors_policy_->allow_origins_.emplace_back(makeExactStringMatcher("localhost")); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, false)).Times(0); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, false)); @@ -319,7 +330,7 @@ TEST_F(CorsFilterTest, OptionsRequestEmptyOriginList) { Http::TestHeaderMapImpl request_headers{ {":method", "OPTIONS"}, {"origin", "test-host"}, {"access-control-request-method", "GET"}}; - cors_policy_->allow_origin_.clear(); + cors_policy_->allow_origins_.clear(); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, false)).Times(0); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, false)); @@ -339,8 +350,8 @@ TEST_F(CorsFilterTest, ValidOptionsRequestWithAllowCredentialsTrue) { {":method", "OPTIONS"}, {"origin", "localhost"}, {"access-control-request-method", "GET"}}; cors_policy_->allow_credentials_ = true; - cors_policy_->allow_origin_.clear(); - cors_policy_->allow_origin_.emplace_back("localhost"); + cors_policy_->allow_origins_.clear(); + cors_policy_->allow_origins_.emplace_back(makeExactStringMatcher("localhost")); Http::TestHeaderMapImpl response_headers{ {":status", "200"}, @@ -485,8 +496,8 @@ TEST_F(CorsFilterTest, EncodeWithAllowCredentialsFalse) { TEST_F(CorsFilterTest, EncodeWithNonMatchingOrigin) { Http::TestHeaderMapImpl request_headers{{"origin", "test-host"}}; - cors_policy_->allow_origin_.clear(); - cors_policy_->allow_origin_.emplace_back("localhost"); + cors_policy_->allow_origins_.clear(); + cors_policy_->allow_origins_.emplace_back(makeExactStringMatcher("localhost")); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, false)); EXPECT_EQ(Http::FilterDataStatus::Continue, filter_.decodeData(data_, false)); @@ -647,8 +658,8 @@ TEST_F(CorsFilterTest, OptionsRequestMatchingOriginByRegex) { {"access-control-max-age", "0"}, }; - cors_policy_->allow_origin_.clear(); - cors_policy_->allow_origin_regex_.emplace_back(std::regex(".*")); + cors_policy_->allow_origins_.clear(); + cors_policy_->allow_origins_.emplace_back(makeStdRegexStringMatcher(".*")); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(HeaderMapEqualRef(&response_headers), true)); @@ -670,8 +681,8 @@ TEST_F(CorsFilterTest, OptionsRequestNotMatchingOriginByRegex) { {"origin", "www.envoyproxy.com"}, {"access-control-request-method", "GET"}}; - cors_policy_->allow_origin_.clear(); - cors_policy_->allow_origin_regex_.emplace_back(std::regex(".*.envoyproxy.io")); + cors_policy_->allow_origins_.clear(); + cors_policy_->allow_origins_.emplace_back(makeStdRegexStringMatcher(".*.envoyproxy.io")); EXPECT_CALL(decoder_callbacks_, encodeHeaders_(_, false)).Times(0); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_.decodeHeaders(request_headers, false)); diff --git a/test/extensions/filters/http/csrf/csrf_filter_test.cc b/test/extensions/filters/http/csrf/csrf_filter_test.cc index 99835d935c..22ab597248 100644 --- a/test/extensions/filters/http/csrf/csrf_filter_test.cc +++ b/test/extensions/filters/http/csrf/csrf_filter_test.cc @@ -11,13 +11,8 @@ #include "gtest/gtest.h" using testing::_; -using testing::DoAll; -using testing::InSequence; -using testing::Invoke; using testing::NiceMock; using testing::Return; -using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc index 076fca7e35..2c2afbf121 100644 --- a/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/http/dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -198,9 +198,9 @@ TEST_P(ProxyFilterIntegrationTest, UpstreamTls) { auto response = codec_client_->makeHeaderOnlyRequest(request_headers); waitForNextUpstreamRequest(); - const Extensions::TransportSockets::Tls::SslSocket* ssl_socket = - dynamic_cast( - fake_upstream_connection_->connection().ssl()); + const Extensions::TransportSockets::Tls::SslSocketInfo* ssl_socket = + dynamic_cast( + fake_upstream_connection_->connection().ssl().get()); EXPECT_STREQ("localhost", SSL_get_servername(ssl_socket->rawSslForTest(), TLSEXT_NAMETYPE_host_name)); @@ -224,9 +224,9 @@ TEST_P(ProxyFilterIntegrationTest, UpstreamTlsWithIpHost) { waitForNextUpstreamRequest(); // No SNI for IP hosts. - const Extensions::TransportSockets::Tls::SslSocket* ssl_socket = - dynamic_cast( - fake_upstream_connection_->connection().ssl()); + const Extensions::TransportSockets::Tls::SslSocketInfo* ssl_socket = + dynamic_cast( + fake_upstream_connection_->connection().ssl().get()); EXPECT_STREQ(nullptr, SSL_get_servername(ssl_socket->rawSslForTest(), TLSEXT_NAMETYPE_host_name)); upstream_request_->encodeHeaders(default_response_headers_, true); diff --git a/test/extensions/filters/http/dynamo/dynamo_filter_test.cc b/test/extensions/filters/http/dynamo/dynamo_filter_test.cc index eb93be0c67..ce8638ccbe 100644 --- a/test/extensions/filters/http/dynamo/dynamo_filter_test.cc +++ b/test/extensions/filters/http/dynamo/dynamo_filter_test.cc @@ -19,7 +19,6 @@ using testing::_; using testing::NiceMock; using testing::Property; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/http/dynamo/dynamo_stats_test.cc b/test/extensions/filters/http/dynamo/dynamo_stats_test.cc index 0458c5dc38..d479f4a109 100644 --- a/test/extensions/filters/http/dynamo/dynamo_stats_test.cc +++ b/test/extensions/filters/http/dynamo/dynamo_stats_test.cc @@ -7,10 +7,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; -using testing::NiceMock; -using testing::Return; - namespace Envoy { namespace Extensions { namespace HttpFilters { diff --git a/test/extensions/filters/http/ext_authz/config_test.cc b/test/extensions/filters/http/ext_authz/config_test.cc index 52942c6a7d..29e4a1097d 100644 --- a/test/extensions/filters/http/ext_authz/config_test.cc +++ b/test/extensions/filters/http/ext_authz/config_test.cc @@ -31,6 +31,7 @@ TEST(HttpExtAuthzConfigTest, CorrectProtoGrpc) { TestUtility::loadFromYaml(yaml, *proto_config); testing::StrictMock context; + EXPECT_CALL(context, messageValidationVisitor()).Times(1); EXPECT_CALL(context, localInfo()).Times(1); EXPECT_CALL(context, clusterManager()).Times(1); EXPECT_CALL(context, runtime()).Times(1); @@ -85,6 +86,7 @@ TEST(HttpExtAuthzConfigTest, CorrectProtoHttp) { ProtobufTypes::MessagePtr proto_config = factory.createEmptyConfigProto(); TestUtility::loadFromYaml(yaml, *proto_config); testing::StrictMock context; + EXPECT_CALL(context, messageValidationVisitor()).Times(1); EXPECT_CALL(context, localInfo()).Times(1); EXPECT_CALL(context, clusterManager()).Times(1); EXPECT_CALL(context, runtime()).Times(1); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index d82354c0e9..9207f2e9c6 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -306,7 +306,7 @@ TEST_F(HttpFilterTest, BadConfig) { envoy::config::filter::http::ext_authz::v2::ExtAuthz proto_config{}; TestUtility::loadFromYaml(filter_config, proto_config); EXPECT_THROW( - MessageUtil::downcastAndValidate( + TestUtility::downcastAndValidate( proto_config), ProtoValidationException); } @@ -768,6 +768,68 @@ TEST_F(HttpFilterTest, NoClearCacheRouteDeniedResponse) { EXPECT_EQ("ext_authz_denied", filter_callbacks_.details_); } +// Verifies that specified metadata is passed along in the check request +TEST_F(HttpFilterTest, MetadataContext) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + metadata_context_namespaces: + - jazz.sax + - rock.guitar + - hiphop.drums + )EOF"); + + const std::string yaml = R"EOF( + filter_metadata: + jazz.sax: + coltrane: john + parker: charlie + jazz.piano: + monk: thelonious + hancock: herbie + rock.guitar: + hendrix: jimi + richards: keith + )EOF"; + + envoy::api::v2::core::Metadata metadata; + TestUtility::loadFromYaml(yaml, metadata); + ON_CALL(filter_callbacks_.stream_info_, dynamicMetadata()).WillByDefault(ReturnRef(metadata)); + + prepareCheck(); + + envoy::service::auth::v2::CheckRequest check_request; + EXPECT_CALL(*client_, check(_, _, _)) + .WillOnce(WithArgs<1>(Invoke([&](const envoy::service::auth::v2::CheckRequest& check_param) + -> void { check_request = check_param; }))); + + filter_->decodeHeaders(request_headers_, false); + Http::MetadataMap metadata_map{{"metadata", "metadata"}}; + EXPECT_EQ(Http::FilterMetadataStatus::Continue, filter_->decodeMetadata(metadata_map)); + + EXPECT_EQ("john", check_request.attributes() + .metadata_context() + .filter_metadata() + .at("jazz.sax") + .fields() + .at("coltrane") + .string_value()); + + EXPECT_EQ("jimi", check_request.attributes() + .metadata_context() + .filter_metadata() + .at("rock.guitar") + .fields() + .at("hendrix") + .string_value()); + + EXPECT_EQ(0, check_request.attributes().metadata_context().filter_metadata().count("jazz.piano")); + + EXPECT_EQ(0, + check_request.attributes().metadata_context().filter_metadata().count("hiphop.drums")); +} + // ------------------- // Parameterized Tests // ------------------- diff --git a/test/extensions/filters/http/fault/config_test.cc b/test/extensions/filters/http/fault/config_test.cc index 0417b007c0..af4e626a58 100644 --- a/test/extensions/filters/http/fault/config_test.cc +++ b/test/extensions/filters/http/fault/config_test.cc @@ -8,7 +8,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::Invoke; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/http/fault/fault_filter_test.cc b/test/extensions/filters/http/fault/fault_filter_test.cc index e6cbf4ace6..24442c4774 100644 --- a/test/extensions/filters/http/fault/fault_filter_test.cc +++ b/test/extensions/filters/http/fault/fault_filter_test.cc @@ -25,13 +25,11 @@ #include "gtest/gtest.h" using testing::_; -using testing::DoAll; -using testing::Invoke; +using testing::AnyNumber; using testing::Matcher; using testing::NiceMock; using testing::Return; using testing::ReturnRef; -using testing::WithArgs; namespace Envoy { namespace Extensions { @@ -136,19 +134,21 @@ class FaultFilterTest : public testing::Test { filter_ = std::make_unique(config_); filter_->setDecoderFilterCallbacks(decoder_filter_callbacks_); filter_->setEncoderFilterCallbacks(encoder_filter_callbacks_); + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(AnyNumber()); } void SetUpTest(const std::string json) { SetUpTest(convertJsonStrToProtoConfig(json)); } void expectDelayTimer(uint64_t duration_ms) { timer_ = new Event::MockTimer(&decoder_filter_callbacks_.dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(duration_ms))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(duration_ms), _)); EXPECT_CALL(*timer_, disableTimer()); } void TestPerFilterConfigFault(const Router::RouteSpecificFilterConfig* route_fault, const Router::RouteSpecificFilterConfig* vhost_fault); + Stats::IsolatedStoreImpl stats_; FaultFilterConfigSharedPtr config_; std::unique_ptr filter_; NiceMock decoder_filter_callbacks_; @@ -156,7 +156,6 @@ class FaultFilterTest : public testing::Test { Http::TestHeaderMapImpl request_headers_; Http::TestHeaderMapImpl response_headers_; Buffer::OwnedImpl data_; - Stats::IsolatedStoreImpl stats_; NiceMock runtime_; Event::MockTimer* timer_{}; Event::SimulatedTimeSystem time_system_; @@ -465,6 +464,7 @@ TEST_F(FaultFilterTest, DelayForDownstreamCluster) { EXPECT_CALL(decoder_filter_callbacks_, continueDecoding()); EXPECT_EQ(Http::FilterDataStatus::StopIterationAndWatermark, filter_->decodeData(data_, false)); + EXPECT_CALL(decoder_filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); timer_->invokeCallback(); EXPECT_EQ(Http::FilterTrailersStatus::Continue, filter_->decodeTrailers(request_headers_)); @@ -766,7 +766,7 @@ TEST_F(FaultFilterTest, TimerResetAfterStreamReset) { SCOPED_TRACE("FixedDelayWithStreamReset"); timer_ = new Event::MockTimer(&decoder_filter_callbacks_.dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5000UL))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5000UL), _)); EXPECT_CALL(decoder_filter_callbacks_.stream_info_, setResponseFlag(StreamInfo::ResponseFlag::DelayInjected)); @@ -1053,7 +1053,7 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { // Send a small amount of data which should be within limit. Buffer::OwnedImpl data1("hello"); - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0))); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(data1, false)); EXPECT_CALL(encoder_filter_callbacks_, injectEncodedDataToFilterChain(BufferStringEqual("hello"), false)); @@ -1064,11 +1064,11 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { // Send 1152 bytes of data which is 1s + 2 refill cycles of data. EXPECT_CALL(encoder_filter_callbacks_, onEncoderFilterAboveWriteBufferHighWatermark()); - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0))); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); Buffer::OwnedImpl data2(std::string(1152, 'a')); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(data2, false)); - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63))); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63), _)); EXPECT_CALL(encoder_filter_callbacks_, onEncoderFilterBelowWriteBufferLowWatermark()); EXPECT_CALL(encoder_filter_callbacks_, injectEncodedDataToFilterChain(BufferStringEqual(std::string(1024, 'a')), false)); @@ -1076,7 +1076,7 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { // Fire timer, also advance time. time_system_.sleep(std::chrono::milliseconds(63)); - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63))); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63), _)); EXPECT_CALL(encoder_filter_callbacks_, injectEncodedDataToFilterChain(BufferStringEqual(std::string(64, 'a')), false)); token_timer->invokeCallback(); @@ -1087,7 +1087,7 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { // Fire timer, also advance time. time_system_.sleep(std::chrono::milliseconds(63)); - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63))); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(63), _)); EXPECT_CALL(encoder_filter_callbacks_, injectEncodedDataToFilterChain(BufferStringEqual(std::string(64, 'a')), false)); token_timer->invokeCallback(); @@ -1102,7 +1102,7 @@ TEST_F(FaultFilterRateLimitTest, ResponseRateLimitEnabled) { time_system_.sleep(std::chrono::seconds(1)); // Now send 1024 in one shot with end_stream true which should go through and end the stream. - EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0))); + EXPECT_CALL(*token_timer, enableTimer(std::chrono::milliseconds(0), _)); Buffer::OwnedImpl data4(std::string(1024, 'c')); EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter_->encodeData(data4, true)); EXPECT_CALL(encoder_filter_callbacks_, diff --git a/test/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter_test.cc b/test/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter_test.cc index aedebdfa6f..644157a15f 100644 --- a/test/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter_test.cc +++ b/test/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter_test.cc @@ -14,11 +14,9 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; using testing::Return; using testing::ReturnPointee; -using testing::ReturnRef; namespace Envoy { namespace Extensions { @@ -36,7 +34,7 @@ class GrpcHttp1BridgeFilterTest : public testing::Test { ~GrpcHttp1BridgeFilterTest() override { filter_.onDestroy(); } - Envoy::Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Grpc::ContextImpl context_; Http1BridgeFilter filter_; NiceMock decoder_callbacks_; diff --git a/test/extensions/filters/http/grpc_http1_reverse_bridge/reverse_bridge_test.cc b/test/extensions/filters/http/grpc_http1_reverse_bridge/reverse_bridge_test.cc index 137970bb6b..503c2c1d44 100644 --- a/test/extensions/filters/http/grpc_http1_reverse_bridge/reverse_bridge_test.cc +++ b/test/extensions/filters/http/grpc_http1_reverse_bridge/reverse_bridge_test.cc @@ -20,7 +20,6 @@ using Envoy::Http::HeaderValueOf; using testing::_; -using testing::Return; using testing::ReturnRef; namespace Envoy { diff --git a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc index 14f491375a..76fed14e16 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/grpc_json_transcoder_integration_test.cc @@ -11,7 +11,6 @@ #include "absl/strings/match.h" #include "gtest/gtest.h" -using Envoy::Protobuf::Message; using Envoy::Protobuf::TextFormat; using Envoy::Protobuf::util::MessageDifferencer; using Envoy::ProtobufUtil::Status; diff --git a/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc b/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc index 673a13e0a8..35d3bc2cbe 100644 --- a/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc +++ b/test/extensions/filters/http/grpc_json_transcoder/json_transcoder_filter_test.cc @@ -23,16 +23,12 @@ using testing::_; using testing::Invoke; using testing::NiceMock; -using testing::Return; -using testing::ReturnPointee; -using testing::ReturnRef; using Envoy::Protobuf::MethodDescriptor; using Envoy::Protobuf::FileDescriptorProto; using Envoy::Protobuf::FileDescriptorSet; using Envoy::Protobuf::util::MessageDifferencer; -using Envoy::ProtobufUtil::Status; using Envoy::ProtobufUtil::error::Code; using google::api::HttpRule; using google::grpc::transcoding::Transcoder; diff --git a/test/extensions/filters/http/grpc_web/grpc_web_filter_test.cc b/test/extensions/filters/http/grpc_web/grpc_web_filter_test.cc index e0a501f48d..b20f517ee5 100644 --- a/test/extensions/filters/http/grpc_web/grpc_web_filter_test.cc +++ b/test/extensions/filters/http/grpc_web/grpc_web_filter_test.cc @@ -106,7 +106,7 @@ class GrpcWebFilterTest : public testing::TestWithParamvalue().getStringView()); } - Envoy::Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Grpc::ContextImpl grpc_context_; GrpcWebFilter filter_; NiceMock decoder_callbacks_; diff --git a/test/extensions/filters/http/health_check/health_check_test.cc b/test/extensions/filters/http/health_check/health_check_test.cc index 85f2d242c3..5c4472e669 100644 --- a/test/extensions/filters/http/health_check/health_check_test.cc +++ b/test/extensions/filters/http/health_check/health_check_test.cc @@ -16,13 +16,10 @@ #include "gtest/gtest.h" using testing::_; -using testing::DoAll; using testing::Eq; using testing::Invoke; using testing::NiceMock; using testing::Return; -using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Extensions { @@ -37,7 +34,7 @@ class HealthCheckFilterTest : public testing::Test { if (caching) { cache_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*cache_timer_, enableTimer(_)); + EXPECT_CALL(*cache_timer_, enableTimer(_, _)); cache_manager_.reset(new HealthCheckCacheManager(dispatcher_, std::chrono::milliseconds(1))); } @@ -47,11 +44,11 @@ class HealthCheckFilterTest : public testing::Test { void prepareFilter( bool pass_through, ClusterMinHealthyPercentagesConstSharedPtr cluster_min_healthy_percentages = nullptr) { - header_data_ = std::make_shared>(); + header_data_ = std::make_shared>(); envoy::api::v2::route::HeaderMatcher matcher; matcher.set_name(":path"); matcher.set_exact_match("/healthcheck"); - header_data_->emplace_back(matcher); + header_data_->emplace_back(std::make_unique(matcher)); filter_ = std::make_unique(context_, pass_through, cache_manager_, header_data_, cluster_min_healthy_percentages); filter_->setDecoderFilterCallbacks(callbacks_); @@ -359,7 +356,7 @@ TEST_F(HealthCheckFilterCachingTest, All) { filter_->decodeHeaders(request_headers_, true)); // Fire the timer, this should result in the next request going through. - EXPECT_CALL(*cache_timer_, enableTimer(_)); + EXPECT_CALL(*cache_timer_, enableTimer(_, _)); cache_timer_->invokeCallback(); prepareFilter(true); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, true)); @@ -393,7 +390,7 @@ TEST_F(HealthCheckFilterCachingTest, DegradedHeader) { filter_->decodeHeaders(request_headers_, true)); // Fire the timer, this should result in the next request going through. - EXPECT_CALL(*cache_timer_, enableTimer(_)); + EXPECT_CALL(*cache_timer_, enableTimer(_, _)); cache_timer_->invokeCallback(); prepareFilter(true); EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(request_headers_, true)); diff --git a/test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc b/test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc index 50679f090c..c5901986db 100644 --- a/test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc +++ b/test/extensions/filters/http/ip_tagging/ip_tagging_filter_test.cc @@ -16,7 +16,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::Return; using testing::ReturnRef; @@ -52,11 +51,11 @@ request_type: internal ~IpTaggingFilterTest() override { filter_->onDestroy(); } + NiceMock stats_; IpTaggingFilterConfigSharedPtr config_; std::unique_ptr filter_; NiceMock filter_callbacks_; Buffer::OwnedImpl data_; - NiceMock stats_; NiceMock runtime_; }; diff --git a/test/extensions/filters/http/jwt_authn/matcher_test.cc b/test/extensions/filters/http/jwt_authn/matcher_test.cc index 0ca3e60350..360ac000f4 100644 --- a/test/extensions/filters/http/jwt_authn/matcher_test.cc +++ b/test/extensions/filters/http/jwt_authn/matcher_test.cc @@ -55,6 +55,28 @@ TEST_F(MatcherTest, TestMatchRegex) { EXPECT_FALSE(matcher->matches(headers)); } +TEST_F(MatcherTest, TestMatchSafeRegex) { + const char config[] = R"( +match: + safe_regex: + google_re2: {} + regex: "/[^c][au]t")"; + + RequirementRule rule; + TestUtility::loadFromYaml(config, rule); + MatcherConstPtr matcher = Matcher::create(rule); + auto headers = TestHeaderMapImpl{{":path", "/but"}}; + EXPECT_TRUE(matcher->matches(headers)); + headers = TestHeaderMapImpl{{":path", "/mat?ok=bye"}}; + EXPECT_TRUE(matcher->matches(headers)); + headers = TestHeaderMapImpl{{":path", "/maut"}}; + EXPECT_FALSE(matcher->matches(headers)); + headers = TestHeaderMapImpl{{":path", "/cut"}}; + EXPECT_FALSE(matcher->matches(headers)); + headers = TestHeaderMapImpl{{":path", "/mut/"}}; + EXPECT_FALSE(matcher->matches(headers)); +} + TEST_F(MatcherTest, TestMatchPath) { const char config[] = R"(match: path: "/match" diff --git a/test/extensions/filters/http/lua/lua_filter_test.cc b/test/extensions/filters/http/lua/lua_filter_test.cc index ea09b1c38e..a6bdafe76e 100644 --- a/test/extensions/filters/http/lua/lua_filter_test.cc +++ b/test/extensions/filters/http/lua/lua_filter_test.cc @@ -22,7 +22,6 @@ using testing::Eq; using testing::InSequence; using testing::Invoke; using testing::Return; -using testing::ReturnPointee; using testing::ReturnRef; using testing::StrEq; @@ -80,8 +79,9 @@ class LuaHttpFilterTest : public testing::Test { } void setupSecureConnection(const bool secure) { + ssl_ = std::make_shared>(); EXPECT_CALL(decoder_callbacks_, connection()).WillOnce(Return(&connection_)); - EXPECT_CALL(Const(connection_), ssl()).Times(1).WillOnce(Return(secure ? &ssl_ : nullptr)); + EXPECT_CALL(Const(connection_), ssl()).Times(1).WillOnce(Return(secure ? ssl_ : nullptr)); } void setupMetadata(const std::string& yaml) { @@ -97,7 +97,7 @@ class LuaHttpFilterTest : public testing::Test { Http::MockStreamDecoderFilterCallbacks decoder_callbacks_; Http::MockStreamEncoderFilterCallbacks encoder_callbacks_; envoy::api::v2::core::Metadata metadata_; - NiceMock ssl_; + std::shared_ptr> ssl_; NiceMock connection_; NiceMock stream_info_; diff --git a/test/extensions/filters/http/lua/wrappers_test.cc b/test/extensions/filters/http/lua/wrappers_test.cc index a1e1515931..3b934e4981 100644 --- a/test/extensions/filters/http/lua/wrappers_test.cc +++ b/test/extensions/filters/http/lua/wrappers_test.cc @@ -8,7 +8,6 @@ #include "test/test_common/utility.h" using testing::InSequence; -using testing::Return; using testing::ReturnPointee; namespace Envoy { diff --git a/test/extensions/filters/http/original_src/original_src_config_factory_test.cc b/test/extensions/filters/http/original_src/original_src_config_factory_test.cc index e581bfbd59..ea164cacc9 100644 --- a/test/extensions/filters/http/original_src/original_src_config_factory_test.cc +++ b/test/extensions/filters/http/original_src/original_src_config_factory_test.cc @@ -11,6 +11,7 @@ #include "gtest/gtest.h" using testing::Invoke; +using testing::NiceMock; namespace Envoy { namespace Extensions { @@ -27,7 +28,7 @@ TEST(OriginalSrcHttpConfigFactoryTest, TestCreateFactory) { ProtobufTypes::MessagePtr proto_config = factory.createEmptyConfigProto(); TestUtility::loadFromYaml(yaml, *proto_config); - Server::Configuration::MockFactoryContext context; + NiceMock context; Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(*proto_config, "", context); diff --git a/test/extensions/filters/http/original_src/original_src_test.cc b/test/extensions/filters/http/original_src/original_src_test.cc index c166824bfa..9eba387591 100644 --- a/test/extensions/filters/http/original_src/original_src_test.cc +++ b/test/extensions/filters/http/original_src/original_src_test.cc @@ -16,7 +16,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::Exactly; using testing::SaveArg; using testing::StrictMock; diff --git a/test/extensions/filters/http/ratelimit/config_test.cc b/test/extensions/filters/http/ratelimit/config_test.cc index 7c68bd808e..5b239b9740 100644 --- a/test/extensions/filters/http/ratelimit/config_test.cc +++ b/test/extensions/filters/http/ratelimit/config_test.cc @@ -10,7 +10,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/http/ratelimit/ratelimit_test.cc b/test/extensions/filters/http/ratelimit/ratelimit_test.cc index 4e030d64b8..95a95a16a2 100644 --- a/test/extensions/filters/http/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/http/ratelimit/ratelimit_test.cc @@ -27,7 +27,6 @@ using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Return; -using testing::ReturnRef; using testing::SetArgReferee; using testing::WithArgs; diff --git a/test/extensions/filters/http/squash/squash_filter_test.cc b/test/extensions/filters/http/squash/squash_filter_test.cc index 0800a51499..491f9930af 100644 --- a/test/extensions/filters/http/squash/squash_filter_test.cc +++ b/test/extensions/filters/http/squash/squash_filter_test.cc @@ -196,7 +196,7 @@ class SquashFilterTest : public testing::Test { expectAsyncClientSend(); - EXPECT_CALL(*attachmentTimeout_timer_, enableTimer(config_->attachmentTimeout())); + EXPECT_CALL(*attachmentTimeout_timer_, enableTimer(config_->attachmentTimeout(), _)); Envoy::Http::TestHeaderMapImpl headers{{":method", "GET"}, {":authority", "www.solo.io"}, @@ -330,6 +330,7 @@ TEST_F(SquashFilterTest, Timeout) { EXPECT_CALL(request_, cancel()); EXPECT_CALL(filter_callbacks_, continueDecoding()); + EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); attachmentTimeout_timer_->invokeCallback(); EXPECT_EQ(Envoy::Http::FilterDataStatus::Continue, filter_->decodeData(buffer, false)); @@ -354,11 +355,12 @@ TEST_F(SquashFilterTest, CheckRetryPollingAttachment) { NiceMock* retry_timer; retry_timer = new NiceMock(&filter_callbacks_.dispatcher_); - EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod())); + EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod(), _)); completeGetStatusRequest("attaching"); // Expect the second get attachment request expectAsyncClientSend(); + EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); retry_timer->invokeCallback(); EXPECT_CALL(filter_callbacks_, continueDecoding()); completeGetStatusRequest("attached"); @@ -372,12 +374,13 @@ TEST_F(SquashFilterTest, CheckRetryPollingAttachmentOnFailure) { NiceMock* retry_timer; retry_timer = new NiceMock(&filter_callbacks_.dispatcher_); - EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod())); + EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod(), _)); popPendingCallback()->onFailure(Envoy::Http::AsyncClient::FailureReason::Reset); // Expect the second get attachment request expectAsyncClientSend(); + EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); retry_timer->invokeCallback(); EXPECT_CALL(filter_callbacks_, continueDecoding()); @@ -391,7 +394,7 @@ TEST_F(SquashFilterTest, DestroyedInTheMiddle) { completeCreateRequest(); auto retry_timer = new NiceMock(&filter_callbacks_.dispatcher_); - EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod())); + EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod(), _)); completeGetStatusRequest("attaching"); EXPECT_CALL(*attachmentTimeout_timer_, disableTimer()); @@ -413,7 +416,7 @@ TEST_F(SquashFilterTest, InvalidJsonForGetAttachment) { completeCreateRequest(); auto retry_timer = new NiceMock(&filter_callbacks_.dispatcher_); - EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod())); + EXPECT_CALL(*retry_timer, enableTimer(config_->attachmentPollPeriod(), _)); completeRequest("200", "This is not a JSON object"); } @@ -430,12 +433,12 @@ TEST_F(SquashFilterTest, TimerExpiresInline) { initFilter(); attachmentTimeout_timer_ = new NiceMock(&filter_callbacks_.dispatcher_); - // TODO: this is a really synthetic test as the callback can't actually be called under the stack - // of enableTimer. It'd be good to clean this up. - EXPECT_CALL(*attachmentTimeout_timer_, enableTimer(config_->attachmentTimeout())) - .WillOnce(Invoke([&](const std::chrono::milliseconds&) { + EXPECT_CALL(*attachmentTimeout_timer_, enableTimer(config_->attachmentTimeout(), _)) + .WillOnce(Invoke([&](const std::chrono::milliseconds&, const ScopeTrackedObject* scope) { + attachmentTimeout_timer_->scope_ = scope; attachmentTimeout_timer_->enabled_ = true; // timer expires inline + EXPECT_CALL(filter_callbacks_.dispatcher_, setTrackedObject(_)).Times(2); attachmentTimeout_timer_->invokeCallback(); })); diff --git a/test/extensions/filters/listener/http_inspector/http_inspector_test.cc b/test/extensions/filters/listener/http_inspector/http_inspector_test.cc index afca8e66e9..718c03de16 100644 --- a/test/extensions/filters/listener/http_inspector/http_inspector_test.cc +++ b/test/extensions/filters/listener/http_inspector/http_inspector_test.cc @@ -11,8 +11,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::AtLeast; -using testing::Eq; using testing::InSequence; using testing::Invoke; using testing::InvokeWithoutArgs; diff --git a/test/extensions/filters/listener/original_src/original_src_config_factory_test.cc b/test/extensions/filters/listener/original_src/original_src_config_factory_test.cc index 08ec55111a..25f758e9e6 100644 --- a/test/extensions/filters/listener/original_src/original_src_config_factory_test.cc +++ b/test/extensions/filters/listener/original_src/original_src_config_factory_test.cc @@ -11,6 +11,7 @@ #include "gtest/gtest.h" using testing::Invoke; +using testing::NiceMock; namespace Envoy { namespace Extensions { @@ -28,7 +29,7 @@ TEST(OriginalSrcConfigFactoryTest, TestCreateFactory) { ProtobufTypes::MessagePtr proto_config = factory.createEmptyConfigProto(); TestUtility::loadFromYaml(yaml, *proto_config); - Server::Configuration::MockListenerFactoryContext context; + NiceMock context; Network::ListenerFilterFactoryCb cb = factory.createFilterFactoryFromProto(*proto_config, context); diff --git a/test/extensions/filters/listener/original_src/original_src_test.cc b/test/extensions/filters/listener/original_src/original_src_test.cc index ffed37e485..9684e6e59b 100644 --- a/test/extensions/filters/listener/original_src/original_src_test.cc +++ b/test/extensions/filters/listener/original_src/original_src_test.cc @@ -14,7 +14,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::Exactly; using testing::SaveArg; namespace Envoy { 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 2f37b861df..683bdfc1c5 100644 --- a/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc +++ b/test/extensions/filters/listener/proxy_protocol/proxy_protocol_test.cc @@ -31,7 +31,6 @@ using testing::_; using testing::AnyNumber; using testing::AtLeast; -using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Return; @@ -77,6 +76,7 @@ class ProxyProtocolTest : public testing::TestWithParamrun(Event::Dispatcher::RunType::NonBlock); @@ -655,7 +664,10 @@ TEST_P(ProxyProtocolTest, v2Fragmented3Error) { const ssize_t rc = ::readv(fd, iov, iovcnt); return Api::SysCallSizeResult{rc, errno}; })); - + EXPECT_CALL(os_sys_calls, close(_)).Times(AnyNumber()).WillRepeatedly(Invoke([](int fd) { + const int rc = ::close(fd); + return Api::SysCallIntResult{rc, errno}; + })); connect(false); write(buffer, 17); @@ -701,7 +713,10 @@ TEST_P(ProxyProtocolTest, v2Fragmented4Error) { const ssize_t rc = ::readv(fd, iov, iovcnt); return Api::SysCallSizeResult{rc, errno}; })); - + EXPECT_CALL(os_sys_calls, close(_)).Times(AnyNumber()).WillRepeatedly(Invoke([](int fd) { + const int rc = ::close(fd); + return Api::SysCallIntResult{rc, errno}; + })); connect(false); write(buffer, 10); dispatcher_->run(Event::Dispatcher::RunType::NonBlock); @@ -906,6 +921,7 @@ class WildcardProxyProtocolTest : public testing::TestWithParam Api::SysCallSizeResult { + ENVOY_LOG_MISC(error, "In mock syscall recv {} {} {} {}", fd, buffer, length, flag); + return Api::SysCallSizeResult{static_cast(0), 0}; + })); EXPECT_CALL(dispatcher_, createFileEvent_(_, _, Event::FileTriggerType::Edge, Event::FileReadyType::Read | Event::FileReadyType::Closed)) @@ -231,6 +236,36 @@ TEST_F(TlsInspectorTest, NotSsl) { EXPECT_EQ(1, cfg_->stats().tls_not_found_.value()); } +TEST_F(TlsInspectorTest, InlineReadSucceed) { + filter_ = std::make_unique(cfg_); + + EXPECT_CALL(cb_, socket()).WillRepeatedly(ReturnRef(socket_)); + EXPECT_CALL(cb_, dispatcher()).WillRepeatedly(ReturnRef(dispatcher_)); + EXPECT_CALL(socket_, ioHandle()).WillRepeatedly(ReturnRef(*io_handle_)); + const std::vector alpn_protos = {absl::string_view("h2")}; + const std::string servername("example.com"); + std::vector client_hello = Tls::Test::generateClientHello(servername, "\x02h2"); + + EXPECT_CALL(os_sys_calls_, recv(42, _, _, MSG_PEEK)) + .WillOnce(Invoke( + [&client_hello](int fd, void* buffer, size_t length, int flag) -> Api::SysCallSizeResult { + ENVOY_LOG_MISC(trace, "In mock syscall recv {} {} {} {}", fd, buffer, length, flag); + ASSERT(length >= client_hello.size()); + memcpy(buffer, client_hello.data(), client_hello.size()); + return Api::SysCallSizeResult{ssize_t(client_hello.size()), 0}; + })); + + // No event is created if the inline recv parse the hello. + EXPECT_CALL(dispatcher_, + createFileEvent_(_, _, Event::FileTriggerType::Edge, + Event::FileReadyType::Read | Event::FileReadyType::Closed)) + .Times(0); + + EXPECT_CALL(socket_, setRequestedServerName(Eq(servername))); + EXPECT_CALL(socket_, setRequestedApplicationProtocols(alpn_protos)); + EXPECT_CALL(socket_, setDetectedTransportProtocol(absl::string_view("tls"))); + EXPECT_EQ(Network::FilterStatus::Continue, filter_->onAccept(cb_)); +} } // namespace } // namespace TlsInspector } // namespace ListenerFilters diff --git a/test/extensions/filters/network/client_ssl_auth/client_ssl_auth_test.cc b/test/extensions/filters/network/client_ssl_auth/client_ssl_auth_test.cc index 23b7f9acf6..4c98ceff6d 100644 --- a/test/extensions/filters/network/client_ssl_auth/client_ssl_auth_test.cc +++ b/test/extensions/filters/network/client_ssl_auth/client_ssl_auth_test.cc @@ -25,9 +25,7 @@ using testing::Eq; using testing::InSequence; using testing::Invoke; using testing::Return; -using testing::ReturnNew; using testing::ReturnRef; -using testing::WithArg; namespace Envoy { namespace Extensions { @@ -58,7 +56,8 @@ class ClientSslAuthFilterTest : public testing::Test { protected: ClientSslAuthFilterTest() : request_(&cm_.async_client_), interval_timer_(new Event::MockTimer(&dispatcher_)), - api_(Api::createApiForTest(stats_store_)) {} + api_(Api::createApiForTest(stats_store_)), + ssl_(std::make_shared()) {} ~ClientSslAuthFilterTest() override { tls_.shutdownThread(); } void setup() { @@ -112,10 +111,10 @@ stat_prefix: vpn std::unique_ptr instance_; Event::MockTimer* interval_timer_; Http::AsyncClient::Callbacks* callbacks_; - Ssl::MockConnectionInfo ssl_; Stats::IsolatedStoreImpl stats_store_; NiceMock random_; Api::ApiPtr api_; + std::shared_ptr ssl_; }; TEST_F(ClientSslAuthFilterTest, NoCluster) { @@ -156,18 +155,18 @@ TEST_F(ClientSslAuthFilterTest, Ssl) { // Create a new filter for an SSL connection, with no backing auth data yet. createAuthFilter(); - ON_CALL(filter_callbacks_.connection_, ssl()).WillByDefault(Return(&ssl_)); + ON_CALL(filter_callbacks_.connection_, ssl()).WillByDefault(Return(ssl_)); filter_callbacks_.connection_.remote_address_ = std::make_shared("192.168.1.1"); std::string expected_sha_1("digest"); - EXPECT_CALL(ssl_, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha_1)); + EXPECT_CALL(*ssl_, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha_1)); EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush)); EXPECT_EQ(Network::FilterStatus::StopIteration, instance_->onNewConnection()); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::Connected); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::RemoteClose); // Respond. - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); Http::MessagePtr message(new Http::ResponseMessageImpl( Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "200"}}})); message->body() = std::make_unique( @@ -184,7 +183,7 @@ TEST_F(ClientSslAuthFilterTest, Ssl) { filter_callbacks_.connection_.remote_address_ = std::make_shared("192.168.1.1"); std::string expected_sha_2("1b7d42ef0025ad89c1c911d6c10d7e86a4cb7c5863b2980abcbad1895f8b5314"); - EXPECT_CALL(ssl_, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha_2)); + EXPECT_CALL(*ssl_, sha256PeerCertificateDigest()).WillOnce(ReturnRef(expected_sha_2)); EXPECT_EQ(Network::FilterStatus::StopIteration, instance_->onNewConnection()); EXPECT_CALL(filter_callbacks_, continueReading()); filter_callbacks_.connection_.raiseEvent(Network::ConnectionEvent::Connected); @@ -224,7 +223,7 @@ TEST_F(ClientSslAuthFilterTest, Ssl) { interval_timer_->invokeCallback(); // Error response. - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); message = std::make_unique( Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "503"}}}); callbacks_->onSuccess(std::move(message)); @@ -234,7 +233,7 @@ TEST_F(ClientSslAuthFilterTest, Ssl) { interval_timer_->invokeCallback(); // Parsing error - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); message = std::make_unique( Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "200"}}}); message->body() = std::make_unique("bad_json"); @@ -245,7 +244,7 @@ TEST_F(ClientSslAuthFilterTest, Ssl) { interval_timer_->invokeCallback(); // No response failure. - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); callbacks_->onFailure(Http::AsyncClient::FailureReason::Reset); // Interval timer fires, cannot obtain async client. @@ -258,7 +257,7 @@ TEST_F(ClientSslAuthFilterTest, Ssl) { Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "503"}}})}); return nullptr; })); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); interval_timer_->invokeCallback(); EXPECT_EQ(4U, stats_store_.counter("auth.clientssl.vpn.update_failure").value()); diff --git a/test/extensions/filters/network/common/redis/client_impl_test.cc b/test/extensions/filters/network/common/redis/client_impl_test.cc index e9d2823561..54c67ba9a5 100644 --- a/test/extensions/filters/network/common/redis/client_impl_test.cc +++ b/test/extensions/filters/network/common/redis/client_impl_test.cc @@ -5,6 +5,7 @@ #include "common/upstream/upstream_impl.h" #include "extensions/filters/network/common/redis/client_impl.h" +#include "extensions/filters/network/common/redis/utility.h" #include "test/extensions/filters/network/common/redis/mocks.h" #include "test/extensions/filters/network/common/redis/test_utils.h" @@ -15,14 +16,11 @@ #include "gtest/gtest.h" using testing::_; -using testing::DoAll; using testing::Eq; using testing::InSequence; using testing::Invoke; using testing::Ref; using testing::Return; -using testing::ReturnNew; -using testing::ReturnRef; using testing::SaveArg; namespace Envoy { @@ -66,15 +64,18 @@ class RedisClientImplTest : public testing::Test, public Common::Redis::DecoderF connect_or_op_timer_ = new Event::MockTimer(&dispatcher_); flush_timer_ = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(*host_, createConnection_(_, _)).WillOnce(Return(conn_info)); EXPECT_CALL(*upstream_connection_, addReadFilter(_)) .WillOnce(SaveArg<0>(&upstream_read_filter_)); EXPECT_CALL(*upstream_connection_, connect()); EXPECT_CALL(*upstream_connection_, noDelay(true)); + redis_command_stats_ = + Common::Redis::RedisCommandStats::createRedisCommandStats(stats_.symbolTable()); + client_ = ClientImpl::create(host_, dispatcher_, Common::Redis::EncoderPtr{encoder_}, *this, - *config_); + *config_, redis_command_stats_, stats_); EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_cx_total_.value()); EXPECT_EQ(1UL, host_->stats_.cx_total_.value()); EXPECT_EQ(false, client_->active()); @@ -85,7 +86,7 @@ class RedisClientImplTest : public testing::Test, public Common::Redis::DecoderF } void onConnected() { - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); upstream_connection_->raiseEvent(Network::ConnectionEvent::Connected); } @@ -99,6 +100,28 @@ class RedisClientImplTest : public testing::Test, public Common::Redis::DecoderF client_impl->onRespValue(std::move(response1)); } + void testInitializeReadPolicy( + envoy::config::filter::network::redis_proxy::v2::RedisProxy::ConnPoolSettings::ReadPolicy + read_policy) { + InSequence s; + + setup(std::make_unique(createConnPoolSettings(20, true, true, 100, read_policy))); + + Common::Redis::RespValue readonly_request = Utility::ReadOnlyRequest::instance(); + EXPECT_CALL(*encoder_, encode(Eq(readonly_request), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + client_->initialize(auth_password_); + + EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_total_.value()); + EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_active_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_active_.value()); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); + } + const std::string cluster_name_{"foo"}; std::shared_ptr host_{new NiceMock()}; Event::MockDispatcher dispatcher_; @@ -111,6 +134,10 @@ class RedisClientImplTest : public testing::Test, public Common::Redis::DecoderF Network::ReadFilterSharedPtr upstream_read_filter_; std::unique_ptr config_; ClientPtr client_; + Stats::IsolatedStoreImpl stats_; + Stats::ScopePtr stats_scope_; + Common::Redis::RedisCommandStatsSharedPtr redis_command_stats_; + std::string auth_password_; }; TEST_F(RedisClientImplTest, BatchWithZeroBufferAndTimeout) { @@ -155,6 +182,7 @@ class ConfigBufferSizeGTSingleRequest : public Config { return std::chrono::milliseconds(1); } uint32_t maxUpstreamUnknownConnections() const override { return 0; } + bool enableCommandStats() const override { return false; } ReadPolicy readPolicy() const override { return ReadPolicy::Master; } }; @@ -170,7 +198,7 @@ TEST_F(RedisClientImplTest, BatchWithTimerFiring) { Common::Redis::RespValue request1; MockPoolCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); - EXPECT_CALL(*flush_timer_, enableTimer(_)); + EXPECT_CALL(*flush_timer_, enableTimer(_, _)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); EXPECT_NE(nullptr, handle1); @@ -212,7 +240,7 @@ TEST_F(RedisClientImplTest, BatchWithTimerCancelledByBufferFlush) { Common::Redis::RespValue request1; MockPoolCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); - EXPECT_CALL(*flush_timer_, enableTimer(_)); + EXPECT_CALL(*flush_timer_, enableTimer(_, _)); PoolRequest* handle1 = client_->makeRequest(request1, callbacks1); EXPECT_NE(nullptr, handle1); @@ -232,7 +260,7 @@ TEST_F(RedisClientImplTest, BatchWithTimerCancelledByBufferFlush) { InSequence s; Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); callbacks_->onRespValue(std::move(response1)); @@ -256,6 +284,8 @@ TEST_F(RedisClientImplTest, Basic) { setup(); + client_->initialize(auth_password_); + Common::Redis::RespValue request1; MockPoolCallbacks callbacks1; EXPECT_CALL(*encoder_, encode(Ref(request1), _)); @@ -282,7 +312,7 @@ TEST_F(RedisClientImplTest, Basic) { InSequence s; Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); callbacks_->onRespValue(std::move(response1)); @@ -301,6 +331,47 @@ TEST_F(RedisClientImplTest, Basic) { client_->close(); } +TEST_F(RedisClientImplTest, InitializedWithAuthPassword) { + InSequence s; + + setup(); + + auth_password_ = "testing password"; + Common::Redis::RespValue auth_request = Utility::makeAuthCommand(auth_password_); + EXPECT_CALL(*encoder_, encode(Eq(auth_request), _)); + EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); + client_->initialize(auth_password_); + + EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_total_.value()); + EXPECT_EQ(1UL, host_->cluster_.stats_.upstream_rq_active_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_total_.value()); + EXPECT_EQ(1UL, host_->stats_.rq_active_.value()); + + EXPECT_CALL(*upstream_connection_, close(Network::ConnectionCloseType::NoFlush)); + EXPECT_CALL(*connect_or_op_timer_, disableTimer()); + client_->close(); +} + +TEST_F(RedisClientImplTest, InitializedWithPreferMasterReadPolicy) { + testInitializeReadPolicy(envoy::config::filter::network::redis_proxy::v2:: + RedisProxy_ConnPoolSettings_ReadPolicy_PREFER_MASTER); +} + +TEST_F(RedisClientImplTest, InitializedWithReplicaReadPolicy) { + testInitializeReadPolicy(envoy::config::filter::network::redis_proxy::v2:: + RedisProxy_ConnPoolSettings_ReadPolicy_REPLICA); +} + +TEST_F(RedisClientImplTest, InitializedWithPreferReplicaReadPolicy) { + testInitializeReadPolicy(envoy::config::filter::network::redis_proxy::v2:: + RedisProxy_ConnPoolSettings_ReadPolicy_PREFER_REPLICA); +} + +TEST_F(RedisClientImplTest, InitializedWithAnyReadPolicy) { + testInitializeReadPolicy( + envoy::config::filter::network::redis_proxy::v2::RedisProxy_ConnPoolSettings_ReadPolicy_ANY); +} + TEST_F(RedisClientImplTest, Cancel) { InSequence s; @@ -330,7 +401,7 @@ TEST_F(RedisClientImplTest, Cancel) { Common::Redis::RespValuePtr response1(new Common::Redis::RespValue()); EXPECT_CALL(callbacks1, onResponse_(_)).Times(0); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); callbacks_->onRespValue(std::move(response1)); @@ -469,6 +540,7 @@ class ConfigOutlierDisabled : public Config { } ReadPolicy readPolicy() const override { return ReadPolicy::Master; } uint32_t maxUpstreamUnknownConnections() const override { return 0; } + bool enableCommandStats() const override { return false; } }; TEST_F(RedisClientImplTest, OutlierDisabled) { @@ -543,7 +615,7 @@ TEST_F(RedisClientImplTest, OpTimeout) { EXPECT_CALL(*encoder_, encode(Ref(request1), _)); EXPECT_CALL(*flush_timer_, enabled()).WillOnce(Return(false)); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); handle1 = client_->makeRequest(request1, callbacks1); EXPECT_NE(nullptr, handle1); @@ -596,7 +668,7 @@ TEST_F(RedisClientImplTest, AskRedirection) { // Simulate redirection failure. EXPECT_CALL(callbacks1, onRedirection(Ref(*response1))).WillOnce(Return(false)); EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); callbacks_->onRespValue(std::move(response1)); @@ -658,7 +730,7 @@ TEST_F(RedisClientImplTest, MovedRedirection) { // Simulate redirection failure. EXPECT_CALL(callbacks1, onRedirection(Ref(*response1))).WillOnce(Return(false)); EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); callbacks_->onRespValue(std::move(response1)); @@ -719,7 +791,7 @@ TEST_F(RedisClientImplTest, AskRedirectionNotEnabled) { response1->asString() = "ASK 1111 10.1.2.3:4321"; // Simulate redirection failure. EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); callbacks_->onRespValue(std::move(response1)); @@ -781,7 +853,7 @@ TEST_F(RedisClientImplTest, MovedRedirectionNotEnabled) { // The exact values of the hash slot and IP info are not important. response1->asString() = "MOVED 1111 10.1.2.3:4321"; EXPECT_CALL(callbacks1, onResponse_(Ref(response1))); - EXPECT_CALL(*connect_or_op_timer_, enableTimer(_)); + EXPECT_CALL(*connect_or_op_timer_, enableTimer(_, _)); EXPECT_CALL(host_->outlier_detector_, putResult(Upstream::Outlier::Result::EXT_ORIGIN_REQUEST_SUCCESS, _)); callbacks_->onRespValue(std::move(response1)); @@ -875,10 +947,14 @@ TEST(RedisClientFactoryImplTest, Basic) { EXPECT_CALL(*host, createConnection_(_, _)).WillOnce(Return(conn_info)); NiceMock dispatcher; ConfigImpl config(createConnPoolSettings()); - ClientPtr client = factory.create(host, dispatcher, config); + Stats::IsolatedStoreImpl stats_; + auto redis_command_stats = + Common::Redis::RedisCommandStats::createRedisCommandStats(stats_.symbolTable()); + const std::string auth_password; + ClientPtr client = + factory.create(host, dispatcher, config, redis_command_stats, stats_, auth_password); client->close(); } - } // namespace Client } // namespace Redis } // namespace Common diff --git a/test/extensions/filters/network/common/redis/mocks.h b/test/extensions/filters/network/common/redis/mocks.h index 859ea03cf4..a44e41ef63 100644 --- a/test/extensions/filters/network/common/redis/mocks.h +++ b/test/extensions/filters/network/common/redis/mocks.h @@ -73,6 +73,7 @@ class MockClient : public Client { MOCK_METHOD0(close, void()); MOCK_METHOD2(makeRequest, PoolRequest*(const Common::Redis::RespValue& request, PoolCallbacks& callbacks)); + MOCK_METHOD1(initialize, void(const std::string& password)); std::list callbacks_; }; diff --git a/test/extensions/filters/network/dubbo_proxy/conn_manager_test.cc b/test/extensions/filters/network/dubbo_proxy/conn_manager_test.cc index 2b0429d2da..f37ced3f9d 100644 --- a/test/extensions/filters/network/dubbo_proxy/conn_manager_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/conn_manager_test.cc @@ -20,13 +20,10 @@ #include "gtest/gtest.h" using testing::_; -using testing::AnyNumber; using testing::InSequence; using testing::Invoke; using testing::NiceMock; -using testing::Ref; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { @@ -134,7 +131,7 @@ class ConnectionManagerTest : public testing::Test { if (!yaml.empty()) { TestUtility::loadFromYaml(yaml, proto_config_); - MessageUtil::validate(proto_config_); + TestUtility::validate(proto_config_); } proto_config_.set_stat_prefix("test"); @@ -306,7 +303,8 @@ class ConnectionManagerTest : public testing::Test { buffer.add(static_cast(&msg_type), 1); buffer.add(std::string{0x14}); addInt64(buffer, request_id); // Request Id - buffer.add(std::string{0x00, 0x00, 0x00, 0x00}); // Body Length + buffer.add(std::string{0x00, 0x00, 0x00, 0x01}); // Body Length + buffer.add(std::string{0x01}); // Body } NiceMock factory_context_; @@ -377,6 +375,7 @@ TEST_F(ConnectionManagerTest, OnDataHandlesHeartbeatEvent) { })); EXPECT_EQ(conn_manager_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(0U, buffer_.length()); filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); EXPECT_EQ(0U, store_.counter("test.request").value()); @@ -1168,7 +1167,9 @@ serialization_type: Hessian2 - match: method: name: - regex: "(.*?)" + safe_regex: + google_re2: {} + regex: "(.*?)" route: cluster: user_service_dubbo_server )EOF"; diff --git a/test/extensions/filters/network/dubbo_proxy/decoder_test.cc b/test/extensions/filters/network/dubbo_proxy/decoder_test.cc index bbada6519a..c904a3f401 100644 --- a/test/extensions/filters/network/dubbo_proxy/decoder_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/decoder_test.cc @@ -12,9 +12,6 @@ using testing::_; using testing::Return; using testing::ReturnRef; -using testing::TestParamInfo; -using testing::TestWithParam; -using testing::Values; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/network/dubbo_proxy/dubbo_hessian2_serializer_impl_test.cc b/test/extensions/filters/network/dubbo_proxy/dubbo_hessian2_serializer_impl_test.cc index 3a74b52b3f..39272bf9dd 100644 --- a/test/extensions/filters/network/dubbo_proxy/dubbo_hessian2_serializer_impl_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/dubbo_hessian2_serializer_impl_test.cc @@ -10,8 +10,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::NotNull; - namespace Envoy { namespace Extensions { namespace NetworkFilters { diff --git a/test/extensions/filters/network/dubbo_proxy/dubbo_protocol_impl_test.cc b/test/extensions/filters/network/dubbo_proxy/dubbo_protocol_impl_test.cc index 38e3d97ad9..6aa87a5f99 100644 --- a/test/extensions/filters/network/dubbo_proxy/dubbo_protocol_impl_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/dubbo_protocol_impl_test.cc @@ -14,8 +14,6 @@ namespace Extensions { namespace NetworkFilters { namespace DubboProxy { -using testing::StrictMock; - TEST(DubboProtocolImplTest, NotEnoughData) { Buffer::OwnedImpl buffer; DubboProtocolImpl dubbo_protocol; diff --git a/test/extensions/filters/network/dubbo_proxy/mocks.h b/test/extensions/filters/network/dubbo_proxy/mocks.h index 4e55ca6b95..dbe59849c5 100644 --- a/test/extensions/filters/network/dubbo_proxy/mocks.h +++ b/test/extensions/filters/network/dubbo_proxy/mocks.h @@ -16,8 +16,6 @@ #include "gmock/gmock.h" -using testing::_; - namespace Envoy { namespace Extensions { namespace NetworkFilters { diff --git a/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc b/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc index 623c6f657c..1099862c54 100644 --- a/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/route_matcher_test.cc @@ -24,7 +24,7 @@ envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration parseRouteConfigurationFromV2Yaml(const std::string& yaml) { envoy::config::filter::network::dubbo_proxy::v2alpha1::RouteConfiguration route_config; TestUtility::loadFromYaml(yaml, route_config); - MessageUtil::validate(route_config); + TestUtility::validate(route_config); return route_config; } @@ -32,7 +32,7 @@ envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy parseDubboProxyFromV2Yaml(const std::string& yaml) { envoy::config::filter::network::dubbo_proxy::v2alpha1::DubboProxy config; TestUtility::loadFromYaml(yaml, config); - MessageUtil::validate(config); + TestUtility::validate(config); return config; } @@ -47,7 +47,9 @@ interface: org.apache.dubbo.demo.DemoService - match: method: name: - regex: "(.*?)" + safe_regex: + google_re2: {} + regex: "(.*?)" route: cluster: user_service_dubbo_server )EOF"; @@ -94,7 +96,9 @@ group: test - match: method: name: - regex: "(.*?)" + safe_regex: + google_re2: {} + regex: "(.*?)" route: cluster: user_service_dubbo_server )EOF"; @@ -128,7 +132,9 @@ version: 1.0.0 - match: method: name: - regex: "(.*?)" + safe_regex: + google_re2: {} + regex: "(.*?)" route: cluster: user_service_dubbo_server )EOF"; @@ -167,7 +173,9 @@ group: HSF - match: method: name: - regex: "(.*?)" + safe_regex: + google_re2: {} + regex: "(.*?)" route: cluster: user_service_dubbo_server )EOF"; @@ -299,7 +307,9 @@ interface: org.apache.dubbo.demo.DemoService - match: method: name: - regex: "\\d{3}test" + safe_regex: + google_re2: {} + regex: "\\d{3}test" route: cluster: user_service_dubbo_server )EOF"; diff --git a/test/extensions/filters/network/dubbo_proxy/router_test.cc b/test/extensions/filters/network/dubbo_proxy/router_test.cc index 8caf2cfd40..77f8df2588 100644 --- a/test/extensions/filters/network/dubbo_proxy/router_test.cc +++ b/test/extensions/filters/network/dubbo_proxy/router_test.cc @@ -16,13 +16,11 @@ using testing::_; using testing::ContainsRegex; using testing::Eq; -using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Ref; using testing::Return; using testing::ReturnRef; -using testing::Values; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/network/ext_authz/ext_authz_test.cc b/test/extensions/filters/network/ext_authz/ext_authz_test.cc index 8a344ed162..0fe0ef8531 100644 --- a/test/extensions/filters/network/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/network/ext_authz/ext_authz_test.cc @@ -26,7 +26,6 @@ using testing::_; using testing::InSequence; using testing::Invoke; using testing::NiceMock; -using testing::Return; using testing::ReturnRef; using testing::WithArgs; @@ -95,7 +94,7 @@ TEST_F(ExtAuthzFilterTest, BadExtAuthzConfig) { envoy::config::filter::network::ext_authz::v2::ExtAuthz proto_config{}; TestUtility::loadFromJson(json_string, proto_config); - EXPECT_THROW(MessageUtil::downcastAndValidate< + EXPECT_THROW(TestUtility::downcastAndValidate< const envoy::config::filter::network::ext_authz::v2::ExtAuthz&>(proto_config), ProtoValidationException); } diff --git a/test/extensions/filters/network/http_connection_manager/config_test.cc b/test/extensions/filters/network/http_connection_manager/config_test.cc index c300885c09..3341802e65 100644 --- a/test/extensions/filters/network/http_connection_manager/config_test.cc +++ b/test/extensions/filters/network/http_connection_manager/config_test.cc @@ -224,6 +224,7 @@ stat_prefix: router operation_name: ingress request_headers_for_tags: - foo + max_path_tag_length: 128 http_filters: - name: envoy.router config: {} @@ -235,11 +236,70 @@ stat_prefix: router EXPECT_THAT(std::vector({Http::LowerCaseString("foo")}), ContainerEq(config.tracingConfig()->request_headers_for_tags_)); + EXPECT_EQ(128, config.tracingConfig()->max_path_tag_length_); EXPECT_EQ(*context_.local_info_.address_, config.localAddress()); EXPECT_EQ("foo", config.serverName()); + EXPECT_EQ(HttpConnectionManagerConfig::HttpConnectionManagerProto::OVERWRITE, + config.serverHeaderTransformation()); EXPECT_EQ(5 * 60 * 1000, config.streamIdleTimeout().count()); } +TEST_F(HttpConnectionManagerConfigTest, ListenerDirectionOutboundOverride) { + const std::string yaml_string = R"EOF( +stat_prefix: router +route_config: + virtual_hosts: + - name: service + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: cluster +tracing: + operation_name: ingress +http_filters: +- name: envoy.router + config: {} + )EOF"; + + ON_CALL(context_, direction()) + .WillByDefault(Return(envoy::api::v2::core::TrafficDirection::OUTBOUND)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_); + EXPECT_EQ(Tracing::OperationName::Egress, config.tracingConfig()->operation_name_); +} + +TEST_F(HttpConnectionManagerConfigTest, ListenerDirectionInboundOverride) { + const std::string yaml_string = R"EOF( +stat_prefix: router +route_config: + virtual_hosts: + - name: service + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: cluster +tracing: + operation_name: egress +http_filters: +- name: envoy.router + config: {} + )EOF"; + + ON_CALL(context_, direction()) + .WillByDefault(Return(envoy::api::v2::core::TrafficDirection::INBOUND)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_); + EXPECT_EQ(Tracing::OperationName::Ingress, config.tracingConfig()->operation_name_); +} + TEST_F(HttpConnectionManagerConfigTest, SamplingDefault) { const std::string yaml_string = R"EOF( stat_prefix: ingress_http @@ -258,6 +318,7 @@ TEST_F(HttpConnectionManagerConfigTest, SamplingDefault) { scoped_routes_config_provider_manager_); EXPECT_EQ(100, config.tracingConfig()->client_sampling_.numerator()); + EXPECT_EQ(Tracing::DefaultMaxPathTagLength, config.tracingConfig()->max_path_tag_length_); EXPECT_EQ(envoy::type::FractionalPercent::HUNDRED, config.tracingConfig()->client_sampling_.denominator()); EXPECT_EQ(10000, config.tracingConfig()->random_sampling_.numerator()); @@ -294,7 +355,7 @@ TEST_F(HttpConnectionManagerConfigTest, SamplingConfigured) { EXPECT_EQ(1, config.tracingConfig()->client_sampling_.numerator()); EXPECT_EQ(envoy::type::FractionalPercent::HUNDRED, config.tracingConfig()->client_sampling_.denominator()); - EXPECT_EQ(2, config.tracingConfig()->random_sampling_.numerator()); + EXPECT_EQ(200, config.tracingConfig()->random_sampling_.numerator()); EXPECT_EQ(envoy::type::FractionalPercent::TEN_THOUSAND, config.tracingConfig()->random_sampling_.denominator()); EXPECT_EQ(3, config.tracingConfig()->overall_sampling_.numerator()); @@ -302,6 +363,40 @@ TEST_F(HttpConnectionManagerConfigTest, SamplingConfigured) { config.tracingConfig()->overall_sampling_.denominator()); } +TEST_F(HttpConnectionManagerConfigTest, FractionalSamplingConfigured) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + internal_address_config: + unix_sockets: true + route_config: + name: local_route + tracing: + operation_name: ingress + client_sampling: + value: 0.1 + random_sampling: + value: 0.2 + overall_sampling: + value: 0.3 + http_filters: + - name: envoy.router + )EOF"; + + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_); + + EXPECT_EQ(0, config.tracingConfig()->client_sampling_.numerator()); + EXPECT_EQ(envoy::type::FractionalPercent::HUNDRED, + config.tracingConfig()->client_sampling_.denominator()); + EXPECT_EQ(20, config.tracingConfig()->random_sampling_.numerator()); + EXPECT_EQ(envoy::type::FractionalPercent::TEN_THOUSAND, + config.tracingConfig()->random_sampling_.denominator()); + EXPECT_EQ(0, config.tracingConfig()->overall_sampling_.numerator()); + EXPECT_EQ(envoy::type::FractionalPercent::HUNDRED, + config.tracingConfig()->overall_sampling_.denominator()); +} + TEST_F(HttpConnectionManagerConfigTest, UnixSocketInternalAddress) { const std::string yaml_string = R"EOF( stat_prefix: ingress_http @@ -388,6 +483,66 @@ TEST_F(HttpConnectionManagerConfigTest, DisabledStreamIdleTimeout) { EXPECT_EQ(0, config.streamIdleTimeout().count()); } +TEST_F(HttpConnectionManagerConfigTest, ServerOverwrite) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + server_header_transformation: OVERWRITE + route_config: + name: local_route + http_filters: + - name: envoy.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_); + EXPECT_EQ(HttpConnectionManagerConfig::HttpConnectionManagerProto::OVERWRITE, + config.serverHeaderTransformation()); +} + +TEST_F(HttpConnectionManagerConfigTest, ServerAppendIfAbsent) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + server_header_transformation: APPEND_IF_ABSENT + route_config: + name: local_route + http_filters: + - name: envoy.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_); + EXPECT_EQ(HttpConnectionManagerConfig::HttpConnectionManagerProto::APPEND_IF_ABSENT, + config.serverHeaderTransformation()); +} + +TEST_F(HttpConnectionManagerConfigTest, ServerPassThrough) { + const std::string yaml_string = R"EOF( + stat_prefix: ingress_http + server_header_transformation: PASS_THROUGH + route_config: + name: local_route + http_filters: + - name: envoy.router + )EOF"; + + EXPECT_CALL(context_.runtime_loader_.snapshot_, featureEnabled(_, An())) + .WillOnce(Invoke(&context_.runtime_loader_.snapshot_, + &Runtime::MockSnapshot::featureEnabledDefault)); + HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_, + date_provider_, route_config_provider_manager_, + scoped_routes_config_provider_manager_); + EXPECT_EQ(HttpConnectionManagerConfig::HttpConnectionManagerProto::PASS_THROUGH, + config.serverHeaderTransformation()); +} + // Validated that by default we don't normalize paths // unless set build flag path_normalization_by_default=true TEST_F(HttpConnectionManagerConfigTest, NormalizePathDefault) { diff --git a/test/extensions/filters/network/kafka/request_codec_unit_test.cc b/test/extensions/filters/network/kafka/request_codec_unit_test.cc index 9c7ff52187..f10af9f953 100644 --- a/test/extensions/filters/network/kafka/request_codec_unit_test.cc +++ b/test/extensions/filters/network/kafka/request_codec_unit_test.cc @@ -8,9 +8,7 @@ using testing::_; using testing::AnyNumber; -using testing::Eq; using testing::Invoke; -using testing::ResultOf; using testing::Return; namespace Envoy { diff --git a/test/extensions/filters/network/kafka/response_codec_unit_test.cc b/test/extensions/filters/network/kafka/response_codec_unit_test.cc index 6d20d6ef6d..274da6525f 100644 --- a/test/extensions/filters/network/kafka/response_codec_unit_test.cc +++ b/test/extensions/filters/network/kafka/response_codec_unit_test.cc @@ -8,9 +8,7 @@ using testing::_; using testing::AnyNumber; -using testing::Eq; using testing::Invoke; -using testing::ResultOf; using testing::Return; namespace Envoy { diff --git a/test/extensions/filters/network/mongo_proxy/proxy_test.cc b/test/extensions/filters/network/mongo_proxy/proxy_test.cc index 2fb9b66221..a0e080dca2 100644 --- a/test/extensions/filters/network/mongo_proxy/proxy_test.cc +++ b/test/extensions/filters/network/mongo_proxy/proxy_test.cc @@ -8,6 +8,7 @@ #include "extensions/filters/network/mongo_proxy/bson_impl.h" #include "extensions/filters/network/mongo_proxy/codec_impl.h" +#include "extensions/filters/network/mongo_proxy/mongo_stats.h" #include "extensions/filters/network/mongo_proxy/proxy.h" #include "extensions/filters/network/well_known_names.h" @@ -22,7 +23,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::AnyNumber; using testing::AtLeast; using testing::Invoke; using testing::Matcher; @@ -62,7 +62,7 @@ class TestProxyFilter : public ProxyFilter { class MongoProxyFilterTest : public testing::Test { public: - MongoProxyFilterTest() { setup(); } + MongoProxyFilterTest() : mongo_stats_(std::make_shared(store_, "test")) { setup(); } void setup() { ON_CALL(runtime_.snapshot_, featureEnabled("mongo.proxy_enabled", 100)) @@ -82,9 +82,9 @@ class MongoProxyFilterTest : public testing::Test { } void initializeFilter(bool emit_dynamic_metadata = false) { - filter_ = std::make_unique("test.", store_, runtime_, access_log_, - fault_config_, drain_decision_, - dispatcher_.timeSource(), emit_dynamic_metadata); + filter_ = std::make_unique( + "test.", store_, runtime_, access_log_, fault_config_, drain_decision_, + dispatcher_.timeSource(), emit_dynamic_metadata, mongo_stats_); filter_->initializeReadFilterCallbacks(read_filter_callbacks_); filter_->onNewConnection(); @@ -114,6 +114,7 @@ class MongoProxyFilterTest : public testing::Test { Buffer::OwnedImpl fake_data_; NiceMock store_; + MongoStatsSharedPtr mongo_stats_; NiceMock runtime_; NiceMock dispatcher_; std::shared_ptr file_{ @@ -133,7 +134,7 @@ TEST_F(MongoProxyFilterTest, DelayFaults) { Event::MockTimer* delay_timer = new Event::MockTimer(&read_filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*delay_timer, enableTimer(std::chrono::milliseconds(10))); + EXPECT_CALL(*delay_timer, enableTimer(std::chrono::milliseconds(10), _)); EXPECT_CALL(*file_, write(_)).Times(AtLeast(1)); EXPECT_CALL(*filter_->decoder_, onData(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { @@ -551,7 +552,7 @@ TEST_F(MongoProxyFilterTest, ConcurrentQueryWithDrainClose) { .WillByDefault(Return(true)); EXPECT_CALL(drain_decision_, drainClose()).WillOnce(Return(true)); drain_timer = new Event::MockTimer(&read_filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*drain_timer, enableTimer(std::chrono::milliseconds(0))); + EXPECT_CALL(*drain_timer, enableTimer(std::chrono::milliseconds(0), _)); filter_->callbacks_->decodeReply(std::move(message)); })); filter_->onWrite(fake_data_, false); @@ -595,7 +596,7 @@ TEST_F(MongoProxyFilterTest, ConnectionDestroyLocal) { Event::MockTimer* delay_timer = new Event::MockTimer(&read_filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*delay_timer, enableTimer(std::chrono::milliseconds(10))); + EXPECT_CALL(*delay_timer, enableTimer(std::chrono::milliseconds(10), _)); EXPECT_CALL(*filter_->decoder_, onData(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { QueryMessagePtr message(new QueryMessageImpl(0, 0)); @@ -619,7 +620,7 @@ TEST_F(MongoProxyFilterTest, ConnectionDestroyRemote) { Event::MockTimer* delay_timer = new Event::MockTimer(&read_filter_callbacks_.connection_.dispatcher_); - EXPECT_CALL(*delay_timer, enableTimer(std::chrono::milliseconds(10))); + EXPECT_CALL(*delay_timer, enableTimer(std::chrono::milliseconds(10), _)); EXPECT_CALL(*filter_->decoder_, onData(_)).WillOnce(Invoke([&](Buffer::Instance&) -> void { QueryMessagePtr message(new QueryMessageImpl(0, 0)); diff --git a/test/extensions/filters/network/ratelimit/config_test.cc b/test/extensions/filters/network/ratelimit/config_test.cc index 387a03b216..24aeb7e74e 100644 --- a/test/extensions/filters/network/ratelimit/config_test.cc +++ b/test/extensions/filters/network/ratelimit/config_test.cc @@ -10,7 +10,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/network/redis_proxy/config_test.cc b/test/extensions/filters/network/redis_proxy/config_test.cc index ff348c9c1a..8c34fb502f 100644 --- a/test/extensions/filters/network/redis_proxy/config_test.cc +++ b/test/extensions/filters/network/redis_proxy/config_test.cc @@ -40,7 +40,9 @@ TEST(RedisProxyFilterConfigFactoryTest, NoUpstreamDefined) { TEST(RedisProxyFilterConfigFactoryTest, RedisProxyNoSettings) { const std::string yaml = R"EOF( -cluster: fake_cluster +prefix_routes: + catch_all_route: + cluster: fake_cluster stat_prefix: foo )EOF"; @@ -51,7 +53,9 @@ stat_prefix: foo TEST(RedisProxyFilterConfigFactoryTest, RedisProxyNoOpTimeout) { const std::string yaml = R"EOF( -cluster: fake_cluster +prefix_routes: + catch_all_route: + cluster: fake_cluster stat_prefix: foo settings: {} )EOF"; @@ -61,7 +65,8 @@ settings: {} ProtoValidationException, "embedded message failed validation"); } -TEST(RedisProxyFilterConfigFactoryTest, RedisProxyCorrectProto) { +TEST(RedisProxyFilterConfigFactoryTest, + DEPRECATED_FEATURE_TEST(RedisProxyCorrectProtoLegacyCluster)) { const std::string yaml = R"EOF( cluster: fake_cluster stat_prefix: foo @@ -80,9 +85,53 @@ stat_prefix: foo cb(connection); } +TEST(RedisProxyFilterConfigFactoryTest, + DEPRECATED_FEATURE_TEST(RedisProxyCorrectProtoLegacyCatchAllCluster)) { + const std::string yaml = R"EOF( +prefix_routes: + catch_all_cluster: fake_cluster +stat_prefix: foo +settings: + op_timeout: 0.02s + )EOF"; + + envoy::config::filter::network::redis_proxy::v2::RedisProxy proto_config{}; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + NiceMock context; + RedisProxyFilterConfigFactory factory; + Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context); + EXPECT_TRUE(factory.isTerminalFilter()); + Network::MockConnection connection; + EXPECT_CALL(connection, addReadFilter(_)); + cb(connection); +} + +TEST(RedisProxyFilterConfigFactoryTest, RedisProxyCorrectProto) { + const std::string yaml = R"EOF( +prefix_routes: + catch_all_route: + cluster: fake_cluster +stat_prefix: foo +settings: + op_timeout: 0.02s + )EOF"; + + envoy::config::filter::network::redis_proxy::v2::RedisProxy proto_config{}; + TestUtility::loadFromYamlAndValidate(yaml, proto_config); + NiceMock context; + RedisProxyFilterConfigFactory factory; + Network::FilterFactoryCb cb = factory.createFilterFactoryFromProto(proto_config, context); + EXPECT_TRUE(factory.isTerminalFilter()); + Network::MockConnection connection; + EXPECT_CALL(connection, addReadFilter(_)); + cb(connection); +} + TEST(RedisProxyFilterConfigFactoryTest, RedisProxyEmptyProto) { const std::string yaml = R"EOF( -cluster: fake_cluster +prefix_routes: + catch_all_route: + cluster: fake_cluster stat_prefix: foo settings: op_timeout: 0.02s diff --git a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc index 22161d0e00..262e8d9bcd 100644 --- a/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/conn_pool_impl_test.cc @@ -66,11 +66,13 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client max_upstream_unknown_connections_reached_.value_++; })); + auto redis_command_stats = + Common::Redis::RedisCommandStats::createRedisCommandStats(store->symbolTable()); std::unique_ptr conn_pool_impl = std::make_unique(cluster_name_, cm_, *this, tls_, Common::Redis::Client::createConnPoolSettings( 20, hashtagging, true, max_unknown_conns, read_policy_), - api_, std::move(store)); + api_, std::move(store), redis_command_stats); // Set the authentication password for this connection pool. conn_pool_impl->tls_->getTyped().auth_password_ = auth_password_; conn_pool_ = std::move(conn_pool_impl); @@ -94,9 +96,6 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client Common::Redis::RespValue value; Common::Redis::Client::MockPoolCallbacks callbacks; Common::Redis::Client::MockPoolRequest active_request; - if (create_client && !auth_password_.empty()) { - EXPECT_CALL(*client_, makeRequest(_, _)).WillOnce(Return(nullptr)); - } EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) .WillRepeatedly(Return(test_address_)); EXPECT_CALL(*client_, makeRequest(Ref(value), Ref(callbacks))) @@ -155,13 +154,17 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client // Common::Redis::Client::ClientFactory Common::Redis::Client::ClientPtr create(Upstream::HostConstSharedPtr host, Event::Dispatcher&, - const Common::Redis::Client::Config&) override { + const Common::Redis::Client::Config&, + const Common::Redis::RedisCommandStatsSharedPtr&, + Stats::Scope&, const std::string& password) override { + EXPECT_EQ(auth_password_, password); return Common::Redis::Client::ClientPtr{create_(host)}; } void testReadPolicy( envoy::config::filter::network::redis_proxy::v2::RedisProxy::ConnPoolSettings::ReadPolicy - read_policy) { + read_policy, + NetworkFilters::Common::Redis::Client::ReadPolicy expected_read_policy) { InSequence s; read_policy_ = read_policy; @@ -178,13 +181,12 @@ class RedisConnPoolImplTest : public testing::Test, public Common::Redis::Client EXPECT_EQ(context->computeHashKey().value(), MurmurHash::murmurHash2_64("hash_key")); EXPECT_EQ(context->metadataMatchCriteria(), nullptr); EXPECT_EQ(context->downstreamConnection(), nullptr); + auto redis_context = + dynamic_cast(context); + EXPECT_EQ(redis_context->readPolicy(), expected_read_policy); return cm_.thread_local_cluster_.lb_.host_; })); EXPECT_CALL(*this, create_(_)).WillOnce(Return(client)); - EXPECT_CALL( - *client, - makeRequest(Eq(NetworkFilters::Common::Redis::Utility::ReadOnlyRequest::instance()), _)) - .WillOnce(Return(&readonly_request)); EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) .WillRepeatedly(Return(test_address_)); EXPECT_CALL(*client, makeRequest(Ref(value), Ref(callbacks))).WillOnce(Return(&active_request)); @@ -243,49 +245,19 @@ TEST_F(RedisConnPoolImplTest, Basic) { tls_.shutdownThread(); }; -TEST_F(RedisConnPoolImplTest, BasicWithAuthPassword) { - InSequence s; - - auth_password_ = "testing password"; - setup(); - - Common::Redis::RespValue value; - Common::Redis::Client::MockPoolRequest auth_request, active_request; - Common::Redis::Client::MockPoolCallbacks callbacks; - Common::Redis::Client::MockClient* client = new NiceMock(); - - EXPECT_CALL(cm_.thread_local_cluster_.lb_, chooseHost(_)) - .WillOnce(Invoke([&](Upstream::LoadBalancerContext* context) -> Upstream::HostConstSharedPtr { - EXPECT_EQ(context->computeHashKey().value(), MurmurHash::murmurHash2_64("hash_key")); - EXPECT_EQ(context->metadataMatchCriteria(), nullptr); - EXPECT_EQ(context->downstreamConnection(), nullptr); - return cm_.thread_local_cluster_.lb_.host_; - })); - EXPECT_CALL(*this, create_(_)).WillOnce(Return(client)); - EXPECT_CALL( - *client, - makeRequest(Eq(NetworkFilters::Common::Redis::Utility::makeAuthCommand(auth_password_)), _)) - .WillOnce(Return(&auth_request)); - EXPECT_CALL(*cm_.thread_local_cluster_.lb_.host_, address()) - .WillRepeatedly(Return(test_address_)); - EXPECT_CALL(*client, makeRequest(Ref(value), Ref(callbacks))).WillOnce(Return(&active_request)); - Common::Redis::Client::PoolRequest* request = - conn_pool_->makeRequest("hash_key", value, callbacks); - EXPECT_EQ(&active_request, request); - - EXPECT_CALL(*client, close()); - tls_.shutdownThread(); -}; - TEST_F(RedisConnPoolImplTest, BasicWithReadPolicy) { testReadPolicy(envoy::config::filter::network::redis_proxy::v2:: - RedisProxy_ConnPoolSettings_ReadPolicy_PREFER_MASTER); + RedisProxy_ConnPoolSettings_ReadPolicy_PREFER_MASTER, + NetworkFilters::Common::Redis::Client::ReadPolicy::PreferMaster); testReadPolicy(envoy::config::filter::network::redis_proxy::v2:: - RedisProxy_ConnPoolSettings_ReadPolicy_REPLICA); + RedisProxy_ConnPoolSettings_ReadPolicy_REPLICA, + NetworkFilters::Common::Redis::Client::ReadPolicy::Replica); testReadPolicy(envoy::config::filter::network::redis_proxy::v2:: - RedisProxy_ConnPoolSettings_ReadPolicy_PREFER_REPLICA); + RedisProxy_ConnPoolSettings_ReadPolicy_PREFER_REPLICA, + NetworkFilters::Common::Redis::Client::ReadPolicy::PreferReplica); testReadPolicy( - envoy::config::filter::network::redis_proxy::v2::RedisProxy_ConnPoolSettings_ReadPolicy_ANY); + envoy::config::filter::network::redis_proxy::v2::RedisProxy_ConnPoolSettings_ReadPolicy_ANY, + NetworkFilters::Common::Redis::Client::ReadPolicy::Any); }; TEST_F(RedisConnPoolImplTest, Hashtagging) { @@ -590,59 +562,6 @@ TEST_F(RedisConnPoolImplTest, MakeRequestToHostWithZeroMaxUnknownUpstreamConnect tls_.shutdownThread(); } -TEST_F(RedisConnPoolImplTest, MakeRequestToHostWithAuthPassword) { - InSequence s; - - auth_password_ = "superduperpassword"; - setup(false); - - Common::Redis::RespValue value; - Common::Redis::Client::MockPoolRequest auth_request1, active_request1; - Common::Redis::Client::MockPoolRequest auth_request2, active_request2; - Common::Redis::Client::MockPoolCallbacks callbacks1; - Common::Redis::Client::MockPoolCallbacks callbacks2; - Common::Redis::Client::MockClient* client1 = new NiceMock(); - Common::Redis::Client::MockClient* client2 = new NiceMock(); - Upstream::HostConstSharedPtr host1; - Upstream::HostConstSharedPtr host2; - - // There is no cluster yet, so makeRequestToHost() should fail. - EXPECT_EQ(nullptr, conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1)); - // Add the cluster now. - update_callbacks_->onClusterAddOrUpdate(cm_.thread_local_cluster_); - - EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host1), Return(client1))); - EXPECT_CALL( - *client1, - makeRequest(Eq(NetworkFilters::Common::Redis::Utility::makeAuthCommand(auth_password_)), _)) - .WillOnce(Return(&auth_request1)); - EXPECT_CALL(*client1, makeRequest(Ref(value), Ref(callbacks1))) - .WillOnce(Return(&active_request1)); - Common::Redis::Client::PoolRequest* request1 = - conn_pool_->makeRequestToHost("10.0.0.1:3000", value, callbacks1); - EXPECT_EQ(&active_request1, request1); - EXPECT_EQ(host1->address()->asString(), "10.0.0.1:3000"); - - // IPv6 address returned from Redis server will not have square brackets - // around it, while Envoy represents Address::Ipv6Instance addresses with square brackets around - // the address. - EXPECT_CALL(*this, create_(_)).WillOnce(DoAll(SaveArg<0>(&host2), Return(client2))); - EXPECT_CALL( - *client2, - makeRequest(Eq(NetworkFilters::Common::Redis::Utility::makeAuthCommand(auth_password_)), _)) - .WillOnce(Return(&auth_request2)); - EXPECT_CALL(*client2, makeRequest(Ref(value), Ref(callbacks2))) - .WillOnce(Return(&active_request2)); - Common::Redis::Client::PoolRequest* request2 = - conn_pool_->makeRequestToHost("2001:470:813B:0:0:0:0:1:3333", value, callbacks2); - EXPECT_EQ(&active_request2, request2); - EXPECT_EQ(host2->address()->asString(), "[2001:470:813b::1]:3333"); - - EXPECT_CALL(*client2, close()); - EXPECT_CALL(*client1, close()); - tls_.shutdownThread(); -} - // This test forces the creation of 2 hosts (one with an IPv4 address, and the other with an IPv6 // address) and pending requests using makeRequestToHost(). After their creation, "new" hosts are // discovered, and the original hosts are put aside to drain. The test then verifies the drain diff --git a/test/extensions/filters/network/redis_proxy/mocks.cc b/test/extensions/filters/network/redis_proxy/mocks.cc index f06a6bd9b0..9c14572583 100644 --- a/test/extensions/filters/network/redis_proxy/mocks.cc +++ b/test/extensions/filters/network/redis_proxy/mocks.cc @@ -1,7 +1,5 @@ #include "mocks.h" -using testing::_; -using testing::Invoke; using testing::Return; using testing::ReturnRef; diff --git a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc index c9f8c2ce10..228135bef5 100644 --- a/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc +++ b/test/extensions/filters/network/redis_proxy/redis_proxy_integration_test.cc @@ -7,7 +7,6 @@ #include "gtest/gtest.h" -using testing::Matcher; using testing::Return; namespace RedisCmdSplitter = Envoy::Extensions::NetworkFilters::RedisProxy::CommandSplitter; @@ -56,7 +55,9 @@ const std::string CONFIG = R"EOF( name: envoy.redis_proxy config: stat_prefix: redis_stats - cluster: cluster_0 + prefix_routes: + catch_all_route: + cluster: cluster_0 settings: op_timeout: 5s )EOF"; @@ -149,7 +150,8 @@ const std::string CONFIG_WITH_ROUTES_BASE = R"EOF( const std::string CONFIG_WITH_ROUTES = CONFIG_WITH_ROUTES_BASE + R"EOF( prefix_routes: - catch_all_cluster: cluster_0 + catch_all_route: + cluster: cluster_0 routes: - prefix: "foo:" cluster: cluster_1 @@ -250,7 +252,8 @@ const std::string CONFIG_WITH_ROUTES_AND_AUTH_PASSWORDS = R"EOF( settings: op_timeout: 5s prefix_routes: - catch_all_cluster: cluster_0 + catch_all_route: + cluster: cluster_0 routes: - prefix: "foo:" cluster: cluster_1 diff --git a/test/extensions/filters/network/redis_proxy/router_impl_test.cc b/test/extensions/filters/network/redis_proxy/router_impl_test.cc index 9ff4bc5ade..b4d12c4194 100644 --- a/test/extensions/filters/network/redis_proxy/router_impl_test.cc +++ b/test/extensions/filters/network/redis_proxy/router_impl_test.cc @@ -8,14 +8,10 @@ #include "test/mocks/runtime/mocks.h" #include "test/test_common/utility.h" -using testing::_; using testing::Eq; -using testing::InSequence; using testing::Matcher; using testing::NiceMock; -using testing::Ref; using testing::Return; -using testing::StrEq; namespace Envoy { namespace Extensions { @@ -229,6 +225,22 @@ TEST(MirrorPolicyImplTest, ExcludeReadCommands) { EXPECT_EQ(true, policy.shouldMirror("set")); } +TEST(MirrorPolicyImplTest, DefaultValueZero) { + envoy::config::filter::network::redis_proxy::v2::RedisProxy::PrefixRoutes::Route:: + RequestMirrorPolicy config; + auto* runtime_fraction = config.mutable_runtime_fraction(); + auto* percentage = runtime_fraction->mutable_default_value(); + percentage->set_numerator(0); + percentage->set_denominator(envoy::type::FractionalPercent::HUNDRED); + auto upstream = std::make_shared(); + NiceMock runtime; + + MirrorPolicyImpl policy(config, upstream, runtime); + + EXPECT_EQ(false, policy.shouldMirror("get")); + EXPECT_EQ(false, policy.shouldMirror("set")); +} + TEST(MirrorPolicyImplTest, DeterminedByRuntimeFraction) { envoy::config::filter::network::redis_proxy::v2::RedisProxy::PrefixRoutes::Route:: RequestMirrorPolicy config; diff --git a/test/extensions/filters/network/sni_cluster/sni_cluster_test.cc b/test/extensions/filters/network/sni_cluster/sni_cluster_test.cc index 9cc39bd6d2..2625e70db4 100644 --- a/test/extensions/filters/network/sni_cluster/sni_cluster_test.cc +++ b/test/extensions/filters/network/sni_cluster/sni_cluster_test.cc @@ -11,7 +11,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::Matcher; using testing::NiceMock; using testing::Return; using testing::ReturnRef; diff --git a/test/extensions/filters/network/thrift_proxy/auto_protocol_impl_test.cc b/test/extensions/filters/network/thrift_proxy/auto_protocol_impl_test.cc index 63f33d6f10..cec16c3a59 100644 --- a/test/extensions/filters/network/thrift_proxy/auto_protocol_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/auto_protocol_impl_test.cc @@ -18,7 +18,6 @@ using testing::NiceMock; using testing::Ref; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc b/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc index a8e76e040a..45e763cb00 100644 --- a/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc +++ b/test/extensions/filters/network/thrift_proxy/conn_manager_test.cc @@ -28,7 +28,6 @@ using testing::Invoke; using testing::NiceMock; using testing::Ref; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { @@ -91,7 +90,7 @@ class ThriftConnectionManagerTest : public testing::Test { proto_config_.set_stat_prefix("test"); } else { TestUtility::loadFromYaml(yaml, proto_config_); - MessageUtil::validate(proto_config_); + TestUtility::validate(proto_config_); } proto_config_.set_stat_prefix("test"); @@ -509,6 +508,17 @@ TEST_F(ThriftConnectionManagerTest, OnDataHandlesTransportApplicationException) EXPECT_EQ(0U, stats_.request_active_.value()); } +// Tests that OnData handles non-thrift input. Regression test for crash on invalid input. +TEST_F(ThriftConnectionManagerTest, OnDataHandlesGarbageRequest) { + initializeFilter(); + addRepeated(buffer_, 8, 0); + EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::FlushWrite)); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_decoding_error").value()); + EXPECT_EQ(0U, stats_.request_active_.value()); +} + TEST_F(ThriftConnectionManagerTest, OnEvent) { // No active calls { @@ -895,6 +905,41 @@ TEST_F(ThriftConnectionManagerTest, RequestAndTransportApplicationException) { EXPECT_EQ(1U, store_.counter("test.response_decoding_error").value()); } +// Tests that a request is routed and a non-thrift response is handled. +TEST_F(ThriftConnectionManagerTest, RequestAndGarbageResponse) { + initializeFilter(); + writeFramedBinaryMessage(buffer_, MessageType::Call, 0x0F); + + ThriftFilters::DecoderFilterCallbacks* callbacks{}; + EXPECT_CALL(*decoder_filter_, setDecoderFilterCallbacks(_)) + .WillOnce( + Invoke([&](ThriftFilters::DecoderFilterCallbacks& cb) -> void { callbacks = &cb; })); + + EXPECT_EQ(filter_->onData(buffer_, false), Network::FilterStatus::StopIteration); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + + addRepeated(write_buffer_, 8, 0); + + FramedTransportImpl transport; + BinaryProtocolImpl proto; + callbacks->startUpstreamResponse(transport, proto); + + EXPECT_CALL(filter_callbacks_.connection_.dispatcher_, deferredDelete_(_)).Times(1); + EXPECT_EQ(ThriftFilters::ResponseStatus::Reset, callbacks->upstreamData(write_buffer_)); + + filter_callbacks_.connection_.dispatcher_.clearDeferredDeleteList(); + + EXPECT_EQ(1U, store_.counter("test.request").value()); + EXPECT_EQ(1U, store_.counter("test.request_call").value()); + EXPECT_EQ(0U, stats_.request_active_.value()); + EXPECT_EQ(0U, store_.counter("test.response").value()); + EXPECT_EQ(0U, store_.counter("test.response_reply").value()); + EXPECT_EQ(1U, store_.counter("test.response_exception").value()); + EXPECT_EQ(0U, store_.counter("test.response_invalid_type").value()); + EXPECT_EQ(0U, store_.counter("test.response_success").value()); + EXPECT_EQ(0U, store_.counter("test.response_error").value()); +} + TEST_F(ThriftConnectionManagerTest, PipelinedRequestAndResponse) { initializeFilter(); diff --git a/test/extensions/filters/network/thrift_proxy/decoder_test.cc b/test/extensions/filters/network/thrift_proxy/decoder_test.cc index 74d706a541..1dc42a1a11 100644 --- a/test/extensions/filters/network/thrift_proxy/decoder_test.cc +++ b/test/extensions/filters/network/thrift_proxy/decoder_test.cc @@ -16,7 +16,6 @@ using testing::_; using testing::AnyNumber; using testing::Combine; using testing::DoAll; -using testing::Expectation; using testing::ExpectationSet; using testing::InSequence; using testing::Invoke; diff --git a/test/extensions/filters/network/thrift_proxy/filters/ratelimit/config_test.cc b/test/extensions/filters/network/thrift_proxy/filters/ratelimit/config_test.cc index e801e87842..63a5f256a9 100644 --- a/test/extensions/filters/network/thrift_proxy/filters/ratelimit/config_test.cc +++ b/test/extensions/filters/network/thrift_proxy/filters/ratelimit/config_test.cc @@ -9,7 +9,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/network/thrift_proxy/filters/ratelimit/ratelimit_test.cc b/test/extensions/filters/network/thrift_proxy/filters/ratelimit/ratelimit_test.cc index 01877c365c..83f0b7edaf 100644 --- a/test/extensions/filters/network/thrift_proxy/filters/ratelimit/ratelimit_test.cc +++ b/test/extensions/filters/network/thrift_proxy/filters/ratelimit/ratelimit_test.cc @@ -28,7 +28,6 @@ using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Return; -using testing::ReturnRef; using testing::SetArgReferee; using testing::WithArgs; diff --git a/test/extensions/filters/network/thrift_proxy/header_transport_impl_test.cc b/test/extensions/filters/network/thrift_proxy/header_transport_impl_test.cc index 791a2f9f4d..4ed3f51fda 100644 --- a/test/extensions/filters/network/thrift_proxy/header_transport_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/header_transport_impl_test.cc @@ -13,7 +13,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::NiceMock; using testing::Return; namespace Envoy { diff --git a/test/extensions/filters/network/thrift_proxy/integration_test.cc b/test/extensions/filters/network/thrift_proxy/integration_test.cc index 0350244d29..ec6db8fd08 100644 --- a/test/extensions/filters/network/thrift_proxy/integration_test.cc +++ b/test/extensions/filters/network/thrift_proxy/integration_test.cc @@ -39,7 +39,9 @@ class ThriftConnManagerIntegrationTest - name: "x-header-1" exact_match: "x-value-1" - name: "x-header-2" - regex_match: "0.[5-9]" + safe_regex_match: + google_re2: {} + regex: "0.[5-9]" - name: "x-header-3" range_match: start: 100 diff --git a/test/extensions/filters/network/thrift_proxy/mocks.cc b/test/extensions/filters/network/thrift_proxy/mocks.cc index 3f591919a4..ae130a3c42 100644 --- a/test/extensions/filters/network/thrift_proxy/mocks.cc +++ b/test/extensions/filters/network/thrift_proxy/mocks.cc @@ -12,7 +12,8 @@ using testing::ReturnRef; namespace Envoy { // Provide a specialization for ProtobufWkt::Struct (for MockFilterConfigFactory) -template <> void MessageUtil::validate(const ProtobufWkt::Struct&) {} +template <> +void MessageUtil::validate(const ProtobufWkt::Struct&, ProtobufMessage::ValidationVisitor&) {} namespace Extensions { namespace NetworkFilters { diff --git a/test/extensions/filters/network/thrift_proxy/route_matcher_test.cc b/test/extensions/filters/network/thrift_proxy/route_matcher_test.cc index 794cc084e0..1966560ca3 100644 --- a/test/extensions/filters/network/thrift_proxy/route_matcher_test.cc +++ b/test/extensions/filters/network/thrift_proxy/route_matcher_test.cc @@ -9,8 +9,6 @@ #include "gtest/gtest.h" -using testing::_; - namespace Envoy { namespace Extensions { namespace NetworkFilters { @@ -22,7 +20,7 @@ envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration parseRouteConfigurationFromV2Yaml(const std::string& yaml) { envoy::config::filter::network::thrift_proxy::v2alpha1::RouteConfiguration route_config; TestUtility::loadFromYaml(yaml, route_config); - MessageUtil::validate(route_config); + TestUtility::validate(route_config); return route_config; } @@ -339,7 +337,9 @@ name: config method_name: "method1" headers: - name: "x-version" - regex_match: "0.[5-9]" + safe_regex_match: + google_re2: {} + regex: "0.[5-9]" route: cluster: "cluster1" )EOF"; diff --git a/test/extensions/filters/network/thrift_proxy/router_test.cc b/test/extensions/filters/network/thrift_proxy/router_test.cc index ad4686746b..6e6743016f 100644 --- a/test/extensions/filters/network/thrift_proxy/router_test.cc +++ b/test/extensions/filters/network/thrift_proxy/router_test.cc @@ -24,7 +24,6 @@ using testing::_; using testing::ContainsRegex; using testing::Eq; -using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Ref; diff --git a/test/extensions/filters/network/thrift_proxy/thrift_object_impl_test.cc b/test/extensions/filters/network/thrift_proxy/thrift_object_impl_test.cc index 7c4bad9614..a79d305056 100644 --- a/test/extensions/filters/network/thrift_proxy/thrift_object_impl_test.cc +++ b/test/extensions/filters/network/thrift_proxy/thrift_object_impl_test.cc @@ -10,14 +10,12 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::AnyNumber; using testing::Expectation; using testing::ExpectationSet; using testing::InSequence; using testing::NiceMock; using testing::Ref; using testing::Return; -using testing::ReturnRef; using testing::Values; namespace Envoy { diff --git a/test/extensions/filters/network/thrift_proxy/utility.h b/test/extensions/filters/network/thrift_proxy/utility.h index a3ab975a64..bd91e3b36b 100644 --- a/test/extensions/filters/network/thrift_proxy/utility.h +++ b/test/extensions/filters/network/thrift_proxy/utility.h @@ -23,8 +23,8 @@ namespace NetworkFilters { namespace ThriftProxy { namespace { -using Envoy::Buffer::addRepeated; -using Envoy::Buffer::addSeq; +using Envoy::Buffer::addRepeated; // NOLINT(misc-unused-using-decls) +using Envoy::Buffer::addSeq; // NOLINT(misc-unused-using-decls) inline std::string fieldTypeToString(const FieldType& field_type) { switch (field_type) { diff --git a/test/extensions/health_checkers/redis/BUILD b/test/extensions/health_checkers/redis/BUILD index 015f73f5e4..8f38c2dfe3 100644 --- a/test/extensions/health_checkers/redis/BUILD +++ b/test/extensions/health_checkers/redis/BUILD @@ -16,6 +16,7 @@ envoy_extension_cc_test( srcs = ["redis_test.cc"], extension_name = "envoy.health_checkers.redis", deps = [ + "//source/common/api:api_lib", "//source/extensions/health_checkers/redis", "//source/extensions/health_checkers/redis:utility", "//test/common/upstream:utility_lib", @@ -25,6 +26,7 @@ envoy_extension_cc_test( "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/upstream:upstream_mocks", + "//test/test_common:utility_lib", ], ) diff --git a/test/extensions/health_checkers/redis/config_test.cc b/test/extensions/health_checkers/redis/config_test.cc index 2dcb9a4391..8b38426fc6 100644 --- a/test/extensions/health_checkers/redis/config_test.cc +++ b/test/extensions/health_checkers/redis/config_test.cc @@ -106,12 +106,14 @@ TEST(HealthCheckerFactoryTest, CreateRedisViaUpstreamHealthCheckerFactory) { Runtime::MockRandomGenerator random; Event::MockDispatcher dispatcher; AccessLog::MockAccessLogManager log_manager; - - EXPECT_NE(nullptr, dynamic_cast( - Upstream::HealthCheckerFactory::create( - Upstream::parseHealthCheckFromV2Yaml(yaml), cluster, runtime, random, - dispatcher, log_manager, ProtobufMessage::getStrictValidationVisitor()) - .get())); + NiceMock api; + + EXPECT_NE(nullptr, + dynamic_cast( + Upstream::HealthCheckerFactory::create( + Upstream::parseHealthCheckFromV2Yaml(yaml), cluster, runtime, random, + dispatcher, log_manager, ProtobufMessage::getStrictValidationVisitor(), api) + .get())); } } // namespace } // namespace RedisHealthChecker diff --git a/test/extensions/health_checkers/redis/redis_test.cc b/test/extensions/health_checkers/redis/redis_test.cc index f8be7912d1..c556162203 100644 --- a/test/extensions/health_checkers/redis/redis_test.cc +++ b/test/extensions/health_checkers/redis/redis_test.cc @@ -1,5 +1,7 @@ #include +#include "envoy/api/api.h" + #include "extensions/health_checkers/redis/redis.h" #include "extensions/health_checkers/redis/utility.h" @@ -16,8 +18,6 @@ using testing::InSequence; using testing::NiceMock; using testing::Ref; using testing::Return; -using testing::ReturnRef; -using testing::SaveArg; using testing::WithArg; namespace Envoy { @@ -31,7 +31,7 @@ class RedisHealthCheckerTest public: RedisHealthCheckerTest() : cluster_(new NiceMock()), - event_logger_(new Upstream::MockHealthCheckEventLogger()) {} + event_logger_(new Upstream::MockHealthCheckEventLogger()), api_(Api::createApiForTest()) {} void setup() { const std::string yaml = R"EOF( @@ -50,9 +50,9 @@ class RedisHealthCheckerTest const auto& redis_config = getRedisHealthCheckConfig( health_check_config, ProtobufMessage::getStrictValidationVisitor()); - health_checker_.reset( - new RedisHealthChecker(*cluster_, health_check_config, redis_config, dispatcher_, runtime_, - random_, Upstream::HealthCheckEventLoggerPtr(event_logger_), *this)); + health_checker_.reset(new RedisHealthChecker( + *cluster_, health_check_config, redis_config, dispatcher_, runtime_, random_, + Upstream::HealthCheckEventLoggerPtr(event_logger_), *api_, *this)); } void setupAlwaysLogHealthCheckFailures() { @@ -73,9 +73,9 @@ class RedisHealthCheckerTest const auto& redis_config = getRedisHealthCheckConfig( health_check_config, ProtobufMessage::getStrictValidationVisitor()); - health_checker_.reset( - new RedisHealthChecker(*cluster_, health_check_config, redis_config, dispatcher_, runtime_, - random_, Upstream::HealthCheckEventLoggerPtr(event_logger_), *this)); + health_checker_.reset(new RedisHealthChecker( + *cluster_, health_check_config, redis_config, dispatcher_, runtime_, random_, + Upstream::HealthCheckEventLoggerPtr(event_logger_), *api_, *this)); } void setupExistsHealthcheck() { @@ -96,9 +96,9 @@ class RedisHealthCheckerTest const auto& redis_config = getRedisHealthCheckConfig( health_check_config, ProtobufMessage::getStrictValidationVisitor()); - health_checker_.reset( - new RedisHealthChecker(*cluster_, health_check_config, redis_config, dispatcher_, runtime_, - random_, Upstream::HealthCheckEventLoggerPtr(event_logger_), *this)); + health_checker_.reset(new RedisHealthChecker( + *cluster_, health_check_config, redis_config, dispatcher_, runtime_, random_, + Upstream::HealthCheckEventLoggerPtr(event_logger_), *api_, *this)); } void setupDontReuseConnection() { @@ -119,14 +119,16 @@ class RedisHealthCheckerTest const auto& redis_config = getRedisHealthCheckConfig( health_check_config, ProtobufMessage::getStrictValidationVisitor()); - health_checker_.reset( - new RedisHealthChecker(*cluster_, health_check_config, redis_config, dispatcher_, runtime_, - random_, Upstream::HealthCheckEventLoggerPtr(event_logger_), *this)); + health_checker_.reset(new RedisHealthChecker( + *cluster_, health_check_config, redis_config, dispatcher_, runtime_, random_, + Upstream::HealthCheckEventLoggerPtr(event_logger_), *api_, *this)); } Extensions::NetworkFilters::Common::Redis::Client::ClientPtr create(Upstream::HostConstSharedPtr, Event::Dispatcher&, - const Extensions::NetworkFilters::Common::Redis::Client::Config&) override { + const Extensions::NetworkFilters::Common::Redis::Client::Config&, + const Extensions::NetworkFilters::Common::Redis::RedisCommandStatsSharedPtr&, + Stats::Scope&, const std::string&) override { return Extensions::NetworkFilters::Common::Redis::Client::ClientPtr{create_()}; } @@ -146,13 +148,13 @@ class RedisHealthCheckerTest void expectExistsRequestCreate() { EXPECT_CALL(*client_, makeRequest(Ref(RedisHealthChecker::existsHealthCheckRequest("")), _)) .WillOnce(DoAll(WithArg<1>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); } void expectPingRequestCreate() { EXPECT_CALL(*client_, makeRequest(Ref(RedisHealthChecker::pingHealthCheckRequest()), _)) .WillOnce(DoAll(WithArg<1>(SaveArgAddress(&pool_callbacks_)), Return(&pool_request_))); - EXPECT_CALL(*timeout_timer_, enableTimer(_)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); } void exerciseStubs() { @@ -168,6 +170,7 @@ class RedisHealthCheckerTest EXPECT_EQ(session->maxBufferSizeBeforeFlush(), 0); EXPECT_EQ(session->bufferFlushTimeoutInMs(), std::chrono::milliseconds(1)); EXPECT_EQ(session->maxUpstreamUnknownConnections(), 0); + EXPECT_FALSE(session->enableCommandStats()); session->onDeferredDeleteBase(); // This must be called to pass assertions in the destructor. } @@ -182,6 +185,7 @@ class RedisHealthCheckerTest Extensions::NetworkFilters::Common::Redis::Client::MockPoolRequest pool_request_; Extensions::NetworkFilters::Common::Redis::Client::PoolCallbacks* pool_callbacks_{}; std::shared_ptr health_checker_; + Api::ApiPtr api_; }; TEST_F(RedisHealthCheckerTest, PingAndVariousFailures) { @@ -204,7 +208,7 @@ TEST_F(RedisHealthCheckerTest, PingAndVariousFailures) { // Success EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); NetworkFilters::Common::Redis::RespValuePtr response( new NetworkFilters::Common::Redis::RespValue()); response->type(NetworkFilters::Common::Redis::RespType::SimpleString); @@ -217,7 +221,7 @@ TEST_F(RedisHealthCheckerTest, PingAndVariousFailures) { // Failure EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); response = std::make_unique(); pool_callbacks_->onResponse(std::move(response)); @@ -226,7 +230,7 @@ TEST_F(RedisHealthCheckerTest, PingAndVariousFailures) { // Redis failure via disconnect EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); pool_callbacks_->onFailure(); client_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -238,7 +242,7 @@ TEST_F(RedisHealthCheckerTest, PingAndVariousFailures) { EXPECT_CALL(pool_request_, cancel()); EXPECT_CALL(*client_, close()); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); timeout_timer_->invokeCallback(); expectClientCreate(); @@ -272,7 +276,7 @@ TEST_F(RedisHealthCheckerTest, FailuresLogging) { // Success EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); NetworkFilters::Common::Redis::RespValuePtr response( new NetworkFilters::Common::Redis::RespValue()); response->type(NetworkFilters::Common::Redis::RespType::SimpleString); @@ -286,7 +290,7 @@ TEST_F(RedisHealthCheckerTest, FailuresLogging) { EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, false)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); response = std::make_unique(); pool_callbacks_->onResponse(std::move(response)); @@ -296,7 +300,7 @@ TEST_F(RedisHealthCheckerTest, FailuresLogging) { // Fail again EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, false)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); response = std::make_unique(); pool_callbacks_->onResponse(std::move(response)); @@ -332,7 +336,7 @@ TEST_F(RedisHealthCheckerTest, LogInitialFailure) { EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*event_logger_, logUnhealthy(_, _, _, true)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); pool_callbacks_->onFailure(); client_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -343,7 +347,7 @@ TEST_F(RedisHealthCheckerTest, LogInitialFailure) { // Success EXPECT_CALL(*event_logger_, logAddHealthy(_, _, false)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); NetworkFilters::Common::Redis::RespValuePtr response( new NetworkFilters::Common::Redis::RespValue()); response->type(NetworkFilters::Common::Redis::RespType::SimpleString); @@ -380,7 +384,7 @@ TEST_F(RedisHealthCheckerTest, Exists) { // Success EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); NetworkFilters::Common::Redis::RespValuePtr response( new NetworkFilters::Common::Redis::RespValue()); response->type(NetworkFilters::Common::Redis::RespType::Integer); @@ -393,7 +397,7 @@ TEST_F(RedisHealthCheckerTest, Exists) { // Failure, exists EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); response = std::make_unique(); response->type(NetworkFilters::Common::Redis::RespType::Integer); response->asInteger() = 1; @@ -404,7 +408,7 @@ TEST_F(RedisHealthCheckerTest, Exists) { // Failure, no value EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); response = std::make_unique(); pool_callbacks_->onResponse(std::move(response)); @@ -432,7 +436,7 @@ TEST_F(RedisHealthCheckerTest, ExistsRedirected) { // Success with moved redirection EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); NetworkFilters::Common::Redis::RespValue moved_response; moved_response.type(NetworkFilters::Common::Redis::RespType::Error); moved_response.asString() = "MOVED 1111 127.0.0.1:81"; // exact values not important @@ -443,7 +447,7 @@ TEST_F(RedisHealthCheckerTest, ExistsRedirected) { // Success with ask redirection EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); NetworkFilters::Common::Redis::RespValue ask_response; ask_response.type(NetworkFilters::Common::Redis::RespType::Error); ask_response.asString() = "ASK 1111 127.0.0.1:81"; // exact values not important @@ -471,7 +475,7 @@ TEST_F(RedisHealthCheckerTest, NoConnectionReuse) { // The connection will close on success. EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); EXPECT_CALL(*client_, close()); NetworkFilters::Common::Redis::RespValuePtr response( new NetworkFilters::Common::Redis::RespValue()); @@ -486,7 +490,7 @@ TEST_F(RedisHealthCheckerTest, NoConnectionReuse) { // The connection will close on failure. EXPECT_CALL(*event_logger_, logEjectUnhealthy(_, _, _)); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); EXPECT_CALL(*client_, close()); response = std::make_unique(); pool_callbacks_->onResponse(std::move(response)); @@ -497,7 +501,7 @@ TEST_F(RedisHealthCheckerTest, NoConnectionReuse) { // Redis failure via disconnect, the connection was closed by the other end. EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); pool_callbacks_->onFailure(); client_->raiseEvent(Network::ConnectionEvent::RemoteClose); @@ -509,7 +513,7 @@ TEST_F(RedisHealthCheckerTest, NoConnectionReuse) { EXPECT_CALL(pool_request_, cancel()); EXPECT_CALL(*client_, close()); EXPECT_CALL(*timeout_timer_, disableTimer()); - EXPECT_CALL(*interval_timer_, enableTimer(_)); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); timeout_timer_->invokeCallback(); expectClientCreate(); diff --git a/test/extensions/quic_listeners/quiche/BUILD b/test/extensions/quic_listeners/quiche/BUILD index 313ef1d0e8..b84e7ba9c8 100644 --- a/test/extensions/quic_listeners/quiche/BUILD +++ b/test/extensions/quic_listeners/quiche/BUILD @@ -30,6 +30,7 @@ envoy_cc_test( name = "envoy_quic_writer_test", srcs = ["envoy_quic_writer_test.cc"], external_deps = ["quiche_quic_platform"], + tags = ["nofips"], deps = [ "//source/common/network:io_socket_error_lib", "//source/extensions/quic_listeners/quiche:envoy_quic_packet_writer_lib", @@ -49,6 +50,87 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "envoy_quic_server_stream_test", + srcs = ["envoy_quic_server_stream_test.cc"], + tags = ["nofips"], + deps = [ + ":quic_test_utils_for_envoy_lib", + "//source/common/http:headers_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_server_stream_lib", + "//test/mocks/http:stream_decoder_mock", + "//test/mocks/network:network_mocks", + "//test/test_common:utility_lib", + "@com_googlesource_quiche//:quic_core_http_spdy_session_lib", + ], +) + +envoy_cc_test( + name = "envoy_quic_server_session_test", + srcs = ["envoy_quic_server_session_test.cc"], + tags = ["nofips"], + deps = [ + ":quic_test_utils_for_envoy_lib", + "//include/envoy/stats:stats_macros", + "//source/extensions/quic_listeners/quiche:codec_impl_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_source_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_server_session_lib", + "//source/server:configuration_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/http:http_mocks", + "//test/mocks/http:stream_decoder_mock", + "//test/mocks/network:network_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:global_lib", + "//test/test_common:logging_lib", + "//test/test_common:simulated_time_system_lib", + ], +) + +envoy_cc_test( + name = "active_quic_listener_test", + srcs = ["active_quic_listener_test.cc"], + tags = ["nofips"], + deps = [ + ":quic_test_utils_for_envoy_lib", + "//source/extensions/quic_listeners/quiche:active_quic_listener_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_utils_lib", + "//source/server:configuration_lib", + "//test/mocks/network:network_mocks", + "//test/test_common:environment_lib", + "//test/test_common:network_utility_lib", + "//test/test_common:simulated_time_system_lib", + ], +) + +envoy_cc_test( + name = "envoy_quic_dispatcher_test", + srcs = ["envoy_quic_dispatcher_test.cc"], + tags = ["nofips"], + deps = [ + ":quic_test_utils_for_envoy_lib", + "//include/envoy/stats:stats_macros", + "//source/extensions/quic_listeners/quiche:envoy_quic_alarm_factory_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_connection_helper_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_dispatcher_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_proof_source_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_server_session_lib", + "//source/server:configuration_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/http:http_mocks", + "//test/mocks/network:network_mocks", + "//test/mocks/stats:stats_mocks", + "//test/test_common:environment_lib", + "//test/test_common:global_lib", + "//test/test_common:simulated_time_system_lib", + ], +) + envoy_cc_test_library( name = "quic_test_utils_for_envoy_lib", srcs = ["crypto_test_utils_for_envoy.cc"], @@ -59,3 +141,39 @@ envoy_cc_test_library( "@com_googlesource_quiche//:quic_test_tools_test_utils_interface_lib", ], ) + +envoy_cc_test( + name = "quic_io_handle_wrapper_test", + srcs = ["quic_io_handle_wrapper_test.cc"], + tags = ["nofips"], + deps = [ + "//source/extensions/quic_listeners/quiche:quic_io_handle_wrapper_lib", + "//test/mocks/api:api_mocks", + "//test/mocks/network:network_mocks", + "//test/test_common:threadsafe_singleton_injector_lib", + ], +) + +envoy_cc_test( + name = "envoy_quic_utils_test", + srcs = ["envoy_quic_utils_test.cc"], + tags = ["nofips"], + deps = [ + ":quic_test_utils_for_envoy_lib", + "//source/extensions/quic_listeners/quiche:envoy_quic_utils_lib", + "//test/mocks/api:api_mocks", + "//test/test_common:threadsafe_singleton_injector_lib", + ], +) + +envoy_cc_test( + name = "active_quic_listener_config_test", + srcs = ["active_quic_listener_config_test.cc"], + tags = ["nofips"], + deps = [ + "//source/common/config:utility_lib", + "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/api/v2/listener:quic_config_cc", + ], +) diff --git a/test/extensions/quic_listeners/quiche/active_quic_listener_config_test.cc b/test/extensions/quic_listeners/quiche/active_quic_listener_config_test.cc new file mode 100644 index 0000000000..6f0c0e4696 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/active_quic_listener_config_test.cc @@ -0,0 +1,48 @@ +#include "envoy/api/v2/listener/quic_config.pb.h" + +#include "common/config/utility.h" + +#include "extensions/quic_listeners/quiche/active_quic_listener.h" +#include "extensions/quic_listeners/quiche/active_quic_listener_config.h" + +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Quic { + +class ActiveQuicListenerFactoryPeer { +public: + static quic::QuicConfig& quicConfig(ActiveQuicListenerFactory& factory) { + return factory.quic_config_; + } +}; + +TEST(ActiveQuicListenerConfigTest, CreateActiveQuicListenerFactory) { + std::string listener_name = QuicListenerName; + auto& config_factory = + Config::Utility::getAndCheckFactory(listener_name); + ProtobufTypes::MessagePtr config = config_factory.createEmptyConfigProto(); + + std::string yaml = R"EOF( + max_concurrent_streams: 10 + idle_timeout: { + seconds: 2 + } + )EOF"; + TestUtility::loadFromYaml(yaml, *config); + Network::ActiveUdpListenerFactoryPtr listener_factory = + config_factory.createActiveUdpListenerFactory(*config); + EXPECT_NE(nullptr, listener_factory); + quic::QuicConfig& quic_config = ActiveQuicListenerFactoryPeer::quicConfig( + dynamic_cast(*listener_factory)); + EXPECT_EQ(10u, quic_config.GetMaxIncomingBidirectionalStreamsToSend()); + EXPECT_EQ(10u, quic_config.GetMaxIncomingUnidirectionalStreamsToSend()); + EXPECT_EQ(2000u, quic_config.IdleNetworkTimeout().ToMilliseconds()); + // Default value if not present in config. + EXPECT_EQ(20000u, quic_config.max_time_before_crypto_handshake().ToMilliseconds()); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc b/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc new file mode 100644 index 0000000000..6c27bade77 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/active_quic_listener_test.cc @@ -0,0 +1,192 @@ +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/crypto/crypto_protocol.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +#pragma GCC diagnostic pop + +#include "server/configuration_impl.h" +#include "common/common/logger.h" +#include "common/network/listen_socket_impl.h" +#include "common/network/socket_option_factory.h" +#include "extensions/quic_listeners/quiche/active_quic_listener.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/environment.h" +#include "test/mocks/network/mocks.h" +#include "test/test_common/utility.h" +#include "test/test_common/network_utility.h" +#include "gtest/gtest.h" +#include "gmock/gmock.h" +#include "extensions/quic_listeners/quiche/platform/envoy_quic_clock.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Quic { + +class ActiveQuicListenerPeer { +public: + static EnvoyQuicDispatcher* quic_dispatcher(ActiveQuicListener& listener) { + return listener.quic_dispatcher_.get(); + } + + static quic::QuicCryptoServerConfig& crypto_config(ActiveQuicListener& listener) { + return *listener.crypto_config_; + } +}; + +class ActiveQuicListenerTest : public testing::TestWithParam, + protected Logger::Loggable { +public: + ActiveQuicListenerTest() + : version_(GetParam()), api_(Api::createApiForTest(simulated_time_system_)), + dispatcher_(api_->allocateDispatcher()), read_filter_(new Network::MockReadFilter()), + filter_factory_({[this](Network::FilterManager& filter_manager) { + filter_manager.addReadFilter(read_filter_); + read_filter_->callbacks_->connection().addConnectionCallbacks( + network_connection_callbacks_); + }}), + connection_handler_(ENVOY_LOGGER(), *dispatcher_) { + EXPECT_CALL(listener_config_, listenerFiltersTimeout()); + EXPECT_CALL(listener_config_, continueOnListenerFiltersTimeout()); + EXPECT_CALL(listener_config_, listenerTag()); + } + + void SetUp() override { + listen_socket_ = std::make_unique>>( + Network::Test::getCanonicalLoopbackAddress(version_), nullptr, /*bind*/ true); + listen_socket_->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); + listen_socket_->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); + client_socket_ = std::make_unique>>( + Network::Test::getCanonicalLoopbackAddress(version_), nullptr, /*bind*/ false); + EXPECT_CALL(listener_config_, socket()).WillRepeatedly(ReturnRef(*listen_socket_)); + ON_CALL(listener_config_, filterChainManager()).WillByDefault(ReturnRef(filter_chain_manager_)); + ON_CALL(filter_chain_manager_, findFilterChain(_)).WillByDefault(Return(&filter_chain_)); + ON_CALL(filter_chain_, networkFilterFactories()).WillByDefault(ReturnRef(filter_factory_)); + ON_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) + .WillByDefault(Invoke([](Network::Connection& connection, + const std::vector& filter_factories) { + EXPECT_EQ(1u, filter_factories.size()); + Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); + return true; + })); + + quic_listener_ = std::make_unique( + *dispatcher_, connection_handler_, ENVOY_LOGGER(), listener_config_, quic_config_); + simulated_time_system_.sleep(std::chrono::milliseconds(100)); + } + + void TearDown() override { + quic_listener_->onListenerShutdown(); + // Trigger alarm to fire before listener destruction. + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + +protected: + Network::Address::IpVersion version_; + Event::SimulatedTimeSystemHelper simulated_time_system_; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + Network::SocketPtr listen_socket_; + Network::SocketPtr client_socket_; + std::shared_ptr read_filter_; + Network::MockConnectionCallbacks network_connection_callbacks_; + std::vector filter_factory_; + Network::MockFilterChain filter_chain_; + Network::MockFilterChainManager filter_chain_manager_; + NiceMock listener_config_; + quic::QuicConfig quic_config_; + Server::ConnectionHandlerImpl connection_handler_; + std::unique_ptr quic_listener_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, ActiveQuicListenerTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(ActiveQuicListenerTest, ReceiveFullQuicCHLO) { + quic::QuicConnectionId connection_id = quic::test::TestConnectionId(1); + EnvoyQuicClock clock(*dispatcher_); + quic::CryptoHandshakeMessage chlo = quic::test::crypto_test_utils::GenerateDefaultInchoateCHLO( + &clock, quic::AllSupportedVersions()[0].transport_version, + &ActiveQuicListenerPeer::crypto_config(*quic_listener_)); + chlo.SetVector(quic::kCOPT, quic::QuicTagVector{quic::kREJ}); + quic::CryptoHandshakeMessage full_chlo; + quic::QuicReferenceCountedPointer signed_config( + new quic::QuicSignedServerConfig); + quic::QuicCompressedCertsCache cache( + quic::QuicCompressedCertsCache::kQuicCompressedCertsCacheSize); + quic::test::crypto_test_utils::GenerateFullCHLO( + chlo, &ActiveQuicListenerPeer::crypto_config(*quic_listener_), + envoyAddressInstanceToQuicSocketAddress(listen_socket_->localAddress()), + envoyAddressInstanceToQuicSocketAddress(client_socket_->localAddress()), + quic::AllSupportedVersions()[0].transport_version, &clock, signed_config, &cache, &full_chlo); + // Overwrite version label to highest current supported version. + full_chlo.SetVersion(quic::kVER, quic::CurrentSupportedVersions()[0]); + quic::QuicConfig quic_config; + quic_config.ToHandshakeMessage(&full_chlo, quic::CurrentSupportedVersions()[0].transport_version); + + std::string packet_content(full_chlo.GetSerialized().AsStringPiece()); + auto encrypted_packet = + std::unique_ptr(quic::test::ConstructEncryptedPacket( + connection_id, quic::EmptyQuicConnectionId(), /*version_flag=*/true, /*reset_flag*/ false, + /*packet_number=*/1, packet_content)); + + Buffer::RawSlice first_slice{reinterpret_cast(const_cast(encrypted_packet->data())), + encrypted_packet->length()}; + // Send a full CHLO to finish 0-RTT handshake. + auto send_rc = + client_socket_->ioHandle().sendto(first_slice, /*flags=*/0, *listen_socket_->localAddress()); + ASSERT_EQ(encrypted_packet->length(), send_rc.rc_); + + EXPECT_CALL(listener_config_, filterChainManager()); + EXPECT_CALL(filter_chain_manager_, findFilterChain(_)); + EXPECT_CALL(filter_chain_, networkFilterFactories()); + EXPECT_CALL(listener_config_, filterChainFactory()); + EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)); + EXPECT_CALL(*read_filter_, onNewConnection()) + // Stop iteration to avoid calling getRead/WriteBuffer(). + .WillOnce(Invoke([this]() { + dispatcher_->exit(); + return Network::FilterStatus::StopIteration; + })); + + dispatcher_->run(Event::Dispatcher::RunType::Block); + + Buffer::InstancePtr result_buffer(new Buffer::OwnedImpl()); + const uint64_t bytes_to_read = 11; + uint64_t bytes_read = 0; + int retry = 0; + + do { + Api::IoCallUint64Result result = + result_buffer->read(client_socket_->ioHandle(), bytes_to_read - bytes_read); + + if (result.ok()) { + bytes_read += result.rc_; + } else if (retry == 10 || result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) { + break; + } + + if (bytes_read == bytes_to_read) { + break; + } + + retry++; + ::usleep(10000); + } while (true); + // TearDown() will close the connection. + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc new file mode 100644 index 0000000000..60cd220f69 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_dispatcher_test.cc @@ -0,0 +1,263 @@ +#include + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_dispatcher.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/quic_test_utils.h" +#include "quiche/quic/platform/api/quic_text_utils.h" +#pragma GCC diagnostic pop + +#include + +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "common/network/listen_socket_impl.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/environment.h" +#include "test/mocks/network/mocks.h" +#include "test/test_common/utility.h" +#include "test/test_common/network_utility.h" +#include "extensions/quic_listeners/quiche/platform/envoy_quic_clock.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/envoy_quic_dispatcher.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/transport_sockets/well_known_names.h" +#include "server/configuration_impl.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Invoke; +using testing::Return; +using testing::ReturnRef; + +namespace quic { +namespace test { +class QuicDispatcherPeer { +public: + static quic::QuicTimeWaitListManager* time_wait_list_manager(QuicDispatcher* dispatcher) { + return dispatcher->time_wait_list_manager_.get(); + } +}; + +} // namespace test +} // namespace quic + +namespace Envoy { +namespace Quic { + +class EnvoyQuicDispatcherTest : public testing::TestWithParam, + protected Logger::Loggable { +public: + EnvoyQuicDispatcherTest() + : version_(GetParam()), api_(Api::createApiForTest(time_system_)), + dispatcher_(api_->allocateDispatcher()), connection_helper_(*dispatcher_), + crypto_config_(quic::QuicCryptoServerConfig::TESTING, quic::QuicRandom::GetInstance(), + std::make_unique(), + quic::KeyExchangeSource::Default()), + version_manager_(quic::CurrentSupportedVersions()), + listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), + POOL_GAUGE(listener_config_.listenerScope()), + POOL_HISTOGRAM(listener_config_.listenerScope()))}), + connection_handler_(ENVOY_LOGGER(), *dispatcher_), + envoy_quic_dispatcher_( + &crypto_config_, quic_config_, &version_manager_, + std::make_unique(*dispatcher_), + std::make_unique(*dispatcher_, *connection_helper_.GetClock()), + quic::kQuicDefaultConnectionIdLength, connection_handler_, listener_config_, + listener_stats_, *dispatcher_) { + auto writer = new testing::NiceMock(); + envoy_quic_dispatcher_.InitializeWithWriter(writer); + EXPECT_CALL(*writer, WritePacket(_, _, _, _, _)) + .WillRepeatedly(Return(quic::WriteResult(quic::WRITE_STATUS_OK, 0))); + } + + void SetUp() override { + listen_socket_ = std::make_unique>>( + Network::Test::getCanonicalLoopbackAddress(version_), nullptr, /*bind*/ true); + // Advance time a bit because QuicTime regards 0 as uninitialized timestamp. + time_system_.sleep(std::chrono::milliseconds(100)); + EXPECT_CALL(listener_config_, socket()).WillRepeatedly(ReturnRef(*listen_socket_)); + } + + void TearDown() override { + envoy_quic_dispatcher_.Shutdown(); + dispatcher_->run(Event::Dispatcher::RunType::NonBlock); + } + + std::unique_ptr + createFullChloPacket(const quic::QuicConnectionId& connection_id, + quic::QuicSocketAddress client_address) { + EnvoyQuicClock clock(*dispatcher_); + quic::CryptoHandshakeMessage chlo = quic::test::crypto_test_utils::GenerateDefaultInchoateCHLO( + &clock, quic::AllSupportedVersions()[0].transport_version, &crypto_config_); + chlo.SetVector(quic::kCOPT, quic::QuicTagVector{quic::kREJ}); + chlo.SetStringPiece(quic::kSNI, "www.abc.com"); + quic::CryptoHandshakeMessage full_chlo; + quic::QuicReferenceCountedPointer signed_config( + new quic::QuicSignedServerConfig); + quic::QuicCompressedCertsCache cache( + quic::QuicCompressedCertsCache::kQuicCompressedCertsCacheSize); + quic::test::crypto_test_utils::GenerateFullCHLO( + chlo, &crypto_config_, + envoyAddressInstanceToQuicSocketAddress(listen_socket_->localAddress()), client_address, + quic::AllSupportedVersions()[0].transport_version, &clock, signed_config, &cache, + &full_chlo); + // Overwrite version label to highest current supported version. + full_chlo.SetVersion(quic::kVER, quic::CurrentSupportedVersions()[0]); + quic::QuicConfig quic_config; + quic_config.ToHandshakeMessage(&full_chlo, + quic::CurrentSupportedVersions()[0].transport_version); + + std::string packet_content(full_chlo.GetSerialized().AsStringPiece()); + std::unique_ptr encrypted_packet( + quic::test::ConstructEncryptedPacket(connection_id, quic::EmptyQuicConnectionId(), + /*version_flag=*/true, /*reset_flag*/ false, + /*packet_number=*/1, packet_content)); + return std::unique_ptr( + quic::test::ConstructReceivedPacket(*encrypted_packet, clock.Now())); + } + +protected: + Network::Address::IpVersion version_; + Event::SimulatedTimeSystemHelper time_system_; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + Network::SocketPtr listen_socket_; + EnvoyQuicConnectionHelper connection_helper_; + quic::QuicCryptoServerConfig crypto_config_; + quic::QuicConfig quic_config_; + quic::QuicVersionManager version_manager_; + + testing::NiceMock listener_config_; + Server::ListenerStats listener_stats_; + Server::ConnectionHandlerImpl connection_handler_; + EnvoyQuicDispatcher envoy_quic_dispatcher_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, EnvoyQuicDispatcherTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +TEST_P(EnvoyQuicDispatcherTest, CreateNewConnectionUponCHLO) { + quic::SetVerbosityLogThreshold(2); + quic::QuicSocketAddress peer_addr(version_ == Network::Address::IpVersion::v4 + ? quic::QuicIpAddress::Loopback4() + : quic::QuicIpAddress::Loopback6(), + 54321); + Network::MockFilterChain filter_chain; + Network::MockFilterChainManager filter_chain_manager; + EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); + EXPECT_CALL(filter_chain_manager, findFilterChain(_)) + .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { + EXPECT_EQ(*listen_socket_->localAddress(), *socket.localAddress()); + EXPECT_EQ(Extensions::TransportSockets::TransportSocketNames::get().Quic, + socket.detectedTransportProtocol()); + EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(socket.remoteAddress())); + return &filter_chain; + })); + std::shared_ptr read_filter(new Network::MockReadFilter()); + Network::MockConnectionCallbacks network_connection_callbacks; + std::vector filter_factory( + {[&](Network::FilterManager& filter_manager) { + filter_manager.addReadFilter(read_filter); + read_filter->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks); + }}); + EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); + EXPECT_CALL(listener_config_, filterChainFactory()); + EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) + .WillOnce(Invoke([](Network::Connection& connection, + const std::vector& filter_factories) { + EXPECT_EQ(1u, filter_factories.size()); + Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); + return true; + })); + EXPECT_CALL(*read_filter, onNewConnection()) + // Stop iteration to avoid calling getRead/WriteBuffer(). + .WillOnce(Invoke([]() { return Network::FilterStatus::StopIteration; })); + + quic::QuicConnectionId connection_id = quic::test::TestConnectionId(1); + // Upon receiving a full CHLO. A new quic connection should be created and have its filter + // installed based on self and peer address. + std::unique_ptr received_packet = + createFullChloPacket(connection_id, peer_addr); + envoy_quic_dispatcher_.ProcessPacket( + envoyAddressInstanceToQuicSocketAddress(listen_socket_->localAddress()), peer_addr, + *received_packet); + EXPECT_EQ(1u, envoy_quic_dispatcher_.session_map().size()); + EXPECT_TRUE( + envoy_quic_dispatcher_.session_map().find(connection_id)->second->IsEncryptionEstablished()); + EXPECT_EQ(1u, connection_handler_.numConnections()); + EXPECT_EQ("www.abc.com", read_filter->callbacks_->connection().requestedServerName()); + EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress( + read_filter->callbacks_->connection().remoteAddress())); + EXPECT_EQ(*listen_socket_->localAddress(), *read_filter->callbacks_->connection().localAddress()); + EXPECT_CALL(network_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); + // Shutdown() to close the connection. + envoy_quic_dispatcher_.Shutdown(); +} + +TEST_P(EnvoyQuicDispatcherTest, CloseConnectionDueToMissingFilterChain) { + quic::QuicSocketAddress peer_addr(version_ == Network::Address::IpVersion::v4 + ? quic::QuicIpAddress::Loopback4() + : quic::QuicIpAddress::Loopback6(), + 54321); + Network::MockFilterChainManager filter_chain_manager; + EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); + EXPECT_CALL(filter_chain_manager, findFilterChain(_)) + .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { + EXPECT_EQ(*listen_socket_->localAddress(), *socket.localAddress()); + EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(socket.remoteAddress())); + return nullptr; + })); + quic::QuicConnectionId connection_id = quic::test::TestConnectionId(1); + std::unique_ptr received_packet = + createFullChloPacket(connection_id, peer_addr); + envoy_quic_dispatcher_.ProcessPacket( + envoyAddressInstanceToQuicSocketAddress(listen_socket_->localAddress()), peer_addr, + *received_packet); + EXPECT_EQ(0u, envoy_quic_dispatcher_.session_map().size()); + EXPECT_EQ(0u, connection_handler_.numConnections()); + EXPECT_TRUE(quic::test::QuicDispatcherPeer::time_wait_list_manager(&envoy_quic_dispatcher_) + ->IsConnectionIdInTimeWait(connection_id)); + EXPECT_EQ(1u, listener_stats_.no_filter_chain_match_.value()); +} + +TEST_P(EnvoyQuicDispatcherTest, CloseConnectionDueToEmptyFilterChain) { + quic::QuicSocketAddress peer_addr(version_ == Network::Address::IpVersion::v4 + ? quic::QuicIpAddress::Loopback4() + : quic::QuicIpAddress::Loopback6(), + 54321); + Network::MockFilterChain filter_chain; + Network::MockFilterChainManager filter_chain_manager; + EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); + EXPECT_CALL(filter_chain_manager, findFilterChain(_)) + .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { + EXPECT_EQ(*listen_socket_->localAddress(), *socket.localAddress()); + EXPECT_EQ(peer_addr, envoyAddressInstanceToQuicSocketAddress(socket.remoteAddress())); + return &filter_chain; + })); + // Empty filter_factory should cause connection close. + std::vector filter_factory; + EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); + + quic::QuicConnectionId connection_id = quic::test::TestConnectionId(1); + std::unique_ptr received_packet = + createFullChloPacket(connection_id, peer_addr); + envoy_quic_dispatcher_.ProcessPacket( + envoyAddressInstanceToQuicSocketAddress(listen_socket_->localAddress()), peer_addr, + *received_packet); + EXPECT_EQ(0u, envoy_quic_dispatcher_.session_map().size()); + EXPECT_EQ(0u, connection_handler_.numConnections()); + EXPECT_TRUE(quic::test::QuicDispatcherPeer::time_wait_list_manager(&envoy_quic_dispatcher_) + ->IsConnectionIdInTimeWait(connection_id)); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc index e548fe72ce..4737a532f5 100644 --- a/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc +++ b/test/extensions/quic_listeners/quiche/envoy_quic_proof_source_test.cc @@ -44,7 +44,7 @@ class EnvoyQuicFakeProofSourceTest : public ::testing::Test { quic::QuicTransportVersion version_{quic::QUIC_VERSION_UNSUPPORTED}; quic::QuicStringPiece chlo_hash_{""}; std::string server_config_{"Server Config"}; - std::vector expected_certs_{absl::StrCat("Fake cert from ", hostname_)}; + std::vector expected_certs_{"Fake cert"}; std::string expected_signature_{absl::StrCat("Fake signature for { ", server_config_, " }")}; EnvoyQuicFakeProofSource proof_source_; EnvoyQuicFakeProofVerifier proof_verifier_; diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc new file mode 100644 index 0000000000..8b5f127cbc --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_session_test.cc @@ -0,0 +1,401 @@ +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/test_tools/crypto_test_utils.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +#pragma GCC diagnostic pop + +#include + +#include "extensions/quic_listeners/quiche/envoy_quic_server_session.h" +#include "extensions/quic_listeners/quiche/codec_impl.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" +#include "extensions/quic_listeners/quiche/envoy_quic_fake_proof_source.h" +#include "extensions/transport_sockets/well_known_names.h" + +#include "envoy/stats/stats_macros.h" +#include "common/event/libevent_scheduler.h" +#include "server/configuration_impl.h" +#include "test/mocks/event/mocks.h" +#include "test/mocks/http/stream_decoder.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/mocks/stats/mocks.h" +#include "test/test_common/global.h" +#include "test/test_common/logging.h" +#include "test/test_common/simulated_time_system.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Invoke; +using testing::Return; +using testing::ReturnRef; + +#include + +namespace Envoy { +namespace Quic { + +class TestEnvoyQuicConnection : public EnvoyQuicConnection { +public: + TestEnvoyQuicConnection(quic::QuicConnectionHelperInterface& helper, + quic::QuicAlarmFactory& alarm_factory, quic::QuicPacketWriter& writer, + const quic::ParsedQuicVersionVector& supported_versions, + Network::ListenerConfig& listener_config, Server::ListenerStats& stats) + : EnvoyQuicConnection(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Loopback4(), 12345), + helper, alarm_factory, writer, /*owns_writer=*/false, + quic::Perspective::IS_SERVER, supported_versions, listener_config, + stats) {} + + Network::Connection::ConnectionStats& connectionStats() const { + return EnvoyQuicConnection::connectionStats(); + } + + MOCK_METHOD2(SendConnectionClosePacket, void(quic::QuicErrorCode, const std::string&)); + MOCK_METHOD1(SendControlFrame, bool(const quic::QuicFrame& frame)); +}; + +class EnvoyQuicServerSessionTest : public testing::TestWithParam { +public: + EnvoyQuicServerSessionTest() + : api_(Api::createApiForTest(time_system_)), dispatcher_(api_->allocateDispatcher()), + connection_helper_(*dispatcher_), + alarm_factory_(*dispatcher_, *connection_helper_.GetClock()), quic_version_([]() { + SetQuicReloadableFlag(quic_enable_version_99, GetParam()); + return quic::ParsedVersionOfIndex(quic::CurrentSupportedVersions(), 0); + }()), + listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), + POOL_GAUGE(listener_config_.listenerScope()), + POOL_HISTOGRAM(listener_config_.listenerScope()))}), + quic_connection_(new TestEnvoyQuicConnection(connection_helper_, alarm_factory_, writer_, + quic_version_, listener_config_, + listener_stats_)), + crypto_config_(quic::QuicCryptoServerConfig::TESTING, quic::QuicRandom::GetInstance(), + std::make_unique(), + quic::KeyExchangeSource::Default()), + envoy_quic_session_(quic_config_, quic_version_, + std::unique_ptr(quic_connection_), + /*visitor=*/nullptr, &crypto_stream_helper_, &crypto_config_, + &compressed_certs_cache_, *dispatcher_), + read_filter_(new Network::MockReadFilter()) { + EXPECT_EQ(time_system_.systemTime(), envoy_quic_session_.streamInfo().startTime()); + EXPECT_EQ(EMPTY_STRING, envoy_quic_session_.nextProtocol()); + time_system_.sleep(std::chrono::milliseconds(1)); + ON_CALL(writer_, WritePacket(_, _, _, _, _)) + .WillByDefault(testing::Return(quic::WriteResult(quic::WRITE_STATUS_OK, 1))); + ON_CALL(crypto_stream_helper_, CanAcceptClientHello(_, _, _, _, _)).WillByDefault(Return(true)); + } + + void SetUp() override { envoy_quic_session_.Initialize(); } + + bool installReadFilter() { + // Setup read filter. + envoy_quic_session_.addReadFilter(read_filter_); + EXPECT_EQ(Http::Protocol::Http2, + read_filter_->callbacks_->connection().streamInfo().protocol().value()); + EXPECT_EQ(envoy_quic_session_.id(), read_filter_->callbacks_->connection().id()); + EXPECT_EQ(&envoy_quic_session_, &read_filter_->callbacks_->connection()); + read_filter_->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks_); + read_filter_->callbacks_->connection().setConnectionStats( + {read_total_, read_current_, write_total_, write_current_, nullptr, nullptr}); + EXPECT_EQ(&read_total_, &quic_connection_->connectionStats().read_total_); + EXPECT_CALL(*read_filter_, onNewConnection()).WillOnce(Invoke([this]() { + // Create ServerConnection instance and setup callbacks for it. + http_connection_ = std::make_unique(envoy_quic_session_, + http_connection_callbacks_); + EXPECT_EQ(Http::Protocol::Http2, http_connection_->protocol()); + // Stop iteration to avoid calling getRead/WriteBuffer(). + return Network::FilterStatus::StopIteration; + })); + return envoy_quic_session_.initializeReadFilters(); + } + + void TearDown() override { + if (quic_connection_->connected()) { + EXPECT_CALL(*quic_connection_, + SendConnectionClosePacket(quic::QUIC_NO_ERROR, "Closed by application")); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + envoy_quic_session_.close(Network::ConnectionCloseType::NoFlush); + } + } + +protected: + Event::SimulatedTimeSystemHelper time_system_; + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + EnvoyQuicConnectionHelper connection_helper_; + EnvoyQuicAlarmFactory alarm_factory_; + quic::ParsedQuicVersionVector quic_version_; + testing::NiceMock writer_; + testing::NiceMock listener_config_; + Server::ListenerStats listener_stats_; + TestEnvoyQuicConnection* quic_connection_; + quic::QuicConfig quic_config_; + quic::QuicCryptoServerConfig crypto_config_; + testing::NiceMock crypto_stream_helper_; + EnvoyQuicServerSession envoy_quic_session_; + quic::QuicCompressedCertsCache compressed_certs_cache_{100}; + std::shared_ptr read_filter_; + Network::MockConnectionCallbacks network_connection_callbacks_; + Http::MockServerConnectionCallbacks http_connection_callbacks_; + testing::StrictMock read_total_; + testing::StrictMock read_current_; + testing::StrictMock write_total_; + testing::StrictMock write_current_; + Http::ServerConnectionPtr http_connection_; +}; + +INSTANTIATE_TEST_SUITE_P(EnvoyQuicServerSessionTests, EnvoyQuicServerSessionTest, + testing::ValuesIn({true, false})); + +TEST_P(EnvoyQuicServerSessionTest, NewStream) { + installReadFilter(); + + Http::MockStreamDecoder request_decoder; + EXPECT_CALL(http_connection_callbacks_, newStream(_, false)) + .WillOnce(testing::ReturnRef(request_decoder)); + quic::QuicStreamId stream_id = + quic_version_[0].transport_version == quic::QUIC_VERSION_99 ? 4u : 5u; + auto stream = + reinterpret_cast(envoy_quic_session_.GetOrCreateStream(stream_id)); + // Receive a GET request on created stream. + quic::QuicHeaderList headers; + headers.OnHeaderBlockStart(); + std::string host("www.abc.com"); + headers.OnHeader(":authority", host); + headers.OnHeader(":method", "GET"); + headers.OnHeader(":path", "/"); + headers.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + // Request headers should be propagated to decoder. + EXPECT_CALL(request_decoder, decodeHeaders_(_, /*end_stream=*/true)) + .WillOnce(Invoke([&host](const Http::HeaderMapPtr& decoded_headers, bool) { + EXPECT_EQ(host, decoded_headers->Host()->value().getStringView()); + EXPECT_EQ("/", decoded_headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + decoded_headers->Method()->value().getStringView()); + })); + EXPECT_CALL(request_decoder, decodeData(_, true)) + .Times(testing::AtMost(1)) + .WillOnce(Invoke([](Buffer::Instance& buffer, bool) { EXPECT_EQ(0, buffer.length()); })); + stream->OnStreamHeaderList(/*fin=*/true, headers.uncompressed_header_bytes(), headers); +} + +TEST_P(EnvoyQuicServerSessionTest, InvalidIncomingStreamId) { + quic::SetVerbosityLogThreshold(1); + installReadFilter(); + Http::MockStreamDecoder request_decoder; + Http::MockStreamCallbacks stream_callbacks; + // IETF stream 5 and G-Quic stream 2 are server initiated. + quic::QuicStreamId stream_id = + quic_version_[0].transport_version == quic::QUIC_VERSION_99 ? 5u : 2u; + std::string data("aaaa"); + quic::QuicStreamFrame stream_frame(stream_id, false, 0, data); + EXPECT_CALL(http_connection_callbacks_, newStream(_, false)).Times(0); + EXPECT_CALL(*quic_connection_, SendConnectionClosePacket(quic::QUIC_INVALID_STREAM_ID, + "Data for nonexistent stream")); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + + envoy_quic_session_.OnStreamFrame(stream_frame); +} + +TEST_P(EnvoyQuicServerSessionTest, NoNewStreamForInvalidIncomingStream) { + quic::SetVerbosityLogThreshold(1); + installReadFilter(); + Http::MockStreamDecoder request_decoder; + Http::MockStreamCallbacks stream_callbacks; + // IETF stream 5 and G-Quic stream 2 are server initiated. + quic::QuicStreamId stream_id = + quic_version_[0].transport_version == quic::QUIC_VERSION_99 ? 5u : 2u; + EXPECT_CALL(http_connection_callbacks_, newStream(_, false)).Times(0); + EXPECT_CALL(*quic_connection_, SendConnectionClosePacket(quic::QUIC_INVALID_STREAM_ID, + "Data for nonexistent stream")); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + + // Stream creation on closed connection should fail. + EXPECT_EQ(nullptr, envoy_quic_session_.GetOrCreateStream(stream_id)); +} + +TEST_P(EnvoyQuicServerSessionTest, OnResetFrame) { + installReadFilter(); + Http::MockStreamDecoder request_decoder; + Http::MockStreamCallbacks stream_callbacks; + EXPECT_CALL(http_connection_callbacks_, newStream(_, false)) + .WillRepeatedly(Invoke([&request_decoder, &stream_callbacks](Http::StreamEncoder& encoder, + bool) -> Http::StreamDecoder& { + encoder.getStream().addCallbacks(stream_callbacks); + return request_decoder; + })); + + // G-QUIC or IETF bi-directional stream. + quic::QuicStreamId stream_id = + quic_version_[0].transport_version == quic::QUIC_VERSION_99 ? 4u : 5u; + + quic::QuicStream* stream1 = envoy_quic_session_.GetOrCreateStream(stream_id); + quic::QuicRstStreamFrame rst1(/*control_frame_id=*/1u, stream1->id(), + quic::QUIC_ERROR_PROCESSING_STREAM, /*bytes_written=*/0u); + EXPECT_CALL(stream_callbacks, onResetStream(Http::StreamResetReason::RemoteReset, _)); + if (quic_version_[0].transport_version < quic::QUIC_VERSION_99) { + EXPECT_CALL(*quic_connection_, SendControlFrame(_)) + .WillOnce(Invoke([stream_id](const quic::QuicFrame& frame) { + EXPECT_EQ(stream_id, frame.rst_stream_frame->stream_id); + EXPECT_EQ(quic::QUIC_RST_ACKNOWLEDGEMENT, frame.rst_stream_frame->error_code); + return false; + })); + } + stream1->OnStreamReset(rst1); + + // G-QUIC bi-directional stream or IETF read uni-directional stream. + quic::QuicStream* stream2 = envoy_quic_session_.GetOrCreateStream(stream_id + 4u); + quic::QuicRstStreamFrame rst2(/*control_frame_id=*/1u, stream2->id(), quic::QUIC_REFUSED_STREAM, + /*bytes_written=*/0u); + EXPECT_CALL(stream_callbacks, + onResetStream(Http::StreamResetReason::RemoteRefusedStreamReset, _)); + stream2->OnStreamReset(rst2); +} + +TEST_P(EnvoyQuicServerSessionTest, ConnectionClose) { + installReadFilter(); + + std::string error_details("dummy details"); + quic::QuicErrorCode error(quic::QUIC_INVALID_FRAME_DATA); + quic::QuicConnectionCloseFrame frame(error, error_details); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::RemoteClose)); + quic_connection_->OnConnectionCloseFrame(frame); + EXPECT_EQ(absl::StrCat(quic::QuicErrorCodeToString(error), " with details: ", error_details), + envoy_quic_session_.transportFailureReason()); + EXPECT_EQ(Network::Connection::State::Closed, envoy_quic_session_.state()); +} + +TEST_P(EnvoyQuicServerSessionTest, ConnectionCloseWithActiveStream) { + installReadFilter(); + + Http::MockStreamDecoder request_decoder; + Http::MockStreamCallbacks stream_callbacks; + EXPECT_CALL(http_connection_callbacks_, newStream(_, false)) + .WillOnce(Invoke([&request_decoder, &stream_callbacks](Http::StreamEncoder& encoder, + bool) -> Http::StreamDecoder& { + encoder.getStream().addCallbacks(stream_callbacks); + return request_decoder; + })); + quic::QuicStreamId stream_id = + quic_version_[0].transport_version == quic::QUIC_VERSION_99 ? 4u : 5u; + quic::QuicStream* stream = envoy_quic_session_.GetOrCreateStream(stream_id); + EXPECT_CALL(*quic_connection_, + SendConnectionClosePacket(quic::QUIC_NO_ERROR, "Closed by application")); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + EXPECT_CALL(stream_callbacks, onResetStream(Http::StreamResetReason::ConnectionTermination, _)); + envoy_quic_session_.close(Network::ConnectionCloseType::NoFlush); + EXPECT_EQ(Network::Connection::State::Closed, envoy_quic_session_.state()); + EXPECT_TRUE(stream->write_side_closed() && stream->reading_stopped()); +} + +TEST_P(EnvoyQuicServerSessionTest, FlushCloseNotSupported) { + installReadFilter(); + + EXPECT_CALL(*quic_connection_, + SendConnectionClosePacket(quic::QUIC_NO_ERROR, "Closed by application")); + EXPECT_CALL(network_connection_callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + EXPECT_LOG_CONTAINS("error", "Flush write is not implemented for QUIC.", + envoy_quic_session_.close(Network::ConnectionCloseType::FlushWrite)); +} + +TEST_P(EnvoyQuicServerSessionTest, ShutdownNotice) { + installReadFilter(); + // Not verifying dummy implementation, just to have coverage. + EXPECT_DEBUG_DEATH(envoy_quic_session_.enableHalfClose(true), ""); + envoy_quic_session_.setBufferLimits(1024 * 1024); + EXPECT_EQ(nullptr, envoy_quic_session_.ssl()); + EXPECT_FALSE(envoy_quic_session_.aboveHighWatermark()); + EXPECT_DEBUG_DEATH(envoy_quic_session_.setDelayedCloseTimeout(std::chrono::milliseconds(1)), ""); + http_connection_->shutdownNotice(); +} + +TEST_P(EnvoyQuicServerSessionTest, GoAway) { + installReadFilter(); + if (quic_version_[0].transport_version < quic::QUIC_VERSION_99) { + EXPECT_CALL(*quic_connection_, SendControlFrame(_)); + } + http_connection_->goAway(); +} + +TEST_P(EnvoyQuicServerSessionTest, InitializeFilterChain) { + // Generate a CHLO packet. + quic::CryptoHandshakeMessage chlo = quic::test::crypto_test_utils::GenerateDefaultInchoateCHLO( + connection_helper_.GetClock(), quic::CurrentSupportedVersions()[0].transport_version, + &crypto_config_); + chlo.SetVector(quic::kCOPT, quic::QuicTagVector{quic::kREJ}); + std::string packet_content(chlo.GetSerialized().AsStringPiece()); + auto encrypted_packet = + std::unique_ptr(quic::test::ConstructEncryptedPacket( + quic_connection_->connection_id(), quic::EmptyQuicConnectionId(), /*version_flag=*/true, + /*reset_flag*/ false, /*packet_number=*/1, packet_content)); + + quic::QuicSocketAddress self_address( + envoyAddressInstanceToQuicSocketAddress(listener_config_.socket().localAddress())); + auto packet = std::unique_ptr( + quic::test::ConstructReceivedPacket(*encrypted_packet, connection_helper_.GetClock()->Now())); + + // Receiving above packet should trigger filter chain retrival. + Network::MockFilterChainManager filter_chain_manager; + EXPECT_CALL(listener_config_, filterChainManager()).WillOnce(ReturnRef(filter_chain_manager)); + Network::MockFilterChain filter_chain; + EXPECT_CALL(filter_chain_manager, findFilterChain(_)) + .WillOnce(Invoke([&](const Network::ConnectionSocket& socket) { + EXPECT_EQ(*quicAddressToEnvoyAddressInstance(quic_connection_->peer_address()), + *socket.remoteAddress()); + EXPECT_EQ(*quicAddressToEnvoyAddressInstance(self_address), *socket.localAddress()); + EXPECT_EQ(listener_config_.socket().ioHandle().fd(), socket.ioHandle().fd()); + EXPECT_EQ(Extensions::TransportSockets::TransportSocketNames::get().Quic, + socket.detectedTransportProtocol()); + return &filter_chain; + })); + std::vector filter_factory{[this]( + Network::FilterManager& filter_manager) { + filter_manager.addReadFilter(read_filter_); + read_filter_->callbacks_->connection().addConnectionCallbacks(network_connection_callbacks_); + }}; + EXPECT_CALL(filter_chain, networkFilterFactories()).WillOnce(ReturnRef(filter_factory)); + EXPECT_CALL(*read_filter_, onNewConnection()) + // Stop iteration to avoid calling getRead/WriteBuffer(). + .WillOnce(Return(Network::FilterStatus::StopIteration)); + EXPECT_CALL(listener_config_.filter_chain_factory_, createNetworkFilterChain(_, _)) + .WillOnce(Invoke([](Network::Connection& connection, + const std::vector& filter_factories) { + EXPECT_EQ(1u, filter_factories.size()); + Server::Configuration::FilterChainUtility::buildFilterChain(connection, filter_factories); + return true; + })); + // A reject should be sent because of the inchoate CHLO. + EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)) + .WillOnce(testing::Return(quic::WriteResult(quic::WRITE_STATUS_OK, 1))); + quic_connection_->ProcessUdpPacket(self_address, quic_connection_->peer_address(), *packet); + EXPECT_TRUE(quic_connection_->connected()); + EXPECT_EQ(nullptr, envoy_quic_session_.socketOptions()); + EXPECT_FALSE(envoy_quic_session_.IsEncryptionEstablished()); + EXPECT_TRUE(quic_connection_->connectionSocket()->ioHandle().isOpen()); + EXPECT_TRUE(quic_connection_->connectionSocket()->ioHandle().close().ok()); + EXPECT_FALSE(quic_connection_->connectionSocket()->ioHandle().isOpen()); +} + +TEST_P(EnvoyQuicServerSessionTest, NetworkConnectionInterface) { + installReadFilter(); + EXPECT_EQ(dispatcher_.get(), &envoy_quic_session_.dispatcher()); + EXPECT_TRUE(envoy_quic_session_.readEnabled()); + EXPECT_FALSE(envoy_quic_session_.localAddressRestored()); + EXPECT_DEBUG_DEATH(envoy_quic_session_.unixSocketPeerCredentials(), + "Unix domain socket is not supported."); + EXPECT_DEBUG_DEATH(envoy_quic_session_.readDisable(true), + "Quic connection should be able to read through out its life time."); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc new file mode 100644 index 0000000000..31df75c0a2 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_server_stream_test.cc @@ -0,0 +1,253 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_server_stream.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/core/quic_versions.h" +#include "quiche/quic/core/http/quic_server_session_base.h" +#include "quiche/quic/test_tools/quic_test_utils.h" + +#pragma GCC diagnostic pop + +#include + +#include "common/event/libevent_scheduler.h" +#include "common/http/headers.h" +#include "test/test_common/utility.h" +#include "extensions/quic_listeners/quiche/envoy_quic_alarm_factory.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection.h" +#include "extensions/quic_listeners/quiche/envoy_quic_connection_helper.h" +#include "test/mocks/http/stream_decoder.h" +#include "test/mocks/network/mocks.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Invoke; + +namespace Envoy { +namespace Quic { + +class MockQuicServerSession : public quic::QuicServerSessionBase { +public: + MockQuicServerSession(const quic::QuicConfig& config, + const quic::ParsedQuicVersionVector& supported_versions, + quic::QuicConnection* connection, quic::QuicSession::Visitor* visitor, + quic::QuicCryptoServerStream::Helper* helper, + const quic::QuicCryptoServerConfig* crypto_config, + quic::QuicCompressedCertsCache* compressed_certs_cache) + : quic::QuicServerSessionBase(config, supported_versions, connection, visitor, helper, + crypto_config, compressed_certs_cache) {} + + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::QuicStreamId id)); + MOCK_METHOD1(CreateIncomingStream, quic::QuicSpdyStream*(quic::PendingStream* pending)); + MOCK_METHOD0(CreateOutgoingBidirectionalStream, quic::QuicSpdyStream*()); + MOCK_METHOD0(CreateOutgoingUnidirectionalStream, quic::QuicSpdyStream*()); + MOCK_METHOD1(ShouldCreateIncomingStream, bool(quic::QuicStreamId id)); + MOCK_METHOD0(ShouldCreateOutgoingBidirectionalStream, bool()); + MOCK_METHOD0(ShouldCreateOutgoingUnidirectionalStream, bool()); + MOCK_METHOD2(CreateQuicCryptoServerStream, + quic::QuicCryptoServerStream*(const quic::QuicCryptoServerConfig*, + quic::QuicCompressedCertsCache*)); +}; + +class EnvoyQuicServerStreamTest : public testing::TestWithParam { +public: + EnvoyQuicServerStreamTest() + : api_(Api::createApiForTest()), dispatcher_(api_->allocateDispatcher()), + connection_helper_(*dispatcher_), + alarm_factory_(*dispatcher_, *connection_helper_.GetClock()), quic_version_([]() { + SetQuicReloadableFlag(quic_enable_version_99, GetParam()); + return quic::CurrentSupportedVersions()[0]; + }()), + listener_stats_({ALL_LISTENER_STATS(POOL_COUNTER(listener_config_.listenerScope()), + POOL_GAUGE(listener_config_.listenerScope()), + POOL_HISTOGRAM(listener_config_.listenerScope()))}), + quic_connection_(quic::test::TestConnectionId(), + quic::QuicSocketAddress(quic::QuicIpAddress::Any6(), 12345), + connection_helper_, alarm_factory_, writer_, + /*owns_writer=*/false, quic::Perspective::IS_SERVER, {quic_version_}, + listener_config_, listener_stats_), + quic_session_(quic_config_, {quic_version_}, &quic_connection_, /*visitor=*/nullptr, + /*helper=*/nullptr, /*crypto_config=*/nullptr, + /*compressed_certs_cache=*/nullptr), + stream_id_(quic_version_.transport_version == quic::QUIC_VERSION_99 ? 4u : 5u), + quic_stream_(stream_id_, &quic_session_, quic::BIDIRECTIONAL) { + quic::SetVerbosityLogThreshold(3); + + quic_stream_.setDecoder(stream_decoder_); + } + + void SetUp() override { + headers_.OnHeaderBlockStart(); + headers_.OnHeader(":authority", host_); + headers_.OnHeader(":method", "GET"); + headers_.OnHeader(":path", "/"); + headers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + + trailers_.OnHeaderBlockStart(); + trailers_.OnHeader("key1", "value1"); + if (quic_version_.transport_version != quic::QUIC_VERSION_99) { + // ":final-offset" is required and stripped off by quic. + trailers_.OnHeader(":final-offset", absl::StrCat("", request_body_.length())); + } + trailers_.OnHeaderBlockEnd(/*uncompressed_header_bytes=*/0, /*compressed_header_bytes=*/0); + } + +protected: + Api::ApiPtr api_; + Event::DispatcherPtr dispatcher_; + EnvoyQuicConnectionHelper connection_helper_; + EnvoyQuicAlarmFactory alarm_factory_; + testing::NiceMock writer_; + quic::ParsedQuicVersion quic_version_; + quic::QuicConfig quic_config_; + testing::NiceMock listener_config_; + Server::ListenerStats listener_stats_; + EnvoyQuicConnection quic_connection_; + MockQuicServerSession quic_session_; + quic::QuicStreamId stream_id_; + EnvoyQuicServerStream quic_stream_; + Http::MockStreamDecoder stream_decoder_; + quic::QuicHeaderList headers_; + quic::QuicHeaderList trailers_; + std::string host_{"www.abc.com"}; + std::string request_body_{"Hello world"}; +}; + +INSTANTIATE_TEST_SUITE_P(EnvoyQuicServerStreamTests, EnvoyQuicServerStreamTest, + testing::ValuesIn({true, false})); + +TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersAndBody) { + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { + EXPECT_EQ(host_, headers->Host()->value().getStringView()); + EXPECT_EQ("/", headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + headers->Method()->value().getStringView()); + })); + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + quic_stream_.OnHeadersDecoded(headers_); + } else { + quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); + } + EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(request_body_, buffer.toString()); + EXPECT_TRUE(finished_reading); + })); + std::string data = request_body_; + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, request_body_); + } + quic::QuicStreamFrame frame(stream_id_, true, 0, data); + quic_stream_.OnStreamFrame(frame); +} + +TEST_P(EnvoyQuicServerStreamTest, DecodeHeadersBodyAndTrailers) { + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { + EXPECT_EQ(host_, headers->Host()->value().getStringView()); + EXPECT_EQ("/", headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + headers->Method()->value().getStringView()); + })); + quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); + EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + + std::string data = request_body_; + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, request_body_); + } + quic::QuicStreamFrame frame(stream_id_, false, 0, data); + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .Times(testing::AtMost(2)) + .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(request_body_, buffer.toString()); + EXPECT_FALSE(finished_reading); + })) + // Depends on QUIC version, there may be an empty STREAM_FRAME with FIN. But + // since there is trailers, finished_reading should always be false. + .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_FALSE(finished_reading); + EXPECT_EQ(0, buffer.length()); + })); + quic_stream_.OnStreamFrame(frame); + + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) + .WillOnce(Invoke([](const Http::HeaderMapPtr& headers) { + Http::LowerCaseString key1("key1"); + Http::LowerCaseString key2(":final-offset"); + EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); + EXPECT_EQ(nullptr, headers->get(key2)); + })); + quic_stream_.OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); +} + +TEST_P(EnvoyQuicServerStreamTest, OutOfOrderTrailers) { + if (quic::VersionUsesQpack(quic_version_.transport_version)) { + return; + } + EXPECT_CALL(stream_decoder_, decodeHeaders_(_, /*end_stream=*/false)) + .WillOnce(Invoke([this](const Http::HeaderMapPtr& headers, bool) { + EXPECT_EQ(host_, headers->Host()->value().getStringView()); + EXPECT_EQ("/", headers->Path()->value().getStringView()); + EXPECT_EQ(Http::Headers::get().MethodValues.Get, + headers->Method()->value().getStringView()); + })); + quic_stream_.OnStreamHeaderList(/*fin=*/false, headers_.uncompressed_header_bytes(), headers_); + EXPECT_TRUE(quic_stream_.FinishedReadingHeaders()); + + // Trailer should be delivered to HCM later after body arrives. + quic_stream_.OnStreamHeaderList(/*fin=*/true, trailers_.uncompressed_header_bytes(), trailers_); + + std::string data = request_body_; + if (quic_version_.transport_version == quic::QUIC_VERSION_99) { + std::unique_ptr data_buffer; + quic::HttpEncoder encoder; + quic::QuicByteCount data_frame_header_length = + encoder.SerializeDataFrameHeader(request_body_.length(), &data_buffer); + quic::QuicStringPiece data_frame_header(data_buffer.get(), data_frame_header_length); + data = absl::StrCat(data_frame_header, request_body_); + } + quic::QuicStreamFrame frame(stream_id_, false, 0, data); + EXPECT_CALL(stream_decoder_, decodeData(_, _)) + .Times(testing::AtMost(2)) + .WillOnce(Invoke([this](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_EQ(request_body_, buffer.toString()); + EXPECT_FALSE(finished_reading); + })) + // Depends on QUIC version, there may be an empty STREAM_FRAME with FIN. But + // since there is trailers, finished_reading should always be false. + .WillOnce(Invoke([](Buffer::Instance& buffer, bool finished_reading) { + EXPECT_FALSE(finished_reading); + EXPECT_EQ(0, buffer.length()); + })); + + EXPECT_CALL(stream_decoder_, decodeTrailers_(_)) + .WillOnce(Invoke([](const Http::HeaderMapPtr& headers) { + Http::LowerCaseString key1("key1"); + Http::LowerCaseString key2(":final-offset"); + EXPECT_EQ("value1", headers->get(key1)->value().getStringView()); + EXPECT_EQ(nullptr, headers->get(key2)); + })); + quic_stream_.OnStreamFrame(frame); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/envoy_quic_utils_test.cc b/test/extensions/quic_listeners/quiche/envoy_quic_utils_test.cc new file mode 100644 index 0000000000..7fff3cc792 --- /dev/null +++ b/test/extensions/quic_listeners/quiche/envoy_quic_utils_test.cc @@ -0,0 +1,65 @@ +#include "extensions/quic_listeners/quiche/envoy_quic_utils.h" + +#pragma GCC diagnostic push +// QUICHE allows unused parameters. +#pragma GCC diagnostic ignored "-Wunused-parameter" +// QUICHE uses offsetof(). +#pragma GCC diagnostic ignored "-Winvalid-offsetof" + +#include "quiche/quic/test_tools/quic_test_utils.h" + +#pragma GCC diagnostic pop + +#include "test/mocks/api/mocks.h" +#include "test/test_common/threadsafe_singleton_injector.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::Return; + +namespace Envoy { +namespace Quic { + +TEST(EnvoyQuicUtilsTest, ConversionBetweenQuicAddressAndEnvoyAddress) { + // Mock out socket() system call to test both V4 and V6 address conversion. + testing::NiceMock os_sys_calls; + TestThreadsafeSingletonInjector os_calls{&os_sys_calls}; + ON_CALL(os_sys_calls, socket(_, _, _)).WillByDefault(Return(Api::SysCallIntResult{1, 0})); + ON_CALL(os_sys_calls, close(_)).WillByDefault(Return(Api::SysCallIntResult{0, 0})); + + quic::QuicSocketAddress quic_uninitialized_addr; + EXPECT_EQ(nullptr, quicAddressToEnvoyAddressInstance(quic_uninitialized_addr)); + + for (const std::string& ip_str : {"fd00:0:0:1::1", "1.2.3.4"}) { + quic::QuicIpAddress quic_ip; + quic_ip.FromString(ip_str); + quic::QuicSocketAddress quic_addr(quic_ip, 12345); + Network::Address::InstanceConstSharedPtr envoy_addr = + quicAddressToEnvoyAddressInstance(quic_addr); + EXPECT_EQ(quic_addr.ToString(), envoy_addr->asStringView()); + EXPECT_EQ(quic_addr, envoyAddressInstanceToQuicSocketAddress(envoy_addr)); + } +} + +TEST(EnvoyQuicUtilsTest, HeadersConversion) { + spdy::SpdyHeaderBlock headers_block; + headers_block[":host"] = "www.google.com"; + headers_block[":path"] = "/index.hml"; + headers_block[":scheme"] = "https"; + Http::HeaderMapImplPtr envoy_headers = spdyHeaderBlockToEnvoyHeaders(headers_block); + EXPECT_EQ(headers_block.size(), envoy_headers->size()); + EXPECT_EQ("www.google.com", + envoy_headers->get(Http::LowerCaseString(":host"))->value().getStringView()); + EXPECT_EQ("/index.hml", + envoy_headers->get(Http::LowerCaseString(":path"))->value().getStringView()); + EXPECT_EQ("https", envoy_headers->get(Http::LowerCaseString(":scheme"))->value().getStringView()); + + quic::QuicHeaderList quic_headers = quic::test::AsHeaderList(headers_block); + Http::HeaderMapImplPtr envoy_headers2 = quicHeadersToEnvoyHeaders(quic_headers); + EXPECT_EQ(*envoy_headers, *envoy_headers2); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/quic_listeners/quiche/platform/BUILD b/test/extensions/quic_listeners/quiche/platform/BUILD index 1383cfbfbb..09ef037677 100644 --- a/test/extensions/quic_listeners/quiche/platform/BUILD +++ b/test/extensions/quic_listeners/quiche/platform/BUILD @@ -34,6 +34,7 @@ envoy_cc_test( copts = ["-Wno-unused-parameter"], data = ["//test/extensions/transport_sockets/tls/test_data:certs"], external_deps = ["quiche_quic_platform"], + tags = ["nofips"], deps = [ ":quic_platform_epoll_clock_lib", "//source/common/memory:stats_lib", @@ -113,6 +114,7 @@ envoy_cc_test_library( "//bazel:linux": ["quic_epoll_clock.h"], "//conditions:default": [], }), + tags = ["nofips"], deps = [ "@com_googlesource_quiche//:quic_platform", "@com_googlesource_quiche//:quic_platform_epoll_lib", @@ -122,12 +124,14 @@ envoy_cc_test_library( envoy_cc_test_library( name = "quic_platform_epoll_impl_lib", hdrs = ["quic_epoll_impl.h"], + tags = ["nofips"], deps = ["@com_googlesource_quiche//:epoll_server_lib"], ) envoy_cc_test_library( name = "quic_platform_expect_bug_impl_lib", hdrs = ["quic_expect_bug_impl.h"], + tags = ["nofips"], deps = [ "@com_googlesource_quiche//:quic_platform_base", "@com_googlesource_quiche//:quic_platform_mock_log", @@ -137,6 +141,7 @@ envoy_cc_test_library( envoy_cc_test_library( name = "quic_platform_mock_log_impl_lib", hdrs = ["quic_mock_log_impl.h"], + tags = ["nofips"], deps = ["@com_googlesource_quiche//:quic_platform_base"], ) @@ -144,6 +149,7 @@ envoy_cc_test_library( name = "quic_platform_port_utils_impl_lib", srcs = ["quic_port_utils_impl.cc"], hdrs = ["quic_port_utils_impl.h"], + tags = ["nofips"], deps = [ "//source/common/network:utility_lib", "//test/test_common:environment_lib", @@ -153,6 +159,7 @@ envoy_cc_test_library( envoy_cc_test_library( name = "quic_platform_test_mem_slice_vector_impl_lib", hdrs = ["quic_test_mem_slice_vector_impl.h"], + tags = ["nofips"], deps = [ "//include/envoy/buffer:buffer_interface", "@com_googlesource_quiche//:quic_platform_mem_slice_span", @@ -162,17 +169,20 @@ envoy_cc_test_library( envoy_cc_test_library( name = "quic_platform_sleep_impl_lib", hdrs = ["quic_sleep_impl.h"], + tags = ["nofips"], deps = ["@com_googlesource_quiche//:quic_core_time_lib"], ) envoy_cc_test_library( name = "quic_platform_system_event_loop_impl_lib", hdrs = ["quic_system_event_loop_impl.h"], + tags = ["nofips"], ) envoy_cc_test_library( name = "quic_platform_thread_impl_lib", hdrs = ["quic_thread_impl.h"], + tags = ["nofips"], deps = [ "//include/envoy/thread:thread_interface", "//source/common/common:assert_lib", @@ -183,6 +193,7 @@ envoy_cc_test_library( envoy_cc_test_library( name = "quic_platform_test_impl_lib", hdrs = ["quic_test_impl.h"], + tags = ["nofips"], deps = ["//source/common/common:assert_lib"], ) @@ -190,6 +201,7 @@ envoy_cc_test_library( name = "quic_platform_test_output_impl_lib", srcs = ["quic_test_output_impl.cc"], hdrs = ["quic_test_output_impl.h"], + tags = ["nofips"], deps = [ "//source/common/filesystem:filesystem_lib", "@com_googlesource_quiche//:quic_platform_base", @@ -217,6 +229,7 @@ envoy_cc_test_library( envoy_cc_test( name = "envoy_quic_clock_test", srcs = ["envoy_quic_clock_test.cc"], + tags = ["nofips"], deps = [ "//source/extensions/quic_listeners/quiche/platform:envoy_quic_clock_lib", "//test/test_common:simulated_time_system_lib", diff --git a/test/extensions/quic_listeners/quiche/quic_io_handle_wrapper_test.cc b/test/extensions/quic_listeners/quiche/quic_io_handle_wrapper_test.cc new file mode 100644 index 0000000000..e72edcd45e --- /dev/null +++ b/test/extensions/quic_listeners/quiche/quic_io_handle_wrapper_test.cc @@ -0,0 +1,88 @@ +#include + +#include + +#include "common/network/address_impl.h" + +#include "extensions/quic_listeners/quiche/quic_io_handle_wrapper.h" + +#include "test/mocks/api/mocks.h" +#include "test/mocks/network/mocks.h" +#include "test/test_common/threadsafe_singleton_injector.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Return; + +namespace Envoy { +namespace Quic { + +class QuicIoHandleWrapperTest : public testing::Test { +public: + QuicIoHandleWrapperTest() : wrapper_(std::make_unique(socket_.ioHandle())) { + EXPECT_TRUE(wrapper_->isOpen()); + EXPECT_FALSE(socket_.ioHandle().isOpen()); + } + ~QuicIoHandleWrapperTest() override = default; + +protected: + testing::NiceMock socket_; + std::unique_ptr wrapper_; + testing::StrictMock os_sys_calls_; + TestThreadsafeSingletonInjector os_calls_{&os_sys_calls_}; +}; + +TEST_F(QuicIoHandleWrapperTest, Close) { + EXPECT_TRUE(wrapper_->close().ok()); + EXPECT_FALSE(wrapper_->isOpen()); +} + +TEST_F(QuicIoHandleWrapperTest, DelegateIoHandleCalls) { + int fd = socket_.ioHandle().fd(); + char data[5]; + Buffer::RawSlice slice{data, 5}; + EXPECT_CALL(os_sys_calls_, readv(fd, _, 1)).WillOnce(Return(Api::SysCallSizeResult{5u, 0})); + wrapper_->readv(5, &slice, 1); + + EXPECT_CALL(os_sys_calls_, writev(fd, _, 1)).WillOnce(Return(Api::SysCallSizeResult{5u, 0})); + wrapper_->writev(&slice, 1); + + EXPECT_CALL(os_sys_calls_, socket(AF_INET6, SOCK_STREAM, 0)) + .WillRepeatedly(Return(Api::SysCallIntResult{1, 0})); + EXPECT_CALL(os_sys_calls_, close(1)).WillRepeatedly(Return(Api::SysCallIntResult{0, 0})); + + Network::Address::InstanceConstSharedPtr addr(new Network::Address::Ipv4Instance(12345)); + EXPECT_CALL(os_sys_calls_, sendto(fd, data, 5u, 0, _, _)) + .WillOnce(Return(Api::SysCallSizeResult{5u, 0})); + wrapper_->sendto(slice, 0, *addr); + + EXPECT_CALL(os_sys_calls_, sendmsg(fd, _, 0)).WillOnce(Return(Api::SysCallSizeResult{5u, 0})); + wrapper_->sendmsg(&slice, 1, 0, /*self_ip=*/nullptr, *addr); + + Network::IoHandle::RecvMsgOutput output(nullptr); + EXPECT_CALL(os_sys_calls_, recvmsg(fd, _, 0)).WillOnce(Invoke([](int, struct msghdr* msg, int) { + sockaddr_storage ss; + auto ipv6_addr = reinterpret_cast(&ss); + memset(ipv6_addr, 0, sizeof(sockaddr_in6)); + ipv6_addr->sin6_family = AF_INET6; + ipv6_addr->sin6_addr = in6addr_loopback; + ipv6_addr->sin6_port = htons(54321); + *reinterpret_cast(msg->msg_name) = *ipv6_addr; + msg->msg_namelen = sizeof(sockaddr_in6); + return Api::SysCallSizeResult{5u, 0}; + })); + wrapper_->recvmsg(&slice, 1, /*self_port=*/12345, output); + + EXPECT_TRUE(wrapper_->close().ok()); + + // Following calls shouldn't be delegated. + wrapper_->readv(5, &slice, 1); + wrapper_->writev(&slice, 1); + wrapper_->sendto(slice, 0, *addr); + wrapper_->sendmsg(&slice, 1, 0, /*self_ip=*/nullptr, *addr); + wrapper_->recvmsg(&slice, 1, /*self_port=*/12345, output); +} + +} // namespace Quic +} // namespace Envoy diff --git a/test/extensions/resource_monitors/fixed_heap/config_test.cc b/test/extensions/resource_monitors/fixed_heap/config_test.cc index 64f8d6eae0..1c91ae642d 100644 --- a/test/extensions/resource_monitors/fixed_heap/config_test.cc +++ b/test/extensions/resource_monitors/fixed_heap/config_test.cc @@ -25,7 +25,8 @@ TEST(FixedHeapMonitorFactoryTest, CreateMonitor) { config.set_max_heap_size_bytes(std::numeric_limits::max()); Event::MockDispatcher dispatcher; Api::ApiPtr api = Api::createApiForTest(); - Server::Configuration::ResourceMonitorFactoryContextImpl context(dispatcher, *api); + Server::Configuration::ResourceMonitorFactoryContextImpl context( + dispatcher, *api, ProtobufMessage::getStrictValidationVisitor()); auto monitor = factory->createResourceMonitor(config, context); EXPECT_NE(monitor, nullptr); } diff --git a/test/extensions/resource_monitors/injected_resource/config_test.cc b/test/extensions/resource_monitors/injected_resource/config_test.cc index 8e1ce2266a..00862c91bf 100644 --- a/test/extensions/resource_monitors/injected_resource/config_test.cc +++ b/test/extensions/resource_monitors/injected_resource/config_test.cc @@ -28,7 +28,8 @@ TEST(InjectedResourceMonitorFactoryTest, CreateMonitor) { config.set_filename(TestEnvironment::temporaryPath("injected_resource")); Api::ApiPtr api = Api::createApiForTest(); Event::DispatcherPtr dispatcher(api->allocateDispatcher()); - Server::Configuration::ResourceMonitorFactoryContextImpl context(*dispatcher, *api); + Server::Configuration::ResourceMonitorFactoryContextImpl context( + *dispatcher, *api, ProtobufMessage::getStrictValidationVisitor()); Server::ResourceMonitorPtr monitor = factory->createResourceMonitor(config, context); EXPECT_NE(monitor, nullptr); } diff --git a/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_test.cc b/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_test.cc index b017c90f0c..a6ac6f290e 100644 --- a/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_test.cc +++ b/test/extensions/resource_monitors/injected_resource/injected_resource_monitor_test.cc @@ -61,7 +61,8 @@ class InjectedResourceMonitorTest : public testing::Test { std::unique_ptr createMonitor() { envoy::config::resource_monitor::injected_resource::v2alpha::InjectedResourceConfig config; config.set_filename(resource_filename_); - Server::Configuration::ResourceMonitorFactoryContextImpl context(*dispatcher_, *api_); + Server::Configuration::ResourceMonitorFactoryContextImpl context( + *dispatcher_, *api_, ProtobufMessage::getStrictValidationVisitor()); return std::make_unique(config, context); } diff --git a/test/extensions/retry/priority/previous_priorities/config_test.cc b/test/extensions/retry/priority/previous_priorities/config_test.cc index 9b2df93460..47f425dfee 100644 --- a/test/extensions/retry/priority/previous_priorities/config_test.cc +++ b/test/extensions/retry/priority/previous_priorities/config_test.cc @@ -31,7 +31,8 @@ class RetryPriorityTest : public testing::Test { // by that method is compatible with the downcast in createRetryPriority. auto empty = factory->createEmptyConfigProto(); empty->MergeFrom(config); - retry_priority_ = factory->createRetryPriority(*empty, 3); + retry_priority_ = + factory->createRetryPriority(*empty, ProtobufMessage::getStrictValidationVisitor(), 3); original_priority_load_ = Upstream::HealthyAndDegradedLoad{original_healthy_priority_load, original_degraded_priority_load}; } diff --git a/test/extensions/stats_sinks/dog_statsd/config_test.cc b/test/extensions/stats_sinks/dog_statsd/config_test.cc index 3299f42aa8..993fc0da13 100644 --- a/test/extensions/stats_sinks/dog_statsd/config_test.cc +++ b/test/extensions/stats_sinks/dog_statsd/config_test.cc @@ -16,10 +16,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/stats_sinks/hystrix/config_test.cc b/test/extensions/stats_sinks/hystrix/config_test.cc index 510e4ed2a8..360b982752 100644 --- a/test/extensions/stats_sinks/hystrix/config_test.cc +++ b/test/extensions/stats_sinks/hystrix/config_test.cc @@ -15,10 +15,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/stats_sinks/hystrix/hystrix_test.cc b/test/extensions/stats_sinks/hystrix/hystrix_test.cc index 677f37de83..64aec79b57 100644 --- a/test/extensions/stats_sinks/hystrix/hystrix_test.cc +++ b/test/extensions/stats_sinks/hystrix/hystrix_test.cc @@ -21,7 +21,6 @@ using testing::InSequence; using testing::Invoke; using testing::NiceMock; using testing::Return; -using testing::ReturnPointee; using testing::ReturnRef; namespace Envoy { diff --git a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc index 4ed180c0a0..9988ef4d89 100644 --- a/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc +++ b/test/extensions/stats_sinks/metrics_service/grpc_metrics_service_impl_test.cc @@ -12,7 +12,6 @@ using testing::_; using testing::InSequence; using testing::Invoke; using testing::NiceMock; -using testing::Return; namespace Envoy { namespace Extensions { diff --git a/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc b/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc index 6d698a36df..e193103473 100644 --- a/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc +++ b/test/extensions/stats_sinks/metrics_service/metrics_service_integration_test.cc @@ -66,9 +66,11 @@ class MetricsServiceIntegrationTest : public Grpc::GrpcClientIntegrationParamTes ABSL_MUST_USE_RESULT AssertionResult waitForMetricsRequest() { + bool known_summary_exists = false; bool known_histogram_exists = false; bool known_counter_exists = false; bool known_gauge_exists = false; + // Sometimes stats do not come in the first flush cycle, this loop ensures that we wait till // required stats are flushed. // TODO(ramaraochavali): Figure out a more robust way to find out all required stats have been @@ -98,11 +100,18 @@ class MetricsServiceIntegrationTest : public Grpc::GrpcClientIntegrationParamTes } if (metrics_family.name() == "cluster.cluster_0.upstream_rq_time" && metrics_family.type() == ::io::prometheus::client::MetricType::SUMMARY) { - known_histogram_exists = true; + known_summary_exists = true; Stats::HistogramStatisticsImpl empty_statistics; EXPECT_EQ(metrics_family.metric(0).summary().quantile_size(), empty_statistics.supportedQuantiles().size()); } + if (metrics_family.name() == "cluster.cluster_0.upstream_rq_time" && + metrics_family.type() == ::io::prometheus::client::MetricType::HISTOGRAM) { + known_histogram_exists = true; + Stats::HistogramStatisticsImpl empty_statistics; + EXPECT_EQ(metrics_family.metric(0).histogram().bucket_size(), + empty_statistics.supportedBuckets().size()); + } ASSERT(metrics_family.metric(0).has_timestamp_ms()); if (known_counter_exists && known_gauge_exists && known_histogram_exists) { break; @@ -111,6 +120,7 @@ class MetricsServiceIntegrationTest : public Grpc::GrpcClientIntegrationParamTes } EXPECT_TRUE(known_counter_exists); EXPECT_TRUE(known_gauge_exists); + EXPECT_TRUE(known_summary_exists); EXPECT_TRUE(known_histogram_exists); return AssertionSuccess(); diff --git a/test/extensions/stats_sinks/statsd/config_test.cc b/test/extensions/stats_sinks/statsd/config_test.cc index b71d29c94a..3ea7353f77 100644 --- a/test/extensions/stats_sinks/statsd/config_test.cc +++ b/test/extensions/stats_sinks/statsd/config_test.cc @@ -17,10 +17,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/tracers/datadog/config_test.cc b/test/extensions/tracers/datadog/config_test.cc index 363be39bba..9ffe6a0035 100644 --- a/test/extensions/tracers/datadog/config_test.cc +++ b/test/extensions/tracers/datadog/config_test.cc @@ -5,7 +5,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::Eq; using testing::NiceMock; using testing::Return; diff --git a/test/extensions/tracers/datadog/datadog_tracer_impl_test.cc b/test/extensions/tracers/datadog/datadog_tracer_impl_test.cc index 50e2722f7c..770d43655b 100644 --- a/test/extensions/tracers/datadog/datadog_tracer_impl_test.cc +++ b/test/extensions/tracers/datadog/datadog_tracer_impl_test.cc @@ -27,7 +27,6 @@ #include "gtest/gtest.h" using testing::_; -using testing::AtLeast; using testing::Eq; using testing::Invoke; using testing::NiceMock; @@ -48,7 +47,7 @@ class DatadogDriverTest : public testing::Test { if (init_timer) { timer_ = new NiceMock(&tls_.dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(900))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(900), _)); } driver_ = std::make_unique(datadog_config, cm_, stats_, tls_, runtime_); @@ -146,7 +145,7 @@ TEST_F(DatadogDriverTest, FlushSpansTimer) { span->finishSpan(); // Timer should be re-enabled. - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(900))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(900), _)); timer_->invokeCallback(); diff --git a/test/extensions/tracers/dynamic_ot/config_test.cc b/test/extensions/tracers/dynamic_ot/config_test.cc index 825f8b0aae..418609f0be 100644 --- a/test/extensions/tracers/dynamic_ot/config_test.cc +++ b/test/extensions/tracers/dynamic_ot/config_test.cc @@ -7,7 +7,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::Eq; using testing::NiceMock; using testing::Return; diff --git a/test/extensions/tracers/lightstep/config_test.cc b/test/extensions/tracers/lightstep/config_test.cc index c0a681183c..7cdd4595ef 100644 --- a/test/extensions/tracers/lightstep/config_test.cc +++ b/test/extensions/tracers/lightstep/config_test.cc @@ -5,7 +5,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; using testing::Eq; using testing::NiceMock; using testing::Return; diff --git a/test/extensions/tracers/lightstep/lightstep_tracer_impl_test.cc b/test/extensions/tracers/lightstep/lightstep_tracer_impl_test.cc index 046bd9596c..e830c55d82 100644 --- a/test/extensions/tracers/lightstep/lightstep_tracer_impl_test.cc +++ b/test/extensions/tracers/lightstep/lightstep_tracer_impl_test.cc @@ -60,7 +60,7 @@ class LightStepDriverTest : public testing::Test { if (init_timer) { timer_ = new NiceMock(&tls_.dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000), _)); } driver_ = std::make_unique(lightstep_config, cm_, stats_, tls_, runtime_, @@ -89,7 +89,7 @@ class LightStepDriverTest : public testing::Test { SystemTime start_time_; StreamInfo::MockStreamInfo stream_info_; - Envoy::Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Grpc::ContextImpl grpc_context_; NiceMock tls_; Stats::IsolatedStoreImpl stats_; @@ -336,7 +336,7 @@ TEST_F(LightStepDriverTest, FlushSpansTimer) { span->finishSpan(); // Timer should be re-enabled. - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(1000), _)); EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.lightstep.request_timeout", 5000U)) .WillOnce(Return(5000U)); EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.lightstep.flush_interval_ms", 1000U)) diff --git a/test/extensions/tracers/opencensus/config_test.cc b/test/extensions/tracers/opencensus/config_test.cc index 686f3e5b58..95cd8767f8 100644 --- a/test/extensions/tracers/opencensus/config_test.cc +++ b/test/extensions/tracers/opencensus/config_test.cc @@ -50,6 +50,8 @@ TEST(OpenCensusTracerConfigTest, OpenCensusHttpTracerWithTypedConfig) { stackdriver_project_id: test_project_id zipkin_exporter_enabled: true zipkin_url: http://127.0.0.1:9411/api/v2/spans + ocagent_exporter_enabled: true + ocagent_address: 127.0.0.1:55678 incoming_trace_context: b3 incoming_trace_context: trace_context incoming_trace_context: grpc_trace_bin diff --git a/test/extensions/tracers/opencensus/tracer_test.cc b/test/extensions/tracers/opencensus/tracer_test.cc index 241f1d0a9a..aef4d925b1 100644 --- a/test/extensions/tracers/opencensus/tracer_test.cc +++ b/test/extensions/tracers/opencensus/tracer_test.cc @@ -27,7 +27,6 @@ #include "opencensus/trace/span_id.h" using testing::NiceMock; -using testing::Return; namespace opencensus { namespace trace { diff --git a/test/extensions/tracers/zipkin/BUILD b/test/extensions/tracers/zipkin/BUILD index 9f98e8c3ba..a481ea7372 100644 --- a/test/extensions/tracers/zipkin/BUILD +++ b/test/extensions/tracers/zipkin/BUILD @@ -31,6 +31,7 @@ envoy_extension_cc_test( "//source/common/common:utility_lib", "//source/common/network:address_lib", "//source/common/network:utility_lib", + "//source/common/protobuf:utility_lib", "//source/common/runtime:runtime_lib", "//source/extensions/tracers/zipkin:zipkin_lib", "//test/mocks:common_lib", diff --git a/test/extensions/tracers/zipkin/config_test.cc b/test/extensions/tracers/zipkin/config_test.cc index b62865dd26..8b211fa74d 100644 --- a/test/extensions/tracers/zipkin/config_test.cc +++ b/test/extensions/tracers/zipkin/config_test.cc @@ -49,7 +49,8 @@ TEST(ZipkinTracerConfigTest, ZipkinHttpTracerWithTypedConfig) { typed_config: "@type": type.googleapis.com/envoy.config.trace.v2.ZipkinConfig collector_cluster: fake_cluster - collector_endpoint: /api/v1/spans + collector_endpoint: /api/v2/spans + collector_endpoint_version: HTTP_PROTO )EOF"; envoy::config::trace::v2::Tracing configuration; diff --git a/test/extensions/tracers/zipkin/span_buffer_test.cc b/test/extensions/tracers/zipkin/span_buffer_test.cc index 320b6a909b..1ef6e88e64 100644 --- a/test/extensions/tracers/zipkin/span_buffer_test.cc +++ b/test/extensions/tracers/zipkin/span_buffer_test.cc @@ -1,3 +1,5 @@ +#include "common/network/utility.h" + #include "extensions/tracers/zipkin/span_buffer.h" #include "test/test_common/test_time.h" @@ -10,104 +12,341 @@ namespace Tracers { namespace Zipkin { namespace { -TEST(ZipkinSpanBufferTest, defaultConstructorEndToEnd) { +enum class IpType { V4, V6 }; + +Endpoint createEndpoint(const IpType ip_type) { + Endpoint endpoint; + endpoint.setAddress(ip_type == IpType::V6 + ? Envoy::Network::Utility::parseInternetAddress( + "2001:db8:85a3::8a2e:370:4444", 7334, true) + : Envoy::Network::Utility::parseInternetAddress("1.2.3.4", 8080, false)); + endpoint.setServiceName("service1"); + return endpoint; +} + +Annotation createAnnotation(const absl::string_view value, const IpType ip_type) { + Annotation annotation; + annotation.setValue(value.data()); + annotation.setTimestamp(1566058071601051); + annotation.setEndpoint(createEndpoint(ip_type)); + return annotation; +} + +BinaryAnnotation createTag() { + BinaryAnnotation tag; + tag.setKey("component"); + tag.setValue("proxy"); + return tag; +} + +Span createSpan(const std::vector& annotation_values, const IpType ip_type) { + DangerousDeprecatedTestTime test_time; + Span span(test_time.timeSystem()); + span.setId(1); + span.setTraceId(1); + span.setDuration(100); + std::vector annotations; + annotations.reserve(annotation_values.size()); + for (absl::string_view value : annotation_values) { + annotations.push_back(createAnnotation(value, ip_type)); + } + span.setAnnotations(annotations); + span.setBinaryAnnotations({createTag()}); + return span; +} + +void expectSerializedBuffer(SpanBuffer& buffer, const bool delay_allocation, + const std::vector& expected_list) { DangerousDeprecatedTestTime test_time; - SpanBuffer buffer; EXPECT_EQ(0ULL, buffer.pendingSpans()); - EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); + EXPECT_EQ("[]", buffer.serialize()); + + if (delay_allocation) { + EXPECT_FALSE(buffer.addSpan(createSpan({"cs", "sr"}, IpType::V4))); + buffer.allocateBuffer(expected_list.size() + 1); + } + + // Add span after allocation, but missing required annotations should be false. EXPECT_FALSE(buffer.addSpan(Span(test_time.timeSystem()))); + EXPECT_FALSE(buffer.addSpan(createSpan({"aa"}, IpType::V4))); - buffer.allocateBuffer(2); - EXPECT_EQ(0ULL, buffer.pendingSpans()); - EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - - buffer.addSpan(Span(test_time.timeSystem())); - EXPECT_EQ(1ULL, buffer.pendingSpans()); - std::string expected_json_array_string = "[{" - R"("traceId":"0000000000000000",)" - R"("name":"",)" - R"("id":"0000000000000000",)" - R"("annotations":[],)" - R"("binaryAnnotations":[])" - "}]"; - EXPECT_EQ(expected_json_array_string, buffer.toStringifiedJsonArray()); + for (uint64_t i = 0; i < expected_list.size(); i++) { + buffer.addSpan(createSpan({"cs", "sr"}, IpType::V4)); + EXPECT_EQ(i + 1, buffer.pendingSpans()); + EXPECT_EQ(expected_list.at(i), buffer.serialize()); + } - buffer.clear(); - EXPECT_EQ(0ULL, buffer.pendingSpans()); - EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - - buffer.addSpan(Span(test_time.timeSystem())); - buffer.addSpan(Span(test_time.timeSystem())); - expected_json_array_string = "[" - "{" - R"("traceId":"0000000000000000",)" - R"("name":"",)" - R"("id":"0000000000000000",)" - R"("annotations":[],)" - R"("binaryAnnotations":[])" - "}," - "{" - R"("traceId":"0000000000000000",)" - R"("name":"",)" - R"("id":"0000000000000000",)" - R"("annotations":[],)" - R"("binaryAnnotations":[])" - "}" - "]"; - EXPECT_EQ(2ULL, buffer.pendingSpans()); - EXPECT_EQ(expected_json_array_string, buffer.toStringifiedJsonArray()); + // Add a valid span. Valid means can be serialized to v2. + EXPECT_TRUE(buffer.addSpan(createSpan({"cs"}, IpType::V4))); + // While the span is valid, however the buffer is full. + EXPECT_FALSE(buffer.addSpan(createSpan({"cs", "sr"}, IpType::V4))); buffer.clear(); EXPECT_EQ(0ULL, buffer.pendingSpans()); - EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); + EXPECT_EQ("[]", buffer.serialize()); } -TEST(ZipkinSpanBufferTest, sizeConstructorEndtoEnd) { - DangerousDeprecatedTestTime test_time; - SpanBuffer buffer(2); +template std::string serializedMessageToJson(const std::string& serialized) { + Type message; + message.ParseFromString(serialized); + std::string json; + Protobuf::util::MessageToJsonString(message, &json); + return json; +} - EXPECT_EQ(0ULL, buffer.pendingSpans()); - EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - - buffer.addSpan(Span(test_time.timeSystem())); - EXPECT_EQ(1ULL, buffer.pendingSpans()); - std::string expected_json_array_string = "[{" - R"("traceId":"0000000000000000",)" - R"("name":"",)" - R"("id":"0000000000000000",)" - R"("annotations":[],)" - R"("binaryAnnotations":[])" - "}]"; - EXPECT_EQ(expected_json_array_string, buffer.toStringifiedJsonArray()); +TEST(ZipkinSpanBufferTest, ConstructBuffer) { + const std::string expected1 = R"([{"traceId":"0000000000000001",)" + R"("name":"",)" + R"("id":"0000000000000001",)" + R"("duration":100,)" + R"("annotations":[{"timestamp":1566058071601051,)" + R"("value":"cs",)" + R"("endpoint":{"ipv4":"1.2.3.4",)" + R"("port":8080,)" + R"("serviceName":"service1"}},)" + R"({"timestamp":1566058071601051,)" + R"("value":"sr",)" + R"("endpoint":{"ipv4":"1.2.3.4",)" + R"("port":8080,)" + R"("serviceName":"service1"}}],)" + R"("binaryAnnotations":[{"key":"component",)" + R"("value":"proxy"}]}])"; - buffer.clear(); - EXPECT_EQ(0ULL, buffer.pendingSpans()); - EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); - - buffer.addSpan(Span(test_time.timeSystem())); - buffer.addSpan(Span(test_time.timeSystem())); - expected_json_array_string = "[" - "{" - R"("traceId":"0000000000000000",)" - R"("name":"",)" - R"("id":"0000000000000000",)" - R"("annotations":[],)" - R"("binaryAnnotations":[])" - "}," - "{" - R"("traceId":"0000000000000000",)" - R"("name":"",)" - R"("id":"0000000000000000",)" - R"("annotations":[],)" - R"("binaryAnnotations":[])" - "}]"; - EXPECT_EQ(2ULL, buffer.pendingSpans()); - EXPECT_EQ(expected_json_array_string, buffer.toStringifiedJsonArray()); + const std::string expected2 = R"([{"traceId":"0000000000000001",)" + R"("name":"",)" + R"("id":"0000000000000001",)" + R"("duration":100,)" + R"("annotations":[{"timestamp":1566058071601051,)" + R"("value":"cs",)" + R"("endpoint":{"ipv4":"1.2.3.4",)" + R"("port":8080,)" + R"("serviceName":"service1"}},)" + R"({"timestamp":1566058071601051,)" + R"("value":"sr",)" + R"("endpoint":{"ipv4":"1.2.3.4",)" + R"("port":8080,)" + R"("serviceName":"service1"}}],)" + R"("binaryAnnotations":[{"key":"component",)" + R"("value":"proxy"}]},)" + R"({"traceId":"0000000000000001",)" + R"("name":"",)" + R"("id":"0000000000000001",)" + R"("duration":100,)" + R"("annotations":[{"timestamp":1566058071601051,)" + R"("value":"cs",)" + R"("endpoint":{"ipv4":"1.2.3.4",)" + R"("port":8080,)" + R"("serviceName":"service1"}},)" + R"({"timestamp":1566058071601051,)" + R"("value":"sr",)" + R"("endpoint":{"ipv4":"1.2.3.4",)" + R"("port":8080,)" + R"("serviceName":"service1"}}],)" + R"("binaryAnnotations":[{"key":"component",)" + R"("value":"proxy"}]}])"; + const bool shared = true; + const bool delay_allocation = true; - buffer.clear(); - EXPECT_EQ(0ULL, buffer.pendingSpans()); - EXPECT_EQ("[]", buffer.toStringifiedJsonArray()); + SpanBuffer buffer1(envoy::config::trace::v2::ZipkinConfig::HTTP_JSON_V1, shared); + expectSerializedBuffer(buffer1, delay_allocation, {expected1, expected2}); + + // Prepare 3 slots, since we will add one more inside the `expectSerializedBuffer` function. + SpanBuffer buffer2(envoy::config::trace::v2::ZipkinConfig::HTTP_JSON_V1, shared, 3); + expectSerializedBuffer(buffer2, !delay_allocation, {expected1, expected2}); +} + +TEST(ZipkinSpanBufferTest, SerializeSpan) { + const bool shared = true; + SpanBuffer buffer1(envoy::config::trace::v2::ZipkinConfig::HTTP_JSON, shared, 2); + buffer1.addSpan(createSpan({"cs"}, IpType::V4)); + EXPECT_EQ("[{" + R"("traceId":"0000000000000001",)" + R"("id":"0000000000000001",)" + R"("kind":"CLIENT",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"1.2.3.4",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"})" + "}]", + buffer1.serialize()); + + SpanBuffer buffer1_v6(envoy::config::trace::v2::ZipkinConfig::HTTP_JSON, shared, 2); + buffer1_v6.addSpan(createSpan({"cs"}, IpType::V6)); + EXPECT_EQ("[{" + R"("traceId":"0000000000000001",)" + R"("id":"0000000000000001",)" + R"("kind":"CLIENT",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv6":"2001:db8:85a3::8a2e:370:4444",)" + R"("port":7334},)" + R"("tags":{)" + R"("component":"proxy"})" + "}]", + buffer1_v6.serialize()); + + SpanBuffer buffer2(envoy::config::trace::v2::ZipkinConfig::HTTP_JSON, shared, 2); + buffer2.addSpan(createSpan({"cs", "sr"}, IpType::V4)); + EXPECT_EQ("[{" + R"("traceId":"0000000000000001",)" + R"("id":"0000000000000001",)" + R"("kind":"CLIENT",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"1.2.3.4",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"}},)" + R"({)" + R"("traceId":"0000000000000001",)" + R"("id":"0000000000000001",)" + R"("kind":"SERVER",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"1.2.3.4",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"},)" + R"("shared":true)" + "}]", + buffer2.serialize()); + + SpanBuffer buffer3(envoy::config::trace::v2::ZipkinConfig::HTTP_JSON, !shared, 2); + buffer3.addSpan(createSpan({"cs", "sr"}, IpType::V4)); + EXPECT_EQ("[{" + R"("traceId":"0000000000000001",)" + R"("id":"0000000000000001",)" + R"("kind":"CLIENT",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"1.2.3.4",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"}},)" + R"({)" + R"("traceId":"0000000000000001",)" + R"("id":"0000000000000001",)" + R"("kind":"SERVER",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"1.2.3.4",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"})" + "}]", + buffer3.serialize()); + + SpanBuffer buffer4(envoy::config::trace::v2::ZipkinConfig::HTTP_PROTO, shared, 2); + buffer4.addSpan(createSpan({"cs"}, IpType::V4)); + EXPECT_EQ("{" + R"("spans":[{)" + R"("traceId":"AAAAAAAAAAE=",)" + R"("id":"AQAAAAAAAAA=",)" + R"("kind":"CLIENT",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"AQIDBA==",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"})" + "}]}", + serializedMessageToJson(buffer4.serialize())); + + SpanBuffer buffer4_v6(envoy::config::trace::v2::ZipkinConfig::HTTP_PROTO, shared, 2); + buffer4_v6.addSpan(createSpan({"cs"}, IpType::V6)); + EXPECT_EQ("{" + R"("spans":[{)" + R"("traceId":"AAAAAAAAAAE=",)" + R"("id":"AQAAAAAAAAA=",)" + R"("kind":"CLIENT",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv6":"IAENuIWjAAAAAIouA3BERA==",)" + R"("port":7334},)" + R"("tags":{)" + R"("component":"proxy"})" + "}]}", + serializedMessageToJson(buffer4_v6.serialize())); + + SpanBuffer buffer5(envoy::config::trace::v2::ZipkinConfig::HTTP_PROTO, shared, 2); + buffer5.addSpan(createSpan({"cs", "sr"}, IpType::V4)); + EXPECT_EQ("{" + R"("spans":[{)" + R"("traceId":"AAAAAAAAAAE=",)" + R"("id":"AQAAAAAAAAA=",)" + R"("kind":"CLIENT",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"AQIDBA==",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"}},)" + R"({)" + R"("traceId":"AAAAAAAAAAE=",)" + R"("id":"AQAAAAAAAAA=",)" + R"("kind":"SERVER",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"AQIDBA==",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"},)" + R"("shared":true)" + "}]}", + serializedMessageToJson(buffer5.serialize())); + + SpanBuffer buffer6(envoy::config::trace::v2::ZipkinConfig::HTTP_PROTO, !shared, 2); + buffer6.addSpan(createSpan({"cs", "sr"}, IpType::V4)); + EXPECT_EQ("{" + R"("spans":[{)" + R"("traceId":"AAAAAAAAAAE=",)" + R"("id":"AQAAAAAAAAA=",)" + R"("kind":"CLIENT",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"AQIDBA==",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"}},)" + R"({)" + R"("traceId":"AAAAAAAAAAE=",)" + R"("id":"AQAAAAAAAAA=",)" + R"("kind":"SERVER",)" + R"("timestamp":"1566058071601051",)" + R"("duration":"100",)" + R"("localEndpoint":{)" + R"("serviceName":"service1",)" + R"("ipv4":"AQIDBA==",)" + R"("port":8080},)" + R"("tags":{)" + R"("component":"proxy"})" + "}]}", + serializedMessageToJson(buffer6.serialize())); } } // namespace diff --git a/test/extensions/tracers/zipkin/tracer_test.cc b/test/extensions/tracers/zipkin/tracer_test.cc index 04fdbe3e07..bc5f9b9e32 100644 --- a/test/extensions/tracers/zipkin/tracer_test.cc +++ b/test/extensions/tracers/zipkin/tracer_test.cc @@ -28,7 +28,7 @@ namespace { class TestReporterImpl : public Reporter { public: TestReporterImpl(int value) : value_(value) {} - void reportSpan(const Span& span) override { reported_spans_.push_back(span); } + void reportSpan(Span&& span) override { reported_spans_.push_back(span); } int getValue() { return value_; } std::vector& reportedSpans() { return reported_spans_; } diff --git a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc index eceef5245f..5566ed107e 100644 --- a/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc +++ b/test/extensions/tracers/zipkin/zipkin_tracer_impl_test.cc @@ -49,26 +49,78 @@ class ZipkinDriverTest : public testing::Test { if (init_timer) { timer_ = new NiceMock(&tls_.dispatcher_); - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5000))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5000), _)); } driver_ = std::make_unique(zipkin_config, cm_, stats_, tls_, runtime_, local_info_, random_, time_source_); } - void setupValidDriver() { + void setupValidDriver(const std::string& version) { EXPECT_CALL(cm_, get(Eq("fake_cluster"))).WillRepeatedly(Return(&cm_.thread_local_cluster_)); - const std::string yaml_string = R"EOF( + const std::string yaml_string = fmt::format(R"EOF( collector_cluster: fake_cluster collector_endpoint: /api/v1/spans - )EOF"; + collector_endpoint_version: {} + )EOF", + version); envoy::config::trace::v2::ZipkinConfig zipkin_config; TestUtility::loadFromYaml(yaml_string, zipkin_config); setup(zipkin_config, true); } + void expectValidFlushSeveralSpans(const std::string& version, const std::string& content_type) { + setupValidDriver(version); + + Http::MockAsyncClientRequest request(&cm_.async_client_); + Http::AsyncClient::Callbacks* callback; + const absl::optional timeout(std::chrono::seconds(5)); + + EXPECT_CALL(cm_.async_client_, + send_(_, _, Http::AsyncClient::RequestOptions().setTimeout(timeout))) + .WillOnce( + Invoke([&](Http::MessagePtr& message, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callback = &callbacks; + + EXPECT_EQ("/api/v1/spans", message->headers().Path()->value().getStringView()); + EXPECT_EQ("fake_cluster", message->headers().Host()->value().getStringView()); + EXPECT_EQ(content_type, message->headers().ContentType()->value().getStringView()); + + return &request; + })); + + EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.zipkin.min_flush_spans", 5)) + .Times(2) + .WillRepeatedly(Return(2)); + EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.zipkin.request_timeout", 5000U)) + .WillOnce(Return(5000U)); + + Tracing::SpanPtr first_span = driver_->startSpan( + config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, true}); + first_span->finishSpan(); + + Tracing::SpanPtr second_span = driver_->startSpan( + config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, true}); + second_span->finishSpan(); + + Http::MessagePtr msg(new Http::ResponseMessageImpl( + Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "202"}}})); + + callback->onSuccess(std::move(msg)); + + EXPECT_EQ(2U, stats_.counter("tracing.zipkin.spans_sent").value()); + EXPECT_EQ(1U, stats_.counter("tracing.zipkin.reports_sent").value()); + EXPECT_EQ(0U, stats_.counter("tracing.zipkin.reports_dropped").value()); + EXPECT_EQ(0U, stats_.counter("tracing.zipkin.reports_failed").value()); + + callback->onFailure(Http::AsyncClient::FailureReason::Reset); + + EXPECT_EQ(1U, stats_.counter("tracing.zipkin.reports_failed").value()); + } + // TODO(#4160): Currently time_system_ is initialized from DangerousDeprecatedTestTime, which uses // real time, not mock-time. When that is switched to use mock-time instead, I think // generateRandom64() may not be as random as we want, and we'll need to inject entropy @@ -133,58 +185,23 @@ TEST_F(ZipkinDriverTest, InitializeDriver) { } TEST_F(ZipkinDriverTest, FlushSeveralSpans) { - setupValidDriver(); - - Http::MockAsyncClientRequest request(&cm_.async_client_); - Http::AsyncClient::Callbacks* callback; - const absl::optional timeout(std::chrono::seconds(5)); - - EXPECT_CALL(cm_.async_client_, - send_(_, _, Http::AsyncClient::RequestOptions().setTimeout(timeout))) - .WillOnce( - Invoke([&](Http::MessagePtr& message, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - callback = &callbacks; - - EXPECT_EQ("/api/v1/spans", message->headers().Path()->value().getStringView()); - EXPECT_EQ("fake_cluster", message->headers().Host()->value().getStringView()); - EXPECT_EQ("application/json", - message->headers().ContentType()->value().getStringView()); - - return &request; - })); - - EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.zipkin.min_flush_spans", 5)) - .Times(2) - .WillRepeatedly(Return(2)); - EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.zipkin.request_timeout", 5000U)) - .WillOnce(Return(5000U)); - - Tracing::SpanPtr first_span = driver_->startSpan(config_, request_headers_, operation_name_, - start_time_, {Tracing::Reason::Sampling, true}); - first_span->finishSpan(); - - Tracing::SpanPtr second_span = driver_->startSpan(config_, request_headers_, operation_name_, - start_time_, {Tracing::Reason::Sampling, true}); - second_span->finishSpan(); - - Http::MessagePtr msg(new Http::ResponseMessageImpl( - Http::HeaderMapPtr{new Http::TestHeaderMapImpl{{":status", "202"}}})); - - callback->onSuccess(std::move(msg)); + expectValidFlushSeveralSpans("HTTP_JSON_V1", "application/json"); +} - EXPECT_EQ(2U, stats_.counter("tracing.zipkin.spans_sent").value()); - EXPECT_EQ(1U, stats_.counter("tracing.zipkin.reports_sent").value()); - EXPECT_EQ(0U, stats_.counter("tracing.zipkin.reports_dropped").value()); - EXPECT_EQ(0U, stats_.counter("tracing.zipkin.reports_failed").value()); +TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpJsonV1) { + expectValidFlushSeveralSpans("HTTP_JSON_V1", "application/json"); +} - callback->onFailure(Http::AsyncClient::FailureReason::Reset); +TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpJson) { + expectValidFlushSeveralSpans("HTTP_JSON", "application/json"); +} - EXPECT_EQ(1U, stats_.counter("tracing.zipkin.reports_failed").value()); +TEST_F(ZipkinDriverTest, FlushSeveralSpansHttpProto) { + expectValidFlushSeveralSpans("HTTP_PROTO", "application/x-protobuf"); } TEST_F(ZipkinDriverTest, FlushOneSpanReportFailure) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); Http::MockAsyncClientRequest request(&cm_.async_client_); Http::AsyncClient::Callbacks* callback; @@ -226,7 +243,7 @@ TEST_F(ZipkinDriverTest, FlushOneSpanReportFailure) { } TEST_F(ZipkinDriverTest, FlushSpansTimer) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); const absl::optional timeout(std::chrono::seconds(5)); EXPECT_CALL(cm_.async_client_, @@ -240,7 +257,7 @@ TEST_F(ZipkinDriverTest, FlushSpansTimer) { span->finishSpan(); // Timer should be re-enabled. - EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5000))); + EXPECT_CALL(*timer_, enableTimer(std::chrono::milliseconds(5000), _)); EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.zipkin.request_timeout", 5000U)) .WillOnce(Return(5000U)); EXPECT_CALL(runtime_.snapshot_, getInteger("tracing.zipkin.flush_interval_ms", 5000U)) @@ -253,7 +270,7 @@ TEST_F(ZipkinDriverTest, FlushSpansTimer) { } TEST_F(ZipkinDriverTest, NoB3ContextSampledTrue) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID)); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID)); @@ -267,7 +284,7 @@ TEST_F(ZipkinDriverTest, NoB3ContextSampledTrue) { } TEST_F(ZipkinDriverTest, NoB3ContextSampledFalse) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID)); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID)); @@ -281,7 +298,7 @@ TEST_F(ZipkinDriverTest, NoB3ContextSampledFalse) { } TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleTrue) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -297,7 +314,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleTrue) { } TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleFalse) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -313,7 +330,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NoSampleDecisionSampleFalse) { } TEST_F(ZipkinDriverTest, PropagateB3NotSampled) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID)); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID)); @@ -335,7 +352,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NotSampled) { } TEST_F(ZipkinDriverTest, PropagateB3NotSampledWithFalse) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID)); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID)); @@ -357,7 +374,7 @@ TEST_F(ZipkinDriverTest, PropagateB3NotSampledWithFalse) { } TEST_F(ZipkinDriverTest, PropagateB3SampledWithTrue) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_SPAN_ID)); EXPECT_EQ(nullptr, request_headers_.get(ZipkinCoreConstants::get().X_B3_TRACE_ID)); @@ -379,7 +396,7 @@ TEST_F(ZipkinDriverTest, PropagateB3SampledWithTrue) { } TEST_F(ZipkinDriverTest, PropagateB3SampleFalse) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -396,7 +413,7 @@ TEST_F(ZipkinDriverTest, PropagateB3SampleFalse) { } TEST_F(ZipkinDriverTest, ZipkinSpanTest) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); // ==== // Test effective setTag() @@ -476,7 +493,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); const std::string trace_id = Hex::uint64ToHex(generateRandom64()); const std::string span_id = Hex::uint64ToHex(generateRandom64()); @@ -500,7 +517,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersEmptyParentSpanTest) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); // Root span so have same trace and span id const std::string id = Hex::uint64ToHex(generateRandom64()); @@ -521,7 +538,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3HeadersEmptyParentSpanTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3Headers128TraceIdTest) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); const uint64_t trace_id_high = generateRandom64(); const uint64_t trace_id_low = generateRandom64(); @@ -549,7 +566,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromB3Headers128TraceIdTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, std::string("xyz")); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_SPAN_ID, @@ -563,7 +580,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidTraceIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidSpanIdB3HeadersTest) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -577,7 +594,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidSpanIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidParentIdB3HeadersTest) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); @@ -592,7 +609,7 @@ TEST_F(ZipkinDriverTest, ZipkinSpanContextFromInvalidParentIdB3HeadersTest) { } TEST_F(ZipkinDriverTest, ExplicitlySetSampledFalse) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, true}); @@ -609,7 +626,7 @@ TEST_F(ZipkinDriverTest, ExplicitlySetSampledFalse) { } TEST_F(ZipkinDriverTest, ExplicitlySetSampledTrue) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); Tracing::SpanPtr span = driver_->startSpan(config_, request_headers_, operation_name_, start_time_, {Tracing::Reason::Sampling, false}); @@ -626,7 +643,7 @@ TEST_F(ZipkinDriverTest, ExplicitlySetSampledTrue) { } TEST_F(ZipkinDriverTest, DuplicatedHeader) { - setupValidDriver(); + setupValidDriver("HTTP_JSON_V1"); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_TRACE_ID, Hex::uint64ToHex(generateRandom64())); request_headers_.addReferenceKey(ZipkinCoreConstants::get().X_B3_SPAN_ID, diff --git a/test/extensions/transport_sockets/alts/config_test.cc b/test/extensions/transport_sockets/alts/config_test.cc index 34fcd34ae6..a17405dc7e 100644 --- a/test/extensions/transport_sockets/alts/config_test.cc +++ b/test/extensions/transport_sockets/alts/config_test.cc @@ -11,10 +11,7 @@ #include "gtest/gtest.h" using Envoy::Server::Configuration::MockTransportSocketFactoryContext; -using testing::_; -using testing::Invoke; using testing::ReturnRef; -using testing::StrictMock; namespace Envoy { namespace Extensions { @@ -23,7 +20,7 @@ namespace Alts { namespace { TEST(UpstreamAltsConfigTest, CreateSocketFactory) { - MockTransportSocketFactoryContext factory_context; + NiceMock factory_context; Singleton::ManagerImpl singleton_manager{Thread::threadFactoryForTest()}; EXPECT_CALL(factory_context, singletonManager()).WillRepeatedly(ReturnRef(singleton_manager)); UpstreamAltsTransportSocketConfigFactory factory; @@ -43,7 +40,7 @@ TEST(UpstreamAltsConfigTest, CreateSocketFactory) { } TEST(DownstreamAltsConfigTest, CreateSocketFactory) { - MockTransportSocketFactoryContext factory_context; + NiceMock factory_context; Singleton::ManagerImpl singleton_manager{Thread::threadFactoryForTest()}; EXPECT_CALL(factory_context, singletonManager()).WillRepeatedly(ReturnRef(singleton_manager)); DownstreamAltsTransportSocketConfigFactory factory; diff --git a/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc b/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc index 09d4191c37..c0db008cb2 100644 --- a/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_frame_protector_test.cc @@ -12,11 +12,6 @@ namespace TransportSockets { namespace Alts { namespace { -using testing::_; -using testing::InSequence; -using testing::Invoke; -using testing::NiceMock; -using testing::SaveArg; using namespace std::string_literals; /** diff --git a/test/extensions/transport_sockets/alts/tsi_socket_test.cc b/test/extensions/transport_sockets/alts/tsi_socket_test.cc index f95db040fb..3fd39b5200 100644 --- a/test/extensions/transport_sockets/alts/tsi_socket_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_socket_test.cc @@ -17,7 +17,6 @@ namespace { using testing::NiceMock; using testing::Return; using testing::ReturnRef; -using testing::StrictMock; class TsiSocketTest : public testing::Test { protected: diff --git a/test/extensions/transport_sockets/tls/BUILD b/test/extensions/transport_sockets/tls/BUILD index 32ec88bb3d..94f93e62a6 100644 --- a/test/extensions/transport_sockets/tls/BUILD +++ b/test/extensions/transport_sockets/tls/BUILD @@ -25,6 +25,7 @@ envoy_cc_test( external_deps = ["ssl"], shard_count = 4, deps = [ + ":test_private_key_method_provider_test_lib", "//include/envoy/network:transport_socket_interface", "//source/common/buffer:buffer_lib", "//source/common/common:empty_string", @@ -41,14 +42,17 @@ envoy_cc_test( "//source/extensions/transport_sockets/tls:context_lib", "//source/extensions/transport_sockets/tls:ssl_socket_lib", "//source/extensions/transport_sockets/tls:utility_lib", + "//source/extensions/transport_sockets/tls/private_key:private_key_manager_lib", "//test/extensions/transport_sockets/tls/test_data:cert_infos", "//test/mocks/buffer:buffer_mocks", "//test/mocks/network:network_mocks", "//test/mocks/runtime:runtime_mocks", "//test/mocks/server:server_mocks", + "//test/mocks/ssl:ssl_mocks", "//test/mocks/stats:stats_mocks", "//test/test_common:environment_lib", "//test/test_common:network_utility_lib", + "//test/test_common:registry_lib", "//test/test_common:simulated_time_system_lib", "//test/test_common:utility_lib", ], @@ -75,6 +79,7 @@ envoy_cc_test( "//test/mocks/runtime:runtime_mocks", "//test/mocks/secret:secret_mocks", "//test/mocks/server:server_mocks", + "//test/mocks/ssl:ssl_mocks", "//test/test_common:environment_lib", "//test/test_common:simulated_time_system_lib", ], @@ -109,3 +114,23 @@ envoy_cc_test_library( "//test/test_common:environment_lib", ], ) + +envoy_cc_test_library( + name = "test_private_key_method_provider_test_lib", + srcs = [ + "test_private_key_method_provider.cc", + ], + hdrs = [ + "test_private_key_method_provider.h", + ], + external_deps = ["ssl"], + deps = [ + "//include/envoy/api:api_interface", + "//include/envoy/event:dispatcher_interface", + "//include/envoy/server:transport_socket_config_interface", + "//include/envoy/ssl/private_key:private_key_config_interface", + "//include/envoy/ssl/private_key:private_key_interface", + "//source/common/config:utility_lib", + "//source/common/protobuf:utility_lib", + ], +) diff --git a/test/extensions/transport_sockets/tls/context_impl_test.cc b/test/extensions/transport_sockets/tls/context_impl_test.cc index ef82b26fff..4327856f54 100644 --- a/test/extensions/transport_sockets/tls/context_impl_test.cc +++ b/test/extensions/transport_sockets/tls/context_impl_test.cc @@ -17,6 +17,7 @@ #include "test/extensions/transport_sockets/tls/test_data/san_dns3_cert_info.h" #include "test/mocks/secret/mocks.h" #include "test/mocks/server/mocks.h" +#include "test/mocks/ssl/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" @@ -1074,7 +1075,7 @@ TEST_F(ServerContextConfigImplTest, MultiSdsConfig) { tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs(); tls_context.mutable_common_tls_context()->add_tls_certificate_sds_secret_configs(); EXPECT_THROW_WITH_REGEX( - MessageUtil::validate(tls_context), + TestUtility::validate(tls_context), EnvoyException, "Proto constraint validation failed"); } @@ -1185,6 +1186,124 @@ TEST_F(ServerContextConfigImplTest, InvalidIgnoreCertsNoCA) { EXPECT_NO_THROW(ServerContextConfigImpl server_context_config(tls_context, factory_context_)); } +TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadFailureNoProvider) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + NiceMock context_manager; + NiceMock private_key_method_manager; + EXPECT_CALL(factory_context_, sslContextManager()).WillOnce(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)); + const std::string tls_context_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: mock_provider + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + test_value: 100 + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); + EXPECT_THROW_WITH_REGEX( + ServerContextConfigImpl server_context_config(tls_context, factory_context_), EnvoyException, + "Failed to load incomplete certificate from "); +} + +TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadFailureNoMethod) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + tls_context.mutable_common_tls_context()->add_tls_certificates(); + Stats::IsolatedStoreImpl store; + NiceMock context_manager; + NiceMock private_key_method_manager; + auto private_key_method_provider_ptr = + std::make_shared>(); + Event::SimulatedTimeSystem time_system; + ContextManagerImpl manager(time_system); + EXPECT_CALL(factory_context_, sslContextManager()).WillOnce(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)); + EXPECT_CALL(private_key_method_manager, createPrivateKeyMethodProvider(_, _)) + .WillOnce(Return(private_key_method_provider_ptr)); + const std::string tls_context_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: mock_provider + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + test_value: 100 + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); + ServerContextConfigImpl server_context_config(tls_context, factory_context_); + EXPECT_THROW_WITH_MESSAGE( + Envoy::Ssl::ServerContextSharedPtr server_ctx( + manager.createSslServerContext(store, server_context_config, std::vector{})), + EnvoyException, "Failed to get BoringSSL private key method from provider"); +} + +TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadSuccess) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + NiceMock context_manager; + NiceMock private_key_method_manager; + auto private_key_method_provider_ptr = + std::make_shared>(); + EXPECT_CALL(factory_context_, sslContextManager()).WillOnce(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)); + EXPECT_CALL(private_key_method_manager, createPrivateKeyMethodProvider(_, _)) + .WillOnce(Return(private_key_method_provider_ptr)); + const std::string tls_context_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: mock_provider + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + test_value: 100 + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); + ServerContextConfigImpl server_context_config(tls_context, factory_context_); +} + +TEST_F(ServerContextConfigImplTest, PrivateKeyMethodLoadFailureBothKeyAndMethod) { + envoy::api::v2::auth::DownstreamTlsContext tls_context; + NiceMock context_manager; + NiceMock private_key_method_manager; + auto private_key_method_provider_ptr = + std::make_shared>(); + EXPECT_CALL(factory_context_, sslContextManager()).WillOnce(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)); + EXPECT_CALL(private_key_method_manager, createPrivateKeyMethodProvider(_, _)) + .WillOnce(Return(private_key_method_provider_ptr)); + const std::string tls_context_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_key.pem" + private_key_provider: + provider_name: mock_provider + typed_config: + "@type": type.googleapis.com/google.protobuf.Struct + value: + test_value: 100 + )EOF"; + TestUtility::loadFromYaml(TestEnvironment::substitute(tls_context_yaml), tls_context); + EXPECT_THROW_WITH_MESSAGE( + ServerContextConfigImpl server_context_config(tls_context, factory_context_), EnvoyException, + "Certificate configuration can't have both private_key and private_key_provider"); +} + } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc index 785e9956ee..ae3205fa75 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.cc @@ -22,8 +22,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::Return; - namespace Envoy { namespace Ssl { diff --git a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.h b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.h index 18b276d6a3..133e73bd43 100644 --- a/test/extensions/transport_sockets/tls/integration/ssl_integration_test.h +++ b/test/extensions/transport_sockets/tls/integration/ssl_integration_test.h @@ -11,8 +11,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::NiceMock; - namespace Envoy { namespace Ssl { diff --git a/test/extensions/transport_sockets/tls/ssl_socket_test.cc b/test/extensions/transport_sockets/tls/ssl_socket_test.cc index 07daedca6c..aff65278f0 100644 --- a/test/extensions/transport_sockets/tls/ssl_socket_test.cc +++ b/test/extensions/transport_sockets/tls/ssl_socket_test.cc @@ -16,6 +16,7 @@ #include "extensions/filters/listener/tls_inspector/tls_inspector.h" #include "extensions/transport_sockets/tls/context_config_impl.h" #include "extensions/transport_sockets/tls/context_impl.h" +#include "extensions/transport_sockets/tls/private_key/private_key_manager_impl.h" #include "extensions/transport_sockets/tls/ssl_socket.h" #include "test/extensions/transport_sockets/tls/ssl_certs_test.h" @@ -25,13 +26,16 @@ #include "test/extensions/transport_sockets/tls/test_data/san_dns_cert_info.h" #include "test/extensions/transport_sockets/tls/test_data/san_uri_cert_info.h" #include "test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert_info.h" +#include "test/extensions/transport_sockets/tls/test_private_key_method_provider.h" #include "test/mocks/buffer/mocks.h" #include "test/mocks/network/mocks.h" #include "test/mocks/secret/mocks.h" #include "test/mocks/server/mocks.h" +#include "test/mocks/ssl/mocks.h" #include "test/mocks/stats/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" +#include "test/test_common/registry.h" #include "test/test_common/utility.h" #include "absl/strings/str_replace.h" @@ -95,7 +99,9 @@ class TestUtilOptions : public TestUtilOptionsBase { TestUtilOptions(const std::string& client_ctx_yaml, const std::string& server_ctx_yaml, bool expect_success, Network::Address::IpVersion version) : TestUtilOptionsBase(expect_success, version), client_ctx_yaml_(client_ctx_yaml), - server_ctx_yaml_(server_ctx_yaml), expect_no_cert_(false), expect_no_cert_chain_(false) { + server_ctx_yaml_(server_ctx_yaml), expect_no_cert_(false), expect_no_cert_chain_(false), + expect_private_key_method_(false), + expected_server_close_event_(Network::ConnectionEvent::RemoteClose) { if (expect_success) { setExpectedServerStats("ssl.handshake"); } else { @@ -204,12 +210,28 @@ class TestUtilOptions : public TestUtilOptionsBase { return expected_expiration_peer_cert_; } + TestUtilOptions& setPrivateKeyMethodExpected(bool expected_method) { + expect_private_key_method_ = expected_method; + return *this; + } + + bool expectedPrivateKeyMethod() const { return expect_private_key_method_; } + + TestUtilOptions& setExpectedServerCloseEvent(Network::ConnectionEvent expected_event) { + expected_server_close_event_ = expected_event; + return *this; + } + + Network::ConnectionEvent expectedServerCloseEvent() const { return expected_server_close_event_; } + private: const std::string client_ctx_yaml_; const std::string server_ctx_yaml_; bool expect_no_cert_; bool expect_no_cert_chain_; + bool expect_private_key_method_; + Network::ConnectionEvent expected_server_close_event_; std::string expected_digest_; std::vector expected_local_uri_; std::string expected_serial_number_; @@ -231,6 +253,21 @@ void testUtil(const TestUtilOptions& options) { server_factory_context; ON_CALL(server_factory_context, api()).WillByDefault(ReturnRef(*server_api)); + // For private key method testing. + NiceMock context_manager; + Extensions::PrivateKeyMethodProvider::TestPrivateKeyMethodFactory test_factory; + Registry::InjectFactory + test_private_key_method_factory(test_factory); + PrivateKeyMethodManagerImpl private_key_method_manager; + if (options.expectedPrivateKeyMethod()) { + EXPECT_CALL(server_factory_context, sslContextManager()) + .WillOnce(ReturnRef(context_manager)) + .WillRepeatedly(ReturnRef(context_manager)); + EXPECT_CALL(context_manager, privateKeyMethodManager()) + .WillOnce(ReturnRef(private_key_method_manager)) + .WillRepeatedly(ReturnRef(private_key_method_manager)); + } + envoy::api::v2::auth::DownstreamTlsContext server_tls_context; TestUtility::loadFromYaml(TestEnvironment::substitute(options.serverCtxYaml()), server_tls_context); @@ -376,7 +413,7 @@ void testUtil(const TestUtilOptions& options) { } else { EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { close_second_time(); })); - EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::RemoteClose)) + EXPECT_CALL(server_connection_callbacks, onEvent(options.expectedServerCloseEvent())) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { close_second_time(); })); } @@ -547,7 +584,8 @@ const std::string testUtilV2(const TestUtilOptionsV2& options) { client_ssl_socket_factory.createTransportSocket(options.transportSocketOptions()), nullptr); if (!options.clientSession().empty()) { - const SslSocket* ssl_socket = dynamic_cast(client_connection->ssl()); + const SslSocketInfo* ssl_socket = + dynamic_cast(client_connection->ssl().get()); SSL* client_ssl_socket = ssl_socket->rawSslForTest(); SSL_CTX* client_ssl_context = SSL_get_SSL_CTX(client_ssl_socket); SSL_SESSION* client_ssl_session = @@ -592,7 +630,8 @@ const std::string testUtilV2(const TestUtilOptionsV2& options) { EXPECT_EQ(options.expectedALPNProtocol(), client_connection->nextProtocol()); } EXPECT_EQ(options.expectedClientCertUri(), server_connection->ssl()->uriSanPeerCertificate()); - const SslSocket* ssl_socket = dynamic_cast(client_connection->ssl()); + const SslSocketInfo* ssl_socket = + dynamic_cast(client_connection->ssl().get()); SSL* client_ssl_socket = ssl_socket->rawSslForTest(); if (!options.expectedProtocolVersion().empty()) { EXPECT_EQ(options.expectedProtocolVersion(), client_connection->ssl()->tlsVersion()); @@ -606,7 +645,8 @@ const std::string testUtilV2(const TestUtilOptionsV2& options) { } absl::optional server_ssl_requested_server_name; - const SslSocket* server_ssl_socket = dynamic_cast(server_connection->ssl()); + const SslSocketInfo* server_ssl_socket = + dynamic_cast(server_connection->ssl().get()); SSL* server_ssl = server_ssl_socket->rawSslForTest(); auto requested_server_name = SSL_get_servername(server_ssl, TLSEXT_NAMETYPE_host_name); if (requested_server_name != nullptr) { @@ -662,7 +702,8 @@ const std::string testUtilV2(const TestUtilOptionsV2& options) { dispatcher->run(Event::Dispatcher::RunType::Block); if (!options.expectedServerStats().empty()) { - EXPECT_EQ(1UL, server_stats_store.counter(options.expectedServerStats()).value()); + EXPECT_EQ(1UL, server_stats_store.counter(options.expectedServerStats()).value()) + << options.expectedServerStats(); } if (!options.expectedClientStats().empty()) { @@ -2303,7 +2344,8 @@ TEST_P(SslSocketTest, ClientAuthMultipleCAs) { ssl_socket_factory.createTransportSocket(nullptr), nullptr); // Verify that server sent list with 2 acceptable client certificate CA names. - const SslSocket* ssl_socket = dynamic_cast(client_connection->ssl()); + const SslSocketInfo* ssl_socket = + dynamic_cast(client_connection->ssl().get()); SSL_set_cert_cb( ssl_socket->rawSslForTest(), [](SSL* ssl, void*) -> int { @@ -2422,7 +2464,8 @@ void testTicketSessionResumption(const std::string& server_ctx_yaml1, EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { - const SslSocket* ssl_socket = dynamic_cast(client_connection->ssl()); + const SslSocketInfo* ssl_socket = + dynamic_cast(client_connection->ssl().get()); ssl_session = SSL_get1_session(ssl_socket->rawSslForTest()); EXPECT_TRUE(SSL_SESSION_is_resumable(ssl_session)); client_connection->close(Network::ConnectionCloseType::NoFlush); @@ -2440,7 +2483,8 @@ void testTicketSessionResumption(const std::string& server_ctx_yaml1, socket2.localAddress(), Network::Address::InstanceConstSharedPtr(), ssl_socket_factory.createTransportSocket(nullptr), nullptr); client_connection->addConnectionCallbacks(client_connection_callbacks); - const SslSocket* ssl_socket = dynamic_cast(client_connection->ssl()); + const SslSocketInfo* ssl_socket = + dynamic_cast(client_connection->ssl().get()); SSL_set_session(ssl_socket->rawSslForTest(), ssl_session); SSL_SESSION_free(ssl_session); @@ -2845,7 +2889,8 @@ TEST_P(SslSocketTest, ClientAuthCrossListenerSessionResumption) { EXPECT_CALL(server_connection_callbacks, onEvent(Network::ConnectionEvent::LocalClose)); EXPECT_CALL(client_connection_callbacks, onEvent(Network::ConnectionEvent::Connected)) .WillOnce(Invoke([&](Network::ConnectionEvent) -> void { - const SslSocket* ssl_socket = dynamic_cast(client_connection->ssl()); + const SslSocketInfo* ssl_socket = + dynamic_cast(client_connection->ssl().get()); ssl_session = SSL_get1_session(ssl_socket->rawSslForTest()); EXPECT_TRUE(SSL_SESSION_is_resumable(ssl_session)); server_connection->close(Network::ConnectionCloseType::NoFlush); @@ -2863,7 +2908,8 @@ TEST_P(SslSocketTest, ClientAuthCrossListenerSessionResumption) { socket2.localAddress(), Network::Address::InstanceConstSharedPtr(), ssl_socket_factory.createTransportSocket(nullptr), nullptr); client_connection->addConnectionCallbacks(client_connection_callbacks); - const SslSocket* ssl_socket = dynamic_cast(client_connection->ssl()); + const SslSocketInfo* ssl_socket = + dynamic_cast(client_connection->ssl().get()); SSL_set_session(ssl_socket->rawSslForTest(), ssl_session); SSL_SESSION_free(ssl_session); @@ -4145,6 +4191,525 @@ TEST_P(SslReadBufferLimitTest, SmallReadsIntoSameSlice) { dispatcher_->run(Event::Dispatcher::RunType::Block); } +// Test asynchronous signing (ECDHE) using a private key provider. +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncSignSuccess) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string successful_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true, + GetParam()); + testUtil(successful_test_options.setPrivateKeyMethodExpected(true)); +} + +// Test asynchronous decryption (RSA). +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncDecryptSuccess) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: decrypt + sync_mode: false + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string successful_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - TLS_RSA_WITH_AES_128_GCM_SHA256 +)EOF"; + + TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true, + GetParam()); + testUtil(successful_test_options.setPrivateKeyMethodExpected(true)); +} + +// Test synchronous signing (ECDHE). +TEST_P(SslSocketTest, RsaPrivateKeyProviderSyncSignSuccess) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string successful_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true, + GetParam()); + testUtil(successful_test_options.setPrivateKeyMethodExpected(true)); +} + +// Test synchronous decryption (RSA). +TEST_P(SslSocketTest, RsaPrivateKeyProviderSyncDecryptSuccess) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: decrypt + sync_mode: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string successful_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - TLS_RSA_WITH_AES_128_GCM_SHA256 +)EOF"; + + TestUtilOptions successful_test_options(successful_client_ctx_yaml, server_ctx_yaml, true, + GetParam()); + testUtil(successful_test_options.setPrivateKeyMethodExpected(true)); +} + +// Test asynchronous signing (ECDHE) failure (invalid signature). +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncSignFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + crypto_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats( + "ssl.connection_error")); +} + +// Test synchronous signing (ECDHE) failure (invalid signature). +TEST_P(SslSocketTest, RsaPrivateKeyProviderSyncSignFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: true + crypto_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats( + "ssl.connection_error")); +} + +// Test the sign operation return with an error. +TEST_P(SslSocketTest, RsaPrivateKeyProviderSignFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + method_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats( + "ssl.connection_error")); +} + +// Test the decrypt operation return with an error. +TEST_P(SslSocketTest, RsaPrivateKeyProviderDecryptFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: decrypt + method_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - TLS_RSA_WITH_AES_128_GCM_SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true).setExpectedServerStats( + "ssl.connection_error")); +} + +// Test the sign operation return with an error in complete. +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncSignCompleteFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + async_method_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - ECDHE-RSA-AES128-GCM-SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true) + .setExpectedServerCloseEvent(Network::ConnectionEvent::LocalClose) + .setExpectedServerStats("ssl.connection_error")); +} + +// Test the decrypt operation return with an error in complete. +TEST_P(SslSocketTest, RsaPrivateKeyProviderAsyncDecryptCompleteFailure) { + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + certificate_chain: + filename: "{{ test_tmpdir }}/unittestcert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: decrypt + async_method_error: true + mode: rsa + validation_context: + trusted_ca: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem" + crl: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.crl" +)EOF"; + const std::string failing_client_ctx_yaml = R"EOF( + common_tls_context: + tls_params: + cipher_suites: + - TLS_RSA_WITH_AES_128_GCM_SHA256 +)EOF"; + + TestUtilOptions failing_test_options(failing_client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true) + .setExpectedServerCloseEvent(Network::ConnectionEvent::LocalClose) + .setExpectedServerStats("ssl.connection_error")); +} + +// Test having one cert with private key method and another with just +// private key. +TEST_P(SslSocketTest, RsaPrivateKeyProviderMultiCertSuccess) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + mode: rsa + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" +)EOF"; + + TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam()); + testUtil(test_options.setPrivateKeyMethodExpected(true)); +} + +// Test having two certs with private key methods. This will +// synchronously fail because the second certificate is a ECDSA one and +// the RSA method can't handle it. +TEST_P(SslSocketTest, RsaPrivateKeyProviderMultiCertFail) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + mode: rsa + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" + expected_operation: sign + sync_mode: false + mode: rsa +)EOF"; + + TestUtilOptions failing_test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam()); + EXPECT_THROW_WITH_MESSAGE(testUtil(failing_test_options.setPrivateKeyMethodExpected(true)), + EnvoyException, "Private key is not RSA.") +} + +// Test ECDSA private key method provider mode. +TEST_P(SslSocketTest, EcdsaPrivateKeyProviderSuccess) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" + expected_operation: sign + mode: ecdsa +)EOF"; + + TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam()); + testUtil(test_options.setPrivateKeyMethodExpected(true)); +} + +// Test having two certs with different private key method modes. It's expected that the ECDSA +// provider mode is being used. RSA provider mode is set to fail with "async_method_error", but +// that's not happening. +TEST_P(SslSocketTest, RsaAndEcdsaPrivateKeyProviderMultiCertSuccess) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + async_method_error: true + mode: rsa + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" + expected_operation: sign + mode: ecdsa +)EOF"; + TestUtilOptions test_options(client_ctx_yaml, server_ctx_yaml, true, GetParam()); + testUtil(test_options.setPrivateKeyMethodExpected(true)); +} + +// Test having two certs with different private key method modes. ECDSA provider is set to fail. +TEST_P(SslSocketTest, RsaAndEcdsaPrivateKeyProviderMultiCertFail) { + const std::string client_ctx_yaml = absl::StrCat(R"EOF( + common_tls_context: + tls_params: + tls_minimum_protocol_version: TLSv1_2 + tls_maximum_protocol_version: TLSv1_2 + cipher_suites: + - ECDHE-ECDSA-AES128-GCM-SHA256 + - ECDHE-RSA-AES128-GCM-SHA256 + validation_context: + verify_certificate_hash: )EOF", + TEST_SELFSIGNED_ECDSA_P256_CERT_HASH); + + const std::string server_ctx_yaml = R"EOF( + common_tls_context: + tls_certificates: + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_tmpdir }}/unittestkey.pem" + expected_operation: sign + sync_mode: false + mode: rsa + - certificate_chain: + filename: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_cert.pem" + private_key_provider: + provider_name: test + config: + private_key_file: "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/selfsigned_ecdsa_p256_key.pem" + expected_operation: sign + async_method_error: true + mode: ecdsa +)EOF"; + TestUtilOptions failing_test_options(client_ctx_yaml, server_ctx_yaml, false, GetParam()); + testUtil(failing_test_options.setPrivateKeyMethodExpected(true) + .setExpectedServerCloseEvent(Network::ConnectionEvent::LocalClose) + .setExpectedServerStats("ssl.connection_error")); +} + } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/test/extensions/transport_sockets/tls/test_private_key_method_provider.cc b/test/extensions/transport_sockets/tls/test_private_key_method_provider.cc new file mode 100644 index 0000000000..cf78fbf3a3 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_private_key_method_provider.cc @@ -0,0 +1,377 @@ +#include "test/extensions/transport_sockets/tls/test_private_key_method_provider.h" + +#include + +#include "envoy/api/api.h" + +#include "openssl/ssl.h" + +namespace Envoy { +namespace Extensions { +namespace PrivateKeyMethodProvider { + +void TestPrivateKeyConnection::delayed_op() { + const std::chrono::milliseconds timeout_0ms{0}; + + timer_ = dispatcher_.createTimer([this]() -> void { + finished_ = true; + this->cb_.onPrivateKeyMethodComplete(); + }); + timer_->enableTimer(timeout_0ms); +} + +static int calculateDigest(const EVP_MD* md, const uint8_t* in, size_t in_len, unsigned char* hash, + unsigned int* hash_len) { + bssl::ScopedEVP_MD_CTX ctx; + + // Calculate the message digest for signing. + if (!EVP_DigestInit_ex(ctx.get(), md, nullptr) || !EVP_DigestUpdate(ctx.get(), in, in_len) || + !EVP_DigestFinal_ex(ctx.get(), hash, hash_len)) { + return 0; + } + return 1; +} + +static ssl_private_key_result_t ecdsaPrivateKeySign(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out, uint16_t signature_algorithm, + const uint8_t* in, size_t in_len) { + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + TestPrivateKeyConnection* ops = static_cast( + SSL_get_ex_data(ssl, TestPrivateKeyMethodProvider::ecdsaConnectionIndex())); + unsigned int out_len_unsigned; + + if (!ops) { + return ssl_private_key_failure; + } + + if (ops->test_options_.method_error_) { + // Have an artificial test failure. + return ssl_private_key_failure; + } + + if (!ops->test_options_.sign_expected_) { + return ssl_private_key_failure; + } + + const EVP_MD* md = SSL_get_signature_algorithm_digest(signature_algorithm); + if (!md) { + return ssl_private_key_failure; + } + + if (!calculateDigest(md, in, in_len, hash, &hash_len)) { + return ssl_private_key_failure; + } + + bssl::UniquePtr ec_key(EVP_PKEY_get1_EC_KEY(ops->getPrivateKey())); + if (!ec_key) { + return ssl_private_key_failure; + } + + // Borrow "out" because it has been already initialized to the max_out size. + if (!ECDSA_sign(0, hash, hash_len, out, &out_len_unsigned, ec_key.get())) { + return ssl_private_key_failure; + } + + if (ops->test_options_.sync_mode_) { + // Return immediately with the results. + if (out_len_unsigned > max_out) { + return ssl_private_key_failure; + } + *out_len = out_len_unsigned; + return ssl_private_key_success; + } + + ops->output_.assign(out, out + out_len_unsigned); + // Tell SSL socket that the operation is ready to be called again. + ops->delayed_op(); + + return ssl_private_key_retry; +} + +static ssl_private_key_result_t ecdsaPrivateKeyDecrypt(SSL*, uint8_t*, size_t*, size_t, + const uint8_t*, size_t) { + return ssl_private_key_failure; +} + +static ssl_private_key_result_t rsaPrivateKeySign(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out, uint16_t signature_algorithm, + const uint8_t* in, size_t in_len) { + TestPrivateKeyConnection* ops = static_cast( + SSL_get_ex_data(ssl, TestPrivateKeyMethodProvider::rsaConnectionIndex())); + unsigned char hash[EVP_MAX_MD_SIZE]; + unsigned int hash_len; + + if (!ops) { + return ssl_private_key_failure; + } + + if (ops->test_options_.method_error_) { + return ssl_private_key_failure; + } + + if (!ops->test_options_.sign_expected_) { + return ssl_private_key_failure; + } + + const EVP_MD* md = SSL_get_signature_algorithm_digest(signature_algorithm); + if (!md) { + return ssl_private_key_failure; + } + + if (!calculateDigest(md, in, in_len, hash, &hash_len)) { + return ssl_private_key_failure; + } + + RSA* rsa = EVP_PKEY_get0_RSA(ops->getPrivateKey()); + if (rsa == nullptr) { + return ssl_private_key_failure; + } + + if (ops->test_options_.crypto_error_) { + // Flip the bits in the first byte of the digest so that the handshake will fail. + hash[0] ^= hash[0]; + } + + // Perform RSA signing. + if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) { + RSA_sign_pss_mgf1(rsa, out_len, out, max_out, hash, hash_len, md, nullptr, -1); + } else { + unsigned int out_len_unsigned; + if (!RSA_sign(EVP_MD_type(md), hash, hash_len, out, &out_len_unsigned, rsa)) { + return ssl_private_key_failure; + } + if (out_len_unsigned > max_out) { + return ssl_private_key_failure; + } + *out_len = out_len_unsigned; + } + + if (ops->test_options_.sync_mode_) { + return ssl_private_key_success; + } + + ops->output_.assign(out, out + *out_len); + ops->delayed_op(); + + return ssl_private_key_retry; +} + +static ssl_private_key_result_t rsaPrivateKeyDecrypt(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out, const uint8_t* in, + size_t in_len) { + TestPrivateKeyConnection* ops = static_cast( + SSL_get_ex_data(ssl, TestPrivateKeyMethodProvider::rsaConnectionIndex())); + + if (!ops) { + return ssl_private_key_failure; + } + + if (ops->test_options_.method_error_) { + return ssl_private_key_failure; + } + + if (!ops->test_options_.decrypt_expected_) { + return ssl_private_key_failure; + } + + RSA* rsa = EVP_PKEY_get0_RSA(ops->getPrivateKey()); + if (rsa == nullptr) { + return ssl_private_key_failure; + } + + if (!RSA_decrypt(rsa, out_len, out, max_out, in, in_len, RSA_NO_PADDING)) { + return ssl_private_key_failure; + } + + if (ops->test_options_.sync_mode_) { + return ssl_private_key_success; + } + + ops->output_.assign(out, out + *out_len); + ops->delayed_op(); + + return ssl_private_key_retry; +} + +static ssl_private_key_result_t privateKeyComplete(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out, int id) { + TestPrivateKeyConnection* ops = static_cast(SSL_get_ex_data(ssl, id)); + + if (!ops->finished_) { + // The operation didn't finish yet, retry. + return ssl_private_key_retry; + } + + if (ops->test_options_.async_method_error_) { + return ssl_private_key_failure; + } + + if (ops->output_.size() > max_out) { + return ssl_private_key_failure; + } + + std::copy(ops->output_.begin(), ops->output_.end(), out); + *out_len = ops->output_.size(); + + return ssl_private_key_success; +} + +static ssl_private_key_result_t rsaPrivateKeyComplete(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out) { + return privateKeyComplete(ssl, out, out_len, max_out, + TestPrivateKeyMethodProvider::rsaConnectionIndex()); +} + +static ssl_private_key_result_t ecdsaPrivateKeyComplete(SSL* ssl, uint8_t* out, size_t* out_len, + size_t max_out) { + return privateKeyComplete(ssl, out, out_len, max_out, + TestPrivateKeyMethodProvider::ecdsaConnectionIndex()); +} + +Ssl::BoringSslPrivateKeyMethodSharedPtr +TestPrivateKeyMethodProvider::getBoringSslPrivateKeyMethod() { + return method_; +} + +bool TestPrivateKeyMethodProvider::checkFips() { + if (mode_ == "rsa") { + RSA* rsa_private_key = EVP_PKEY_get0_RSA(pkey_.get()); + if (rsa_private_key == nullptr || !RSA_check_fips(rsa_private_key)) { + return false; + } + } else { // if (mode_ == "ecdsa") + const EC_KEY* ecdsa_private_key = EVP_PKEY_get0_EC_KEY(pkey_.get()); + if (ecdsa_private_key == nullptr || !EC_KEY_check_fips(ecdsa_private_key)) { + return false; + } + } + return true; +} + +TestPrivateKeyConnection::TestPrivateKeyConnection( + Ssl::PrivateKeyConnectionCallbacks& cb, Event::Dispatcher& dispatcher, + bssl::UniquePtr pkey, TestPrivateKeyConnectionTestOptions& test_options) + : test_options_(test_options), cb_(cb), dispatcher_(dispatcher), pkey_(std::move(pkey)) {} + +void TestPrivateKeyMethodProvider::registerPrivateKeyMethod(SSL* ssl, + Ssl::PrivateKeyConnectionCallbacks& cb, + Event::Dispatcher& dispatcher) { + TestPrivateKeyConnection* ops; + // In multi-cert case, when the same provider is used in different modes with the same SSL object, + // we need to keep both rsa and ecdsa connection objects in store because the test options for the + // two certificates may be different. We need to be able to deduct in the signing, decryption, and + // completion functions which options to use, so we associate the connection objects to the same + // SSL object using different user data indexes. + // + // Another way to do this would be to store both test options in one connection object. + int index = mode_ == "rsa" ? TestPrivateKeyMethodProvider::rsaConnectionIndex() + : TestPrivateKeyMethodProvider::ecdsaConnectionIndex(); + + // Check if there is another certificate of the same mode associated with the context. This would + // be an error. + ops = static_cast(SSL_get_ex_data(ssl, index)); + if (ops != nullptr) { + throw EnvoyException( + "Can't distinguish between two registered providers for the same SSL object."); + } + + ops = new TestPrivateKeyConnection(cb, dispatcher, bssl::UpRef(pkey_), test_options_); + SSL_set_ex_data(ssl, index, ops); +} + +void TestPrivateKeyMethodProvider::unregisterPrivateKeyMethod(SSL* ssl) { + int index = mode_ == "rsa" ? TestPrivateKeyMethodProvider::rsaConnectionIndex() + : TestPrivateKeyMethodProvider::ecdsaConnectionIndex(); + TestPrivateKeyConnection* ops = + static_cast(SSL_get_ex_data(ssl, index)); + SSL_set_ex_data(ssl, index, nullptr); + delete ops; +} + +static int createIndex() { + int index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr); + RELEASE_ASSERT(index >= 0, "Failed to get SSL user data index."); + return index; +} + +int TestPrivateKeyMethodProvider::rsaConnectionIndex() { + CONSTRUCT_ON_FIRST_USE(int, createIndex()); +} + +int TestPrivateKeyMethodProvider::ecdsaConnectionIndex() { + CONSTRUCT_ON_FIRST_USE(int, createIndex()); +} + +TestPrivateKeyMethodProvider::TestPrivateKeyMethodProvider( + const ProtobufWkt::Struct& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) { + std::string private_key_path; + + for (auto& value_it : config.fields()) { + auto& value = value_it.second; + if (value_it.first == "private_key_file" && + value.kind_case() == ProtobufWkt::Value::kStringValue) { + private_key_path = value.string_value(); + } + if (value_it.first == "sync_mode" && value.kind_case() == ProtobufWkt::Value::kBoolValue) { + test_options_.sync_mode_ = value.bool_value(); + } + if (value_it.first == "crypto_error" && value.kind_case() == ProtobufWkt::Value::kBoolValue) { + test_options_.crypto_error_ = value.bool_value(); + } + if (value_it.first == "method_error" && value.kind_case() == ProtobufWkt::Value::kBoolValue) { + test_options_.method_error_ = value.bool_value(); + } + if (value_it.first == "async_method_error" && + value.kind_case() == ProtobufWkt::Value::kBoolValue) { + test_options_.async_method_error_ = value.bool_value(); + } + if (value_it.first == "expected_operation" && + value.kind_case() == ProtobufWkt::Value::kStringValue) { + if (value.string_value() == "decrypt") { + test_options_.decrypt_expected_ = true; + } else if (value.string_value() == "sign") { + test_options_.sign_expected_ = true; + } + } + if (value_it.first == "mode" && value.kind_case() == ProtobufWkt::Value::kStringValue) { + mode_ = value.string_value(); + } + } + + std::string private_key = factory_context.api().fileSystem().fileReadToEnd(private_key_path); + bssl::UniquePtr bio( + BIO_new_mem_buf(const_cast(private_key.data()), private_key.size())); + bssl::UniquePtr pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); + if (pkey == nullptr) { + throw EnvoyException("Failed to read private key from disk."); + } + + method_ = std::make_shared(); + + // Have two modes, "rsa" and "ecdsa", for testing multi-cert use cases. + if (mode_ == "rsa") { + if (EVP_PKEY_id(pkey.get()) != EVP_PKEY_RSA) { + throw EnvoyException("Private key is not RSA."); + } + method_->sign = rsaPrivateKeySign; + method_->decrypt = rsaPrivateKeyDecrypt; + method_->complete = rsaPrivateKeyComplete; + } else if (mode_ == "ecdsa") { + if (EVP_PKEY_id(pkey.get()) != EVP_PKEY_EC) { + throw EnvoyException("Private key is not ECDSA."); + } + method_->sign = ecdsaPrivateKeySign; + method_->decrypt = ecdsaPrivateKeyDecrypt; + method_->complete = ecdsaPrivateKeyComplete; + } else { + throw EnvoyException("Unknown test provider mode, supported modes are \"rsa\" and \"ecdsa\"."); + } + + pkey_ = std::move(pkey); +} + +} // namespace PrivateKeyMethodProvider +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/test_private_key_method_provider.h b/test/extensions/transport_sockets/tls/test_private_key_method_provider.h new file mode 100644 index 0000000000..6aadf93010 --- /dev/null +++ b/test/extensions/transport_sockets/tls/test_private_key_method_provider.h @@ -0,0 +1,96 @@ +#pragma once + +#include "envoy/event/dispatcher.h" +#include "envoy/server/transport_socket_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/private_key/private_key_config.h" + +#include "common/config/utility.h" +#include "common/protobuf/utility.h" + +namespace Envoy { +namespace Extensions { +namespace PrivateKeyMethodProvider { + +struct TestPrivateKeyConnectionTestOptions { + // Return private key method value directly without asynchronous operation. + bool sync_mode_{}; + + // The "decrypt" private key method is expected to he called. + bool decrypt_expected_{}; + + // The "sign" private key method is expected to he called. + bool sign_expected_{}; + + // Add a cryptographic error (invalid signature, incorrect decryption). + bool crypto_error_{}; + + // Return an error from the private key method. + bool method_error_{}; + + // Return an error from the private key method completion function. + bool async_method_error_{}; +}; + +// An example private key method provider for testing the decrypt() and sign() +// functionality. +class TestPrivateKeyConnection { +public: + TestPrivateKeyConnection(Ssl::PrivateKeyConnectionCallbacks& cb, Event::Dispatcher& dispatcher, + bssl::UniquePtr pkey, + TestPrivateKeyConnectionTestOptions& test_options); + EVP_PKEY* getPrivateKey() { return pkey_.get(); } + void delayed_op(); + // Store the output data temporarily. + std::vector output_; + // The complete callback can return other value than "retry" only after + // onPrivateKeyMethodComplete() function has been called. This is controlled by "finished" + // variable. + bool finished_{}; + TestPrivateKeyConnectionTestOptions& test_options_; + +private: + Ssl::PrivateKeyConnectionCallbacks& cb_; + Event::Dispatcher& dispatcher_; + bssl::UniquePtr pkey_; + // A zero-length timer controls the callback. + Event::TimerPtr timer_; +}; + +class TestPrivateKeyMethodProvider : public virtual Ssl::PrivateKeyMethodProvider { +public: + TestPrivateKeyMethodProvider( + const ProtobufWkt::Struct& config, + Server::Configuration::TransportSocketFactoryContext& factory_context); + // Ssl::PrivateKeyMethodProvider + void registerPrivateKeyMethod(SSL* ssl, Ssl::PrivateKeyConnectionCallbacks& cb, + Event::Dispatcher& dispatcher) override; + void unregisterPrivateKeyMethod(SSL* ssl) override; + bool checkFips() override; + Ssl::BoringSslPrivateKeyMethodSharedPtr getBoringSslPrivateKeyMethod() override; + + static int rsaConnectionIndex(); + static int ecdsaConnectionIndex(); + +private: + Ssl::BoringSslPrivateKeyMethodSharedPtr method_{}; + bssl::UniquePtr pkey_; + TestPrivateKeyConnectionTestOptions test_options_; + std::string mode_; +}; + +class TestPrivateKeyMethodFactory : public Ssl::PrivateKeyMethodProviderInstanceFactory { +public: + // Ssl::PrivateKeyMethodProviderInstanceFactory + Ssl::PrivateKeyMethodProviderSharedPtr createPrivateKeyMethodProviderInstance( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Server::Configuration::TransportSocketFactoryContext& factory_context) override { + return std::make_shared(config.config(), factory_context); + } + + std::string name() const override { return std::string("test"); }; +}; + +} // namespace PrivateKeyMethodProvider +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/transport_sockets/tls/utility_test.cc b/test/extensions/transport_sockets/tls/utility_test.cc index 502058490c..ccf803faa0 100644 --- a/test/extensions/transport_sockets/tls/utility_test.cc +++ b/test/extensions/transport_sockets/tls/utility_test.cc @@ -22,7 +22,7 @@ namespace { TEST(UtilityTest, TestGetSubjectAlternateNamesWithDNS) { bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_dns_cert.pem")); - const std::vector& subject_alt_names = Utility::getSubjectAltNames(*cert, GEN_DNS); + const auto& subject_alt_names = Utility::getSubjectAltNames(*cert, GEN_DNS); EXPECT_EQ(1, subject_alt_names.size()); } @@ -30,22 +30,21 @@ TEST(UtilityTest, TestMultipleGetSubjectAlternateNamesWithDNS) { bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( "{{ test_rundir " "}}/test/extensions/transport_sockets/tls/test_data/san_multiple_dns_cert.pem")); - const std::vector& subject_alt_names = Utility::getSubjectAltNames(*cert, GEN_DNS); + const auto& subject_alt_names = Utility::getSubjectAltNames(*cert, GEN_DNS); EXPECT_EQ(2, subject_alt_names.size()); } TEST(UtilityTest, TestGetSubjectAlternateNamesWithUri) { bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/san_uri_cert.pem")); - const std::vector& subject_alt_names = Utility::getSubjectAltNames(*cert, GEN_URI); + const auto& subject_alt_names = Utility::getSubjectAltNames(*cert, GEN_URI); EXPECT_EQ(1, subject_alt_names.size()); } TEST(UtilityTest, TestGetSubjectAlternateNamesWithNoSAN) { bssl::UniquePtr cert = readCertFromFile(TestEnvironment::substitute( "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/no_san_cert.pem")); - const std::vector& uri_subject_alt_names = - Utility::getSubjectAltNames(*cert, GEN_URI); + const auto& uri_subject_alt_names = Utility::getSubjectAltNames(*cert, GEN_URI); EXPECT_EQ(0, uri_subject_alt_names.size()); } diff --git a/test/fuzz/utility.h b/test/fuzz/utility.h index 155226aa02..adc3912c0e 100644 --- a/test/fuzz/utility.h +++ b/test/fuzz/utility.h @@ -103,8 +103,10 @@ inline test::fuzz::Headers toHeaders(const Http::HeaderMap& headers) { return fuzz_headers; } -inline TestStreamInfo fromStreamInfo(const test::fuzz::StreamInfo& stream_info, - const Ssl::MockConnectionInfo* connection_info) { +const std::string TestSubjectPeer = + "CN=Test Server,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US"; + +inline TestStreamInfo fromStreamInfo(const test::fuzz::StreamInfo& stream_info) { // Set mocks' default string return value to be an empty string. testing::DefaultValue::Set(EMPTY_STRING); TestStreamInfo test_stream_info; @@ -129,10 +131,10 @@ inline TestStreamInfo fromStreamInfo(const test::fuzz::StreamInfo& stream_info, test_stream_info.downstream_local_address_ = address; test_stream_info.downstream_direct_remote_address_ = address; test_stream_info.downstream_remote_address_ = address; - test_stream_info.setDownstreamSslConnection(connection_info); + auto connection_info = std::make_shared>(); ON_CALL(*connection_info, subjectPeerCertificate()) - .WillByDefault(testing::Return( - "CN=Test Server,OU=Lyft Engineering,O=Lyft,L=San Francisco,ST=California,C=US")); + .WillByDefault(testing::ReturnRef(TestSubjectPeer)); + test_stream_info.setDownstreamSslConnection(connection_info); return test_stream_info; } diff --git a/test/integration/BUILD b/test/integration/BUILD index 393eadbfa2..02a8a0101d 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -33,6 +33,7 @@ envoy_cc_test_library( "//source/common/config:protobuf_link_hacks", "//source/common/config:resources_lib", "//source/common/protobuf:utility_lib", + "//source/extensions/filters/network/redis_proxy:config", "//test/common/grpc:grpc_client_integration_lib", "//test/test_common:network_utility_lib", "//test/test_common:utility_lib", @@ -251,6 +252,17 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "http_subset_lb_integration_test", + srcs = [ + "http_subset_lb_integration_test.cc", + ], + deps = [ + ":http_integration_lib", + "//test/common/upstream:utility_lib", + ], +) + envoy_cc_test( name = "http_timeout_integration_test", srcs = [ @@ -434,6 +446,7 @@ envoy_cc_test_library( "//source/common/network:utility_lib", "//source/common/runtime:runtime_lib", "//source/common/stats:isolated_store_lib", + "//source/common/stats:symbol_table_creator_lib", "//source/common/stats:thread_local_store_lib", "//source/common/thread_local:thread_local_lib", "//source/common/upstream:upstream_includes", @@ -536,6 +549,7 @@ envoy_cc_test( deps = [ ":integration_lib", "//source/common/network:listen_socket_lib", + "//source/server:active_raw_udp_listener_config", "//test/integration/filters:udp_listener_echo_filter_lib", "//test/server:utility_lib", "//test/test_common:utility_lib", @@ -545,9 +559,13 @@ envoy_cc_test( envoy_cc_test( name = "stats_integration_test", srcs = ["stats_integration_test.cc"], + # The symbol table cluster memory tests take a while to run specially under tsan. + # Shard it to avoid test timeout. + shard_count = 2, deps = [ ":integration_lib", "//source/common/memory:stats_lib", + "//source/common/stats:symbol_table_creator_lib", "//source/extensions/filters/http/router:config", "//source/extensions/filters/network/http_connection_manager:config", "//test/common/stats:stat_test_utility_lib", diff --git a/test/integration/ads_integration.cc b/test/integration/ads_integration.cc index cf62f423aa..4e2590e4b4 100644 --- a/test/integration/ads_integration.cc +++ b/test/integration/ads_integration.cc @@ -14,10 +14,7 @@ #include "test/test_common/network_utility.h" #include "test/test_common/utility.h" -using testing::AssertionFailure; using testing::AssertionResult; -using testing::AssertionSuccess; -using testing::IsSubstring; namespace Envoy { @@ -46,6 +43,17 @@ envoy::api::v2::Cluster AdsIntegrationTest::buildCluster(const std::string& name name)); } +envoy::api::v2::Cluster AdsIntegrationTest::buildRedisCluster(const std::string& name) { + return TestUtility::parseYaml(fmt::format(R"EOF( + name: {} + connect_timeout: 5s + type: EDS + eds_cluster_config: {{ eds_config: {{ ads: {{}} }} }} + lb_policy: MAGLEV + )EOF", + name)); +} + envoy::api::v2::ClusterLoadAssignment AdsIntegrationTest::buildClusterLoadAssignment(const std::string& name) { return TestUtility::parseYaml( @@ -87,6 +95,29 @@ envoy::api::v2::Listener AdsIntegrationTest::buildListener(const std::string& na name, Network::Test::getLoopbackAddressString(ipVersion()), stat_prefix, route_config)); } +envoy::api::v2::Listener AdsIntegrationTest::buildRedisListener(const std::string& name, + const std::string& cluster) { + return TestUtility::parseYaml(fmt::format( + R"EOF( + name: {} + address: + socket_address: + address: {} + port_value: 0 + filter_chains: + filters: + - name: envoy.redis_proxy + config: + settings: + op_timeout: 1s + stat_prefix: {} + prefix_routes: + catch_all_route: + cluster: {} + )EOF", + name, Network::Test::getLoopbackAddressString(ipVersion()), name, cluster)); +} + envoy::api::v2::RouteConfiguration AdsIntegrationTest::buildRouteConfig(const std::string& name, const std::string& cluster) { return TestUtility::parseYaml(fmt::format(R"EOF( diff --git a/test/integration/ads_integration.h b/test/integration/ads_integration.h index 024bcf04d6..628550fb9c 100644 --- a/test/integration/ads_integration.h +++ b/test/integration/ads_integration.h @@ -49,11 +49,15 @@ class AdsIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, public H envoy::api::v2::Cluster buildCluster(const std::string& name); + envoy::api::v2::Cluster buildRedisCluster(const std::string& name); + envoy::api::v2::ClusterLoadAssignment buildClusterLoadAssignment(const std::string& name); envoy::api::v2::Listener buildListener(const std::string& name, const std::string& route_config, const std::string& stat_prefix = "ads_test"); + envoy::api::v2::Listener buildRedisListener(const std::string& name, const std::string& cluster); + envoy::api::v2::RouteConfiguration buildRouteConfig(const std::string& name, const std::string& cluster); diff --git a/test/integration/ads_integration_test.cc b/test/integration/ads_integration_test.cc index 9479194d73..e2cb9250c0 100644 --- a/test/integration/ads_integration_test.cc +++ b/test/integration/ads_integration_test.cc @@ -22,10 +22,7 @@ #include "gtest/gtest.h" -using testing::AssertionFailure; using testing::AssertionResult; -using testing::AssertionSuccess; -using testing::IsSubstring; namespace Envoy { @@ -200,6 +197,46 @@ TEST_P(AdsIntegrationTest, DuplicateInitialClusters) { test_server_->waitForCounterGe("cluster_manager.cds.update_rejected", 1); } +// Validates that removing a redis cluster does not crash Envoy. +// Regression test for issue https://github.com/envoyproxy/envoy/issues/7990. +TEST_P(AdsIntegrationTest, RedisClusterRemoval) { + initialize(); + + // Send initial configuration with a redis cluster and a redis proxy listener. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + sendDiscoveryResponse(Config::TypeUrl::get().Cluster, + {buildRedisCluster("redis_cluster")}, + {buildRedisCluster("redis_cluster")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "", + {"redis_cluster"}, {}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().ClusterLoadAssignment, {buildClusterLoadAssignment("redis_cluster")}, + {buildClusterLoadAssignment("redis_cluster")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "1", {}, {}, {})); + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "", {}, {}, {})); + sendDiscoveryResponse( + Config::TypeUrl::get().Listener, {buildRedisListener("listener_0", "redis_cluster")}, + {buildRedisListener("listener_0", "redis_cluster")}, {}, "1"); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().ClusterLoadAssignment, "1", + {"redis_cluster"}, {}, {})); + + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Listener, "1", {}, {}, {})); + + // Validate that redis listener is successfully created. + test_server_->waitForCounterGe("listener_manager.listener_create_success", 1); + + // Now send a CDS update, removing redis cluster added above. + sendDiscoveryResponse( + Config::TypeUrl::get().Cluster, {buildCluster("cluster_2")}, {buildCluster("cluster_2")}, + {"redis_cluster"}, "2"); + + // Validate that the cluster is removed successfully. + test_server_->waitForCounterGe("cluster_manager.cluster_removed", 1); +} + // Validate that the request with duplicate clusters in the subsequent requests (warming clusters) // is rejected. TEST_P(AdsIntegrationTest, DuplicateWarmingClusters) { diff --git a/test/integration/cds_integration_test.cc b/test/integration/cds_integration_test.cc index a8efa18cfa..1ea5f5fe67 100644 --- a/test/integration/cds_integration_test.cc +++ b/test/integration/cds_integration_test.cc @@ -19,10 +19,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -using testing::AssertionFailure; using testing::AssertionResult; -using testing::AssertionSuccess; -using testing::IsSubstring; namespace Envoy { namespace { diff --git a/test/integration/fake_upstream.cc b/test/integration/fake_upstream.cc index b30deb3aee..f558063619 100644 --- a/test/integration/fake_upstream.cc +++ b/test/integration/fake_upstream.cc @@ -73,30 +73,22 @@ void FakeStream::decodeMetadata(Http::MetadataMapPtr&& metadata_map_ptr) { } void FakeStream::encode100ContinueHeaders(const Http::HeaderMapImpl& headers) { - // TSan complains about thread-safety of std::shared_ptr when linked against libc++. - // See: https://github.com/envoyproxy/envoy/pull/7929 - std::unique_ptr headers_copy( + std::shared_ptr headers_copy( new Http::HeaderMapImpl(static_cast(headers))); - parent_.connection().dispatcher().post([this, headers = headers_copy.release()]() -> void { - encoder_.encode100ContinueHeaders(*headers); - delete headers; - }); + parent_.connection().dispatcher().post( + [this, headers_copy]() -> void { encoder_.encode100ContinueHeaders(*headers_copy); }); } void FakeStream::encodeHeaders(const Http::HeaderMapImpl& headers, bool end_stream) { - // TSan complains about thread-safety of std::shared_ptr when linked against libc++. - // See: https://github.com/envoyproxy/envoy/pull/7929 - std::unique_ptr headers_copy( + std::shared_ptr headers_copy( new Http::HeaderMapImpl(static_cast(headers))); if (add_served_by_header_) { headers_copy->addCopy(Http::LowerCaseString("x-served-by"), parent_.connection().localAddress()->asString()); } - parent_.connection().dispatcher().post( - [this, headers = headers_copy.release(), end_stream]() -> void { - encoder_.encodeHeaders(*headers, end_stream); - delete headers; - }); + parent_.connection().dispatcher().post([this, headers_copy, end_stream]() -> void { + encoder_.encodeHeaders(*headers_copy, end_stream); + }); } void FakeStream::encodeData(absl::string_view data, bool end_stream) { @@ -114,24 +106,16 @@ void FakeStream::encodeData(uint64_t size, bool end_stream) { } void FakeStream::encodeData(Buffer::Instance& data, bool end_stream) { - // TSan complains about thread-safety of std::shared_ptr when linked against libc++. - // See: https://github.com/envoyproxy/envoy/pull/7929 - std::unique_ptr data_copy(new Buffer::OwnedImpl(data)); - parent_.connection().dispatcher().post([this, data = data_copy.release(), end_stream]() -> void { - encoder_.encodeData(*data, end_stream); - delete data; - }); + std::shared_ptr data_copy(new Buffer::OwnedImpl(data)); + parent_.connection().dispatcher().post( + [this, data_copy, end_stream]() -> void { encoder_.encodeData(*data_copy, end_stream); }); } void FakeStream::encodeTrailers(const Http::HeaderMapImpl& trailers) { - // TSan complains about thread-safety of std::shared_ptr when linked against libc++. - // See: https://github.com/envoyproxy/envoy/pull/7929 - std::unique_ptr trailers_copy( + std::shared_ptr trailers_copy( new Http::HeaderMapImpl(static_cast(trailers))); - parent_.connection().dispatcher().post([this, trailers = trailers_copy.release()]() -> void { - encoder_.encodeTrailers(*trailers); - delete trailers; - }); + parent_.connection().dispatcher().post( + [this, trailers_copy]() -> void { encoder_.encodeTrailers(*trailers_copy); }); } void FakeStream::encodeResetStream() { diff --git a/test/integration/fake_upstream.h b/test/integration/fake_upstream.h index ce1b6e24be..6257fedee9 100644 --- a/test/integration/fake_upstream.h +++ b/test/integration/fake_upstream.h @@ -293,8 +293,8 @@ class SharedConnectionWrapper : public Network::ConnectionCallbacks { private: Network::Connection& connection_; Thread::MutexBasicLockable lock_; - Common::CallbackManager<> disconnect_callback_manager_ GUARDED_BY(lock_); - bool disconnected_ GUARDED_BY(lock_){}; + Common::CallbackManager<> disconnect_callback_manager_ ABSL_GUARDED_BY(lock_); + bool disconnected_ ABSL_GUARDED_BY(lock_){}; const bool allow_unexpected_disconnects_; }; @@ -339,7 +339,7 @@ class QueuedConnectionWrapper : public LinkedObject { private: SharedConnectionWrapper shared_connection_; Thread::MutexBasicLockable lock_; - bool parented_ GUARDED_BY(lock_); + bool parented_ ABSL_GUARDED_BY(lock_); const bool allow_unexpected_disconnects_; }; @@ -399,7 +399,7 @@ class FakeConnectionBase : public Logger::Loggable { bool initialized_{}; Thread::CondVar connection_event_; Thread::MutexBasicLockable lock_; - bool half_closed_ GUARDED_BY(lock_){}; + bool half_closed_ ABSL_GUARDED_BY(lock_){}; Event::TestTimeSystem& time_system_; }; @@ -605,13 +605,17 @@ class FakeUpstream : Logger::Loggable, Stats::Scope& listenerScope() override { return parent_.stats_store_; } uint64_t listenerTag() const override { return 0; } const std::string& name() const override { return name_; } + Network::ActiveUdpListenerFactory* udpListenerFactory() override { + return udp_listener_factory_.get(); + } FakeUpstream& parent_; + Network::ActiveUdpListenerFactoryPtr udp_listener_factory_; std::string name_; }; void threadRoutine(); - SharedConnectionWrapper& consumeConnection() EXCLUSIVE_LOCKS_REQUIRED(lock_); + SharedConnectionWrapper& consumeConnection() ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_); Network::SocketPtr socket_; ConditionalInitializer server_initialized_; @@ -624,11 +628,11 @@ class FakeUpstream : Logger::Loggable, Event::TestTimeSystem& time_system_; Event::DispatcherPtr dispatcher_; Network::ConnectionHandlerPtr handler_; - std::list new_connections_ GUARDED_BY(lock_); + std::list new_connections_ ABSL_GUARDED_BY(lock_); // When a QueuedConnectionWrapper is popped from new_connections_, ownership is transferred to // consumed_connections_. This allows later the Connection destruction (when the FakeUpstream is // deleted) on the same thread that allocated the connection. - std::list consumed_connections_ GUARDED_BY(lock_); + std::list consumed_connections_ ABSL_GUARDED_BY(lock_); bool allow_unexpected_disconnects_; bool read_disable_on_new_connection_; const bool enable_half_close_; diff --git a/test/integration/filters/pause_filter.cc b/test/integration/filters/pause_filter.cc index 8ed74d4198..c6463ef27b 100644 --- a/test/integration/filters/pause_filter.cc +++ b/test/integration/filters/pause_filter.cc @@ -66,7 +66,8 @@ class TestPauseFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpF Http::FilterFactoryCb createFilter(const std::string&, Server::Configuration::FactoryContext&) override { return [&](Http::FilterChainFactoryCallbacks& callbacks) -> void { - // GUARDED_BY insists the lock be held when the guarded variables are passed by reference. + // ABSL_GUARDED_BY insists the lock be held when the guarded variables are passed by + // reference. absl::WriterMutexLock m(&encode_lock_); callbacks.addStreamFilter(std::make_shared<::Envoy::TestPauseFilter>( encode_lock_, number_of_encode_calls_, number_of_decode_calls_)); @@ -74,8 +75,8 @@ class TestPauseFilterConfig : public Extensions::HttpFilters::Common::EmptyHttpF } absl::Mutex encode_lock_; - uint32_t number_of_encode_calls_ GUARDED_BY(encode_lock_) = 0; - uint32_t number_of_decode_calls_ GUARDED_BY(encode_lock_) = 0; + uint32_t number_of_encode_calls_ ABSL_GUARDED_BY(encode_lock_) = 0; + uint32_t number_of_decode_calls_ ABSL_GUARDED_BY(encode_lock_) = 0; }; // perform static registration diff --git a/test/integration/filters/process_context_filter.cc b/test/integration/filters/process_context_filter.cc index 6765042b2c..e3394dff00 100644 --- a/test/integration/filters/process_context_filter.cc +++ b/test/integration/filters/process_context_filter.cc @@ -44,7 +44,7 @@ class ProcessContextFilterConfig : public Extensions::HttpFilters::Common::Empty Server::Configuration::FactoryContext& factory_context) override { return [&factory_context](Http::FilterChainFactoryCallbacks& callbacks) { callbacks.addStreamFilter( - std::make_shared(factory_context.processContext())); + std::make_shared(*factory_context.processContext())); }; } }; diff --git a/test/integration/filters/random_pause_filter.cc b/test/integration/filters/random_pause_filter.cc index b709da758c..24609cf704 100644 --- a/test/integration/filters/random_pause_filter.cc +++ b/test/integration/filters/random_pause_filter.cc @@ -62,7 +62,7 @@ class RandomPauseFilterConfig : public Extensions::HttpFilters::Common::EmptyHtt } absl::Mutex rand_lock_; - std::unique_ptr rng_ GUARDED_BY(rand_lock_); + std::unique_ptr rng_ ABSL_GUARDED_BY(rand_lock_); }; // perform static registration diff --git a/test/integration/http2_integration_test.cc b/test/integration/http2_integration_test.cc index 197522b3cd..1dbe5cd37f 100644 --- a/test/integration/http2_integration_test.cc +++ b/test/integration/http2_integration_test.cc @@ -1433,7 +1433,7 @@ const int64_t TransmitThreshold = 100 * 1024 * 1024; void Http2FloodMitigationTest::setNetworkConnectionBufferSize() { // nghttp2 library has its own internal mitigation for outbound control frames. The mitigation is - // trigerred when there are more than 10000 PING or SETTINGS frames with ACK flag in the nghttp2 + // triggered when there are more than 10000 PING or SETTINGS frames with ACK flag in the nghttp2 // internal outbound queue. It is possible to trigger this mitigation in nghttp2 before triggering // Envoy's own flood mitigation. This can happen when a buffer larger enough to contain over 10K // PING or SETTINGS frames is dispatched to the nghttp2 library. To prevent this from happening diff --git a/test/integration/http2_upstream_integration_test.cc b/test/integration/http2_upstream_integration_test.cc index 15303a0a6f..5854164ed3 100644 --- a/test/integration/http2_upstream_integration_test.cc +++ b/test/integration/http2_upstream_integration_test.cc @@ -10,8 +10,6 @@ #include "gtest/gtest.h" -using testing::HasSubstr; - namespace Envoy { INSTANTIATE_TEST_SUITE_P(IpVersions, Http2UpstreamIntegrationTest, diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index fbd7308fd4..be622d93f3 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -34,12 +34,6 @@ #include "gtest/gtest.h" -using testing::_; -using testing::AnyNumber; -using testing::HasSubstr; -using testing::Invoke; -using testing::Not; - namespace Envoy { namespace { @@ -304,6 +298,40 @@ void HttpIntegrationTest::cleanupUpstreamAndDownstream() { } } +void HttpIntegrationTest::sendRequestAndVerifyResponse( + const Http::TestHeaderMapImpl& request_headers, const int request_size, + const Http::TestHeaderMapImpl& response_headers, const int response_size, + const int backend_idx) { + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = sendRequestAndWaitForResponse(request_headers, request_size, response_headers, + response_size, backend_idx); + verifyResponse(std::move(response), "200", response_headers, std::string(response_size, 'a')); + + EXPECT_TRUE(upstream_request_->complete()); + EXPECT_EQ(request_size, upstream_request_->bodyLength()); + cleanupUpstreamAndDownstream(); +} + +void HttpIntegrationTest::verifyResponse(IntegrationStreamDecoderPtr response, + const std::string& response_code, + const Http::TestHeaderMapImpl& expected_headers, + const std::string& expected_body) { + EXPECT_TRUE(response->complete()); + EXPECT_EQ(response_code, response->headers().Status()->value().getStringView()); + expected_headers.iterate( + [](const Http::HeaderEntry& header, void* context) -> Http::HeaderMap::Iterate { + auto response_headers = static_cast(context); + const Http::HeaderEntry* entry = + response_headers->get(Http::LowerCaseString{std::string(header.key().getStringView())}); + EXPECT_NE(entry, nullptr); + EXPECT_EQ(header.value().getStringView(), entry->value().getStringView()); + return Http::HeaderMap::Iterate::Continue; + }, + const_cast(static_cast(&response->headers()))); + + EXPECT_EQ(response->body(), expected_body); +} + uint64_t HttpIntegrationTest::waitForNextUpstreamRequest(const std::vector& upstream_indices) { uint64_t upstream_with_request; @@ -755,7 +783,7 @@ void HttpIntegrationTest::testEnvoyProxying100Continue(bool continue_before_upst auto* virtual_host = route_config->mutable_virtual_hosts(0); { auto* cors = virtual_host->mutable_cors(); - cors->add_allow_origin("*"); + cors->mutable_allow_origin_string_match()->Add()->set_exact("*"); cors->set_allow_headers("content-type,x-grpc-web"); cors->set_allow_methods("GET,POST"); } diff --git a/test/integration/http_integration.h b/test/integration/http_integration.h index bbeda04491..1a2193556e 100644 --- a/test/integration/http_integration.h +++ b/test/integration/http_integration.h @@ -138,6 +138,20 @@ class HttpIntegrationTest : public BaseIntegrationTest { // Close |codec_client_| and |fake_upstream_connection_| cleanly. void cleanupUpstreamAndDownstream(); + // Verifies the response_headers contains the expected_headers, and response body matches given + // body string. + void verifyResponse(IntegrationStreamDecoderPtr response, const std::string& response_code, + const Http::TestHeaderMapImpl& expected_headers, + const std::string& expected_body); + + // Helper that sends a request to Envoy, and verifies if Envoy response headers and body size is + // the same as the expected headers map. + // Requires the "http" port has been registered. + void sendRequestAndVerifyResponse(const Http::TestHeaderMapImpl& request_headers, + const int request_size, + const Http::TestHeaderMapImpl& response_headers, + const int response_size, const int backend_idx); + // Check for completion of upstream_request_, and a simple "200" response. void checkSimpleRequestSuccess(uint64_t expected_request_size, uint64_t expected_response_size, IntegrationStreamDecoder* response); diff --git a/test/integration/http_subset_lb_integration_test.cc b/test/integration/http_subset_lb_integration_test.cc new file mode 100644 index 0000000000..10b882feef --- /dev/null +++ b/test/integration/http_subset_lb_integration_test.cc @@ -0,0 +1,202 @@ +#include "test/integration/http_integration.h" + +#include "absl/strings/str_replace.h" +#include "gtest/gtest.h" + +namespace Envoy { + +class HttpSubsetLbIntegrationTest : public testing::TestWithParam, + public HttpIntegrationTest { +public: + // Returns all load balancer types except ORIGINAL_DST_LB and CLUSTER_PROVIDED. + static std::vector getSubsetLbTestParams() { + int first = static_cast(envoy::api::v2::Cluster_LbPolicy_LbPolicy_MIN); + int last = static_cast(envoy::api::v2::Cluster_LbPolicy_LbPolicy_MAX); + ASSERT(first < last); + + std::vector ret; + for (int i = first; i <= last; i++) { + if (!envoy::api::v2::Cluster_LbPolicy_IsValid(i)) { + continue; + } + + auto policy = static_cast(i); + + if (policy == envoy::api::v2::Cluster_LbPolicy_ORIGINAL_DST_LB || + policy == envoy::api::v2::Cluster_LbPolicy_CLUSTER_PROVIDED || + policy == envoy::api::v2::Cluster_LbPolicy_LOAD_BALANCING_POLICY_CONFIG) { + continue; + } + + ret.push_back(policy); + } + + return ret; + } + + // Converts an LbPolicy to strings suitable for test names. + static std::string + subsetLbTestParamsToString(const testing::TestParamInfo& p) { + const std::string& policy_name = envoy::api::v2::Cluster_LbPolicy_Name(p.param); + return absl::StrReplaceAll(policy_name, {{"_", ""}}); + } + + HttpSubsetLbIntegrationTest() + : HttpIntegrationTest(Http::CodecClient::Type::HTTP1, Network::Address::IpVersion::v4, + ConfigHelper::HTTP_PROXY_CONFIG), + num_hosts_{4}, is_hash_lb_(GetParam() == envoy::api::v2::Cluster_LbPolicy_RING_HASH || + GetParam() == envoy::api::v2::Cluster_LbPolicy_MAGLEV) { + autonomous_upstream_ = true; + setUpstreamCount(num_hosts_); + + config_helper_.addConfigModifier([&](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + auto* static_resources = bootstrap.mutable_static_resources(); + auto* cluster = static_resources->mutable_clusters(0); + + cluster->set_lb_policy(GetParam()); + + // Create subsets based on type value of the "type" metadata. + cluster->mutable_lb_subset_config()->add_subset_selectors()->add_keys(type_key_); + + cluster->clear_hosts(); + + // Create a load assignment with num_hosts_ entries with metadata split evenly between type=a + // and type=b. + auto* load_assignment = cluster->mutable_load_assignment(); + load_assignment->set_cluster_name(cluster->name()); + auto* endpoints = load_assignment->add_endpoints(); + for (uint32_t i = 0; i < num_hosts_; i++) { + auto* lb_endpoint = endpoints->add_lb_endpoints(); + + // ConfigHelper will fill in ports later. + auto* endpoint = lb_endpoint->mutable_endpoint(); + auto* addr = endpoint->mutable_address()->mutable_socket_address(); + addr->set_address("127.0.0.1"); + addr->set_port_value(0); + + // Assign type metadata based on i. + auto* metadata = lb_endpoint->mutable_metadata(); + Envoy::Config::Metadata::mutableMetadataValue(*metadata, "envoy.lb", type_key_) + .set_string_value((i % 2 == 0) ? "a" : "b"); + } + }); + + config_helper_.addConfigModifier( + [&](envoy::config::filter::network::http_connection_manager::v2::HttpConnectionManager& + hcm) { + auto* vhost = hcm.mutable_route_config()->mutable_virtual_hosts(0); + + // Report the host's type metadata and remote address on every response. + auto* resp_header = vhost->add_response_headers_to_add(); + auto* header = resp_header->mutable_header(); + header->set_key(host_type_header_); + header->set_value( + fmt::format(R"EOF(%UPSTREAM_METADATA(["envoy.lb", "{}"])%)EOF", type_key_)); + + resp_header = vhost->add_response_headers_to_add(); + header = resp_header->mutable_header(); + header->set_key(host_header_); + header->set_value("%UPSTREAM_REMOTE_ADDRESS%"); + + // Create routes for x-type=a and x-type=b headers. + vhost->clear_routes(); + configureRoute(vhost->add_routes(), "a"); + configureRoute(vhost->add_routes(), "b"); + }); + } + + void configureRoute(envoy::api::v2::route::Route* route, const std::string& host_type) { + auto* match = route->mutable_match(); + match->set_prefix("/"); + + // Match the x-type header against the given host_type (a/b). + auto* match_header = match->add_headers(); + match_header->set_name(type_header_); + match_header->set_exact_match(host_type); + + // Route to cluster_0, selecting metadata type=a or type=b. + auto* action = route->mutable_route(); + action->set_cluster("cluster_0"); + auto* metadata_match = action->mutable_metadata_match(); + Envoy::Config::Metadata::mutableMetadataValue(*metadata_match, "envoy.lb", type_key_) + .set_string_value(host_type); + + // Set a hash policy for hashing load balancers. + if (is_hash_lb_) { + action->add_hash_policy()->mutable_header()->set_header_name(hash_header_); + } + }; + + void SetUp() override { + setDownstreamProtocol(Http::CodecClient::Type::HTTP1); + setUpstreamProtocol(FakeHttpConnection::Type::HTTP1); + } + + // Runs a subset lb test with the given request headers, expecting the x-host-type header to + // the given type ("a" or "b"). If is_hash_lb_, verifies that a single host is selected over n + // iterations (e.g. for maglev/hash-ring policies). Otherwise, expected more than one host to be + // selected over n iterations. + void runTest(Http::TestHeaderMapImpl& request_headers, const std::string expected_host_type, + const int n = 10) { + std::set hosts; + for (int i = 0; i < n; i++) { + Http::TestHeaderMapImpl response_headers{{":status", "200"}}; + + // Send header only request. + IntegrationStreamDecoderPtr response = codec_client_->makeHeaderOnlyRequest(request_headers); + response->waitForEndStream(); + + // Expect a response from a host in the correct subset. + EXPECT_EQ(response->headers() + .get(Envoy::Http::LowerCaseString{host_type_header_}) + ->value() + .getStringView(), + expected_host_type); + + // Record the upstream address. + hosts.emplace(response->headers() + .get(Envoy::Http::LowerCaseString{host_header_}) + ->value() + .getStringView()); + } + + if (is_hash_lb_) { + EXPECT_EQ(hosts.size(), 1) << "Expected a single unique host to be selected for " + << envoy::api::v2::Cluster_LbPolicy_Name(GetParam()); + } else { + EXPECT_GT(hosts.size(), 1) << "Expected multiple hosts to be selected for " + << envoy::api::v2::Cluster_LbPolicy_Name(GetParam()); + } + } + + const uint32_t num_hosts_; + const bool is_hash_lb_; + + const std::string hash_header_{"x-hash"}; + const std::string host_type_header_{"x-host-type"}; + const std::string host_header_{"x-host"}; + const std::string type_header_{"x-type"}; + const std::string type_key_{"type"}; + + Http::TestHeaderMapImpl type_a_request_headers_{{":method", "GET"}, {":path", "/test"}, + {":scheme", "http"}, {":authority", "host"}, + {"x-type", "a"}, {"x-hash", "hash-a"}}; + Http::TestHeaderMapImpl type_b_request_headers_{{":method", "GET"}, {":path", "/test"}, + {":scheme", "http"}, {":authority", "host"}, + {"x-type", "b"}, {"x-hash", "hash-b"}}; +}; + +INSTANTIATE_TEST_SUITE_P(SubsetCompatibleLoadBalancers, HttpSubsetLbIntegrationTest, + testing::ValuesIn(HttpSubsetLbIntegrationTest::getSubsetLbTestParams()), + HttpSubsetLbIntegrationTest::subsetLbTestParamsToString); + +// Tests each subset-compatible load balancer policy with 4 hosts divided into 2 subsets. +TEST_P(HttpSubsetLbIntegrationTest, SubsetLoadBalancer) { + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + runTest(type_a_request_headers_, "a"); + runTest(type_b_request_headers_, "b"); +} + +} // namespace Envoy diff --git a/test/integration/integration.cc b/test/integration/integration.cc index 5fbcc86b71..d25f5c35d1 100644 --- a/test/integration/integration.cc +++ b/test/integration/integration.cc @@ -441,7 +441,8 @@ void BaseIntegrationTest::createGeneratedApiTestServer(const std::string& bootst if (!allow_lds_rejection) { RELEASE_ASSERT(test_server_->counter(rejected) == nullptr || test_server_->counter(rejected)->value() == 0, - "Lds update failed"); + "Lds update failed. For details, run test with -l trace and look for " + "\"Error adding/updating listener(s)\" in the logs."); } time_system_.sleep(std::chrono::milliseconds(10)); } @@ -523,9 +524,9 @@ void BaseIntegrationTest::createXdsUpstream() { auto cfg = std::make_unique( tls_context, factory_context_); - static Stats::Scope* upstream_stats_store = new Stats::TestIsolatedStoreImpl(); + upstream_stats_store_ = std::make_unique(); auto context = std::make_unique( - std::move(cfg), context_manager_, *upstream_stats_store, std::vector{}); + std::move(cfg), context_manager_, *upstream_stats_store_, std::vector{}); fake_upstreams_.emplace_back(new FakeUpstream( std::move(context), 0, FakeHttpConnection::Type::HTTP2, version_, timeSystem())); } diff --git a/test/integration/integration.h b/test/integration/integration.h index 4903edba34..2512114290 100644 --- a/test/integration/integration.h +++ b/test/integration/integration.h @@ -173,6 +173,9 @@ class BaseIntegrationTest : protected Logger::Loggable { void setUpstreamProtocol(FakeHttpConnection::Type protocol); // Sets fake_upstreams_count_ and alters the upstream protocol in the config_helper_ void setUpstreamCount(uint32_t count) { fake_upstreams_count_ = count; } + // Skip validation that ensures that all upstream ports are referenced by the + // configuration generated in ConfigHelper::finalize. + void skipPortUsageValidation() { config_helper_.skipPortUsageValidation(); } // Make test more deterministic by using a fixed RNG value. void setDeterministic() { deterministic_ = true; } @@ -327,6 +330,8 @@ class BaseIntegrationTest : protected Logger::Loggable { bool initialized() const { return initialized_; } + std::unique_ptr upstream_stats_store_; + // The IpVersion (IPv4, IPv6) to use. Network::Address::IpVersion version_; // IP Address to use when binding sockets on upstreams. diff --git a/test/integration/integration_admin_test.cc b/test/integration/integration_admin_test.cc index 2af1c1f069..41085b762d 100644 --- a/test/integration/integration_admin_test.cc +++ b/test/integration/integration_admin_test.cc @@ -500,9 +500,14 @@ TEST_F(IntegrationAdminIpv4Ipv6Test, Ipv4Ipv6Listen) { // Testing the behavior of StatsMatcher, which allows/denies the instantiation of stats based on // restrictions on their names. +// +// Note: using 'Event::TestUsingSimulatedTime' appears to conflict with LDS in +// StatsMatcherIntegrationTest.IncludeExact, which manifests in a coverage test +// crash, which is really difficult to debug. See #7215. It's possible this is +// due to a bad interaction between the wait-for constructs in the integration +// test framework with sim-time. class StatsMatcherIntegrationTest : public testing::Test, - public Event::TestUsingSimulatedTime, public HttpIntegrationTest, public testing::WithParamInterface { public: @@ -537,21 +542,21 @@ TEST_P(StatsMatcherIntegrationTest, ExcludePrefixServerDot) { EXPECT_THAT(response_->body(), testing::Not(testing::HasSubstr("server."))); } -TEST_P(StatsMatcherIntegrationTest, ExcludeRequests) { +TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeRequests)) { stats_matcher_.mutable_exclusion_list()->add_patterns()->set_regex(".*requests.*"); initialize(); makeRequest(); EXPECT_THAT(response_->body(), testing::Not(testing::HasSubstr("requests"))); } -TEST_P(StatsMatcherIntegrationTest, ExcludeExact) { +TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeExact)) { stats_matcher_.mutable_exclusion_list()->add_patterns()->set_exact("server.concurrency"); initialize(); makeRequest(); EXPECT_THAT(response_->body(), testing::Not(testing::HasSubstr("server.concurrency"))); } -TEST_P(StatsMatcherIntegrationTest, ExcludeMultipleExact) { +TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(ExcludeMultipleExact)) { stats_matcher_.mutable_exclusion_list()->add_patterns()->set_exact("server.concurrency"); stats_matcher_.mutable_exclusion_list()->add_patterns()->set_regex(".*live"); initialize(); @@ -564,7 +569,7 @@ TEST_P(StatsMatcherIntegrationTest, ExcludeMultipleExact) { // `listener_manager.listener_create_success` must be instantiated, because BaseIntegrationTest // blocks on its creation (see waitForCounterGe and the suite of waitFor* functions). // If this invariant is changed, this test must be rewritten. -TEST_P(StatsMatcherIntegrationTest, IncludeExact) { +TEST_P(StatsMatcherIntegrationTest, DEPRECATED_FEATURE_TEST(IncludeExact)) { // Stats matching does not play well with LDS, at least in test. See #7215. use_lds_ = false; stats_matcher_.mutable_inclusion_list()->add_patterns()->set_exact( diff --git a/test/integration/integration_test.cc b/test/integration/integration_test.cc index 7ad5e33970..74a487a25e 100644 --- a/test/integration/integration_test.cc +++ b/test/integration/integration_test.cc @@ -23,7 +23,6 @@ using Envoy::Http::HeaderValueOf; using Envoy::Http::HttpStatusIs; using testing::EndsWith; using testing::HasSubstr; -using testing::MatchesRegex; using testing::Not; namespace Envoy { diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index 5d6bd41de4..545e176863 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -33,11 +33,7 @@ #include "gtest/gtest.h" -using testing::_; -using testing::AnyNumber; using testing::HasSubstr; -using testing::Invoke; -using testing::Not; namespace Envoy { diff --git a/test/integration/scoped_rds_integration_test.cc b/test/integration/scoped_rds_integration_test.cc index 32e916da6d..5f4bb27395 100644 --- a/test/integration/scoped_rds_integration_test.cc +++ b/test/integration/scoped_rds_integration_test.cc @@ -4,6 +4,7 @@ #include "test/common/grpc/grpc_client_integration.h" #include "test/integration/http_integration.h" +#include "test/test_common/printers.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -12,12 +13,12 @@ namespace Envoy { namespace { class ScopedRdsIntegrationTest : public HttpIntegrationTest, - public Grpc::GrpcClientIntegrationParamTest { + public Grpc::DeltaSotwGrpcClientIntegrationParamTest { protected: struct FakeUpstreamInfo { FakeHttpConnectionPtr connection_; FakeUpstream* upstream_{}; - FakeStreamPtr stream_; + absl::flat_hash_map stream_by_resource_name_; }; ScopedRdsIntegrationTest() @@ -29,7 +30,15 @@ class ScopedRdsIntegrationTest : public HttpIntegrationTest, } void initialize() override { + // Setup two upstream hosts, one for each cluster. + setUpstreamCount(2); + config_helper_.addConfigModifier([](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { + // Add the static cluster to serve SRDS. + auto* cluster_1 = bootstrap.mutable_static_resources()->add_clusters(); + cluster_1->MergeFrom(bootstrap.static_resources().clusters()[0]); + cluster_1->set_name("cluster_1"); + // Add the static cluster to serve SRDS. auto* scoped_rds_cluster = bootstrap.mutable_static_resources()->add_clusters(); scoped_rds_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); @@ -48,13 +57,18 @@ class ScopedRdsIntegrationTest : public HttpIntegrationTest, http_connection_manager) { const std::string& scope_key_builder_config_yaml = R"EOF( fragments: - - header_value_extractor: { name: X-Google-VIP } + - header_value_extractor: + name: Addr + element_separator: ; + element: + key: x-foo-key + separator: = )EOF"; envoy::config::filter::network::http_connection_manager::v2::ScopedRoutes::ScopeKeyBuilder scope_key_builder; TestUtility::loadFromYaml(scope_key_builder_config_yaml, scope_key_builder); auto* scoped_routes = http_connection_manager.mutable_scoped_routes(); - scoped_routes->set_name("foo-scoped-routes"); + scoped_routes->set_name(srds_config_name_); *scoped_routes->mutable_scope_key_builder() = scope_key_builder; envoy::api::v2::core::ApiConfigSource* rds_api_config_source = @@ -68,7 +82,11 @@ class ScopedRdsIntegrationTest : public HttpIntegrationTest, scoped_routes->mutable_scoped_rds() ->mutable_scoped_rds_config_source() ->mutable_api_config_source(); - srds_api_config_source->set_api_type(envoy::api::v2::core::ApiConfigSource::GRPC); + if (isDelta()) { + srds_api_config_source->set_api_type(envoy::api::v2::core::ApiConfigSource::DELTA_GRPC); + } else { + srds_api_config_source->set_api_type(envoy::api::v2::core::ApiConfigSource::GRPC); + } grpc_service = srds_api_config_source->add_grpc_services(); setGrpcService(*grpc_service, "srds_cluster", getScopedRdsFakeUpstream().localAddress()); }); @@ -104,29 +122,87 @@ class ScopedRdsIntegrationTest : public HttpIntegrationTest, resetFakeUpstreamInfo(&scoped_rds_upstream_info_); } - FakeUpstream& getRdsFakeUpstream() const { return *fake_upstreams_[2]; } + FakeUpstream& getRdsFakeUpstream() const { return *fake_upstreams_[3]; } - FakeUpstream& getScopedRdsFakeUpstream() const { return *fake_upstreams_[1]; } + FakeUpstream& getScopedRdsFakeUpstream() const { return *fake_upstreams_[2]; } - void createStream(FakeUpstreamInfo* upstream_info, FakeUpstream& upstream) { - upstream_info->upstream_ = &upstream; - AssertionResult result = - upstream_info->upstream_->waitForHttpConnection(*dispatcher_, upstream_info->connection_); - RELEASE_ASSERT(result, result.message()); - result = upstream_info->connection_->waitForNewStream(*dispatcher_, upstream_info->stream_); + void createStream(FakeUpstreamInfo* upstream_info, FakeUpstream& upstream, + const std::string& resource_name) { + if (upstream_info->upstream_ == nullptr) { + // bind upstream if not yet. + upstream_info->upstream_ = &upstream; + AssertionResult result = + upstream_info->upstream_->waitForHttpConnection(*dispatcher_, upstream_info->connection_); + RELEASE_ASSERT(result, result.message()); + } + if (!upstream_info->stream_by_resource_name_.try_emplace(resource_name, nullptr).second) { + RELEASE_ASSERT(false, + fmt::format("stream with resource name '{}' already exists!", resource_name)); + } + auto result = upstream_info->connection_->waitForNewStream( + *dispatcher_, upstream_info->stream_by_resource_name_[resource_name]); RELEASE_ASSERT(result, result.message()); - upstream_info->stream_->startGrpcStream(); + upstream_info->stream_by_resource_name_[resource_name]->startGrpcStream(); } - void createRdsStream() { createStream(&rds_upstream_info_, getRdsFakeUpstream()); } + void createRdsStream(const std::string& resource_name) { + createStream(&rds_upstream_info_, getRdsFakeUpstream(), resource_name); + } void createScopedRdsStream() { - createStream(&scoped_rds_upstream_info_, getScopedRdsFakeUpstream()); + createStream(&scoped_rds_upstream_info_, getScopedRdsFakeUpstream(), srds_config_name_); + } + + void sendRdsResponse(const std::string& route_config, const std::string& version) { + envoy::api::v2::DiscoveryResponse response; + response.set_version_info(version); + response.set_type_url(Config::TypeUrl::get().RouteConfiguration); + auto route_configuration = + TestUtility::parseYaml(route_config); + response.add_resources()->PackFrom(route_configuration); + ASSERT(rds_upstream_info_.stream_by_resource_name_[route_configuration.name()] != nullptr); + rds_upstream_info_.stream_by_resource_name_[route_configuration.name()]->sendGrpcMessage( + response); + } + + void sendSrdsResponse(const std::vector& sotw_list, + const std::vector& to_add_list, + const std::vector& to_delete_list, + const std::string& version) { + if (isDelta()) { + sendDeltaScopedRdsResponse(to_add_list, to_delete_list, version); + } else { + sendSotwScopedRdsResponse(sotw_list, version); + } + } + + void sendDeltaScopedRdsResponse(const std::vector& to_add_list, + const std::vector& to_delete_list, + const std::string& version) { + ASSERT(scoped_rds_upstream_info_.stream_by_resource_name_[srds_config_name_] != nullptr); + + envoy::api::v2::DeltaDiscoveryResponse response; + response.set_system_version_info(version); + response.set_type_url(Config::TypeUrl::get().ScopedRouteConfiguration); + + for (const auto& scope_name : to_delete_list) { + *response.add_removed_resources() = scope_name; + } + for (const auto& resource_proto : to_add_list) { + envoy::api::v2::ScopedRouteConfiguration scoped_route_proto; + TestUtility::loadFromYaml(resource_proto, scoped_route_proto); + auto resource = response.add_resources(); + resource->set_name(scoped_route_proto.name()); + resource->set_version(version); + resource->mutable_resource()->PackFrom(scoped_route_proto); + } + scoped_rds_upstream_info_.stream_by_resource_name_[srds_config_name_]->sendGrpcMessage( + response); } - void sendScopedRdsResponse(const std::vector& resource_protos, - const std::string& version) { - ASSERT(scoped_rds_upstream_info_.stream_ != nullptr); + void sendSotwScopedRdsResponse(const std::vector& resource_protos, + const std::string& version) { + ASSERT(scoped_rds_upstream_info_.stream_by_resource_name_[srds_config_name_] != nullptr); envoy::api::v2::DiscoveryResponse response; response.set_version_info(version); @@ -137,62 +213,163 @@ class ScopedRdsIntegrationTest : public HttpIntegrationTest, TestUtility::loadFromYaml(resource_proto, scoped_route_proto); response.add_resources()->PackFrom(scoped_route_proto); } - - scoped_rds_upstream_info_.stream_->sendGrpcMessage(response); + scoped_rds_upstream_info_.stream_by_resource_name_[srds_config_name_]->sendGrpcMessage( + response); } + const std::string srds_config_name_{"foo-scoped-routes"}; FakeUpstreamInfo scoped_rds_upstream_info_; FakeUpstreamInfo rds_upstream_info_; }; INSTANTIATE_TEST_SUITE_P(IpVersionsAndGrpcTypes, ScopedRdsIntegrationTest, - GRPC_CLIENT_INTEGRATION_PARAMS); + DELTA_SOTW_GRPC_CLIENT_INTEGRATION_PARAMS); // Test that a SRDS DiscoveryResponse is successfully processed. TEST_P(ScopedRdsIntegrationTest, BasicSuccess) { - const std::string scope_route1 = R"EOF( -name: foo_scope1 -route_configuration_name: foo_route1 + const std::string scope_tmpl = R"EOF( +name: {} +route_configuration_name: {} key: fragments: - - string_key: x-foo-key + - string_key: {} )EOF"; - const std::string scope_route2 = R"EOF( -name: foo_scope2 -route_configuration_name: foo_route2 -key: - fragments: - - string_key: x-foo-key + const std::string scope_route1 = fmt::format(scope_tmpl, "foo_scope1", "foo_route1", "foo-route"); + const std::string scope_route2 = fmt::format(scope_tmpl, "foo_scope2", "foo_route1", "bar-route"); + + const std::string route_config_tmpl = R"EOF( + name: {} + virtual_hosts: + - name: integration + domains: ["*"] + routes: + - match: {{ prefix: "/" }} + route: {{ cluster: {} }} )EOF"; - on_server_init_function_ = [this, &scope_route1, &scope_route2]() { + on_server_init_function_ = [&]() { createScopedRdsStream(); - sendScopedRdsResponse({scope_route1, scope_route2}, "1"); + sendSrdsResponse({scope_route1, scope_route2}, {scope_route1, scope_route2}, {}, "1"); + createRdsStream("foo_route1"); + // CreateRdsStream waits for connection which is fired by RDS subscription. + sendRdsResponse(fmt::format(route_config_tmpl, "foo_route1", "cluster_0"), "1"); }; initialize(); - - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_attempt", 1); + registerTestServerPorts({"http"}); + + // No scope key matches "xyz-route". + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = codec_client_->makeHeaderOnlyRequest( + Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", "x-foo-key=xyz-route"}}); + response->waitForEndStream(); + verifyResponse(std::move(response), "404", Http::TestHeaderMapImpl{}, ""); + cleanupUpstreamAndDownstream(); + + // Test "foo-route" and 'bar-route' both gets routed to cluster_0. + test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + for (const std::string& scope_key : std::vector{"foo-route", "bar-route"}) { + sendRequestAndVerifyResponse( + Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", fmt::format("x-foo-key={}", scope_key)}}, + 456, Http::TestHeaderMapImpl{{":status", "200"}, {"service", scope_key}}, 123, + /*cluster_0*/ 0); + } + test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_attempt", + // update_attempt only increase after a response + isDelta() ? 1 : 2); test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 1); // The version gauge should be set to xxHash64("1"). test_server_->waitForGaugeEq("http.config_test.scoped_rds.foo-scoped-routes.version", 13237225503670494420UL); - const std::string scope_route3 = R"EOF( -name: foo_scope3 -route_configuration_name: foo_route3 -key: - fragments: - - string_key: x-baz-key -)EOF"; - sendScopedRdsResponse({scope_route3}, "2"); - - test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_attempt", 2); + // Add a new scope scope_route3 with a brand new RouteConfiguration foo_route2. + const std::string scope_route3 = fmt::format(scope_tmpl, "foo_scope3", "foo_route2", "baz-route"); + + sendSrdsResponse({scope_route1, scope_route2, scope_route3}, /*added*/ {scope_route3}, {}, "2"); + test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_attempt", 2); + sendRdsResponse(fmt::format(route_config_tmpl, "foo_route1", "cluster_1"), "3"); + test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 2); + createRdsStream("foo_route2"); + test_server_->waitForCounterGe("http.config_test.rds.foo_route2.update_attempt", 1); + sendRdsResponse(fmt::format(route_config_tmpl, "foo_route2", "cluster_0"), "1"); + test_server_->waitForCounterGe("http.config_test.rds.foo_route2.update_success", 1); test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 2); + // The version gauge should be set to xxHash64("2"). test_server_->waitForGaugeEq("http.config_test.scoped_rds.foo-scoped-routes.version", 6927017134761466251UL); - - // TODO(AndresGuedez): test actual scoped routing logic; only the config handling is implemented - // at this point. + // After RDS update, requests within scope 'foo_scope1' or 'foo_scope2' get routed to + // 'cluster_1'. + for (const std::string& scope_key : std::vector{"foo-route", "bar-route"}) { + sendRequestAndVerifyResponse( + Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", fmt::format("x-foo-key={}", scope_key)}}, + 456, Http::TestHeaderMapImpl{{":status", "200"}, {"service", scope_key}}, 123, + /*cluster_1*/ 1); + } + // Now requests within scope 'foo_scope3' get routed to 'cluster_0'. + sendRequestAndVerifyResponse( + Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", fmt::format("x-foo-key={}", "baz-route")}}, + 456, Http::TestHeaderMapImpl{{":status", "200"}, {"service", "bluh"}}, 123, + /*cluster_0*/ 0); + + // Delete foo_scope1 and requests within the scope gets 400s. + sendSrdsResponse({scope_route2, scope_route3}, {}, {"foo_scope1"}, "3"); + test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 3); + codec_client_ = makeHttpConnection(lookupPort("http")); + response = codec_client_->makeHeaderOnlyRequest( + Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", "x-foo-key=foo-route"}}); + response->waitForEndStream(); + verifyResponse(std::move(response), "404", Http::TestHeaderMapImpl{}, ""); + cleanupUpstreamAndDownstream(); + // Add a new scope foo_scope4. + const std::string& scope_route4 = + fmt::format(scope_tmpl, "foo_scope4", "foo_route4", "xyz-route"); + sendSrdsResponse({scope_route3, scope_route2, scope_route4}, {scope_route4}, {}, "4"); + test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_success", 4); + codec_client_ = makeHttpConnection(lookupPort("http")); + response = codec_client_->makeHeaderOnlyRequest( + Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", "x-foo-key=xyz-route"}}); + response->waitForEndStream(); + // Get 404 because RDS hasn't pushed route configuration "foo_route4" yet. + // But scope is found and the Router::NullConfigImpl is returned. + verifyResponse(std::move(response), "404", Http::TestHeaderMapImpl{}, ""); + cleanupUpstreamAndDownstream(); + + // RDS updated foo_route4, requests with scope key "xyz-route" now hit cluster_1. + test_server_->waitForCounterGe("http.config_test.rds.foo_route4.update_attempt", 1); + createRdsStream("foo_route4"); + sendRdsResponse(fmt::format(route_config_tmpl, "foo_route4", "cluster_1"), "3"); + test_server_->waitForCounterGe("http.config_test.rds.foo_route4.update_success", 1); + sendRequestAndVerifyResponse( + Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", "x-foo-key=xyz-route"}}, + 456, Http::TestHeaderMapImpl{{":status", "200"}, {"service", "xyz-route"}}, 123, + /*cluster_1 */ 1); } // Test that a bad config update updates the corresponding stats. @@ -203,16 +380,56 @@ TEST_P(ScopedRdsIntegrationTest, ConfigUpdateFailure) { route_configuration_name: foo_route1 key: fragments: - - string_key: x-foo-key + - string_key: foo )EOF"; on_server_init_function_ = [this, &scope_route1]() { createScopedRdsStream(); - sendScopedRdsResponse({scope_route1}, "1"); + sendSrdsResponse({scope_route1}, {scope_route1}, {}, "1"); }; initialize(); test_server_->waitForCounterGe("http.config_test.scoped_rds.foo-scoped-routes.update_rejected", 1); + codec_client_ = makeHttpConnection(lookupPort("http")); + auto response = + codec_client_->makeHeaderOnlyRequest(Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", "x-foo-key=foo"}}); + response->waitForEndStream(); + verifyResponse(std::move(response), "404", Http::TestHeaderMapImpl{}, ""); + cleanupUpstreamAndDownstream(); + + // SRDS update fixed the problem. + const std::string scope_route2 = R"EOF( +name: foo_scope1 +route_configuration_name: foo_route1 +key: + fragments: + - string_key: foo +)EOF"; + sendSrdsResponse({scope_route2}, {scope_route2}, {}, "1"); + test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_attempt", 1); + createRdsStream("foo_route1"); + const std::string route_config_tmpl = R"EOF( + name: {} + virtual_hosts: + - name: integration + domains: ["*"] + routes: + - match: {{ prefix: "/" }} + route: {{ cluster: {} }} +)EOF"; + sendRdsResponse(fmt::format(route_config_tmpl, "foo_route1", "cluster_0"), "1"); + test_server_->waitForCounterGe("http.config_test.rds.foo_route1.update_success", 1); + sendRequestAndVerifyResponse( + Http::TestHeaderMapImpl{{":method", "GET"}, + {":path", "/meh"}, + {":authority", "host"}, + {":scheme", "http"}, + {"Addr", "x-foo-key=foo"}}, + 456, Http::TestHeaderMapImpl{{":status", "200"}, {"service", "bluh"}}, 123, /*cluster_0*/ 0); } } // namespace diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index d260bdf6f0..dc5867b0fa 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -28,9 +28,6 @@ #include "integration.h" #include "utility.h" -using testing::NiceMock; -using testing::Return; - namespace Envoy { namespace Ssl { diff --git a/test/integration/sds_static_integration_test.cc b/test/integration/sds_static_integration_test.cc index 453942faf0..cbbf3085e9 100644 --- a/test/integration/sds_static_integration_test.cc +++ b/test/integration/sds_static_integration_test.cc @@ -26,9 +26,6 @@ #include "integration.h" #include "utility.h" -using testing::NiceMock; -using testing::Return; - namespace Envoy { namespace Ssl { diff --git a/test/integration/server.cc b/test/integration/server.cc index 6161eeedf8..83777e19db 100644 --- a/test/integration/server.cc +++ b/test/integration/server.cc @@ -8,6 +8,7 @@ #include "common/common/thread.h" #include "common/local_info/local_info_impl.h" #include "common/network/utility.h" +#include "common/stats/symbol_table_creator.h" #include "common/stats/thread_local_store.h" #include "common/thread_local/thread_local_impl.h" @@ -187,10 +188,10 @@ void IntegrationTestServerImpl::createAndRunEnvoyServer( Runtime::RandomGeneratorPtr&& random_generator, absl::optional> process_object) { { - Stats::FakeSymbolTableImpl symbol_table; + Stats::SymbolTablePtr symbol_table = Stats::SymbolTableCreator::makeSymbolTable(); Server::HotRestartNopImpl restarter; ThreadLocal::InstanceImpl tls; - Stats::AllocatorImpl stats_allocator(symbol_table); + Stats::AllocatorImpl stats_allocator(*symbol_table); Stats::ThreadLocalStoreImpl stat_store(stats_allocator); std::unique_ptr process_context; if (process_object.has_value()) { diff --git a/test/integration/stats_integration_test.cc b/test/integration/stats_integration_test.cc index ceadf07bac..2118a0e1aa 100644 --- a/test/integration/stats_integration_test.cc +++ b/test/integration/stats_integration_test.cc @@ -7,6 +7,7 @@ #include "common/config/well_known_names.h" #include "common/memory/stats.h" +#include "common/stats/symbol_table_creator.h" #include "test/common/stats/stat_test_utility.h" #include "test/config/utility.h" @@ -145,9 +146,25 @@ class ClusterMemoryTestHelper : public BaseIntegrationTest { ClusterMemoryTestHelper() : BaseIntegrationTest(testing::TestWithParam::GetParam()) {} - static size_t computeMemory(int num_clusters) { + static size_t computeMemoryDelta(int initial_num_clusters, int initial_num_hosts, + int final_num_clusters, int final_num_hosts, bool allow_stats) { + // Use the same number of fake upstreams for both helpers in order to exclude memory overhead + // added by the fake upstreams. + int fake_upstreams_count = 1 + final_num_clusters * final_num_hosts; + + size_t initial_memory; + { + ClusterMemoryTestHelper helper; + helper.setUpstreamCount(fake_upstreams_count); + helper.skipPortUsageValidation(); + initial_memory = + helper.clusterMemoryHelper(initial_num_clusters, initial_num_hosts, allow_stats); + } + ClusterMemoryTestHelper helper; - return helper.clusterMemoryHelper(num_clusters, true); + helper.setUpstreamCount(fake_upstreams_count); + return helper.clusterMemoryHelper(final_num_clusters, final_num_hosts, allow_stats) - + initial_memory; } private: @@ -156,15 +173,26 @@ class ClusterMemoryTestHelper : public BaseIntegrationTest { * @param allow_stats if false, enable set_reject_all in stats_config * @return size_t the total memory allocated */ - size_t clusterMemoryHelper(int num_clusters, bool allow_stats) { + size_t clusterMemoryHelper(int num_clusters, int num_hosts, bool allow_stats) { Stats::TestUtil::MemoryTest memory_test; config_helper_.addConfigModifier([&](envoy::config::bootstrap::v2::Bootstrap& bootstrap) { if (!allow_stats) { bootstrap.mutable_stats_config()->mutable_stats_matcher()->set_reject_all(true); } - for (int i = 1; i < num_clusters; i++) { - auto* c = bootstrap.mutable_static_resources()->add_clusters(); - c->set_name(fmt::format("cluster_{}", i)); + for (int i = 1; i < num_clusters; ++i) { + auto* cluster = bootstrap.mutable_static_resources()->add_clusters(); + cluster->set_name(fmt::format("cluster_{}", i)); + } + + for (int i = 0; i < num_clusters; ++i) { + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(i); + for (int j = 0; j < num_hosts; ++j) { + auto* host = cluster->add_hosts(); + auto* socket_address = host->mutable_socket_address(); + socket_address->set_protocol(envoy::api::v2::core::SocketAddress::TCP); + socket_address->set_address("0.0.0.0"); + socket_address->set_port_value(80); + } } }); initialize(); @@ -172,19 +200,29 @@ class ClusterMemoryTestHelper : public BaseIntegrationTest { return memory_test.consumedBytes(); } }; -class ClusterMemoryTestRunner : public testing::TestWithParam {}; +class ClusterMemoryTestRunner : public testing::TestWithParam { +protected: + ClusterMemoryTestRunner() : save_use_fakes_(Stats::SymbolTableCreator::useFakeSymbolTables()) {} + ~ClusterMemoryTestRunner() override { + Stats::TestUtil::SymbolTableCreatorTestPeer::setUseFakeSymbolTables(save_use_fakes_); + } + +private: + const bool save_use_fakes_; +}; INSTANTIATE_TEST_SUITE_P(IpVersions, ClusterMemoryTestRunner, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); -TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { +TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithFakeSymbolTable) { + Stats::TestUtil::SymbolTableCreatorTestPeer::setUseFakeSymbolTables(true); + // A unique instance of ClusterMemoryTest allows for multiple runs of Envoy with // differing configuration. This is necessary for measuring the memory consumption // between the different instances within the same test. - const size_t m1 = ClusterMemoryTestHelper::computeMemory(1); - const size_t m1001 = ClusterMemoryTestHelper::computeMemory(1001); - const size_t m_per_cluster = (m1001 - m1) / 1000; + const size_t m1000 = ClusterMemoryTestHelper::computeMemoryDelta(1, 0, 1001, 0, true); + const size_t m_per_cluster = (m1000) / 1000; // Note: if you are increasing this golden value because you are adding a // stat, please confirm that this will be generally useful to most Envoy @@ -213,6 +251,7 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { // 2019/07/15 7555 42806 43000 static link libstdc++ in tests // 2019/07/24 7503 43030 44000 add upstream filters to clusters // 2019/08/13 7877 42838 44000 skip EdfScheduler creation if all host weights equal + // 2019/09/02 8118 42830 43000 Share symbol-tables in cluster/host stats. // Note: when adjusting this value: EXPECT_MEMORY_EQ is active only in CI // 'release' builds, where we control the platform and tool-chain. So you @@ -222,9 +261,74 @@ TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithStats) { // On a local clang8/libstdc++/linux flow, the memory usage was observed in // June 2019 to be 64 bytes higher than it is in CI/release. Your mileage may // vary. - EXPECT_MEMORY_EQ(m_per_cluster, 42838); // 104 bytes higher than a debug build. + EXPECT_MEMORY_EQ(m_per_cluster, 42830); // 104 bytes higher than a debug build. EXPECT_MEMORY_LE(m_per_cluster, 44000); } +TEST_P(ClusterMemoryTestRunner, MemoryLargeClusterSizeWithRealSymbolTable) { + Stats::TestUtil::SymbolTableCreatorTestPeer::setUseFakeSymbolTables(false); + + // A unique instance of ClusterMemoryTest allows for multiple runs of Envoy with + // differing configuration. This is necessary for measuring the memory consumption + // between the different instances within the same test. + const size_t m1000 = ClusterMemoryTestHelper::computeMemoryDelta(1, 0, 1001, 0, true); + const size_t m_per_cluster = (m1000) / 1000; + + // Note: if you are increasing this golden value because you are adding a + // stat, please confirm that this will be generally useful to most Envoy + // users. Otherwise you are adding to the per-cluster memory overhead, which + // will be significant for Envoy installations that are massively + // multi-tenant. + // + // History of golden values: + // + // Date PR Bytes Per Cluster Notes + // exact upper-bound + // ---------- ----- ----------------- ----- + // 2019/08/09 7882 35489 36000 Initial version + // 2019/09/02 8118 34585 34500 Share symbol-tables in cluster/host stats. + + // Note: when adjusting this value: EXPECT_MEMORY_EQ is active only in CI + // 'release' builds, where we control the platform and tool-chain. So you + // will need to find the correct value only after failing CI and looking + // at the logs. + // + // On a local clang8/libstdc++/linux flow, the memory usage was observed in + // June 2019 to be 64 bytes higher than it is in CI/release. Your mileage may + // vary. + EXPECT_MEMORY_EQ(m_per_cluster, 34585); // 104 bytes higher than a debug build. + EXPECT_MEMORY_LE(m_per_cluster, 36000); +} + +TEST_P(ClusterMemoryTestRunner, MemoryLargeHostSizeWithStats) { + Stats::TestUtil::SymbolTableCreatorTestPeer::setUseFakeSymbolTables(false); + + // A unique instance of ClusterMemoryTest allows for multiple runs of Envoy with + // differing configuration. This is necessary for measuring the memory consumption + // between the different instances within the same test. + const size_t m1000 = ClusterMemoryTestHelper::computeMemoryDelta(1, 1, 1, 1001, true); + const size_t m_per_host = (m1000) / 1000; + + // Note: if you are increasing this golden value because you are adding a + // stat, please confirm that this will be generally useful to most Envoy + // users. Otherwise you are adding to the per-host memory overhead, which + // will be significant for Envoy installations configured to talk to large + // numbers of hosts. + // + // History of golden values: + // + // Date PR Bytes Per Host Notes + // exact upper-bound + // ---------- ----- ----------------- ----- + // 2019/09/09 8189 2739 3100 Initial per-host memory snapshot + + // Note: when adjusting this value: EXPECT_MEMORY_EQ is active only in CI + // 'release' builds, where we control the platform and tool-chain. So you + // will need to find the correct value only after failing CI and looking + // at the logs. + EXPECT_MEMORY_EQ(m_per_host, 2739); + EXPECT_MEMORY_LE(m_per_host, 3100); +} + } // namespace } // namespace Envoy diff --git a/test/integration/vhds_integration_test.cc b/test/integration/vhds_integration_test.cc index 83c018b2ba..1276d5e817 100644 --- a/test/integration/vhds_integration_test.cc +++ b/test/integration/vhds_integration_test.cc @@ -19,10 +19,7 @@ #include "absl/synchronization/notification.h" #include "gtest/gtest.h" -using testing::AssertionFailure; using testing::AssertionResult; -using testing::AssertionSuccess; -using testing::IsSubstring; namespace Envoy { namespace { diff --git a/test/integration/websocket_integration_test.cc b/test/integration/websocket_integration_test.cc index 0a650a8731..9e0b490ef4 100644 --- a/test/integration/websocket_integration_test.cc +++ b/test/integration/websocket_integration_test.cc @@ -15,8 +15,6 @@ #include "absl/strings/str_cat.h" #include "gtest/gtest.h" -using testing::MatchesRegex; - namespace Envoy { namespace { diff --git a/test/mocks/access_log/mocks.cc b/test/mocks/access_log/mocks.cc index 33c7b022fd..9a3ff3f7cf 100644 --- a/test/mocks/access_log/mocks.cc +++ b/test/mocks/access_log/mocks.cc @@ -5,7 +5,6 @@ using testing::_; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace AccessLog { diff --git a/test/mocks/api/mocks.cc b/test/mocks/api/mocks.cc index 77f6a4463d..40204115f2 100644 --- a/test/mocks/api/mocks.cc +++ b/test/mocks/api/mocks.cc @@ -7,7 +7,7 @@ #include "gtest/gtest.h" using testing::_; -using testing::Return; +using testing::Invoke; namespace Envoy { namespace Api { @@ -26,7 +26,12 @@ Event::DispatcherPtr MockApi::allocateDispatcher(Buffer::WatermarkFactoryPtr&& w return Event::DispatcherPtr{allocateDispatcher_(std::move(watermark_factory), time_system_)}; } -MockOsSysCalls::MockOsSysCalls() = default; +MockOsSysCalls::MockOsSysCalls() { + ON_CALL(*this, close(_)).WillByDefault(Invoke([](int fd) { + const int rc = ::close(fd); + return SysCallIntResult{rc, errno}; + })); +} MockOsSysCalls::~MockOsSysCalls() = default; diff --git a/test/mocks/api/mocks.h b/test/mocks/api/mocks.h index a85587a626..f415023ba7 100644 --- a/test/mocks/api/mocks.h +++ b/test/mocks/api/mocks.h @@ -66,6 +66,8 @@ class MockOsSysCalls : public OsSysCallsImpl { MOCK_METHOD4(recv, SysCallSizeResult(int socket, void* buffer, size_t length, int flags)); MOCK_METHOD6(recvfrom, SysCallSizeResult(int sockfd, void* buffer, size_t length, int flags, struct sockaddr* addr, socklen_t* addrlen)); + MOCK_METHOD6(sendto, SysCallSizeResult(int sockfd, const void* buffer, size_t length, int flags, + const struct sockaddr* addr, socklen_t addrlen)); MOCK_METHOD3(recvmsg, SysCallSizeResult(int socket, struct msghdr* msg, int flags)); MOCK_METHOD2(ftruncate, SysCallIntResult(int fd, off_t length)); MOCK_METHOD6(mmap, SysCallPtrResult(void* addr, size_t length, int prot, int flags, int fd, diff --git a/test/mocks/common.h b/test/mocks/common.h index 22f9fcafa4..c8cda0e447 100644 --- a/test/mocks/common.h +++ b/test/mocks/common.h @@ -56,7 +56,7 @@ class MockTimeSystem : public Event::TestTimeSystem { void sleep(const Duration& duration) override { real_time_.sleep(duration); } Thread::CondVar::WaitStatus waitFor(Thread::MutexBasicLockable& mutex, Thread::CondVar& condvar, - const Duration& duration) noexcept EXCLUSIVE_LOCKS_REQUIRED(mutex) override { + const Duration& duration) noexcept ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) override { return real_time_.waitFor(mutex, condvar, duration); // NO_CHECK_FORMAT(real_time) } MOCK_METHOD0(systemTime, SystemTime()); @@ -88,6 +88,7 @@ inline bool operator==(const StringViewSaver& saver, const char* str) { } class MockScopedTrackedObject : public ScopeTrackedObject { +public: MOCK_CONST_METHOD2(dumpState, void(std::ostream&, int)); }; diff --git a/test/mocks/event/mocks.cc b/test/mocks/event/mocks.cc index 0cd2f11d33..af00b66b06 100644 --- a/test/mocks/event/mocks.cc +++ b/test/mocks/event/mocks.cc @@ -27,7 +27,11 @@ MockDispatcher::MockDispatcher() { MockDispatcher::~MockDispatcher() = default; MockTimer::MockTimer() { - ON_CALL(*this, enableTimer(_)).WillByDefault(Assign(&enabled_, true)); + ON_CALL(*this, enableTimer(_, _)) + .WillByDefault(Invoke([&](const std::chrono::milliseconds&, const ScopeTrackedObject* scope) { + enabled_ = true; + scope_ = scope; + })); ON_CALL(*this, disableTimer()).WillByDefault(Assign(&enabled_, false)); ON_CALL(*this, enabled()).WillByDefault(ReturnPointee(&enabled_)); } @@ -36,6 +40,7 @@ MockTimer::MockTimer() { // createTimer_(), so to avoid destructing it twice, the MockTimer must have been dynamically // allocated and must not be deleted by it's creator. MockTimer::MockTimer(MockDispatcher* dispatcher) : MockTimer() { + dispatcher_ = dispatcher; EXPECT_CALL(*dispatcher, createTimer_(_)) .WillOnce(DoAll(SaveArg<0>(&callback_), Return(this))) .RetiresOnSaturation(); diff --git a/test/mocks/event/mocks.h b/test/mocks/event/mocks.h index 0e65635e4b..b52d8a378c 100644 --- a/test/mocks/event/mocks.h +++ b/test/mocks/event/mocks.h @@ -17,6 +17,8 @@ #include "envoy/network/transport_socket.h" #include "envoy/ssl/context.h" +#include "common/common/scope_tracker.h" + #include "test/mocks/buffer/mocks.h" #include "test/test_common/test_time.h" @@ -63,9 +65,9 @@ class MockDispatcher : public Dispatcher { createListener_(socket, cb, bind_to_port, hand_off_restored_destination_connections)}; } - Network::ListenerPtr createUdpListener(Network::Socket& socket, - Network::UdpListenerCallbacks& cb) override { - return Network::ListenerPtr{createUdpListener_(socket, cb)}; + Network::UdpListenerPtr createUdpListener(Network::Socket& socket, + Network::UdpListenerCallbacks& cb) override { + return Network::UdpListenerPtr{createUdpListener_(socket, cb)}; } Event::TimerPtr createTimer(Event::TimerCb cb) override { @@ -106,7 +108,7 @@ class MockDispatcher : public Dispatcher { bool bind_to_port, bool hand_off_restored_destination_connections)); MOCK_METHOD2(createUdpListener_, - Network::Listener*(Network::Socket& socket, Network::UdpListenerCallbacks& cb)); + Network::UdpListener*(Network::Socket& socket, Network::UdpListenerCallbacks& cb)); MOCK_METHOD1(createTimer_, Timer*(Event::TimerCb cb)); MOCK_METHOD1(deferredDelete_, void(DeferredDeletable* to_delete)); MOCK_METHOD0(exit, void()); @@ -116,6 +118,7 @@ class MockDispatcher : public Dispatcher { MOCK_METHOD1(setTrackedObject, const ScopeTrackedObject*(const ScopeTrackedObject* object)); MOCK_CONST_METHOD0(isThreadSafe, bool()); Buffer::WatermarkFactory& getWatermarkFactory() override { return buffer_factory_; } + MOCK_METHOD0(getCurrentThreadId, Thread::ThreadId()); GlobalTimeSystem time_system_; std::list to_delete_; @@ -131,14 +134,23 @@ class MockTimer : public Timer { void invokeCallback() { EXPECT_TRUE(enabled_); enabled_ = false; + if (scope_ == nullptr) { + callback_(); + return; + } + ScopeTrackerScopeState scope(scope_, *dispatcher_); + scope_ = nullptr; callback_(); } // Timer MOCK_METHOD0(disableTimer, void()); - MOCK_METHOD1(enableTimer, void(const std::chrono::milliseconds&)); + MOCK_METHOD2(enableTimer, + void(const std::chrono::milliseconds&, const ScopeTrackedObject* scope)); MOCK_METHOD0(enabled, bool()); + MockDispatcher* dispatcher_{}; + const ScopeTrackedObject* scope_{}; bool enabled_{}; private: diff --git a/test/mocks/filesystem/mocks.cc b/test/mocks/filesystem/mocks.cc index 7cf7333150..af5d9fcddc 100644 --- a/test/mocks/filesystem/mocks.cc +++ b/test/mocks/filesystem/mocks.cc @@ -9,10 +9,10 @@ namespace Filesystem { MockFile::MockFile() : num_opens_(0), num_writes_(0), is_open_(false) {} MockFile::~MockFile() = default; -Api::IoCallBoolResult MockFile::open(FlagSet) { +Api::IoCallBoolResult MockFile::open(FlagSet flag) { Thread::LockGuard lock(open_mutex_); - Api::IoCallBoolResult result = open_(); + Api::IoCallBoolResult result = open_(flag); is_open_ = result.rc_; num_opens_++; open_event_.notifyOne(); diff --git a/test/mocks/filesystem/mocks.h b/test/mocks/filesystem/mocks.h index 459cdfd8ce..c4ae006a83 100644 --- a/test/mocks/filesystem/mocks.h +++ b/test/mocks/filesystem/mocks.h @@ -25,7 +25,8 @@ class MockFile : public File { bool isOpen() const override { return is_open_; }; MOCK_CONST_METHOD0(path, std::string()); - MOCK_METHOD0(open_, Api::IoCallBoolResult()); + // The first parameter here must be `const FlagSet&` otherwise it doesn't compile with libstdc++ + MOCK_METHOD1(open_, Api::IoCallBoolResult(const FlagSet& flag)); MOCK_METHOD1(write_, Api::IoCallSizeResult(absl::string_view buffer)); MOCK_METHOD0(close_, Api::IoCallBoolResult()); diff --git a/test/mocks/http/mocks.cc b/test/mocks/http/mocks.cc index 0f1161c572..2ab8a561b6 100644 --- a/test/mocks/http/mocks.cc +++ b/test/mocks/http/mocks.cc @@ -8,13 +8,8 @@ using testing::_; using testing::Invoke; -using testing::MakeMatcher; -using testing::Matcher; -using testing::MatcherInterface; -using testing::MatchResultListener; using testing::Return; using testing::ReturnRef; -using testing::SaveArg; namespace Envoy { namespace Http { diff --git a/test/mocks/local_info/mocks.cc b/test/mocks/local_info/mocks.cc index 7faff3e6b8..bcf3251c69 100644 --- a/test/mocks/local_info/mocks.cc +++ b/test/mocks/local_info/mocks.cc @@ -5,7 +5,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::Invoke; using testing::Return; using testing::ReturnRef; diff --git a/test/mocks/network/connection.h b/test/mocks/network/connection.h index f660269de1..501fa8a2df 100644 --- a/test/mocks/network/connection.h +++ b/test/mocks/network/connection.h @@ -71,7 +71,7 @@ class MockConnection : public Connection, public MockConnectionBase { absl::optional()); MOCK_CONST_METHOD0(localAddress, const Address::InstanceConstSharedPtr&()); MOCK_METHOD1(setConnectionStats, void(const ConnectionStats& stats)); - MOCK_CONST_METHOD0(ssl, const Ssl::ConnectionInfo*()); + MOCK_CONST_METHOD0(ssl, Ssl::ConnectionInfoConstSharedPtr()); MOCK_CONST_METHOD0(requestedServerName, absl::string_view()); MOCK_CONST_METHOD0(state, State()); MOCK_METHOD2(write, void(Buffer::Instance& data, bool end_stream)); @@ -117,7 +117,7 @@ class MockClientConnection : public ClientConnection, public MockConnectionBase absl::optional()); MOCK_CONST_METHOD0(localAddress, const Address::InstanceConstSharedPtr&()); MOCK_METHOD1(setConnectionStats, void(const ConnectionStats& stats)); - MOCK_CONST_METHOD0(ssl, const Ssl::ConnectionInfo*()); + MOCK_CONST_METHOD0(ssl, Ssl::ConnectionInfoConstSharedPtr()); MOCK_CONST_METHOD0(requestedServerName, absl::string_view()); MOCK_CONST_METHOD0(state, State()); MOCK_METHOD2(write, void(Buffer::Instance& data, bool end_stream)); @@ -166,7 +166,7 @@ class MockFilterManagerConnection : public FilterManagerConnection, public MockC absl::optional()); MOCK_CONST_METHOD0(localAddress, const Address::InstanceConstSharedPtr&()); MOCK_METHOD1(setConnectionStats, void(const ConnectionStats& stats)); - MOCK_CONST_METHOD0(ssl, const Ssl::ConnectionInfo*()); + MOCK_CONST_METHOD0(ssl, Ssl::ConnectionInfoConstSharedPtr()); MOCK_CONST_METHOD0(requestedServerName, absl::string_view()); MOCK_CONST_METHOD0(state, State()); MOCK_METHOD2(write, void(Buffer::Instance& data, bool end_stream)); diff --git a/test/mocks/network/mocks.h b/test/mocks/network/mocks.h index e63d3f2295..77d2c37d74 100644 --- a/test/mocks/network/mocks.h +++ b/test/mocks/network/mocks.h @@ -311,6 +311,7 @@ class MockListenerConfig : public ListenerConfig { MOCK_METHOD0(listenerScope, Stats::Scope&()); MOCK_CONST_METHOD0(listenerTag, uint64_t()); MOCK_CONST_METHOD0(name, const std::string&()); + MOCK_METHOD0(udpListenerFactory, const Network::ActiveUdpListenerFactory*()); testing::NiceMock filter_chain_factory_; testing::NiceMock socket_; @@ -334,6 +335,8 @@ class MockConnectionHandler : public ConnectionHandler { ~MockConnectionHandler() override; MOCK_METHOD0(numConnections, uint64_t()); + MOCK_METHOD0(incNumConnections, void()); + MOCK_METHOD0(decNumConnections, void()); MOCK_METHOD1(addListener, void(ListenerConfig& config)); MOCK_METHOD1(findListenerByAddress, Network::Listener*(const Network::Address::Instance& address)); @@ -397,7 +400,7 @@ class MockTransportSocket : public TransportSocket { MOCK_METHOD1(doRead, IoResult(Buffer::Instance& buffer)); MOCK_METHOD2(doWrite, IoResult(Buffer::Instance& buffer, bool end_stream)); MOCK_METHOD0(onConnected, void()); - MOCK_CONST_METHOD0(ssl, const Ssl::ConnectionInfo*()); + MOCK_CONST_METHOD0(ssl, Ssl::ConnectionInfoConstSharedPtr()); TransportSocketCallbacks* callbacks_{}; }; diff --git a/test/mocks/router/BUILD b/test/mocks/router/BUILD index eead78a0d5..d5d8729ccc 100644 --- a/test/mocks/router/BUILD +++ b/test/mocks/router/BUILD @@ -27,5 +27,6 @@ envoy_cc_mock( "//include/envoy/upstream:cluster_manager_interface", "//source/common/stats:fake_symbol_table_lib", "//test/mocks:common_lib", + "//test/mocks/stats:stats_mocks", ], ) diff --git a/test/mocks/router/mocks.cc b/test/mocks/router/mocks.cc index 827e9c84f8..291bd3248e 100644 --- a/test/mocks/router/mocks.cc +++ b/test/mocks/router/mocks.cc @@ -116,11 +116,25 @@ MockRoute::MockRoute() { } MockRoute::~MockRoute() = default; +MockRouteConfigProvider::MockRouteConfigProvider() { + ON_CALL(*this, config()).WillByDefault(Return(route_config_)); +} +MockRouteConfigProvider::~MockRouteConfigProvider() = default; + MockRouteConfigProviderManager::MockRouteConfigProviderManager() = default; MockRouteConfigProviderManager::~MockRouteConfigProviderManager() = default; -MockScopedConfig::MockScopedConfig() = default; +MockScopedConfig::MockScopedConfig() { + ON_CALL(*this, getRouteConfig(_)).WillByDefault(Return(route_config_)); +} MockScopedConfig::~MockScopedConfig() = default; +MockScopedRouteConfigProvider::MockScopedRouteConfigProvider() + : config_(std::make_shared()) { + ON_CALL(*this, getConfig()).WillByDefault(Return(config_)); + ON_CALL(*this, apiType()).WillByDefault(Return(ApiType::Delta)); +} +MockScopedRouteConfigProvider::~MockScopedRouteConfigProvider() = default; + } // namespace Router } // namespace Envoy diff --git a/test/mocks/router/mocks.h b/test/mocks/router/mocks.h index aeda21400f..b4e7c4ff77 100644 --- a/test/mocks/router/mocks.h +++ b/test/mocks/router/mocks.h @@ -8,9 +8,10 @@ #include #include +#include "envoy/common/time.h" +#include "envoy/config/config_provider.h" #include "envoy/config/typed_metadata.h" #include "envoy/event/dispatcher.h" -#include "envoy/json/json_object.h" #include "envoy/local_info/local_info.h" #include "envoy/router/rds.h" #include "envoy/router/route_config_provider_manager.h" @@ -24,12 +25,14 @@ #include "common/stats/fake_symbol_table_impl.h" +#include "test/mocks/stats/mocks.h" #include "test/test_common/global.h" #include "gmock/gmock.h" namespace Envoy { namespace Router { +using ::testing::NiceMock; class MockDirectResponseEntry : public DirectResponseEntry { public: @@ -50,8 +53,9 @@ class MockDirectResponseEntry : public DirectResponseEntry { class TestCorsPolicy : public CorsPolicy { public: // Router::CorsPolicy - const std::list& allowOrigins() const override { return allow_origin_; }; - const std::list& allowOriginRegexes() const override { return allow_origin_regex_; }; + const std::vector& allowOrigins() const override { + return allow_origins_; + }; const std::string& allowMethods() const override { return allow_methods_; }; const std::string& allowHeaders() const override { return allow_headers_; }; const std::string& exposeHeaders() const override { return expose_headers_; }; @@ -60,13 +64,12 @@ class TestCorsPolicy : public CorsPolicy { bool enabled() const override { return enabled_; }; bool shadowEnabled() const override { return shadow_enabled_; }; - std::list allow_origin_{}; - std::list allow_origin_regex_{}; - std::string allow_methods_{}; - std::string allow_headers_{}; - std::string expose_headers_{}; + std::vector allow_origins_; + std::string allow_methods_; + std::string allow_headers_; + std::string expose_headers_; std::string max_age_{}; - absl::optional allow_credentials_{}; + absl::optional allow_credentials_; bool enabled_{}; bool shadow_enabled_{}; }; @@ -199,7 +202,7 @@ class TestVirtualCluster : public VirtualCluster { // Router::VirtualCluster Stats::StatName statName() const override { return stat_name_.statName(); } - Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Stats::StatNameManagedStorage stat_name_{"fake_virtual_cluster", *symbol_table_}; }; @@ -223,7 +226,7 @@ class MockVirtualHost : public VirtualHost { return stat_name_->statName(); } - mutable Test::Global symbol_table_; + mutable Stats::TestSymbolTable symbol_table_; std::string name_{"fake_vhost"}; mutable std::unique_ptr stat_name_; testing::NiceMock rate_limit_policy_; @@ -379,16 +382,30 @@ class MockConfig : public Config { std::string name_{"fake_config"}; }; +class MockRouteConfigProvider : public RouteConfigProvider { +public: + MockRouteConfigProvider(); + ~MockRouteConfigProvider() override; + + MOCK_METHOD0(config, ConfigConstSharedPtr()); + MOCK_CONST_METHOD0(configInfo, absl::optional()); + MOCK_CONST_METHOD0(lastUpdated, SystemTime()); + MOCK_METHOD0(onConfigUpdate, void()); + MOCK_CONST_METHOD1(validateConfig, void(const envoy::api::v2::RouteConfiguration&)); + + std::shared_ptr> route_config_{new NiceMock()}; +}; + class MockRouteConfigProviderManager : public RouteConfigProviderManager { public: MockRouteConfigProviderManager(); ~MockRouteConfigProviderManager() override; - MOCK_METHOD3(createRdsRouteConfigProvider, + MOCK_METHOD4(createRdsRouteConfigProvider, RouteConfigProviderPtr( const envoy::config::filter::network::http_connection_manager::v2::Rds& rds, Server::Configuration::FactoryContext& factory_context, - const std::string& stat_prefix)); + const std::string& stat_prefix, Init::Manager& init_manager)); MOCK_METHOD2(createStaticRouteConfigProvider, RouteConfigProviderPtr(const envoy::api::v2::RouteConfiguration& route_config, Server::Configuration::FactoryContext& factory_context)); @@ -400,6 +417,23 @@ class MockScopedConfig : public ScopedConfig { ~MockScopedConfig() override; MOCK_CONST_METHOD1(getRouteConfig, ConfigConstSharedPtr(const Http::HeaderMap& headers)); + + std::shared_ptr route_config_{new NiceMock()}; +}; + +class MockScopedRouteConfigProvider : public Envoy::Config::ConfigProvider { +public: + MockScopedRouteConfigProvider(); + ~MockScopedRouteConfigProvider() override; + + // Config::ConfigProvider + MOCK_CONST_METHOD0(lastUpdated, SystemTime()); + MOCK_CONST_METHOD0(getConfigProto, Protobuf::Message*()); + MOCK_CONST_METHOD0(getConfigProtos, Envoy::Config::ConfigProvider::ConfigProtoVector()); + MOCK_CONST_METHOD0(getConfig, ConfigConstSharedPtr()); + MOCK_CONST_METHOD0(apiType, ApiType()); + + std::shared_ptr config_; }; } // namespace Router diff --git a/test/mocks/server/mocks.cc b/test/mocks/server/mocks.cc index 9d2d49f527..92ba5f5456 100644 --- a/test/mocks/server/mocks.cc +++ b/test/mocks/server/mocks.cc @@ -10,7 +10,6 @@ using testing::_; using testing::Invoke; using testing::Return; -using testing::ReturnNew; using testing::ReturnPointee; using testing::ReturnRef; using testing::SaveArg; @@ -77,7 +76,7 @@ MockGuardDog::MockGuardDog() : watch_dog_(new NiceMock()) { } MockGuardDog::~MockGuardDog() = default; -MockHotRestart::MockHotRestart() : stats_allocator_(symbol_table_.get()) { +MockHotRestart::MockHotRestart() : stats_allocator_(*symbol_table_) { ON_CALL(*this, logLock()).WillByDefault(ReturnRef(log_lock_)); ON_CALL(*this, accessLogLock()).WillByDefault(ReturnRef(access_log_lock_)); ON_CALL(*this, statsAllocator()).WillByDefault(ReturnRef(stats_allocator_)); @@ -224,6 +223,7 @@ MockHealthCheckerFactoryContext::MockHealthCheckerFactoryContext() { ON_CALL(*this, eventLogger_()).WillByDefault(Return(event_logger_)); ON_CALL(*this, messageValidationVisitor()) .WillByDefault(ReturnRef(ProtobufMessage::getStrictValidationVisitor())); + ON_CALL(*this, api()).WillByDefault(ReturnRef(api_)); } MockHealthCheckerFactoryContext::~MockHealthCheckerFactoryContext() = default; diff --git a/test/mocks/server/mocks.h b/test/mocks/server/mocks.h index 4ba0920f56..b8398a630b 100644 --- a/test/mocks/server/mocks.h +++ b/test/mocks/server/mocks.h @@ -85,6 +85,7 @@ class MockOptions : public Options { MOCK_CONST_METHOD0(signalHandlingEnabled, bool()); MOCK_CONST_METHOD0(mutexTracingEnabled, bool()); MOCK_CONST_METHOD0(libeventBufferEnabled, bool()); + MOCK_CONST_METHOD0(fakeSymbolTableEnabled, bool()); MOCK_CONST_METHOD0(cpusetThreadsEnabled, bool()); MOCK_CONST_METHOD0(toCommandLineOptions, Server::CommandLineOptionsPtr()); @@ -218,7 +219,7 @@ class MockHotRestart : public HotRestart { MOCK_METHOD0(statsAllocator, Stats::Allocator&()); private: - Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; Thread::MutexBasicLockable log_lock_; Thread::MutexBasicLockable access_log_lock_; Stats::AllocatorImpl stats_allocator_; @@ -381,7 +382,7 @@ class MockInstance : public Instance { MOCK_METHOD0(stats, Stats::Store&()); MOCK_METHOD0(grpcContext, Grpc::Context&()); MOCK_METHOD0(httpContext, Http::Context&()); - MOCK_METHOD0(processContext, ProcessContext&()); + MOCK_METHOD0(processContext, absl::optional>()); MOCK_METHOD0(threadLocal, ThreadLocal::Instance&()); MOCK_METHOD0(localInfo, const LocalInfo::LocalInfo&()); MOCK_CONST_METHOD0(statsFlushInterval, std::chrono::milliseconds()); @@ -469,7 +470,7 @@ class MockFactoryContext : public virtual FactoryContext { Event::TestTimeSystem& timeSystem() { return time_system_; } Grpc::Context& grpcContext() override { return grpc_context_; } Http::Context& httpContext() override { return http_context_; } - MOCK_METHOD0(processContext, ProcessContext&()); + MOCK_METHOD0(processContext, absl::optional>()); MOCK_METHOD0(messageValidationVisitor, ProtobufMessage::ValidationVisitor&()); MOCK_METHOD0(api, Api::Api&()); @@ -545,6 +546,7 @@ class MockHealthCheckerFactoryContext : public virtual HealthCheckerFactoryConte MOCK_METHOD0(runtime, Envoy::Runtime::Loader&()); MOCK_METHOD0(eventLogger_, Upstream::HealthCheckEventLogger*()); MOCK_METHOD0(messageValidationVisitor, ProtobufMessage::ValidationVisitor&()); + MOCK_METHOD0(api, Api::Api&()); Upstream::HealthCheckEventLoggerPtr eventLogger() override { return Upstream::HealthCheckEventLoggerPtr(eventLogger_()); } @@ -554,6 +556,7 @@ class MockHealthCheckerFactoryContext : public virtual HealthCheckerFactoryConte testing::NiceMock random_; testing::NiceMock runtime_; testing::NiceMock* event_logger_{}; + testing::NiceMock api_{}; }; } // namespace Configuration diff --git a/test/mocks/ssl/mocks.cc b/test/mocks/ssl/mocks.cc index 72702de823..50ed3f3ae6 100644 --- a/test/mocks/ssl/mocks.cc +++ b/test/mocks/ssl/mocks.cc @@ -18,5 +18,11 @@ MockClientContextConfig::~MockClientContextConfig() = default; MockServerContextConfig::MockServerContextConfig() = default; MockServerContextConfig::~MockServerContextConfig() = default; +MockPrivateKeyMethodManager::MockPrivateKeyMethodManager() = default; +MockPrivateKeyMethodManager::~MockPrivateKeyMethodManager() = default; + +MockPrivateKeyMethodProvider::MockPrivateKeyMethodProvider() = default; +MockPrivateKeyMethodProvider::~MockPrivateKeyMethodProvider() = default; + } // namespace Ssl } // namespace Envoy diff --git a/test/mocks/ssl/mocks.h b/test/mocks/ssl/mocks.h index 20621bd29c..041888aa99 100644 --- a/test/mocks/ssl/mocks.h +++ b/test/mocks/ssl/mocks.h @@ -29,6 +29,7 @@ class MockContextManager : public ContextManager { const std::vector& server_names)); MOCK_CONST_METHOD0(daysUntilFirstCertExpires, size_t()); MOCK_METHOD1(iterateContexts, void(std::function callback)); + MOCK_METHOD0(privateKeyMethodManager, Ssl::PrivateKeyMethodManager&()); }; class MockConnectionInfo : public ConnectionInfo { @@ -39,21 +40,21 @@ class MockConnectionInfo : public ConnectionInfo { MOCK_CONST_METHOD0(peerCertificatePresented, bool()); MOCK_CONST_METHOD0(uriSanLocalCertificate, std::vector()); MOCK_CONST_METHOD0(sha256PeerCertificateDigest, const std::string&()); - MOCK_CONST_METHOD0(serialNumberPeerCertificate, std::string()); - MOCK_CONST_METHOD0(issuerPeerCertificate, std::string()); - MOCK_CONST_METHOD0(subjectPeerCertificate, std::string()); + MOCK_CONST_METHOD0(serialNumberPeerCertificate, const std::string&()); + MOCK_CONST_METHOD0(issuerPeerCertificate, const std::string&()); + MOCK_CONST_METHOD0(subjectPeerCertificate, const std::string&()); MOCK_CONST_METHOD0(uriSanPeerCertificate, std::vector()); - MOCK_CONST_METHOD0(subjectLocalCertificate, std::string()); + MOCK_CONST_METHOD0(subjectLocalCertificate, const std::string&()); MOCK_CONST_METHOD0(urlEncodedPemEncodedPeerCertificate, const std::string&()); MOCK_CONST_METHOD0(urlEncodedPemEncodedPeerCertificateChain, const std::string&()); MOCK_CONST_METHOD0(dnsSansPeerCertificate, std::vector()); MOCK_CONST_METHOD0(dnsSansLocalCertificate, std::vector()); MOCK_CONST_METHOD0(validFromPeerCertificate, absl::optional()); MOCK_CONST_METHOD0(expirationPeerCertificate, absl::optional()); - MOCK_CONST_METHOD0(sessionId, std::string()); + MOCK_CONST_METHOD0(sessionId, const std::string&()); MOCK_CONST_METHOD0(ciphersuiteId, uint16_t()); MOCK_CONST_METHOD0(ciphersuiteString, std::string()); - MOCK_CONST_METHOD0(tlsVersion, std::string()); + MOCK_CONST_METHOD0(tlsVersion, const std::string&()); }; class MockClientContext : public ClientContext { @@ -108,5 +109,28 @@ class MockServerContextConfig : public ServerContextConfig { MOCK_CONST_METHOD0(sessionTicketKeys, const std::vector&()); }; +class MockPrivateKeyMethodManager : public PrivateKeyMethodManager { +public: + MockPrivateKeyMethodManager(); + ~MockPrivateKeyMethodManager() override; + + MOCK_METHOD2(createPrivateKeyMethodProvider, + PrivateKeyMethodProviderSharedPtr( + const envoy::api::v2::auth::PrivateKeyProvider& config, + Envoy::Server::Configuration::TransportSocketFactoryContext& factory_context)); +}; + +class MockPrivateKeyMethodProvider : public PrivateKeyMethodProvider { +public: + MockPrivateKeyMethodProvider(); + ~MockPrivateKeyMethodProvider() override; + + MOCK_METHOD3(registerPrivateKeyMethod, + void(SSL* ssl, PrivateKeyConnectionCallbacks& cb, Event::Dispatcher& dispatcher)); + MOCK_METHOD1(unregisterPrivateKeyMethod, void(SSL* ssl)); + MOCK_METHOD0(checkFips, bool()); + MOCK_METHOD0(getBoringSslPrivateKeyMethod, BoringSslPrivateKeyMethodSharedPtr()); +}; + } // namespace Ssl } // namespace Envoy diff --git a/test/mocks/stats/BUILD b/test/mocks/stats/BUILD index 6539e81846..bf9048b253 100644 --- a/test/mocks/stats/BUILD +++ b/test/mocks/stats/BUILD @@ -22,6 +22,7 @@ envoy_cc_mock( "//source/common/stats:isolated_store_lib", "//source/common/stats:stats_lib", "//source/common/stats:store_impl_lib", + "//source/common/stats:symbol_table_creator_lib", "//test/mocks:common_lib", "//test/test_common:global_lib", ], diff --git a/test/mocks/stats/mocks.cc b/test/mocks/stats/mocks.cc index 56dfbaf84d..8196b0a43b 100644 --- a/test/mocks/stats/mocks.cc +++ b/test/mocks/stats/mocks.cc @@ -10,7 +10,6 @@ using testing::_; using testing::Invoke; using testing::NiceMock; -using testing::Return; using testing::ReturnPointee; using testing::ReturnRef; @@ -63,7 +62,7 @@ MockMetricSnapshot::~MockMetricSnapshot() = default; MockSink::MockSink() = default; MockSink::~MockSink() = default; -MockStore::MockStore() : StoreImpl(*fake_symbol_table_) { +MockStore::MockStore() : StoreImpl(*global_symbol_table_) { ON_CALL(*this, counter(_)).WillByDefault(ReturnRef(counter_)); ON_CALL(*this, histogram(_)).WillByDefault(Invoke([this](const std::string& name) -> Histogram& { auto* histogram = new NiceMock(); // symbol_table_); @@ -75,8 +74,7 @@ MockStore::MockStore() : StoreImpl(*fake_symbol_table_) { } MockStore::~MockStore() = default; -MockIsolatedStatsStore::MockIsolatedStatsStore() - : IsolatedStoreImpl(Test::Global::get()) {} +MockIsolatedStatsStore::MockIsolatedStatsStore() : IsolatedStoreImpl(*global_symbol_table_) {} MockIsolatedStatsStore::~MockIsolatedStatsStore() = default; MockStatsMatcher::MockStatsMatcher() = default; diff --git a/test/mocks/stats/mocks.h b/test/mocks/stats/mocks.h index d418ad430f..d5ad120ffc 100644 --- a/test/mocks/stats/mocks.h +++ b/test/mocks/stats/mocks.h @@ -18,6 +18,7 @@ #include "common/stats/histogram_impl.h" #include "common/stats/isolated_store_impl.h" #include "common/stats/store_impl.h" +#include "common/stats/symbol_table_creator.h" #include "test/test_common/global.h" @@ -26,6 +27,25 @@ namespace Envoy { namespace Stats { +class TestSymbolTableHelper { +public: + TestSymbolTableHelper() : symbol_table_(SymbolTableCreator::makeSymbolTable()) {} + SymbolTable& symbolTable() { return *symbol_table_; } + const SymbolTable& constSymbolTable() const { return *symbol_table_; } + +private: + SymbolTablePtr symbol_table_; +}; + +class TestSymbolTable { +public: + SymbolTable& operator*() { return global_.get().symbolTable(); } + const SymbolTable& operator*() const { return global_.get().constSymbolTable(); } + SymbolTable* operator->() { return &global_.get().symbolTable(); } + const SymbolTable* operator->() const { return &global_.get().constSymbolTable(); } + Envoy::Test::Global global_; +}; + template class MockMetric : public BaseClass { public: MockMetric() : name_(*this), tag_pool_(*symbol_table_) {} @@ -39,7 +59,7 @@ template class MockMetric : public BaseClass { explicit MetricName(MockMetric& mock_metric) : mock_metric_(mock_metric) {} ~MetricName() { if (stat_name_storage_ != nullptr) { - stat_name_storage_->free(*mock_metric_.symbol_table_); + stat_name_storage_->free(mock_metric_.symbolTable()); } } @@ -57,8 +77,8 @@ template class MockMetric : public BaseClass { std::unique_ptr stat_name_storage_; }; - SymbolTable& symbolTable() override { return symbol_table_.get(); } - const SymbolTable& constSymbolTable() const override { return symbol_table_.get(); } + SymbolTable& symbolTable() override { return *symbol_table_; } + const SymbolTable& constSymbolTable() const override { return *symbol_table_; } // Note: cannot be mocked because it is accessed as a Property in a gmock EXPECT_CALL. This // creates a deadlock in gmock and is an unintended use of mock functions. @@ -90,7 +110,7 @@ template class MockMetric : public BaseClass { } } - Test::Global symbol_table_; // Must outlive name_. + TestSymbolTable symbol_table_; // Must outlive name_. MetricName name_; void setTags(const std::vector& tags) { @@ -251,7 +271,7 @@ class MockSink : public Sink { class SymbolTableProvider { public: - Test::Global fake_symbol_table_; + TestSymbolTable global_symbol_table_; }; class MockStore : public SymbolTableProvider, public StoreImpl { @@ -285,7 +305,7 @@ class MockStore : public SymbolTableProvider, public StoreImpl { return histogram(symbol_table_->toString(name)); } - Test::Global symbol_table_; + TestSymbolTable symbol_table_; testing::NiceMock counter_; std::vector> histograms_; }; @@ -294,8 +314,7 @@ class MockStore : public SymbolTableProvider, public StoreImpl { * With IsolatedStoreImpl it's hard to test timing stats. * MockIsolatedStatsStore mocks only deliverHistogramToSinks for better testing. */ -class MockIsolatedStatsStore : private Test::Global, - public IsolatedStoreImpl { +class MockIsolatedStatsStore : public SymbolTableProvider, public IsolatedStoreImpl { public: MockIsolatedStatsStore(); ~MockIsolatedStatsStore() override; diff --git a/test/mocks/stream_info/mocks.cc b/test/mocks/stream_info/mocks.cc index 6c842c9ecb..1f4c4a8f72 100644 --- a/test/mocks/stream_info/mocks.cc +++ b/test/mocks/stream_info/mocks.cc @@ -8,7 +8,6 @@ using testing::_; using testing::Const; using testing::Invoke; -using testing::Return; using testing::ReturnPointee; using testing::ReturnRef; @@ -64,10 +63,16 @@ MockStreamInfo::MockStreamInfo() ON_CALL(*this, downstreamRemoteAddress()).WillByDefault(ReturnRef(downstream_remote_address_)); ON_CALL(*this, setDownstreamSslConnection(_)) .WillByDefault(Invoke( - [this](const auto* connection_info) { downstream_connection_info_ = connection_info; })); + [this](const auto& connection_info) { downstream_connection_info_ = connection_info; })); + ON_CALL(*this, setUpstreamSslConnection(_)) + .WillByDefault(Invoke( + [this](const auto& connection_info) { upstream_connection_info_ = connection_info; })); ON_CALL(*this, downstreamSslConnection()).WillByDefault(Invoke([this]() { return downstream_connection_info_; })); + ON_CALL(*this, upstreamSslConnection()).WillByDefault(Invoke([this]() { + return upstream_connection_info_; + })); ON_CALL(*this, protocol()).WillByDefault(ReturnPointee(&protocol_)); ON_CALL(*this, responseCode()).WillByDefault(ReturnPointee(&response_code_)); ON_CALL(*this, responseCodeDetails()).WillByDefault(ReturnPointee(&response_code_details_)); diff --git a/test/mocks/stream_info/mocks.h b/test/mocks/stream_info/mocks.h index 0fbd342222..648cafe20a 100644 --- a/test/mocks/stream_info/mocks.h +++ b/test/mocks/stream_info/mocks.h @@ -65,8 +65,10 @@ class MockStreamInfo : public StreamInfo { const Network::Address::InstanceConstSharedPtr&()); MOCK_METHOD1(setDownstreamRemoteAddress, void(const Network::Address::InstanceConstSharedPtr&)); MOCK_CONST_METHOD0(downstreamRemoteAddress, const Network::Address::InstanceConstSharedPtr&()); - MOCK_METHOD1(setDownstreamSslConnection, void(const Ssl::ConnectionInfo*)); - MOCK_CONST_METHOD0(downstreamSslConnection, const Ssl::ConnectionInfo*()); + MOCK_METHOD1(setDownstreamSslConnection, void(const Ssl::ConnectionInfoConstSharedPtr&)); + MOCK_CONST_METHOD0(downstreamSslConnection, Ssl::ConnectionInfoConstSharedPtr()); + MOCK_METHOD1(setUpstreamSslConnection, void(const Ssl::ConnectionInfoConstSharedPtr&)); + MOCK_CONST_METHOD0(upstreamSslConnection, Ssl::ConnectionInfoConstSharedPtr()); MOCK_CONST_METHOD0(routeEntry, const Router::RouteEntry*()); MOCK_METHOD0(dynamicMetadata, envoy::api::v2::core::Metadata&()); MOCK_CONST_METHOD0(dynamicMetadata, const envoy::api::v2::core::Metadata&()); @@ -103,7 +105,8 @@ class MockStreamInfo : public StreamInfo { Network::Address::InstanceConstSharedPtr downstream_local_address_; Network::Address::InstanceConstSharedPtr downstream_direct_remote_address_; Network::Address::InstanceConstSharedPtr downstream_remote_address_; - const Ssl::ConnectionInfo* downstream_connection_info_{}; + Ssl::ConnectionInfoConstSharedPtr downstream_connection_info_; + Ssl::ConnectionInfoConstSharedPtr upstream_connection_info_; std::string requested_server_name_; std::string route_name_; std::string upstream_transport_failure_reason_; diff --git a/test/mocks/thread_local/mocks.h b/test/mocks/thread_local/mocks.h index 3d7a43efae..a9abc6a6d5 100644 --- a/test/mocks/thread_local/mocks.h +++ b/test/mocks/thread_local/mocks.h @@ -63,6 +63,14 @@ class MockInstance : public Instance { void runOnAllThreads(Event::PostCb cb, Event::PostCb main_callback) override { parent_.runOnAllThreads(cb, main_callback); } + void runOnAllThreads(const UpdateCb& cb) override { + parent_.runOnAllThreads([cb, this]() { parent_.data_[index_] = cb(parent_.data_[index_]); }); + } + void runOnAllThreads(const UpdateCb& cb, Event::PostCb main_callback) override { + parent_.runOnAllThreads([cb, this]() { parent_.data_[index_] = cb(parent_.data_[index_]); }, + main_callback); + } + void set(InitializeCb cb) override { parent_.data_[index_] = cb(parent_.dispatcher_); } MockInstance& parent_; diff --git a/test/mocks/tracing/mocks.cc b/test/mocks/tracing/mocks.cc index db9be39023..3a688957f0 100644 --- a/test/mocks/tracing/mocks.cc +++ b/test/mocks/tracing/mocks.cc @@ -16,6 +16,7 @@ MockConfig::MockConfig() { ON_CALL(*this, operationName()).WillByDefault(Return(operation_name_)); ON_CALL(*this, requestHeadersForTags()).WillByDefault(ReturnRef(headers_)); ON_CALL(*this, verbose()).WillByDefault(Return(verbose_)); + ON_CALL(*this, maxPathTagLength()).WillByDefault(Return(uint32_t(256))); } MockConfig::~MockConfig() = default; diff --git a/test/mocks/tracing/mocks.h b/test/mocks/tracing/mocks.h index 22a694edfd..1cd5b3a0c9 100644 --- a/test/mocks/tracing/mocks.h +++ b/test/mocks/tracing/mocks.h @@ -18,6 +18,7 @@ class MockConfig : public Config { MOCK_CONST_METHOD0(operationName, OperationName()); MOCK_CONST_METHOD0(requestHeadersForTags, const std::vector&()); MOCK_CONST_METHOD0(verbose, bool()); + MOCK_CONST_METHOD0(maxPathTagLength, uint32_t()); OperationName operation_name_{OperationName::Ingress}; std::vector headers_; diff --git a/test/mocks/upstream/host.h b/test/mocks/upstream/host.h index 095c67c1ae..6fea5dec1f 100644 --- a/test/mocks/upstream/host.h +++ b/test/mocks/upstream/host.h @@ -108,7 +108,7 @@ class MockHostDescription : public HostDescription { testing::NiceMock cluster_; testing::NiceMock stats_store_; HostStats stats_{ALL_HOST_STATS(POOL_COUNTER(stats_store_), POOL_GAUGE(stats_store_))}; - mutable Test::Global symbol_table_; + mutable Stats::TestSymbolTable symbol_table_; mutable std::unique_ptr locality_zone_stat_name_; }; @@ -186,7 +186,7 @@ class MockHost : public Host { testing::NiceMock outlier_detector_; NiceMock stats_store_; HostStats stats_{ALL_HOST_STATS(POOL_COUNTER(stats_store_), POOL_GAUGE(stats_store_))}; - mutable Test::Global symbol_table_; + mutable Stats::TestSymbolTable symbol_table_; mutable std::unique_ptr locality_zone_stat_name_; }; diff --git a/test/mocks/upstream/mocks.cc b/test/mocks/upstream/mocks.cc index 05785eead3..7d629b1061 100644 --- a/test/mocks/upstream/mocks.cc +++ b/test/mocks/upstream/mocks.cc @@ -12,7 +12,6 @@ using testing::_; using testing::Eq; using testing::Invoke; using testing::Return; -using testing::ReturnPointee; using testing::ReturnRef; using testing::SaveArg; diff --git a/test/mocks/upstream/mocks.h b/test/mocks/upstream/mocks.h index f194bffcec..1b7f0b07b2 100644 --- a/test/mocks/upstream/mocks.h +++ b/test/mocks/upstream/mocks.h @@ -142,7 +142,9 @@ class MockRetryPriorityFactory : public RetryPriorityFactory { public: MockRetryPriorityFactory(const MockRetryPriority& retry_priority) : retry_priority_(retry_priority) {} - RetryPrioritySharedPtr createRetryPriority(const Protobuf::Message&, uint32_t) override { + RetryPrioritySharedPtr createRetryPriority(const Protobuf::Message&, + ProtobufMessage::ValidationVisitor&, + uint32_t) override { return std::make_shared>(retry_priority_); } diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index da55f2095f..4c4af655bb 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -34,7 +34,7 @@ BAZEL_USE_LLVM_NATIVE_COVERAGE=1 GCOV=llvm-profdata bazel coverage ${BAZEL_BUILD COVERAGE_DIR="${SRCDIR}"/generated/coverage mkdir -p "${COVERAGE_DIR}" -COVERAGE_IGNORE_REGEX="(/external/|pb\.(validate\.)?(h|cc)|/chromium_url/|/test/|/tmp)" +COVERAGE_IGNORE_REGEX="(/external/|pb\.(validate\.)?(h|cc)|/chromium_url/|/test/|/tmp|/source/extensions/quic_listeners/quiche/)" COVERAGE_BINARY="bazel-bin/test/coverage/coverage_tests" COVERAGE_DATA="${COVERAGE_DIR}/coverage.dat" diff --git a/test/server/BUILD b/test/server/BUILD index 6f2893539e..161688205c 100644 --- a/test/server/BUILD +++ b/test/server/BUILD @@ -53,10 +53,12 @@ envoy_cc_test( "//source/common/common:utility_lib", "//source/common/network:address_lib", "//source/common/stats:stats_lib", + "//source/server:active_raw_udp_listener_config", "//source/server:connection_handler_lib", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", "//test/test_common:network_utility_lib", + "//test/test_common:threadsafe_singleton_injector_lib", ], ) @@ -160,11 +162,25 @@ envoy_cc_test( ], ) +envoy_cc_test_library( + name = "listener_manager_impl_test_lib", + hdrs = ["listener_manager_impl_test.h"], + deps = [ + "//source/server:listener_manager_lib", + "//test/mocks/network:network_mocks", + "//test/mocks/server:server_mocks", + "//test/test_common:environment_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:test_time_lib", + ], +) + envoy_cc_test( name = "listener_manager_impl_test", srcs = ["listener_manager_impl_test.cc"], data = ["//test/extensions/transport_sockets/tls/test_data:certs"], deps = [ + ":listener_manager_impl_test_lib", ":utility_lib", "//source/common/api:os_sys_calls_lib", "//source/common/config:metadata_lib", @@ -180,13 +196,21 @@ envoy_cc_test( "//source/extensions/transport_sockets/raw_buffer:config", "//source/extensions/transport_sockets/tls:config", "//source/extensions/transport_sockets/tls:ssl_socket_lib", - "//source/server:listener_manager_lib", - "//test/mocks/network:network_mocks", - "//test/mocks/server:server_mocks", - "//test/test_common:environment_lib", + "//source/server:active_raw_udp_listener_config", "//test/test_common:registry_lib", - "//test/test_common:simulated_time_system_lib", - "//test/test_common:test_time_lib", + "//test/test_common:threadsafe_singleton_injector_lib", + ], +) + +# Stand-alone quic test because of FIPS. +envoy_cc_test( + name = "listener_manager_impl_quic_only_test", + srcs = ["listener_manager_impl_quic_only_test.cc"], + tags = ["nofips"], + deps = [ + ":listener_manager_impl_test_lib", + "//source/extensions/quic_listeners/quiche:active_quic_listener_config_lib", + "//source/extensions/transport_sockets/raw_buffer:config", "//test/test_common:threadsafe_singleton_injector_lib", ], ) @@ -259,6 +283,7 @@ envoy_cc_test( ":invalid_layered_runtime_duplicate_name.yaml", ":invalid_layered_runtime_missing_name.yaml", ":invalid_layered_runtime_no_layer_specifier.yaml", + ":invalid_legacy_runtime_bootstrap.yaml", ":invalid_runtime_bootstrap.yaml", ":node_bootstrap.yaml", ":node_bootstrap_no_admin_port.yaml", diff --git a/test/server/config_validation/cluster_manager_test.cc b/test/server/config_validation/cluster_manager_test.cc index 0d3639d486..0653c0cc8a 100644 --- a/test/server/config_validation/cluster_manager_test.cc +++ b/test/server/config_validation/cluster_manager_test.cc @@ -51,7 +51,6 @@ TEST(ValidationClusterManagerTest, MockedMethods) { log_manager, singleton_manager, time_system); const envoy::config::bootstrap::v2::Bootstrap bootstrap; - Stats::FakeSymbolTableImpl symbol_table; ClusterManagerPtr cluster_manager = factory.clusterManagerFromProto(bootstrap); EXPECT_EQ(nullptr, cluster_manager->httpConnPoolForCluster("cluster", ResourcePriority::Default, Http::Protocol::Http11, nullptr)); diff --git a/test/server/configuration_impl_test.cc b/test/server/configuration_impl_test.cc index dbc13a45d1..823290aa4f 100644 --- a/test/server/configuration_impl_test.cc +++ b/test/server/configuration_impl_test.cc @@ -22,9 +22,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::InSequence; using testing::Return; -using testing::ReturnRef; namespace Envoy { namespace Server { diff --git a/test/server/connection_handler_test.cc b/test/server/connection_handler_test.cc index 7e4e2e0323..a6dadb2b9e 100644 --- a/test/server/connection_handler_test.cc +++ b/test/server/connection_handler_test.cc @@ -1,7 +1,9 @@ +#include "envoy/server/active_udp_listener_config.h" #include "envoy/stats/scope.h" #include "common/common/utility.h" #include "common/network/address_impl.h" +#include "common/network/io_socket_handle_impl.h" #include "common/network/raw_buffer_socket.h" #include "common/network/utility.h" @@ -10,6 +12,7 @@ #include "test/mocks/network/mocks.h" #include "test/mocks/server/mocks.h" #include "test/test_common/network_utility.h" +#include "test/test_common/threadsafe_singleton_injector.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -26,7 +29,6 @@ using testing::ReturnRef; namespace Envoy { namespace Server { namespace { - class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable { public: ConnectionHandlerTest() @@ -44,6 +46,12 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable(listener_name) + .createActiveUdpListenerFactory(dummy); EXPECT_CALL(socket_, socketType()).WillOnce(Return(socket_type)); } @@ -66,6 +74,9 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable udp_listener_factory_; }; using TestListenerPtr = std::unique_ptr; @@ -99,6 +111,8 @@ class ConnectionHandlerTest : public testing::Test, protected Logger::Loggable factory_; std::list listeners_; const Network::FilterChainSharedPtr filter_chain_; + NiceMock os_sys_calls_; + TestThreadsafeSingletonInjector os_calls_{&os_sys_calls_}; }; TEST_F(ConnectionHandlerTest, RemoveListener) { @@ -600,9 +614,11 @@ TEST_F(ConnectionHandlerTest, ListenerFilterTimeout) { .WillOnce(Invoke([&](Network::ListenerFilterCallbacks&) -> Network::FilterStatus { return Network::FilterStatus::StopIteration; })); - Event::MockTimer* timeout = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*timeout, enableTimer(std::chrono::milliseconds(15000))); Network::MockConnectionSocket* accepted_socket = new NiceMock(); + Network::IoSocketHandleImpl io_handle{42}; + EXPECT_CALL(*accepted_socket, ioHandle()).WillRepeatedly(ReturnRef(io_handle)); + Event::MockTimer* timeout = new Event::MockTimer(&dispatcher_); + EXPECT_CALL(*timeout, enableTimer(std::chrono::milliseconds(15000), _)); listener_callbacks->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); Stats::Gauge& downstream_pre_cx_active = stats_store_.gauge("downstream_pre_cx_active", Stats::Gauge::ImportMode::Accumulate); @@ -648,9 +664,11 @@ TEST_F(ConnectionHandlerTest, ContinueOnListenerFilterTimeout) { .WillOnce(Invoke([&](Network::ListenerFilterCallbacks&) -> Network::FilterStatus { return Network::FilterStatus::StopIteration; })); - Event::MockTimer* timeout = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*timeout, enableTimer(std::chrono::milliseconds(15000))); Network::MockConnectionSocket* accepted_socket = new NiceMock(); + Network::IoSocketHandleImpl io_handle{42}; + EXPECT_CALL(*accepted_socket, ioHandle()).WillRepeatedly(ReturnRef(io_handle)); + Event::MockTimer* timeout = new Event::MockTimer(&dispatcher_); + EXPECT_CALL(*timeout, enableTimer(std::chrono::milliseconds(15000), _)); listener_callbacks->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); Stats::Gauge& downstream_pre_cx_active = stats_store_.gauge("downstream_pre_cx_active", Stats::Gauge::ImportMode::Accumulate); @@ -697,9 +715,12 @@ TEST_F(ConnectionHandlerTest, ListenerFilterTimeoutResetOnSuccess) { listener_filter_cb = &cb; return Network::FilterStatus::StopIteration; })); - Event::MockTimer* timeout = new Event::MockTimer(&dispatcher_); - EXPECT_CALL(*timeout, enableTimer(std::chrono::milliseconds(15000))); Network::MockConnectionSocket* accepted_socket = new NiceMock(); + Network::IoSocketHandleImpl io_handle{42}; + EXPECT_CALL(*accepted_socket, ioHandle()).WillRepeatedly(ReturnRef(io_handle)); + + Event::MockTimer* timeout = new Event::MockTimer(&dispatcher_); + EXPECT_CALL(*timeout, enableTimer(std::chrono::milliseconds(15000), _)); listener_callbacks->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); EXPECT_CALL(manager_, findFilterChain(_)).WillOnce(Return(nullptr)); @@ -744,6 +765,51 @@ TEST_F(ConnectionHandlerTest, ListenerFilterDisabledTimeout) { EXPECT_CALL(*listener, onDestroy()); } +// Listener Filter could close socket in the context of listener callback. +TEST_F(ConnectionHandlerTest, ListenerFilterReportError) { + InSequence s; + + TestListener* test_listener = addListener(1, true, false, "test_listener"); + Network::MockListener* listener = new Network::MockListener(); + Network::ListenerCallbacks* listener_callbacks; + EXPECT_CALL(dispatcher_, createListener_(_, _, _, false)) + .WillOnce(Invoke( + [&](Network::Socket&, Network::ListenerCallbacks& cb, bool, bool) -> Network::Listener* { + listener_callbacks = &cb; + return listener; + })); + EXPECT_CALL(test_listener->socket_, localAddress()); + handler_->addListener(*test_listener); + + Network::MockListenerFilter* first_filter = new Network::MockListenerFilter(); + Network::MockListenerFilter* last_filter = new Network::MockListenerFilter(); + + EXPECT_CALL(factory_, createListenerFilterChain(_)) + .WillRepeatedly(Invoke([&](Network::ListenerFilterManager& manager) -> bool { + manager.addAcceptFilter(Network::ListenerFilterPtr{first_filter}); + manager.addAcceptFilter(Network::ListenerFilterPtr{last_filter}); + return true; + })); + // The first filter close the socket + EXPECT_CALL(*first_filter, onAccept(_)) + .WillOnce(Invoke([&](Network::ListenerFilterCallbacks& cb) -> Network::FilterStatus { + cb.socket().close(); + return Network::FilterStatus::StopIteration; + })); + // The last filter won't be invoked + EXPECT_CALL(*last_filter, onAccept(_)).Times(0); + Network::MockConnectionSocket* accepted_socket = new NiceMock(); + listener_callbacks->onAccept(Network::ConnectionSocketPtr{accepted_socket}, true); + + dispatcher_.clearDeferredDeleteList(); + // Make sure the error leads to no listener timer created. + EXPECT_CALL(dispatcher_, createTimer_(_)).Times(0); + // Make sure we never try to match the filer chain since listener filter doesn't complete. + EXPECT_CALL(manager_, findFilterChain(_)).Times(0); + + EXPECT_CALL(*listener, onDestroy()); +} + // Ensure an exception is thrown if there are no filters registered for a UDP listener TEST_F(ConnectionHandlerTest, UdpListenerNoFilterThrowsException) { InSequence s; @@ -753,9 +819,10 @@ TEST_F(ConnectionHandlerTest, UdpListenerNoFilterThrowsException) { std::chrono::milliseconds()); Network::MockUdpListener* listener = new Network::MockUdpListener(); EXPECT_CALL(dispatcher_, createUdpListener_(_, _)) - .WillOnce(Invoke([&](Network::Socket&, Network::UdpListenerCallbacks&) -> Network::Listener* { - return listener; - })); + .WillOnce( + Invoke([&](Network::Socket&, Network::UdpListenerCallbacks&) -> Network::UdpListener* { + return listener; + })); EXPECT_CALL(factory_, createUdpListenerFilterChain(_, _)) .WillOnce(Invoke([&](Network::UdpListenerFilterManager&, Network::UdpReadFilterCallbacks&) -> bool { return true; })); diff --git a/test/server/drain_manager_impl_test.cc b/test/server/drain_manager_impl_test.cc index 58919df1ce..8197d23356 100644 --- a/test/server/drain_manager_impl_test.cc +++ b/test/server/drain_manager_impl_test.cc @@ -10,7 +10,6 @@ using testing::_; using testing::InSequence; using testing::Return; -using testing::SaveArg; namespace Envoy { namespace Server { @@ -33,7 +32,7 @@ TEST_F(DrainManagerImplTest, Default) { // Test parent shutdown. Event::MockTimer* shutdown_timer = new Event::MockTimer(&server_.dispatcher_); - EXPECT_CALL(*shutdown_timer, enableTimer(std::chrono::milliseconds(900000))); + EXPECT_CALL(*shutdown_timer, enableTimer(std::chrono::milliseconds(900000), _)); drain_manager.startParentShutdownSequence(); EXPECT_CALL(server_.hot_restart_, sendParentTerminateRequest()); @@ -47,14 +46,14 @@ TEST_F(DrainManagerImplTest, Default) { // Test drain sequence. Event::MockTimer* drain_timer = new Event::MockTimer(&server_.dispatcher_); - EXPECT_CALL(*drain_timer, enableTimer(_)); + EXPECT_CALL(*drain_timer, enableTimer(_, _)); ReadyWatcher drain_complete; drain_manager.startDrainSequence([&drain_complete]() -> void { drain_complete.ready(); }); // 600s which is the default drain time. for (size_t i = 0; i < 599; i++) { if (i < 598) { - EXPECT_CALL(*drain_timer, enableTimer(_)); + EXPECT_CALL(*drain_timer, enableTimer(_, _)); } else { EXPECT_CALL(drain_complete, ready()); } diff --git a/test/server/filter_chain_benchmark_test.cc b/test/server/filter_chain_benchmark_test.cc index 0517635414..1ce0577c14 100644 --- a/test/server/filter_chain_benchmark_test.cc +++ b/test/server/filter_chain_benchmark_test.cc @@ -149,6 +149,7 @@ const char YamlSingleDstPortBottom[] = R"EOF( class FilterChainBenchmarkFixture : public benchmark::Fixture { public: + using Fixture::SetUp; void SetUp(const ::benchmark::State& state) override { int64_t input_size = state.range(0); std::vector port_chains; diff --git a/test/server/filter_chain_manager_impl_test.cc b/test/server/filter_chain_manager_impl_test.cc index 3a6f3caee1..ee5bc1b15b 100644 --- a/test/server/filter_chain_manager_impl_test.cc +++ b/test/server/filter_chain_manager_impl_test.cc @@ -37,13 +37,9 @@ #include "absl/strings/match.h" #include "gtest/gtest.h" -using testing::_; -using testing::InSequence; -using testing::Invoke; using testing::NiceMock; using testing::Return; using testing::ReturnRef; -using testing::Throw; namespace Envoy { namespace Server { diff --git a/test/server/guarddog_impl_test.cc b/test/server/guarddog_impl_test.cc index e4f5d35f8e..66f5b11712 100644 --- a/test/server/guarddog_impl_test.cc +++ b/test/server/guarddog_impl_test.cc @@ -36,7 +36,7 @@ class DebugTestInterlock : public GuardDogImpl::TestInterlockHook { } void waitFromTest(Thread::MutexBasicLockable& mutex, MonotonicTime time) override - EXCLUSIVE_LOCKS_REQUIRED(mutex) { + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { while (impl_reached_ < time) { impl_.wait(mutex); } diff --git a/test/server/hot_restart_impl_test.cc b/test/server/hot_restart_impl_test.cc index 94ab208fae..817aae5e3a 100644 --- a/test/server/hot_restart_impl_test.cc +++ b/test/server/hot_restart_impl_test.cc @@ -20,8 +20,6 @@ using testing::_; using testing::AnyNumber; using testing::Invoke; using testing::InvokeWithoutArgs; -using testing::Return; -using testing::ReturnRef; using testing::WithArg; namespace Envoy { diff --git a/test/server/hot_restarting_parent_test.cc b/test/server/hot_restarting_parent_test.cc index 3b2fc96179..7b65c3316e 100644 --- a/test/server/hot_restarting_parent_test.cc +++ b/test/server/hot_restarting_parent_test.cc @@ -6,7 +6,6 @@ #include "gtest/gtest.h" -using testing::_; using testing::Return; using testing::ReturnRef; diff --git a/test/server/http/BUILD b/test/server/http/BUILD index f513226fd1..20df4a917b 100644 --- a/test/server/http/BUILD +++ b/test/server/http/BUILD @@ -19,6 +19,7 @@ envoy_cc_test( "//source/common/profiler:profiler_lib", "//source/common/protobuf", "//source/common/protobuf:utility_lib", + "//source/common/stats:symbol_table_creator_lib", "//source/common/stats:thread_local_store_lib", "//source/extensions/transport_sockets/tls:context_config_lib", "//source/server/http:admin_lib", diff --git a/test/server/http/admin_test.cc b/test/server/http/admin_test.cc index b6c34ebf50..b18ca9a557 100644 --- a/test/server/http/admin_test.cc +++ b/test/server/http/admin_test.cc @@ -14,12 +14,14 @@ #include "common/profiler/profiler.h" #include "common/protobuf/protobuf.h" #include "common/protobuf/utility.h" +#include "common/stats/symbol_table_creator.h" #include "common/stats/thread_local_store.h" #include "server/http/admin.h" #include "extensions/transport_sockets/tls/context_config_impl.h" +#include "test/mocks/http/mocks.h" #include "test/mocks/runtime/mocks.h" #include "test/mocks/server/mocks.h" #include "test/test_common/environment.h" @@ -50,7 +52,8 @@ namespace Server { class AdminStatsTest : public testing::TestWithParam { public: - AdminStatsTest() : alloc_(symbol_table_) { + AdminStatsTest() + : symbol_table_(Stats::SymbolTableCreator::makeSymbolTable()), alloc_(*symbol_table_) { store_ = std::make_unique(alloc_); store_->addSink(sink_); } @@ -63,7 +66,7 @@ class AdminStatsTest : public testing::TestWithParam main_thread_dispatcher_; NiceMock tls_; Stats::AllocatorImpl alloc_; @@ -87,6 +90,17 @@ class AdminFilterTest : public testing::TestWithParam initManager; ON_CALL(server_, initManager()).WillByDefault(ReturnRef(initManager)); + ON_CALL(server_.hot_restart_, version()).WillByDefault(Return("foo_version")); { Http::HeaderMapImpl response_headers; @@ -1252,6 +1267,7 @@ TEST_P(AdminInstanceTest, GetRequest) { // values such as timestamps + Envoy version are tricky to test for. TestUtility::loadFromJson(body, server_info_proto); EXPECT_EQ(server_info_proto.state(), envoy::admin::v2alpha::ServerInfo::LIVE); + EXPECT_EQ(server_info_proto.hot_restart_version(), "foo_version"); EXPECT_EQ(server_info_proto.command_line_options().restart_epoch(), 2); EXPECT_EQ(server_info_proto.command_line_options().service_cluster(), "cluster"); } @@ -1374,15 +1390,16 @@ class HistogramWrapper { class PrometheusStatsFormatterTest : public testing::Test { protected: - PrometheusStatsFormatterTest() : alloc_(symbol_table_) {} + PrometheusStatsFormatterTest() + : symbol_table_(Stats::SymbolTableCreator::makeSymbolTable()), alloc_(*symbol_table_) {} void addCounter(const std::string& name, std::vector cluster_tags) { - Stats::StatNameManagedStorage storage(name, symbol_table_); + Stats::StatNameManagedStorage storage(name, *symbol_table_); counters_.push_back(alloc_.makeCounter(storage.statName(), name, cluster_tags)); } void addGauge(const std::string& name, std::vector cluster_tags) { - Stats::StatNameManagedStorage storage(name, symbol_table_); + Stats::StatNameManagedStorage storage(name, *symbol_table_); gauges_.push_back(alloc_.makeGauge(storage.statName(), name, cluster_tags, Stats::Gauge::ImportMode::Accumulate)); } @@ -1396,7 +1413,7 @@ class PrometheusStatsFormatterTest : public testing::Test { return MockHistogramSharedPtr(new NiceMock()); } - Stats::FakeSymbolTableImpl symbol_table_; + Stats::SymbolTablePtr symbol_table_; Stats::AllocatorImpl alloc_; std::vector counters_; std::vector gauges_; diff --git a/test/server/http/config_tracker_impl_test.cc b/test/server/http/config_tracker_impl_test.cc index 60d4633f08..2fcd777fca 100644 --- a/test/server/http/config_tracker_impl_test.cc +++ b/test/server/http/config_tracker_impl_test.cc @@ -4,8 +4,6 @@ #include "gmock/gmock.h" -using testing::_; - namespace Envoy { namespace Server { diff --git a/test/server/invalid_legacy_runtime_bootstrap.yaml b/test/server/invalid_legacy_runtime_bootstrap.yaml new file mode 100644 index 0000000000..99c67b7d2d --- /dev/null +++ b/test/server/invalid_legacy_runtime_bootstrap.yaml @@ -0,0 +1,4 @@ +runtime: + base: + foo: + - bar: baz diff --git a/test/server/invalid_runtime_bootstrap.yaml b/test/server/invalid_runtime_bootstrap.yaml index 99c67b7d2d..3ed04a71c3 100644 --- a/test/server/invalid_runtime_bootstrap.yaml +++ b/test/server/invalid_runtime_bootstrap.yaml @@ -1,4 +1,6 @@ -runtime: - base: - foo: - - bar: baz +layered_runtime: + layers: + - name: some_static_layer + static_layer: + foo: + - bar: baz diff --git a/test/server/lds_api_test.cc b/test/server/lds_api_test.cc index 3f54aed535..1cedee3abb 100644 --- a/test/server/lds_api_test.cc +++ b/test/server/lds_api_test.cc @@ -18,7 +18,6 @@ using testing::_; using testing::InSequence; using testing::Invoke; using testing::Return; -using testing::ReturnRef; using testing::Throw; namespace Envoy { diff --git a/test/server/listener_manager_impl_quic_only_test.cc b/test/server/listener_manager_impl_quic_only_test.cc new file mode 100644 index 0000000000..ad0f08d1a8 --- /dev/null +++ b/test/server/listener_manager_impl_quic_only_test.cc @@ -0,0 +1,46 @@ +#include "test/server/listener_manager_impl_test.h" +#include "test/test_common/threadsafe_singleton_injector.h" + +using testing::AtLeast; + +namespace Envoy { +namespace Server { +namespace { + +class ListenerManagerImplQuicOnlyTest : public ListenerManagerImplTest { +protected: + NiceMock os_sys_calls_; + TestThreadsafeSingletonInjector os_calls_{&os_sys_calls_}; +}; + +TEST_F(ListenerManagerImplQuicOnlyTest, QuicListenerFactory) { + const std::string proto_text = R"EOF( +address: { + socket_address: { + protocol: UDP + address: "127.0.0.1" + port_value: 1234 + } +} +filter_chains: {} +udp_listener_config: { + udp_listener_name: "quiche_quic_listener" + config: {} +} + )EOF"; + envoy::api::v2::Listener listener_proto; + EXPECT_TRUE(Protobuf::TextFormat::ParseFromString(proto_text, &listener_proto)); + + EXPECT_CALL(server_.random_, uuid()); + EXPECT_CALL(listener_factory_, + createListenSocket(_, Network::Address::SocketType::Datagram, _, true)); + EXPECT_CALL(os_sys_calls_, setsockopt_(_, _, _, _, _)).Times(testing::AtLeast(1)); + EXPECT_CALL(os_sys_calls_, close(_)).WillRepeatedly(Return(Api::SysCallIntResult{0, errno})); + manager_->addOrUpdateListener(listener_proto, "", true); + EXPECT_EQ(1u, manager_->listeners().size()); + EXPECT_NE(nullptr, manager_->listeners()[0].get().udpListenerFactory()); +} + +} // namespace +} // namespace Server +} // namespace Envoy diff --git a/test/server/listener_manager_impl_test.cc b/test/server/listener_manager_impl_test.cc index 768468f8c9..2840964f74 100644 --- a/test/server/listener_manager_impl_test.cc +++ b/test/server/listener_manager_impl_test.cc @@ -1,10 +1,11 @@ +#include "test/server/listener_manager_impl_test.h" + #include #include #include #include #include -#include "envoy/admin/v2alpha/config_dump.pb.h" #include "envoy/registry/registry.h" #include "envoy/server/filter_config.h" @@ -17,134 +18,25 @@ #include "common/network/utility.h" #include "common/protobuf/protobuf.h" -#include "server/configuration_impl.h" -#include "server/listener_manager_impl.h" - #include "extensions/filters/listener/original_dst/original_dst.h" #include "extensions/transport_sockets/tls/ssl_socket.h" -#include "test/mocks/network/mocks.h" -#include "test/mocks/server/mocks.h" #include "test/server/utility.h" -#include "test/test_common/environment.h" #include "test/test_common/registry.h" -#include "test/test_common/simulated_time_system.h" #include "test/test_common/threadsafe_singleton_injector.h" #include "test/test_common/utility.h" #include "absl/strings/escaping.h" #include "absl/strings/match.h" -#include "gtest/gtest.h" -using testing::_; using testing::AtLeast; using testing::InSequence; -using testing::Invoke; -using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; using testing::Throw; namespace Envoy { namespace Server { namespace { -class ListenerHandle { -public: - ListenerHandle() { EXPECT_CALL(*drain_manager_, startParentShutdownSequence()).Times(0); } - ~ListenerHandle() { onDestroy(); } - - MOCK_METHOD0(onDestroy, void()); - - Init::ExpectableTargetImpl target_; - MockDrainManager* drain_manager_ = new MockDrainManager(); - Configuration::FactoryContext* context_{}; -}; - -class ListenerManagerImplTest : public testing::Test { -protected: - ListenerManagerImplTest() : api_(Api::createApiForTest()) { - ON_CALL(server_, api()).WillByDefault(ReturnRef(*api_)); - EXPECT_CALL(worker_factory_, createWorker_()).WillOnce(Return(worker_)); - manager_ = - std::make_unique(server_, listener_factory_, worker_factory_, false); - } - - /** - * This routing sets up an expectation that does various things: - * 1) Allows us to track listener destruction via filter factory destruction. - * 2) Allows us to register for init manager handling much like RDS, etc. would do. - * 3) Stores the factory context for later use. - * 4) Creates a mock local drain manager for the listener. - */ - ListenerHandle* expectListenerCreate( - bool need_init, bool added_via_api, - envoy::api::v2::Listener::DrainType drain_type = envoy::api::v2::Listener_DrainType_DEFAULT) { - if (added_via_api) { - EXPECT_CALL(server_.validation_context_, staticValidationVisitor()).Times(0); - EXPECT_CALL(server_.validation_context_, dynamicValidationVisitor()); - } else { - EXPECT_CALL(server_.validation_context_, staticValidationVisitor()); - EXPECT_CALL(server_.validation_context_, dynamicValidationVisitor()).Times(0); - } - ListenerHandle* raw_listener = new ListenerHandle(); - EXPECT_CALL(listener_factory_, createDrainManager_(drain_type)) - .WillOnce(Return(raw_listener->drain_manager_)); - EXPECT_CALL(listener_factory_, createNetworkFilterFactoryList(_, _)) - .WillOnce(Invoke( - [raw_listener, need_init]( - const Protobuf::RepeatedPtrField&, - Configuration::FactoryContext& context) -> std::vector { - std::shared_ptr notifier(raw_listener); - raw_listener->context_ = &context; - if (need_init) { - context.initManager().add(notifier->target_); - } - return {[notifier](Network::FilterManager&) -> void {}}; - })); - - return raw_listener; - } - - void checkStats(uint64_t added, uint64_t modified, uint64_t removed, uint64_t warming, - uint64_t active, uint64_t draining) { - EXPECT_EQ(added, server_.stats_store_.counter("listener_manager.listener_added").value()); - EXPECT_EQ(modified, server_.stats_store_.counter("listener_manager.listener_modified").value()); - EXPECT_EQ(removed, server_.stats_store_.counter("listener_manager.listener_removed").value()); - EXPECT_EQ(warming, server_.stats_store_ - .gauge("listener_manager.total_listeners_warming", - Stats::Gauge::ImportMode::NeverImport) - .value()); - EXPECT_EQ(active, server_.stats_store_ - .gauge("listener_manager.total_listeners_active", - Stats::Gauge::ImportMode::NeverImport) - .value()); - EXPECT_EQ(draining, server_.stats_store_ - .gauge("listener_manager.total_listeners_draining", - Stats::Gauge::ImportMode::NeverImport) - .value()); - } - - void checkConfigDump(const std::string& expected_dump_yaml) { - auto message_ptr = server_.admin_.config_tracker_.config_tracker_callbacks_["listeners"](); - const auto& listeners_config_dump = - dynamic_cast(*message_ptr); - - envoy::admin::v2alpha::ListenersConfigDump expected_listeners_config_dump; - TestUtility::loadFromYaml(expected_dump_yaml, expected_listeners_config_dump); - EXPECT_EQ(expected_listeners_config_dump.DebugString(), listeners_config_dump.DebugString()); - } - - NiceMock server_; - NiceMock listener_factory_; - MockWorker* worker_ = new MockWorker(); - NiceMock worker_factory_; - std::unique_ptr manager_; - NiceMock guard_dog_; - Event::SimulatedTimeSystem time_system_; - Api::ApiPtr api_; -}; - class ListenerManagerImplWithRealFiltersTest : public ListenerManagerImplTest { public: ListenerManagerImplWithRealFiltersTest() { @@ -176,6 +68,7 @@ class ListenerManagerImplWithRealFiltersTest : public ListenerManagerImplTest { socket_ = std::make_unique>(); local_address_.reset(new Network::Address::Ipv4Instance("127.0.0.1", 1234)); remote_address_.reset(new Network::Address::Ipv4Instance("127.0.0.1", 1234)); + EXPECT_CALL(os_sys_calls_, close(_)).WillRepeatedly(Return(Api::SysCallIntResult{0, errno})); } const Network::FilterChain* @@ -265,11 +158,9 @@ class ListenerManagerImplWithRealFiltersTest : public ListenerManagerImplTest { const envoy::api::v2::core::SocketOption::SocketState& expected_state, const Network::SocketOptionName& expected_option, int expected_value, uint32_t expected_num_options = 1) { - NiceMock os_sys_calls; - TestThreadsafeSingletonInjector os_calls(&os_sys_calls); if (expected_option.has_value()) { expectCreateListenSocket(expected_state, expected_num_options); - expectSetsockopt(os_sys_calls, expected_option.level(), expected_option.option(), + expectSetsockopt(os_sys_calls_, expected_option.level(), expected_option.option(), expected_value, expected_num_options); manager_->addOrUpdateListener(listener, "", true); EXPECT_EQ(1U, manager_->listeners().size()); @@ -280,6 +171,10 @@ class ListenerManagerImplWithRealFiltersTest : public ListenerManagerImplTest { } } +protected: + NiceMock os_sys_calls_; + TestThreadsafeSingletonInjector os_calls_{&os_sys_calls_}; + private: std::unique_ptr socket_; Network::Address::InstanceConstSharedPtr local_address_; @@ -390,9 +285,8 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, UdpAddress) { EXPECT_CALL(server_.random_, uuid()); EXPECT_CALL(listener_factory_, createListenSocket(_, Network::Address::SocketType::Datagram, _, true)); - NiceMock os_sys_calls; - TestThreadsafeSingletonInjector os_calls(&os_sys_calls); - EXPECT_CALL(os_sys_calls, setsockopt_(_, _, _, _, _)).Times(testing::AtLeast(1)); + EXPECT_CALL(os_sys_calls_, setsockopt_(_, _, _, _, _)).Times(testing::AtLeast(1)); + EXPECT_CALL(os_sys_calls_, close(_)).WillRepeatedly(Return(Api::SysCallIntResult{0, errno})); manager_->addOrUpdateListener(listener_proto, "", true); EXPECT_EQ(1u, manager_->listeners().size()); } @@ -694,6 +588,7 @@ drain_type: default ON_CALL(os_sys_calls, socket(AF_INET, _, 0)).WillByDefault(Return(Api::SysCallIntResult{5, 0})); ON_CALL(os_sys_calls, socket(AF_INET6, _, 0)).WillByDefault(Return(Api::SysCallIntResult{-1, 0})); + ON_CALL(os_sys_calls, close(_)).WillByDefault(Return(Api::SysCallIntResult{0, 0})); EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, true)); @@ -726,6 +621,7 @@ drain_type: default ON_CALL(os_sys_calls, socket(AF_INET, _, 0)).WillByDefault(Return(Api::SysCallIntResult{-1, 0})); ON_CALL(os_sys_calls, socket(AF_INET6, _, 0)).WillByDefault(Return(Api::SysCallIntResult{5, 0})); + ON_CALL(os_sys_calls, close(_)).WillByDefault(Return(Api::SysCallIntResult{0, 0})); EXPECT_CALL(listener_factory_, createListenSocket(_, _, _, true)); @@ -1433,7 +1329,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDestinationP auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -1476,7 +1372,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithDestinationI auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -1524,7 +1420,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithServerNamesM auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); } @@ -1563,7 +1459,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithTransportPro auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); } @@ -1603,7 +1499,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithApplicationP auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); } @@ -1644,7 +1540,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceTypeMa auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -1655,7 +1551,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceTypeMa EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - server_names = ssl_socket->dnsSansLocalCertificate(); + server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); } @@ -1698,7 +1594,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourceIpMatc auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -1782,7 +1678,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, SingleFilterChainWithSourcePortMa auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -1842,7 +1738,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -1852,7 +1748,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - auto uri = ssl_socket->uriSanLocalCertificate(); + auto uri = ssl_socket->ssl()->uriSanLocalCertificate(); EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); // EXTERNAL TLS client without "http/1.1" ALPN - using 3nd filter chain. @@ -1861,7 +1757,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainWithSourceType EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - server_names = ssl_socket->dnsSansLocalCertificate(); + server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 2); EXPECT_EQ(server_names.front(), "*.example.com"); } @@ -1910,7 +1806,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto uri = ssl_socket->uriSanLocalCertificate(); + auto uri = ssl_socket->ssl()->uriSanLocalCertificate(); EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); // IPv4 client connects to port 8080 - using 2nd filter chain. @@ -1919,7 +1815,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -1929,7 +1825,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - server_names = ssl_socket->dnsSansLocalCertificate(); + server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 2); EXPECT_EQ(server_names.front(), "*.example.com"); @@ -1939,7 +1835,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - uri = ssl_socket->uriSanLocalCertificate(); + uri = ssl_socket->ssl()->uriSanLocalCertificate(); EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); } @@ -1987,7 +1883,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto uri = ssl_socket->uriSanLocalCertificate(); + auto uri = ssl_socket->ssl()->uriSanLocalCertificate(); EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); // IPv4 client connects to exact IP match - using 2nd filter chain. @@ -1996,7 +1892,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -2006,7 +1902,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - server_names = ssl_socket->dnsSansLocalCertificate(); + server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 2); EXPECT_EQ(server_names.front(), "*.example.com"); @@ -2016,7 +1912,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithDestinati EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - uri = ssl_socket->uriSanLocalCertificate(); + uri = ssl_socket->ssl()->uriSanLocalCertificate(); EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); } @@ -2073,7 +1969,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto uri = ssl_socket->uriSanLocalCertificate(); + auto uri = ssl_socket->ssl()->uriSanLocalCertificate(); EXPECT_EQ(uri[0], "spiffe://lyft.com/test-team"); // TLS client with exact SNI match - using 2nd filter chain. @@ -2083,7 +1979,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); @@ -2094,7 +1990,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - server_names = ssl_socket->dnsSansLocalCertificate(); + server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 2); EXPECT_EQ(server_names.front(), "*.example.com"); @@ -2105,7 +2001,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithServerNam EXPECT_TRUE(filter_chain->transportSocketFactory().implementsSecureTransport()); transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); ssl_socket = dynamic_cast(transport_socket.get()); - server_names = ssl_socket->dnsSansLocalCertificate(); + server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 2); EXPECT_EQ(server_names.front(), "*.example.com"); } @@ -2147,7 +2043,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithTransport auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); } @@ -2190,7 +2086,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithApplicati auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); } @@ -2246,7 +2142,7 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, MultipleFilterChainsWithMultipleR auto transport_socket = filter_chain->transportSocketFactory().createTransportSocket(nullptr); auto ssl_socket = dynamic_cast(transport_socket.get()); - auto server_names = ssl_socket->dnsSansLocalCertificate(); + auto server_names = ssl_socket->ssl()->dnsSansLocalCertificate(); EXPECT_EQ(server_names.size(), 1); EXPECT_EQ(server_names.front(), "server1.example.com"); } @@ -2926,7 +2822,6 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, TransparentFreebindListenerDisabl TEST_F(ListenerManagerImplWithRealFiltersTest, TransparentListenerEnabled) { auto listener = createIPv4Listener("TransparentListener"); listener.mutable_transparent()->set_value(true); - testSocketOption(listener, envoy::api::v2::core::SocketOption::STATE_PREBIND, ENVOY_SOCKET_IP_TRANSPARENT, /* expected_value */ 1, /* expected_num_options */ 2); @@ -2961,9 +2856,6 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, FastOpenListenerEnabled) { } TEST_F(ListenerManagerImplWithRealFiltersTest, LiteralSockoptListenerEnabled) { - NiceMock os_sys_calls; - TestThreadsafeSingletonInjector os_calls(&os_sys_calls); - const envoy::api::v2::Listener listener = parseListenerFromV2Yaml(R"EOF( name: SockoptsListener address: @@ -2981,11 +2873,11 @@ TEST_F(ListenerManagerImplWithRealFiltersTest, LiteralSockoptListenerEnabled) { expectCreateListenSocket(envoy::api::v2::core::SocketOption::STATE_PREBIND, /* expected_num_options */ 3); - expectSetsockopt(os_sys_calls, + expectSetsockopt(os_sys_calls_, /* expected_sockopt_level */ 1, /* expected_sockopt_name */ 2, /* expected_value */ 3); - expectSetsockopt(os_sys_calls, + expectSetsockopt(os_sys_calls_, /* expected_sockopt_level */ 4, /* expected_sockopt_name */ 5, /* expected_value */ 6); diff --git a/test/server/listener_manager_impl_test.h b/test/server/listener_manager_impl_test.h new file mode 100644 index 0000000000..698982d93a --- /dev/null +++ b/test/server/listener_manager_impl_test.h @@ -0,0 +1,119 @@ +#include "envoy/admin/v2alpha/config_dump.pb.h" + +#include "server/configuration_impl.h" +#include "server/listener_manager_impl.h" + +#include "test/mocks/network/mocks.h" +#include "test/mocks/server/mocks.h" +#include "test/test_common/environment.h" +#include "test/test_common/simulated_time_system.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::_; +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Server { + +class ListenerHandle { +public: + ListenerHandle() { EXPECT_CALL(*drain_manager_, startParentShutdownSequence()).Times(0); } + ~ListenerHandle() { onDestroy(); } + + MOCK_METHOD0(onDestroy, void()); + + Init::ExpectableTargetImpl target_; + MockDrainManager* drain_manager_ = new MockDrainManager(); + Configuration::FactoryContext* context_{}; +}; + +class ListenerManagerImplTest : public testing::Test { +protected: + ListenerManagerImplTest() : api_(Api::createApiForTest()) { + ON_CALL(server_, api()).WillByDefault(ReturnRef(*api_)); + EXPECT_CALL(worker_factory_, createWorker_()).WillOnce(Return(worker_)); + manager_ = + std::make_unique(server_, listener_factory_, worker_factory_, false); + } + + /** + * This routing sets up an expectation that does various things: + * 1) Allows us to track listener destruction via filter factory destruction. + * 2) Allows us to register for init manager handling much like RDS, etc. would do. + * 3) Stores the factory context for later use. + * 4) Creates a mock local drain manager for the listener. + */ + ListenerHandle* expectListenerCreate( + bool need_init, bool added_via_api, + envoy::api::v2::Listener::DrainType drain_type = envoy::api::v2::Listener_DrainType_DEFAULT) { + if (added_via_api) { + EXPECT_CALL(server_.validation_context_, staticValidationVisitor()).Times(0); + EXPECT_CALL(server_.validation_context_, dynamicValidationVisitor()); + } else { + EXPECT_CALL(server_.validation_context_, staticValidationVisitor()); + EXPECT_CALL(server_.validation_context_, dynamicValidationVisitor()).Times(0); + } + auto raw_listener = new ListenerHandle(); + EXPECT_CALL(listener_factory_, createDrainManager_(drain_type)) + .WillOnce(Return(raw_listener->drain_manager_)); + EXPECT_CALL(listener_factory_, createNetworkFilterFactoryList(_, _)) + .WillOnce(Invoke( + [raw_listener, need_init]( + const Protobuf::RepeatedPtrField&, + Configuration::FactoryContext& context) -> std::vector { + std::shared_ptr notifier(raw_listener); + raw_listener->context_ = &context; + if (need_init) { + context.initManager().add(notifier->target_); + } + return {[notifier](Network::FilterManager&) -> void {}}; + })); + + return raw_listener; + } + + void checkStats(uint64_t added, uint64_t modified, uint64_t removed, uint64_t warming, + uint64_t active, uint64_t draining) { + EXPECT_EQ(added, server_.stats_store_.counter("listener_manager.listener_added").value()); + EXPECT_EQ(modified, server_.stats_store_.counter("listener_manager.listener_modified").value()); + EXPECT_EQ(removed, server_.stats_store_.counter("listener_manager.listener_removed").value()); + EXPECT_EQ(warming, server_.stats_store_ + .gauge("listener_manager.total_listeners_warming", + Stats::Gauge::ImportMode::NeverImport) + .value()); + EXPECT_EQ(active, server_.stats_store_ + .gauge("listener_manager.total_listeners_active", + Stats::Gauge::ImportMode::NeverImport) + .value()); + EXPECT_EQ(draining, server_.stats_store_ + .gauge("listener_manager.total_listeners_draining", + Stats::Gauge::ImportMode::NeverImport) + .value()); + } + + void checkConfigDump(const std::string& expected_dump_yaml) { + auto message_ptr = server_.admin_.config_tracker_.config_tracker_callbacks_["listeners"](); + const auto& listeners_config_dump = + dynamic_cast(*message_ptr); + + envoy::admin::v2alpha::ListenersConfigDump expected_listeners_config_dump; + TestUtility::loadFromYaml(expected_dump_yaml, expected_listeners_config_dump); + EXPECT_EQ(expected_listeners_config_dump.DebugString(), listeners_config_dump.DebugString()); + } + + NiceMock server_; + NiceMock listener_factory_; + MockWorker* worker_ = new MockWorker(); + NiceMock worker_factory_; + std::unique_ptr manager_; + NiceMock guard_dog_; + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; +}; + +} // namespace Server +} // namespace Envoy diff --git a/test/server/options_impl_test.cc b/test/server/options_impl_test.cc index 71662bb36b..0a365db185 100644 --- a/test/server/options_impl_test.cc +++ b/test/server/options_impl_test.cc @@ -26,8 +26,6 @@ #include "gtest/gtest.h" #include "spdlog/spdlog.h" -using testing::HasSubstr; - namespace Envoy { namespace { @@ -99,6 +97,7 @@ TEST_F(OptionsImplTest, All) { EXPECT_TRUE(options->cpusetThreadsEnabled()); EXPECT_TRUE(options->allowUnknownStaticFields()); EXPECT_TRUE(options->rejectUnknownDynamicFields()); + EXPECT_TRUE(options->fakeSymbolTableEnabled()); options = createOptionsImpl("envoy --mode init_only"); EXPECT_EQ(Server::Mode::InitOnly, options->mode()); @@ -129,6 +128,7 @@ TEST_F(OptionsImplTest, SetAll) { bool hot_restart_disabled = options->hotRestartDisabled(); bool signal_handling_enabled = options->signalHandlingEnabled(); bool cpuset_threads_enabled = options->cpusetThreadsEnabled(); + bool fake_symbol_table_enabled = options->fakeSymbolTableEnabled(); options->setBaseId(109876); options->setConcurrency(42); @@ -155,6 +155,7 @@ TEST_F(OptionsImplTest, SetAll) { options->setCpusetThreads(!options->cpusetThreadsEnabled()); options->setAllowUnkownFields(true); options->setRejectUnknownFieldsDynamic(true); + options->setFakeSymbolTableEnabled(!options->fakeSymbolTableEnabled()); EXPECT_EQ(109876, options->baseId()); EXPECT_EQ(42U, options->concurrency()); @@ -181,6 +182,7 @@ TEST_F(OptionsImplTest, SetAll) { EXPECT_EQ(!cpuset_threads_enabled, options->cpusetThreadsEnabled()); EXPECT_TRUE(options->allowUnknownStaticFields()); EXPECT_TRUE(options->rejectUnknownDynamicFields()); + EXPECT_EQ(!fake_symbol_table_enabled, options->fakeSymbolTableEnabled()); // Validate that CommandLineOptions is constructed correctly. Server::CommandLineOptionsPtr command_line_options = options->toCommandLineOptions(); @@ -241,13 +243,15 @@ TEST_F(OptionsImplTest, OptionsAreInSyncWithProto) { Server::CommandLineOptionsPtr command_line_options = options->toCommandLineOptions(); // Failure of this condition indicates that the server_info proto is not in sync with the options. // If an option is added/removed, please update server_info proto as well to keep it in sync. - // Currently the following 5 options are not defined in proto, hence the count differs by 5. + // Currently the following 7 options are not defined in proto, hence the count differs by 7. // 1. version - default TCLAP argument. // 2. help - default TCLAP argument. // 3. ignore_rest - default TCLAP argument. // 4. use-libevent-buffers - short-term override for rollout of new buffer implementation. // 5. allow-unknown-fields - deprecated alias of allow-unknown-static-fields. - EXPECT_EQ(options->count() - 5, command_line_options->GetDescriptor()->field_count()); + // 6. use-fake-symbol-table - short-term override for rollout of real symbol-table implementation. + // 7. hot restart version - print the hot restart version and exit. + EXPECT_EQ(options->count() - 7, command_line_options->GetDescriptor()->field_count()); } TEST_F(OptionsImplTest, BadCliOption) { diff --git a/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5734693923717120 b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5734693923717120 new file mode 100644 index 0000000000..187e397539 --- /dev/null +++ b/test/server/server_corpus/clusterfuzz-testcase-server_fuzz_test-5734693923717120 @@ -0,0 +1,18 @@ +static_resources { + clusters { + name: "@" + connect_timeout { + nanos: 250000000 + } + common_lb_config { + zone_aware_lb_config { + min_cluster_size { + value: 38 + } + } + } + } +} +stats_flush_interval { + nanos: 32256 +} diff --git a/test/server/server_fuzz_test.cc b/test/server/server_fuzz_test.cc index 2ee39d2f8d..b47f1aed96 100644 --- a/test/server/server_fuzz_test.cc +++ b/test/server/server_fuzz_test.cc @@ -1,5 +1,7 @@ #include +#include "envoy/config/bootstrap/v2/bootstrap.pb.validate.h" + #include "common/network/address_impl.h" #include "common/thread_local/thread_local_impl.h" diff --git a/test/server/server_test.cc b/test/server/server_test.cc index ca2a755d26..cc7a0f69f6 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -26,8 +26,6 @@ using testing::HasSubstr; using testing::InSequence; using testing::Invoke; using testing::InvokeWithoutArgs; -using testing::Property; -using testing::Ref; using testing::Return; using testing::SaveArg; using testing::StrictMock; @@ -221,21 +219,26 @@ class ServerInstanceImplTestBase { Thread::ThreadPtr startTestServer(const std::string& bootstrap_path, const bool use_intializing_instance) { absl::Notification started; + absl::Notification post_init; auto server_thread = Thread::threadFactoryForTest().createThread([&] { initialize(bootstrap_path, use_intializing_instance); auto startup_handle = server_->registerCallback(ServerLifecycleNotifier::Stage::Startup, [&] { started.Notify(); }); + auto post_init_handle = server_->registerCallback(ServerLifecycleNotifier::Stage::PostInit, + [&] { post_init.Notify(); }); auto shutdown_handle = server_->registerCallback(ServerLifecycleNotifier::Stage::ShutdownExit, [&](Event::PostCb) { FAIL(); }); shutdown_handle = nullptr; // unregister callback server_->run(); startup_handle = nullptr; + post_init_handle = nullptr; server_ = nullptr; thread_local_ = nullptr; }); started.WaitForNotification(); + post_init.WaitForNotification(); return server_thread; } @@ -323,8 +326,8 @@ TEST_P(ServerInstanceImplTest, EmptyShutdownLifecycleNotifications) { } TEST_P(ServerInstanceImplTest, LifecycleNotifications) { - bool startup = false, shutdown = false, shutdown_with_completion = false; - absl::Notification started, shutdown_begin, completion_block, completion_done; + bool startup = false, post_init = false, shutdown = false, shutdown_with_completion = false; + absl::Notification started, post_init_fired, shutdown_begin, completion_block, completion_done; // Run the server in a separate thread so we can test different lifecycle stages. auto server_thread = Thread::threadFactoryForTest().createThread([&] { @@ -333,11 +336,15 @@ TEST_P(ServerInstanceImplTest, LifecycleNotifications) { startup = true; started.Notify(); }); - auto handle2 = server_->registerCallback(ServerLifecycleNotifier::Stage::ShutdownExit, [&] { + auto handle2 = server_->registerCallback(ServerLifecycleNotifier::Stage::PostInit, [&] { + post_init = true; + post_init_fired.Notify(); + }); + auto handle3 = server_->registerCallback(ServerLifecycleNotifier::Stage::ShutdownExit, [&] { shutdown = true; shutdown_begin.Notify(); }); - auto handle3 = server_->registerCallback(ServerLifecycleNotifier::Stage::ShutdownExit, + auto handle4 = server_->registerCallback(ServerLifecycleNotifier::Stage::ShutdownExit, [&](Event::PostCb completion_cb) { // Block till we're told to complete completion_block.WaitForNotification(); @@ -345,16 +352,17 @@ TEST_P(ServerInstanceImplTest, LifecycleNotifications) { server_->dispatcher().post(completion_cb); completion_done.Notify(); }); - auto handle4 = + auto handle5 = server_->registerCallback(ServerLifecycleNotifier::Stage::Startup, [&] { FAIL(); }); - handle4 = server_->registerCallback(ServerLifecycleNotifier::Stage::ShutdownExit, + handle5 = server_->registerCallback(ServerLifecycleNotifier::Stage::ShutdownExit, [&](Event::PostCb) { FAIL(); }); - handle4 = nullptr; + handle5 = nullptr; server_->run(); handle1 = nullptr; handle2 = nullptr; handle3 = nullptr; + handle4 = nullptr; server_ = nullptr; thread_local_ = nullptr; }); @@ -363,6 +371,10 @@ TEST_P(ServerInstanceImplTest, LifecycleNotifications) { EXPECT_TRUE(startup); EXPECT_FALSE(shutdown); + post_init_fired.WaitForNotification(); + EXPECT_TRUE(post_init); + EXPECT_FALSE(shutdown); + server_->dispatcher().post([&] { server_->shutdown(); }); shutdown_begin.WaitForNotification(); EXPECT_TRUE(shutdown); @@ -575,6 +587,11 @@ TEST_P(ServerInstanceImplTest, RuntimeNoAdminLayer) { EXPECT_EQ("No admin layer specified", response_body); } +TEST_P(ServerInstanceImplTest, DEPRECATED_FEATURE_TEST(InvalidLegacyBootstrapRuntime)) { + EXPECT_THROW_WITH_MESSAGE(initialize("test/server/invalid_runtime_bootstrap.yaml"), + EnvoyException, "Invalid runtime entry value for foo"); +} + // Validate invalid runtime in bootstrap is rejected. TEST_P(ServerInstanceImplTest, InvalidBootstrapRuntime) { EXPECT_THROW_WITH_MESSAGE(initialize("test/server/invalid_runtime_bootstrap.yaml"), @@ -696,7 +713,7 @@ TEST_P(ServerInstanceImplTest, EmptyBootstrap) { } // Custom header bootstrap succeeds. -TEST_P(ServerInstanceImplTest, CusomHeaderBoostrap) { +TEST_P(ServerInstanceImplTest, CustomHeaderBootstrap) { options_.config_path_ = TestEnvironment::writeStringToFileForTest( "custom.yaml", "header_prefix: \"x-envoy\"\nstatic_resources:\n"); options_.service_cluster_name_ = "some_cluster_name"; @@ -836,8 +853,8 @@ TEST_P(ServerInstanceImplTest, WithProcessContext) { EXPECT_NO_THROW(initialize("test/server/empty_bootstrap.yaml")); - ProcessContext& context = server_->processContext(); - auto& object_from_context = dynamic_cast(context.get()); + auto context = server_->processContext(); + auto& object_from_context = dynamic_cast(context->get().get()); EXPECT_EQ(&object_from_context, &object); EXPECT_TRUE(object_from_context.boolean_flag_); diff --git a/test/stress/stress_test_upstream.cc b/test/stress/stress_test_upstream.cc index 3e41bf0201..82dee89081 100644 --- a/test/stress/stress_test_upstream.cc +++ b/test/stress/stress_test_upstream.cc @@ -585,6 +585,8 @@ uint64_t Server::listenerTag() const { return 0; } const std::string& Server::name() const { return name_; } +const Network::ActiveUdpListenerFactory* Server::udpListenerFactory() { return nullptr; } + const Network::FilterChain* Server::findFilterChain(const Network::ConnectionSocket&) const { return &server_filter_chain_; } diff --git a/test/stress/stress_test_upstream.h b/test/stress/stress_test_upstream.h index a994957825..a2fde9ba6b 100644 --- a/test/stress/stress_test_upstream.h +++ b/test/stress/stress_test_upstream.h @@ -303,6 +303,8 @@ class Server : public Network::FilterChainManager, const std::string& name() const override; + const Network::ActiveUdpListenerFactory* udpListenerFactory() override; + // // Network::FilterChainManager // diff --git a/test/test_common/BUILD b/test/test_common/BUILD index c1d4c4f692..e1bb1467dc 100644 --- a/test/test_common/BUILD +++ b/test/test_common/BUILD @@ -117,6 +117,21 @@ envoy_cc_test_library( ], ) +envoy_cc_test_library( + name = "test_runtime_lib", + hdrs = ["test_runtime.h"], + deps = [ + "//source/common/runtime:runtime_lib", + "//source/common/stats:isolated_store_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/init:init_mocks", + "//test/mocks/local_info:local_info_mocks", + "//test/mocks/protobuf:protobuf_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/thread_local:thread_local_mocks", + ], +) + envoy_cc_test_library( name = "thread_factory_for_test_lib", srcs = ["thread_factory_for_test.cc"], @@ -231,6 +246,7 @@ envoy_cc_test( ":simulated_time_system_lib", ":utility_lib", "//source/common/event:libevent_scheduler_lib", + "//test/mocks/event:event_mocks", ], ) diff --git a/test/test_common/simulated_time_system.cc b/test/test_common/simulated_time_system.cc index c4801ae68b..43db9e5111 100644 --- a/test/test_common/simulated_time_system.cc +++ b/test/test_common/simulated_time_system.cc @@ -50,8 +50,9 @@ class UnlockGuard { // mechanism used in RealTimeSystem timers is employed for simulated alarms. class SimulatedTimeSystemHelper::Alarm : public Timer { public: - Alarm(SimulatedTimeSystemHelper& time_system, Scheduler& base_scheduler, TimerCb cb) - : base_timer_(base_scheduler.createTimer([this, cb] { runAlarm(cb); })), + Alarm(SimulatedTimeSystemHelper& time_system, Scheduler& base_scheduler, TimerCb cb, + Dispatcher& dispatcher) + : base_timer_(base_scheduler.createTimer([this, cb] { runAlarm(cb); }, dispatcher)), time_system_(time_system), index_(time_system.nextIndex()), armed_(false), pending_(false) { } @@ -59,7 +60,8 @@ class SimulatedTimeSystemHelper::Alarm : public Timer { // Timer void disableTimer() override; - void enableTimer(const std::chrono::milliseconds& duration) override; + void enableTimer(const std::chrono::milliseconds& duration, + const ScopeTrackedObject* scope) override; bool enabled() override { Thread::LockGuard lock(time_system_.mutex_); return armed_ || base_timer_->enabled(); @@ -75,7 +77,8 @@ class SimulatedTimeSystemHelper::Alarm : public Timer { * Activates the timer so it will be run the next time the libevent loop is run, * typically via Dispatcher::run(). */ - void activateLockHeld() EXCLUSIVE_LOCKS_REQUIRED(time_system_.mutex_) { + void activateLockHeld(const ScopeTrackedObject* scope = nullptr) + EXCLUSIVE_LOCKS_REQUIRED(time_system_.mutex_) { ASSERT(armed_); armed_ = false; if (pending_) { @@ -91,7 +94,7 @@ class SimulatedTimeSystemHelper::Alarm : public Timer { // time_system_.mutex_ prior to running libevent, which may delete this. UnlockGuard unlocker(time_system_.mutex_); std::chrono::milliseconds duration = std::chrono::milliseconds::zero(); - base_timer_->enableTimer(duration); + base_timer_->enableTimer(duration, scope); } MonotonicTime time() const EXCLUSIVE_LOCKS_REQUIRED(time_system_.mutex_) { @@ -146,8 +149,9 @@ class SimulatedTimeSystemHelper::SimulatedScheduler : public Scheduler { public: SimulatedScheduler(SimulatedTimeSystemHelper& time_system, Scheduler& base_scheduler) : time_system_(time_system), base_scheduler_(base_scheduler) {} - TimerPtr createTimer(const TimerCb& cb) override { - return std::make_unique(time_system_, base_scheduler_, cb); + TimerPtr createTimer(const TimerCb& cb, Dispatcher& dispatcher) override { + return std::make_unique(time_system_, base_scheduler_, cb, + dispatcher); }; private: @@ -173,13 +177,13 @@ void SimulatedTimeSystemHelper::Alarm::Alarm::disableTimerLockHeld() { } } -void SimulatedTimeSystemHelper::Alarm::Alarm::enableTimer( - const std::chrono::milliseconds& duration) { +void SimulatedTimeSystemHelper::Alarm::Alarm::enableTimer(const std::chrono::milliseconds& duration, + const ScopeTrackedObject* scope) { Thread::LockGuard lock(time_system_.mutex_); disableTimerLockHeld(); armed_ = true; if (duration.count() == 0) { - activateLockHeld(); + activateLockHeld(scope); } else { time_system_.addAlarmLockHeld(this, duration); } diff --git a/test/test_common/simulated_time_system_test.cc b/test/test_common/simulated_time_system_test.cc index 399ee31a87..f584c29401 100644 --- a/test/test_common/simulated_time_system_test.cc +++ b/test/test_common/simulated_time_system_test.cc @@ -3,6 +3,7 @@ #include "common/event/libevent_scheduler.h" #include "common/event/timer_impl.h" +#include "test/mocks/event/mocks.h" #include "test/test_common/simulated_time_system.h" #include "test/test_common/utility.h" @@ -23,10 +24,12 @@ class SimulatedTimeSystemTest : public testing::Test { void addTask(int64_t delay_ms, char marker) { std::chrono::milliseconds delay(delay_ms); - TimerPtr timer = scheduler_->createTimer([this, marker, delay]() { - output_.append(1, marker); - EXPECT_GE(time_system_.monotonicTime(), start_monotonic_time_ + delay); - }); + TimerPtr timer = scheduler_->createTimer( + [this, marker, delay]() { + output_.append(1, marker); + EXPECT_GE(time_system_.monotonicTime(), start_monotonic_time_ + delay); + }, + dispatcher_); timer->enableTimer(delay); timers_.push_back(std::move(timer)); } @@ -41,6 +44,7 @@ class SimulatedTimeSystemTest : public testing::Test { base_scheduler_.run(Dispatcher::RunType::NonBlock); } + testing::NiceMock dispatcher_; LibeventScheduler base_scheduler_; SimulatedTimeSystem time_system_; SchedulerPtr scheduler_; @@ -71,11 +75,13 @@ TEST_F(SimulatedTimeSystemTest, WaitFor) { }); Thread::CondVar condvar; Thread::MutexBasicLockable mutex; - TimerPtr timer = scheduler_->createTimer([&condvar, &mutex, &done]() { - Thread::LockGuard lock(mutex); - done = true; - condvar.notifyOne(); - }); + TimerPtr timer = scheduler_->createTimer( + [&condvar, &mutex, &done]() { + Thread::LockGuard lock(mutex); + done = true; + condvar.notifyOne(); + }, + dispatcher_); timer->enableTimer(std::chrono::seconds(60)); // Wait 50 simulated seconds of simulated time, which won't be enough to @@ -201,7 +207,7 @@ TEST_F(SimulatedTimeSystemTest, DeleteTime) { TEST_F(SimulatedTimeSystemTest, DuplicateTimer) { // Set one alarm two times to test that pending does not get duplicated.. std::chrono::milliseconds delay(0); - TimerPtr zero_timer = scheduler_->createTimer([this]() { output_.append(1, '2'); }); + TimerPtr zero_timer = scheduler_->createTimer([this]() { output_.append(1, '2'); }, dispatcher_); zero_timer->enableTimer(delay); zero_timer->enableTimer(delay); sleepMsAndLoop(1); @@ -216,11 +222,13 @@ TEST_F(SimulatedTimeSystemTest, DuplicateTimer) { }); Thread::CondVar condvar; Thread::MutexBasicLockable mutex; - TimerPtr timer = scheduler_->createTimer([&condvar, &mutex, &done]() { - Thread::LockGuard lock(mutex); - done = true; - condvar.notifyOne(); - }); + TimerPtr timer = scheduler_->createTimer( + [&condvar, &mutex, &done]() { + Thread::LockGuard lock(mutex); + done = true; + condvar.notifyOne(); + }, + dispatcher_); timer->enableTimer(std::chrono::seconds(10)); { @@ -234,7 +242,7 @@ TEST_F(SimulatedTimeSystemTest, DuplicateTimer) { } TEST_F(SimulatedTimeSystemTest, Enabled) { - TimerPtr timer = scheduler_->createTimer({}); + TimerPtr timer = scheduler_->createTimer({}, dispatcher_); timer->enableTimer(std::chrono::milliseconds(0)); EXPECT_TRUE(timer->enabled()); } diff --git a/test/test_common/test_runtime.h b/test/test_common/test_runtime.h new file mode 100644 index 0000000000..ca796ca23f --- /dev/null +++ b/test/test_common/test_runtime.h @@ -0,0 +1,52 @@ +// A simple test utility to easily allow for runtime feature overloads in unit tests. +// +// As long as this class is in scope one can do runtime feature overrides: +// +// TestScopedRuntime scoped_runtime; +// Runtime::LoaderSingleton::getExisting()->mergeValues( +// {{"envoy.reloadable_features.test_feature_true", "false"}}); +// +// As long as a TestScopedRuntime exists, Runtime::LoaderSingleton::getExisting()->mergeValues() +// can safely be called to override runtime values. + +#pragma once + +#include "common/runtime/runtime_impl.h" +#include "common/stats/isolated_store_impl.h" + +#include "test/mocks/event/mocks.h" +#include "test/mocks/init/mocks.h" +#include "test/mocks/local_info/mocks.h" +#include "test/mocks/protobuf/mocks.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/thread_local/mocks.h" + +#include "gmock/gmock.h" + +namespace Envoy { + +class TestScopedRuntime { +public: + TestScopedRuntime() : api_(Api::createApiForTest()) { + envoy::config::bootstrap::v2::LayeredRuntime config; + // The existence of an admin layer is required for mergeValues() to work. + config.add_layers()->mutable_admin_layer(); + + loader_ = std::make_unique( + std::make_unique(dispatcher_, tls_, config, local_info_, init_manager_, + store_, generator_, validation_visitor_, *api_)); + } + +private: + Event::MockDispatcher dispatcher_; + testing::NiceMock tls_; + Stats::IsolatedStoreImpl store_; + Runtime::MockRandomGenerator generator_; + Api::ApiPtr api_; + testing::NiceMock local_info_; + Init::MockManager init_manager_; + testing::NiceMock validation_visitor_; + std::unique_ptr loader_; +}; + +} // namespace Envoy diff --git a/test/test_common/utility.cc b/test/test_common/utility.cc index 88f6271438..c236ac4ce7 100644 --- a/test/test_common/utility.cc +++ b/test/test_common/utility.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -422,9 +423,11 @@ void TestHeaderMapImpl::addCopy(const std::string& key, const std::string& value void TestHeaderMapImpl::remove(const std::string& key) { remove(LowerCaseString(key)); } -std::string TestHeaderMapImpl::get_(const std::string& key) { return get_(LowerCaseString(key)); } +std::string TestHeaderMapImpl::get_(const std::string& key) const { + return get_(LowerCaseString(key)); +} -std::string TestHeaderMapImpl::get_(const LowerCaseString& key) { +std::string TestHeaderMapImpl::get_(const LowerCaseString& key) const { const HeaderEntry* header = get(key); if (!header) { return EMPTY_STRING; diff --git a/test/test_common/utility.h b/test/test_common/utility.h index 06c5bd5294..8135f0d583 100644 --- a/test/test_common/utility.h +++ b/test/test_common/utility.h @@ -31,11 +31,11 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::_; +using testing::_; // NOLINT(misc-unused-using-decls) using testing::AssertionFailure; using testing::AssertionResult; using testing::AssertionSuccess; -using testing::Invoke; +using testing::Invoke; // NOLINT(misc-unused-using-decls) namespace Envoy { @@ -105,6 +105,19 @@ namespace Envoy { } \ } while (false) +// A convenience macro for testing Envoy deprecated features. This will disable the test when +// tests are built with --define deprecated_features=disabled to avoid the hard-failure mode for +// deprecated features. Sample usage is: +// +// TEST_F(FixtureName, DEPRECATED_FEATURE_TEST(TestName)) { +// ... +// } +#ifndef ENVOY_DISABLE_DEPRECATED_FEATURES +#define DEPRECATED_FEATURE_TEST(X) X +#else +#define DEPRECATED_FEATURE_TEST(X) DISABLED_##X +#endif + // Random number generator which logs its seed to stderr. To repeat a test run with a non-zero seed // one can run the test with --test_arg=--gtest_random_seed=[seed] class TestRandomGenerator { @@ -499,8 +512,7 @@ class TestUtility { template static inline MessageType anyConvert(const ProtobufWkt::Any& message) { - return MessageUtil::anyConvert(message, - ProtobufMessage::getStrictValidationVisitor()); + return MessageUtil::anyConvert(message); } template @@ -515,6 +527,16 @@ class TestUtility { ProtobufMessage::getStrictValidationVisitor()); } + template static void validate(const MessageType& message) { + return MessageUtil::validate(message, ProtobufMessage::getStrictValidationVisitor()); + } + + template + static const MessageType& downcastAndValidate(const Protobuf::Message& config) { + return MessageUtil::downcastAndValidate( + config, ProtobufMessage::getStrictValidationVisitor()); + } + static void jsonConvert(const Protobuf::Message& source, Protobuf::Message& dest) { // Explicit round-tripping to support conversions inside tests between arbitrary messages as a // convenience. @@ -615,8 +637,8 @@ class TestHeaderMapImpl : public HeaderMapImpl { using HeaderMapImpl::remove; void addCopy(const std::string& key, const std::string& value); void remove(const std::string& key); - std::string get_(const std::string& key); - std::string get_(const LowerCaseString& key); + std::string get_(const std::string& key) const; + std::string get_(const LowerCaseString& key) const; bool has(const std::string& key); bool has(const LowerCaseString& key); }; diff --git a/test/test_common/utility_test.cc b/test/test_common/utility_test.cc index 3725ba564e..cb911489d1 100644 --- a/test/test_common/utility_test.cc +++ b/test/test_common/utility_test.cc @@ -4,8 +4,6 @@ #include "gtest/gtest.h" -using Envoy::Http::HeaderMap; - namespace Envoy { TEST(HeaderMapEqualIgnoreOrder, ActuallyEqual) { diff --git a/test/test_runner.h b/test/test_runner.h index 26e373ddd3..8f70da98a9 100644 --- a/test/test_runner.h +++ b/test/test_runner.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "common/common/logger.h" #include "common/common/logger_delegates.h" #include "common/common/thread.h" diff --git a/test/tools/router_check/router.cc b/test/tools/router_check/router.cc index c75053792f..0547c15e03 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/message_validator_impl.h" #include "common/protobuf/utility.h" #include "common/stream_info/stream_info_impl.h" @@ -63,7 +64,8 @@ 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_file) { +RouterCheckTool RouterCheckTool::create(const std::string& router_config_file, + const bool disableDeprecationCheck) { // TODO(hennna): Allow users to load a full config and extract the route configuration from it. envoy::api::v2::RouteConfiguration route_config; auto stats = std::make_unique(); @@ -72,6 +74,11 @@ RouterCheckTool RouterCheckTool::create(const std::string& router_config_file) { auto factory_context = std::make_unique>(); auto config = std::make_unique(route_config, *factory_context, false); + if (!disableDeprecationCheck) { + MessageUtil::checkForUnexpectedFields(route_config, + ProtobufMessage::getStrictValidationVisitor(), + &factory_context->runtime_loader_); + } return RouterCheckTool(std::move(factory_context), std::move(config), std::move(stats), std::move(api), Coverage(route_config)); @@ -93,19 +100,14 @@ RouterCheckTool::RouterCheckTool( bool RouterCheckTool::compareEntriesInJson(const std::string& expected_route_json) { Json::ObjectSharedPtr loader = Json::Factory::loadFromFile(expected_route_json, *api_); loader->validateSchema(Json::ToolSchema::routerCheckSchema()); - bool no_failures = true; for (const Json::ObjectSharedPtr& check_config : loader->asObjectArray()) { headers_finalized_ = false; ToolConfig tool_config = ToolConfig::create(check_config); tool_config.route_ = config_->route(*tool_config.headers_, tool_config.random_value_); - std::string test_name = check_config->getString("test_name", ""); - if (details_) { - std::cout << test_name << std::endl; - } + tests_.emplace_back(test_name, std::vector{}); Json::ObjectSharedPtr validate = check_config->getObject("validate"); - using checkerFunc = std::function; const std::unordered_map checkers = { {"cluster_name", @@ -121,7 +123,6 @@ bool RouterCheckTool::compareEntriesInJson(const std::string& expected_route_jso {"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)) { @@ -135,7 +136,6 @@ bool RouterCheckTool::compareEntriesInJson(const std::string& expected_route_jso } } } - if (validate->hasObject("header_fields")) { for (const Json::ObjectSharedPtr& header_field : validate->getObjectArray("header_fields")) { if (!compareHeaderField(tool_config, header_field->getString("field"), @@ -144,7 +144,6 @@ bool RouterCheckTool::compareEntriesInJson(const std::string& expected_route_jso } } } - if (validate->hasObject("custom_header_fields")) { for (const Json::ObjectSharedPtr& header_field : validate->getObjectArray("custom_header_fields")) { @@ -155,7 +154,7 @@ bool RouterCheckTool::compareEntriesInJson(const std::string& expected_route_jso } } } - + printResults(); return no_failures; } @@ -165,7 +164,7 @@ bool RouterCheckTool::compareEntries(const std::string& expected_routes) { auto api = Api::createApiForTest(*stats); const std::string contents = api->fileSystem().fileReadToEnd(expected_routes); TestUtility::loadFromFile(expected_routes, validation_config, *api); - MessageUtil::validate(validation_config); + TestUtility::validate(validation_config); bool no_failures = true; for (const envoy::RouterCheckToolSchema::ValidationItem& check_config : @@ -176,9 +175,7 @@ bool RouterCheckTool::compareEntries(const std::string& expected_routes) { tool_config.route_ = config_->route(*tool_config.headers_, tool_config.random_value_); const std::string& test_name = check_config.test_name(); - if (details_) { - std::cout << test_name << std::endl; - } + tests_.emplace_back(test_name, std::vector{}); const envoy::RouterCheckToolSchema::ValidationAssert& validate = check_config.validate(); using checkerFunc = @@ -201,7 +198,7 @@ bool RouterCheckTool::compareEntries(const std::string& expected_routes) { } } } - + printResults(); return no_failures; } @@ -220,13 +217,13 @@ bool RouterCheckTool::compareCluster(ToolConfig& tool_config, const std::string& bool RouterCheckTool::compareCluster( ToolConfig& tool_config, const envoy::RouterCheckToolSchema::ValidationAssert& expected) { - if (expected.cluster_name().empty()) { + if (!expected.has_cluster_name()) { return true; } if (tool_config.route_ == nullptr) { - return compareResults("", expected.cluster_name(), "cluster_name"); + return compareResults("", expected.cluster_name().value(), "cluster_name"); } - return compareCluster(tool_config, expected.cluster_name()); + return compareCluster(tool_config, expected.cluster_name().value()); } bool RouterCheckTool::compareVirtualCluster(ToolConfig& tool_config, const std::string& expected) { @@ -247,13 +244,13 @@ bool RouterCheckTool::compareVirtualCluster(ToolConfig& tool_config, const std:: bool RouterCheckTool::compareVirtualCluster( ToolConfig& tool_config, const envoy::RouterCheckToolSchema::ValidationAssert& expected) { - if (expected.virtual_cluster_name().empty()) { + if (!expected.has_virtual_cluster_name()) { return true; } if (tool_config.route_ == nullptr) { - return compareResults("", expected.virtual_cluster_name(), "virtual_cluster_name"); + return compareResults("", expected.virtual_cluster_name().value(), "virtual_cluster_name"); } - return compareVirtualCluster(tool_config, expected.virtual_cluster_name()); + return compareVirtualCluster(tool_config, expected.virtual_cluster_name().value()); } bool RouterCheckTool::compareVirtualHost(ToolConfig& tool_config, const std::string& expected) { @@ -271,13 +268,13 @@ bool RouterCheckTool::compareVirtualHost(ToolConfig& tool_config, const std::str bool RouterCheckTool::compareVirtualHost( ToolConfig& tool_config, const envoy::RouterCheckToolSchema::ValidationAssert& expected) { - if (expected.virtual_host_name().empty()) { + if (!expected.has_virtual_host_name()) { return true; } if (tool_config.route_ == nullptr) { - return compareResults("", expected.virtual_host_name(), "virtual_host_name"); + return compareResults("", expected.virtual_host_name().value(), "virtual_host_name"); } - return compareVirtualHost(tool_config, expected.virtual_host_name()); + return compareVirtualHost(tool_config, expected.virtual_host_name().value()); } bool RouterCheckTool::compareRewritePath(ToolConfig& tool_config, const std::string& expected) { @@ -302,13 +299,13 @@ bool RouterCheckTool::compareRewritePath(ToolConfig& tool_config, const std::str bool RouterCheckTool::compareRewritePath( ToolConfig& tool_config, const envoy::RouterCheckToolSchema::ValidationAssert& expected) { - if (expected.path_rewrite().empty()) { + if (!expected.has_path_rewrite()) { return true; } if (tool_config.route_ == nullptr) { - return compareResults("", expected.path_rewrite(), "path_rewrite"); + return compareResults("", expected.path_rewrite().value(), "path_rewrite"); } - return compareRewritePath(tool_config, expected.path_rewrite()); + return compareRewritePath(tool_config, expected.path_rewrite().value()); } bool RouterCheckTool::compareRewriteHost(ToolConfig& tool_config, const std::string& expected) { @@ -333,13 +330,13 @@ bool RouterCheckTool::compareRewriteHost(ToolConfig& tool_config, const std::str bool RouterCheckTool::compareRewriteHost( ToolConfig& tool_config, const envoy::RouterCheckToolSchema::ValidationAssert& expected) { - if (expected.host_rewrite().empty()) { + if (!expected.has_host_rewrite()) { return true; } if (tool_config.route_ == nullptr) { - return compareResults("", expected.host_rewrite(), "host_rewrite"); + return compareResults("", expected.host_rewrite().value(), "host_rewrite"); } - return compareRewriteHost(tool_config, expected.host_rewrite()); + return compareRewriteHost(tool_config, expected.host_rewrite().value()); } bool RouterCheckTool::compareRedirectPath(ToolConfig& tool_config, const std::string& expected) { @@ -357,13 +354,13 @@ bool RouterCheckTool::compareRedirectPath(ToolConfig& tool_config, const std::st bool RouterCheckTool::compareRedirectPath( ToolConfig& tool_config, const envoy::RouterCheckToolSchema::ValidationAssert& expected) { - if (expected.path_redirect().empty()) { + if (!expected.has_path_redirect()) { return true; } if (tool_config.route_ == nullptr) { - return compareResults("", expected.path_redirect(), "path_redirect"); + return compareResults("", expected.path_redirect().value(), "path_redirect"); } - return compareRedirectPath(tool_config, expected.path_redirect()); + return compareRedirectPath(tool_config, expected.path_redirect().value()); } bool RouterCheckTool::compareHeaderField( @@ -417,13 +414,24 @@ bool RouterCheckTool::compareResults(const std::string& actual, const std::strin if (expected == actual) { return true; } + tests_.back().second.emplace_back("expected: [" + expected + "], actual: [" + actual + + "], test type: " + test_type); + return false; +} +void RouterCheckTool::printResults() { // Output failure details to stdout if details_ flag is set to true - if (details_) { - std::cerr << "expected: [" << expected << "], actual: [" << actual - << "], test type: " << test_type << std::endl; + for (const auto& test_result : tests_) { + // All test names are printed if the details_ flag is true unless only_show_failures_ is also + // true. + if ((details_ && !only_show_failures_) || + (only_show_failures_ && !test_result.second.empty())) { + std::cout << test_result.first << std::endl; + for (const auto& failure : test_result.second) { + std::cerr << failure << std::endl; + } + } } - return false; } // The Mock for runtime value checks. @@ -439,6 +447,10 @@ Options::Options(int argc, char** argv) { TCLAP::CmdLine cmd("router_check_tool", ' ', "none", true); TCLAP::SwitchArg is_proto("p", "useproto", "Use Proto test file schema", cmd, false); TCLAP::SwitchArg is_detailed("d", "details", "Show detailed test execution results", cmd, false); + TCLAP::SwitchArg only_show_failures("", "only-show-failures", "Only display failing tests", cmd, + false); + TCLAP::SwitchArg disable_deprecation_check("", "disable-deprecation-check", + "Disable deprecated fields check", cmd, false); TCLAP::ValueArg fail_under("f", "fail-under", "Fail if test coverage is under a specified amount", false, 0.0, "float", cmd); @@ -459,8 +471,10 @@ Options::Options(int argc, char** argv) { is_proto_ = is_proto.getValue(); is_detailed_ = is_detailed.getValue(); + only_show_failures_ = only_show_failures.getValue(); fail_under_ = fail_under.getValue(); comprehensive_coverage_ = comprehensive_coverage.getValue(); + disable_deprecation_check_ = disable_deprecation_check.getValue(); if (is_proto_) { config_path_ = config_path.getValue(); diff --git a/test/tools/router_check/router.h b/test/tools/router_check/router.h index a3395de2ad..78352e86e5 100644 --- a/test/tools/router_check/router.h +++ b/test/tools/router_check/router.h @@ -54,7 +54,7 @@ struct ToolConfig { private: ToolConfig(std::unique_ptr headers, int random_value); - Test::Global symbol_table_; + Stats::TestSymbolTable symbol_table_; }; /** @@ -65,10 +65,12 @@ class RouterCheckTool : Logger::Loggable { public: /** * @param router_config_file v2 router config file. + * @param disableDeprecationCheck flag to disable the RouteConfig deprecated field check * @return RouterCheckTool a RouterCheckTool instance with member variables set by the router * config file. * */ - static RouterCheckTool create(const std::string& router_config_file); + static RouterCheckTool create(const std::string& router_config_file, + const bool disableDeprecationCheck); /** * TODO(tonya11en): Use a YAML format for the expected routes. This will require a proto. @@ -89,6 +91,11 @@ class RouterCheckTool : Logger::Loggable { */ void setShowDetails() { details_ = true; } + /** + * Set whether to only print failing match cases. + */ + void setOnlyShowFailures() { only_show_failures_ = true; } + float coverage(bool detailed) { return detailed ? coverage_.detailedReport() : coverage_.report(); } @@ -135,6 +142,8 @@ class RouterCheckTool : Logger::Loggable { bool compareResults(const std::string& actual, const std::string& expected, const std::string& test_type); + void printResults(); + bool runtimeMock(const std::string& key, const envoy::type::FractionalPercent& default_value, uint64_t random_value); @@ -142,6 +151,12 @@ class RouterCheckTool : Logger::Loggable { bool details_{false}; + bool only_show_failures_{false}; + + // The first member of each pair is the name of the test. + // The second member is a list of any failing results for that test as strings. + std::vector>> tests_; + // TODO(hennna): Switch away from mocks following work done by @rlazarus in github issue #499. std::unique_ptr> factory_context_; std::unique_ptr config_; @@ -194,10 +209,20 @@ class Options { bool isProto() const { return is_proto_; } /** - * @return true is detailed test execution results are displayed. + * @return true if detailed test execution results are displayed. */ bool isDetailed() const { return is_detailed_; } + /** + * @return true if only test failures are displayed. + */ + bool onlyShowFailures() const { return only_show_failures_; } + + /** + * @return true if the deprecated field check for RouteConfiguration is disabled. + */ + bool disableDeprecationCheck() const { return disable_deprecation_check_; } + private: std::string test_path_; std::string config_path_; @@ -207,5 +232,7 @@ class Options { bool comprehensive_coverage_; bool is_proto_; bool is_detailed_; + bool only_show_failures_; + bool disable_deprecation_check_; }; } // namespace Envoy diff --git a/test/tools/router_check/router_check.cc b/test/tools/router_check/router_check.cc index e17f3eecc8..1792af5c5b 100644 --- a/test/tools/router_check/router_check.cc +++ b/test/tools/router_check/router_check.cc @@ -10,13 +10,18 @@ int main(int argc, char* argv[]) { const bool enforce_coverage = options.failUnder() != 0.0; try { Envoy::RouterCheckTool checktool = - options.isProto() ? Envoy::RouterCheckTool::create(options.configPath()) - : Envoy::RouterCheckTool::create(options.unlabelledConfigPath()); + options.isProto() ? Envoy::RouterCheckTool::create(options.configPath(), + options.disableDeprecationCheck()) + : Envoy::RouterCheckTool::create(options.unlabelledConfigPath(), true); if (options.isDetailed()) { checktool.setShowDetails(); } + if (options.onlyShowFailures()) { + checktool.setOnlyShowFailures(); + } + bool is_equal = options.isProto() ? checktool.compareEntries(options.testPath()) : checktool.compareEntriesInJson(options.unlabelledTestPath()); diff --git a/test/tools/router_check/test/config/ComprehensiveRoutes.golden.proto.json b/test/tools/router_check/test/config/ComprehensiveRoutes.golden.proto.json new file mode 100644 index 0000000000..ebc21381c3 --- /dev/null +++ b/test/tools/router_check/test/config/ComprehensiveRoutes.golden.proto.json @@ -0,0 +1,63 @@ +{ + "tests": [ + { + "test_name": "Test 1", + "input": { + "authority": "www.lyft.com", + "path": "/new_endpoint", + "method": "GET" + }, + "validate": { + "cluster_name": "www2", + "virtual_cluster_name": "other", + "virtual_host_name": "www2_host", + "path_rewrite": "/api/new_endpoint", + "host_rewrite": "www.lyft.com", + "path_redirect": "" + } + }, + { + "test_name": "Test 2", + "input": { + "authority": "www.lyft.com", + "path": "/", + "method": "GET" + }, + "validate": { + "cluster_name": "root_www2", + "virtual_cluster_name": "other", + "virtual_host_name": "www2_host", + "path_rewrite": "/", + "host_rewrite": "www.lyft.com", + "path_redirect": "" + } + }, + { + "test_name": "Test 3", + "input": { + "authority": "www.lyft.com", + "path": "/foobar", + "method": "GET" + }, + "validate": { + "cluster_name": "www2", + "virtual_cluster_name": "other", + "virtual_host_name": "www2_host", + "path_rewrite": "/foobar", + "host_rewrite": "www.lyft.com", + "path_redirect": "" + } + }, + { + "test_name": "Test 4", + "input": { + "authority": "www.lyft.com", + "path": "/users/123", + "method": "PUT" + }, + "validate": { + "virtual_cluster_name": "update_user" + } + } + ] +} diff --git a/test/tools/router_check/test/config/ComprehensiveRoutes.yaml b/test/tools/router_check/test/config/ComprehensiveRoutes.yaml index 0613410256..6efad99a09 100644 --- a/test/tools/router_check/test/config/ComprehensiveRoutes.yaml +++ b/test/tools/router_check/test/config/ComprehensiveRoutes.yaml @@ -17,6 +17,11 @@ virtual_hosts: route: cluster: www2 virtual_clusters: - - pattern: ^/users/\d+$ - method: PUT + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/users/\d+$ + - name: :method + exact_match: PUT name: update_user diff --git a/test/tools/router_check/test/config/HeaderMatchedRouting.yaml b/test/tools/router_check/test/config/HeaderMatchedRouting.yaml index f28c96a50a..5f891bea08 100644 --- a/test/tools/router_check/test/config/HeaderMatchedRouting.yaml +++ b/test/tools/router_check/test/config/HeaderMatchedRouting.yaml @@ -30,7 +30,9 @@ virtual_hosts: prefix: / headers: - name: test_header_pattern - regex_match: ^user=test-\d+$ + safe_regex_match: + google_re2: {} + regex: ^user=test-\d+$ route: cluster: local_service_with_header_pattern_set_regex - match: diff --git a/test/tools/router_check/test/config/TestRoutes.yaml b/test/tools/router_check/test/config/TestRoutes.yaml index 34b665fb9f..862b6a4da8 100644 --- a/test/tools/router_check/test/config/TestRoutes.yaml +++ b/test/tools/router_check/test/config/TestRoutes.yaml @@ -104,26 +104,61 @@ virtual_hosts: timeout: seconds: 30 virtual_clusters: - - pattern: ^/rides$ - method: POST + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/rides$ + - name: :method + exact_match: POST name: ride_request - - pattern: ^/rides/\d+$ - method: PUT + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/rides/\d+$ + - name: :method + exact_match: PUT name: update_ride - - pattern: ^/users/\d+/chargeaccounts$ - method: POST + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/users/\d+/chargeaccounts$ + - name: :method + exact_match: POST name: cc_add - - pattern: ^/users/\d+/chargeaccounts/(?!validate)\w+$ - method: PUT + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/users/\d+/chargeaccounts/[^validate]\w+$ + - name: :method + exact_match: PUT name: cc_add - - pattern: ^/users$ - method: POST + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/users$ + - name: :method + exact_match: POST name: create_user_login - - pattern: ^/users/\d+$ - method: PUT + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/users/\d+$ + - name: :method + exact_match: PUT name: update_user - - pattern: ^/users/\d+/location$ - method: POST + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/users/\d+/location$ + - name: :method + exact_match: POST name: ulu internal_only_headers: - x-lyft-user-id diff --git a/test/tools/router_check/test/config/Weighted.golden.json b/test/tools/router_check/test/config/Weighted.golden.json index e12cb45bad..32e7adc566 100644 --- a/test/tools/router_check/test/config/Weighted.golden.json +++ b/test/tools/router_check/test/config/Weighted.golden.json @@ -13,10 +13,11 @@ "test_name": "Test_2", "input": { ":authority": "www1.lyft.com", - ":path": "/foo", - "random_value": 115 + ":path": "/test/123", + "random_value": 115, + ":method": "GET" }, - "validate": {"cluster_name": "cluster1"} + "validate": {"cluster_name": "cluster1", "virtual_cluster_name": "test_virtual_cluster"} }, { "test_name": "Test_3", diff --git a/test/tools/router_check/test/config/Weighted.golden.proto.json b/test/tools/router_check/test/config/Weighted.golden.proto.json index 63622d1b4f..e48a044692 100644 --- a/test/tools/router_check/test/config/Weighted.golden.proto.json +++ b/test/tools/router_check/test/config/Weighted.golden.proto.json @@ -15,11 +15,11 @@ "test_name": "Test_2", "input": { "authority": "www1.lyft.com", - "path": "/foo", + "path": "/test/123", "method": "GET", "random_value": 115 }, - "validate": {"cluster_name": "cluster1"} + "validate": {"cluster_name": "cluster1", "virtual_cluster_name": "test_virtual_cluster"} }, { "test_name": "Test_3", diff --git a/test/tools/router_check/test/config/Weighted.golden.proto.pb_text b/test/tools/router_check/test/config/Weighted.golden.proto.pb_text index c2ab5d18c3..9a9d0d8f7a 100644 --- a/test/tools/router_check/test/config/Weighted.golden.proto.pb_text +++ b/test/tools/router_check/test/config/Weighted.golden.proto.pb_text @@ -8,7 +8,7 @@ tests { method: "GET" } validate: { - path_redirect: "" + path_redirect: { value: "" } } } @@ -16,11 +16,12 @@ tests { test_name: "Test_2" input: { authority: "www1.lyft.com" - path: "/foo" + path: "/test/123" method: "GET" random_value: 115 } validate: { - cluster_name: "cluster1" + cluster_name: { value: "cluster1"} + virtual_cluster_name: { value: "test_virtual_cluster" } } } \ No newline at end of file diff --git a/test/tools/router_check/test/config/Weighted.golden.proto.yaml b/test/tools/router_check/test/config/Weighted.golden.proto.yaml index 2508111d52..db59c022dc 100644 --- a/test/tools/router_check/test/config/Weighted.golden.proto.yaml +++ b/test/tools/router_check/test/config/Weighted.golden.proto.yaml @@ -11,11 +11,12 @@ tests: - test_name: Test_2 input: authority: www1.lyft.com - path: "/foo" + path: "/test/123" method: GET random_value: 115 validate: cluster_name: cluster1 + virtual_cluster_name: test_virtual_cluster - test_name: Test_3 input: authority: www1.lyft.com diff --git a/test/tools/router_check/test/config/Weighted.yaml b/test/tools/router_check/test/config/Weighted.yaml index dd31345021..074a24b75b 100644 --- a/test/tools/router_check/test/config/Weighted.yaml +++ b/test/tools/router_check/test/config/Weighted.yaml @@ -14,6 +14,15 @@ virtual_hosts: weight: 30 - name: cluster3 weight: 40 + virtual_clusters: + - headers: + - name: :path + safe_regex_match: + google_re2: {} + regex: ^/test/\d+$ + - name: :method + exact_match: GET + name: test_virtual_cluster - name: www2 domains: - www2.lyft.com diff --git a/test/tools/router_check/test/route_tests.sh b/test/tools/router_check/test/route_tests.sh index 0b06cb7425..74a4eeccc0 100755 --- a/test/tools/router_check/test/route_tests.sh +++ b/test/tools/router_check/test/route_tests.sh @@ -24,6 +24,12 @@ if [[ "${COVERAGE_OUTPUT}" != *"Current route coverage: "* ]] ; then exit 1 fi +COMP_COVERAGE_CMD="${PATH_BIN} -c ${PATH_CONFIG}/ComprehensiveRoutes.yaml -t ${PATH_CONFIG}/ComprehensiveRoutes.golden.proto.json --details --useproto -f " +COVERAGE_OUTPUT=$($COMP_COVERAGE_CMD "100" "--covall" 2>&1) || echo "${COVERAGE_OUTPUT:-no-output}" +if [[ "${COVERAGE_OUTPUT}" != *"Current route coverage: 100%"* ]] ; then + exit 1 +fi + COMP_COVERAGE_CMD="${PATH_BIN} ${PATH_CONFIG}/ComprehensiveRoutes.yaml ${PATH_CONFIG}/ComprehensiveRoutes.golden.json --details -f " COVERAGE_OUTPUT=$($COMP_COVERAGE_CMD "100" "--covall" 2>&1) || echo "${COVERAGE_OUTPUT:-no-output}" if [[ "${COVERAGE_OUTPUT}" != *"Current route coverage: 100%"* ]] ; then @@ -58,10 +64,25 @@ if [[ "${BAD_CONFIG_OUTPUT}" != *"Unable to parse"* ]]; then exit 1 fi -# Failure test case -echo "testing failure test case" +# Failure output flag test cases +echo "testing failure test cases" +# Failure test case with only details flag set FAILURE_OUTPUT=$("${PATH_BIN}" "${PATH_CONFIG}/TestRoutes.yaml" "${PATH_CONFIG}/Weighted.golden.json" "--details" 2>&1) || echo "${FAILURE_OUTPUT:-no-output}" -if [[ "${FAILURE_OUTPUT}" != *"expected: [cluster1], actual: [instant-server], test type: cluster_name"* ]]; then +if [[ "${FAILURE_OUTPUT}" != *"Test_1"*"Test_2"*"expected: [test_virtual_cluster], actual: [other], test type: virtual_cluster_name"*"expected: [cluster1], actual: [instant-server], test type: cluster_name"*"Test_3"* ]]; then + exit 1 +fi + +# Failure test case with details flag set and failures flag set +FAILURE_OUTPUT=$("${PATH_BIN}" "-c" "${PATH_CONFIG}/TestRoutes.yaml" "-t" "${PATH_CONFIG}/Weighted.golden.proto.json" "--details" "--only-show-failures" "--useproto" 2>&1) || + echo "${FAILURE_OUTPUT:-no-output}" +if [[ "${FAILURE_OUTPUT}" != *"Test_2"*"expected: [cluster1], actual: [instant-server], test type: cluster_name"* ]] || [[ "${FAILURE_OUTPUT}" == *"Test_1"* ]]; then + exit 1 +fi + +# Failure test case with details flag unset and failures flag set +FAILURE_OUTPUT=$("${PATH_BIN}" "-c" "${PATH_CONFIG}/TestRoutes.yaml" "-t" "${PATH_CONFIG}/Weighted.golden.proto.json" "--only-show-failures" "--useproto" 2>&1) || + echo "${FAILURE_OUTPUT:-no-output}" +if [[ "${FAILURE_OUTPUT}" != *"Test_2"*"expected: [cluster1], actual: [instant-server], test type: cluster_name"* ]] || [[ "${FAILURE_OUTPUT}" == *"Test_1"* ]]; then exit 1 fi diff --git a/test/tools/router_check/validation.proto b/test/tools/router_check/validation.proto index 9c86153cd3..4ff3ab35f4 100644 --- a/test/tools/router_check/validation.proto +++ b/test/tools/router_check/validation.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.RouterCheckToolSchema; import "envoy/api/v2/core/base.proto"; +import "google/protobuf/wrappers.proto"; import "validate/validate.proto"; // [#protodoc-title: RouterCheckTool Validation] @@ -78,22 +79,22 @@ message ValidationInput { // For example, to test that no cluster match is expected use {“cluster_name”: “”}. message ValidationAssert { // Match the cluster name. - string cluster_name = 1; + google.protobuf.StringValue cluster_name = 1; // Match the virtual cluster name. - string virtual_cluster_name = 2; + google.protobuf.StringValue virtual_cluster_name = 2; // Match the virtual host name. - string virtual_host_name = 3; + google.protobuf.StringValue virtual_host_name = 3; // Match the host header field after rewrite. - string host_rewrite = 4; + google.protobuf.StringValue host_rewrite = 4; // Match the path header field after rewrite. - string path_rewrite = 5; + google.protobuf.StringValue path_rewrite = 5; // Match the returned redirect path. - string path_redirect = 6; + google.protobuf.StringValue path_redirect = 6; // Match the listed header fields. // Examples header fields include the “:path”, “cookie”, and “date” fields. diff --git a/test/tools/schema_validator/validator.cc b/test/tools/schema_validator/validator.cc index 80b2a41370..fee3cde141 100644 --- a/test/tools/schema_validator/validator.cc +++ b/test/tools/schema_validator/validator.cc @@ -59,13 +59,13 @@ void Validator::validate(const std::string& config_path, Schema::Type schema_typ case Schema::Type::DiscoveryResponse: { envoy::api::v2::DiscoveryResponse discovery_response_config; TestUtility::loadFromFile(config_path, discovery_response_config, *api_); - MessageUtil::validate(discovery_response_config); + TestUtility::validate(discovery_response_config); break; } case Schema::Type::Route: { envoy::api::v2::RouteConfiguration route_config; TestUtility::loadFromFile(config_path, route_config, *api_); - MessageUtil::validate(route_config); + TestUtility::validate(route_config); break; } default: diff --git a/tools/api/clone.sh b/tools/api/clone.sh new file mode 100755 index 0000000000..a319979343 --- /dev/null +++ b/tools/api/clone.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Simple script to clone vM to vN API, performing sed-style heuristic fixup of +# build paths and package references. +# +# Usage: +# +# ./tools/api/clone.sh v2 v3 + +set -e + +declare -r OLD_VERSION="$1" +declare -r NEW_VERSION="$2" + +# For vM -> vN, replace //$1*/vMalpha\d* with //$1*/vN in BUILD file $2 +# For vM -> vN, replace //$1*/vM with //$1*/vN in BUILD file $2 +function replace_build() { + sed -i -e "s#\(//$1[^\S]*\)/${OLD_VERSION}alpha[[:digit:]]*#\1/${NEW_VERSION}#g" "$2" + sed -i -e "s#\(//$1[^\S]*\)/${OLD_VERSION}#\1/${NEW_VERSION}#g" "$2" +} + +# For vM -> vN, replace $1*[./]vMalpha with $1*[./]vN in .proto file $2 +# For vM -> vN, replace $1*[./]vM with $1*[./]vN in .proto file $2 +function replace_proto() { + sed -i -e "s#\($1\S*[\./]\)${OLD_VERSION}alpha[[:digit:]]*#\1${NEW_VERSION}#g" "$2" + sed -i -e "s#\($1\S*[\./]\)${OLD_VERSION}#\1${NEW_VERSION}#g" "$2" +} + +# We consider both {vM, vMalpha} to deal with the multiple possible combinations +# of {vM, vMalpha} existence for a given package. +for p in $(find api/ -name "${OLD_VERSION}*") +do + declare PACKAGE_ROOT="$(dirname "$p")" + declare OLD_VERSION_ROOT="${PACKAGE_ROOT}/${OLD_VERSION}" + declare NEW_VERSION_ROOT="${PACKAGE_ROOT}/${NEW_VERSION}" + + # Deal with the situation where there is both vM and vMalpha, we only want vM. + if [[ -a "${OLD_VERSION_ROOT}" && "$p" != "${OLD_VERSION_ROOT}" ]] + then + continue + fi + + # Copy BUILD and .protos across + rsync -a "${p}"/ "${NEW_VERSION_ROOT}/" + + # Update BUILD files with vM -> vN + for b in $(find "${NEW_VERSION_ROOT}" -name BUILD) + do + replace_build envoy "$b" + # Misc. cleanup for go BUILD rules + sed -i -e "s#\"${OLD_VERSION}\"#\"${NEW_VERSION}\"#g" "$b" + done + + # Update .proto files with vM -> vN + for f in $(find "${NEW_VERSION_ROOT}" -name "*.proto") + do + replace_proto envoy "$f" + replace_proto api "$f" + replace_proto service "$f" + replace_proto common "$f" + replace_proto config "$f" + replace_proto filter "$f" + replace_proto "" "$f" + done +done diff --git a/tools/api_proto_plugin/BUILD b/tools/api_proto_plugin/BUILD new file mode 100644 index 0000000000..6464902769 --- /dev/null +++ b/tools/api_proto_plugin/BUILD @@ -0,0 +1,17 @@ +licenses(["notice"]) # Apache 2 + +py_library( + name = "api_proto_plugin", + srcs = [ + "annotations.py", + "plugin.py", + "traverse.py", + "type_context.py", + "visitor.py", + ], + srcs_version = "PY3", + visibility = ["//visibility:public"], + deps = [ + "@com_google_protobuf//:protobuf_python", + ], +) diff --git a/tools/api_proto_plugin/__init__.py b/tools/api_proto_plugin/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/api_proto_plugin/annotations.py b/tools/api_proto_plugin/annotations.py new file mode 100644 index 0000000000..eadd080b03 --- /dev/null +++ b/tools/api_proto_plugin/annotations.py @@ -0,0 +1,79 @@ +"""Envoy API annotations.""" + +from collections import namedtuple + +import re + +# Key-value annotation regex. +ANNOTATION_REGEX = re.compile('\[#([\w-]+?):(.*?)\]\s?', re.DOTALL) + +# Page/section titles with special prefixes in the proto comments +DOC_TITLE_ANNOTATION = 'protodoc-title' + +# Not implemented yet annotation on leading comments, leading to insertion of +# warning on field. +NOT_IMPLEMENTED_WARN_ANNOTATION = 'not-implemented-warn' + +# Not implemented yet annotation on leading comments, leading to hiding of +# field. +NOT_IMPLEMENTED_HIDE_ANNOTATION = 'not-implemented-hide' + +# Comment that allows for easy searching for things that need cleaning up in the next major +# API version. +NEXT_MAJOR_VERSION_ANNOTATION = 'next-major-version' + +# Comment. Just used for adding text that will not go into the docs at all. +COMMENT_ANNOTATION = 'comment' + +# proto compatibility status. +PROTO_STATUS_ANNOTATION = 'proto-status' + +# Where v2 differs from v1.. +V2_API_DIFF_ANNOTATION = 'v2-api-diff' + +VALID_ANNOTATIONS = set([ + DOC_TITLE_ANNOTATION, + NOT_IMPLEMENTED_WARN_ANNOTATION, + NOT_IMPLEMENTED_HIDE_ANNOTATION, + V2_API_DIFF_ANNOTATION, + NEXT_MAJOR_VERSION_ANNOTATION, + COMMENT_ANNOTATION, + PROTO_STATUS_ANNOTATION, +]) + +# These can propagate from file scope to message/enum scope (and be overridden). +INHERITED_ANNOTATIONS = set([ + PROTO_STATUS_ANNOTATION, +]) + + +class AnnotationError(Exception): + """Base error class for the annotations module.""" + + +def ExtractAnnotations(s, inherited_annotations=None): + """Extract annotations map from a given comment string. + + Args: + s: string that may contains annotations. + inherited_annotations: annotation map from file-level inherited annotations + (or None) if this is a file-level comment. + + Returns: + Annotation map. + """ + annotations = { + k: v for k, v in (inherited_annotations or {}).items() if k in INHERITED_ANNOTATIONS + } + # Extract annotations. + groups = re.findall(ANNOTATION_REGEX, s) + for group in groups: + annotation = group[0] + if annotation not in VALID_ANNOTATIONS: + raise AnnotationError('Unknown annotation: %s' % annotation) + annotations[group[0]] = group[1].lstrip() + return annotations + + +def WithoutAnnotations(s): + return re.sub(ANNOTATION_REGEX, '', s) diff --git a/tools/api_proto_plugin/plugin.py b/tools/api_proto_plugin/plugin.py new file mode 100644 index 0000000000..0a56ea8e3f --- /dev/null +++ b/tools/api_proto_plugin/plugin.py @@ -0,0 +1,57 @@ +"""Python protoc plugin for Envoy APIs.""" + +import cProfile +import io +import os +import pstats +import sys + +from tools.api_proto_plugin import traverse + +from google.protobuf.compiler import plugin_pb2 + + +def Plugin(output_suffix, visitor): + """Protoc plugin entry point. + + This defines protoc plugin and manages the stdin -> stdout flow. An + api_proto_plugin is defined by the provided visitor. + + See + http://www.expobrain.net/2015/09/13/create-a-plugin-for-google-protocol-buffer/ + for further details on protoc plugin basics. + + Args: + output_suffix: output files are generated alongside their corresponding + input .proto, with this filename suffix. + visitor: visitor.Visitor defining the business logic of the plugin. + """ + request = plugin_pb2.CodeGeneratorRequest() + request.ParseFromString(sys.stdin.buffer.read()) + response = plugin_pb2.CodeGeneratorResponse() + cprofile_enabled = os.getenv('CPROFILE_ENABLED') + + # We use request.file_to_generate rather than request.file_proto here since we + # are invoked inside a Bazel aspect, each node in the DAG will be visited once + # by the aspect and we only want to generate docs for the current node. + for file_to_generate in request.file_to_generate: + # Find the FileDescriptorProto for the file we actually are generating. + file_proto = [pf for pf in request.proto_file if pf.name == file_to_generate][0] + f = response.file.add() + f.name = file_proto.name + output_suffix + if cprofile_enabled: + pr = cProfile.Profile() + pr.enable() + # We don't actually generate any RST right now, we just string dump the + # input proto file descriptor into the output file. + f.content = traverse.TraverseFile(file_proto, visitor) + if cprofile_enabled: + pr.disable() + stats_stream = io.StringIO() + ps = pstats.Stats(pr, + stream=stats_stream).sort_stats(os.getenv('CPROFILE_SORTBY', 'cumulative')) + stats_file = response.file.add() + stats_file.name = file_proto.name + output_suffix + '.profile' + ps.print_stats() + stats_file.content = stats_stream.getvalue() + sys.stdout.buffer.write(response.SerializeToString()) diff --git a/tools/api_proto_plugin/traverse.py b/tools/api_proto_plugin/traverse.py new file mode 100644 index 0000000000..6ad97b8699 --- /dev/null +++ b/tools/api_proto_plugin/traverse.py @@ -0,0 +1,72 @@ +"""FileDescriptorProto traversal for api_proto_plugin framework.""" + +from tools.api_proto_plugin import type_context + + +def TraverseEnum(type_context, enum_proto, visitor): + """Traverse an enum definition. + + Args: + type_context: type_context.TypeContext for enum type. + enum_proto: EnumDescriptorProto for enum. + visitor: visitor.Visitor defining the business logic of the plugin. + + Returns: + Plugin specific output. + """ + return visitor.VisitEnum(enum_proto, type_context) + + +def TraverseMessage(type_context, msg_proto, visitor): + """Traverse a message definition. + + Args: + type_context: type_context.TypeContext for message type. + msg_proto: DescriptorProto for message. + visitor: visitor.Visitor defining the business logic of the plugin. + + Returns: + Plugin specific output. + """ + # Skip messages synthesized to represent map types. + if msg_proto.options.map_entry: + return '' + # We need to do some extra work to recover the map type annotation from the + # synthesized messages. + type_context.map_typenames = { + '%s.%s' % (type_context.name, nested_msg.name): (nested_msg.field[0], nested_msg.field[1]) + for nested_msg in msg_proto.nested_type + if nested_msg.options.map_entry + } + nested_msgs = [ + TraverseMessage(type_context.ExtendNestedMessage(index, nested_msg.name), nested_msg, visitor) + for index, nested_msg in enumerate(msg_proto.nested_type) + ] + nested_enums = [ + TraverseEnum(type_context.ExtendNestedEnum(index, nested_enum.name), nested_enum, visitor) + for index, nested_enum in enumerate(msg_proto.enum_type) + ] + return visitor.VisitMessage(msg_proto, type_context, nested_msgs, nested_enums) + + +def TraverseFile(file_proto, visitor): + """Traverse a proto file definition. + + Args: + file_proto: FileDescriptorProto for file. + visitor: visitor.Visitor defining the business logic of the plugin. + + Returns: + Plugin specific output. + """ + source_code_info = type_context.SourceCodeInfo(file_proto.name, file_proto.source_code_info) + package_type_context = type_context.TypeContext(source_code_info, file_proto.package) + msgs = [ + TraverseMessage(package_type_context.ExtendMessage(index, msg.name), msg, visitor) + for index, msg in enumerate(file_proto.message_type) + ] + enums = [ + TraverseEnum(package_type_context.ExtendEnum(index, enum.name), enum, visitor) + for index, enum in enumerate(file_proto.enum_type) + ] + return visitor.VisitFile(file_proto, package_type_context, msgs, enums) diff --git a/tools/api_proto_plugin/type_context.py b/tools/api_proto_plugin/type_context.py new file mode 100644 index 0000000000..d69c120a1a --- /dev/null +++ b/tools/api_proto_plugin/type_context.py @@ -0,0 +1,195 @@ +"""Type context for FileDescriptorProto traversal.""" + +from collections import namedtuple + +from tools.api_proto_plugin import annotations + +# A comment is a (raw comment, annotation map) pair. +Comment = namedtuple('Comment', ['raw', 'annotations']) + + +class SourceCodeInfo(object): + """Wrapper for SourceCodeInfo proto.""" + + def __init__(self, name, source_code_info): + self.name = name + self.proto = source_code_info + # Map from path to SourceCodeInfo.Location + self._locations = {str(location.path): location for location in self.proto.location} + self._file_level_comments = None + self._file_level_annotations = None + + @property + def file_level_comments(self): + """Obtain inferred file level comment.""" + if self._file_level_comments: + return self._file_level_comments + comments = [] + # We find the earliest detached comment by first finding the maximum start + # line for any location and then scanning for any earlier locations with + # detached comments. + earliest_detached_comment = max(location.span[0] for location in self.proto.location) + 1 + for location in self.proto.location: + if location.leading_detached_comments and location.span[0] < earliest_detached_comment: + comments = location.leading_detached_comments + earliest_detached_comment = location.span[0] + self._file_level_comments = comments + return comments + + @property + def file_level_annotations(self): + """Obtain inferred file level annotations.""" + if self._file_level_annotations: + return self._file_level_annotations + self._file_level_annotations = dict( + sum([list(annotations.ExtractAnnotations(c).items()) for c in self.file_level_comments], + [])) + return self._file_level_annotations + + def LocationPathLookup(self, path): + """Lookup SourceCodeInfo.Location by path in SourceCodeInfo. + + Args: + path: a list of path indexes as per + https://github.com/google/protobuf/blob/a08b03d4c00a5793b88b494f672513f6ad46a681/src/google/protobuf/descriptor.proto#L717. + + Returns: + SourceCodeInfo.Location object if found, otherwise None. + """ + return self._locations.get(str(path), None) + + # TODO(htuch): consider integrating comment lookup with overall + # FileDescriptorProto, perhaps via two passes. + def LeadingCommentPathLookup(self, path): + """Lookup leading comment by path in SourceCodeInfo. + + Args: + path: a list of path indexes as per + https://github.com/google/protobuf/blob/a08b03d4c00a5793b88b494f672513f6ad46a681/src/google/protobuf/descriptor.proto#L717. + + Returns: + Comment object. + """ + location = self.LocationPathLookup(path) + if location is not None: + return Comment( + location.leading_comments, + annotations.ExtractAnnotations(location.leading_comments, self.file_level_annotations)) + return Comment('', {}) + + +class TypeContext(object): + """Contextual information for a message/field. + + Provides information around namespaces and enclosing types for fields and + nested messages/enums. + """ + + def __init__(self, source_code_info, name): + # SourceCodeInfo as per + # https://github.com/google/protobuf/blob/a08b03d4c00a5793b88b494f672513f6ad46a681/src/google/protobuf/descriptor.proto. + self.source_code_info = source_code_info + # path: a list of path indexes as per + # https://github.com/google/protobuf/blob/a08b03d4c00a5793b88b494f672513f6ad46a681/src/google/protobuf/descriptor.proto#L717. + # Extended as nested objects are traversed. + self.path = [] + # Message/enum/field name. Extended as nested objects are traversed. + self.name = name + # Map from type name to the correct type annotation string, e.g. from + # ".envoy.api.v2.Foo.Bar" to "map". This is lost during + # proto synthesis and is dynamically recovered in TraverseMessage. + self.map_typenames = {} + # Map from a message's oneof index to the fields sharing a oneof. + self.oneof_fields = {} + # Map from a message's oneof index to the name of oneof. + self.oneof_names = {} + # Map from a message's oneof index to the "required" bool property. + self.oneof_required = {} + self.type_name = 'file' + + def _Extend(self, path, type_name, name): + if not self.name: + extended_name = name + else: + extended_name = '%s.%s' % (self.name, name) + extended = TypeContext(self.source_code_info, extended_name) + extended.path = self.path + path + extended.type_name = type_name + extended.map_typenames = self.map_typenames.copy() + extended.oneof_fields = self.oneof_fields.copy() + extended.oneof_names = self.oneof_names.copy() + extended.oneof_required = self.oneof_required.copy() + return extended + + def ExtendMessage(self, index, name): + """Extend type context with a message. + + Args: + index: message index in file. + name: message name. + """ + return self._Extend([4, index], 'message', name) + + def ExtendNestedMessage(self, index, name): + """Extend type context with a nested message. + + Args: + index: nested message index in message. + name: message name. + """ + return self._Extend([3, index], 'message', name) + + def ExtendField(self, index, name): + """Extend type context with a field. + + Args: + index: field index in message. + name: field name. + """ + return self._Extend([2, index], 'field', name) + + def ExtendEnum(self, index, name): + """Extend type context with an enum. + + Args: + index: enum index in file. + name: enum name. + """ + return self._Extend([5, index], 'enum', name) + + def ExtendNestedEnum(self, index, name): + """Extend type context with a nested enum. + + Args: + index: enum index in message. + name: enum name. + """ + return self._Extend([4, index], 'enum', name) + + def ExtendEnumValue(self, index, name): + """Extend type context with an enum enum. + + Args: + index: enum value index in enum. + name: value name. + """ + return self._Extend([2, index], 'enum_value', name) + + def ExtendOneof(self, index, name): + """Extend type context with an oneof declaration. + + Args: + index: oneof index in oneof_decl. + name: oneof name. + """ + return self._Extend([8, index], 'oneof', name) + + @property + def location(self): + """SourceCodeInfo.Location for type context.""" + return self.source_code_info.LocationPathLookup(self.path) + + @property + def leading_comment(self): + """Leading comment for type context.""" + return self.source_code_info.LeadingCommentPathLookup(self.path) diff --git a/tools/api_proto_plugin/visitor.py b/tools/api_proto_plugin/visitor.py new file mode 100644 index 0000000000..0065537f0a --- /dev/null +++ b/tools/api_proto_plugin/visitor.py @@ -0,0 +1,45 @@ +"""FileDescriptorProto visitor interface for api_proto_plugin implementations.""" + + +class Visitor(object): + """Abstract visitor interface for api_proto_plugin implementation.""" + + def VisitEnum(self, enum_proto, type_context): + """Visit an enum definition. + + Args: + enum_proto: EnumDescriptorProto for enum. + type_context: type_context.TypeContext for enum type. + + Returns: + Plugin specific output. + """ + pass + + def VisitMessage(self, msg_proto, type_context, nested_msgs, nested_enums): + """Visit a message definition. + + Args: + msg_proto: DescriptorProto for message. + type_context: type_context.TypeContext for message type. + nested_msgs: a list of results from visiting nested messages. + nested_enums: a list of results from visiting nested enums. + + Returns: + Plugin specific output. + """ + pass + + def VisitFile(self, file_proto, type_context, msgs, enums): + """Visit a proto file definition. + + Args: + file_proto: FileDescriptorProto for file. + type_context: type_context.TypeContext for file. + msgs: a list of results from visiting messages. + enums: a list of results from visiting enums. + + Returns: + Plugin specific output. + """ + pass diff --git a/tools/check_format.py b/tools/check_format.py index 9327d95338..5f52b2d5b9 100755 --- a/tools/check_format.py +++ b/tools/check_format.py @@ -43,20 +43,6 @@ "./test/test_common/utility.cc", "./test/test_common/utility.h", "./test/integration/integration.h") -# Files matching these directories can use stats by string for now. These should -# be eliminated but for now we don't want to grow this work. The goal for this -# whitelist is to eliminate it by making code transformations similar to -# https://github.com/envoyproxy/envoy/pull/7573 and others. -# -# TODO(#4196): Eliminate this list completely and then merge #4980. -STAT_FROM_STRING_WHITELIST = ("./source/extensions/filters/http/fault/fault_filter.cc", - "./source/extensions/filters/http/ip_tagging/ip_tagging_filter.cc", - "./source/extensions/filters/network/mongo_proxy/proxy.cc", - "./source/extensions/stat_sinks/common/statsd/statsd.cc", - "./source/extensions/transport_sockets/tls/context_impl.cc", - "./source/server/guarddog_impl.cc", - "./source/server/overload_manager_impl.cc") - # Files in these paths can use MessageLite::SerializeAsString SERIALIZE_AS_STRING_WHITELIST = ("./test/common/protobuf/utility_test.cc", "./test/common/grpc/codec_test.cc") @@ -64,6 +50,16 @@ # Files in these paths can use Protobuf::util::JsonStringToMessage JSON_STRING_TO_MESSAGE_WHITELIST = ("./source/common/protobuf/utility.cc") +# Files in these paths can use std::regex +STD_REGEX_WHITELIST = ("./source/common/common/utility.cc", "./source/common/common/regex.h", + "./source/common/common/regex.cc", + "./source/common/stats/tag_extractor_impl.h", + "./source/common/stats/tag_extractor_impl.cc", + "./source/common/access_log/access_log_formatter.cc", + "./source/extensions/filters/http/squash/squash_filter.h", + "./source/extensions/filters/http/squash/squash_filter.cc", + "./source/server/http/admin.h", "./source/server/http/admin.cc") + CLANG_FORMAT_PATH = os.getenv("CLANG_FORMAT", "clang-format-8") BUILDIFIER_PATH = os.getenv("BUILDIFIER_BIN", "$GOPATH/bin/buildifier") ENVOY_BUILD_FIXER_PATH = os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), @@ -77,6 +73,7 @@ PROTO_OPTION_JAVA_PACKAGE = "option java_package = \"" PROTO_OPTION_JAVA_OUTER_CLASSNAME = "option java_outer_classname = \"" PROTO_OPTION_JAVA_MULTIPLE_FILES = "option java_multiple_files = " +PROTO_OPTION_GO_PACKAGE = "option go_package = \"" # yapf: disable PROTOBUF_TYPE_ERRORS = { @@ -142,7 +139,6 @@ "extensions/common/tap", "extensions/transport_sockets/raw_buffer", "extensions/transport_sockets/tap", - "extensions/transport_sockets/tls", "extensions/tracers/zipkin", "extensions/tracers/dynamic_ot", "extensions/tracers/opencensus", @@ -331,8 +327,8 @@ def whitelistedForJsonStringToMessage(file_path): return file_path in JSON_STRING_TO_MESSAGE_WHITELIST -def whitelistedForStatFromString(file_path): - return file_path in STAT_FROM_STRING_WHITELIST +def whitelistedForStdRegex(file_path): + return file_path.startswith("./test") or file_path in STD_REGEX_WHITELIST def findSubstringAndReturnError(pattern, file_path, error_message): @@ -579,10 +575,12 @@ def checkSourceLine(line, file_path, reportError): reportError("Don't use Protobuf::util::JsonStringToMessage, use TestUtility::loadFromJson.") if isInSubdir(file_path, 'source') and file_path.endswith('.cc') and \ - not whitelistedForStatFromString(file_path) and \ ('.counter(' in line or '.gauge(' in line or '.histogram(' in line): reportError("Don't lookup stats by name at runtime; use StatName saved during construction") + if not whitelistedForStdRegex(file_path) and "std::regex" in line: + reportError("Don't use std::regex in code that handles untrusted input. Use RegexMatcher") + def checkBuildLine(line, file_path, reportError): if "@bazel_tools" in line and not (isSkylarkFile(file_path) or file_path.startswith("./bazel/")): @@ -627,6 +625,17 @@ def checkBuildPath(file_path): command = "%s %s | diff %s -" % (ENVOY_BUILD_FIXER_PATH, file_path, file_path) error_messages += executeCommand(command, "envoy_build_fixer check failed", file_path) + if isBuildFile(file_path) and file_path.startswith(args.api_prefix + "envoy"): + found = False + finput = fileinput.input(file_path) + for line in finput: + if "api_proto_package(" in line: + found = True + break + finput.close() + if not found: + error_messages += ["API build file does not provide api_proto_package()"] + command = "%s -mode=diff %s" % (BUILDIFIER_PATH, file_path) error_messages += executeCommand(command, "buildifier check failed", file_path) error_messages += checkFileContents(file_path, checkBuildLine) @@ -676,6 +685,9 @@ def checkSourcePath(file_path): "Java proto option 'java_outer_classname' not set") error_messages += errorIfNoSubstringFound("\n" + PROTO_OPTION_JAVA_MULTIPLE_FILES, file_path, "Java proto option 'java_multiple_files' not set") + with open(file_path) as f: + if PROTO_OPTION_GO_PACKAGE in f.read(): + error_messages += ["go_package option should not be set in %s" % file_path] return error_messages diff --git a/tools/check_format_test_helper.py b/tools/check_format_test_helper.py index 7309a4bcaf..844488a8aa 100755 --- a/tools/check_format_test_helper.py +++ b/tools/check_format_test_helper.py @@ -217,6 +217,8 @@ def checkFileExpectingOK(filename): errors += checkUnfixableError( "histogram_from_string.cc", "Don't lookup stats by name at runtime; use StatName saved during construction") + errors += checkUnfixableError( + "regex.cc", "Don't use std::regex in code that handles untrusted input. Use RegexMatcher") errors += fixFileExpectingFailure( "api/missing_package.proto", @@ -236,6 +238,7 @@ def checkFileExpectingOK(filename): errors += checkAndFixError("bad_envoy_build_sys_ref.BUILD", "Superfluous '@envoy//' prefix") errors += checkAndFixError("proto_format.proto", "clang-format check failed") errors += checkAndFixError("api/java_options.proto", "Java proto option") + errors += checkFileExpectingError("api/go_package.proto", "go_package option should not be set") errors += checkAndFixError( "cpp_std.cc", "term absl::make_unique< should be replaced with standard library term std::make_unique<") diff --git a/tools/deprecate_features/deprecate_features.sh b/tools/deprecate_features/deprecate_features.sh index 43878548be..661b348e0f 100644 --- a/tools/deprecate_features/deprecate_features.sh +++ b/tools/deprecate_features/deprecate_features.sh @@ -4,11 +4,4 @@ set -e -SCRIPT_DIR=$(realpath "$(dirname "$0")") -BUILD_DIR=build_tools -VENV_DIR="$BUILD_DIR"/deprecate_features - -source_venv "$VENV_DIR" -pip install -r "${SCRIPT_DIR}"/requirements.txt - -python "${SCRIPT_DIR}/deprecate_features.py" $* +python_venv deprecate_features diff --git a/tools/deprecate_version/deprecate_version.sh b/tools/deprecate_version/deprecate_version.sh index fe77384bcb..5421f66565 100755 --- a/tools/deprecate_version/deprecate_version.sh +++ b/tools/deprecate_version/deprecate_version.sh @@ -4,11 +4,4 @@ set -e -SCRIPT_DIR=$(realpath "$(dirname "$0")") -BUILD_DIR=build_tools -VENV_DIR="$BUILD_DIR"/deprecate_version - -source_venv "$VENV_DIR" -pip install -r "${SCRIPT_DIR}"/requirements.txt - -python "${SCRIPT_DIR}/deprecate_version.py" $* +python_venv deprecate_version diff --git a/tools/format_python_tools.py b/tools/format_python_tools.py index 8b2cacc321..9d56bcc9d9 100644 --- a/tools/format_python_tools.py +++ b/tools/format_python_tools.py @@ -39,7 +39,7 @@ def validateFormat(fix=False): successful_update_files = set() for python_file in collectFiles(): reformatted_source, encoding, changed = FormatFile(python_file, - style_config='.style.yapf', + style_config='tools/.style.yapf', in_place=fix, print_diff=not fix) if not fix: diff --git a/tools/format_python_tools.sh b/tools/format_python_tools.sh index 6749ade470..56587489a7 100755 --- a/tools/format_python_tools.sh +++ b/tools/format_python_tools.sh @@ -1,19 +1,11 @@ #!/bin/bash -set -e - -VENV_DIR="pyformat" -SCRIPTPATH=$(realpath "$(dirname $0)") -. $SCRIPTPATH/shell_utils.sh -cd "$SCRIPTPATH" +. tools/shell_utils.sh -source_venv "$VENV_DIR" -echo "Installing requirements..." -pip3 -q install --upgrade pip -pip3 -q install -r requirements.txt +set -e echo "Running Python format check..." -python3 format_python_tools.py $1 +python_venv format_python_tools $1 echo "Running Python3 flake8 check..." python3 -m flake8 --version diff --git a/tools/github/requirements.txt b/tools/github/requirements.txt new file mode 100644 index 0000000000..e1b66335b7 --- /dev/null +++ b/tools/github/requirements.txt @@ -0,0 +1 @@ +PyGithub==1.43.8 diff --git a/tools/github/sync_assignable.py b/tools/github/sync_assignable.py new file mode 100644 index 0000000000..3a437fa8d3 --- /dev/null +++ b/tools/github/sync_assignable.py @@ -0,0 +1,53 @@ +# Sync envoyproxy organization users to envoyproxy/assignable team. +# +# This can be used for bulk cleanups if envoyproxy/assignable is not consistent +# with organization membership. In general, prefer to add new members by editing +# the envoyproxy/assignable in the GitHub UI, which will also cause an +# organization invite to be sent; this reduces the need to manually manage +# access tokens. +# +# Note: the access token supplied must have admin:org (write:org, read:org) +# permissions (and ideally be scoped no more widely than this). See Settings -> +# Developer settings -> Personal access tokens for access token generation. +# Ideally, these should be cleaned up after use. + +import os +import sys + +import github + + +def GetConfirmation(): + """Obtain stdin confirmation to add users in GH.""" + return input('Add users to envoyproxy/assignable ? [yN] ').strip().lower() in ('y', 'yes') + + +def SyncAssignable(access_token): + organization = github.Github(access_token).get_organization('envoyproxy') + team = organization.get_team_by_slug('assignable') + organization_members = set(organization.get_members()) + assignable_members = set(team.get_members()) + missing = organization_members.difference(assignable_members) + + if not missing: + print('envoyproxy/assignable is consistent with organization membership.') + return 0 + + print('The following organization members are missing from envoyproxy/assignable:') + for m in missing: + print(m.login) + + if not GetConfirmation(): + return 1 + + for m in missing: + team.add_membership(m, 'member') + + +if __name__ == '__main__': + access_token = os.getenv('GH_ACCESS_TOKEN') + if not access_token: + print('Missing GH_ACCESS_TOKEN') + sys.exit(1) + + sys.exit(SyncAssignable(access_token)) diff --git a/tools/github/sync_assignable.sh b/tools/github/sync_assignable.sh new file mode 100755 index 0000000000..ac11d9ccc3 --- /dev/null +++ b/tools/github/sync_assignable.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +. tools/shell_utils.sh + +set -e + +python_venv sync_assignable diff --git a/tools/protodoc/BUILD b/tools/protodoc/BUILD index 8e428b5d24..d2c9b12a67 100644 --- a/tools/protodoc/BUILD +++ b/tools/protodoc/BUILD @@ -3,9 +3,10 @@ licenses(["notice"]) # Apache 2 py_binary( name = "protodoc", srcs = ["protodoc.py"], - python_version = "PY2", + python_version = "PY3", visibility = ["//visibility:public"], deps = [ + "//tools/api_proto_plugin", "@com_envoyproxy_protoc_gen_validate//validate:validate_py", "@com_google_protobuf//:protobuf_python", ], diff --git a/tools/protodoc/protodoc.py b/tools/protodoc/protodoc.py index 8835dce7e4..e386757a52 100755 --- a/tools/protodoc/protodoc.py +++ b/tools/protodoc/protodoc.py @@ -4,15 +4,14 @@ # https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html for Sphinx RST syntax. from collections import defaultdict -import cProfile import functools import os -import pstats -import StringIO import re -import sys -from google.protobuf.compiler import plugin_pb2 +from tools.api_proto_plugin import annotations +from tools.api_proto_plugin import plugin +from tools.api_proto_plugin import visitor + from validate import validate_pb2 # Namespace prefix for Envoy core APIs. @@ -30,60 +29,55 @@ # http://www.fileformat.info/info/unicode/char/2063/index.htm UNICODE_INVISIBLE_SEPARATOR = u'\u2063' -# Key-value annotation regex. -ANNOTATION_REGEX = re.compile('\[#([\w-]+?):(.*?)\]\s?', re.DOTALL) - -# Page/section titles with special prefixes in the proto comments -DOC_TITLE_ANNOTATION = 'protodoc-title' +# Template for data plane API URLs. +DATA_PLANE_API_URL_FMT = 'https://github.com/envoyproxy/envoy/blob/{}/api/%s#L%d'.format( + os.environ['ENVOY_BLOB_SHA']) -# Not implemented yet annotation on leading comments, leading to insertion of -# warning on field. -NOT_IMPLEMENTED_WARN_ANNOTATION = 'not-implemented-warn' -# Not implemented yet annotation on leading comments, leading to hiding of -# field. -NOT_IMPLEMENTED_HIDE_ANNOTATION = 'not-implemented-hide' +class ProtodocError(Exception): + """Base error class for the protodoc module.""" -# Comment. Just used for adding text that will not go into the docs at all. -COMMENT_ANNOTATION = 'comment' -# proto compatibility status. -PROTO_STATUS_ANNOTATION = 'proto-status' +def HideNotImplemented(comment): + """Should a given type_context.Comment be hidden because it is tagged as [#not-implemented-hide:]?""" + return annotations.NOT_IMPLEMENTED_HIDE_ANNOTATION in comment.annotations -# Where v2 differs from v1.. -V2_API_DIFF_ANNOTATION = 'v2-api-diff' -VALID_ANNOTATIONS = set([ - DOC_TITLE_ANNOTATION, - NOT_IMPLEMENTED_WARN_ANNOTATION, - NOT_IMPLEMENTED_HIDE_ANNOTATION, - V2_API_DIFF_ANNOTATION, - COMMENT_ANNOTATION, - PROTO_STATUS_ANNOTATION, -]) +def GithubUrl(type_context): + """Obtain data plane API Github URL by path from a TypeContext. -# These can propagate from file scope to message/enum scope (and be overridden). -INHERITED_ANNOTATIONS = set([ - PROTO_STATUS_ANNOTATION, -]) + Args: + type_context: type_context.TypeContext for node. -# Template for data plane API URLs. -DATA_PLANE_API_URL_FMT = 'https://github.com/envoyproxy/envoy/blob/{}/api/%s#L%d'.format( - os.environ['ENVOY_BLOB_SHA']) + Returns: + A string with a corresponding data plane API GitHub Url. + """ + if type_context.location is not None: + return DATA_PLANE_API_URL_FMT % (type_context.source_code_info.name, + type_context.location.span[0]) + return '' -class ProtodocError(Exception): - """Base error class for the protodoc module.""" +def FormatCommentWithAnnotations(comment, type_name=''): + """Format a comment string with additional RST for annotations. + Args: + comment: comment string. + type_name: optional, 'message' or 'enum' may be specified for additional + message/enum specific annotations. -def FormatCommentWithAnnotations(s, annotations, type_name): - if NOT_IMPLEMENTED_WARN_ANNOTATION in annotations: + Returns: + A string with additional RST from annotations. + """ + s = annotations.WithoutAnnotations(StripLeadingSpace(comment.raw) + '\n') + if annotations.NOT_IMPLEMENTED_WARN_ANNOTATION in comment.annotations: s += '\n.. WARNING::\n Not implemented yet\n' - if V2_API_DIFF_ANNOTATION in annotations: - s += '\n.. NOTE::\n **v2 API difference**: ' + annotations[V2_API_DIFF_ANNOTATION] + '\n' + if annotations.V2_API_DIFF_ANNOTATION in comment.annotations: + s += '\n.. NOTE::\n **v2 API difference**: ' + comment.annotations[ + annotations.V2_API_DIFF_ANNOTATION] + '\n' if type_name == 'message' or type_name == 'enum': - if PROTO_STATUS_ANNOTATION in annotations: - status = annotations[PROTO_STATUS_ANNOTATION] + if annotations.PROTO_STATUS_ANNOTATION in comment.annotations: + status = comment.annotations[annotations.PROTO_STATUS_ANNOTATION] if status not in ['frozen', 'draft', 'experimental']: raise ProtodocError('Unknown proto status: %s' % status) if status == 'draft' or status == 'experimental': @@ -92,209 +86,13 @@ def FormatCommentWithAnnotations(s, annotations, type_name): return s -def ExtractAnnotations(s, inherited_annotations=None, type_name='file'): - """Extract annotations from a given comment string. - - Args: - s: string that may contains annotations. - inherited_annotations: annotation map from file-level inherited annotations - (or None) if this is a file-level comment. - Returns: - Pair of string with with annotations stripped and annotation map. - """ - annotations = { - k: v for k, v in (inherited_annotations or {}).items() if k in INHERITED_ANNOTATIONS - } - # Extract annotations. - groups = re.findall(ANNOTATION_REGEX, s) - # Remove annotations. - without_annotations = re.sub(ANNOTATION_REGEX, '', s) - for group in groups: - annotation = group[0] - if annotation not in VALID_ANNOTATIONS: - raise ProtodocError('Unknown annotation: %s' % annotation) - annotations[group[0]] = group[1].lstrip() - return FormatCommentWithAnnotations(without_annotations, annotations, type_name), annotations - - -class SourceCodeInfo(object): - """Wrapper for SourceCodeInfo proto.""" - - def __init__(self, name, source_code_info): - self._name = name - self._proto = source_code_info - self._leading_comments = { - str(location.path): location.leading_comments for location in self._proto.location - } - self._file_level_comment = None - - @property - def file_level_comment(self): - """Obtain inferred file level comment.""" - if self._file_level_comment: - return self._file_level_comment - comment = '' - earliest_detached_comment = max(max(location.span) for location in self._proto.location) - for location in self._proto.location: - if location.leading_detached_comments and location.span[0] < earliest_detached_comment: - comment = StripLeadingSpace(''.join(location.leading_detached_comments)) + '\n' - earliest_detached_comment = location.span[0] - self._file_level_comment = comment - return comment - - def LeadingCommentPathLookup(self, path, type_name): - """Lookup leading comment by path in SourceCodeInfo. - - Args: - path: a list of path indexes as per - https://github.com/google/protobuf/blob/a08b03d4c00a5793b88b494f672513f6ad46a681/src/google/protobuf/descriptor.proto#L717. - type_name: name of type the comment belongs to. - Returns: - Pair of attached leading comment and Annotation objects, where there is a - leading comment - otherwise ('', []). - """ - leading_comment = self._leading_comments.get(str(path), None) - if leading_comment is not None: - _, file_annotations = ExtractAnnotations(self.file_level_comment) - return ExtractAnnotations( - StripLeadingSpace(leading_comment) + '\n', file_annotations, type_name) - return '', [] - - def GithubUrl(self, path): - """Obtain data plane API Github URL by path from SourceCodeInfo. - - Args: - path: a list of path indexes as per - https://github.com/google/protobuf/blob/a08b03d4c00a5793b88b494f672513f6ad46a681/src/google/protobuf/descriptor.proto#L717. - Returns: - A string with a corresponding data plane API GitHub Url. - """ - for location in self._proto.location: - if location.path == path: - return DATA_PLANE_API_URL_FMT % (self._name, location.span[0]) - return '' - - -class TypeContext(object): - """Contextual information for a message/field. - - Provides information around namespaces and enclosing types for fields and - nested messages/enums. - """ - - def __init__(self, source_code_info, name): - # SourceCodeInfo as per - # https://github.com/google/protobuf/blob/a08b03d4c00a5793b88b494f672513f6ad46a681/src/google/protobuf/descriptor.proto. - self.source_code_info = source_code_info - # path: a list of path indexes as per - # https://github.com/google/protobuf/blob/a08b03d4c00a5793b88b494f672513f6ad46a681/src/google/protobuf/descriptor.proto#L717. - # Extended as nested objects are traversed. - self.path = [] - # Message/enum/field name. Extended as nested objects are traversed. - self.name = name - # Map from type name to the correct type annotation string, e.g. from - # ".envoy.api.v2.Foo.Bar" to "map". This is lost during - # proto synthesis and is dynamically recovered in FormatMessage. - self.map_typenames = {} - # Map from a message's oneof index to the fields sharing a oneof. - self.oneof_fields = {} - # Map from a message's oneof index to the name of oneof. - self.oneof_names = {} - # Map from a message's oneof index to the "required" bool property. - self.oneof_required = {} - self.type_name = 'file' - - def _Extend(self, path, type_name, name): - if not self.name: - extended_name = name - else: - extended_name = '%s.%s' % (self.name, name) - extended = TypeContext(self.source_code_info, extended_name) - extended.path = self.path + path - extended.type_name = type_name - extended.map_typenames = self.map_typenames.copy() - extended.oneof_fields = self.oneof_fields.copy() - extended.oneof_names = self.oneof_names.copy() - extended.oneof_required = self.oneof_required.copy() - return extended - - def ExtendMessage(self, index, name): - """Extend type context with a message. - - Args: - index: message index in file. - name: message name. - """ - return self._Extend([4, index], 'message', name) - - def ExtendNestedMessage(self, index, name): - """Extend type context with a nested message. - - Args: - index: nested message index in message. - name: message name. - """ - return self._Extend([3, index], 'message', name) - - def ExtendField(self, index, name): - """Extend type context with a field. - - Args: - index: field index in message. - name: field name. - """ - return self._Extend([2, index], 'field', name) - - def ExtendEnum(self, index, name): - """Extend type context with an enum. - - Args: - index: enum index in file. - name: enum name. - """ - return self._Extend([5, index], 'enum', name) - - def ExtendNestedEnum(self, index, name): - """Extend type context with a nested enum. - - Args: - index: enum index in message. - name: enum name. - """ - return self._Extend([4, index], 'enum', name) - - def ExtendEnumValue(self, index, name): - """Extend type context with an enum enum. - - Args: - index: enum value index in enum. - name: value name. - """ - return self._Extend([2, index], 'enum_value', name) - - def ExtendOneof(self, index, name): - """Extend type context with an oneof declaration. - - Args: - index: oneof index in oneof_decl. - name: oneof name. - """ - return self._Extend([8, index], "oneof", name) - - def LeadingCommentPathLookup(self): - return self.source_code_info.LeadingCommentPathLookup(self.path, self.type_name) - - def GithubUrl(self): - return self.source_code_info.GithubUrl(self.path) - - def MapLines(f, s): """Apply a function across each line in a flat string. Args: f: A string transform function for a line. s: A string consisting of potentially multiple lines. + Returns: A flat string with f applied to each line. """ @@ -325,28 +123,33 @@ def FormatHeader(style, text): Args: style: underline style, e.g. '=', '-'. text: header text + Returns: RST formatted header. """ return '%s\n%s\n\n' % (text, style * len(text)) -def FormatHeaderFromFile(style, file_level_comment, alt): +def FormatHeaderFromFile(style, source_code_info, proto_name): """Format RST header based on special file level title Args: style: underline style, e.g. '=', '-'. - file_level_comment: detached comment at top of file. - alt: If the file_level_comment does not contain a user - specified title, use the alt text as page title. + source_code_info: SourceCodeInfo object. + proto_name: If the file_level_comment does not contain a user specified + title, use this as page title. + Returns: RST formatted header, and file level comment without page title strings. """ - anchor = FormatAnchor(FileCrossRefLabel(alt)) - stripped_comment, annotations = ExtractAnnotations(file_level_comment) - if DOC_TITLE_ANNOTATION in annotations: - return anchor + FormatHeader(style, annotations[DOC_TITLE_ANNOTATION]), stripped_comment - return anchor + FormatHeader(style, alt), stripped_comment + anchor = FormatAnchor(FileCrossRefLabel(proto_name)) + stripped_comment = annotations.WithoutAnnotations( + StripLeadingSpace('\n'.join(c + '\n' for c in source_code_info.file_level_comments))) + if annotations.DOC_TITLE_ANNOTATION in source_code_info.file_level_annotations: + return anchor + FormatHeader( + style, + source_code_info.file_level_annotations[annotations.DOC_TITLE_ANNOTATION]), stripped_comment + return anchor + FormatHeader(style, proto_name), stripped_comment def FormatFieldTypeAsJson(type_context, field): @@ -355,10 +158,9 @@ def FormatFieldTypeAsJson(type_context, field): Args: type_context: contextual information for message/enum/field. field: FieldDescriptor proto. - Return: - RST formatted pseudo-JSON string representation of field type. + Return: RST formatted pseudo-JSON string representation of field type. """ - if NormalizeFQN(field.type_name) in type_context.map_typenames: + if TypeNameFromFQN(field.type_name) in type_context.map_typenames: return '"{...}"' if field.label == field.LABEL_REPEATED: return '[]' @@ -373,14 +175,13 @@ def FormatMessageAsJson(type_context, msg): Args: type_context: contextual information for message/enum/field. msg: message definition DescriptorProto. - Return: - RST formatted pseudo-JSON string representation of message definition. + Return: RST formatted pseudo-JSON string representation of message definition. """ lines = [] for index, field in enumerate(msg.field): field_type_context = type_context.ExtendField(index, field.name) - leading_comment, comment_annotations = field_type_context.LeadingCommentPathLookup() - if NOT_IMPLEMENTED_HIDE_ANNOTATION in comment_annotations: + leading_comment = field_type_context.leading_comment + if HideNotImplemented(leading_comment): continue lines.append('"%s": %s' % (field.name, FormatFieldTypeAsJson(type_context, field))) @@ -390,21 +191,44 @@ def FormatMessageAsJson(type_context, msg): return '.. code-block:: json\n\n {}\n\n' -def NormalizeFQN(fqn): - """Normalize a fully qualified field type name. +def NormalizeFieldTypeName(field_fqn): + """Normalize a fully qualified field type name, e.g. + + .envoy.foo.bar. - Strips leading ENVOY_API_NAMESPACE_PREFIX and ENVOY_PREFIX and makes pretty wrapped type names. + Strips leading ENVOY_API_NAMESPACE_PREFIX and ENVOY_PREFIX. Args: - fqn: a fully qualified type name from FieldDescriptorProto.type_name. - Return: - Normalized type name. + field_fqn: a fully qualified type name from FieldDescriptorProto.type_name. + Return: Normalized type name. """ - if fqn.startswith(ENVOY_API_NAMESPACE_PREFIX): - return fqn[len(ENVOY_API_NAMESPACE_PREFIX):] - if fqn.startswith(ENVOY_PREFIX): - return fqn[len(ENVOY_PREFIX):] - return fqn + if field_fqn.startswith(ENVOY_API_NAMESPACE_PREFIX): + return field_fqn[len(ENVOY_API_NAMESPACE_PREFIX):] + if field_fqn.startswith(ENVOY_PREFIX): + return field_fqn[len(ENVOY_PREFIX):] + return field_fqn + + +def NormalizeTypeContextName(type_name): + """Normalize a type name, e.g. + + envoy.foo.bar. + + Strips leading ENVOY_API_NAMESPACE_PREFIX and ENVOY_PREFIX. + + Args: + type_name: a name from a TypeContext. + Return: Normalized type name. + """ + return NormalizeFieldTypeName(QualifyTypeName(type_name)) + + +def QualifyTypeName(type_name): + return '.' + type_name + + +def TypeNameFromFQN(fqn): + return fqn[1:] def FormatEmph(s): @@ -421,15 +245,17 @@ def FormatFieldType(type_context, field): Args: type_context: contextual information for message/enum/field. field: FieldDescriptor proto. - Return: - RST formatted field type. + Return: RST formatted field type. """ if field.type_name.startswith(ENVOY_API_NAMESPACE_PREFIX) or field.type_name.startswith( ENVOY_PREFIX): - type_name = NormalizeFQN(field.type_name) + type_name = NormalizeFieldTypeName(field.type_name) if field.type == field.TYPE_MESSAGE: - if type_context.map_typenames and type_name in type_context.map_typenames: - return type_context.map_typenames[type_name] + if type_context.map_typenames and TypeNameFromFQN( + field.type_name) in type_context.map_typenames: + return 'map<%s, %s>' % tuple( + map(functools.partial(FormatFieldType, type_context), + type_context.map_typenames[TypeNameFromFQN(field.type_name)])) return FormatInternalLink(type_name, MessageCrossRefLabel(type_name)) if field.type == field.TYPE_ENUM: return FormatInternalLink(type_name, EnumCrossRefLabel(type_name)) @@ -511,49 +337,55 @@ def FormatFieldAsDefinitionListItem(outer_type_context, type_context, field): outer_type_context: contextual information for enclosing message. type_context: contextual information for message/enum/field. field: FieldDescriptorProto. + Returns: RST formatted definition list item. """ - annotations = [] + field_annotations = [] - anchor = FormatAnchor(FieldCrossRefLabel(type_context.name)) + anchor = FormatAnchor(FieldCrossRefLabel(NormalizeTypeContextName(type_context.name))) if field.options.HasExtension(validate_pb2.rules): rule = field.options.Extensions[validate_pb2.rules] if ((rule.HasField('message') and rule.message.required) or (rule.HasField('string') and rule.string.min_bytes > 0) or (rule.HasField('repeated') and rule.repeated.min_items > 0)): - annotations = ['*REQUIRED*'] - leading_comment, comment_annotations = type_context.LeadingCommentPathLookup() - if NOT_IMPLEMENTED_HIDE_ANNOTATION in comment_annotations: + field_annotations = ['*REQUIRED*'] + leading_comment = type_context.leading_comment + formatted_leading_comment = FormatCommentWithAnnotations(leading_comment) + if HideNotImplemented(leading_comment): return '' if field.HasField('oneof_index'): oneof_context = outer_type_context.ExtendOneof(field.oneof_index, type_context.oneof_names[field.oneof_index]) - oneof_comment, oneof_comment_annotations = oneof_context.LeadingCommentPathLookup() - if NOT_IMPLEMENTED_HIDE_ANNOTATION in oneof_comment_annotations: + oneof_comment = oneof_context.leading_comment + formatted_oneof_comment = FormatCommentWithAnnotations(oneof_comment) + if HideNotImplemented(oneof_comment): return '' # If the oneof only has one field and marked required, mark the field as required. if len(type_context.oneof_fields[field.oneof_index]) == 1 and type_context.oneof_required[ field.oneof_index]: - annotations = ['*REQUIRED*'] + field_annotations = ['*REQUIRED*'] if len(type_context.oneof_fields[field.oneof_index]) > 1: # Fields in oneof shouldn't be marked as required when we have oneof comment below it. - annotations = [] + field_annotations = [] oneof_template = '\nPrecisely one of %s must be set.\n' if type_context.oneof_required[ field.oneof_index] else '\nOnly one of %s may be set.\n' - oneof_comment += oneof_template % ', '.join( - FormatInternalLink(f, FieldCrossRefLabel(outer_type_context.ExtendField(i, f).name)) + formatted_oneof_comment += oneof_template % ', '.join( + FormatInternalLink( + f, + FieldCrossRefLabel(NormalizeTypeContextName( + outer_type_context.ExtendField(i, f).name))) for i, f in type_context.oneof_fields[field.oneof_index]) else: - oneof_comment = '' + formatted_oneof_comment = '' comment = '(%s) ' % ', '.join([FormatFieldType(type_context, field)] + - annotations) + leading_comment + field_annotations) + formatted_leading_comment return anchor + field.name + '\n' + MapLines(functools.partial(Indent, 2), - comment + oneof_comment) + comment + formatted_oneof_comment) def FormatMessageAsDefinitionList(type_context, msg): @@ -562,6 +394,7 @@ def FormatMessageAsDefinitionList(type_context, msg): Args: type_context: contextual information for message/enum/field. msg: DescriptorProto. + Returns: RST formatted definition list item. """ @@ -570,9 +403,8 @@ def FormatMessageAsDefinitionList(type_context, msg): type_context.oneof_names = defaultdict(list) for index, field in enumerate(msg.field): if field.HasField('oneof_index'): - _, comment_annotations = type_context.ExtendField(index, - field.name).LeadingCommentPathLookup() - if NOT_IMPLEMENTED_HIDE_ANNOTATION in comment_annotations: + leading_comment = type_context.ExtendField(index, field.name).leading_comment + if HideNotImplemented(leading_comment): continue type_context.oneof_fields[field.oneof_index].append((index, field.name)) for index, oneof_decl in enumerate(msg.oneof_decl): @@ -584,59 +416,23 @@ def FormatMessageAsDefinitionList(type_context, msg): field) for index, field in enumerate(msg.field)) + '\n' -def FormatMessage(type_context, msg): - """Format a DescriptorProto as RST section. - - Args: - type_context: contextual information for message/enum/field. - msg: DescriptorProto. - Returns: - RST formatted section. - """ - # Skip messages synthesized to represent map types. - if msg.options.map_entry: - return '' - # We need to do some extra work to recover the map type annotation from the - # synthesized messages. - type_context.map_typenames = { - '%s.%s' % (type_context.name, nested_msg.name): - 'map<%s, %s>' % tuple(map(functools.partial(FormatFieldType, type_context), nested_msg.field)) - for nested_msg in msg.nested_type - if nested_msg.options.map_entry - } - nested_msgs = '\n'.join( - FormatMessage(type_context.ExtendNestedMessage(index, nested_msg.name), nested_msg) - for index, nested_msg in enumerate(msg.nested_type)) - nested_enums = '\n'.join( - FormatEnum(type_context.ExtendNestedEnum(index, nested_enum.name), nested_enum) - for index, nested_enum in enumerate(msg.enum_type)) - anchor = FormatAnchor(MessageCrossRefLabel(type_context.name)) - header = FormatHeader('-', type_context.name) - proto_link = FormatExternalLink('[%s proto]' % type_context.name, - type_context.GithubUrl()) + '\n\n' - leading_comment, annotations = type_context.LeadingCommentPathLookup() - if NOT_IMPLEMENTED_HIDE_ANNOTATION in annotations: - return '' - return anchor + header + proto_link + leading_comment + FormatMessageAsJson( - type_context, msg) + FormatMessageAsDefinitionList(type_context, - msg) + nested_msgs + '\n' + nested_enums - - def FormatEnumValueAsDefinitionListItem(type_context, enum_value): """Format a EnumValueDescriptorProto as RST definition list item. Args: type_context: contextual information for message/enum/field. enum_value: EnumValueDescriptorProto. + Returns: RST formatted definition list item. """ - anchor = FormatAnchor(EnumValueCrossRefLabel(type_context.name)) + anchor = FormatAnchor(EnumValueCrossRefLabel(NormalizeTypeContextName(type_context.name))) default_comment = '*(DEFAULT)* ' if enum_value.number == 0 else '' - leading_comment, annotations = type_context.LeadingCommentPathLookup() - if NOT_IMPLEMENTED_HIDE_ANNOTATION in annotations: + leading_comment = type_context.leading_comment + formatted_leading_comment = FormatCommentWithAnnotations(leading_comment) + if HideNotImplemented(leading_comment): return '' - comment = default_comment + UNICODE_INVISIBLE_SEPARATOR + leading_comment + comment = default_comment + UNICODE_INVISIBLE_SEPARATOR + formatted_leading_comment return anchor + enum_value.name + '\n' + MapLines(functools.partial(Indent, 2), comment) @@ -646,6 +442,7 @@ def FormatEnumAsDefinitionList(type_context, enum): Args: type_context: contextual information for message/enum/field. enum: DescriptorProto. + Returns: RST formatted definition list item. """ @@ -655,78 +452,57 @@ def FormatEnumAsDefinitionList(type_context, enum): for index, enum_value in enumerate(enum.value)) + '\n' -def FormatEnum(type_context, enum): - """Format an EnumDescriptorProto as RST section. +def FormatProtoAsBlockComment(proto): + """Format a proto as a RST block comment. - Args: - type_context: contextual information for message/enum/field. - enum: EnumDescriptorProto. - Returns: - RST formatted section. + Useful in debugging, not usually referenced. """ - anchor = FormatAnchor(EnumCrossRefLabel(type_context.name)) - header = FormatHeader('-', 'Enum %s' % type_context.name) - proto_link = FormatExternalLink('[%s proto]' % type_context.name, - type_context.GithubUrl()) + '\n\n' - leading_comment, annotations = type_context.LeadingCommentPathLookup() - if NOT_IMPLEMENTED_HIDE_ANNOTATION in annotations: - return '' - return anchor + header + proto_link + leading_comment + FormatEnumAsDefinitionList( - type_context, enum) + return '\n\nproto::\n\n' + MapLines(functools.partial(Indent, 2), str(proto)) + '\n' -def FormatProtoAsBlockComment(proto): - """Format as RST a proto as a block comment. +class RstFormatVisitor(visitor.Visitor): + """Visitor to generate a RST representation from a FileDescriptor proto. - Useful in debugging, not usually referenced. + See visitor.Visitor for visitor method docs comments. """ - return '\n\nproto::\n\n' + MapLines(functools.partial(Indent, 2), str(proto)) + '\n' + def VisitEnum(self, enum_proto, type_context): + normal_enum_type = NormalizeTypeContextName(type_context.name) + anchor = FormatAnchor(EnumCrossRefLabel(normal_enum_type)) + header = FormatHeader('-', 'Enum %s' % normal_enum_type) + github_url = GithubUrl(type_context) + proto_link = FormatExternalLink('[%s proto]' % normal_enum_type, github_url) + '\n\n' + leading_comment = type_context.leading_comment + formatted_leading_comment = FormatCommentWithAnnotations(leading_comment, 'enum') + if HideNotImplemented(leading_comment): + return '' + return anchor + header + proto_link + formatted_leading_comment + FormatEnumAsDefinitionList( + type_context, enum_proto) + + def VisitMessage(self, msg_proto, type_context, nested_msgs, nested_enums): + normal_msg_type = NormalizeTypeContextName(type_context.name) + anchor = FormatAnchor(MessageCrossRefLabel(normal_msg_type)) + header = FormatHeader('-', normal_msg_type) + github_url = GithubUrl(type_context) + proto_link = FormatExternalLink('[%s proto]' % normal_msg_type, github_url) + '\n\n' + leading_comment = type_context.leading_comment + formatted_leading_comment = FormatCommentWithAnnotations(leading_comment, 'message') + if HideNotImplemented(leading_comment): + return '' + return anchor + header + proto_link + formatted_leading_comment + FormatMessageAsJson( + type_context, msg_proto) + FormatMessageAsDefinitionList( + type_context, msg_proto) + '\n'.join(nested_msgs) + '\n' + '\n'.join(nested_enums) -def GenerateRst(proto_file): - """Generate a RST representation from a FileDescriptor proto.""" - source_code_info = SourceCodeInfo(proto_file.name, proto_file.source_code_info) - # Find the earliest detached comment, attribute it to file level. - # Also extract file level titles if any. - header, comment = FormatHeaderFromFile('=', source_code_info.file_level_comment, proto_file.name) - package_prefix = NormalizeFQN('.' + proto_file.package + '.')[:-1] - package_type_context = TypeContext(source_code_info, package_prefix) - msgs = '\n'.join( - FormatMessage(package_type_context.ExtendMessage(index, msg.name), msg) - for index, msg in enumerate(proto_file.message_type)) - enums = '\n'.join( - FormatEnum(package_type_context.ExtendEnum(index, enum.name), enum) - for index, enum in enumerate(proto_file.enum_type)) - debug_proto = FormatProtoAsBlockComment(proto_file) - return header + comment + msgs + enums # + debug_proto + def VisitFile(self, file_proto, type_context, msgs, enums): + # Find the earliest detached comment, attribute it to file level. + # Also extract file level titles if any. + header, comment = FormatHeaderFromFile('=', type_context.source_code_info, file_proto.name) + debug_proto = FormatProtoAsBlockComment(file_proto) + return header + comment + '\n'.join(msgs) + '\n'.join(enums) # + debug_proto def Main(): - # http://www.expobrain.net/2015/09/13/create-a-plugin-for-google-protocol-buffer/ - request = plugin_pb2.CodeGeneratorRequest() - request.ParseFromString(sys.stdin.read()) - response = plugin_pb2.CodeGeneratorResponse() - cprofile_enabled = os.getenv('CPROFILE_ENABLED') - - for proto_file in request.proto_file: - f = response.file.add() - f.name = proto_file.name + '.rst' - if cprofile_enabled: - pr = cProfile.Profile() - pr.enable() - # We don't actually generate any RST right now, we just string dump the - # input proto file descriptor into the output file. - f.content = GenerateRst(proto_file) - if cprofile_enabled: - pr.disable() - stats_stream = StringIO.StringIO() - ps = pstats.Stats(pr, - stream=stats_stream).sort_stats(os.getenv('CPROFILE_SORTBY', 'cumulative')) - stats_file = response.file.add() - stats_file.name = proto_file.name + '.rst.profile' - ps.print_stats() - stats_file.content = stats_stream.getvalue() - sys.stdout.write(response.SerializeToString()) + plugin.Plugin('.rst', RstFormatVisitor()) if __name__ == '__main__': diff --git a/tools/shell_utils.sh b/tools/shell_utils.sh index 4d0471e89a..560087615a 100644 --- a/tools/shell_utils.sh +++ b/tools/shell_utils.sh @@ -1,6 +1,6 @@ source_venv() { VENV_DIR=$1 - if [[ "$VIRTUAL_ENV" == "" ]]; then + if [[ "${VIRTUAL_ENV}" == "" ]]; then if [[ ! -d "${VENV_DIR}"/venv ]]; then virtualenv "${VENV_DIR}"/venv --no-site-packages --python=python3 fi @@ -9,3 +9,17 @@ source_venv() { echo "Found existing virtualenv" fi } + +python_venv() { + SCRIPT_DIR=$(realpath "$(dirname "$0")") + + BUILD_DIR=build_tools + PY_NAME="$1" + VENV_DIR="${BUILD_DIR}/${PY_NAME}" + + source_venv "${VENV_DIR}" + pip install -r "${SCRIPT_DIR}"/requirements.txt + + shift + python3 "${SCRIPT_DIR}/${PY_NAME}.py" $* +} diff --git a/tools/spelling_dictionary.txt b/tools/spelling_dictionary.txt index 3f935e236c..0cd9883bd9 100644 --- a/tools/spelling_dictionary.txt +++ b/tools/spelling_dictionary.txt @@ -9,13 +9,16 @@ ALPN ALS AMZ API +ABI ASAN ASCII ASSERTs AWS BSON +CAS CB CBs +CEL CDS CHACHA CHLO @@ -88,6 +91,7 @@ EWOULDBLOCK EXPECTs EXPR FAQ +FCDS FDs FFFF FIN @@ -120,6 +124,7 @@ HV IAM IANA IDL +IETF INADDR INET IO @@ -199,7 +204,10 @@ POSTs PREBIND PRNG PROT +ProcessBufferedChlos +PSS QPS +QoS QUIC RAII RANLUX @@ -269,6 +277,7 @@ TLSv TLV TMPDIR TODO +TPM TPROXY TSAN TSI @@ -291,6 +300,9 @@ VC VH VHDS VLOG +VM +WASM +WAVM WKT WRR WS @@ -411,6 +423,7 @@ decompressor decrement decrypt decrypting +decls dedup dedupe deduplicate @@ -448,6 +461,7 @@ dynamodb eg emplace emplaced +emscripten enablement encodings endian @@ -774,6 +788,7 @@ uint un unacked unary +uncomment unconfigurable unconfigured uncontended @@ -789,6 +804,7 @@ unescaping unindexed uninsantiated uninstantiated +uniq unix unlink unlinked diff --git a/tools/stack_decode.py b/tools/stack_decode.py index 3e3c7bd87e..cc22bfd82a 100755 --- a/tools/stack_decode.py +++ b/tools/stack_decode.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Call addr2line as needed to resolve addresses in a stack trace. The addresses # will be replaced if they can be resolved into file and line numbers. The @@ -11,7 +11,6 @@ # In each case this script will add file and line information to any backtrace log # lines found and echo back all non-Backtrace lines untouched. -from __future__ import print_function import collections import re import subprocess @@ -24,10 +23,14 @@ # any nonmatching lines unmodified. End when EOF received. def decode_stacktrace_log(object_file, input_source): traces = {} - # Match something like [backtrace] - # bazel-out/local-dbg/bin/source/server/_virtual_includes/backtrace_lib/server/backtrace.h:84] + # Match something like: + # [backtrace] [bazel-out/local-dbg/bin/source/server/_virtual_includes/backtrace_lib/server/backtrace.h:84] backtrace_marker = "\[backtrace\] [^\s]+" - stackaddr_re = re.compile("%s #\d+: .* \[(0x[0-9a-fA-F]+)\]$" % backtrace_marker) + # Match something like: + # ${backtrace_marker} #10: SYMBOL [0xADDR] + # or: + # ${backtrace_marker} #10: [0xADDR] + stackaddr_re = re.compile("%s #\d+:(?: .*)? \[(0x[0-9a-fA-F]+)\]$" % backtrace_marker) try: while True: @@ -53,7 +56,7 @@ def decode_stacktrace_log(object_file, input_source): # # Returns list of result lines def run_addr2line(obj_file, addr_to_resolve): - return subprocess.check_output(["addr2line", "-Cpie", obj_file, addr_to_resolve]) + return subprocess.check_output(["addr2line", "-Cpie", obj_file, addr_to_resolve]).decode('utf-8') # Because of how bazel compiles, addr2line reports file names that begin with @@ -69,7 +72,10 @@ def trim_proc_cwd(file_and_line_number): decode_stacktrace_log(sys.argv[2], sys.stdin) sys.exit(0) elif len(sys.argv) > 1: - rununder = subprocess.Popen(sys.argv[1:], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + rununder = subprocess.Popen(sys.argv[1:], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True) decode_stacktrace_log(sys.argv[1], rununder.stdout) rununder.wait() sys.exit(rununder.returncode) # Pass back test pass/fail result diff --git a/tools/testdata/check_format/api/go_package.proto b/tools/testdata/check_format/api/go_package.proto new file mode 100644 index 0000000000..b32347b6e4 --- /dev/null +++ b/tools/testdata/check_format/api/go_package.proto @@ -0,0 +1,5 @@ +option go_package = "foo"; +option java_package = "io.envoyproxy.envoy.foo"; +option java_outer_classname = "JavaOptionsProto"; +option java_multiple_files = true; +package envoy.foo; diff --git a/tools/testdata/check_format/regex.cc b/tools/testdata/check_format/regex.cc new file mode 100644 index 0000000000..53241fae9c --- /dev/null +++ b/tools/testdata/check_format/regex.cc @@ -0,0 +1,9 @@ +#include + +namespace Envoy { + +struct BadRegex { + std::regex bad_; +} + +} // namespace Envoy