From 40790ebaacd2634e4adaeac7bcd1bb264b449ff1 Mon Sep 17 00:00:00 2001 From: Pradeep Rao <84025829+pradeepcrao@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:40:01 -0500 Subject: [PATCH 001/151] stats: Default enable include histograms api for SinkPredicates (#30459) Signed-off-by: pcrao --- changelogs/current.yaml | 5 +++++ source/common/runtime/runtime_features.cc | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 292d36bcdca6..5e52e2d1bd3d 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -6,6 +6,11 @@ behavior_changes: change: | Remove the hop by hop TE header from downstream request headers if it's not set to ``trailers``, else keep it. This change can be temporarily reverted by setting ``envoy.reloadable_features.sanitize_te`` to false. +- area: stats + change: | + The runtime flag ``envoy.reloadable_features.enable_include_histograms`` is now enabled by default. + This causes the ``includeHistogram()`` method on ``Stats::SinkPredicates`` to filter histograms to + be flushed to stat sinks. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 19173d3b2cd5..b445a9cf5435 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -42,6 +42,7 @@ RUNTIME_GUARD(envoy_reloadable_features_dns_cache_set_first_resolve_complete); RUNTIME_GUARD(envoy_reloadable_features_edf_lb_locality_scheduler_init_fix); RUNTIME_GUARD(envoy_reloadable_features_enable_compression_bomb_protection); RUNTIME_GUARD(envoy_reloadable_features_enable_connect_udp_support); +RUNTIME_GUARD(envoy_reloadable_features_enable_include_histograms); RUNTIME_GUARD(envoy_reloadable_features_enable_intermediate_ca); RUNTIME_GUARD(envoy_reloadable_features_enable_zone_routing_different_zone_counts); RUNTIME_GUARD(envoy_reloadable_features_exclude_host_in_eds_status_draining); @@ -117,8 +118,6 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_runtime_initialized); // TODO(mattklein123): Also unit test this if this sticks and this becomes the default for Apple & // Android. FALSE_RUNTIME_GUARD(envoy_reloadable_features_always_use_v6); -// TODO(pradeepcrao) reset this to true after 2 releases (1.27) -FALSE_RUNTIME_GUARD(envoy_reloadable_features_enable_include_histograms); // TODO(wbpcode) complete remove this feature is no one use it. FALSE_RUNTIME_GUARD(envoy_reloadable_features_refresh_rtt_after_request); // TODO(danzh) false deprecate it once QUICHE has its own enable/disable flag. From 199d035c9333fc963e8c383baa23c5e5487433db Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Tue, 13 Feb 2024 17:59:18 -0500 Subject: [PATCH 002/151] mobile: Remove the no-remote-exec tag for most iOS tests (#32367) This allows the test to run on RBE instead of the local CI machines, speeding up the CI run time (hopefully). Tests that start an EnvoyTestServer must still have the no-remote-exec tag because they try to bind a local socket, which is not permitted on the remote environments (we get errors to this effect, e.g. https://github.com/envoyproxy/envoy/actions/runs/7893306052/job/21541684998#step:8:2268). Signed-off-by: Ali Beyad --- mobile/test/objective-c/BUILD | 2 -- mobile/test/swift/BUILD | 1 - mobile/test/swift/integration/BUILD | 51 +++-------------------------- mobile/test/swift/stats/BUILD | 1 - 4 files changed, 4 insertions(+), 51 deletions(-) diff --git a/mobile/test/objective-c/BUILD b/mobile/test/objective-c/BUILD index 1d977756c4ca..e36c5ed326f5 100644 --- a/mobile/test/objective-c/BUILD +++ b/mobile/test/objective-c/BUILD @@ -11,7 +11,6 @@ envoy_mobile_objc_test( "EnvoyBridgeUtilityTest.m", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI - tags = ["no-remote-exec"], # TODO(jpsim): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_objc_bridge_lib", @@ -24,7 +23,6 @@ envoy_mobile_objc_test( "EnvoyKeyValueStoreBridgeImplTest.m", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI - tags = ["no-remote-exec"], # TODO(jpsim): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_key_value_store_bridge_impl_lib", diff --git a/mobile/test/swift/BUILD b/mobile/test/swift/BUILD index 83b7b81a1a75..090b92a24be2 100644 --- a/mobile/test/swift/BUILD +++ b/mobile/test/swift/BUILD @@ -21,7 +21,6 @@ envoy_mobile_swift_test( "RetryPolicyTests.swift", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI - tags = ["no-remote-exec"], # TODO(jpsim): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_engine_objc_lib", diff --git a/mobile/test/swift/integration/BUILD b/mobile/test/swift/integration/BUILD index 4830157ab739..16d243f8c4d2 100644 --- a/mobile/test/swift/integration/BUILD +++ b/mobile/test/swift/integration/BUILD @@ -5,13 +5,13 @@ licenses(["notice"]) # Apache 2 envoy_mobile_package() -# TODO(jpsim): Fix remote execution for all the tests in this file. - envoy_mobile_swift_test( name = "end_to_end_networking_test", srcs = [ "EndToEndNetworkingTest.swift", ], + # The test starts an Envoy server, which binds to a local socket. Binding to a local socket is + # not permitted on remote execution hosts, so we have to add the `no-remote-exec` tag. tags = [ "no-remote-exec", ], @@ -28,9 +28,6 @@ envoy_mobile_swift_test( srcs = [ "CancelStreamTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -43,9 +40,6 @@ envoy_mobile_swift_test( srcs = [ "EngineApiTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -58,9 +52,6 @@ envoy_mobile_swift_test( srcs = [ "FilterResetIdleTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -73,9 +64,6 @@ envoy_mobile_swift_test( srcs = [ "GRPCReceiveErrorTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -88,6 +76,8 @@ envoy_mobile_swift_test( srcs = [ "IdleTimeoutTest.swift", ], + # The test starts an Envoy server, which binds to a local socket. Binding to a local socket is + # not permitted on remote execution hosts, so we have to add the `no-remote-exec` tag. tags = [ "no-remote-exec", ], @@ -104,9 +94,6 @@ envoy_mobile_swift_test( srcs = [ "KeyValueStoreTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -119,9 +106,6 @@ envoy_mobile_swift_test( srcs = [ "ReceiveDataTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -134,9 +118,6 @@ envoy_mobile_swift_test( srcs = [ "ReceiveErrorTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -149,9 +130,6 @@ envoy_mobile_swift_test( srcs = [ "ResetConnectivityStateTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -164,9 +142,6 @@ envoy_mobile_swift_test( srcs = [ "SendDataTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -179,9 +154,6 @@ envoy_mobile_swift_test( srcs = [ "SendHeadersTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -194,9 +166,6 @@ envoy_mobile_swift_test( srcs = [ "SendTrailersTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -209,9 +178,6 @@ envoy_mobile_swift_test( srcs = [ "SetEventTrackerTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -224,9 +190,6 @@ envoy_mobile_swift_test( srcs = [ "SetEventTrackerTestNoTracker.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -239,9 +202,6 @@ envoy_mobile_swift_test( srcs = [ "SetLoggerTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -254,9 +214,6 @@ envoy_mobile_swift_test( srcs = [ "CancelGRPCStreamTest.swift", ], - tags = [ - "no-remote-exec", - ], visibility = ["//visibility:public"], deps = [ ":test_extensions", diff --git a/mobile/test/swift/stats/BUILD b/mobile/test/swift/stats/BUILD index dbc34bd9c78a..2d77e215c8a3 100644 --- a/mobile/test/swift/stats/BUILD +++ b/mobile/test/swift/stats/BUILD @@ -13,7 +13,6 @@ envoy_mobile_swift_test( "TagsBuilderTests.swift", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI - tags = ["no-remote-exec"], # TODO(jpsim): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_engine_objc_lib", From bd4bbfafde9a5c7a6ccd7a3a984f555d07086755 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Tue, 13 Feb 2024 21:14:50 -0500 Subject: [PATCH 003/151] mobile: Add docs on no-remote-exec usage to EnvoyTestServer (#32385) Signed-off-by: Ali Beyad --- mobile/test/objective-c/EnvoyTestServer.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobile/test/objective-c/EnvoyTestServer.h b/mobile/test/objective-c/EnvoyTestServer.h index df61cc4bf06b..16f0e997c62d 100644 --- a/mobile/test/objective-c/EnvoyTestServer.h +++ b/mobile/test/objective-c/EnvoyTestServer.h @@ -3,6 +3,10 @@ #import // Interface for starting and managing a test server. Calls into to test_server.cc +// +// NB: Any test that utilizes this class must have a `no-remote-exec` tag in its BUILD target. +// EnvoyTestServer binds to a listening socket on the machine it runs on, and on CI, this +// operation is not permitted in remote execution environments. @interface EnvoyTestServer : NSObject // Get the port of the upstream server. From cbfd9aa0b33691811dfd9b9b296bd33c469ce819 Mon Sep 17 00:00:00 2001 From: birenroy Date: Tue, 13 Feb 2024 21:42:52 -0500 Subject: [PATCH 004/151] http2: restores some log statements, simplifies onBeginData() (#32381) These log statements were accidentally omitted in the refactoring performed in #31878. This change also removes the redundant frame type argument from ConnectionImpl::onBeginData(). $ bazel test //test/common/http/http2/... //test/integration/... INFO: Invocation ID: 2516e9a0-c488-4630-93d9-a01e0e81d5c3 INFO: Analyzed 397 targets (0 packages loaded, 0 targets configured). INFO: Found 295 targets and 102 test targets... INFO: Elapsed time: 752.367s, Critical Path: 750.91s INFO: 399 processes: 1 remote cache hit, 399 remote. INFO: Build completed successfully, 399 total actions Executed 102 out of 102 tests: 102 tests pass. Commit Message: http2: restores some log statements, simplifies onBeginData() Additional Description: Risk Level: low Testing: ran unit and integration tests locally Docs Changes: Release Notes: Platform Specific Features: Signed-off-by: Biren Roy --- source/common/http/http2/codec_impl.cc | 15 ++++++++++----- source/common/http/http2/codec_impl.h | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index bfeb546128c9..c580718202f7 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -1107,6 +1107,9 @@ enum GoAwayErrorCode ngHttp2ErrorCodeToErrorCode(uint32_t code) noexcept { } Status ConnectionImpl::onPing(uint64_t opaque_data, bool is_ack) { + ENVOY_CONN_LOG(trace, "recv frame type=PING", connection_); + ASSERT(connection_.state() == Network::Connection::State::Open); + if (is_ack) { ENVOY_CONN_LOG(trace, "recv PING ACK {}", connection_, opaque_data); @@ -1115,9 +1118,10 @@ Status ConnectionImpl::onPing(uint64_t opaque_data, bool is_ack) { return okStatus(); } -Status ConnectionImpl::onBeginData(int32_t stream_id, size_t length, uint8_t type, uint8_t flags, +Status ConnectionImpl::onBeginData(int32_t stream_id, size_t length, uint8_t flags, size_t padding) { - RETURN_IF_ERROR(trackInboundFrames(stream_id, length, type, flags, padding)); + ENVOY_CONN_LOG(trace, "recv frame type=DATA stream_id={}", connection_, stream_id); + RETURN_IF_ERROR(trackInboundFrames(stream_id, length, NGHTTP2_DATA, flags, padding)); StreamImpl* stream = getStreamUnchecked(stream_id); if (!stream) { @@ -1133,6 +1137,7 @@ Status ConnectionImpl::onBeginData(int32_t stream_id, size_t length, uint8_t typ } Status ConnectionImpl::onGoAway(uint32_t error_code) { + ENVOY_CONN_LOG(trace, "recv frame type=GOAWAY", connection_); // Only raise GOAWAY once, since we don't currently expose stream information. Shutdown // notifications are the same as a normal GOAWAY. // TODO: handle multiple GOAWAY frames. @@ -1191,11 +1196,12 @@ Status ConnectionImpl::onHeaders(int32_t stream_id, size_t length, uint8_t flags } Status ConnectionImpl::onRstStream(int32_t stream_id, uint32_t error_code) { - ENVOY_CONN_LOG(trace, "remote reset: {} {}", connection_, stream_id, error_code); + ENVOY_CONN_LOG(trace, "recv frame type=RST_STREAM stream_id={}", connection_, stream_id); StreamImpl* stream = getStreamUnchecked(stream_id); if (!stream) { return okStatus(); } + ENVOY_CONN_LOG(trace, "remote reset: {} {}", connection_, stream_id, error_code); // Track bytes received. stream->bytes_meter_->addWireBytesReceived(/*frame_length=*/4 + H2_FRAME_HEADER_SIZE); stream->remote_rst_ = true; @@ -1221,8 +1227,7 @@ Status ConnectionImpl::onFrameReceived(const nghttp2_frame* frame) { return onPing(data, frame->ping.hd.flags & NGHTTP2_FLAG_ACK); } if (frame->hd.type == NGHTTP2_DATA) { - return onBeginData(frame->hd.stream_id, frame->hd.length, frame->hd.type, frame->hd.flags, - frame->data.padlen); + return onBeginData(frame->hd.stream_id, frame->hd.length, frame->hd.flags, frame->data.padlen); } if (frame->hd.type == NGHTTP2_GOAWAY) { ASSERT(frame->hd.stream_id == 0); diff --git a/source/common/http/http2/codec_impl.h b/source/common/http/http2/codec_impl.h index 5e7572970378..50e9411eaad7 100644 --- a/source/common/http/http2/codec_impl.h +++ b/source/common/http/http2/codec_impl.h @@ -708,7 +708,7 @@ class ConnectionImpl : public virtual Connection, int onData(int32_t stream_id, const uint8_t* data, size_t len); Status onBeforeFrameReceived(int32_t stream_id, size_t length, uint8_t type, uint8_t flags); Status onPing(uint64_t opaque_data, bool is_ack); - Status onBeginData(int32_t stream_id, size_t length, uint8_t type, uint8_t flags, size_t padding); + Status onBeginData(int32_t stream_id, size_t length, uint8_t flags, size_t padding); Status onGoAway(uint32_t error_code); Status onHeaders(int32_t stream_id, size_t length, uint8_t flags); Status onRstStream(int32_t stream_id, uint32_t error_code); From c21cc42fa40e565fdaf6058b355cf224ffa4b458 Mon Sep 17 00:00:00 2001 From: botengyao Date: Wed, 14 Feb 2024 00:21:47 -0500 Subject: [PATCH 005/151] hcm: fuzz to handle upgrade and end stream correctly. (#32386) fuzz infra to handle connect-udp Signed-off-by: Boteng Yao --- ...nn_manager_impl_fuzz_test-5160321246167040 | 37 +++++++++++++++++++ .../http/conn_manager_impl_fuzz_test.cc | 7 +++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 test/common/http/conn_manager_impl_corpus/clusterfuzz-testcase-minimized-conn_manager_impl_fuzz_test-5160321246167040 diff --git a/test/common/http/conn_manager_impl_corpus/clusterfuzz-testcase-minimized-conn_manager_impl_fuzz_test-5160321246167040 b/test/common/http/conn_manager_impl_corpus/clusterfuzz-testcase-minimized-conn_manager_impl_fuzz_test-5160321246167040 new file mode 100644 index 000000000000..6d064ff30a6b --- /dev/null +++ b/test/common/http/conn_manager_impl_corpus/clusterfuzz-testcase-minimized-conn_manager_impl_fuzz_test-5160321246167040 @@ -0,0 +1,37 @@ +actions { + new_stream { + request_headers { + headers { + key: ":path" + value: "/" + } + } + } +} +actions { + new_stream { + } +} +actions { + new_stream { + request_headers { + headers { + key: ":path" + value: "/" + } + headers { + key: "upgrade" + value: "connect-udp" + } + } + } +} +actions { + stream_action { + stream_id: 128 + response { + continue_headers { + } + } + } +} diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index d8ce2ffbfa45..81693fbb0cc3 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -94,7 +94,7 @@ class FuzzConfig : public ConnectionManagerConfig { callbacks.streamInfo().setResponseCodeDetails(""); })); EXPECT_CALL(*encoder_filter_, setEncoderFilterCallbacks(_)); - EXPECT_CALL(filter_factory_, createUpgradeFilterChain("WebSocket", _, _)) + EXPECT_CALL(filter_factory_, createUpgradeFilterChain(_, _, _)) .WillRepeatedly(Invoke([&](absl::string_view, const Http::FilterChainFactory::UpgradeMap*, FilterChainManager& manager) -> bool { return filter_factory_.createFilterChain(manager); @@ -341,6 +341,11 @@ class FuzzStream { response_state_ = end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; })); + ON_CALL(encoder_, encodeData(_, true)) + .WillByDefault(Invoke([this](const Buffer::Instance&, bool end_stream) -> void { + response_state_ = + end_stream ? StreamState::Closed : StreamState::PendingDataOrTrailers; + })); decoder_->decodeHeaders(std::move(headers), end_stream); return Http::okStatus(); })); From 1fef0467ed57b650f93bafe0f267ce182b99d9fd Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Wed, 14 Feb 2024 00:23:14 -0500 Subject: [PATCH 006/151] Comment: Update comment since nameCStr was removed. (#32383) Commit Message: Comment: Update comment since nameCStr was removed. Additional Description: Risk Level: low Testing: na Docs Changes: na Release Notes: na Platform Specific Features: na Signed-off-by: Kevin Baichoo --- envoy/stats/stats.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/envoy/stats/stats.h b/envoy/stats/stats.h index ea4596f64aa5..10af2c396620 100644 --- a/envoy/stats/stats.h +++ b/envoy/stats/stats.h @@ -38,10 +38,7 @@ class Metric : public RefcountInterface { * Returns the full name of the Metric. This is intended for most uses, such * as streaming out the name to a stats sink or admin request, or comparing * against it in a test. Independent of the evolution of the data - * representation for the name, this method will be available. For storing the - * name as a map key, however, nameCStr() is a better choice, albeit one that - * might change in the future to return a symbolized representation of the - * elaborated string. + * representation for the name, this method will be available. */ virtual std::string name() const PURE; From 45b3f9af9d3889f23b4aded7416d92687cc72de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20R=2E=20Sede=C3=B1o?= Date: Wed, 14 Feb 2024 00:26:01 -0500 Subject: [PATCH 007/151] Update QUICHE from 60a22a631 to 2c1f10f46 (#32379) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit https://github.com/google/quiche/compare/60a22a631..2c1f10f46 ``` $ git log 60a22a631..2c1f10f46 --date=short --no-merges --format="%ad %al %s" 2024-02-13 martinduke Process incoming Unsubscribes. 2024-02-13 quiche-dev Fix issues required to build additional Quiche tests for Chrome 2024-02-12 renjietang Add 2 counters for number of successful multi-port probes. 2024-02-12 dschinazi Remove unused connect-udp-version 2024-02-09 quiche-dev Fix flaky //third_party/quic/core/batch_writer:quic_batch_writer_test 2024-02-09 quiche-dev Fix spelling 2024-02-09 rch Add a comment to kBBRv2 which explains that it's essentially BBRv3 and a TODO to rename it when the implementation is complete. 2024-02-08 danzh Change QuicConnection::cipher_id() to get underlying cipher id differently. 2024-02-08 rch Always disable the QPACK dyanmic table for Hyperloop connections. 2024-02-08 martinduke Handle MoQT Objects that arrive before SUBSCRIBE_OK. 2024-02-07 bnc No public description 2024-02-06 martinduke Deprecate gfe2_restart_flag_quic_receive_ecn3. 2024-02-05 martinduke No public description 2024-02-05 martinduke Update four-pass algorithm to reduce copying and conform to draft-ietf-quic-load-balancers-19. ``` Risk Level: Low Testing: Existing tests pass Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Alejandro R Sedeño --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index d6b03fdff44b..1aaa11ebc889 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1175,12 +1175,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "60a22a631bdf944e26407d32a767b4aba953bc39", - sha256 = "70213d0e4016ce79db9f3dfc5e94b4e707c54b69e00fae7ea2593a08e9dfd11e", + version = "2c1f10f46ce16d42fcf692d324799d859e8478cc", + sha256 = "8754391ff9d75aa1ff3dddbb57c73830c63932f01cfb30d4ca158720b2eadeba", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2024-02-05", + release_date = "2024-02-13", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", From 7c341210d2dc5569cedbff06b20901183f0bb89e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:52:47 +0000 Subject: [PATCH 008/151] build(deps): bump slack-sdk from 3.26.2 to 3.27.0 in /tools/base (#32393) Bumps [slack-sdk](https://github.com/slackapi/python-slack-sdk) from 3.26.2 to 3.27.0. - [Release notes](https://github.com/slackapi/python-slack-sdk/releases) - [Changelog](https://github.com/slackapi/python-slack-sdk/blob/main/docs-v2/changelog.html) - [Commits](https://github.com/slackapi/python-slack-sdk/compare/v3.26.2...v3.27.0) --- updated-dependencies: - dependency-name: slack-sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 268f131abcaf..6520e28cf2a0 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -700,7 +700,9 @@ google-apitools==0.5.32 \ google-auth[aiohttp]==2.27.0 \ --hash=sha256:8e4bad367015430ff253fe49d500fdc3396c1a434db5740828c728e45bcce245 \ --hash=sha256:e863a56ccc2d8efa83df7a80272601e43487fa9a728a376205c86c26aaefa821 - # via gsutil + # via + # google-auth + # gsutil google-reauth==0.1.1 \ --hash=sha256:cb39074488d74c8853074dde47368bbf8f739d4a4338b89aab696c895b6d8368 \ --hash=sha256:f9f6852a55c2c5453d581cd01f3d1278e86147c03d008409800390a834235892 @@ -1061,6 +1063,7 @@ pyjwt[crypto]==2.8.0 \ # via # gidgethub # pygithub + # pyjwt pylsqpack==0.3.18 \ --hash=sha256:005ddce84bdcbf5c3cf99f764504208e1aa0a91a8331bf47108f2708f2a315e6 \ --hash=sha256:06e1bbe47514b83cd03158e5558ef8cc44f578169c1820098be9f3cc4137f16a \ @@ -1217,9 +1220,9 @@ six==1.16.0 \ # pyu2f # sphinxcontrib-httpdomain # thrift -slack-sdk==3.26.2 \ - --hash=sha256:a10e8ee69ca17d274989d0c2bbecb875f19898da3052d8d57de0898a00b1ab52 \ - --hash=sha256:bcdac5e688fa50e9357ecd00b803b6a8bad766aa614d35d8dc0636f40adc48bf +slack-sdk==3.27.0 \ + --hash=sha256:811472ce598db855ab3c02f098fa430323ccb253cfe17ba20c7b05ab206d984d \ + --hash=sha256:a901c68cb5547d5459cdefd81343d116db56d65f6b33f4081ddf1cdd243bf07e # via -r requirements.in smmap==5.0.1 \ --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ From 993db04ef92d30f49c0e79a98d434346fd7691ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:53:02 +0000 Subject: [PATCH 009/151] build(deps): bump orjson from 3.9.13 to 3.9.14 in /tools/base (#32394) Bumps [orjson](https://github.com/ijl/orjson) from 3.9.13 to 3.9.14. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.13...3.9.14) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 102 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 6520e28cf2a0..aa097b0eadd1 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -921,57 +921,57 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.13 \ - --hash=sha256:031df1026c7ea8303332d78711f180231e3ae8b564271fb748a03926587c5546 \ - --hash=sha256:0d3ba9d88e20765335260d7b25547d7c571eee2b698200f97afa7d8c7cd668fc \ - --hash=sha256:0d691c44604941945b00e0a13b19a7d9c1a19511abadf0080f373e98fdeb6b31 \ - --hash=sha256:0fd9a2101d04e85086ea6198786a3f016e45475f800712e6833e14bf9ce2832f \ - --hash=sha256:16946d095212a3dec552572c5d9bca7afa40f3116ad49695a397be07d529f1fa \ - --hash=sha256:1ab9dbdec3f13f3ea6f937564ce21651844cfbf2725099f2f490426acf683c23 \ - --hash=sha256:23f21faf072ed3b60b5954686f98157e073f6a8068eaa58dbde83e87212eda84 \ - --hash=sha256:266e55c83f81248f63cc93d11c5e3a53df49a5d2598fa9e9db5f99837a802d5d \ - --hash=sha256:2cc03a35bfc71c8ebf96ce49b82c2a7be6af4b3cd3ac34166fdb42ac510bbfff \ - --hash=sha256:2f37f0cdd026ef777a4336e599d8194c8357fc14760c2a5ddcfdf1965d45504b \ - --hash=sha256:31372ba3a9fe8ad118e7d22fba46bbc18e89039e3bfa89db7bc8c18ee722dca8 \ - --hash=sha256:31fb66b41fb2c4c817d9610f0bc7d31345728d7b5295ac78b63603407432a2b2 \ - --hash=sha256:3869d65561f10071d3e7f35ae58fd377056f67d7aaed5222f318390c3ad30339 \ - --hash=sha256:3deadd8dc0e9ff844b5b656fa30a48dbee1c3b332d8278302dd9637f6b09f627 \ - --hash=sha256:43fd6036b16bb6742d03dae62f7bdf8214d06dea47e4353cde7e2bd1358d186f \ - --hash=sha256:446d9ad04204e79229ae19502daeea56479e55cbc32634655d886f5a39e91b44 \ - --hash=sha256:4584e8eb727bc431baaf1bf97e35a1d8a0109c924ec847395673dfd5f4ef6d6f \ - --hash=sha256:49b7e3fe861cb246361825d1a238f2584ed8ea21e714bf6bb17cebb86772e61c \ - --hash=sha256:5b98cd948372f0eb219bc309dee4633db1278687161e3280d9e693b6076951d2 \ - --hash=sha256:5ef58869f3399acbbe013518d8b374ee9558659eef14bca0984f67cb1fbd3c37 \ - --hash=sha256:60da7316131185d0110a1848e9ad15311e6c8938ee0b5be8cbd7261e1d80ee8f \ - --hash=sha256:62e9a99879c4d5a04926ac2518a992134bfa00d546ea5a4cae4b9be454d35a22 \ - --hash=sha256:63ef57a53bfc2091a7cd50a640d9ae866bd7d92a5225a1bab6baa60ef62583f2 \ - --hash=sha256:6e47153db080f5e87e8ba638f1a8b18995eede6b0abb93964d58cf11bcea362f \ - --hash=sha256:730385fdb99a21fce9bb84bb7fcbda72c88626facd74956bda712834b480729d \ - --hash=sha256:7ccd5bd222e5041069ad9d9868ab59e6dbc53ecde8d8c82b919954fbba43b46b \ - --hash=sha256:7e8e4a571d958910272af8d53a9cbe6599f9f5fd496a1bc51211183bb2072cbd \ - --hash=sha256:811ac076855e33e931549340288e0761873baf29276ad00f221709933c644330 \ - --hash=sha256:828c502bb261588f7de897e06cb23c4b122997cb039d2014cb78e7dabe92ef0c \ - --hash=sha256:838b898e8c1f26eb6b8d81b180981273f6f5110c76c22c384979aca854194f1b \ - --hash=sha256:860d0f5b42d0c0afd73fa4177709f6e1b966ba691fcd72175affa902052a81d6 \ - --hash=sha256:8a730bf07feacb0863974e67b206b7c503a62199de1cece2eb0d4c233ec29c11 \ - --hash=sha256:9156b96afa38db71344522f5517077eaedf62fcd2c9148392ff93d801128809c \ - --hash=sha256:9171e8e1a1f221953e38e84ae0abffe8759002fd8968106ee379febbb5358b33 \ - --hash=sha256:978117122ca4cc59b28af5322253017f6c5fc03dbdda78c7f4b94ae984c8dd43 \ - --hash=sha256:9b1b5adc5adf596c59dca57156b71ad301d73956f5bab4039b0e34dbf50b9fa0 \ - --hash=sha256:9bcf56efdb83244cde070e82a69c0f03c47c235f0a5cb6c81d9da23af7fbaae4 \ - --hash=sha256:a8c83718346de08d68b3cb1105c5d91e5fc39885d8610fdda16613d4e3941459 \ - --hash=sha256:ae77275a28667d9c82d4522b681504642055efa0368d73108511647c6499b31c \ - --hash=sha256:b57c0954a9fdd2b05b9cec0f5a12a0bdce5bf021a5b3b09323041613972481ab \ - --hash=sha256:b812417199eeb169c25f67815cfb66fd8de7ff098bf57d065e8c1943a7ba5c8f \ - --hash=sha256:cfad553a36548262e7da0f3a7464270e13900b898800fb571a5d4b298c3f8356 \ - --hash=sha256:d3222db9df629ef3c3673124f2e05fb72bc4a320c117e953fec0d69dde82e36d \ - --hash=sha256:d714595d81efab11b42bccd119977d94b25d12d3a806851ff6bfd286a4bce960 \ - --hash=sha256:d92a3e835a5100f1d5b566fff79217eab92223ca31900dba733902a182a35ab0 \ - --hash=sha256:ddc089315d030c54f0f03fb38286e2667c05009a78d659f108a8efcfbdf2e585 \ - --hash=sha256:e3b0c4da61f39899561e08e571f54472a09fa71717d9797928af558175ae5243 \ - --hash=sha256:eaaf80957c38e9d3f796f355a80fad945e72cd745e6b64c210e635b7043b673e \ - --hash=sha256:fa6b67f8bef277c2a4aadd548d58796854e7d760964126c3209b19bccc6a74f1 \ - --hash=sha256:fc6bc65b0cf524ee042e0bc2912b9206ef242edfba7426cf95763e4af01f527a +orjson==3.9.14 \ + --hash=sha256:0572f174f50b673b7df78680fb52cd0087a8585a6d06d295a5f790568e1064c6 \ + --hash=sha256:06fb40f8e49088ecaa02f1162581d39e2cf3fd9dbbfe411eb2284147c99bad79 \ + --hash=sha256:08e722a8d06b13b67a51f247a24938d1a94b4b3862e40e0eef3b2e98c99cd04c \ + --hash=sha256:135d518f73787ce323b1a5e21fb854fe22258d7a8ae562b81a49d6c7f826f2a3 \ + --hash=sha256:19cdea0664aec0b7f385be84986d4defd3334e9c3c799407686ee1c26f7b8251 \ + --hash=sha256:1f7b6f3ef10ae8e3558abb729873d033dbb5843507c66b1c0767e32502ba96bb \ + --hash=sha256:20837e10835c98973673406d6798e10f821e7744520633811a5a3d809762d8cc \ + --hash=sha256:236230433a9a4968ab895140514c308fdf9f607cb8bee178a04372b771123860 \ + --hash=sha256:23d1528db3c7554f9d6eeb09df23cb80dd5177ec56eeb55cc5318826928de506 \ + --hash=sha256:26280a7fcb62d8257f634c16acebc3bec626454f9ab13558bbf7883b9140760e \ + --hash=sha256:29512eb925b620e5da2fd7585814485c67cc6ba4fe739a0a700c50467a8a8065 \ + --hash=sha256:2eefc41ba42e75ed88bc396d8fe997beb20477f3e7efa000cd7a47eda452fbb2 \ + --hash=sha256:3014ccbda9be0b1b5f8ea895121df7e6524496b3908f4397ff02e923bcd8f6dd \ + --hash=sha256:449bf090b2aa4e019371d7511a6ea8a5a248139205c27d1834bb4b1e3c44d936 \ + --hash=sha256:4dc1c132259b38d12c6587d190cd09cd76e3b5273ce71fe1372437b4cbc65f6f \ + --hash=sha256:58b36f54da759602d8e2f7dad958752d453dfe2c7122767bc7f765e17dc59959 \ + --hash=sha256:5bf597530544db27a8d76aced49cfc817ee9503e0a4ebf0109cd70331e7bbe0c \ + --hash=sha256:6f39a10408478f4c05736a74da63727a1ae0e83e3533d07b19443400fe8591ca \ + --hash=sha256:6f52ac2eb49e99e7373f62e2a68428c6946cda52ce89aa8fe9f890c7278e2d3a \ + --hash=sha256:7183cc68ee2113b19b0b8714221e5e3b07b3ba10ca2bb108d78fd49cefaae101 \ + --hash=sha256:751250a31fef2bac05a2da2449aae7142075ea26139271f169af60456d8ad27a \ + --hash=sha256:75fc593cf836f631153d0e21beaeb8d26e144445c73645889335c2247fcd71a0 \ + --hash=sha256:7913079b029e1b3501854c9a78ad938ed40d61fe09bebab3c93e60ff1301b189 \ + --hash=sha256:793f6c9448ab6eb7d4974b4dde3f230345c08ca6c7995330fbceeb43a5c8aa5e \ + --hash=sha256:814f288c011efdf8f115c5ebcc1ab94b11da64b207722917e0ceb42f52ef30a3 \ + --hash=sha256:90903d2908158a2c9077a06f11e27545de610af690fb178fd3ba6b32492d4d1c \ + --hash=sha256:917311d6a64d1c327c0dfda1e41f3966a7fb72b11ca7aa2e7a68fcccc7db35d9 \ + --hash=sha256:95c03137b0cf66517c8baa65770507a756d3a89489d8ecf864ea92348e1beabe \ + --hash=sha256:978f416bbff9da8d2091e3cf011c92da68b13f2c453dcc2e8109099b2a19d234 \ + --hash=sha256:9a1af21160a38ee8be3f4fcf24ee4b99e6184cadc7f915d599f073f478a94d2c \ + --hash=sha256:a2591faa0c031cf3f57e5bce1461cfbd6160f3f66b5a72609a130924917cb07d \ + --hash=sha256:a603161318ff699784943e71f53899983b7dee571b4dd07c336437c9c5a272b0 \ + --hash=sha256:a6bc7928d161840096adc956703494b5c0193ede887346f028216cac0af87500 \ + --hash=sha256:a88cafb100af68af3b9b29b5ccd09fdf7a48c63327916c8c923a94c336d38dd3 \ + --hash=sha256:ab90c02cb264250b8a58cedcc72ed78a4a257d956c8d3c8bebe9751b818dfad8 \ + --hash=sha256:abcda41ecdc950399c05eff761c3de91485d9a70d8227cb599ad3a66afe93bcc \ + --hash=sha256:ac0c7eae7ad3a223bde690565442f8a3d620056bd01196f191af8be58a5248e1 \ + --hash=sha256:ac650d49366fa41fe702e054cb560171a8634e2865537e91f09a8d05ea5b1d37 \ + --hash=sha256:b7c11667421df2d8b18b021223505dcc3ee51be518d54e4dc49161ac88ac2b87 \ + --hash=sha256:ba3518b999f88882ade6686f1b71e207b52e23546e180499be5bbb63a2f9c6e6 \ + --hash=sha256:c19009ff37f033c70acd04b636380379499dac2cba27ae7dfc24f304deabbc81 \ + --hash=sha256:ce6f095eef0026eae76fc212f20f786011ecf482fc7df2f4c272a8ae6dd7b1ef \ + --hash=sha256:d2cf1d0557c61c75e18cf7d69fb689b77896e95553e212c0cc64cf2087944b84 \ + --hash=sha256:d450a8e0656efb5d0fcb062157b918ab02dcca73278975b4ee9ea49e2fcf5bd5 \ + --hash=sha256:df3266d54246cb56b8bb17fa908660d2a0f2e3f63fbc32451ffc1b1505051d07 \ + --hash=sha256:df76ecd17b1b3627bddfd689faaf206380a1a38cc9f6c4075bd884eaedcf46c2 \ + --hash=sha256:e2450d87dd7b4f277f4c5598faa8b49a0c197b91186c47a2c0b88e15531e4e3e \ + --hash=sha256:ea890e6dc1711aeec0a33b8520e395c2f3d59ead5b4351a788e06bf95fc7ba81 \ + --hash=sha256:f75823cc1674a840a151e999a7dfa0d86c911150dd6f951d0736ee9d383bf415 \ + --hash=sha256:fca33fdd0b38839b01912c57546d4f412ba7bfa0faf9bf7453432219aec2df07 # via # -r requirements.in # envoy-base-utils From 581185debbd88ecec9bd426714ee6eaeff43e3b2 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Wed, 14 Feb 2024 11:51:47 -0500 Subject: [PATCH 010/151] mobile: Add CONNECT Proxy support for iOS (#30903) This commit enables reading the iOS system proxy settings in Envoy Mobile and using those settings to configure the NetworkConfiigurationFilter, so that the transport socket can use HTTP CONNECT to go through the configured proxy. Risk Level: low, since it's mobile only and we have a flag on EngineBuilder to enable/disable it, and it's disabled by default Testing: unit, integration, manual Signed-off-by: Rafal Augustyniak Signed-off-by: Ryan Hamilton Signed-off-by: Ali Beyad Co-authored-by: Rafal Augustyniak Co-authored-by: Ryan Hamilton --- .../swift/hello_world/ViewController.swift | 1 + mobile/library/common/apple/BUILD | 28 +++ mobile/library/common/apple/utility.cc | 46 ++++ mobile/library/common/apple/utility.h | 17 ++ .../filters/http/network_configuration/BUILD | 4 + .../http/network_configuration/filter.cc | 83 ++++++- .../http/network_configuration/filter.h | 13 +- mobile/library/common/network/BUILD | 70 +++++- .../network/apple_pac_proxy_resolver.cc | 109 +++++++++ .../common/network/apple_pac_proxy_resolver.h | 48 ++++ .../network/apple_platform_cert_verifier.h | 1 + .../common/network/apple_proxy_resolution.cc | 27 +++ .../common/network/apple_proxy_resolution.h | 16 ++ .../common/network/apple_proxy_resolver.cc | 67 ++++++ .../common/network/apple_proxy_resolver.h | 57 +++++ .../apple_system_proxy_settings_monitor.cc | 99 ++++++++ .../apple_system_proxy_settings_monitor.h | 50 ++++ mobile/library/common/network/proxy_api.h | 15 ++ .../common/network/proxy_resolver_interface.h | 34 +++ .../library/common/network/proxy_settings.cc | 91 ++++++++ .../library/common/network/proxy_settings.h | 172 ++++++++++---- mobile/library/objective-c/BUILD | 1 + mobile/library/objective-c/EnvoyEngine.h | 5 +- mobile/library/objective-c/EnvoyEngineImpl.mm | 9 +- mobile/library/swift/EngineBuilder.swift | 23 +- .../library/swift/mocks/MockEnvoyEngine.swift | 3 +- .../filters/http/network_configuration/BUILD | 3 + .../network_configuration_filter_test.cc | 104 ++++++++- .../common/network/proxy_settings_test.cc | 36 +++ mobile/test/common/proxy/BUILD | 38 +++ .../proxy/test_apple_api_registration.cc | 46 ++++ .../proxy/test_apple_api_registration.h | 23 ++ .../proxy/test_apple_pac_proxy_resolver.cc | 92 ++++++++ .../proxy/test_apple_pac_proxy_resolver.h | 27 +++ .../common/proxy/test_apple_proxy_resolver.cc | 17 ++ .../common/proxy/test_apple_proxy_resolver.h | 27 +++ .../test_apple_proxy_settings_monitor.cc | 68 ++++++ .../proxy/test_apple_proxy_settings_monitor.h | 34 +++ mobile/test/objective-c/BUILD | 14 ++ mobile/test/objective-c/EnvoyTestApi.h | 13 ++ mobile/test/objective-c/EnvoyTestApi.mm | 13 ++ mobile/test/objective-c/EnvoyTestServer.h | 4 + mobile/test/objective-c/EnvoyTestServer.mm | 8 + mobile/test/swift/integration/BUILD | 1 + mobile/test/swift/integration/proxying/BUILD | 24 ++ .../proxying/HTTPRequestUsingProxyTest.swift | 220 ++++++++++++++++++ 46 files changed, 1847 insertions(+), 54 deletions(-) create mode 100644 mobile/library/common/apple/BUILD create mode 100644 mobile/library/common/apple/utility.cc create mode 100644 mobile/library/common/apple/utility.h create mode 100644 mobile/library/common/network/apple_pac_proxy_resolver.cc create mode 100644 mobile/library/common/network/apple_pac_proxy_resolver.h create mode 100644 mobile/library/common/network/apple_proxy_resolution.cc create mode 100644 mobile/library/common/network/apple_proxy_resolution.h create mode 100644 mobile/library/common/network/apple_proxy_resolver.cc create mode 100644 mobile/library/common/network/apple_proxy_resolver.h create mode 100644 mobile/library/common/network/apple_system_proxy_settings_monitor.cc create mode 100644 mobile/library/common/network/apple_system_proxy_settings_monitor.h create mode 100644 mobile/library/common/network/proxy_api.h create mode 100644 mobile/library/common/network/proxy_resolver_interface.h create mode 100644 mobile/library/common/network/proxy_settings.cc create mode 100644 mobile/test/common/proxy/BUILD create mode 100644 mobile/test/common/proxy/test_apple_api_registration.cc create mode 100644 mobile/test/common/proxy/test_apple_api_registration.h create mode 100644 mobile/test/common/proxy/test_apple_pac_proxy_resolver.cc create mode 100644 mobile/test/common/proxy/test_apple_pac_proxy_resolver.h create mode 100644 mobile/test/common/proxy/test_apple_proxy_resolver.cc create mode 100644 mobile/test/common/proxy/test_apple_proxy_resolver.h create mode 100644 mobile/test/common/proxy/test_apple_proxy_settings_monitor.cc create mode 100644 mobile/test/common/proxy/test_apple_proxy_settings_monitor.h create mode 100644 mobile/test/objective-c/EnvoyTestApi.h create mode 100644 mobile/test/objective-c/EnvoyTestApi.mm create mode 100644 mobile/test/swift/integration/proxying/BUILD create mode 100644 mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift diff --git a/mobile/examples/swift/hello_world/ViewController.swift b/mobile/examples/swift/hello_world/ViewController.swift index 40445047ddc4..30c9e447ef5e 100644 --- a/mobile/examples/swift/hello_world/ViewController.swift +++ b/mobile/examples/swift/hello_world/ViewController.swift @@ -22,6 +22,7 @@ final class ViewController: UITableViewController { .addPlatformFilter(DemoFilter.init) .addPlatformFilter(BufferDemoFilter.init) .addPlatformFilter(AsyncDemoFilter.init) + .respectSystemProxySettings(true) .addNativeFilter( name: "envoy.filters.http.buffer", typedConfig: """ diff --git a/mobile/library/common/apple/BUILD b/mobile/library/common/apple/BUILD new file mode 100644 index 000000000000..6b3cbcd92fa2 --- /dev/null +++ b/mobile/library/common/apple/BUILD @@ -0,0 +1,28 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_mobile_package") + +licenses(["notice"]) # Apache 2 + +envoy_mobile_package() + +envoy_cc_library( + name = "utility_lib", + srcs = select({ + "@envoy//bazel:apple": [ + "utility.cc", + ], + "//conditions:default": [], + }), + hdrs = select({ + "@envoy//bazel:apple": [ + "utility.h", + ], + "//conditions:default": [], + }), + repository = "@envoy", + deps = select({ + "@envoy//bazel:apple": [ + "@envoy//source/common/common:assert_lib", + ], + "//conditions:default": [], + }), +) diff --git a/mobile/library/common/apple/utility.cc b/mobile/library/common/apple/utility.cc new file mode 100644 index 000000000000..bcbdfc2a3d3a --- /dev/null +++ b/mobile/library/common/apple/utility.cc @@ -0,0 +1,46 @@ +#include "library/common/apple/utility.h" + +#include "source/common/common/assert.h" + +namespace Envoy { +namespace Apple { + +std::string toString(CFStringRef cf_string) { + // A pointer to a C string or NULL if the internal storage of string + // does not allow this to be returned efficiently. + // CFStringGetCStringPtr will return a pointer to the string with no memory allocation and in + // constant time, if it can; otherwise, it will return null. + const char* efficient_c_str = CFStringGetCStringPtr(cf_string, kCFStringEncodingUTF8); + if (efficient_c_str) { + return std::string(efficient_c_str); + } + + CFIndex length = CFStringGetLength(cf_string); + // Adding 1 to accomodate the `\0` null delimiter in a C string. + CFIndex size = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1; + char* c_str = static_cast(malloc(size)); + // Use less efficient method of getting c string if CFStringGetCStringPtr failed. + const bool ret = CFStringGetCString(cf_string, c_str, size, kCFStringEncodingUTF8); + ENVOY_BUG(ret, "CFStringGetCString failed to convert CFStringRef to C string."); + + std::string ret_str; + ret_str = std::string(c_str); + + free(c_str); + return ret_str; +} + +int toInt(CFNumberRef number) { + if (number == nullptr) { + return 0; + } + + int value = 0; + const bool ret = CFNumberGetValue(number, kCFNumberSInt32Type, &value); + ENVOY_BUG(ret, "CFNumberGetValue failed to convert CFNumberRef to int."); + + return value; +} + +} // namespace Apple +} // namespace Envoy diff --git a/mobile/library/common/apple/utility.h b/mobile/library/common/apple/utility.h new file mode 100644 index 000000000000..a6ef2943f807 --- /dev/null +++ b/mobile/library/common/apple/utility.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +#include + +namespace Envoy { +namespace Apple { + +// Converts a CStringRef from Objective-C into a C++ std::string in the most effecient way feasible. +std::string toString(CFStringRef); + +// Converts a CFNumberRef (an Objective-C number representation) into a C++ int value. +int toInt(CFNumberRef); + +} // namespace Apple +} // namespace Envoy diff --git a/mobile/library/common/extensions/filters/http/network_configuration/BUILD b/mobile/library/common/extensions/filters/http/network_configuration/BUILD index ad0cce1697a7..2360408a9028 100644 --- a/mobile/library/common/extensions/filters/http/network_configuration/BUILD +++ b/mobile/library/common/extensions/filters/http/network_configuration/BUILD @@ -21,9 +21,13 @@ envoy_cc_extension( repository = "@envoy", deps = [ ":filter_cc_proto", + "//library/common/api:external_api_lib", "//library/common/http:header_utility_lib", "//library/common/http:internal_headers_lib", "//library/common/network:connectivity_manager_lib", + "//library/common/network:proxy_api_lib", + "//library/common/network:proxy_resolver_interface_lib", + "//library/common/network:proxy_settings_lib", "//library/common/stream_info:extra_stream_info_lib", "//library/common/types:c_types_lib", "@envoy//envoy/http:codes_interface", diff --git a/mobile/library/common/extensions/filters/http/network_configuration/filter.cc b/mobile/library/common/extensions/filters/http/network_configuration/filter.cc index 0502df10dca6..4aa7579f931d 100644 --- a/mobile/library/common/extensions/filters/http/network_configuration/filter.cc +++ b/mobile/library/common/extensions/filters/http/network_configuration/filter.cc @@ -2,9 +2,14 @@ #include "envoy/server/filter_config.h" +#include "source/common/http/headers.h" +#include "source/common/http/utility.h" #include "source/common/network/filter_state_proxy_info.h" +#include "library/common/api/external.h" +#include "library/common/data/utility.h" #include "library/common/http/header_utility.h" +#include "library/common/types/c_types.h" namespace Envoy { namespace Extensions { @@ -55,6 +60,17 @@ bool NetworkConfigurationFilter::onAddressResolved( return false; } +void NetworkConfigurationFilter::onProxyResolutionComplete( + Network::ProxySettingsConstSharedPtr proxy_settings) { + if (continueWithProxySettings(proxy_settings) == Http::FilterHeadersStatus::Continue) { + // PAC URL resolution happens via Apple APIs on a separate thread (Apple's run loop thread), so + // we schedule the continuation callback on the engine's dispatcher. + continue_decoding_callback_ = decoder_callbacks_->dispatcher().createSchedulableCallback( + [this]() { decoder_callbacks_->continueDecoding(); }); + continue_decoding_callback_->scheduleCallbackNextIteration(); + } +} + Http::FilterHeadersStatus NetworkConfigurationFilter::decodeHeaders(Http::RequestHeaderMap& request_headers, bool) { ENVOY_LOG(trace, "NetworkConfigurationFilter::decodeHeaders", request_headers); @@ -64,19 +80,76 @@ NetworkConfigurationFilter::decodeHeaders(Http::RequestHeaderMap& request_header return Http::FilterHeadersStatus::Continue; } - // If there is no proxy configured, continue. + // For iOS, we use the `envoy_proxy_resolver` API, which reads proxy settings asynchronously, + // and callbacks are invoked in the filter when proxy settings are updated. + auto* proxy_resolver = static_cast( + Api::External::retrieveApi("envoy_proxy_resolver", /*allow_absent=*/true)); + if (proxy_resolver != nullptr) { + return resolveProxy(request_headers, proxy_resolver); + } + + // For Android, proxy settings are pushed to the ConnectivityManager and synchronously handled + // here. const auto proxy_settings = connectivity_manager_->getProxySettings(); + return continueWithProxySettings(proxy_settings); +} + +Http::FilterHeadersStatus +NetworkConfigurationFilter::resolveProxy(Http::RequestHeaderMap& request_headers, + Network::ProxyResolverApi* proxy_resolver) { + ASSERT(proxy_resolver != nullptr, "proxy_resolver must not be null."); + + const std::string target_url = Http::Utility::buildOriginalUri(request_headers, absl::nullopt); + + std::weak_ptr weak_self = weak_from_this(); + Network::ProxyResolutionResult proxy_resolution_result = proxy_resolver->resolver->resolveProxy( + target_url, proxy_settings_, + [&weak_self](const std::vector& proxies) { + // This is the callback invoked from the Apple APIs resolving the PAC file URL, which + // happens on a separate Apple run loop thread. We keep a weak_ptr to this filter instance + // so that, if the stream is canceled and the filter chain is torn down in the meantime, + // we will fail to aquire the weak_ptr lock and won't execute any callbacks on the resolved + // proxies. + if (auto caller_ptr = weak_self.lock()) { + Network::ProxySettingsConstSharedPtr proxy_settings = + Network::ProxySettings::create(proxies); + // We are currently in the main thread, so post the proxy resolution callback to the + // worker thread's (i.e. the Engine thread) dispatcher. + caller_ptr->decoder_callbacks_->dispatcher().post([&weak_self, proxy_settings]() { + // Again, the stream may have been canceled in the interim, so try aquiring the + // filter through its weak_ptr before proceding with the proxy resolution callback. + if (auto filter_ptr = weak_self.lock()) { + filter_ptr->onProxyResolutionComplete(proxy_settings); + } + }); + } + }); + + switch (proxy_resolution_result) { + case Network::ProxyResolutionResult::NO_PROXY_CONFIGURED: + return Http::FilterHeadersStatus::Continue; + case Network::ProxyResolutionResult::RESULT_COMPLETED: + return continueWithProxySettings(Network::ProxySettings::create(proxy_settings_)); + case Network::ProxyResolutionResult::RESULT_IN_PROGRESS: + // `onProxyResolutionComplete` method will be called once the proxy resolution completes + return Http::FilterHeadersStatus::StopAllIterationAndWatermark; + } +} + +Http::FilterHeadersStatus NetworkConfigurationFilter::continueWithProxySettings( + Network::ProxySettingsConstSharedPtr proxy_settings) { + // If there is no proxy configured, continue. if (proxy_settings == nullptr) { return Http::FilterHeadersStatus::Continue; } - ENVOY_LOG(trace, "netconf_filter_processing_proxy_for_request", proxy_settings->asString()); + ENVOY_LOG(trace, "netconf_filter_processing_proxy_for_request proxy_settings={}", + proxy_settings->asString()); // If there is a proxy with a raw address, set the information, and continue. const auto proxy_address = proxy_settings->address(); if (proxy_address != nullptr) { - const auto authorityHeader = request_headers.get(AuthorityHeaderName); - - setInfo(request_headers.getHostValue(), proxy_address); + const auto host_value = decoder_callbacks_->streamInfo().getRequestHeaders()->getHostValue(); + setInfo(host_value, proxy_address); return Http::FilterHeadersStatus::Continue; } diff --git a/mobile/library/common/extensions/filters/http/network_configuration/filter.h b/mobile/library/common/extensions/filters/http/network_configuration/filter.h index 7fbe37583ffb..f17ccc38ec8c 100644 --- a/mobile/library/common/extensions/filters/http/network_configuration/filter.h +++ b/mobile/library/common/extensions/filters/http/network_configuration/filter.h @@ -7,6 +7,9 @@ #include "library/common/extensions/filters/http/network_configuration/filter.pb.h" #include "library/common/network/connectivity_manager.h" +#include "library/common/network/proxy_api.h" +#include "library/common/network/proxy_resolver_interface.h" +#include "library/common/network/proxy_settings.h" #include "library/common/stream_info/extra_stream_info.h" #include "library/common/types/c_types.h" @@ -21,7 +24,8 @@ namespace NetworkConfiguration { class NetworkConfigurationFilter final : public Http::PassThroughFilter, public Logger::Loggable, - public Extensions::Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryCallbacks { + public Extensions::Common::DynamicForwardProxy::DnsCache::LoadDnsCacheEntryCallbacks, + public std::enable_shared_from_this { public: NetworkConfigurationFilter(Network::ConnectivityManagerSharedPtr connectivity_manager, bool enable_drain_post_dns_refresh, bool enable_interface_binding) @@ -43,11 +47,16 @@ class NetworkConfigurationFilter final const Extensions::Common::DynamicForwardProxy::DnsHostInfoSharedPtr& host_info) override; void onDestroy() override; + void onProxyResolutionComplete(Network::ProxySettingsConstSharedPtr proxy_settings); private: void setInfo(absl::string_view authority, Network::Address::InstanceConstSharedPtr address); bool onAddressResolved(const Extensions::Common::DynamicForwardProxy::DnsHostInfoSharedPtr& host_info); + Http::FilterHeadersStatus resolveProxy(Http::RequestHeaderMap& request_headers, + Network::ProxyResolverApi* resolver); + Http::FilterHeadersStatus + continueWithProxySettings(Network::ProxySettingsConstSharedPtr proxy_settings); // This is only present if there is an active proxy DNS lookup in progress. std::unique_ptr @@ -57,6 +66,8 @@ class NetworkConfigurationFilter final bool enable_drain_post_dns_refresh_; bool enable_interface_binding_; Event::SchedulableCallbackPtr continue_decoding_callback_; + // The lifetime of proxy settings must be attached to the lifetime of the filter. + std::vector proxy_settings_; }; } // namespace NetworkConfiguration diff --git a/mobile/library/common/network/BUILD b/mobile/library/common/network/BUILD index 7a119bf0bd52..d7f3ae1745be 100644 --- a/mobile/library/common/network/BUILD +++ b/mobile/library/common/network/BUILD @@ -11,17 +11,16 @@ envoy_cc_library( ], hdrs = [ "connectivity_manager.h", - "proxy_settings.h", ], repository = "@envoy", deps = [ + ":proxy_settings_lib", "//library/common/network:src_addr_socket_option_lib", "//library/common/types:c_types_lib", "@envoy//envoy/network:socket_interface", "@envoy//envoy/singleton:manager_interface", "@envoy//source/common/common:assert_lib", "@envoy//source/common/common:scalar_to_byte_vector_lib", - "@envoy//source/common/common:utility_lib", "@envoy//source/common/network:addr_family_aware_socket_option_lib", "@envoy//source/common/network:socket_option_lib", "@envoy//source/extensions/common/dynamic_forward_proxy:dns_cache_manager_impl", @@ -87,3 +86,70 @@ envoy_cc_library( "//conditions:default": [], }), ) + +envoy_cc_library( + name = "proxy_settings_lib", + srcs = [ + "proxy_settings.cc", + ], + hdrs = [ + "proxy_settings.h", + ], + repository = "@envoy", + deps = [ + "@envoy//source/common/network:utility_lib", + ], +) + +envoy_cc_library( + name = "proxy_resolver_interface_lib", + hdrs = ["proxy_resolver_interface.h"], + repository = "@envoy", + deps = [ + ":proxy_settings_lib", + ], +) + +envoy_cc_library( + name = "proxy_api_lib", + hdrs = ["proxy_api.h"], + repository = "@envoy", + deps = [ + ":proxy_resolver_interface_lib", + ], +) + +envoy_cc_library( + name = "apple_proxy_resolution_lib", + srcs = select({ + "@envoy//bazel:apple": [ + "apple_pac_proxy_resolver.cc", + "apple_proxy_resolution.cc", + "apple_proxy_resolver.cc", + "apple_system_proxy_settings_monitor.cc", + ], + "//conditions:default": [], + }), + hdrs = select({ + "@envoy//bazel:apple": [ + "apple_pac_proxy_resolver.h", + "apple_proxy_resolution.h", + "apple_proxy_resolver.h", + "apple_system_proxy_settings_monitor.h", + ], + "//conditions:default": [], + }), + repository = "@envoy", + deps = select({ + "@envoy//bazel:apple": [ + ":proxy_api_lib", + ":proxy_resolver_interface_lib", + ":proxy_settings_lib", + "//library/common/api:external_api_lib", + "//library/common/apple:utility_lib", + "//library/common/extensions/cert_validator/platform_bridge:c_types_lib", + "//library/common/types:c_types_lib", + ], + "//conditions:default": [], + }), +) diff --git a/mobile/library/common/network/apple_pac_proxy_resolver.cc b/mobile/library/common/network/apple_pac_proxy_resolver.cc new file mode 100644 index 000000000000..9a07a00ba607 --- /dev/null +++ b/mobile/library/common/network/apple_pac_proxy_resolver.cc @@ -0,0 +1,109 @@ +#include "library/common/network/apple_pac_proxy_resolver.h" + +#include +#include +#include + +#include "source/common/common/assert.h" + +#include "library/common/apple/utility.h" + +namespace Envoy { +namespace Network { + +namespace { + +// Creates a CFURLRef from a C++ string URL. +CFURLRef createCFURL(const std::string& url_string) { + auto cf_url_string = + CFStringCreateWithCString(kCFAllocatorDefault, url_string.c_str(), kCFStringEncodingUTF8); + auto cf_url = CFURLCreateWithString(kCFAllocatorDefault, cf_url_string, /*baseURL=*/nullptr); + CFRelease(cf_url_string); + return cf_url; +} + +} // namespace + +void proxyAutoConfigurationResultCallback(void* ptr, CFArrayRef cf_proxies, CFErrorRef cf_error) { + // `ptr` contains the unowned pointer to the ProxySettingsResolvedCallback. We extract it from the + // void* and wrap it in a unique_ptr so the memory gets reclaimed at the end of the function when + // `completion_callback` goes out of scope. + std::unique_ptr completion_callback( + static_cast(ptr)); + + std::vector proxies; + if (cf_error != nullptr || cf_proxies == nullptr) { + ENVOY_BUG(cf_error != nullptr, Apple::toString(CFErrorCopyDescription(cf_error))); + // Treat the error case as if no proxy was configured. Seems to be consistent with what iOS + // system (URLSession) is doing. + (*completion_callback)(proxies); + return; + } + + for (int i = 0; i < CFArrayGetCount(cf_proxies); i++) { + CFDictionaryRef cf_dictionary = + static_cast(CFArrayGetValueAtIndex(cf_proxies, i)); + CFStringRef cf_proxy_type = + static_cast(CFDictionaryGetValue(cf_dictionary, kCFProxyTypeKey)); + bool is_http_proxy = CFStringCompare(cf_proxy_type, kCFProxyTypeHTTP, 0) == kCFCompareEqualTo; + bool is_https_proxy = CFStringCompare(cf_proxy_type, kCFProxyTypeHTTPS, 0) == kCFCompareEqualTo; + bool is_direct_proxy = CFStringCompare(cf_proxy_type, kCFProxyTypeNone, 0) == kCFCompareEqualTo; + + if (is_http_proxy || is_https_proxy) { + CFStringRef cf_host = + static_cast(CFDictionaryGetValue(cf_dictionary, kCFProxyHostNameKey)); + CFNumberRef cf_port = + static_cast(CFDictionaryGetValue(cf_dictionary, kCFProxyPortNumberKey)); + proxies.emplace_back(ProxySettings(Apple::toString(cf_host), Apple::toInt(cf_port))); + } else if (is_direct_proxy) { + proxies.push_back(ProxySettings::direct()); + } + } + + (*completion_callback)(proxies); +} + +CFRunLoopSourceRef +ApplePacProxyResolver::createPacUrlResolverSource(CFURLRef cf_proxy_autoconfiguration_file_url, + CFURLRef cf_target_url, + CFStreamClientContext* context) { + // Even though neither the name of the method nor Apple's documentation mentions that, manual + // testing shows that `CFNetworkExecuteProxyAutoConfigurationURL` method does caching of fetched + // PAC file and does not fetch it on every proxy resolution request. + return CFNetworkExecuteProxyAutoConfigurationURL(cf_proxy_autoconfiguration_file_url, + cf_target_url, + proxyAutoConfigurationResultCallback, context); +} + +void ApplePacProxyResolver::resolveProxies( + const std::string& target_url, const std::string& proxy_autoconfiguration_file_url, + ProxySettingsResolvedCallback proxy_resolution_completed) { + CFURLRef cf_target_url = createCFURL(target_url); + CFURLRef cf_proxy_autoconfiguration_file_url = createCFURL(proxy_autoconfiguration_file_url); + + std::unique_ptr completion_callback = + std::make_unique(std::move(proxy_resolution_completed)); + // According to https://developer.apple.com/documentation/corefoundation/cfstreamclientcontext, + // the version must be 0. + auto context = std::make_unique( + CFStreamClientContext{/*version=*/0, + /*info=*/completion_callback.release(), + /*retain=*/nullptr, + /*release=*/nullptr, + /*copyDescription=*/nullptr}); + + // Ownership of the context gets released to the CFRunLoopSourceRef. When + // `proxyAutoConfigurationResultCallback` gets invoked, the pointer is passed in and is + // responsible for releasing the memory. + CFRunLoopSourceRef run_loop_source = createPacUrlResolverSource( + cf_proxy_autoconfiguration_file_url, cf_target_url, context.release()); + + CFRunLoopAddSource(CFRunLoopGetMain(), run_loop_source, kCFRunLoopDefaultMode); + + CFRelease(cf_target_url); + CFRelease(cf_proxy_autoconfiguration_file_url); + CFRelease(run_loop_source); +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_pac_proxy_resolver.h b/mobile/library/common/network/apple_pac_proxy_resolver.h new file mode 100644 index 000000000000..24d349056506 --- /dev/null +++ b/mobile/library/common/network/apple_pac_proxy_resolver.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +#include +#include + +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +// The callback function for when the PAC file URL has been resolved and the configured proxies +// are available, if any. +// `ptr` contains the unowned pointer to the ProxySettingsResolvedCallback. +// `cf_proxies` is an array of the resolved proxies. +// `cf_error` is the error, if any, when trying to resolve the PAC file URL. +void proxyAutoConfigurationResultCallback(void* ptr, CFArrayRef cf_proxies, CFErrorRef cf_error); + +/** + * Resolves auto configuration (PAC) proxies. + */ +class ApplePacProxyResolver { +public: + virtual ~ApplePacProxyResolver() = default; + /** + * Resolves proxy for a given URL using proxy auto configuration file that's hosted at a given + * URL. + * @param target_url A request URL to resolve the proxy for. + * @param proxy_autoconfiguration_file_url A URL at which a proxy configuration file is hosted. + * @param proxy_resolution_completed A function that's called with result proxies as its + * arguments when proxy resolution completes. + */ + void resolveProxies(const std::string& target_url, + const std::string& proxy_autoconfiguration_file_url, + ProxySettingsResolvedCallback proxy_resolution_completed); + +protected: + // Creates a CFRunLoopSourceRef for resolving the PAC file URL that the main CFRunLoop will run. + // Implemented as a separate function and made virtual so we can overload in tests. + virtual CFRunLoopSourceRef + createPacUrlResolverSource(CFURLRef cf_proxy_autoconfiguration_file_url, CFURLRef cf_target_url, + CFStreamClientContext* context); +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_platform_cert_verifier.h b/mobile/library/common/network/apple_platform_cert_verifier.h index b559d41a4978..4ed6604c4a83 100644 --- a/mobile/library/common/network/apple_platform_cert_verifier.h +++ b/mobile/library/common/network/apple_platform_cert_verifier.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "absl/strings/string_view.h" diff --git a/mobile/library/common/network/apple_proxy_resolution.cc b/mobile/library/common/network/apple_proxy_resolution.cc new file mode 100644 index 000000000000..5af6f62711ca --- /dev/null +++ b/mobile/library/common/network/apple_proxy_resolution.cc @@ -0,0 +1,27 @@ +#include "library/common/network/apple_proxy_resolution.h" + +#include + +#include "library/common/api/external.h" +#include "library/common/network/apple_proxy_resolver.h" +#include "library/common/network/proxy_api.h" + +// NOLINT(namespace-envoy) + +#ifdef __cplusplus +extern "C" { +#endif + +void registerAppleProxyResolver() { + auto resolver = std::make_unique(); + resolver->start(); + + auto api = std::make_unique(); + api->resolver = std::move(resolver); + + Envoy::Api::External::registerApi("envoy_proxy_resolver", api.release()); +} + +#ifdef __cplusplus +} +#endif diff --git a/mobile/library/common/network/apple_proxy_resolution.h b/mobile/library/common/network/apple_proxy_resolution.h new file mode 100644 index 000000000000..068bf8fceda8 --- /dev/null +++ b/mobile/library/common/network/apple_proxy_resolution.h @@ -0,0 +1,16 @@ +#pragma once + +// NOLINT(namespace-envoy) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Registers the Apple proxy resolver. + */ +void registerAppleProxyResolver(); + +#ifdef __cplusplus +} +#endif diff --git a/mobile/library/common/network/apple_proxy_resolver.cc b/mobile/library/common/network/apple_proxy_resolver.cc new file mode 100644 index 000000000000..8329b1d853b2 --- /dev/null +++ b/mobile/library/common/network/apple_proxy_resolver.cc @@ -0,0 +1,67 @@ +#include "library/common/network/apple_proxy_resolver.h" + +#include "source/common/common/assert.h" + +namespace Envoy { +namespace Network { + +AppleProxyResolver::AppleProxyResolver() + : proxy_settings_monitor_( + std::make_unique(proxySettingsUpdater())), + pac_proxy_resolver_(std::make_unique()) {} + +SystemProxySettingsReadCallback AppleProxyResolver::proxySettingsUpdater() { + return SystemProxySettingsReadCallback( + [this](absl::optional proxy_settings) { + absl::MutexLock l(&mutex_); + proxy_settings_ = std::move(proxy_settings); + }); +} + +void AppleProxyResolver::start() { + proxy_settings_monitor_->start(); + started_ = true; +} + +ProxyResolutionResult +AppleProxyResolver::resolveProxy(const std::string& target_url_string, + std::vector& proxies, + ProxySettingsResolvedCallback proxy_resolution_completed) { + ASSERT(started_, "AppleProxyResolver not started."); + + std::string pac_file_url; + { + absl::MutexLock l(&mutex_); + if (!proxy_settings_.has_value()) { + return ProxyResolutionResult::NO_PROXY_CONFIGURED; + } + + const auto proxy_settings = proxy_settings_.value(); + if (!proxy_settings.isPacEnabled()) { + ProxySettings settings(proxy_settings.hostname(), proxy_settings.port()); + if (settings.isDirect()) { + return ProxyResolutionResult::NO_PROXY_CONFIGURED; + } + proxies.emplace_back(std::move(settings)); + return ProxyResolutionResult::RESULT_COMPLETED; + } + + pac_file_url = proxy_settings.pacFileUrl(); + } + + // TODO(abeyad): As stated in ApplePacProxyResolver's comments, + // CFNetworkExecuteProxyAutoConfigurationURL caches PAC file resolution, so it's not fetched on + // every request. However, it would be good to not depend on Apple's undocumented behavior and + // implement a caching layer with TTLs for PAC file URL resolution within AppleProxyResolver + // itself. + ASSERT(!pac_file_url.empty(), "PAC file URL must not be empty if PAC is enabled."); + pac_proxy_resolver_->resolveProxies( + pac_file_url, target_url_string, + [proxy_resolution_completed](const std::vector& proxies) { + proxy_resolution_completed(proxies); + }); + return ProxyResolutionResult::RESULT_IN_PROGRESS; +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_proxy_resolver.h b/mobile/library/common/network/apple_proxy_resolver.h new file mode 100644 index 000000000000..7a39eed82fbe --- /dev/null +++ b/mobile/library/common/network/apple_proxy_resolver.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +#include +#include + +#include "library/common/network/apple_pac_proxy_resolver.h" +#include "library/common/network/apple_system_proxy_settings_monitor.h" +#include "library/common/network/proxy_resolver_interface.h" +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +/** + * Resolves proxies on Apple platforms. + */ +class AppleProxyResolver : public ProxyResolver { +public: + AppleProxyResolver(); + virtual ~AppleProxyResolver() = default; + + /** + * Starts proxy resolver. It needs to be called prior to any proxy resolution attempt. + */ + void start(); + + /** + * Resolves the proxy settings for the target URL. The result of proxy resolution is returned in + * the ProxyResolutionResult enum. If proxy resolution returns RESULT_COMPLETED, the `proxies` + * vector gets populated with the resolved proxy setting. If proxy resolution returns + * RESULT_IN_PROGRESS, the `proxy_resolution_completed` function gets invoked upon successful + * resolution of the proxy settings. + */ + virtual ProxyResolutionResult + resolveProxy(const std::string& target_url_string, std::vector& proxies, + ProxySettingsResolvedCallback proxy_resolution_completed) override; + + /* + * Supplies a function that updates this instance's proxy settings. + */ + SystemProxySettingsReadCallback proxySettingsUpdater(); + +private: + friend class TestAppleProxyResolver; + + std::unique_ptr proxy_settings_monitor_; + std::unique_ptr pac_proxy_resolver_; + absl::optional proxy_settings_; + absl::Mutex mutex_; + bool started_; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_system_proxy_settings_monitor.cc b/mobile/library/common/network/apple_system_proxy_settings_monitor.cc new file mode 100644 index 000000000000..b5f5ef4b1b0d --- /dev/null +++ b/mobile/library/common/network/apple_system_proxy_settings_monitor.cc @@ -0,0 +1,99 @@ +#include "library/common/network/apple_system_proxy_settings_monitor.h" + +#include +#include +#include + +#include "library/common/apple/utility.h" + +namespace Envoy { +namespace Network { + +// The interval at which system proxy settings should be polled at. +CFTimeInterval kProxySettingsRefreshRateSeconds = 7; + +AppleSystemProxySettingsMonitor::AppleSystemProxySettingsMonitor( + SystemProxySettingsReadCallback proxy_settings_read_callback) + : proxy_settings_read_callback_(proxy_settings_read_callback) {} + +void AppleSystemProxySettingsMonitor::start() { + if (started_) { + return; + } + + started_ = true; + + dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class( + DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, DISPATCH_QUEUE_PRIORITY_DEFAULT); + queue_ = dispatch_queue_create("io.envoyproxy.envoymobile.AppleSystemProxySettingsMonitor", + attributes); + + __block absl::optional proxy_settings; + dispatch_sync(queue_, ^{ + proxy_settings = readSystemProxySettings(); + proxy_settings_read_callback_(std::move(proxy_settings)); + }); + + source_ = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue_); + dispatch_source_set_timer(source_, dispatch_time(DISPATCH_TIME_NOW, 0), + kProxySettingsRefreshRateSeconds * NSEC_PER_SEC, 0); + dispatch_source_set_event_handler(source_, ^{ + const auto new_proxy_settings = readSystemProxySettings(); + if (new_proxy_settings != proxy_settings) { + proxy_settings = new_proxy_settings; + proxy_settings_read_callback_(std::move(new_proxy_settings)); + } + }); + dispatch_resume(source_); +} + +AppleSystemProxySettingsMonitor::~AppleSystemProxySettingsMonitor() { + if (source_ != nullptr) { + dispatch_suspend(source_); + } + if (queue_ != nullptr) { + dispatch_release(queue_); + } +} + +CFDictionaryRef AppleSystemProxySettingsMonitor::getSystemProxySettings() const { + return CFNetworkCopySystemProxySettings(); +} + +absl::optional +AppleSystemProxySettingsMonitor::readSystemProxySettings() const { + CFDictionaryRef proxy_settings = getSystemProxySettings(); + if (proxy_settings == nullptr) { + return absl::nullopt; + } + + // iOS system settings allow users to enter an arbitrary big integer number i.e. 88888888 as a + // port number. That being said, testing using iOS 16 shows that Apple's APIs return + // `is_http_proxy_enabled` equal to false unless entered port number is within [0, 65535] range. + CFNumberRef cf_is_http_proxy_enabled = + static_cast(CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesHTTPEnable)); + CFNumberRef cf_is_auto_config_proxy_enabled = static_cast( + CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesProxyAutoConfigEnable)); + const bool is_http_proxy_enabled = Apple::toInt(cf_is_http_proxy_enabled) > 0; + const bool is_auto_config_proxy_enabled = Apple::toInt(cf_is_auto_config_proxy_enabled) > 0; + + absl::optional settings; + if (is_http_proxy_enabled) { + CFStringRef cf_hostname = + static_cast(CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesHTTPProxy)); + CFNumberRef cf_port = + static_cast(CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesHTTPPort)); + settings = absl::make_optional(Apple::toString(cf_hostname), + Apple::toInt(cf_port)); + } else if (is_auto_config_proxy_enabled) { + CFStringRef cf_pac_file_url_string = static_cast( + CFDictionaryGetValue(proxy_settings, kCFNetworkProxiesProxyAutoConfigURLString)); + settings = absl::make_optional(Apple::toString(cf_pac_file_url_string)); + } + + CFRelease(proxy_settings); + return settings; +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/apple_system_proxy_settings_monitor.h b/mobile/library/common/network/apple_system_proxy_settings_monitor.h new file mode 100644 index 000000000000..bbf898bb5333 --- /dev/null +++ b/mobile/library/common/network/apple_system_proxy_settings_monitor.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +#include + +#include "absl/types/optional.h" +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +/** + * Monitors Apple system proxy settings changes. Optimized for iOS platform. + */ +class AppleSystemProxySettingsMonitor { +public: + /** + * Creates a new instance of the receiver. Calls provided function every time it detects a + * change of system proxy settings. Treats invalid PAC URLs as a lack of proxy configuration. + * + * @param proxy_settings_read_callback The closure to call every time system proxy settings + * change. The closure is called on a non-main queue. + */ + AppleSystemProxySettingsMonitor(SystemProxySettingsReadCallback proxy_settings_read_callback); + virtual ~AppleSystemProxySettingsMonitor(); + + /** + * Starts monitoring system proxy settings. + */ + void start(); + +protected: + // Protected and virtual so they can be overridden by tests to provide mocked system proxy + // settings. + virtual CFDictionaryRef getSystemProxySettings() const; + +private: + absl::optional readSystemProxySettings() const; + + dispatch_source_t source_; + dispatch_queue_t queue_; + bool started_; + SystemProxySettingsReadCallback proxy_settings_read_callback_; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/proxy_api.h b/mobile/library/common/network/proxy_api.h new file mode 100644 index 000000000000..83f9f8d47fbf --- /dev/null +++ b/mobile/library/common/network/proxy_api.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include "library/common/network/proxy_resolver_interface.h" + +namespace Envoy { +namespace Network { + +struct ProxyResolverApi { + std::unique_ptr resolver; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/proxy_resolver_interface.h b/mobile/library/common/network/proxy_resolver_interface.h new file mode 100644 index 000000000000..dd9213566819 --- /dev/null +++ b/mobile/library/common/network/proxy_resolver_interface.h @@ -0,0 +1,34 @@ +#pragma once + +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +enum class ProxyResolutionResult { + NO_PROXY_CONFIGURED = 0, + RESULT_COMPLETED = 1, + RESULT_IN_PROGRESS = 2, +}; + +// An interface for resolving the system's proxy settings. +class ProxyResolver { +public: + virtual ~ProxyResolver() = default; + + /** + * Resolves proxy for a given url. Depending on the type current system proxy settings the method + * may return results in synchronous or asynchronous way. + * @param proxies Result proxies for when the proxy resolution is performed synchronously. + * @param proxy_resolution_completed A function that's called with result proxies as its + * arguments for when the proxy resolution is performed asynchronously. + * @return Whether there is a proxy or no and whether proxy resolution was performed synchronously + * or whether it's still running. + */ + virtual ProxyResolutionResult + resolveProxy(const std::string& target_url_string, std::vector& proxies, + ProxySettingsResolvedCallback proxy_resolution_completed) PURE; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/proxy_settings.cc b/mobile/library/common/network/proxy_settings.cc new file mode 100644 index 000000000000..6350b67c3e23 --- /dev/null +++ b/mobile/library/common/network/proxy_settings.cc @@ -0,0 +1,91 @@ +#include "library/common/network/proxy_settings.h" + +#include + +namespace Envoy { +namespace Network { + +ProxySettings::ProxySettings(const std::string& host, const uint16_t port) + : address_(Envoy::Network::Utility::parseInternetAddressNoThrow(host, port)), hostname_(host), + port_(port) {} + +/*static*/ +ProxySettingsConstSharedPtr ProxySettings::parseHostAndPort(const std::string& host, + const uint16_t port) { + if (host == "" && port == 0) { + return nullptr; + } + return std::make_shared(host, port); +} + +/*static*/ +ProxySettings ProxySettings::direct() { return ProxySettings("", 0); } + +/*static*/ +ProxySettingsConstSharedPtr ProxySettings::create(const std::vector& settings_list) { + if (settings_list.empty()) { + return nullptr; + } + + for (const auto& proxy_settings : settings_list) { + if (!proxy_settings.isDirect()) { + // We'll use the first non-direct ProxySettings. + return std::make_shared(proxy_settings.hostname(), proxy_settings.port()); + } + } + + // No non-direct ProxySettings was found. + return nullptr; +} + +bool ProxySettings::isDirect() const { return hostname_ == "" && port_ == 0; } + +const Envoy::Network::Address::InstanceConstSharedPtr& ProxySettings::address() const { + return address_; +} + +const std::string& ProxySettings::hostname() const { return hostname_; } + +uint16_t ProxySettings::port() const { return port_; } + +const std::string ProxySettings::asString() const { + if (address_ != nullptr) { + return address_->asString(); + } + if (!hostname_.empty()) { + return absl::StrCat(hostname_, ":", port_); + } + return "no_proxy_configured"; +} + +bool ProxySettings::operator==(ProxySettings const& rhs) const { + // Even if the hostnames are IP addresses, they'll be stored in hostname_ + return hostname() == rhs.hostname() && port() == rhs.port(); +} + +bool ProxySettings::operator!=(ProxySettings const& rhs) const { return !(*this == rhs); } + +SystemProxySettings::SystemProxySettings(std::string&& hostname, int port) + : hostname_(std::move(hostname)), port_(port) {} + +SystemProxySettings::SystemProxySettings(std::string&& pac_file_url) + : port_(-1), pac_file_url_(std::move(pac_file_url)) {} + +const std::string& SystemProxySettings::hostname() const { return hostname_; } + +int SystemProxySettings::port() const { return port_; } + +const std::string& SystemProxySettings::pacFileUrl() const { return pac_file_url_; } + +bool SystemProxySettings::isPacEnabled() const { return !pac_file_url_.empty(); } + +bool SystemProxySettings::operator==(SystemProxySettings const& rhs) const { + return hostname() == rhs.hostname() && port() == rhs.port() && pacFileUrl() == rhs.pacFileUrl(); +} + +bool SystemProxySettings::operator!=(SystemProxySettings const& rhs) const { + return !(*this == rhs); +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/library/common/network/proxy_settings.h b/mobile/library/common/network/proxy_settings.h index c99aa8bc3066..19a7e4b28ffc 100644 --- a/mobile/library/common/network/proxy_settings.h +++ b/mobile/library/common/network/proxy_settings.h @@ -1,34 +1,47 @@ #pragma once +#include +#include +#include + #include "source/common/network/utility.h" +#include "absl/types/optional.h" + namespace Envoy { namespace Network { -struct ProxySettings; +class ProxySettings; +class SystemProxySettings; using ProxySettingsConstSharedPtr = std::shared_ptr; +using ProxySettingsResolvedCallback = std::function&)>; +using SystemProxySettingsReadCallback = std::function)>; /** * Proxy settings coming from platform specific APIs, i.e. ConnectivityManager in * the case of Android platform. * + * ProxySettings represents already-resolved proxy settings. For example, if the proxy + * settings are obtained from a PAC URL, then this object represents the resolved proxy + * settings after reading and parsing the PAC URL. + * + * TODO(abeyad): rename this class to ProxySetting (the plural in the name is confusing). */ -struct ProxySettings { +class ProxySettings { +public: /** - * @brief Construct a new Proxy Settings object. + * Construct a new Proxy Settings object. * * @param host The proxy host defined as a hostname or an IP address. Some platforms * (i.e., Android) allow users to specify proxy using either one of these. * @param port The proxy port. */ - ProxySettings(const std::string& host, const uint16_t port) - : address_(Envoy::Network::Utility::parseInternetAddressNoThrow(host, port)), hostname_(host), - port_(port) {} + ProxySettings(const std::string& host, const uint16_t port); /** - * @brief Parses given host and domain and creates proxy settings. Returns nullptr - * for an empty host and a port equal to 0 as they are passed to c++ native layer - * as a synonym of the lack of proxy settings configured on a device. + * Parses given host and domain and creates proxy settings. Returns nullptr for an empty host + * and a port equal to 0, as they are passed to the C++ native layer to represent the lack of + * proxy settings configured on a device. * * @param host The proxy host defined as a hostname or an IP address. Some platforms * (i.e., Android) allow users to specify proxy using either one of these. @@ -36,59 +49,76 @@ struct ProxySettings { * @return The created proxy settings, nullptr if the passed host is an empty string and * port is equal to 0. */ - static const ProxySettingsConstSharedPtr parseHostAndPort(const std::string& host, - const uint16_t port) { - if (host == "" && port == 0) { - return nullptr; - } - return std::make_shared(host, port); - } + static ProxySettingsConstSharedPtr parseHostAndPort(const std::string& host, const uint16_t port); + + /** + * Creates a ProxySettings instance to represent a direct connection (i.e. no proxy at all). + * + * @return The ProxySettings object. Calling isDirect() on it returns true. + */ + static ProxySettings direct(); + + /** + * Creates a shared pointer instance of the ProxySettings to use, from the list of resolved + * system proxy settings. + * + * @param settings_list A list of ProxySettings provided by the system. + * + * @return A shared pointer to an instance of the ProxySettings that was chosen from the proxy + * settings list. + */ + static ProxySettingsConstSharedPtr create(const std::vector& settings_list); /** - * @brief Returns an address of a proxy. This method returns nullptr for proxy settings - * that are initialized with anything other than an IP address. + * @return true if the instance represents a direct connection (i.e. no proxy), false otherwise. + */ + bool isDirect() const; + + /** + * Returns an address of a proxy. This method returns nullptr for proxy settings that are + * initialized with anything other than an IP address. * * @return Address of a proxy or nullptr if proxy address is incorrect or host is * defined using a hostname and not an IP address. */ - const Envoy::Network::Address::InstanceConstSharedPtr& address() const { return address_; } + const Envoy::Network::Address::InstanceConstSharedPtr& address() const; /** - * @brief Returns the hostname of a proxy. + * Returns a reference to the hostname of the proxy. * - * @return Hostname of a proxy or the empty string if there is no hostname. + * @return Hostname of the proxy (if the string is empty, there is no hostname). */ - const std::string& hostname() const { return hostname_; } + const std::string& hostname() const; /** - * @brief Returns the port of the proxy. + * Returns the port of the proxy. * * @return Port of the proxy. */ - uint16_t port() const { return port_; } + uint16_t port() const; /** - * @brief Returns a human readable representation of the proxy settings represented - * by the receiver + * Returns a human readable representation of the proxy settings represented by the receiver. * * @return const A human readable representation of the receiver. */ - const std::string asString() const { - if (address_ != nullptr) { - return address_->asString(); - } - if (!hostname_.empty()) { - return absl::StrCat(hostname_, ":", port_); - } - return "no_proxy_configured"; - } + const std::string asString() const; - bool operator==(ProxySettings const& rhs) const { - // Even if the hostnames are IP addresses, they'll be stored in hostname_ - return hostname() == rhs.hostname() && port() == rhs.port(); - } + /** + * Equals operator overload. + * @param rhs another ProxySettings object. + * @return returns true if the two ProxySettings objects are equivalent (represents the + * same configuration); false otherwise. + */ + bool operator==(ProxySettings const& rhs) const; - bool operator!=(ProxySettings const& rhs) const { return !(*this == rhs); } + /** + * Not equals operator overload. + * @param rhs another ProxySettings object. + * @return returns true if the two ProxySettings objects are not equivalent (represents + * different proxy configuration); false otherwise. + */ + bool operator!=(ProxySettings const& rhs) const; private: Envoy::Network::Address::InstanceConstSharedPtr address_; @@ -96,5 +126,67 @@ struct ProxySettings { uint16_t port_; }; +/** + * System proxy settings. There are 2 ways to specify desired proxy settings. Either provide proxy's + * hostname or provide a URL to a Proxy Auto-Configuration (PAC) file. + */ +class SystemProxySettings { +public: + /** + * Creates proxy settings using provided hostname and port. + * @param hostname A domain name or an IP address. + * @param port A valid internet port from within [0-65535] range. + */ + SystemProxySettings(std::string&& hostname, int port); + + /** + * Creates proxy settings using provided URL to a Proxy Auto-Configuration (PAC) file. + * @param pac_file_url A URL to a Proxy Auto-Configuration (PAC) file. + */ + SystemProxySettings(std::string&& pac_file_url); + + /** + * @return a reference to the hostname (which will be an empty string if not configured). + */ + const std::string& hostname() const; + + /* + * @return the port number, or -1 if a PAC file is configured. + */ + int port() const; + + /** + * @return a reference to the PAC file URL (which will be an empty string if not configured). + */ + const std::string& pacFileUrl() const; + + /** + * @return returns true if this object represents a PAC file URL configured proxy, + * returns false if this object represents a host/port configured proxy. + */ + bool isPacEnabled() const; + + /** + * Equals operator overload. + * @param rhs another SystemProxySettings object. + * @return returns true if the two SystemProxySettings objects are equivalent (represents the + * same configuration); false otherwise. + */ + bool operator==(SystemProxySettings const& rhs) const; + + /** + * Not equals operator overload. + * @param rhs another SystemProxySettings object. + * @return returns true if the two SystemProxySettings objects are not equivalent (represents + * different proxy configuration); false otherwise. + */ + bool operator!=(SystemProxySettings const& rhs) const; + +private: + std::string hostname_; + int port_; + std::string pac_file_url_; +}; + } // namespace Network } // namespace Envoy diff --git a/mobile/library/objective-c/BUILD b/mobile/library/objective-c/BUILD index 00d6435a4939..ee5c61619d10 100644 --- a/mobile/library/objective-c/BUILD +++ b/mobile/library/objective-c/BUILD @@ -60,6 +60,7 @@ envoy_objc_library( "//library/common:internal_engine_lib", "//library/common/api:c_types", "//library/common/network:apple_platform_cert_verifier", + "//library/common/network:apple_proxy_resolution_lib", ], ) diff --git a/mobile/library/objective-c/EnvoyEngine.h b/mobile/library/objective-c/EnvoyEngine.h index bddcd0775507..bb765e0a470a 100644 --- a/mobile/library/objective-c/EnvoyEngine.h +++ b/mobile/library/objective-c/EnvoyEngine.h @@ -33,11 +33,14 @@ NS_ASSUME_NONNULL_BEGIN @param logger Logging interface. @param eventTracker Event tracking interface. @param networkMonitoringMode Configure how the engines observe network reachability. + @param respectSystemProxySettings Whether to respect system proxy settings when performing + network requests. */ - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning logger:(nullable void (^)(NSInteger, NSString *))logger eventTracker:(nullable void (^)(EnvoyEvent *))eventTracker - networkMonitoringMode:(int)networkMonitoringMode; + networkMonitoringMode:(int)networkMonitoringMode + respectSystemProxySettings:(BOOL)respectSystemProxySettings; /** Run the Envoy engine with the provided configuration and log level. diff --git a/mobile/library/objective-c/EnvoyEngineImpl.mm b/mobile/library/objective-c/EnvoyEngineImpl.mm index adac27f8ed0a..0395f1b8641b 100644 --- a/mobile/library/objective-c/EnvoyEngineImpl.mm +++ b/mobile/library/objective-c/EnvoyEngineImpl.mm @@ -10,6 +10,8 @@ #import "library/cc/engine_builder.h" #import "library/common/internal_engine.h" +#include "library/common/network/apple_proxy_resolution.h" + #if TARGET_OS_IPHONE #import #endif @@ -405,7 +407,8 @@ @implementation EnvoyEngineImpl { - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning logger:(nullable void (^)(NSInteger, NSString *))logger eventTracker:(nullable void (^)(EnvoyEvent *))eventTracker - networkMonitoringMode:(int)networkMonitoringMode { + networkMonitoringMode:(int)networkMonitoringMode + respectSystemProxySettings:(BOOL)respectSystemProxySettings { self = [super init]; if (!self) { return nil; @@ -436,6 +439,10 @@ - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning _engine = new Envoy::InternalEngine(native_callbacks, native_logger, native_event_tracker); _engineHandle = reinterpret_cast(_engine); + if (respectSystemProxySettings) { + registerAppleProxyResolver(); + } + if (networkMonitoringMode == 1) { [_networkMonitor startReachability]; } else if (networkMonitoringMode == 2) { diff --git a/mobile/library/swift/EngineBuilder.swift b/mobile/library/swift/EngineBuilder.swift index 2969e21bc3ea..7a754d517c8a 100644 --- a/mobile/library/swift/EngineBuilder.swift +++ b/mobile/library/swift/EngineBuilder.swift @@ -154,6 +154,7 @@ open class EngineBuilder: NSObject { private var quicHints: [String: Int] = [:] private var quicCanonicalSuffixes: [String] = [] private var enableInterfaceBinding: Bool = false + private var respectSystemProxySettings: Bool = false private var enforceTrustChainVerification: Bool = true private var enablePlatformCertificateValidation: Bool = false private var enableDrainPostDnsRefresh: Bool = false @@ -366,6 +367,25 @@ open class EngineBuilder: NSObject { return self } + /// + /// Specify whether system proxy settings should be respected. If yes, Envoy Mobile will + /// use iOS APIs to query iOS Proxy settings configured on a device and will + /// respect these settings when establishing connections with remote services. + /// + /// The method is introduced for experimentation purposes and as a safety guard against + /// critical issues in the implementation of the proxying feature. It's intended to be removed + /// after it's confirmed that proxies on iOS work as expected. + /// + /// - parameter respectSystemProxySettings: whether to use the system's proxy settings for + /// outbound connections. + /// + /// - returns: This builder. + @discardableResult + public func respectSystemProxySettings(_ respectSystemProxySettings: Bool) -> Self { + self.respectSystemProxySettings = respectSystemProxySettings + return self + } + /// Specify whether to drain connections after the resolution of a soft DNS refresh. /// A refresh may be triggered directly via the Engine API, or as a result of a network /// status update provided by the OS. Draining connections does not interrupt existing @@ -697,7 +717,8 @@ open class EngineBuilder: NSObject { } }, eventTracker: self.eventTracker, - networkMonitoringMode: Int32(self.monitoringMode.rawValue)) + networkMonitoringMode: Int32(self.monitoringMode.rawValue), + respectSystemProxySettings: self.respectSystemProxySettings) let config = self.makeConfig() #if canImport(EnvoyCxxSwiftInterop) if self.enableSwiftBootstrap { diff --git a/mobile/library/swift/mocks/MockEnvoyEngine.swift b/mobile/library/swift/mocks/MockEnvoyEngine.swift index 057699ef05a0..f02a3cf8d6c2 100644 --- a/mobile/library/swift/mocks/MockEnvoyEngine.swift +++ b/mobile/library/swift/mocks/MockEnvoyEngine.swift @@ -5,7 +5,8 @@ import Foundation final class MockEnvoyEngine: NSObject { init(runningCallback onEngineRunning: (() -> Void)? = nil, logger: ((Int, String) -> Void)? = nil, - eventTracker: (([String: String]) -> Void)? = nil, networkMonitoringMode: Int32 = 0) {} + eventTracker: (([String: String]) -> Void)? = nil, networkMonitoringMode: Int32 = 0, + respectSystemProxySettings: Bool = false) {} /// Closure called when `run(withConfig:)` is called. static var onRunWithConfig: ((_ config: EnvoyConfiguration, _ logLevel: String?) -> Void)? diff --git a/mobile/test/common/extensions/filters/http/network_configuration/BUILD b/mobile/test/common/extensions/filters/http/network_configuration/BUILD index 9630ee6652bb..bac5601177cb 100644 --- a/mobile/test/common/extensions/filters/http/network_configuration/BUILD +++ b/mobile/test/common/extensions/filters/http/network_configuration/BUILD @@ -18,6 +18,9 @@ envoy_extension_cc_test( "//library/common/data:utility_lib", "//library/common/extensions/filters/http/network_configuration:config", "//library/common/extensions/filters/http/network_configuration:filter_cc_proto", + "//library/common/network:proxy_api_lib", + "//library/common/network:proxy_resolver_interface_lib", + "//library/common/network:proxy_settings_lib", "@envoy//test/extensions/common/dynamic_forward_proxy:mocks", "@envoy//test/mocks/event:event_mocks", "@envoy//test/mocks/http:http_mocks", diff --git a/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc b/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc index 813c4b7fdcd4..05a272da5bc0 100644 --- a/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc +++ b/mobile/test/common/extensions/filters/http/network_configuration/network_configuration_filter_test.cc @@ -11,6 +11,8 @@ #include "library/common/data/utility.h" #include "library/common/extensions/filters/http/network_configuration/filter.h" #include "library/common/extensions/filters/http/network_configuration/filter.pb.h" +#include "library/common/network/proxy_api.h" +#include "library/common/network/proxy_resolver_interface.h" #include "library/common/network/proxy_settings.h" using Envoy::Extensions::Common::DynamicForwardProxy::DnsCache; @@ -60,8 +62,8 @@ class MockConnectivityManager : public Network::ConnectivityManager { class NetworkConfigurationFilterTest : public testing::Test { public: NetworkConfigurationFilterTest() - : connectivity_manager_(new NiceMock), - proxy_settings_(new Network::ProxySettings("127.0.0.1", 82)), + : connectivity_manager_(std::make_shared>()), + proxy_settings_(std::make_shared("127.0.0.1", 82)), filter_(connectivity_manager_, false, false) { filter_.setDecoderFilterCallbacks(decoder_callbacks_); ON_CALL(decoder_callbacks_.stream_info_, getRequestHeaders()) @@ -184,6 +186,104 @@ TEST_F(NetworkConfigurationFilterTest, AsyncDnsLookupSuccess) { filter_.onLoadDnsCacheComplete(host_info_); } +class MockProxyResolver : public Network::ProxyResolver { +public: + MOCK_METHOD(Network::ProxyResolutionResult, resolveProxy, + (const std::string& target_url_string, std::vector& proxies, + Network::ProxySettingsResolvedCallback proxy_resolution_completed)); +}; + +class NetworkConfigurationFilterProxyResolverApiTest : public NetworkConfigurationFilterTest { +public: + NetworkConfigurationFilterProxyResolverApiTest() : NetworkConfigurationFilterTest() {} + + void SetUp() override { + auto proxy_resolver_api = std::make_unique(); + proxy_resolver_api->resolver = std::make_unique>(); + Api::External::registerApi("envoy_proxy_resolver", proxy_resolver_api.release()); + } + + void TearDown() override { + std::unique_ptr wrapped_api(getResolverApi()); + // Safe deletion of the envoy_proxy_resolver API. + wrapped_api.reset(); + } + + Network::ProxyResolverApi* getResolverApi() { + return static_cast( + Api::External::retrieveApi("envoy_proxy_resolver")); + } + + MockProxyResolver& getMockProxyResolver() { + return *static_cast(getResolverApi()->resolver.get()); + } +}; + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, NoProxyConfigured) { + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce(Return(Network::ProxyResolutionResult::NO_PROXY_CONFIGURED)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, EmptyProxyConfigured) { + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce(Return(Network::ProxyResolutionResult::RESULT_COMPLETED)); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, DirectProxyConfigured) { + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce([](const std::string& /*target_url_string*/, + std::vector& proxies, + Network::ProxySettingsResolvedCallback /*proxy_resolution_completed*/) { + proxies.emplace_back(Network::ProxySettings::direct()); + return Network::ProxyResolutionResult::RESULT_COMPLETED; + }); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, ProxyWithIpAddressConfigured) { + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce([](const std::string& /*target_url_string*/, + std::vector& proxies, + Network::ProxySettingsResolvedCallback /*proxy_resolution_completed*/) { + proxies.emplace_back(Network::ProxySettings("127.0.0.1", 8080)); + return Network::ProxyResolutionResult::RESULT_COMPLETED; + }); + EXPECT_CALL(decoder_callbacks_.stream_info_, filterState()); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + +TEST_F(NetworkConfigurationFilterProxyResolverApiTest, ProxyWithHostConfigured) { + createCache(); + + std::vector proxy_settings; + EXPECT_CALL(getMockProxyResolver(), resolveProxy(_, proxy_settings, _)) + .WillOnce([](const std::string& /*target_url_string*/, + std::vector& proxies, + Network::ProxySettingsResolvedCallback /*proxy_resolution_completed*/) { + proxies.emplace_back(Network::ProxySettings("foo.com", 8080)); + return Network::ProxyResolutionResult::RESULT_COMPLETED; + }); + EXPECT_CALL(decoder_callbacks_.stream_info_, filterState()); + EXPECT_CALL(*dns_cache_, loadDnsCacheEntry_(Eq("foo.com"), 8080, false, _)) + .WillOnce( + Invoke([&](absl::string_view, uint16_t, bool, DnsCache::LoadDnsCacheEntryCallbacks&) { + return MockDnsCache::MockLoadDnsCacheEntryResult{ + DnsCache::LoadDnsCacheEntryStatus::InCache, nullptr, host_info_}; + })); + EXPECT_EQ(Http::FilterHeadersStatus::Continue, + filter_.decodeHeaders(default_request_headers_, false)); +} + } // namespace } // namespace NetworkConfiguration } // namespace HttpFilters diff --git a/mobile/test/common/network/proxy_settings_test.cc b/mobile/test/common/network/proxy_settings_test.cc index db815c633551..bf2f538bd2db 100644 --- a/mobile/test/common/network/proxy_settings_test.cc +++ b/mobile/test/common/network/proxy_settings_test.cc @@ -1,3 +1,5 @@ +#include + #include "gtest/gtest.h" #include "library/common/network/proxy_settings.h" @@ -49,5 +51,39 @@ TEST_F(ProxySettingsTest, Hostname) { EXPECT_EQ(ProxySettings("foo.com", 80).asString(), "foo.com:80"); } +TEST_F(ProxySettingsTest, Direct) { + const ProxySettings direct = ProxySettings::direct(); + EXPECT_TRUE(direct.isDirect()); + EXPECT_EQ(direct.hostname(), ""); + EXPECT_EQ(direct.port(), 0); + EXPECT_EQ(direct.address(), nullptr); + EXPECT_EQ(direct.asString(), "no_proxy_configured"); + + const ProxySettings non_direct = ProxySettings("foo.com", 80); + EXPECT_FALSE(non_direct.isDirect()); +} + +TEST_F(ProxySettingsTest, Create) { + std::vector settings_list; + + // Empty list, so nullptr is returned. + EXPECT_EQ(ProxySettings::create(settings_list), nullptr); + + // 2nd setting in the list is non-direct, so it should be chosen to create the shared_ptr from. + settings_list.emplace_back(ProxySettings::direct()); + settings_list.emplace_back(ProxySettings("foo.com", 80)); + settings_list.emplace_back(ProxySettings("bar.com", 8080)); + ProxySettingsConstSharedPtr settings = ProxySettings::create(settings_list); + EXPECT_EQ(settings->hostname(), "foo.com"); + EXPECT_EQ(settings->port(), 80); + EXPECT_FALSE(settings->isDirect()); + + // All ProxySettings in the list are direct, so nullptr is returned. + settings_list.clear(); + settings_list.emplace_back(ProxySettings::direct()); + settings_list.emplace_back(ProxySettings::direct()); + EXPECT_EQ(ProxySettings::create(settings_list), nullptr); +} + } // namespace Network } // namespace Envoy diff --git a/mobile/test/common/proxy/BUILD b/mobile/test/common/proxy/BUILD new file mode 100644 index 000000000000..6c05ec3608b8 --- /dev/null +++ b/mobile/test/common/proxy/BUILD @@ -0,0 +1,38 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_test_library", "envoy_mobile_package") + +licenses(["notice"]) # Apache 2 + +envoy_mobile_package() + +envoy_cc_test_library( + name = "test_apple_proxy_settings_lib", + srcs = select({ + "@envoy//bazel:apple": [ + "test_apple_api_registration.cc", + "test_apple_pac_proxy_resolver.cc", + "test_apple_proxy_resolver.cc", + "test_apple_proxy_settings_monitor.cc", + ], + "//conditions:default": [], + }), + hdrs = select({ + "@envoy//bazel:apple": [ + "test_apple_api_registration.h", + "test_apple_pac_proxy_resolver.h", + "test_apple_proxy_resolver.h", + "test_apple_proxy_settings_monitor.h", + ], + "//conditions:default": [], + }), + repository = "@envoy", + deps = select({ + "@envoy//bazel:apple": [ + "//library/common/api:external_api_lib", + "//library/common/network:apple_proxy_resolution_lib", + "//library/common/network:proxy_api_lib", + "//library/common/network:proxy_settings_lib", + "//library/common/types:c_types_lib", + ], + "//conditions:default": [], + }), +) diff --git a/mobile/test/common/proxy/test_apple_api_registration.cc b/mobile/test/common/proxy/test_apple_api_registration.cc new file mode 100644 index 000000000000..5a8ea1ff6f9c --- /dev/null +++ b/mobile/test/common/proxy/test_apple_api_registration.cc @@ -0,0 +1,46 @@ +#include "test/common/proxy/test_apple_api_registration.h" + +#include "test/common/proxy/test_apple_pac_proxy_resolver.h" +#include "test/common/proxy/test_apple_proxy_resolver.h" +#include "test/common/proxy/test_apple_proxy_settings_monitor.h" + +#include "library/common/api/external.h" +#include "library/common/network/apple_proxy_resolution.h" +#include "library/common/network/apple_proxy_resolver.h" +#include "library/common/network/proxy_api.h" +#include "library/common/network/proxy_resolver_interface.h" +#include "library/common/types/c_types.h" + +// NOLINT(namespace-envoy) + +void registerTestAppleProxyResolver(absl::string_view host, int port, const bool use_pac_resolver) { + // Fetch the existing registered envoy_proxy_resolver API, if it exists. + void* existing_proxy_resolver = + Envoy::Api::External::retrieveApi("envoy_proxy_resolver", /*allow_absent=*/true); + if (existing_proxy_resolver != nullptr) { + std::unique_ptr wrapped( + static_cast(existing_proxy_resolver)); + // Delete the existing ProxyResolverApi. + wrapped.reset(); + } + + // Create a new test proxy resolver. + auto test_resolver = std::make_unique(); + // Create a TestAppleSystemProxySettingsMonitor and set the test resolver to use it. + test_resolver->setSettingsMonitorForTest( + std::make_unique( + std::string(host), port, use_pac_resolver, test_resolver->proxySettingsUpdater())); + if (use_pac_resolver) { + // Create a TestApplePacProxyResolver and set the test resolver to use it. + test_resolver->setPacResolverForTest( + std::make_unique(std::string(host), port)); + } + // Start the resolver, as we do when registering the envoy_proxy_resolver API. + test_resolver->start(); + // Create a new test ProxyResolverApi. + auto proxy_resolver = std::make_unique(); + // Set the API to use the test AppleProxyResolver. + proxy_resolver->resolver = std::move(test_resolver); + // Register the new test ProxyResolverApi. The Api registry takes over the pointer. + Envoy::Api::External::registerApi("envoy_proxy_resolver", proxy_resolver.release()); +} diff --git a/mobile/test/common/proxy/test_apple_api_registration.h b/mobile/test/common/proxy/test_apple_api_registration.h new file mode 100644 index 000000000000..4edc2f2cafa9 --- /dev/null +++ b/mobile/test/common/proxy/test_apple_api_registration.h @@ -0,0 +1,23 @@ +#pragma once + +#include "absl/strings/string_view.h" + +// NOLINT(namespace-envoy) + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Registers the test Apple proxy resolver. If the `envoy_proxy_resolver` API already exists, it + * gets removed and deleted first. The test proxy resolver then gets registered as the + * `envoy_proxy_resolver` API. + * + * @param host The hostname of the test proxy. + * @param port The port of the test proxy. + */ +void registerTestAppleProxyResolver(absl::string_view host, int port, bool use_pac_resolver); + +#ifdef __cplusplus +} +#endif diff --git a/mobile/test/common/proxy/test_apple_pac_proxy_resolver.cc b/mobile/test/common/proxy/test_apple_pac_proxy_resolver.cc new file mode 100644 index 000000000000..9c46434ce4c7 --- /dev/null +++ b/mobile/test/common/proxy/test_apple_pac_proxy_resolver.cc @@ -0,0 +1,92 @@ +#include "test/common/proxy/test_apple_pac_proxy_resolver.h" + +#include +#include + +#include + +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +namespace { + +// Contains state that is passed into the run loop callback. +struct RunLoopSourceInfo { + RunLoopSourceInfo(CFStreamClientContext* _context, const std::string& _host, const int _port) + : context(_context), host(_host), port(_port) {} + + CFStreamClientContext* context; + const std::string& host; + const int port; +}; + +void runLoopCallback(void* info) { + // Wrap in unique_ptr so we release the memory at the end of the function execution. + std::unique_ptr run_loop_info(static_cast(info)); + + const void* keys[] = {kCFProxyTypeKey, kCFProxyHostNameKey, kCFProxyPortNumberKey}; + const void* values[] = { + kCFProxyTypeHTTPS, + CFStringCreateWithCString(kCFAllocatorDefault, run_loop_info->host.c_str(), + kCFStringEncodingUTF8), + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &run_loop_info->port)}; + const int num_pairs = sizeof(keys) / sizeof(CFStringRef); + + CFDictionaryRef settings_dict = CFDictionaryCreate( + kCFAllocatorDefault, static_cast(keys), static_cast(values), + num_pairs, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + const void* objects[] = {settings_dict}; + CFArrayRef proxies = CFArrayCreate(kCFAllocatorDefault, static_cast(objects), + /*numValues=*/1, &kCFTypeArrayCallBacks); + + // Wrap in unique_ptr so we release the memory at the end of the function execution. + std::unique_ptr client_context(run_loop_info->context); + + // Invoke the proxy resolution callback that the ApplePacProxyResolver invokes. + Network::proxyAutoConfigurationResultCallback(/*ptr=*/client_context->info, proxies, + /*cf_error=*/nullptr); + + // Release the memory allocated above. + CFRelease(proxies); + CFRelease(settings_dict); + // We don't want to release the first value, since it's a global constant. + for (int i = 1; i < num_pairs; ++i) { + CFRelease(values[i]); + } +} + +} // namespace + +TestApplePacProxyResolver::TestApplePacProxyResolver(const std::string& host, const int port) + : host_(host), port_(port) {} + +CFRunLoopSourceRef TestApplePacProxyResolver::createPacUrlResolverSource( + CFURLRef /*cf_proxy_autoconfiguration_file_url*/, CFURLRef /*cf_target_url*/, + CFStreamClientContext* context) { + auto run_loop_info = std::make_unique(context, host_, port_); + run_loop_context_ = std::make_unique( + CFRunLoopSourceContext{/*version=*/0, + /*info=*/run_loop_info.release(), + /*retain=*/nullptr, + /*release=*/nullptr, + /*copyDescription=*/nullptr, + /*equal=*/nullptr, + /*hash=*/nullptr, + /*schedule=*/nullptr, + /*cancel=*/nullptr, + /*perform=*/runLoopCallback}); + + auto run_loop_source = + CFRunLoopSourceCreate(kCFAllocatorDefault, /*order=*/0, run_loop_context_.get()); + // There are no network events that get triggered to notify this test run loop source that + // it should process a result, so we have to signal it manually. + CFRunLoopSourceSignal(run_loop_source); + + return run_loop_source; +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_pac_proxy_resolver.h b/mobile/test/common/proxy/test_apple_pac_proxy_resolver.h new file mode 100644 index 000000000000..d1985c967c03 --- /dev/null +++ b/mobile/test/common/proxy/test_apple_pac_proxy_resolver.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "library/common/network/apple_pac_proxy_resolver.h" + +namespace Envoy { +namespace Network { + +class TestApplePacProxyResolver : public Network::ApplePacProxyResolver { +public: + TestApplePacProxyResolver(const std::string& host, const int port); + virtual ~TestApplePacProxyResolver() = default; + +protected: + CFRunLoopSourceRef createPacUrlResolverSource(CFURLRef cf_proxy_autoconfiguration_file_url, + CFURLRef cf_target_url, + CFStreamClientContext* context) override; + + const std::string host_; + const int port_; + std::unique_ptr run_loop_context_; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_proxy_resolver.cc b/mobile/test/common/proxy/test_apple_proxy_resolver.cc new file mode 100644 index 000000000000..283518fef8bc --- /dev/null +++ b/mobile/test/common/proxy/test_apple_proxy_resolver.cc @@ -0,0 +1,17 @@ +#include "test/common/proxy/test_apple_proxy_resolver.h" + +namespace Envoy { +namespace Network { + +void TestAppleProxyResolver::setSettingsMonitorForTest( + std::unique_ptr&& monitor) { + proxy_settings_monitor_ = std::move(monitor); +} + +void TestAppleProxyResolver::setPacResolverForTest( + std::unique_ptr&& resolver) { + pac_proxy_resolver_ = std::move(resolver); +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_proxy_resolver.h b/mobile/test/common/proxy/test_apple_proxy_resolver.h new file mode 100644 index 000000000000..f5543685cb2a --- /dev/null +++ b/mobile/test/common/proxy/test_apple_proxy_resolver.h @@ -0,0 +1,27 @@ +#pragma once + +#include "library/common/network/apple_pac_proxy_resolver.h" +#include "library/common/network/apple_proxy_resolver.h" +#include "library/common/network/apple_system_proxy_settings_monitor.h" + +namespace Envoy { +namespace Network { + +class TestAppleProxyResolver : public Network::AppleProxyResolver { +public: + /** + * Resets the proxy settings monitor to the supplied monitor instance. + * For tests only. + */ + void + setSettingsMonitorForTest(std::unique_ptr&& monitor); + + /** + * Resets the PAC URL resolver to the supplied instance. + * For tests only. + */ + void setPacResolverForTest(std::unique_ptr&& resolver); +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_proxy_settings_monitor.cc b/mobile/test/common/proxy/test_apple_proxy_settings_monitor.cc new file mode 100644 index 000000000000..7c6ff214dc9d --- /dev/null +++ b/mobile/test/common/proxy/test_apple_proxy_settings_monitor.cc @@ -0,0 +1,68 @@ +#include "test/common/proxy/test_apple_proxy_settings_monitor.h" + +#include +#include + +namespace Envoy { +namespace Network { + +TestAppleSystemProxySettingsMonitor::TestAppleSystemProxySettingsMonitor( + const std::string& host, const int port, const bool use_pac_resolver, + Network::SystemProxySettingsReadCallback proxy_settings_read_callback) + : AppleSystemProxySettingsMonitor(std::move(proxy_settings_read_callback)), host_(host), + port_(port), use_pac_resolver_(use_pac_resolver) {} + +CFDictionaryRef TestAppleSystemProxySettingsMonitor::getSystemProxySettings() const { + if (use_pac_resolver_) { + return getSystemProxySettingsWithPac(); + } + return getSystemProxySettingsWithoutPac(); +} + +CFDictionaryRef TestAppleSystemProxySettingsMonitor::getSystemProxySettingsWithoutPac() const { + const void* keys[] = {kCFNetworkProxiesHTTPEnable, kCFNetworkProxiesHTTPProxy, + kCFNetworkProxiesHTTPPort}; + + const void* values[] = { + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &one_), + CFStringCreateWithCString(kCFAllocatorDefault, host_.c_str(), kCFStringEncodingUTF8), + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &port_)}; + + int num_pairs = sizeof(keys) / sizeof(CFStringRef); + + CFDictionaryRef settings_dict = CFDictionaryCreate( + kCFAllocatorDefault, static_cast(keys), static_cast(values), + num_pairs, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + for (int i = 0; i < num_pairs; ++i) { + CFRelease(values[i]); + } + return settings_dict; +} + +CFDictionaryRef TestAppleSystemProxySettingsMonitor::getSystemProxySettingsWithPac() const { + const void* keys[] = {kCFNetworkProxiesProxyAutoConfigEnable, + kCFNetworkProxiesProxyAutoConfigURLString}; + + const void* values[] = { + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &one_), + // The PAC file URL doesn't matter, we don't use it in the tests; we just need it to exist to + // exercise the PAC file URL code path in the tests. + CFStringCreateWithCString(kCFAllocatorDefault, + "http://randomproxysettingsserver.com/random.pac", + kCFStringEncodingUTF8)}; + + int num_pairs = sizeof(keys) / sizeof(CFStringRef); + + CFDictionaryRef settings_dict = CFDictionaryCreate( + kCFAllocatorDefault, static_cast(keys), static_cast(values), + num_pairs, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + for (int i = 0; i < num_pairs; ++i) { + CFRelease(values[i]); + } + return settings_dict; +} + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/common/proxy/test_apple_proxy_settings_monitor.h b/mobile/test/common/proxy/test_apple_proxy_settings_monitor.h new file mode 100644 index 000000000000..1255b6c609df --- /dev/null +++ b/mobile/test/common/proxy/test_apple_proxy_settings_monitor.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "library/common/network/apple_system_proxy_settings_monitor.h" +#include "library/common/network/proxy_settings.h" + +namespace Envoy { +namespace Network { + +class TestAppleSystemProxySettingsMonitor : public Network::AppleSystemProxySettingsMonitor { +public: + TestAppleSystemProxySettingsMonitor( + const std::string& host, const int port, bool use_pac_resolver, + Network::SystemProxySettingsReadCallback proxy_settings_read_callback); + virtual ~TestAppleSystemProxySettingsMonitor() = default; + +protected: + CFDictionaryRef getSystemProxySettings() const override; + // Gets mock system proxy settings without a PAC file. + CFDictionaryRef getSystemProxySettingsWithoutPac() const; + // Gets mock system proxy settings with a PAC file. + CFDictionaryRef getSystemProxySettingsWithPac() const; + + const std::string host_; + const int port_; + const bool use_pac_resolver_; + // Used to pass into the Apple APIs to represent the number 1. We pass in pointers to the Apple + // APIs, so the value needs to outline the passed-in pointer. + const int one_{1}; +}; + +} // namespace Network +} // namespace Envoy diff --git a/mobile/test/objective-c/BUILD b/mobile/test/objective-c/BUILD index e36c5ed326f5..2460ad8698cd 100644 --- a/mobile/test/objective-c/BUILD +++ b/mobile/test/objective-c/BUILD @@ -43,3 +43,17 @@ envoy_objc_library( "//test/common/integration:test_server_interface_lib", ], ) + +envoy_objc_library( + name = "envoy_test_api", + testonly = True, + srcs = [ + "EnvoyTestApi.mm", + ], + hdrs = ["EnvoyTestApi.h"], + module_name = "EnvoyTestApi", + visibility = ["//visibility:public"], + deps = [ + "//test/common/proxy:test_apple_proxy_settings_lib", + ], +) diff --git a/mobile/test/objective-c/EnvoyTestApi.h b/mobile/test/objective-c/EnvoyTestApi.h new file mode 100644 index 000000000000..6f75e418728a --- /dev/null +++ b/mobile/test/objective-c/EnvoyTestApi.h @@ -0,0 +1,13 @@ +#pragma once + +#import + +// Interface for dealing with custom API registration for test classes. +@interface EnvoyTestApi : NSObject + +// Registers a test Proxy Resolver API. ++ (void)registerTestProxyResolver:(NSString *)host + port:(NSInteger)port + usePacResolver:(BOOL)usePacResolver; + +@end diff --git a/mobile/test/objective-c/EnvoyTestApi.mm b/mobile/test/objective-c/EnvoyTestApi.mm new file mode 100644 index 000000000000..3458aa5774c5 --- /dev/null +++ b/mobile/test/objective-c/EnvoyTestApi.mm @@ -0,0 +1,13 @@ +#import "test/objective-c/EnvoyTestApi.h" + +#import "test/common/proxy/test_apple_api_registration.h" + +@implementation EnvoyTestApi + ++ (void)registerTestProxyResolver:(NSString *)host + port:(NSInteger)port + usePacResolver:(BOOL)usePacResolver { + registerTestAppleProxyResolver([host UTF8String], port, usePacResolver); +} + +@end diff --git a/mobile/test/objective-c/EnvoyTestServer.h b/mobile/test/objective-c/EnvoyTestServer.h index 16f0e997c62d..13b0ea1f48bf 100644 --- a/mobile/test/objective-c/EnvoyTestServer.h +++ b/mobile/test/objective-c/EnvoyTestServer.h @@ -13,6 +13,10 @@ + (NSInteger)getEnvoyPort; // Starts a server with HTTP1 and no TLS. + (void)startHttp1PlaintextServer; +// Starts a server as a HTTP proxy. ++ (void)startHttpProxyServer; +// Starts a server as a HTTPS proxy. ++ (void)startHttpsProxyServer; // Shut down and clean up server. + (void)shutdownTestServer; // Add response data to the upstream. diff --git a/mobile/test/objective-c/EnvoyTestServer.mm b/mobile/test/objective-c/EnvoyTestServer.mm index 808863fb67cd..e1d64714fd52 100644 --- a/mobile/test/objective-c/EnvoyTestServer.mm +++ b/mobile/test/objective-c/EnvoyTestServer.mm @@ -11,6 +11,14 @@ + (void)startHttp1PlaintextServer { start_server(Envoy::TestServerType::HTTP1_WITHOUT_TLS); } ++ (void)startHttpProxyServer { + start_server(Envoy::TestServerType::HTTP_PROXY); +} + ++ (void)startHttpsProxyServer { + start_server(Envoy::TestServerType::HTTPS_PROXY); +} + + (void)shutdownTestServer { shutdown_server(); } diff --git a/mobile/test/swift/integration/BUILD b/mobile/test/swift/integration/BUILD index 16d243f8c4d2..3fb55499ee22 100644 --- a/mobile/test/swift/integration/BUILD +++ b/mobile/test/swift/integration/BUILD @@ -227,6 +227,7 @@ objc_library( "TestExtensions.h", ], module_name = "TestExtensions", + visibility = ["//visibility:public"], deps = [ "@envoy_build_config//:test_extensions_no_autoregister", ], diff --git a/mobile/test/swift/integration/proxying/BUILD b/mobile/test/swift/integration/proxying/BUILD new file mode 100644 index 000000000000..d15f5a6f6526 --- /dev/null +++ b/mobile/test/swift/integration/proxying/BUILD @@ -0,0 +1,24 @@ +load("@envoy//bazel:envoy_build_system.bzl", "envoy_mobile_package") +load("@envoy_mobile//bazel:apple.bzl", "envoy_mobile_swift_test") + +licenses(["notice"]) # Apache 2 + +envoy_mobile_package() + +envoy_mobile_swift_test( + name = "http_request_using_proxy_test", + srcs = [ + "HTTPRequestUsingProxyTest.swift", + ], + # The test starts an Envoy test server, which requires the `no-remote-exec` tag. + tags = [ + "no-remote-exec", + ], + visibility = ["//visibility:public"], + deps = [ + "//library/objective-c:envoy_engine_objc_lib", + "//test/objective-c:envoy_test_api", + "//test/objective-c:envoy_test_server", + "//test/swift/integration:test_extensions", + ], +) diff --git a/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift b/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift new file mode 100644 index 000000000000..424159b9e6e4 --- /dev/null +++ b/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift @@ -0,0 +1,220 @@ +import Envoy +import EnvoyEngine +import EnvoyTestApi +import EnvoyTestServer +import Foundation +import TestExtensions +import XCTest + +final class HTTPRequestUsingProxyTest: XCTestCase { + override static func setUp() { + super.setUp() + register_test_extensions() + } + + func testHTTPRequestUsingProxy() throws { + EnvoyTestServer.startHttpProxyServer() + let port = EnvoyTestServer.getEnvoyPort() + + let engineExpectation = self.expectation(description: "Run started engine") + let responseHeadersExpectation = + self.expectation(description: "Successful response headers received") + let responseTrailersExpectation = + self.expectation(description: "Successful response trailers received") + + let engine = EngineBuilder() + .addLogLevel(.trace) + .setOnEngineRunning { + engineExpectation.fulfill() + } + .respectSystemProxySettings(true) + .build() + + EnvoyTestApi.registerTestProxyResolver("127.0.0.1", port: port, usePacResolver: false) + + XCTAssertEqual(XCTWaiter.wait(for: [engineExpectation], timeout: 5), .completed) + + let requestHeaders = RequestHeadersBuilder(method: .get, scheme: "http", + authority: "neverssl.com", path: "/") + .build() + + var responseBuffer = Data() + engine.streamClient() + .newStreamPrototype() + .setOnResponseHeaders { responseHeaders, _, _ in + XCTAssertEqual(200, responseHeaders.httpStatus) + responseHeadersExpectation.fulfill() + } + .setOnResponseData { data, _, _ in + responseBuffer.append(contentsOf: data) + } + .setOnResponseTrailers { _, _ in + responseTrailersExpectation.fulfill() + } + .start() + .sendHeaders(requestHeaders, endStream: true) + + let expectations = [responseHeadersExpectation, responseTrailersExpectation] + XCTAssertEqual(XCTWaiter.wait(for: expectations, timeout: 10), .completed) + + if let responseBody = String(data: responseBuffer, encoding: .utf8) { + XCTAssertGreaterThanOrEqual(responseBody.utf8.count, 3900) + } + + engine.terminate() + EnvoyTestServer.shutdownTestServer() + } + + func testHTTPSRequestUsingProxy() throws { + EnvoyTestServer.startHttpsProxyServer() + let port = EnvoyTestServer.getEnvoyPort() + + let engineExpectation = self.expectation(description: "Run started engine") + let responseHeadersExpectation = + self.expectation(description: "Successful response headers received") + let responseBodyExpectation = + self.expectation(description: "Successful response trailers received") + + let engine = EngineBuilder() + .addLogLevel(.debug) + .setOnEngineRunning { + engineExpectation.fulfill() + } + .respectSystemProxySettings(true) + .build() + + EnvoyTestApi.registerTestProxyResolver("127.0.0.1", port: port, usePacResolver: false) + + XCTAssertEqual(XCTWaiter.wait(for: [engineExpectation], timeout: 5), .completed) + + let requestHeaders = RequestHeadersBuilder(method: .get, scheme: "https", + authority: "cloud.google.com", path: "/") + .build() + + var responseBuffer = Data() + engine.streamClient() + .newStreamPrototype() + .setOnResponseHeaders { responseHeaders, _, _ in + XCTAssertEqual(200, responseHeaders.httpStatus) + responseHeadersExpectation.fulfill() + } + .setOnResponseData { data, endStream, _ in + responseBuffer.append(contentsOf: data) + if endStream { + responseBodyExpectation.fulfill() + } + } + .setOnResponseTrailers { _, _ in + } + .start() + .sendHeaders(requestHeaders, endStream: true) + + let expectations = [responseHeadersExpectation, responseBodyExpectation] + XCTAssertEqual(XCTWaiter.wait(for: expectations, timeout: 10), .completed) + + if let responseBody = String(data: responseBuffer, encoding: .utf8) { + XCTAssertGreaterThanOrEqual(responseBody.utf8.count, 3900) + } + + engine.terminate() + EnvoyTestServer.shutdownTestServer() + } + + func testHTTPSRequestUsingPacFileUrlResolver() throws { + EnvoyTestServer.startHttpsProxyServer() + let port = EnvoyTestServer.getEnvoyPort() + + let engineExpectation = self.expectation(description: "Run started engine") + let responseHeadersExpectation = + self.expectation(description: "Successful response headers received") + let responseBodyExpectation = + self.expectation(description: "Successful response trailers received") + + let engine = EngineBuilder() + .addLogLevel(.debug) + .setOnEngineRunning { + engineExpectation.fulfill() + } + .respectSystemProxySettings(true) + .build() + + EnvoyTestApi.registerTestProxyResolver("127.0.0.1", port: port, usePacResolver: true) + + XCTAssertEqual(XCTWaiter.wait(for: [engineExpectation], timeout: 5), .completed) + + let requestHeaders = RequestHeadersBuilder(method: .get, scheme: "https", + authority: "cloud.google.com", path: "/") + .build() + + var responseBuffer = Data() + engine.streamClient() + .newStreamPrototype() + .setOnResponseHeaders { responseHeaders, _, _ in + XCTAssertEqual(200, responseHeaders.httpStatus) + responseHeadersExpectation.fulfill() + } + .setOnResponseData { data, endStream, _ in + responseBuffer.append(contentsOf: data) + if endStream { + responseBodyExpectation.fulfill() + } + } + .setOnResponseTrailers { _, _ in + } + .start() + .sendHeaders(requestHeaders, endStream: true) + + let expectations = [responseHeadersExpectation, responseBodyExpectation] + XCTAssertEqual(XCTWaiter.wait(for: expectations, timeout: 10), .completed) + + if let responseBody = String(data: responseBuffer, encoding: .utf8) { + XCTAssertGreaterThanOrEqual(responseBody.utf8.count, 3900) + } + + engine.terminate() + EnvoyTestServer.shutdownTestServer() + } + + func testHTTPRequestUsingProxyCancelStream() throws { + EnvoyTestServer.startHttpProxyServer() + let port = EnvoyTestServer.getEnvoyPort() + + let engineExpectation = self.expectation(description: "Run started engine") + + let engine = EngineBuilder() + .addLogLevel(.trace) + .setOnEngineRunning { + engineExpectation.fulfill() + } + .respectSystemProxySettings(true) + .build() + + EnvoyTestApi.registerTestProxyResolver("127.0.0.1", port: port, usePacResolver: false) + + XCTAssertEqual(XCTWaiter.wait(for: [engineExpectation], timeout: 5), .completed) + + let requestHeaders = RequestHeadersBuilder(method: .get, scheme: "http", + authority: "neverssl.com", path: "/") + .build() + + let cancelExpectation = self.expectation(description: "Stream run with expected cancellation") + + engine.streamClient() + .newStreamPrototype() + .setOnCancel { _ in + // Handle stream cancellation, which is expected since we immediately + // cancel the stream after sending headers on it. + cancelExpectation.fulfill() + } + .start() + .sendHeaders(requestHeaders, endStream: true) + .cancel() + + XCTAssertEqual(XCTWaiter.wait(for: [cancelExpectation], timeout: 10), .completed) + + engine.terminate() + EnvoyTestServer.shutdownTestServer() + } + + // TODO(abeyad): Add test for proxy system settings updated. +} From 0b513f31ff3d45b6a66de830603741a09245db8d Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Wed, 14 Feb 2024 23:30:19 -0600 Subject: [PATCH 011/151] mobile: Fix possible inequality bug (#32411) mobile: Fix potential inequality bug Signed-off-by: Fredy Wijaya --- .../io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java b/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java index 6aff90ddd823..96102da6debc 100644 --- a/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java +++ b/mobile/library/java/io/envoyproxy/envoymobile/engine/AndroidProxyMonitor.java @@ -81,7 +81,7 @@ private ProxyInfo extractProxyInfo(final Intent intent) { // proxy is configured. // // See https://github.com/envoyproxy/envoy-mobile/issues/2531 for more details. - if (info.getPacFileUrl() != null && info.getPacFileUrl() != Uri.EMPTY) { + if (!Uri.EMPTY.equals(info.getPacFileUrl())) { if (intent == null) { // PAC proxies are supported only when Intent is present return null; From 7e621bc6cb156b892f827ce4553c8006fb863060 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 15 Feb 2024 08:06:21 +0000 Subject: [PATCH 012/151] repo: Sync version histories (#32324) Signed-off-by: Ryan Northey --- changelogs/1.26.7.yaml | 28 +++++++++++++++ changelogs/1.27.3.yaml | 52 ++++++++++++++++++++++++++++ changelogs/1.28.1.yaml | 53 +++++++++++++++++++++++++++++ changelogs/1.29.1.yaml | 35 +++++++++++++++++++ docs/inventories/v1.26/objects.inv | Bin 153917 -> 153965 bytes docs/inventories/v1.27/objects.inv | Bin 159876 -> 159932 bytes docs/inventories/v1.28/objects.inv | Bin 164171 -> 164311 bytes docs/inventories/v1.29/objects.inv | Bin 0 -> 167998 bytes docs/versions.yaml | 7 ++-- 9 files changed, 172 insertions(+), 3 deletions(-) create mode 100644 changelogs/1.26.7.yaml create mode 100644 changelogs/1.27.3.yaml create mode 100644 changelogs/1.28.1.yaml create mode 100644 changelogs/1.29.1.yaml create mode 100644 docs/inventories/v1.29/objects.inv diff --git a/changelogs/1.26.7.yaml b/changelogs/1.26.7.yaml new file mode 100644 index 000000000000..48e27387880c --- /dev/null +++ b/changelogs/1.26.7.yaml @@ -0,0 +1,28 @@ +date: February 9, 2024 + +bug_fixes: +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. +- area: http + change: | + Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. +- area: proxy protocol + change: | + fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. diff --git a/changelogs/1.27.3.yaml b/changelogs/1.27.3.yaml new file mode 100644 index 000000000000..a67d0b6cabb2 --- /dev/null +++ b/changelogs/1.27.3.yaml @@ -0,0 +1,52 @@ +date: February 9, 2024 + +minor_behavior_changes: +- area: access_log + change: | + When emitting grpc logs, only downstream filter state was used. Now, both downstream and upstream filter states will be tried + to find the keys configured in filter_state_objects_to_log. + +bug_fixes: +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. +- area: http + change: | + Fixed recursion when HTTP connection is disconnected due to a high number of premature resets. +- area: grpc + change: | + Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug where Datadog spans tagged as errors would not have the appropriate error property set. +- area: tracing + change: | + Fixed a bug where child spans produced by the Datadog tracer would have an incorrect operation name. +- area: tracing + change: | + Fixed a bug that caused the Datadog tracing extension to drop traces that + should be kept on account of an extracted sampling decision. +- area: proxy protocol + change: | + fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: tls + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter + ``%DOWNSTREAM_PEER_IP_SAN%``. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. diff --git a/changelogs/1.28.1.yaml b/changelogs/1.28.1.yaml new file mode 100644 index 000000000000..626bbf687b29 --- /dev/null +++ b/changelogs/1.28.1.yaml @@ -0,0 +1,53 @@ +date: February 9, 2024 + +behavior_changes: +- area: listener + change: | + undeprecated runtime key ``overload.global_downstream_max_connections`` until :ref:`downstream connections monitor + ` extension becomes stable. + +bug_fixes: +- area: buffer + change: | + Fixed a bug (https://github.com/envoyproxy/envoy/issues/28760) that the internal listener causes an undefined + behavior due to the unintended release of the buffer memory. +- area: grpc + change: | + Fixed a bug in gRPC async client cache which intermittently causes CPU spikes due to busy loop in timer expiration. +- area: tracing + change: | + Fixed a bug where Datadog spans tagged as errors would not have the appropriate error property set. +- area: tracing + change: | + Fixed a bug where child spans produced by the Datadog tracer would have an incorrect operation name. +- area: tracing + change: | + Fixed a bug that caused the Datadog tracing extension to drop traces that + should be kept on account of an extracted sampling decision. +- area: quic + change: | + Fixed a bug in QUIC and HCM interaction which could cause use-after-free during asynchronous certificates retrieval. + The fix is guarded by runtime ``envoy.reloadable_features.quic_fix_filter_manager_uaf``. +- area: proxy protocol + change: | + Fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: tls + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter + ``%DOWNSTREAM_PEER_IP_SAN%``. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. diff --git a/changelogs/1.29.1.yaml b/changelogs/1.29.1.yaml new file mode 100644 index 000000000000..31831f89848b --- /dev/null +++ b/changelogs/1.29.1.yaml @@ -0,0 +1,35 @@ +date: February 9, 2024 + +bug_fixes: +- area: tracing + change: | + Added support for configuring resource detectors on the OpenTelemetry tracer. +- area: proxy protocol + change: | + Fixed a crash when Envoy is configured for PROXY protocol on both a listener and cluster, and the listener receives + a PROXY protocol header with address type LOCAL (typically used for health checks). +- area: url matching + change: | + Fixed excessive CPU utilization when using regex URL template matcher. +- area: http + change: | + Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: proxy_protocol + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in a proxy protocol header. Connections will instead be dropped/reset. +- area: proxy_protocol + change: | + Fixed a bug where TLVs with non utf8 characters were inserted as protobuf values into filter metadata circumventing + ext_authz checks when ``failure_mode_allow`` is set to ``true``. +- area: tls + change: | + Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is + received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter + ``%DOWNSTREAM_PEER_IP_SAN%``. + +removed_config_or_runtime: +- area: postgres proxy + change: | + Fix a race condition that may result from upstream servers refusing to switch to TLS/SSL. + This fix first appeared in ``v1.29.0`` release. diff --git a/docs/inventories/v1.26/objects.inv b/docs/inventories/v1.26/objects.inv index 477894d1f6bccd82887a88b242a0da1ebc5bf066..26b06951df94722c60592253d264b2378a4075bd 100644 GIT binary patch delta 8466 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 delta 8413 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 diff --git a/docs/inventories/v1.27/objects.inv b/docs/inventories/v1.27/objects.inv index 0b0acf5c7b4287c47fed7f8d5f931d7530922948..4eee783c0fd4c10b7ce5d67cf5f70f67cbc2a93a 100644 GIT binary patch delta 19241 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 delta 19228 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 diff --git a/docs/inventories/v1.28/objects.inv b/docs/inventories/v1.28/objects.inv index c454862b2315130bb7c402d16f03517eb4ba674f..19b5b89f7d8eea42888ffe2570388818e9bd2220 100644 GIT binary patch delta 19774 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

W|Ps~4rYt-6BrvgK|FKT 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 Cv}UjX diff --git a/docs/inventories/v1.29/objects.inv b/docs/inventories/v1.29/objects.inv new file mode 100644 index 0000000000000000000000000000000000000000..70feb4b0ae53595ba3d2aec2a78939b1eac7ac07 GIT binary patch literal 167998 zcmV)EK)}BvAX9K?X>NERX>N99Zgg*Qc_4OWa&u{KZXhxWBOp+6Z)#;@bUGkqZgy{Z z3L_v^WpZ_Ab7^j8AbM@L9^4{RyA{&+Nv5` z8*7_0ZAwX5rEX=WN};Oi(>F9mkP5Mr5sV}mQYzQhY+qwvZ=Yo04hIN=_+><>&qbz4 z5dQh+2oS`t`&shX_q#u@%Rc!&X?A6s_f@m)&hlbYZQnfhoBH3_vouZ8Z_ZX_+joKa zd0r5z_g!DM!Ez$IBKntyrrqTIfBds+wvjL+QGjf=57jy=_M2TOk|Hq%_)E2AYKk%| zi!L5)B&`8&^6t}r{9pg~3{aHR;kNmv%-V8YewhcV$m`H;*){uiRn7yJ(hk@^%`Xgz z@+Y-e`f>}R;dnv-#CQ8$+dNct*>d?^)7b&wAwy7P7(~(L)i$duRz0`!wFrRG<)%=7 zS-1JBeAw4>F$Vw#_@BB@)h;W)RQ&AcxQ3;Q9%9la07F(i;hYG*MqtlSp6rrP#^ctzJ1tHk5u z*b<^R)fG?Zi+TqrH*{^Ge`9?A=h6>Roa%}v^xJ0NmjH1G7Z&39>^Q95`qGg;JK4pG zH3$XIX5p-6IWBXT^3jg(8|n8DzDs`j~V;-|CfHv!OwOt2S7?GceZpuy5z627W%uMX_zUsxlV6`J5 z2Eu;WB2+cci$W|H>jF0G@UFEggAiTWXDilm>|;9Univ47ZMNTpp|PdQY||9g!^>Yj zl`r4_$5USK%kTfhu7BXyKfv_|e*GbwWjr(^1dVXnX4Uqg3A9F)0DwLZo;u)ph0qym zx;AD1Sni|bQ$6WE^~ zSu5YL5%eZo?pL2!SLZ)g`^~=2!JxUC+@OIpin3>YrLs682?-fWq3Zq#W3yvY5RgU+ z`>s?bC}t>>06P4WVCVM?$0|?bQXG#NWk2_9TlUXQ`zhm}3itq5 zI40=oEZ_H!l&~r>*Rxro$l-KE|D_4>DRv6Hs)RI(87skPx5CFMaZ{hb4&1vzn%{Oyf zW;YgStD6-wOMG~$-ArSwFkHtvjOx$2KoAKh478-F9pU9)9YmsSzC@xRzeRK@zrIukRoP!zEm7%9!ENUEfQG^0wZl8A(}Zhaoxun%&8!nzQ$ zqRi__nh*eaUQ?&EY>{7-KY&afFO$&@tTPxd@=0gFC>r)D`aYRHMTq2oZJVU8`nvqz z13y;U5mK`2RZQ z_$e6zmt$FWfdle42yF!!XX9uWETMX+Rym9ozr_dNO~GmO9e_W|Nh-wH-3mYKHVe?8 zXC@_>dl(vyfuu{3S>DQ%YM}r`3j9I}L!EyX5IdXz!A?d?;bOUkb5h)ATS-+znR-7(*@&`&Qfv zJkd-H`!A-5oI7noWI+&2XQ}mkhV|4BbR;@p>e=(!Z52XZV-_H7)<2eGHOaylIGBxyk9-? zACN8sIoSlM3T~lXi{w3jgna}zk3An7~p_``oG&qITp-eI)Pg$@I z_wvA3IvgbQ)h0mxH$XH0D{V%p(oytWs(sn@&1Sd=P^62HO1yi&Om=Pc1pR&17{G+U zQ&m`vD{B1b*GU<r1b%H35qb|raze>ojkq^0 zE4Qq5D`V8Ea=LE5-;F=B5cr(;@XxQ2GEV26jJdmN2mk!Al##}O*SAm&zPwCOTl`GW z;)TE6G-T%*K2Yz09y!`Z4zbKvpPGk

rMP)cfQ~iCkdxDeTH{Z6fceyY1>@3sGwS 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

+Dcpt3*b^a&Z^y-NMMe1 zz*d6^$W)CF99ET1W?XDWr&>+gAww#W@c62@1D$8=pY|d_63yb@UW+_gx8K2RTUm(K zrZb2catY^Rl#T;4@eILNI8@IbO7;b7Cyj*unBL)%y_SF5HqToz(tk(Kzj1I?i_pau zOHe*bC5GLIjQw(I)PYQi)!_Q|Y%CW(j~UE_ zJXPg0z4IBq^gLY6m}|ven&xaIbJi?jQXyYfHJtVu5Z2J;=HyuZROF`yAjKAdbf~XC zPqOo3eUYju*k|mN#H5m2aWSk_I=JCB*pm|D8gOeZ1h5fKm_yi|^ruvRL&$AsVR{9Q z%!TUwn31`@7f;~6su+b9&br4OM~7aHLi`NRn37nwEc@q@^@^w}8_AL&y#XoeO@-dk z%n>E(#Kmf=2-WH*D-TdiWQ`0(rc@}93%J59uTDuUpLY=jHwYO?43KhhUjGS~`L=kj z3f45z`hvG&vCdm7zaD3ryrp)@Tq)P`R@^5-N~5)^+SQ)*{+F!CKSlgFBRLBh%9C>J z3x1af2^SIZi?fczmuj=$=$op#Q!*7bDJA9UpgiTu)0y1=RkRI^B}dqn6Z2MTl15sR z2nQGLvFYAImtA=`9E=hvy543ZoXoJdd)cy+r3{?CeSEZC+4m%dq z-@=J&dT1Gd48d!UU8D?|2%)n}bKwb3nftIdLe{vkpiR934HWL=txluOt8LJ$+3ZTb zS-3kBLPq5R@RBZcg%nWNG^OTKI})X>%SH}7qnLsFa;cvXgOR<+?c|rOitaTyrl9BI za^WUZly#rK3LUQL6930dgI|2y%2z5V&~xP@Q@;`!&~xJ>)4mcJ&~xV_)4diORJ5@0 zX+k?};?xE4Xsn7G^cJ#dF5hbUVE-xX%KjSB4U;mCz8K4sa$LD@b+p-gN`r5GEy`UN zM`e(+@W2cy0!uFJuG|*ob~Wvo(!9v(k+sj=u|It@`9a_`>%s3d8sZF)j2sZ&V~}pz z#f6Cp;inQ`oG5xv2|Ay;Swj3Nx&1PFP%%NUF~fW*eUw}#bW{v z9&zkEnk6{rtDeg46nqr)kMhz^rrp=4;lY9L;D5VzT;c4qO~XFtry#-cQ;vU+HwoV8 zl-k`YzQqSABLP5zPKiNFwrs0n9oCH=3ohw+0VXXMoM zset{t=@YuvmG}MbN55L>E#XI_Dg;5FXg}IU|ClEmd@mnGXR6?l`XR+b0G2bs!AXbb zfsaim9itPuQKFCEa`$j_R)G2T_Fb~?@^$GWfQQG9PJyFdsuh7H7`f0FQ#!6Y{&yG2 zQHE5iNsVm6S%j3xemAbS;@+T|&!cbhZG>iZa(g)SZllAe(LnWTR&F)@Yl z(6lh1;a>S-SfYTHm6-S-_>{vVMS7IbqXZwBC^*4l0nCyfx85PwE*DPjW&^s3sL^?zoqI9us9WQ*5Nj zDnMjILn3xM1S!3!SQ;04l<`3D&@tVyPHK4qDk)cyN)?wp_Pn_7T=X8xz)6gFS^`=G z@uqth8*%LD1Z)KK9sPb!Ri4nqtqdvaq9!5|oPwLgz7-b%6^jBH9G?vbzN0t^{Md)H z4)Nx_Td}d&N%&yf&f)2s3D@NX7Ln`N!q=ZbGbw zTCt{zm0j1aux7mi#Z)LwnNqB8$wx{_7ThI2eskWLrgfYKh}E=LH$p1b4UyhmjK=)) znJ}QiQj)hc~C*`<*U3~nI{9Z2Uq}jKB{r&zsTvUY#sg>z%(pMypy_zZV$ib8> zBJ+T1?42!=h?7C5I&m}W%=*>t_?)y^?)K|v!YGI-d5j*sTa%rW4c^#qNQK=4yC zEGr0?X`P2<)zPVdBCD6LQGBVQGG!_+hg#HuVATss}TtFtb@vx+GlyPp)q`WvnzGhR&UVE2{#q z_)bA@Uz>S~+EbzZci9R;aze==R&4S$t4FU+pVYp`KOsEjpsiqrv5Uy<`g%hr(Sq7J z&RGuMR0?wRrX5C7LIj?o!obJ;v7 z2aZDurOmfRvw8K`k<{MdpCFz%KMqcuUY#mVpM&N*=pDgT5aDLOE!h9dYWw<>L3he^ z%a$4w6HIjT|JrQJEPu}1GJMI#X25_IKprxum{~6D{DUPWm}HrS_i|@ls8SxF?EYnz zq+=SX$&85)?^w1|C*U@!GN;&K%13Ah@8IBcc|vw|*p7J}z&e0gjrYsrAk4X4UMwDE z!t>3{lLeV@lcP}*&r`q`CknVL9V?Fv0wVFRO|{vv!PTjWrBY!^3YEF)7d~1pTtrodD+pn;fx$2=IioQG)YscXnIHcnr~&2mv7i%ig^reM=Pc*;xt}1dE#v9W(7&4;|d`rGev#1NrIt^iBqBtrHGG zMh7%PH`>MQI)^QG!nMfgtOhB&g}rLR>q&`D5!5~N<{fxDHu77yb57X^Nh4K(dP^6Y zR&dX8JZ~O|cGuKp@)q~)k#8&XV%XFM)!`d)VkpQxIPdviU2$1dGm06-j4BcD4*oZ3 zlJyeey|O`J==Sb7{jx%m7TWHeIvJ6#z>{$i_*gd5`;Pfo6)_t*H8?u25&DH=SD%wOYLvd(yVRKfw&!YSq7H?cf@*axC!DR5!Yq^ZG2ekwAx` z-lx#GB`^3OY=9cvzb8(;jyLR?&$|MNRSA*b%eoAi2MZwqfGGh|180m4Z{ot^Q9QD) zVE34RI##i8*i?ZfUP_8|R&JnGZu2D`Eo6z@j5v3K4r&5A9YJ4FYO+!aPR(H;`A|hU zv~h4kq<7c1i{$(N@qeU07<4tO4ufOt>Y%xY5TyW_O305Fi(6$IEl$YluWn3vWkhW~MsdB{JXjSFk^+H#=r z#E@)3er}$VqDii0X_pB-`iqqT>-eB$d=;ddlF`=Qry_1*Jaa1XB}eFh)ku(M5zb?} zV6J%5uR1;FROq?mf6e@STQ#OX>{P-*%djEC@N<8i?9sr|L{HgGo7#FUV_uU}=4S)M zXeRp)Y9Gb4a52U;_^MnbKb$@<&et-V$hx7MWi`(;yt3Utk>OcS6cYpqei}!H$wJJ_WdhmwM zwEXen?tZlH3U~UBKwgNdcQ7}GzY!G{nM_5#X^`%ddkY}=_-ce8wynN*`}uyRQ~bu@I~MonclTkI@KeVSl2L4{Vac67 zT0D(GQn%b_L%LPF?(fb&EN(wCyWX)^;McpWj3qBVesGy*Twpz>4MM>mFMqwmH`K+3 zoLQZ``4(=ihUWY7KUsTyeKD{3E+KB}bXoR@gh4}!8vc0o{`x+3g=RYm}n4IZJtfThf zCkJnAkWc%%pbC0lu!Z-%Fx14Ku=7O?Idr_JxpsECcnJ9Jlyne(H1crtO60WK^epTV zS;>hx)bht)Z>gw${J!wN*ZkoQ#@}hvbnbRA<^`%1J!2a&){m1*O?ELbQHL;5j>aX5Ol}6aR zsM4I64ys{qWFTvlw6W%}li9T_-|cEz2Q~zsb!r|`JU=rZeW5VO=AkvSkAeu~z?xNqz3m3$lQ3OuK6<5sl5CgDw{upNioCe6h`Wq83 z^J$}EBM)Z+@~_m;zySbJpK%F6cumID&u-ET+3YZG`;9bVv<=u}gtjj@ke=_L*69fc zo#_fR23L`cD^9Oq2d+vP4hU-wc|4}PX1%w4S)JT6_BOLVOIf}q$&@KgQR#V&ej~x1 zCfv?^Ik_cA{Sw7mNxwxq>$`hL+=H^iG`)IBt^~YfM0M8f}p{Q6uNCy6S8By042kc_ZhxI0bR~=1%yvUa;hN`-B&d*RFZe zi3e0Z>Br;1`p4ZvzP2e`EB8q^Vvp(3Moheio|r!Q6^-Y~KK<;K%a;3MUB2#@J0;zz z(0$ME6Mi3WO8v_8nZEm*g@flQJ451(KTmwezi;VoI0QJ6o2HBj z@IQ(kzM)8*HNar=11GUVfZo)sa5os5uN0PBS1#862`~k~OZHER;4Yli3L3?Jxopf^ zMorq4_`9m$_-;fCpU6$4mvAR{ikdiZukh`BDqMcFHOxdMB|YdFUKYo?Y%1J1{8SY{ zhVOoh+n>fU4EpgwD#23%Pi@LK`Ik-Qp3FW#yFmY`Y`~um){a~8R8oQvmjCR)%>-S#asCrl^o+k32D*fNsZh=%pQM9w2+@LD5Bnw&nRq;u zWXJ^JT7*IB%uHxwqsi#^BhKpl3q}BZK!d+qQs!Qia@8PAr|JXAD2KMq6hF1(O0)&3g|4@ls@TZayhBL^SjDI&M&qja7nlUOEN8vHS@YKp4tzp(Gb zMoACVtw`GlKtsbi$(Tn;2Ny=HlZ?s9&oExQfaEbn@$KYMr`)X}r=?3wQw%)ChghoN zWk$2hGTF^7TQ}^JvYtVa3kL8?NySh6Pb|TDpU+^&q(Z0x_znB#A!Uq(XKN@$?(v<0 zjXVCGnL@}`4FeV9b`LuxxkW>c3on>la>-Mj#JD>emV5O&6uCzl)Q7%j@TzbTC7+bQyKEg#WQIfKm$ZZfT8=Ey)@-3Edqq=FpTiD#?lyuSWjS5>3w-+PCeyM0 z&gOIlNbrB=e?3%K37)W61U&xnFnRu;r=tPb&;Qd+VBf(a?0Vw*=ZHKM6@T94{iB=E zmo(pe<>v{@v?@-)YS9H38dlI#GZRYG8y`11iu?*MuC8g4;-dW_w_4Tx5r@9Pj zp@gyij-ryDp7>O!<5{R7uTV=Ss-{Ukw!G^FzIiZ;X234j136n1+8%`Qe`s1}E!uo? z>Z&e7S{Pvww)Es|r6N5cjOXAd4aQKO-Tp@*Jk@A@;xmE$D$jXal!a(KVO#hK4KVAo zq=%D#XxdjH4SKZuD3U|SU;azI7U>}dFzHR>X!k7)mWzzMkDR^<9J43&qHYNF5k{Q& zz0?%x3E@1aPK8gz6BX$R5j=63?Nx}Prb&vAYSUw9TKJJVAyr+|B*jNn%)gzqAEYQz zZ_L~%6id0QW4o4$q=Od4a6xvQn5$gf3+m{@irVHZ)zn4(6ymKr&K zaqB0{y1Ngol(UYFkNQ5I^F%(7Hp!)f-g};sEFP-|NH|-FM>DL*x<}8Jfm7O&a_z#R zjx0)+W&d23TXexDwk&kBtb?n6opq>PS|@+OeAPCr)3e#veYLAguZjK>Fs7HpaP&eW zZ$9Z_R4)39=wLQ_iZ7Up=obn#!cW~;yPGM6!ag-qih->&9cy=<2+K4LSL0##b@i7Eqjm;*=Dpq&OwT zHtT$h53~2=wasU@tcP>$538{}u4g8u2~TD&*CVGR<=ds{H#22}rD9+ID$WGRup6*H=%l=WV;%x8SbyC60&*!L%D&<&_GdR8@OUiE!RPBw9#s zst#ftpd<0Nsbd*y;sJC?A{7eHMT(5CW+eLDWp%z;7I`E?Dd(s5+@+-A{P#t2gO^q^ zVp~{Ot+&CSQI(Yj5JXQ*6$z!lQylwS@H8XgCHv++R|V@i7@`6D$ZEn~qB1{)1b!>- zc|_n4f^p-eBN25DrUs%N8&^V9l~`^@ z4*eGY7?p?Z(jWVa1`)4n4vc&8g+G6z{sLz7nq4FXlJc`CU=EMLUtE4jZ{UJY-d5dWl!fmuS_O=V41bd1<-Yv{ZyY?Yc zDU1?5HPeD?Q6BV64nh}#p zhcMGl(ND;*cVQLJ0;GaSAiS(;x9pC!8nyuej}uXC|5>i$e>Ms-;o-zXMGPw5VXTO2 zaUVj9KNLrpPEL_OBy}_x#i>gk=3+4Asmj$8OK z-hOe)m_!j6&9dbPvGBGx;B5c5)FIa99Sr;A%XjHjc=f{q4`cM4=pg5t-Z$xc@xaj@s;)btIZMe9w$lB_$p49v6l?eU9348xQ5xjDgsNRCRIrc7 zeLiv@JtD(ffVsb{F6|}8+m6Yv`d3H}{7wJ)+|TI+NHIUh=bm~2fBpSl>jfAYeX8nO zEHlKBQYF2-AhTGRNdCrd#FB)U)cpSaY=WacJ_gk;N1XM(z0n5fYV7ma544c-@|Ey6 zbYUjJx1oX~s2Phoy0o7Z-_HuJpSU{O#|mzq?CUxcHG?mx?v)9rs_I|Sm&ZUpQ6Iv& zl3KIQ)=o`m%^Kz%Zd}Nauii&CqfEfhgnQkwDHNlx=QiK%V&C#W{WM%lg$_PcOU`ev z16gJHt^((2+47|?yVu~DGL7IdEhUX*{}wL3{m#l**$;NovQ(TG3O$O`!e$A>cxTa4 zuj-^EsT(GAP-iiKoa_*sl%CKc3CjLM0L8AWv#wcvDq(i!p*amhrX=M`QdF{7+^Alw zLTJX1mx9STCQ`X-9xe$ZMZ>#$DjGaY5~_h07S|Z-l6?}&FX*%&oFVKRDF4s@K^r$; ziEql4(st8?-(BI2qoJ8{hYt$-O*T7<>-AdnrCy5nmB<7h@XK!m)NM5gW>K}Uh8rFs z>0on!f$AuTGtgru4H+)+`5it!=TGC!{1@71HEfni+7j4lTkVlW+ESQo$!Qo-77O$w z^daArfyTTVccEy3X$iLPo(8>%-_V32KX}P;X)2Unh?L}fHFEuJ;|&{)+X`G)9<&HQ zkH1hJx*o4+`Mhrq;E2*OOu0I&4mVgce!+xqe{b4P>Gd|fZJRYT_uu(=8;brlO4?B9 zsx==e&B~spG+wig&{RfX%~wHPQzM(NerUhS~~(SGUYaYqmIKwC^kOP8(e0UOndGrGwtuj|f5ye<3oCEGQ1wR$0E zKJw)|?eBn^Q>*4liqi@@@wdsr;mj5zTWp%>rZtW~o&HmFrl?aC{u`amoBt*J!?M@1 z{NFBvX3ROnu@BmtLXc0vky#F9^)!ytB92Aru~keYg)6GO@5{{&`bPWhN#8}YLSt$O zZ`cgWm^E!GgTcH8dy`tnvk*(lFFQ5@D~q)KEQ1woy4NtqqS^g8#H4D&z6cfcq11(D z450iPcDd9C$E)xF<_H_!OT_pDpgHYXGjZSEXTg#{uB=5JXUB- z4dTrP3lOUgPUV-c(!>rLvF9^Q4x#-@-5QfRLs}4Hs7vUrg;s%}+wdy2+oIY1IL;*a z!tXnD4)Dq*jWoL-hnn=lH=4c9Cwr{Wm>SOeH7QTnh7lG0X$%JpzYbQn*Pw0D?0y_& z66=Y;8aWik>vokq48pR-nkporyyhK(JKR_!htQ034SxOK)bm@;{0&9$kNAB*2U;{z z?0gz$4=q&^@7MmF4C({&7%hZ7J^UI+>5;#VE^RjjcJlD+l4Jo0N26*%8ifD{t9EAxEV_7d9+3s;wzlw=$h~&32l6%g(NN*G% z;U$8wzq9|O(iheN>Q?S}idwv6DKpk1k$x6eLW%U#9^(2;Q#O$uDV3hsCGxe1KX5}S z6B6>b!(&B0AK~-p$RB*rA&JRl4Ean@l#a{-{9uW^-I*>mNf=R-Ud)6JQBJyZEFFc{ z&uR-{IeWJU1qZh=h?5zwxr0Ld1Ye6=E6tdF^{zq;*wQ^=9K6Zi%i6yxacb_?#gqwm zxi_Zh=9ab!5yV!F%`t4Txhxse?PyNl&epRBD1@zLGmxFFV?)q{DEH$bg0s)0ItFpG z#Js6gc#9rEZt2!av#+~@TZIvZn+V=884}bhugZOK zEGq%&*Grd`k)HyywxQNe@w2)J)lpc`Q-F~5!w{F?M)CJKC=NJrH7GavFCGY3PEq0c zn@KS9$b&BaxaN5LW)jvs^sLLa?4O(VQ(WIq6VmTT^yr!F_Y6Xp@0 zk`vIjeS#*ehZAlQ9q$QY4JC;2aORC`R!+R}t)SzXJ0J6d3j7HCgNt0Ahw-mE*code zA%x{A7}Cp6y(IdA!i4UAxorF{?i^GHfFPQXU3j*G>#;vO2fagTzB9o-6Mz)vLrxn_)q-K{f_%RSvmMwJz7nOnVeebR6gYopYm+kHlIQVZ8{+pA%qs@ zPf6q7;3j#?ZyucA$B`b9rwQ^bbQ0aWxL$x@|meJx)NC>-^9tM@e;t^Hv&kh9Q&nI?v#B1|Q`^-l<~{*L_9k zd^gQ@eb9(O0SMuT*{MJA9Y7Hua5!4zNIvgz zR?gd=jjWSc|0??=0kehkjM{8qc4U{g-Rw`{K{lcOROtT$B_xy(V#9GpHU}6Hd_L1< zMT9@=lNe!kc3tVtu3aWXO=BOCx@FGWMfG3v28VW|HZR@h`k*#{00`m;*+rZGviRl3 z)8;GahjgOmvyAFSscMeYR?H_pzpYSo`cB24ggHMm|MvVMx!OK7?W)9fnza=7=z@|T zp)W(~^g(amd;>z?zFpJx;Pl~;7jTRb!c<7mYj{hy?l)0WbDY|JDEIkwT+@6&H96RZ z(8_Y?*e7oA;B;YQr*=q5&LtRCg7I!hj!D?(k;Klp+b@}I{hm7rhtSo1KX)o~E^n1X zI&iSFpZ)Q$c`TW53-Yca7T?_qlhKY=jzfQb8`hRZbBgMwveR{*@Y0$34|#<(%H6gSyUAv<9soj0m}M8+%9N8M zlz{%679Xx%WTRsYx~T`p8#EbW4m-#UND5czLEWr=wL8QNDIP+bWhR=i5jBjKETn@f z$~>B~jD>FT@s?c5+Wn0g^d8WZ%p@P$fG8YU7m&3B_40LjqSlmY1xIk`-MC3u-wczJ z<{^1xL#hDw{eHVGAxa&3PQ*htuWE#kYnWxG14wLXG5#6nq~Yj$wnHK7Lqi*q`Qf2~2b zPm%D@B+eH(kBdRxnDt%lLwMlA}rLVQtch3BqGH@89Ghm1@^V* zLXSzPyn{P(a&M>Ex7B*>ISz9?hEm%-U8uaCs`A-MgYw9Wi7rI=NEbSEmb(vNN%${C z2gg{9=qZ4luh(t4&cR39r96bS;D@j#T9-}Jv!BH6mO|>5xy{-Ku($GRW%@=-^+j2` zzrbN;bC^r~zuXqPrrP$C9}l)yms|8E61=Tis+Y260`jr8U;0X+#1tin2f`JpaC)^$S;=*I0NPQxi;$!gdo^vRoCsyu$Kd6xCRRmmH~dF zltAB0VhIlklKi@5P15QyUsiS1zf4&5eSLUw00@M&t2@0YaQ_C-qcf7eZ$5Ntkxbwoy0m9SGu~eZ%3HA0Si0at3)Q~ zT4|G9cLb#p>VhtGT}8>>pq2kC5vEQ>qX?E%bwv0I9=uZI1AqNg)pcSJ?LOnt?vFqW z&{9Hs$F38)uJaexiPUwo)Y2D6EKsq8x}l4SF5G0yK3<=Wp$&H*BN*Q>jh7FD!FKK0 z@CzN2M>@cYXeFJ41T|g5VfOv9sM;e_5`G8eQFc;tF+SS9O85hGy-`hRqaP8B1APQ~ z2!>IfxkHlW{sH$iV|e%$UYCx&zN4;{;L-NQF$xG_sD+<)tCMf?? zz|~6biwhn|-K_3ptVRCRWo!1yd6fJ3onEz@B^W{-#j^q9Ov4!9#~i`AE()YVASI7eo>ySDkhWGLs zCgCR}Xo(@fy9TdW!VQvKoY2-POdd99SwiTbg$d2S##ml>AyIr$&J6_Y!OxfixIMs3-TMTFc#*mc3np=7jY);tbMvoQleQg?f z=LSbByQl??)6jKnAGFiCIF9x17{vm1;LR*(&LtPOimzKP}&!}nJy|pQo2qZu1B0$`qVikIK`?h4k}G_ zvSKSDiN-S zg*Mte5y)}u47z(IuB25~$_06t4ylg{_wcpM!x>weI1k0ydr=N1Y}$na6?oW*GTDU! zdbIj*$WAxa1P%7g2s<%Pt0tDMZkMiYZoxLqtxYGi%vtGlqtFfTD&KYcx;)$p70pW? zy$TD}=u`NDZpOY7o_Lr(oM}6FqK&{(1x$niG)zC?U?*f=dOQXOv5{6tkh9Y`ENaJ7 z)O=JGJ?*ZFO%)0}R5+e&7kGK`1o0yad4bA{W;Ink$%+b-bGowu%OS()c^9C^BFWXt zk;C|*jm2|W*V(6S^Sm{8(%noP(wxojEJN})$Y9$#bHi;awpfej)jY zZZ7rmS%N3VT?@M^R;)UWZ|zi^Vw}g=K@Q|dWDYlpvRIeFsv+}OA$+3vI5Y2(e48Mg z+O}Je>hh_qv;MgW)-5Gm1QN`Z0IrF|botw;d30wg;I@1y+j2X3zq!NIoDCs5`eRoz z&C4}m)65eb==wT{SA`6N`xyBhd!C{G&*?vsN(4`TXkl}m3RZfuL>pz`ed zH{T17zZbdU(!&qc<=d*SzRn~vNGtV;KTC#Zp=7!l6uPNm_ORORYju^z1*6`@5m4{c z_i6A7JKjU-z#=C{<*t5-;-JV;z2+X85NOjBuz(g=5Tm_kq54OA(DXfL4!O37`>|Vc z22N8)h9#c6VTs3uBfQVTj^V6ey_l+wWMV=fZo*G3#M?s7`c+I|#=)G(L~)iZdspAPsOV&tokwfl9|_pyaKk>&*M$pPZxv3h{i zvxRu>C-n3fS>Hi`s}1Q!BV+dOG zRGilLb7Cc4=bL4b&mp4=6N}#$$qgn4sc*~Adl(^f)p}b=_q4G*%qRn)JN`Ub@aJA; z%Y0irS4ICgTA_(yWk%qP-il|VB{COQ$J!0W0e&12O;~VX$>@jLuYUjVASYa-RI7rd z7L&XxakMi-qbf)32c(SW#vJ0sHmjRvH%IeWl$2k7NN-pobs_!wVbQAFZ|3M9i;{jy z^eb*Qy9V7q&d{vMijg*@ju)5QFnqi3Ob#RAfy8JFu?qov*BJ!s&X#Retml=54MomB z!1Hu{cYBe%#peOc+HMw=wj1mIc(?E?>)MAo$_6n~wzzxeSH{N4%|;L15T)0MKos!V z(0dN7hedf%O*DveI+w}=KM9AEf+#jVX9vy&9S?3(Eay@hL=_gzq&S?e5&eNPHn~WFo<(1e`62Cl$e(UeEIkJtdEf< zN-If}=vQnG&=Ezz5#z%abi{$SpHoQ(DY-)I)zm=s`C*2#ksvLeL8be_uX39eCA#9= z!)dH?&iB)XK}5DGf11GB!oMerv%AX$9)r7Je}Cl?exB3d_#+uy<0(DgQD<5O_8-T4=X6ra6Oyfjz8es9EqZTjd$ zLlmRQQHb$r2fL)vPiUZ4qj%7$*H8TB2bKUp9`L<2h)Ytot$PNP&-=Wavyw3qB*mEI zR|zHcu&=d^yiv-{5W^xeJWS}JpU{^%ovuNO)c*Y^uckhqGYK(B(IT}xc59c}lXF+x z!7%5=Hv(||gX36u4ghij1*PRDqtDwdF*pp}__ni$v>o_zVjmca4{$gV@g^YK9uE=E9^r?j>=g>4G(b}3Z zy8WC$@Uxhi!^s;0+DAoypF>3t9t>i^2RA$hf%UbI4iamm5QqN5&hUMJR9~ zDk^UB93r;x%Z?!AGeY~$XQsyownkK*9Tj&Dx8wAh{1F`IaI?gLGz86^6V+}OBXev# zLr6zImUcgfj&{$v2-onw&UjEjw>hlrk>SiS_htm{nnNpgIxi^9)hr^>Q)fnR|8oKu z&7x}#gJcMWH8b?jEK=4OLgsKr@nL@EunL?EocVK3L;B3(XbueIjrwzV+@85?{oCVw zp?%^5o6O;A43091=QAr%$t*gScqgVSnE6+BF9V)0rpU*}G}h zU8sJ4W8)Qdwmnv9Pm|%6^RysV;eD689A>!XMrF)W4f~xmfZeX9Nz9D$2kV}SlKSC6 z5QouSsP@qv{k&?@H-~zZ4J8uno!0AncD6U!8C0E)X+3^tz2L*;3}(aS@CGG#|II9y zwlgRgeBzp&-`bJ*J0GK)OZZxi!5fzFrj^%Kp?Z~3QXJ}|a8wU9$!{{*L2kGV;xJsw z`qz3@_#zU8X%32OWQ!4IcTyIlJfQ#TbUhlR#Nx5k?UK~upRAx*pk|$uywo{lLdts7 z+q?$9SFKyq;|sH(Te%#R*d(Z{CvM$((33eV0n08~sJ@9L?K7rixrpi3KfVoCKE9~Nw{agp>vw6p^&%}5}pHFJ$3*wCDFq@4bEQC1#ggvIB zL$^8pq9@9tAy{C(+11YPx1LpOu;6#!_@WR7A*&G!JpmFY(iJSe75h~mrcBK)J3cby z8-EbN03CYB!D&drZ#;oQ1C)f#5Q*Gy9F--wXGx}+sf!`+zj22)48T@{AX+0nT4D>? z;Y~q^!h#H$5bz>Q8D2hbcQG~3jz{;as$K0_ zYrbT;J~^AcXei>#Qxi;`RwXa$QM#}9<;CyYh5V;X9Q$Qkg0&Y7@j3R@n^|9{_Gp@RFwwA3-J56`nNwQ4Fdz!=7ii72dW6mN{3Vc|6uGt(3CSNlc@pgCdLZ0ita8SD2z zl{hiORH!j96_3OO^fyX);}I(wh-8l}W7EEzY4@TsUn)_QZy~ZND~D~DanF^;j&1lW zTO4}}lOaK2|riwW5d_*QxlVU|68`jHm2&J+po z4kAQs>6{m;W*{kGQ8X5uL=vh~srm*Mh$K!T(#Pz}?S7NJVZ*mKtSK+wJpK5F{f2nT z4Bcoo?VFAjg5J8a{Lu85XUrOrKNA8tbz)!(s!}GIf+Qqrno`nq#quR1N_Uj;hBPcN zqrngioemQG*pyHJE$!a?TcE_5{86%Ezu9HoW6rudZbMKIQjDt52FZpW)Dl)KFpHCY zvjO)Hb+s*<9bbnm+PvDr6i{ETU)+}PDGJsgJT##uO!TE=eVREu`wh$fk?37Q_622+ zG_BfE1MmuF4Z6H0UGHduB+{G>_?|z#C8)-0#Cw*@#h+6_Cc}>FBN89*Q;t7o%%{z+ z0S~ujJE>MMH3?fls?{6#Jh{2@j9n;C1suP9I#GQ7o-4m+&kam@3-X5Vfr1TYyngKR zw(8&!eq5TJ>ysd5!GZzEX#;%(WNxZ2Bef}0iw2oEUdYUR@!`$KU7&T~gpD_NWSyg- zJ+g(m>QrrbOR@GtGM89CFO1T1asoAP#!{IIwq2i ziC9bIcVUOPIOleV+ba;amkCV~@cstBq`z|YsI1FXZ~L@SVUP@VLI@_LASf^Ig78bL zcC`c))-;PH{)z+r3n3R}x}350y$r{*cdXK5qzv8RuIMJqwyY6pKJ9l8Y|v1@ypnC2 zQlE9(?gq2J!|YM^)k_>3ypmy3o5!M?B@O!GOzHN^uIwp?xP8d$dYP|2y{?@qmDEDT zTL=r3;?rL8_8oy7uY|-lh5te&WuY{$#KZ=ROQ;FdDuHuGqmqc*y`K}I@xrY14G-fo^pYpd4@3p+V zbN!Updm4RHO1|5b+amW@Nit@ccX2jM6sHEoChK^Q{x=ZhGXa!xj)Lcbm5{H-`Nwp^ zK6Ujj*3LnZ#U7TyH)4~#`+g_-z+zU0f>l0O{o{TMSCF|2|Lvp$tSGytL{cXaqwNet z^vuPy4*xZ~-xvU;YplU!6ZzJDk2wM;H+zWpxNECMZss~VX-$WbHqUcQdHlpX+{&L91+0+089C(h z@E^$pnlRFqN^K@f_!)z9QX*aY1sX)3Lw;kQK-Ns+*)tN3H;lf8s#d#c^!?*Ewn=79 zk^+!&Uva1UxKEUrY@nE@mm-s$yFB*8r1+HR>M^gj*rdD}ob&OXl&`@PU|^W^!HD7O zw&k`DtBgVpp6P^ABO$c_RCD-*?FC3<^Y&&qzQuc=?)FZFlGnJtHK!P&ZI{=BN}HkL zj22%`b6X|WH=w@*>*nh=-;|rGw=O@CO(dr*k;pv&A(oL0_HHI>Rl5{^KZ}f4ETd&b ziloGGM{HtNd0oPeoVF9myqiRM@saN*6p~=C1P?J(WX5L4%_JR|s3oA1@r;`a~!8v3I`Ke)qs#*nr^$1kne`eik>&7T?ko60l*0rECKe#;fDulkwi>uz2PmN({unq z+cC^|WCM&sSP+5&{j8~=kP?Ma2?*kF=m(v((vSTHrqC{dIC&usu)XA8H?myZ3J0qz zPg8X)^G7UFBMWm0P)@R6+=@7v7tWSUDTKa#;qz;-w}$m}aq#xgw41zt74xP3bgJKC zI1}NMcCh!R?D=@}9g2jQ3>WmH)7;p$=^q+4;C=1ieqfTin3#(JF6hUndA$!!{| z>wr=q;-L1pTE22wSV(w_K^!L^zZwB0@Km+^KCd5}u7A_l9gGYa0Bkk%eZh}py+R;0 z5HMQ)8<+VRNZ;4p-5}%sRQG&&nZfM#J^JkMzwJF=42PXQfrFx+uYnI*Qo}nJ z$Y_U*ys^HKATw(R4|X<2`*+65(6T8b?l_9|z$~xZR03vPWddx=e8D8w?{`F_rrJ|g zyTwZI0w*oKLe7vB^V5lM*+ZSLyEpr7PX5u$f<4N+(_20wkg5byB49CId`RCR^Iuiw zcPJQSKWoq#;T;=wOD(9v$!32vOCX@tSl@#x(R5IvhGP|jk2Qb zDIx>zg9K_G0U(z z7H-aHg-q3_STb59ql0mgM$qZA*R@1d>NJPWzgcXOjt06n)O#An+|cuUl(!wt02oai zV5k#zCgrgADKVY44k#%{QAXd;3pJ2I(>+G~_9%zr9Zf~Vm&6x&M~jf{!^+iAzDkL> zNQIQl-(|tjqUhbb3wx_N2E#*iFsxZJ1M2F{aIOxJDUo$WA)vN(UUghb0`iPYpAC?> zNWdW@%1hv-Tr^eA4>BQ2R4E$D%x&gdo_NJEEYq_Vz%{tx8pDl}Se}|S5Oqw!0f1I) zh&iXwKu{~PQIh26Dm8Dk7mnxV6et%R7b${@CkD>0g0+_c_lWoAXTp1n zcngLXUcg^jX=%S2Ce5jpAgI+!#F|rxAgI+!ObV)s2BZD~Nn!O*IDDdSP*K88D%SA6 z0FH7uTv|I*N( zuwf+*b2V@B=Ps-B&9cb(`8}LhtC%=Hi5}7oR2}}G4R|=yFrdd4ETE-KKIQWKm>*6U zYV2%9XfTha4X-7Dzxy{qqfK5}SFO>v7|c~Xca$m;>2F@IVZ+qpCb|eUEut)S<+BTw zfKmcNIo*nMVuiP;4TW?5+|jPL1trqoa{(Li11$3`dw&Wx8kYkinZ#4dEx1&UuC^L5 z-{CaYHLI*8NGeBwW5TII*jc;=)8C3DzHWVmNRd?{omGk~9k%tYXlJQHG98h07NBbBtptJMdh?cnEEve~%8my2|J6%R zqdB+y_*BG<5`<&T*+08xo3T#mw!(3Q&ZJ7&S%zlA#UTwq;ZutLKY8D}-X^kb`zn5ca#p*0_1WjA zVk?Pu+meqYr#t%(9~NbsWdv+MNz_k&YRN;nyTAK6= z&qB$gJ;sJQHt#cHf`05|E{}i267pktBHE(H66WcFbXw}IBx8@OxF3G5&=DQoQN4=v zX7_7xA{y0(V)2(sQG0w-MfsIh1+SbiXm?<&m4f{JS}bx!$W4o06?uG}MsmA!s{;7y z2Z8MQK$37SPmk}Zs1s995riyQXeA<~pgMi*1`Q)AZOLd#^AC0|3gP9k4I6doMX>ha z3a8ESNP4kh$Ht|Pr+`!GaMVsCo(N8*Yk-IXbX1+3&0+#aqC$gX!{St!?#oX)%w`s( zI+)Y!(!DKAZRRH?lbrS3^A*{xCp{Yo8`DHAfHa>tw=Jy1+Gt+Afw1>9!PTzj^X)Qm z5#9EZ$9Un+NqKJ=H`bXv$Reo^T2eSGprvdorAU%s8hlTx=mh8J>SU{7CbtYWf2eB` zN@?P|f&aIAbfwU!IvqkZ6lJe3Si-h*4T!cH z>x@l9M{6{t>-kSAD*X@J@UI%lS7J@H-tvR=J!MlZ z$KG@86Su^@tX?xqH{8;|nHam-3(Q^d!6q*a-1OzBONlp{kwSx6V-RtW>yCgb_u)pW>Zm=Mk2@XD0XWD*Q_pYz>^1uss3>1ytbC(A~bPm!Kd> ziUfPlz_rj(MZlfATf>**tNZloI$N5)s1-_h39mpTjNKMpR{q?Z%nZpq%2QX1v^0Zl zvjdYLkc3*Lr7)92%%^0pkl+uvbO%?iMRMrgrTKJ`OgoO!4E*k^MkY^RUglTRG-xAk zF>NJq$z)rRWiqOL&4n%?CldK7iECuI7nf1o%hl@ET;Rf1b{eu8Y;%0y;t@fc?`1!t zTVa2D8PF-63LD&~`OW5yUJM45cWv;LpFsr^LzripftQ>V_8@sjydWMEEH}bq4dgak z%Li%XeYzYsDdQ#@?vuI})xhvbywE|VdLMyTU7^O3rCRF|v6bL>$k(0ahpoz|O}TTZ zq=dS?*m#Jm?X*MlvMqvqatmtQsB)r4G^9$?1!i-8Qhwd+hx$;sJi#!pp{=9EEg-x;<}^? zG`;4dFi_6O1G-&vvPM=Bs=DXA>^Lx9$RJp~q^I4B`le&!Wp?cc$IB^UPd$#3>}-W9 zA~aQME$=p$>hCCer8MS2yLoDXo8jJu7uNKDi2@H?aV5S-FQd54d%0u*x3H9qwpCH3 zlM7h~o(S-#q^;27mM6KENiL)wi}>^_RbQpT$8UH|0nUVy@6_3V=RKqCOxNl+h#8l|1%+Qzd|) zo)&sX=E--JlqZ9$7WmPz05nGYd5n+dv1q!8YY>*^tVBK*yQYiSjl5_y9vPg)|0>Cn~>op&HXropFnokF3r$QIYceR zUp07H9@zT8Y&CV}sEMl5k*@8*qZn=|ID(nfU@q%iE68=8_*a#@Q72M`-;d!v$D2dUI_!IE2Ha-9k? ztBk;5nHyv<^^b1#VGWDhXzk?SMYa+hk?0x=xZ=r7f(#ZO+zFB^x547c+BCswp9^m5 zvFFWQ_|=Ewuqj#r8PSn77L1j%`^QYmcH1*G66 z*b&=nPj&Z18ik#?MI@wz1O=%gaE-S~x01@gWNqvynFjYuhZ4B4`{)OLwm%-IMH1(C zVjtH_oAKngAWLPe6sP47*L9AoL8ewO?pGOGI}9pW6<6;E4T)7R(^r|8k6%{8RBP^4 z_SN{;Zb&l!dKdbceY*MAXy1f*zSrY{-Fid<{ygx|iMZ01NANrSyz-x5^_4a)xBMO+ zB=NvlfnE+vEX(D=0#7}K8XHSInlLzy9o}dhu}3#tm)ch_d?D8l9)}9Q zq^!}nr7%xc0?ckwPHp8)j=bwAWbVI;-LJ*ED;Oo*C_$s2X1CK$&_PP;(9tM(BJTv9 z{bU5}-0EL9t0U-Lk5w}Hi&Lf~O03J8)zwiE@jfj$t+(5WvA&R>X7(9?eZuS$ZvSR6T{-i`+L`_}TdiAi3P;KfVRzmoKg164 zG2rK>m(&0EFkP)%lh}o?Ve})kPeB@fnEx?f{5fv`pY{LT&7oM!+~{Duui1)&-}9oKK^z~J-;sW=J1Si`!jjeij^;f9neT_c4I~e)0YHR>0ANn$OyLQ5fv4AxA%J%Ib_AEZ9MN0tn46dTtUox3$MD zM)EB~S60PXe**#>0wUl(SMmAx!{=d;=jMdxSY_^JIhoC8^Up0HLwUoU_x4~HOfuZ- z+2nTiKkeWL{!m5y05+Psoqe6QfEg#$89`}gwy5^~a_Sf=?r-CO@|d-k*zep#JBW36 zoPrE;YXo02#k;=J)b-+FzMd}IpnZ0C&(sE1Zs_ym;d6Zfk&cV>$VcF!&rSRkzJled zmr;C4|MyrxwKfr&IDF}+NRQN1wojs%F#FU0*O|pL9hU`UNB6u4MrOh6-U^|l6~QwAbOmnD$N$Hnqk&&!Bh0DBVmyl3zXp3JYOw>{-3tmblpC_RJB#Qj4E|Rf&H$CMpggB#~!&)efVx8L|&q0v_s85p_E71;XIbAR2 zH>~NjnD-)p3^Y7GYlUP6jjZ|PwkLkrqjKEjc?bLG&w(c*A_~!L{*|?ZZ=A{U^TXYA zzV3xL3@~FD?ePi2STlzL!_1#pBf6hwH+mhP8^f^pqaQZA%!!_y#0_mWSZjIhG7>!z zj%M+&b{3zUqy&aE+{yDsGa!5o#x}PrZW~&veXq}tseAd$oN6n|v zpD*q%J&T60#6)sT%js(Iu)K!1?U%{Js;}lT4?N5&JzUOu2Gq-d9_EvW^%qtN&#osu zlK#%#=n(7VEC?$wjO`c?r*2?g6%C|>*w4o6KysJ zC&)cU5w4EG2n!qbyu)H1Xl(P{bC>!F4qnM1N&M0=zA9;qN2KgD=d13)afkzKq>N3u z2p8DIJgW1B^%jnd)OA&Y-NTybXDWpRC$(5Fq3CPhr7fC-}rT>LS+y&Zt> zmOi`CggiIS3fJBA^8{M7LvTMw-pF|%zPljv&#{2#!0sMy*Rz3&dV`kF7~~DxL7M>j zItgD>#Sg`&-08u$qXPe3DsS5*I9_;U--%E_q=gHwMDoqqi#zzTD}GpqjqFl+8(_<~ zsXVai{dzI~H2eIp#G%8*dU3tD?OT?7OWyg(l4C2%a>I(pYQ3CJ?z+h0`RN9n|6thR zOX{UIc^8ZmFh0Fmbtb`j8muQAI9@m+83BR-h}*?y))l{XZsy&|MT!6H!>%_k&BkLV zQ4m0}Sk6Ar=D=^Y?#vImgZ^ebX2SkzJz3ALdtj6Jn{3>Y$T46+WYxv&dfm1hhO7@C zz3$>C))Wi&mu_ISjx3ocV19CY+Xs`xxniypXq|IMjgXlaSg_umLY`1+f( znBRWuhg6)S3hvY0U%W*Jz_si+0>2i5`D6R>|-^b6m-$lm#-ZtZcNAA2pUVz5b zci*#{YZ8RIpg>&T!YsfNnoD12H?6WwF$}1iW->D0DD)7495B1MolaJ3M+}#BB39v- zW-o3Whi8RHp1rt{HLWH$&d15^WPUxHe|GK{x3lYSqQ}rhg{}J>(C?%06{hbdpKqsM zyKqR{(2KUCa1f@Klljf!t`i68X>A-D(q-Jm5=z&X$?8idGO&QaNuozT9ET3rC*1xL zmM}QW#mCva6(#PbO8lbQ?c1dr?pbnP9QmQ;DpE&R%)dF)`OW=eHeU~> zhw$L&@Gs9#5$Whs;JcZAnmpXDoz=s~)pR&j!pMv4RXT>sIZ z7}ZYvl=C$w@Y8cBDb+BQHUwcxb)4JTYCX6_^0ISY1*k?TpiC8o#NIuXLZjJrM!5RPc}Kx7BF!omWNJRKCGqvCZYn zz{$2B+8(dyC?EJf;;3P5GJA$+^HyUU(kFKCD4p?ZzOgU2yM+D2zL;}mQ=c-dg3E5g zVR66SR~L5=OZE>tYR*;f68!vWy(@a0{Li1fcly^~&HARoiw^r%xFo|Rvv7{H6&?)$ zctD50SaS+i=pCgfyy^%~93l+^mcpw}2?Y@-2%^Asg6w^C%Nj?Ym-pA*sliSZSRU)R ztPifOAa%S5J=yG1IxcGokTq;PeTggaB7DdJ;LQ{^&%ml)XTrMg+TvH|W`Q1X9#+$? zT8&)*(0(78o}3z*9heB(Up5(!&51}iA15cJ`ok`rd^DejI)wZE$}(7H@at6@pugG% z`s>BlUz-9|@kMsRqlUQR#xd*ah;>Y^=Q;k9 z0cLV@0N)?3d}}cKzjB zE9jZOrO__44PBaD&+ev+hjlCHyKp4kbHfkwF4{L(e>@C)v-QOidX~j<^{MnQb}XPB ztPYEQG$AKbZA0u3)X^w=goB#g-Y)(ejJTIXaf97NW6%d^axc-%a&g~qA5m*55(;rO z8ZD9;K@E;hEudLW|9g7fdi` z7{cJycjuSshAgGsE@G&IhqD8sHM6NAq3;e}@ zezAxe&3&1DZv3$E5P7cWu?|M6V!B;so45(;!hJ@-3wG58Q37_?*Y_RK@4Ru)gWZQ4 zL-b;VSi~wsU(xv2c|7GpPV(TlIwGOAY{Yo5hC{fJ<~U$y;nexq#DiQzez5kEHXT15 zm}8EG^MsY4Q@|{Pi`3=mJ$cSHx1t{Zf2jpHQG2VgG9iT0!uV{hEUR5Kf{PM z>dG-%A_XntgbME3oSe85=MKs+@++(puv#xZFDG}MXr;UD)=l=~l9C`+4|jKy<+nZv z;cCh=$uj{7*H7k<1X)}!**Gs@YJijG~$XDtc9Ff@Hm%u%_^0xpr2I>p!Mzr{RoA zJG~mV{6~?fW%nRqjVByDgU_Z5G2q7-B^PdLP7HpIBWLhwzP_Rgsz;A^R(eE=rf#OE zdmmLegXq~3Ot5sj_-qDp=lL1C@!1cbQLjB*z+VUts3GmyvTX0!Z`b{U`X za5EbCtT8LA8M$TrQfx!;_My8y?i&viL*8)Et_P2|q6iX@tc}zjcCpVz0=oG&pWL1H zme-sqM-PFT_vgcw|B{1RE*{oXXF6Y>?nULSDIfk*i{tpIbZfnVUe(EHH~msBAR<+W zM9`Z?0dI_$ZdnV+b0A`g3;2WmS+PcRL`QeG_zM1Kr^~y^oaOs%zxl2#Ir8hXl0(5$ zUtcICoaOXpwrtQ!HxeYJ)QPf}<7;TG>%dSEj%qcXI*av}(~D<~SZ9>R^o&JQn(@`TPZ29?*=34x<5G+K6&iAYhDbCkj9j%|VOEY&Hp9xg}MRHIN^VwexY}8n-CwKSFiNk>?>S8Z@7-je}DU^5_FwYTznrV_m z7N7YXB{_mph4m9*p$SJRWBfU*>GJDz=}ec)#jDvA!4Ut{HalN z5Eu(#sh~*AuBVPcc&bWmWXUVNJziLcBY-&Q;_0#fy3n+##z;8E{Mk_$zp%d1Ed);& zkB|AA@o{dSXao> zhp=LNmB8_3|GjFWK_Q1wn)>x_%1q9+pPVYS$x(vhQ>StjK%>diro2xM@mZwXc$_ce z0bE$J@h(gK@byx(l>U=Oq4OAdd&f4dy#Doys zjg(3dhz@};2p@m#iOwd7cAkTm9C2-Qeuzo@SYo=qT|C@4xRU`Ud3(%Ia2_QDIN@g7 z3VDDdehYBGtCgVL!0~oN%BNoSR){u#$2TK&US+sgdfS!Tzy0{t57m0)fO~V49&F`C z>&}kqlpfq(8!dFCqzn&$w+dJ|u(3tj3TmhzYtk8YlE>Ri>Z z^mZ{X%|b9bR)!toU<4n;#cO%7HPf9pYR2j2AH41v$I?sL>3EM=5dfpK;%8kRE|w;* zd(Waw9znzC^2)!*@)?iXR@2Q(Qk}9g9>0}#fI$-L{`AR|qbOjX-hNbi3WVavt}Nk- z0YT_{s0+AO4=?FGrCje>oq9zi@`X^vm$-YzSTiqlXPuq+tAQ24` z0Z7-2`D%Lou%3OLI{*IbuiofByBYoA?wu#s4|WMXH|hc&k??dGK2O%uKPTUuPm|g0 z!*bf2Btf)+rH6_C?1$&73t}W3W2HE^i)&b%xSV~S&3jQu7=hhi8%D}~J_}6*Dx#qh zWMSLa^>n_TeD0ffp@5vH2!--dp-7>4bdpC*RXMN|^BlAgcyMRv#p>budb(OU%gK7$ z8_ToHew}hL<8}TfSzZWzf)PxF`;FQtgPd|j=o3?_h43eu=>`D?Phf{}gORYKX&94f ziyd|rq$7dnwqf(asxACFhq}XqKjl*Q6!4eCf6B(f&W*3XEN7qA#tg(uf$tRAK%>Dn z4GjSi2qxFiIO)VCq}h)sc^#9CgfO43|6DBp=!1@PEUK4XS}M|)py!;5_|=3aY?vCk z2ucXBIqO&N=B>2wQoErw;X~7yifF=HU(pK0YOyzRcp7>97CPHx~ zPaCz;IdPvdM~=+>>({Y`ID(ZRRyAhFu-+^upVp2Xu>04qLkJW-J)N^A0B`Z>Q$yx) z!WY(DJ~B{&uP#N{Q4$wv2pg&sbTE3%VQ*0Zne+#q%7;8@^w`alHr7I>N_YY!PZe$z z7d60*9sG9GeK2BHQ z7xnIbIbFfKXIR0~nAXgcovBnDFp9LgpB|Y%>ne|S!NDsv6W!;hlYSGw^2>!*pq$(3 z*Xiw%iSquHyLz+AT~#P|Wl_0b->h=KRw(zYMdf~bv&#Keq1<$kYF?stpI{hv3h-2bUi?*CX+?!Vrwa{pDK+<#eA?*G17<^FGla{qTQ<;1wh z8|+*OXR68b^KEJK6NUL}SXEwjPfuGntoH1)RubUkx;s)`HE5CVh6Yqzc&_|X7Fz4}it4Rr-SWz7`BfjY%5>KEg z6DBRcIb_gvmZFVl>EF&TSO4|;hDA&NCdgp*KfvSj`HK0wHlX`p;Wkf8!}u;9r}M9T68f+(4k{D;#R=JW-~&&%ob=3DO)z$^F61we?OsJGZ!6ZNro_;4O_*5IaBgaLG5KSjhN*bgzbM$8h z<={VoQ@s5$W&LC7{qVCF>9nst_CUQZWm6y#Prs8yEZO3N18S8RkV6Iy0vGhJS`=>H zxN68?F~7E{8hiD!1Z%gSz8UQ_9;^pBU(MDH_Yg`QVGsDF`y+J%cl+qMpq~g4;Il+F zE^Ok$O#p7X4>vDK6h^zWsb-M)hdgogfs~tSCm2QqV5tzE3fv@u2owR60^rH&+x+^= zaxq^#tOk+it*N8q?WzsLXu}o${&VPu+&S!zR9ux8#ituQM+qyzTfCE9{QjG zyUK` z!D7nVZFiUeV*A*p-`;?Do2H6I?p+%t4HRhPxMeK<99(1nERDFEuAj5Yz00yWGxkk4 zMvg^W-nViEVuSvv06(Zye?Zd-Mv7dE?y4BDDlco6f^qxf`*|M>nOv zn@vj{rY*e2mMwM=hNnidNUeg>ruQ`bMBx88SxtZcZ8({XhkedUr1pbArv>6Jly}?5 z=BuC1NFeTYnWLXS+(s0O1f@uu~$C`hgWaYfCJJ%qwKPq2X|dvCY+<@C@`xa)@D)Y_!z0lugk#?5aZ`We zz^RceJTVLj21i|#;7MUfFbyG*2niy@Q|O+~uARw4ibIp&LHwe1A$_iB+(AGVYCiMG>%3Rct6pKIxE#IiqSbrWgg_Ht5{8gADy9Etzsid zy+mBJOKl$GYTpH z?&1di_8>w^6Wb*;Qh0@xo4W6Rphnsktz^7rhp-JY^%=Lrx(hYvN`_vKNH*$mf_H?gf zPdIz|J$I)&x&3qUZRLDgEdQJ=ZyI~m9WaYx@N6Kn=%183NL+q@F}c5=&TnMKjGHI_ z(C4M_pq$SWlDeUv`G3=YP);bytI2$}p8e1CJaWKEY4#7y1#tVIa>7|n*H&^PcC#07 zBEc6Ld$^pKrY;{>_qTCCw+t_KL?CG2#((lRj^R?sx06Z7)F4GQh*U$Zy`0_M-%jtQ z^Yvs61C6Wu>GkZ>?AmFuCUcnVe5EvYcH!p54WFs^56e9uPjd2qOsDtG!~FWoWd3=2 zGk)GA^gry^j^Pa7JINbPUYjiZ@98z{A~Bvb942q%HVoXP|8ASmM{`I@fP4F;1P}A+ z>UwfN#l8JDo7By7E1`VK^`D$wN`pdvEvGlLC40=QWEk!iOZbEnZgFBoXp65=*)#Q@ z9QQG$;XBN!dh)V&>OT=dUi43ApPb3Z)pS03?!ctPkHpXDKPp>t)-V_I=W@25j-3&) zo2E|ol0>`bmw!}V0C6IT-%nO6XZ>Zlc=-IqN{%ov8(7&H#WX=Yp}VD*^cl9HyuWM^ zY)OGF5p1WQA#CA@sU>uhQIq5`VmCA`9Lq`NImaMn>cDseOV zvfRPa3jU&1LMn?D%3 z7;U6O6jCSix71mgrOyXmVhg6KuZwqVm@`&v%9IYm^+_l%hlTX z_^n+N6#9PogT_z!8N7z#C<;=Vv21xx*>|oNM}fcDU%H`jwXYdM;WWD)^@Ei1r7<}J zQf5GM1_T&P=HKi|wsq62`DU3jRZWn%o2=GmJ7&6R7boc$zl-s@Q??k$Pf9_Q-~(<2BgbjlOhAc8Qe^7 zC*N#|28)H0H!T`xFkLPett!9lCGnp!fnKc)uiO$ZhK@=b3tPGHkV@*-^Y!AxZkv~4 zbK62~wXnQ$JAIjXKh8h;&H@wnAnz#yS~(W!P5c(yxZj2N-L`-CD}TGK;qNCKdfQUo zZ?>3iH-A6b(R!QuQTbzcA4INK)?Zv6x>~BRj$VE%Kn;)u!b~|24!93qHMPiu9-o3Y zmkIqo3FXapu%XD2S2_s{zGrnyJ*rpUcL$)=72FQxV10ePxb0n_M?Z9v{aaB+p#3a|2bQ)dw9<=IdE-A93@#^$Er(bEK314uVr)CSj}R*#{0!`-M>mtiAnI0(bW-f zKP^q8jxHUG3FU_#yOeH1>< zG;=ra;`tR!KGSTn2`s7?1e+H>@Ddufo8LQE zEf08svO#byF3$0yJ)I?(cOtK%!&H@$+86WcovC@H%Xwu_Pnc(YCsHC6rs56zv?m~q zU+9)5Zjinc88t#o%&9uFBU~LhrD-h+V*CCaoULHMc=zk_3|E z%l~Srk|R{xM6VxOeI^hTM7ZsrkOQ19CIee^?x#!VdeST?ot_iyOP{?KBPJlfQ{-&E z8Ats>hC+3JG)dLDG%Ra727%w-)i;S#wYvx&dFX~ zw4Xz+Huc^956af=uH11m_M;oPA-J4%&@Gft(@WS3?Yy|?DHc!g82&u58lHC!I7j@| zsXVDM^Ff(G=S0e;Sx(?iZTDp5oDOSDWD0t)tPi_!A zx|`SBg(pkdizEMZNQFAtc*3BWu+@)XJTZ)O%a5ulfDcciC`{(xoay}LeleS`ZRjT} zJO1SxRZ!M;f`qK^Ru3N=h8OFqVi-B{${Uq1jEG_|UpSv8x3?cB*MAHmo^(=D5>cKy zfuFval32|}X-SZaRMWYgt=2Xrm6x42SU_u(RUU5i5_>mYPi`jbi8FN3O=-ZIrL0X| zzVHrSOOgsX&du`ss;S~{^F`KFbNg?&rnuIlBvTE$^vyweo$D`? z*}Rj)Ui2eOvxK@^c0o!TZPGZ++&G}3N(g8O!RZ~m*kI#Nt)yVJCq1i>lQME5kQ0Uc zb~>L;=d2^Jp4{Iy^zYhHy)6_)e&y{52cU@neFtR?1)Exs3*7H+-eOtf!1JO84ur3J zDru}?3e)j!kUkz;QB=b8OlrAGoJ8Oxfb(X)8c+##!4KYq798pHZZf+a zP!B(LT+eF~9rY|k@na(4&hDMb%}vt)r!D1SpAHg&M!sssS5}{0;aTLyylGiF61Dk7-DhZ{q5xX zv@kD}y_azb*T#+AM?dhhy$WA|>q(E2=eOfBO)oX!%5Oxb%C3q-fQvRSzO2{x9r0se z!!ZbO!?410)saHB86^k6?E3n&{)wB>z~A~fE2U1lzW8(A zl~?}tE@Xeant-+z*yUdX5&}qWj)bLc$L}TDC1*kqp?q09EPJN7WpBQWo(V|-=8nC1 z*VDdj_!_#PnHFThKV%X($U3oHLU_5f3}7MW<=LyPRdzPLoV{p(wtCYxOU zF}>-9Rya1Vut03yA2YE?5Fdv2&BF_2hQi8udQh*m$%Hu({3ExB0c% z1FQqL_RQ+Yo4)gL0tpd&bi*HU9)&N<;_AV2_!`F`U;}akpg8q|@Cp_Pw*VtQzqCSk zH8{E}M0a%(-BZGM0=q#>lMPGZ(xYL-x(l$onB(1rUQ$zdC1QE60q6t6q-2;#!(Sfe zH_IbMxH*-r9|Y_N_Nhq2ceC5u*<#)Um5hc?#=5Sl8;+1Q%sNrl*VDPPW*z9&WPUo6 zRI4}5;P82W`53t2E6tK!K)FjUhy6WDNu}YaL=4y+ARrn7?kx-ca*tUK``eKd({4X1 zB>;eoZl?2ZW`vH8MIB6#zCkt{VX20BtkKjrPSdth5ozuCN8d0{k8VhSybZ|1;^M;a6HO0j>^P#e zY5*@cLzJ0`I55H(B-Hix35D74;h~nSP5AjzoSOiDbiHSmML$kd?%gqKhCaI9XMSji z^q4cPUXe;e4C#^|njnQx7!`2nhe=xDF$j$w z9=eUQp`ROWV+wzbp#6+t6(h9YpIko}T*X6s)BMUOykY{Mr{U37 zT`-O$x&$(VREt@)`D>bk9< zJz@nDpO0sF}KDCpY561n-t+ zFOkP;ARN&f9P@#>JG{VZx(UBvgxycMCr)aq=Tl+uF2B^GCj`OAyqG3iH)QotFmyBS zz}y{NxX&*bVTTBaaX>dOk-w>lh14VTlC?W=e$CH~f%9`4poJv_yuxD>u)-+=#0g_L zBsQyCsTzb=wXE0e8Zui-rUYSy5>h_FhfuxN_^T?o^Nfux5D7PcFBCiS5MGZ@u@PYvuIV2AP(dx&Dmp?@8P@dP1yv3 z@W5~$OCf#oEi|U8EH2?13?Am!j7ac)EDxc<-f)4JTB>M|Sr)zPFZkUj{H{#}EKJxL zIxiU$6j}_OLqN&Q&O5qbY6&MT-p?te5zEG*$3&Hx6{QO}FvEzeYj>ghomIvb9@Pqi zD}L!AzhuH7quV%2++=U*oqT9-Ew8kBC$mSJz0mjKAsKosrfiK>9(U|qJd;LaGCSEF z|9kLvR>9PRc4CHCI52<$1~0h5C*`XwKIqq`c+HH2nj_n?!sfzH24_)W@*%%?2N&1u zq7jeyIe}N0oi;h8aIJGV$KacENAeNXNN{eQ;Kc9p>awx4=Kg= zqY1|EZsNOP<_2jL(&vcvgR{ZM(5r2JReqdQo*K}bNSN)hbjqDkpq(ZOK<~L z1kQf<0e%Ol^WW8B#>5G`hrm5}Ro+7|ZTNE%&&Kx>Yztosr*;nOy*&9p>yg172B4Gy z={1iC14VlVK=}<$P+kbr3rLt)6 zV+FUgB|aNqMd!HaNDM-H!pAY7_-jZ0+yJI#h6pqK<0M2`c1}Ej>SxVS$y*E_GiBxU zGt~u<`GcdZoqS5V;4*y_@=5PGd@Em!MIZSb9-Uhf(N5+VGm(>3b+Yd4;sFg(m z7TNy}m`}WP2SLqkHhbaLB2VptPA~F9W6&5W#SvAU72If1R;m{T`^M;f&kAH_#`lcD zDFBB(^?`7zstzp{>>Hy`H!pN+2eveYAB};+E6=D-#v9C0!fl;uW3=1+3F`t=S}}c| z&}O5DsS7%@#|SN%K0{sLb$NQ=N1k$gD+16TZ}X{N8{n-nW6%JsM2Qz2eqsSFUKD;j zE_BUHI5f?tdxv0 zxD6X=m=q0@+^{<8&Zc>(>z7Q2!d6G6X))`UNY~P3TB~(L-%nK*_$j!w^229&F?KW9 z8QOpVzpn*}1EWM4l?ROp$V2hzK-#CFE3(Ge<)QpUd%E$~$g_k}o5<0gZoDmWV10={ zH?NC&qXQs_4x&TFbdp~TtJok!?5G5D6zZrG#pYF4zoe91ytyjkR`DT}9(RQ3yWtn|`&QXbXrw(x-75p&XW#>bm!@<7;5#|d!&n(yZ z%1;hT1K3sqXitt+zQK0?r5G9UId{Cm{>HXR@dW^Qg@E}IoKDniKZZ!ChldTp<1tg< za)eBgwlPP#=7LyoJ6Z`7Yk%`1Rt99uj`hC6$X*@5dVOJ(cl@O37O?T?lKW}5gPf}_ zEb@=mxGlDsC2(odw0)e=XZll~KSvQXy%KuWV3-+hYyn(1NVq`)26Nl(u;Ym(*xVNW zZ48$Cf74^cd(qbQLxVv}enSiJ2et`MD)0T5uykfsJR5^s@k}!8k6H6!w!x^iA7m?z zI>WN7X1UaQ)VVm~tPIM7mcX$IW(_sBhJRb)^`FBiq0YulsWsHxCPAB;{gw>sHfIe4 zUYI+vMe8`^)O~{2N~5LnmwJnO@_zL zE(z*E@g>ya=gqNcM))-_BCQ!gJW-+cfR9_KtwddK%X$S2)ngQ4WV4trDED^TIKNiu zS^#eqn?ut_evlcg=UI;7AQL$rZ^TN7CLjhyQgd50hHHGI<)?o`$h?I6`B3`d!5`+pMf)SFNw6#Y9Z4$JZn>Qs9yERb}dxMM?;EyS^lqS9# zIN@&lSmThcH;mjAk*0LR#;A9X|Dl`AaasQfM(zfFB!xNHwoZh(G5}Ksw#ss+@|zY; zh5?!uPA>G5<1m9=n$aYc2g3Q&Ntp7srbxx_gyP23cEzKiq`<=y^UB1GPaf+#W(U7_8H33DFKMj zj&xe}HhSmOvI}yOopANyK){Ic*a;K_QRt9N*+D(TO7sX%8MjO{%LOFuh>Z7073_C? zqKafjdZ3DodV8KmdVsn;G2BI@>v`03zWXlxbeI+c8X{K5DL~f-a`Ne=tnG2r^-;LViRq*Zs>tG%m`hz zz*~WR=O~x8Cr54g(jk2JH0F0;ReY!X=7QBjAjVW6V5GAPU&H7}=!g~>4;o>G6PPE$ zylfx}^GBeWvV3$?>iC`wMqmn@R0)$Uky{5(ASe+nxb^`jH1~%|3 zv+SkvOt{$!DCTU!%`Qo5gTnXDA~&ZUw?GZeXIo`QIO9Tdn(3CP_wF{Zh#Jjlrj1d@ ztnroMJH)k9S8|I)S}X!Eh41+oLBe^7GKal94%aLU!$Fv3?b(b-vlUQUvVmn?!ckmm zkqxXYQ)Vlm7_&)W^*7A0XY|=h{eu>XvRDMr4*uzx+diSAwcM~NWEx@#1fPRA_+SG# zx5@n+qRldhmL#6Q+y664!Crr?)AVeS4HzeAoJixfuv+rl?lSt*fz^d}si9pW?N)&? zCG_NjF03Q-!XReQ@v`y)1hWLq5@ogmiYXhhoL#op<;;n_zJyp?FFO-*PDcPZ3eS`) zACr*%EnfLxY4HESNXNE+;@5O{4aMUQWU& zlSK$^8HAReaW#j8_B7*df&U$aR)KSSnsH;`(dLzAj;%_So8xt>X1XP6TkkA^nw!f_ z8>7zLEOj#1N{}jqU}!eN zIIfpe#vyyq_;GAzJmC(OlE`k4V&HerL2nlWv*e~_*^{QZ*Nux~xVkVDMZ!KmQ97&G z>6lt^V~=Oxv(AM@R>EYFPXUJ+lbF?)umMHYI|I`Y_VSwV;s8NA0b!mBqL zvUw$~)}T2_DPI;2lPvv%&sT$E`r@-GtbyMsaxHSK^cpl&2IbXkCs7k7au@sPSta+`b07s+T}fghR)*di5JfO8$KacD6=&n~ltAP` zKm2S=HZu}9{H8dma7FXg@9IdH=WJ0WP>#$r31d8j2O%R`MAaw=WtO>NG z+bG%Nme$SbibwpqAt3&1lBcbXnm~G?E_-nNzv%#wAZ>zaD=SSP#qKl38=ek=t@Z*- zrcW^af;A&}yKw8q@gSM0ASxRNR+~W0qKrK^@ykB-;Q@NwpY(y&3N4x;(hT8ds%$sM z%=ZpD8+Y?U2Sdz_5N_nq?s2rRy#`0iF8Q9=%=zK^nX}n%f|DZS)*OCwQ{2iN5#|V( zBf?w-gr?{N|B1pxu`T%0UIBj-42cM^MAjvd!Iup|01M4E5Z+&_Q)(Xro2mgkifD z`T;}Hs~qRq6Mfv`qgREf4X4o`yaO10J$HxvMtSTG`L%oMSemRESNmGvU)w36)lk$s zf`47}s;I)*fw%UpNX$wpKCZwasFr2gnEQ|;{KlM{^>vLoo=-`y;9ObuhcELsUbn_t zw0rl~aQ8#l9di!V97j@}(??LYCw*RZTpgx|L zBd+1a4|?CNc3`;d5ndXu#?z9NniaOrO3hA}Xq6DPqdKN9 zDh(@>6VUmGb@0+Gesm(xswLAGjg~Ez14^QEZtsB?Lj<`&-F_?Z<2>EE?cN^l0wGJ! zc7aISN4o&r+LK*?Zt`Hmws+k1F1E8f*cKKW+I!zKpWBn$=LxpUrM)T;O$lfY90zlV zrQz3nNGcy#6%d-DAH;8n1{c#m=^sP`)L@lfH$>9Fsa1Cxrd^B^NNx~tH#374P;Lz7 z+=(!DVBQe7oWUE$L(d^}Ig7~cRe@+qz{2w+O*DFL{B0c2EzJ^t)8OU6f7p03P*1(- zYd{ACabJf6gsH=y=71`~+xCg5X>3~&jm`xx3L$&yLDfqy>@#9pl?ehSyi@OyDr|n- zY#~&4h<>crLFGox0q+ujXry3*{XdE=bK`{m?~c6)rFZR9?swQ7O;P2EOJCY7ab>0{ zE_T>xPN^L*b||NcazjU)e)UV*5J zl$g7BaVKXGJp;e|M4H7HpBeMEX3fn-*|)0PzY>V{fq zOj@#9l*CGy+EQcPZtoay6VG02~+~+YlNgxD(NP1Q1S~J~+IHWozM-D-;D3}NK%+5KASAyss>bX>Pw+bCkhym{(yH>we8e4d6>JEV=q7#&U-3#X zwgS9tmoU3g+!}TL`1;_$PmH}+!I$lQ0X|RC3WK6`jnGAY*p!KC+{;#jtv-6muz63t zjNE#Ez09C00)OXk5EHvx&ZoH~^@SanlTW5uV zZdaU$UR+b;>;p>Q2xX{XxorU|!iU3j1n}H0{%s0e_S4Poc;C?FsL0g(twh}zMTok;d*jX+8EXu-OMq#YFq zzT<};1&7O^OTxM=(Kf7G(J?GDhz_KIAsU!^oK1Q2CIWPf$7$D>?Hjl3AEPLPn9nix zXK_-L8-Rel$0(>9fkK{2BMR#0_$|(ba-;A@y~ypi#a$ogYiRI1EH6` z{M@mn<-_0?G4Pb(0*2^$cdYAu6VXpg}NQ)TN08H}NK=v*`Mt^2!DF6VI0%#!3 z1yDzh8pTU3Q_YVw2vxYu*$FrO&ejdx=i{C70f-6fL|QMQF{i`Y!|pDCZ9&iUr$fVM zKctp92?zus5CNeIS9^Bio$7~7t0#9e7DMr-&tcTzYsHi%3H5NPjnzg>fhJ>^Wt4`L z$0M%`46+?niL3=d6_6^8cFBfX&tnxROWHp`)S8q^fwkb%k@BvDgthBa)e=^qdSJLY z8-|;l;igZ9!Pzhja)v<%hO(~m)0oc%j8e(U3RR&f)ubhK<}{yNaKQ^MTI|0+(KeB` z3rJcJfq4UaLopf)Fw6&Iat!A9aow<+?!%3<349oI7H!;d9xGhgjBp5tBb(sz**b-{ zodTF3PfsK#?%BFOxrb zCd@385!U5&-9-tFIh|T(9>Z}s>fhry+UPq?g2?qo8gh~5)tE~ek2#~5_3|B9OAG$e z>`s~FCjf#75Ja0msl;Z^QTmb|si!9WTWjWvc-k`3Ix!+(=P1my9*l$`54tlu9(Bte zwH(LM&|7BYjyX`XM@^xqcc*rm)<`WK5d%b;&T+J5@x)5*3m^Tg3wk~@8-zeK2#2`3 zv5P`yjG0-TT@(@t;Sg6_cEYQYbZiJO$HceZyx=rB7UgkK!*T>^YvO+RnvO$LSXL;^ zDTo)mW%$yn_-p|iSN^NPqP)zhIOs7D%)B-!Q*4vUL^4PsD zv?>o^wWbGw|D;x@HsB4%4`LF^EJYNDz*;hlc3I%FBs`kY%~+*ZfFL=D16bYYsbntc zcNkl@x^}smtb#-kQog9Uh_EGZ_A1TTlf88w{m>in(Udp{;E+#e6}ZMcS@O}A56JTI z*y2dB#fS6&NpaYiKd6V`dShDah}Ak%L~;nODN~%PRM zsh!4L(J~R1`A1Xq6858;+9^g7EgFPB0Ej?$zotlKm)l8qkDi~f>N86AR-sD-q=29j zmnpf?I28?dVX)t_8sODQ*q3%0aKnTf)=(HSz~_f=fN=2GCJQtuwsdgg8jw~*JU@LM ziwD>iG;$cNS%{2@VgDeG!}%L38^7al6S;+mh(ILUJ{Q$MJF*|EekgP9tGYPgH&X3! zz^~WJ#9C{#z@~}AmU_%d=A~oR8(>@18##>DEXK?OWxt2q(DKD%`?!VLV34G088j=TMq*k>jVAHWz$KqiUt6;Tlf``qd8t2h@mmnO)>ynMlhz3}X(k z{7+B7`H!F3Y7{6sutZU(LVd;;h6Iok4u3^Cl0$H^LggJ zBs6`A0?#rP34r7PDnNB1HcAfVv}87w0U1-02lj`U6s;nJcEe3|d~m>cR5~01{xv0& zvMQYQa-qDso?~?~S3w{Efp7@<#BLQXb9O(X1m+w^BF3$2lhs8uN9c%7D-J~a$!6zg zk4y=6a|+8(otG%h@=E4t!$+iq@jG}*j!3E*H97AE1JOk!um+U?5~3lgK-Z4u=mCzKaOsJ*HH^E_N)mIRMryV;+Y7%(lxi-Zob5_o56aG@Sc z7h2#aRvFt70s)a6Pzhi|!s5^chb`dCkyA_f zg;}D^mT?%e2&fzG;znCW`eeb)=G#QdI80eYDb<^buo(lpJ|b?92zO|W9fPB{WMRVy z_Rd6jAql4`Zc{b*TF6tYqIzY_KhdOa-w=7U-GTyH7CQ)K69r9NF!jjA3T2}nZcjRhbk#R-?mW7I8*V78{9rZ3*us<`qE19W zBm!au?12P=%|cKlp}m4ThQtrfO(oduByr-7CyBF;KqtNU+j8&3Zs>2CO+yVQiU30p zj5Z}z1F{ow;chylKTy=vfl2*2+t+erkq3YWjU&|&{L?A0n^{;uUns^E3vE5ej-Y4S{+ zAAaaXKgOXeVyGh!B7jtY>qPeND5Q@2!(~s<$VD5F5JaMt=_+`g`C@wN3UrEK3**xh zt4Cor1S-_=Bq)Hxt{oBJppnVJRYLAr78rq&U9Z&to1TjzFkU8qjJV1`d)6U|b{Vu) z`94qYTx1Zih=f&wX(|GCQkpDG>6If|RqIhoC|XgF)0r@u?6A5q6dU65ukDhPbFMg} zp5xKJzG#fxP!QZ(CeC2&=z;M@Yjqp3Xd3czP&RGCURJOJ{|sVG&PnJKG~u0u zz1}VH0=E&uJQ3yzny?zB}fYaS0PM_I)voPG;tQu)8;V@;vZx+8$v4-%XHA*^$JP;DcP=wTp-IE&x zkFco6a!D)DlOSaAD}m}nP@bkV!0p;O<5#**N6pnTv|I;(CNsMV??8%-SqAFGuu&*L z+cV*fP~cC$3{#k|6_YS9gmqR0pM&Es(wO^NKya0qyXoSywA|Ej!?DUa13=uA000e4 zH&Vr!MLr=j620OCD4#I8s07%F@|I?<=Vq>R7L}Job5Lp3!R*AHFDckY9v!b3^W_eJ z0ZVcbQYUsPD=O3}m6&lzB9wBGzNiG)jWTbn%Ia{WH<4&VYbZ*v3^`I4J3hF}$NJ}P8AoSCVXmVq7K)UVl)~uFLw4lJ( zWWqGN-NpbAQMT0>N);kAQd^fbf4^I=JGoh{(k$a>!$Lhu8T#Y4xMZbhpP+rdG$n^( z$|&EyiX9W{%NO+BwAf?P5F9~gNK-UkZE=$3R2NgVcC^`Hm`eMa*G2u7};vgKyNu-9v6Dk*GT5a^^c4dS207+XS{BF$B{Bh0` z#|X#PS#nUAzj@V@YrsJrh1^w+gnB;jz@Lp63Q~m>^>9sv5DNzQIF!9QO08eW4Ig4L z<04iE!s0*-KVW+!zGfez5#&4a3uxMtg1*OyoS<8z%r3*HbL9^b6AC_1u0Bn z^uh5%DE$wO!|iytoaK3bX7zGvuhJ<2AR2)Dup1}Xp(iHh4gHbz%nkMqigg1L$RW0F zT(#n0b?b1Ms#Z`XjxsVcqTDr?+Gq(OAT3ejPQnyXv|NFv4ISw}p8I06OGx^(m$oYr z8IuZA9GFU@&8xN5SIR6AX3IFbv2eEFRhgzEu_4lY9j=z_cp{j_CH}R|PXoX?Uf|yz z=vW+(uVmA5{-l!2*w2*CtM3QRo<}z{W$wYPRb2U=VfPnLzLua0oITcl8qQO}INVxu z3a1%s(;&J%GVLR7?lDH=kSX)%N%3COx!W ziKrPVzA;wB&~ZFJ-N58>)7dt0w<#NboBSd-T1H_+!Jzfm4Ypx3PZ(?AK%^VYj?O}0 zou69fC+F(m!ymj$RAHJZ)AFDri~T_x$jU3UmfHTHF;vL8GR$V|#o`0C_JB{$(f^1S z8Fcy^@;z9_V9dOXg-_H;EI2H2aR{#^PjKl;H#?lB{rAtG7SH&|4}(L)HSoHQ8rV`W2*u=b#pB zujJ3>*{Bwx{FF2z(W^k`_2g8mRQU`wr_)(4>5)v#)40!Pp^$W-rlC3pc>om(YM-Yh zg|c(em`rD}3gs}}Z8kJbOQ}pz*r$L zTU&X+1eRy2F09$QlL1)4%2CzFR*O$6Aojs_-0_40RfKW?t}l|#fBY8T;A$>OQ!TbuZP24M(=Kp?^9|iosj~~Q4#FZfx*QCo z^ctc&S0w@@;UJq6Y9<*js}twAP7wEf(a?({*1)i`knsQl?(&il#EQm4RBidmrwaCW zz?clKEVs{5KKK#_;V`PvwdHDe0p}sfgGHQemT$`G{(NbJnCZU+_elI@iH%`aRd7$w zFtuE#6rSw64)sQ3)_{r`kct8B80*My#@x&A< zXT}%OVh+NJmuDK%#NRwd-kz_)#BIxb3v&Z@A~oP96E-0y?t!0Ff#JKX`Ht%g4On-U z;zGun$%3{Yy2;)-y*Stk?=n2X;fbZgI@STCU=I|G2A-=F93GMIa;)8@h+XJ5o~YWb zuvSRqfiXGmK_cbE#EErYItU^`h)*L%@=$y_;BD0^8+j-{5rB@S6CL&nqodM606Lm3 zbU=#{^b8WqXNnSX(hCbuoY$2c@3ixD;st-jPj1O z#L|)rAFpyxy8;`I4$mI}!9D2#>?D~nI}wtN#L&9KP|*#xPpG2ToA-IUaoV2)r@i8| z@6HMLGs_uZF~ID=2=n+%aVNH%vlh?@Kok5=K<`ZzF%o@df(TSqS`+~HCPS9EesF#| zNLW=u6e0JZgjgVPmd}Y;(^3DR)PnC%m~(qngeH~GN)_b(lsLPmMMzQoxKx7fA_ktO zG`mc80ZqrGFOd+A1pjtnrbdy+B&CM6^Fi${oa7ksn3Uv@wHmN)Kxmh_xSAU?6{ocE zVV2h~-8iP9*MwCeB_vl&+lZsMm6tf20v7-1fhL9;r)Nh~z~Ud>Y4Xc%08TU6 zUnhxnnIAr%8*P3WVZvV`VIBbQR}oC?oVDNpHLyMN{~0&&(mS>YIm$BZzU1)yR28-J zltLl|O)hk}y-RTJg+eKz0j&VvyDomp_xPN-Dt^j$D_tA_?_H5}pJsG>=6VQ_CD2E; zNN{Pg>v>R=F8MEUc{gnO5|@v&PH;)GOrasW1lJ*pn|Revq*;0Sq8g0>o!CF&EwvBu zXaxQk02AJbZRsn#A}ezR>rG$>m<1V?eht%&%UbDjp?=NRG|ZCyC2adS>0VBdl=mC5 zJc^DSLq`-kc`&dV;Q04El|W?hr@I1buY!Xc44Qa{8)oD`~V#HWbdC zlTO10IpIQ7+-b(JXcm8nW~KcW&}FCy$nn%Nem$&jdQm{qh6xA8Y8&5kvr{|&2Vld$ z*6bf3YQgF`i8oG4lkfhZMgAR zc#cN{cW;{&w@tY10+Lot#H1bHuxp)*m`)VdOK4i~f&H{$Mr(AZni!T7^NC1pLsGvF<$2heoR#h$m2vBGBPRF>>=HH&0|kQ<*;k&5R@c z$wV849$G$RRt?CE6nphjIJH4T*^&olP`Qr@0YZdTJ~b5G4{+`dLyekA%hiC)NQp7G zCNWETA|nRIhX@YTm-x_+G*qN6%D_V>^81`CtV?8O#UUxxu1+Bj{qWd_Y4Bu*Fh+FqYeG0=F=P z;|<*#Ee_py@Geb_QLOm?-PAI} z8i=+$U}8;pCxe|qPSY1ne}p&!A`$R~09CM>P?XUCFJV`~bCm3dsnfM!gwl>YFhU87 z`B;0v8ycmuBM*#F4%hg|5t=Oj$dSR~180{6>Co`N5J5xAq!G|TM}Bgc=EFYf-co-* z4)*HJAbKZ1{OkpMDMKvXhOMOz{hCT(1c9k{nYcv&BNA8>^sS|_*iD;&Z%{^k%%sXe9aC$I)7+V4bL|pm#@HWX=bDXe%BciSa!6f ztj_SzkCj+}bjdF@U$%y;m&8rNA(jEJA_%!AittNf{hE%bJ+G$k_(AxXy!@;2kCqqd zg~8G^NJ0wEe+^d}LzlN2!h<^0JMV6%NOkX-6Ryp zvfUuHO@$XS*p94&D5L7ePfO_1>ykhvtAi|1D$BLZ0^-rnW#-R(JP0B~|-y|a~zzR%}L z7rf=#bPlWvZQoijc?(sbY?K%+akC^(5o_L*$`{2Q!ndQ>-+V9ixs|7C!8lm?Ryicl zpJZ~qnRX58bY7)j+HJNT5$h;S<4#^@U0}$?GZ@htSs#QeN56FJ{plrs;;<%AvQUD$mCVmWsZ6PLp<(Nlu>2K9-sLY(r|B|A z8v^7N6T>G82I4CN9$b~CyS>L!g>MTl0Ukz`o#)eVbAD<_T=HNL_F{u7Ya^U*xlkDO zvQY(mAcdMw(b`o-;F1S}uunJ9`4tEO3jPj5diRLI$j|o$A`t4YkVDWGv^mG>qNoi= zcmR43ZFutZ=jH|W^+>+XF7eMzBd(1^sya-7w4Ou<%~)C?4IDRl9;w;R0T2YBqv_6R z^!XS;tuQ5Xh^;eGQ0sE+3y2xi_}{_L z+gLLOC9(nzP6%kwdZU{}5mS)Kn4SY$Ktnj199RdgnVT87#OR+Lvrm+Lg{3{AjJ4rt zHd-&2o9A6ug~X7J^p{}b1EwD$(tm@pT!v!E#J~kbKLn(~=1wMrb9y%xM+5D-Qf6e_ zt_$(m7$WZ}HLP>n0_1%kFw(3B)uAQskLEqNZUC7n(KAy<4Z&Ih%FVO@$ai!!3ii|k zPHxR0hSPDpPLm+EeZy8^Yjp^%NboDR&x>g?jE+VKRf)ggV$NV#SeB#v(ANnqcJ~;gm5H;ArY4ezpjv+f@;WI z1gF=NvPYI1FA%+`0Mv?-ex+&WxKCF46_b^E$C;Qe!5fNEm$h{|ANn2K_&{N9I=k2p zBeaW88QLwvXv^WbS=HS$1F)(?Fhq~7-*Fy)?6wph{sS;A$Q0^zlO1*akt$T^t@hO& zBh{vpHVn5uI&H_8$~jlN^>>+NVN@71|U4g>Wc* zamoR>j%4Z2m~LQU9E|SmQk$*#GFb_QC@2TuI+BHmY~%4m>+q)|3+@hJJRZV6|LDlY zZiBYW#Kej0F%}8kQ2+ag^@Icm3N9&{RM)WG#DJ|M9cTCO_Hj6LyO*j5*lu2Pum!dpq*B5oG6vbKMI- zy3ufAnrv9Zdo(&k0;mCINH2_>EODQnd^w)9AEC<~Mnr&69aRP52*CDKN2ORrttF+{ z-3O8DK~x<#u{d}q>=I#@f3(D&(Vv-p#K z5oV@xLb>q@K#eK!k&%>Umo|rRlvScxZ9YVkW$`bvj!{+NW<%r5!KtlZ{?&wpOu>*q zg>k}-^O-hgyojYWqacWWIPN1xyg{erwpjrHA_0JZ^ov7G-PmnjcHt}aAR^;)vw4l) z`3VR_K&ZmimZS8I4^vne&}N*{DLwWB42-uU%V{0HwtQ)bqpj@i=m!lKp7%(TTPMnT z2~As4e5wF8;DAKzMQ@oFw+~%cBJ3X^>PC&F!;=B9u(@mlvDJS>iI6m2*HJ0N!ni0FXI7~8UszSAW6|Kz=ka;rGIu6rxK0cF(cf38{k zZYW}i!ygQ84oTcszx5p?#K}fN2;8fxdl);m^t>-1bo`Vhsai)U-^shbEM%2P)`zl$ z3W94|*YPC{Y=mM{D`@Tlp)}4W!I0+1tZNnYtFmI0%t^+HU5Tb(+3SydVrP4Y*w~Z_S@~ z@nFn8CMZ6^e1iCJQ#W2SrTDBBt+7reeycKygwDBiQ4)6WOL?TAlv!P1hto24AI|XI z*xnKdBw%C#Q|ak_R4*K(-^eE}O9;_43lQnOKtvZ^YdkF5hBD)g&pDeNtWqRu3wCzt z^nlWf@cji(30)`fK+T`Oj@SJ8Yux;4nyk3gw-{WP&Ca(Bqj$&@s=)4yP=tZf9r{zc zc>VFFA}v?lPfB}!fh^oC)~C|v*Em5fVPELXf2LczxG`u#3r@Fh_WozO*{dK1?cX7r ztI`9Nd;d9Jxi_-w?>0N@v22^!0v>Gv20gs4Cl5@LEtEOk6Ux0VH7RTwZT7RD1;M+>q9#tD{BVDYCHa@I3hObkaQ-1Ji24@q0a;ZGvB7*!XEhM0j{ zAyr)txI0LAk?Lxt>_Af+gi^o^sa ze57%+G=8DI0LV#2aM3vpa$XL+xX$McYFpVuu;gCESLno1eY{(m$(acwvvK}Q$+0uC zUfLDrNNi-%9WEuzk`2jZ;#snkB+ukEQNT_svZ*33+j`S`M&~BF+$IPX!*fx+C9%pv z9V}(rwg^QworP^7*~*4&6knjYdT%rNrEht6ZB`*cR>)FdmIAXBD$6+*2Q5pZ<*k>1 zISq1l01j&VgyGArhJZ@+5p6ZOILH&djnuDf1Pt@E3hguk{=rBiNt9$&ihHtzre^@r z0gRBA=*n)gY>}sS2|gF7mf$i6fjPiGOOcjd5AH}-XTK_c@2Z{0f4jwX4a?8U_VO7u z`S;Rsvfq2;)?%E15C9<jui%G#nhF;f8t*Z|P~Mk`g925IyrW`My9 zY7wI&TB}X|v%fO=59v}g3sn8xrq#Whbh6o=82x-K!eI;mH;l?ZK(w3r0^{OSopCff zdHe3f$^7-iX`^eg59F+jrbF(diso-}-X_RGHKSD;)WSD9kT;)kF@d2v9*hodg)s~0 zDmdt}lu4}~SEW2rxX-zqIElW_-d+n+rgUz-F6EK_kjqjYY4^P=nH(2UrcddP5rR_ z$nrdbk2h}Hj&g=R{2V_%X$B_+aFO8t^L20uaM^O!9mNt6>9uT9=ze1koB1^Mwa;O8 zPLpXy%n*oHwl&H;JKqCwW0r!mH1l`iuRr6z{_p<=B3GH=%OYASjSs4H z!xBklW@K!MOy}kuR?C(jAKO#Yg#q~q$giLM685%$@-->j_dL}RPBv^1_A=D5Oen)% z_SW$HF50ZQ-ACtl*y=X2UXIO!pC5UA323~Ls~`7G8KF*rDPsM3sA#r%oO2N2kY{6< zx2JP5k`(OBj0CMdKjF`c*K-m240!Xs&5Gtfe+9IiGW6p*!-q1R(cL|-&!6cvrOD73 zj*=#x!uF~3Cuse^GN~U3O1WKIZJvYw1dTO=W6ZWhEJoQ>qg(vC*??FtH#L{y#IM%0 z+3ov(cARU{!`}iCSE)`@c-ON+-Jd#pTkbslPVVWihw?v+Tl52z>(Gp?CdMB!`@oFcCA7itJr>})m zb3lTD)J~oq-5QlTd}e(JjRsD4xJDl=L$iogySYH%QL$?cHct|*?!!BlE(!irGX@kv^L>_ZHno_}7W6}k>oAXUiC+Sug zTFqW^YCo9ycC%c%nM-37cQF#|J*`7a;arCp?lPbkDzpg9vyZSufZB(Jsz7hstx*`{ zWL4)KQqmGU`cfELS_;THn%OU*hp+2JSp3JOM(f{M@=xSxXZaMgt?xeJ*PMYixzTy# z&oTDOFNV3iRPlB3q$`<@+OJ5>+PBd)z@YFjzd~WqoU17|2Z^^Tg*}QdRk?#O%3MI^ z9mf(8H=1@e72PIon=-27BEs-w8_%D503rZHNDyIwOu%`OTV#7`l({R6m1=afIh7s< zq1i{B?gmgYIa1DP4s(HeUM!{8N-bV-WdCd$X0>SPQCcLoQdZD_l~-Cr;OMxnvrBP> z(Ng~OX=k)b2@80Y>LLN^+wjLp2K6aDsZ5|%%G5XNy<~L&`e>D)=M&+!RKvMHoej5C z$6HdBHyq7`KiB)p5&$DQm_C~?wJO24bKIntC#yXJiB?QPGpPgbs?k)QHSr`+YH0Cz z`VF)Jst+sF7o)m~bNQ_KcoJVDfCc)Ch$IMUiqnUF@LJ-3XHdI`zzQERJjZsnU`QpH zYKGQ_5kF~m`D{{S`vThBac3tKR=4>AW6kl}c>#k*wNq^aaHA@PX1TLu%-vT#qc4TS zFYYW!1%d2IGtpcmLMaF>ZWXXJygVk*;Z#pUq!qGM64p;+@A7o8*p<)VX?e=Br@?;1 zXjG?^aC&eqY7Njuo#NYw#7YFE)OC?hp}9zGO8F#?_P-gCV|+_am;!%o*RwUk@pJx6eMwg1j*+k={kJnCS*#AP$LLyEMW+C43F-!r%JJV9;b3QJqh zRJoz!aLvRS4nfD%P&AlUs-uOxysyPaR%A8Aki+d}=xe)brX|PR08wp_DYvTx7|{lP3YU61a3`sN!3zT2-b+AyB>fO;P;gj34?YQc3 zJxSo$T%1;ec>ItL*SR*W3-2|qgAmJzZk1Ns$H4{?U7rZkf~bl{dv*rx2A&p2l}Orf zbkG~zXY?RCtLrv&=l3Jk#50sVp!A?yw@I=B=BHnFE{aIJZc1f^&`|Td9&D)F^I4|! z(dNgw1UjbSWiUZ&>4AEz9ihDD+EX^{U1(3a5U$YHW5wo#x?LU6quS=YKXX*OPx?UprMjJ?bCo8t z(U!$Im=OQeS$yTcKi^4kkOD4?`;ix5d|jp%elNP;<97^##oG)%Q?hh+*VcM^^Sc(# z>zdH^k>x2YJxEl0L=BSRf`@0(yCy_Jf)S>uH^p-nJOjteG%(gz$LUo!ELX~Yk1L(| zdbEBFs8jj9CVJLP-bp^u38L#rA4oUHj?+pOL`hb^egDM=PpcMi4+`H$Ni8kGP(LsVjm(ABr`k1OWOJeIIL49r$aT%*AB zblo!j7QU$7EY)NF#z9k;JAP5;ZYH}>3GHb5#zD*FMS6y*?|0i1W8H~s>fyasyO|=) zFod#`^z4znm5e3pttW7ouCm?=+hwKoSjPD?&I+~PFusX!pQ;@cVeb0c!Z(C6rgN59 z@2sn4F|~t&o$e39ce_alyeyLR8W#zDt);CgRhnr`liYz=>*g1?b!#k4ld|e z6}*IhH^CZ&pRA`LnFn!d51!r6?oh1SJ4m0{XXFlMv^B=2QQPkArd@K-7Jd{2^)%qJ zSB7P;tUulI?2NaG-dAw9a+UU1G>}AxGzsdeI!Ws$D>$(yp@O#;Y|u~~;US*0Obl1R z;jXyl8p&JalJ0zp2~4O>d=X4A*aK)+9}c9vuhok>=#b*2lsk(f62T+}ma{az2p}lb z&eHf&B@zQ`t+eA@nv-{<&)bmGdKRZOm{J1ipj>rzwT>m>(6^V@Rg*N&7;&N4uA>D9 zXrjElY~O9v^{-&20y6FLw*pCFWH5qLAjjdE^I&JFMcEycyA)-WMwh&N=MCy3+&4O)20{>ofKOXN2y<-K8X;s3G*5P@u&cbgTKkB` zR?hHI8&J)#DsURItktqZRcE$(n;j1FHfE0T2Qq1Vha8 zSu3sLkB{se(V**-WdptiQr@D=dqm?Pr>Jjn6}Dbr=4u?^cOa2l^c)#3*;66EXp{k( zo4(+aF9)JrE2w)EVdLXVp72i1Jn zn(v-94=23QM3aLVFr21GQ)Eul?B1-ww{_aQRc#)4hR}0M6r3P9{MVcEqHGR%BS%kx}obC()Hqb1^nJu014-1`s%z-5UvnR zLz&5L8dd)MxlW2voTMT`8*kv!zIZn1RZhqVyQyYE7=Hjl zcr{8dBosurSj!r}sR#wc#ub`* zCO>T}E9(Sm%Ie-{o0l7CE;T<@o%tu+3ms{bn|iAP{+B-mKM;Bw_%1bKqFc@0kP(*X zu#Dw#fHlYKYyh4~%WHol(Rc^+E_}P|eJl<@soYdAKULOPcgb#~7rJQ)OT7@C*=75H z{(&nTp&U#}$_oY|O6simS~Aa^e+#-P{?a0){44ldFGq`c=CjZ+0FQ&9IN+{!_7124 zCJP_{j^t{9X)YIq&j$)UE8+~F4i6f{pYq}@LEw9f6_K=Qvf@&|@!L(|XYv**Z+=}T zL{)w zl(19{AF4eXt#wV|2*445BjQ>x&IIBL!59dNKiX3%29Z)6Ce#XT`Z|x$vul~`DpBLm zX+@j~#1(>hZ$fN@!Av=Mfh1|(#JgU@yo;iOf-9M!W^q?o(5*^@pUS#={bUBh*7CcR zlbA09O=2#Sjm75Z3a6qkQf2w3MX*6&T+~D$G{G#Jy-Yk_GRxkpG>-VLb2S)TB|xzq zn4hy$x!aS&6o)v^Benb{s0NsDAv4ztf-v(bA`4yNnjmZ2A7QKJ>+0nOE6gQZx-qy< zYM9>E+Y)>TVxfd@^tbdE63BRI-S$d2U!n8G#`~#{;h-Zc_R*p9B^`YX2OU-VUkK$Q zZ~t%_C{HMdPF?mE?9@IAawAZ)Xu({dNyeqKfIPCgO!zU_q3FM*=S=<`=9vA;pFiD< zZ#BJ{slZHm%RK|Y0l!Kw>2El2$`1c^fR%5yFWY;(@(q{job>~0n5YN=*8!UrF?_`5 zrmXHtRaRuz_6iGVaKNf5BUvLqe?_1I2flg0Bt(6Me@|Q;g1t&g3$z{ z!AKmU2}Bd+hXAxi0u~wsudq8N#lL@cQvE3J<+-T9nkSZ*%jNQ90p1qWI$w3LkX{|f zIAj{g@P4M&jt`i_Sw7(Q2Y$`rn zh~%t{r+8e;B9rI8-a{JCJkbavsDaYd(kbZ5+U7zDTP59CE zR>uuTla88CxL(UV*g5I7q-!{@0OyJx_yy7w#{TqP)9!fe9;lG~b;-BaW;)+qdzhV-L z-x3OH0(7ATx^z$_+0#xXKPrd$a=+5E9;F^S6Nh=eD^A%gjonovGtU0MSZu33#&p}9)FYLS1zrxznB!SaRk0r{iNB*~y=x5BodXLYz zHx>0+#7~1Mzz>*wxH{wx5IcXr!7Hx}?eU%|f~BR$Ad0(NM}j8u+xoV<&5nncDvD4Rp;T+OXmPLnKlaux+3@FHP(G=T}sw#uVh z{5VXP7fP9tz>HLmOE}9sJKtgbJMI0j5hUg!x?J?pa>Pz(y(y%bk8x}PT4lvKyT$p& zoLx5E;Sx&qWvtz1$ySsq8?4$wHk_R$;(dF(#g*C)t8G4EuWE=xk!%jw%dGd#9lHB` z%ZI?U2IF(gyN_a}iT7oz(rekI+chsVFYggY`CG0)8=P@!(&liX2w>; zn^wRzt9AWb^KD$S)r3wJTTKbI%xv>=11hyL7s||vPu4C2JQrkT7ILA?toVZ6?%jRv zc7sC=HGq>)J%wy+edKun+xy5<5FjI!r4{JLRcOuWQiI0VExT6n)wW}mvVodnwpIHG zUv(Dh;1Szk;t5Z;mc{(-?fb7dzkSB^5qzTN*~Y*41q_+|Jl^>*i$7f!e>e-{qlJ10 zGiZ3iHY6Z8O)JOZPe8{Iy>vluGM!9I) z#`(Lsr_$;EqD-9DA+!#4@W{2YZBn@YdC`uZ{C&&YOIFa{V^4>5AQ8ML;J`YH9sUab zGR+`527Rd0zfR=PFI0t?;Y??Mso)Uts>i z%zs4Wq=+iA`>0WQ_eh*n$g1vVne3xN<=rE3QbDn{A$A^2;(8h?V>3m>A&5i8u$?1x zQYx!8-?Dud6>0NRR$mcqR=bN>MiGp{Tj-u1aau}Fe{W@-?sTV7=K5Cavsen@rul-H zj2j5yP6CyApT$xLRCnSh!;StGK*bAk<=icq>(4lmSL8nCQ|8+IKFbmziL5bU0NA(k zmeTaw5E68@qRUnv*>TH^Z}H_>Z zZ!aa7IqNV$>{Zn92ki2FNq4)vfPi9cu%023gQ&p09|J*VpNDNX$3RH?vEz+Txho+C zioFnsge~thm~157pua&l!}NMv?rI8+uV2@!K7fBNK@UH zEdTQ-T655`fH}*k|E`z&%>gbEs`dznO>3aJGwA3p>pH5X;a9ky=^G(rPgCjcZ%HMH zN@%JF#4CJ&UK+iPy82taqXp1?q<4DnfTlTD2-mw9?J3rP=nhwiQg0*GsFy!Xc2%g} z3PmJ{Xohr^c20`hBsRq5+iZ*otcxtNHDq(eySmTq#_pSjjaaZIqeX}rs8Iy*;; z<}_|5kRZAVk3*_8(o`C6Dh}_{Me@L_+ICT^T$(Su&mJ{e}04;uA|xDUYnM> zUx7L;Ru!jxKqtZ6?SKh{tK5uqa^DDIaP4MHwn8ALf{7gA5*{*ba3MdEz2D{hNVxZl z+7O@q-qMgmW6y;XksdGW@cal4ID_lCRs~}*m20H%8rNSqYZ@1jEZ8(+GkHX!7PTmn zNxl_IDIreh?fZWsJt9_rMR{86D7}X!zW=8ixvRefpp9w$8S%fzHR6BAM*OcIBlc{% zTCLH0D&YWa)Xf^FM{1RB*c?4hJ9MK4s1{g%Ww$8%h~jtXK-^a%u!!tKD3%`DN}0G! zXlHrMfG{tB+;Epxmg3g@s$A;sEWYL4(OdONoPDMSC`OnL)R42TW^paS}%; z#QQ`W6x{>K-1ir1K>V3r?wv#&1I>rh1Jp4GV*S@Nd2#%nBh0);UL*8~Ttw|s+AP`P zXM&g)d4w-LIAba#ItV*@{5LE&Wr_dVu|n+dDofIxXB{XE-}4TJ@($1yFqG=MPoQoB zt-kFfsVo;RyjAnn)KEZKH$kVt_VX8ze>dT^N8_TVX>Rqdasa^4)8lav1mTeO)C2i< za(Xl_YA^!zj%qM4wpL@(xTvAN`YQdYvM`IMhjM8AKmqi590bwomi_e5avPO@kH$%j zG0S)tHOB1cQ;)_$%?=9|26~2z6C4&K3^g^f85m)Q#U6SfccF6EB+GO()m^Qmfk#%I z<|(Ec;h!Q5gwZsxG$%~WzXPHtss^GzQG^Z!^c30FgQC4+Dp?v_t;|}etPKoX1!|R1 zmdG|oz_n1O6tx5j81y=*DKJ-YJL?)sa#RB~-w3O|IDrCEdWS8v%a4!H$^b<$3g2je zA^_z9ri)@u2|tE(WHQ}CuJ15W6^7C$n7XxGz+?jY5)I2U!uH`VP>oPVr>=o$ts8z! zzkT~CVI@a!ksX0x0(w~^C0O6=j1a5kN_`RXfr4F8dtGx1z_`8!NbZ{U7!#S)HXaz6 zL3Gzj@r}YQkDL}t6naVI6r7919Up>&=ia(U9sU|CsCsqmK7z0s-7!poxrqz3Qe`Aa z=Bof4!7*@B9fe&+Jt2fR%$3x0fNnx>RaRD6bKggv&b<%gBt=!4-s3vmuc~O)lz^nG zAojRcDcrR#yiEldj-kNOm-9}Qs3FF>y3{~YbHAnPM+cCGI4Vq^$>tAz9g8%G5sRme1-Bj^8D=y)PRfha( zL5j6ic;xV24*~1bM2>Z5cqH=P6TNEm2yC%36%qb+ON%7>jXuiiC$19M_)E63g6UHc z;t@mq3XOrCdZ!sYYn83M{dvM#xEV91J_(@a&k?`~$yQUyIoE~~m95PL>oIk-V4u|O z;MM-AeHd3Muz%=B*%nC@ej3w+HrUk^aR#%Rp5VHw*BhNhwA0yMvNeZbjyXVUp=DJE z7X^93Ken)GT;=x&=R;FZOlWch5I8%V0Y*r+nl9DttR|qqI8~hne>?$#niC@!VFJrm z*=JmT?sT3YYjuVU3>O95`6$0u@GLdhBq*(#EUovD#a)mck1%*9DZQxfl3TcKa47j5 znC&*hPqmj{S-Pu61fZAILf+!0sj~OB+4-^s7Gbb-hwCKi}CMxD^!Prg2lmj@qgT95_W9dbsMMou~6oi1GE(i^SI5v`OpGkQn@)^a2f)+2-;^4 zz@aR!8~+U@*05>jHA>nl-s1)YeS{!1!UUFsGD<-u&>zm?7Nq>v4to1w{Xs5n1p?R% z@HS5DGrqUXjH!XY9`<@mj&UOlyx8j2P?5XB&NLrI7^jKnJs4XlTubIE^DfU-uE29` zo)9Lmtd)fioMehx9W|*ZCE!xQo(Rs@YI&dmzGKV7Y;LS*W9-_u>9HqM%?Q6{RaI7W zOYJbrK}}gF452Y*@ElcADr&H6pV@{JC~IdRK2Wz)AKC+ver768Nv_%g>M+qqwNHb0 zN;ESG%`}AcAsnWFrvQ>bZNE_+UvGJu96>b#2?f-{v)7Bu(08eT@QQfK+XS%{u~WNU zW$V~VZ*P}M-kbcL;Ex2(Ua0H`IF4%gD?FDh^xB8MH2w;+sAUj+G%qswU7Y9TkGzD# zFNT;D%{#@oqI_3Nh0fhoo*TE$AI$zQPqWvWMYF7~ax=xWQ z6kzSpzOqztg(N1Ht-zl&yL>{}KCU4JFL)Jz&;TQ(#hU0t)fY53#9<-PMOP1Eb{wXj zU0>lUH@g&N#RHDrmN-#X&_~!WW3^^*y?8>Ru9Xf+WZc`Lkrtw_GzLOo%Or7lc274T zFn7ko$0G}-AYFB|f@k0dQ2*Ba!3k#54&9DyU;X$!;IFA84hH0jf@T><(+9swbXFq{U?JLHWW*j}1 zqi_V`EI@WN!*0ggdbRd*+WNd}V{5EeuuXK0rz&mZHEbf(Td<93xz1g+{>1w3rX31h z8DhkT&~1W@JQuph6})B4k-p;9!6(+s-75SOH`S6Ew~YmtE|r? ztP;o%{&D5as-~4STd2K<1*eP!W!%G9EG5tChK|DZ0YILD^Q4!>xU^yI8G@?TX;EiQ z7BF%W?MDlli!Sp%i>(muhsAdRhtOCFcM7ZA2Q-#icG=;t6XR!RzfKO!R|+fvvxI*v zQA38d?`T!7wrzQ04!Q!FfbEwXcc^qvgdQX8xCI{?Gn7ms7e#dnoo>+M8aNP|p-MBT zxM-7Pw(53~!~8D5yX?Q-q5Iijy_2)s90`)+Z#o3YspF3{@KPfZ0vYq-qK?55zR6+7 zjiO0^o51ukXQ6U7DD3(9hxnfNKY&{tHs207%&0|8;D`=KANkR#np6kT+2QD;B|2qV zve`S)a#|WbfD(vYnoV4Iuvlf)K&{+>qtW>mhQ(mJc4l{RAE5K|8W@{kWkeA=E z2tVKD1aRKn85qLY2&rT_BlYj0gR<^q3{aM+fPUvU`Y@vCf}8aqLssfT&Z?L3B3$ZZ z<{ti#Jx~3dU3#Ae?4cse48X0P_WWDu(hn9<*T~Z@qOJ)mZ-dxs_J$u`l`X&8Qil$2 zW1_DLZ)1v3@hy0(bNSu2z}aOXcU3Z#sW==ABoGMZ!fBwV$gUn_wO(?}+gk=(sx;+5 zR_n#lyagxWJi&EUuVzg2Y~4${n%2>~3QiAEZFL76MKcwuY%PY(dei~O%X-)@llV;V zzz>s zUpRBni;Jwl`?(C&tk}V2s6UMpNuKSU@r2SvXKpr{jNLAUtMn| z%mXn^d;=-e*_E^`<3 z1rgDFaxagB#>BFqOJR%2_=dO?2j%JQ;EvqSkK?l#%PyW?@yz|%G`CAyq*Zo?5Ee9n z8Am3ldw(1Hsa$Eega#N+27#jn{yI-n+_HbiNM6_n^dgO?-DGhLfg;g!!7I07Bt(0E2GoG6MxVB8Df7>olu2(oS+!>uo}5 zk8GVtS=W)z=>1fx{HJ90*<5vUi+A5Em|NuYm+&m*O4++z#DhMFDY0HIV@_+o>zIX3 zU&xbm)>83poi?%=n{U-B8i-R_3d$1ywdO9$CgAyyxv}{=Uu)vB3J6?Q%&T&Jjw@Sv zw^l^}hPJ9|6h_tW^Lb_Neq-jIHFmjsM$$TGpVR?Qv~ha5DGXS_}7*Y zHwSvMjKvqFkry8=rNs^leKxQUWJ_S|uh5qSW!KN$3Y=F=!SUwQyiyDEGV-imStub2OGcKM>q<;|XnIukWE zzwALcU|TTQLus+ z{F!1X=YS%hnuHl+oas}a!&*k2R^M6pGr`%?i7*+K2!L5bNqNy5a^!h>Ze;a+!LD0c z2B%@Ep_jAJId_|E4O!O6fmvaTPm5nYI_oiwy&~&N%!ihB7Z`;QESql6muF82&CbyW z%f=SJpQo{7&%&p~5IWD^tGK#{rB<2p>k{trmdZl7sM}gU9pu9nYDl!X*McxsT1S_b zrH&zlP!RkT1u@O$B(prn7YIKQCrOpobzps*yHL3gXjUmoe_>+_*?L&8C(wq{6o%dv zXpV1{Slr*33z(gR;_k}|VT#L5VB1{Ey>CMWF}Mw<*_>p`I^g*$sj_c*J?wKU8U&&v z7*VZOxASdj${gLAk2qHg-|-eTMfYygIlk3mCGdYCm~*!0`DF-^_0Md_<|s3P5DB|q z$3QW42~W~FDb>Z=(rTwadAgv**|`RXlaPP1n`|oXeaeVpe9C6%)+lYOd>?U;amTP! zLX+m{6&7UjADxOyorr^!0YJZybSW5zif{~gPenLFtpH3KS4FZifN{H`ByF&5Uhkqy zVTcZ6Oy;VQc66?G5vKE|S7!;f-5%a`tmy*jy7^Qe2n9>DcqTdb6b|!Nfg4qqh)Lq4 z#k!R9CuLRnMswon`_5A6yOuh}Ij>g&ZbEsu2pbDdd{;T3LkkkKf`4rJ-WK1%ejpdE zA)O|r)l?Sus3&4t-PK#twfy${tzuPXCUJT#WreIrZnAm5tg``rZpq%|C@4pva~u#@ z@~a4-5lq#Ju5polq;&`xDr1}}Cc2C#d2B_c7-^fnScecp848tQFMmh)#rLoubbjV` zdAnhg~MsEBNWbKT1N+-?7pz6+~r#^JQtFb zKLyPQjgqW_p%DlK(xeYZBDYl60SN*U1f&NhpAoP2AurEyegyppGNrpmxLWUVPWb$p z_I!$E(O%Rr40v46RN?}O*Gz($1T!t*UTw$^1Zt%?2J)6lv+tP^vOrz{ z4u@FKIFD(4Isp6V9Yj5%%u_n5ezd36rW#eo$%9?1p7V2@rn-T+-sL+C=!Y-1IEWNN z2n8`k=B81$-eg@RO)`vKE^BM@Y|l)k+)|~9ft5(Yn5J`6`fwM5EwwG$hTu8BUfuhV z?@=4nD~~DrK8>Ryj0!c|>fclihL$q6dYfzSKUX1dLiwD8m`K4@VAg`ORurx(u-wm* z9Yo;62yuQjDWpk5V>mi(Tc5&Q6=y{W40AzV;6 zY9aUOF|AhznqxW8Ja)j#HIT>0x)p%u(VZ~E>%oNT2+FG|xs^AKhq>2L?Ch;R$?8*n zb13=EL;35j@Vthp#t`#xZhM(c_2#fMyw1K)SMbjDbUtNXX~hwGIkgK@ zeVmU-{b}E&e{)JVg3J*v)7uU1bx*iAW!ZzkQ9h+kG3ay3L8SUfE3|<%d+LJxSyP9y zOb<-d)E{7v;Tv5XA?r$G2*FpV0WrdI7KnJ~GJiNtXk&efLC+K#LOH8@H0@+#Q7Y>P zb4(UWc(6D1<{CKfN4rf=t6L zMD!y%Y*lYwZ&%stgLzeXkUMo)85W(Dxu+pp>&g;Uc0a)hxW|qil=N*JDwtS%HGG zAM!YA;^kZvA7C$KywHq;6li@DY>q0aB)aRVui+&!fv&HfCa4_6(JG>yP93dg(x=Q| zaZyu#r&V6Yu=Q{eU((PO6P3MC*$;VKG*!5O5Oz*ivHNzZbXB;#5ioSjO2U}qTcqUJaNFhvdIqAEN+rImBO>|2#x^}uEc|hq) z^nP6wQ^HM}C$y^!fZf(q_#4$`mhWT%m+a*N><6bp_NaEfLR!MT$`7c8eB|ZNUgCBB zBcfHZcI|`G;s!lXGjywj@@k(w8obeckwCSOUNff~-^x{up-D0GoLrByt^y*z?6z5Ht&^8uog@odO-2+PR`enk9 z?ps@BM{Z%-W!6&NUH z`TK~|MfWX*e(fX329i0|4=7zkLyU%VcKd|rac$OwE}kG_1Z6KM`yr33rds|Hp3DJe zeUBA!<}l;C&*H46$R0;T-&jwPb#c*$Jg%BTEleHDQ8^nFvm$sn54XB%n>@_qed?uUuc0esV`7CeuKbrWB2y9@3}^wA1g54Y?Hr3fG%Zr1Tshwk*8 z+Z;Hg9^F3+FeaIWKD=T1`O`gq-k>hrD~!X7ohHL-d++&i8nN2S=ikClb$v0G^=Nnw zAHV|xPk)8+`bIor_u)zZ;rw4U9{X9P^=FwU2ePMMzxb~jS=OWr6|FJtD0h%)&#HT; ztZ;uRW4d*mBs{tBjY9iAnIsNL;7+CZBl@<5KKW~0qB8}+p@(^Tcn4!62kmMLEr~1T zPaa*qHT5ZU%K<4&Br9dquul>_8T46Wz`H7H(6Tlm{3a%j=d=}6K` z6+-|Z7Lsq$__h6yeQaeLf@n5o+xhC@gXL)UG@tTR>z_UzC1ui1!w`TjMt z>_r*nB@9wVT~WEmrBjrGIE;cEIA-|lg)Zrz*hZRFf5aG{OS!$Ob*lXJQlHaWcByr1 zDVA04q^LoMd}-qMJbmN1W%U^*ASglRJUce43zU8MXLt$j7gQY(Ujy{Bz z2WnGj1sy?x+mai(Dsq9jng~uBmernpYp}02KhmHU6oowxYJC&sY9QEX;NR)!#+&0S zYMr4vaG_SJgIqj{T)k4J27!x$Cax}NQwM`r`SGh0aue<-(eGwp+1Nw)En_Pd7%`$5JnAKhLJV8ti4=K??Tw zV!oQdUBu()#hdUyk6Qeky2_r!a^HqrWvdR{75t>xWq*Qy+<~74Wb@6eGJFFXQo5ErClThv&pU2qsElcP4G=}ah{L>x8d@(QGFYE#7vyH<1p)TDP z%z&kGiIb?)%N0gSdb_fmKilGK3;L<7`+LMt)?=F0!Zs|C9;&k45@xBME$`unf{eJ> zeUcTD%-H{-FFtcG0(JF`c-CtRfBm_Jsb4H*=dQ4@K~dQ);WWf!xd7aKmfv^J;1OyD0|vbV=m z=RD%!A5RbG$xSdWm^kK%(PpC91Snp?n?q7A_^&lwm{U!6w$r&L%CtzLXMwQ2vdRoO zeB!Cgdl~m+ohL}`VY2eGq7pHw?+PhRXT@mX$m@{ejODWjZ`l zlPE4?_BmTDYUX@BM4J)`Bd`U8v8g%ePL!9Ii)_EXePlV#hKAQJgw97X06l>nur8h}3Z%Kr~1*Eq2q zo)59J``Yg!^Wj$aw$fl_LlPbG>P58PH9Uf~cA$aRbDDz@=`ZXg2;ZbX7$4Z1015w* z3FDx$On-$Hkd%;Vz)yyZ#s{bF(9VWSbvx6jIgEY7JMB5`vGYa`x#%Nr>(3}FWWL{V zZs`f6i(%CA+Sv?9g9hDaNckZxkW^^WN&byYsnPXKDwc zL;8f@P=F@wHcpr!+;3%`o$m*B#f(PJuyZPWy9G7A**Ua(y93o3gj1Bh)$6_$Oz%;k z)4i?Gq?n!#j5@{dW)k#0Oj#5sNd~=iIBmE$mtp@>d4^eZ%)|K@s$_nuYC{tTo2b8r zi-Qf}r)-?mwY`sO3cza_si+@0IZ)=;O5Xc}-W||1-mQfBdbDmvH0MM6FpcNWhVque z9%FfWh1vv0c$1@P%d(^T1eYu`TW=L7D@ztmB0LAZlWkgU+jVlcP|_`<;N4mUMy zcCPl(t$H}^+nYUWZnIgSHtB}r5A|q z3%w=usd{encb++-zV}C`g+n?VPk6C>m5k8IV7G{gq`#wrSky~pCifz~W@orVe&Q;D z4VixLW69Fp@<%?|<2pC;^Z4c{URkrqd4f%Sz+}*ayqw5m^=5Lk*rSs@r#U66)7+Nm6&e zQPg84tG$wGsxPT-|M!KXBD=UW;uZP38W_>!8@Pg7kQ@HLh&i~ux^XF;hB`K|>Q<%D z&d9&hS97Z)?+lwzvw>&EDVYr&bUeR|D+}We4OA@ksY+!tpY{cp2a}b|s7gOzhl=@a zC8pfL^uP@~h}=EF-_vg4Lqcr;6ZZEdF|oI>Z_sq%8A;OD9r+qz1KrGayyv%Y1adqZtF#$|IsSB`kAk zw)!g@{Mg<6v)@(gkcP?1lHCqp!!N;~1;|q;H zrOB6lqb?uQZzPL$y2&WKPH*iNH{Co6z7t`oM+V&#C z&bk)&5nb5Xe-M`xwmB>^664n|Y)UWbZrt4>>=Xb}h3waeuT&-#=WY5j*Nh|%k^zb$)&~vqHL0e^s2UriRM8mg!+*!(xXTv96hz%gnKSlg-LsRmq>Z0 zKPqtrmHH62$-wnS3)HF<|Im;BbSZ)XoT7~ZDnUWN6Un zS(8?A4&F90ndn+3vT!Iahc!hl#bt28;6j5xQ|8IQR9>oye0o5#hc$~K(VUY>?8$_z zxV266E?W~{(R;S2|9u}L5L6(jK+qw%Cqw>j<9u#&<0JT>`1sY6>YuDg%AaFj0mMir z-{7@(AW8i{Ps}tunP4XA|Cuslf+~AqGgJ6`R&KeQDZa*Eu8A=mU;R`-Km69mVXgGuT^xZ~qAi0<`|QDhoEKh_q0!>7bb;ln3{ zzXEVlI7|h}ijN$k?N;z8Ii!A=y26x#C`a60+*ot@hwx$jE%0~TjBfonzkSB~FHUy? z{kUHQV6yxdHzVk`{jva4)qh!eUp#kXO$}i$Q@qw0o?7%7SIJLYrI8Uw=LK9+*k&}K z)07sUUQyE@BMmKI8@Rt$)P;fZk~-`uEk3>CK;Ex)RdK{I^O_p#uyE-gDS1#&qmP_Q zY53`&nue)4c>%v1RMY6Am1Xj*wV45LrQ)%|b=7--$02gzxHogXlj z@T+dGhto}bLh}uHJviXfDi0#v0JCm=)8(A{E#8LJ@RgE>)o_1B+@x@Q+bo-?6kd7{ z(0ub$K92Q%$9a~-O$m#dvm(2;R~PY>5g;GFR8-O_~^OU1(c`@@PwIQVCcSo)*>Ox|@^iXvI|Kf0+opPA3%;e*Szb-?B|VN@gQ+`QBek1CO&80e3Hu@Rb~Ci;iY71v(xYlR z#DAl&_vb5AchFCV^e-ZIw0?}>d*^`XpKoy@Gco-LKh|yD$@Xxo*J%@(yVQ2|M|_J5 zeWV@d1}0`KSb{Y$V3<63L20^rAye#cxvMLX9k5_vT$^RUZ($8?wgS_27EJa&h&hNX z7`Z241A}PSN!mzyflEdi>q{y^Pj1lF@oz9k+qeOg-}TeBPm+0Hxk$T3e^piIKkqc z%HPU&8b3^4*8qSU&q&5o<@4-=7ojCa0s?_$sQDNiGcX?$EzM zZv9QWyR)i~X^Ah;RH@Sc(sti4&+I*Z&z1yPIhs?@64OG2DP&fne>baJy?oW%(HxZU z<~Dhz6#q_P=?IZNXpQfyd8P1$!tcx3z_03cFRZ#Ut}9c>1~#?lvwVa^=L+JJW%0{d zMf{@U9|+paGL?a>$lcDb?w`UJAf_|3CoPPVeOuMpbQ)Sbq`7^k)#c0jCNyvFI{frs zbN%8^wV0l?Fi!Su{dZlzranq%aQ#kbwsIYg3vBvi|=^}dpXB-EvuMid-*(^ z2h1o;#tCkBFVr3*#x?PkI?Wf^(v3c)AWAj=qsMIPgzDFkIVM0)-ZHxjJU zA1M2+O&R2%+!|=O(*~FeYt-iB!8rdxEKbT1@fdT-SdbTl)UZXg_lojkZ^{TG% ztD$44Y!m-rd%m`ScPTuE@p1g37QzmMN{Xs&oTl_`VWi%PZQ=*7>8IYZZQ=)~37kz` zg%+r68KocD?~A($N`zLTRoS@r9v8_^mNcJ{-ZMylM4xboD(kk;AY}}TKFG|?CY_4V zI|%iWRg^8>P(tHx;!Bm*H~H%{YN|NPZL4$po22-K=ARSh*FO36+-*Ygj-CN@H^r3_MlKUL_VkRf^#qQA48I<^ zRt$FZ$4d>M&dyQXHlJjPe1~sjZP)r?rcxpcbh@tUWdZ^cAmI!q2n_$V(uuRLQ6nAE zgcho}z2BSEQYrz@bVYQH?m)sMfV$hiUg4N!Ha_=V|h@B6%~M zo46vLAE{8ttJ|VH^Q>vhM^X^&@O)A?wFx>Sz{(wtg018Gs}I!Wp#FY~WyJ~y z8Z4h+Dg_A@gMN>NroZV~R=DX4F|J!s;+?8dn+cx)Rc5Hl zRiE3dCGPdgpoaLVYxXu$Fy5U%%d$@8udBMTHlf={!AQ~FhHfJT;~fDC0yJs;$j9(f zNmkMFp01#5AcBldy!^1HOkoSgrbzmxh7GN9=~vy?r4?)9EWP1H%6J;`w74{%FJu$1 zxW)^L;m3M>na77wp1oJGL&YSJP3ll#j>$b4@&eaP@5l4tg8Sj_jaD96?ieHO0Tjh5XI+ndTS!w#sOw|9oQox7DOx*v9ju+9#4+Z zIEpUiUF*GJ%%Wi+4XbLub1HmVG_DK6)A0rD8rKFfaw>!OWO(w6hwoqHn&L?a6BpHt z4zDh~;$<~S9_+drI`%?O#5wEscv4QA`HD}_X-{{5vaXn56wEt+vx(+>fUempOVawa zo(md#uLs1sDI0-Y@-&6xnFab}9NL49QXjdyFe=r|`{*T#=y?U4=(laN8eERGqP;FgZ=5v24C;JzbO@Fvb5dx>Smsq1kO@Rx13tx;8S)f%1YLx zN?1Tx)UfBIOY8U;){z)dltPP94dr>A-V8S1I8DL`ZDQ|@0-jRg%_1)&sG)7zbR>0& zUBi1ePQKHs$?8$R&nwtCyl1tTVHekFR_50Jx(_QJ#8XeBi%-1sF+vgEuuTUj7CH9H zm)oYJ*=m})d^rEaRRRG>fA6C>tx2H*lDarHl?7TinYW{dJo}#05}4K~6sy$1>mt=G z{edw5*46QMs<%6iWJ|TO?eaU2e;`J0=q$9DDTy06*}g~TT()f7HiA8pjqST&CF(*P zi}PaOAS|jGFw?2Yv8#uJ=&5EXP}Ss^5V*OIIYiR742g7HBbAydiC(OfS2|zl>RHT} zeO6>m1k*bt+6$RE)T8fg&?A(dDUmjI%Fi)0%Rd+@Pac$2_74V;=K83PDd94hqEr7v zU*fFXoRXho5SV}P>l3JYIr=cXgu^(?b6+^os43ymbYxqYR5T?#T2Fm&U#4)6q_X^+ zHJ`m44(Sw7_G~(hxSjowZ|L}#ZC{1AS$kLOe=o~M)|ld3_rOQ_ zb@Y|qkLHGk?dmQBD**PPl=V)XKjZ3>>aJEj0tK;n4|m-VtnN_Kbw7fAD8++Qc^b=9 zp`Vb3NDk)?018^$LVzRW^)4i7agSn{Z-6EG(L4Y=5jX02bQ<8U*Z&b`d0WwT3=}T% zXcjObZXQ7;?P(0Xg%MnQAE;OHfhYXz!H&g;Qam{2K6y5THNIxjE)lPvVaIksKZs8= z`0IV4Es<$Mu%nqlTR1bD3A$c3?WB85*|hU7vzeglWoHHMCK%mP7|wH&s-}h=KY* z#(IyaHv2+OQHA`GUr#0Za*cwHzMOXyIQ6-MPvx!m(RGKN0A6|6NoQVji({WKO{O}c z?iL4c676K+=Zry!`y>>w5xoc#$0>IOMY z(r@Am^<%$)F$_A*@5LJ2dFD{{FVhd)B!}q?QZLHkRkpp`Dm$QXf)fa*Ic;T2rZbn0 zX;s8Iyr*SE9ES(GXH`%tYZD+f#(Ei+5)4LwT z2~U0Oh68Zeg$h34W26VT$y20n!7=ZMz6;yk5OBD@S8zKI{PhUViFS(}9!T}dkm06o znqkJ(QF-OygJt!^?F~Gb1ob_I`>`uPgl@DIrG+APO+-A9F$-l z-07qC7lt6}2;Q?PJcaW$Y~k6cJ!1?3o~>!BILlwg8>a1>V@mMm4lfvZiQ9>nKA88@ z5Z|{iFLFn*GyPj?EWx)suX0ndJ%hVyLWGS$A90&E7@-Od;`g#@xG{sXajg8nB)|+x z)Xf6?Ee4v>K-_pCAOmi#PPJA5-W)@yTHAm`Cf;hPI4k-ymz!d!-+`JRz!pKxPT7g(TWrCU0PB0otF zVT}&{sGx?FR-!XmWvK7+XIGA{)kU3qPe0@DtgPPN@3+`8KI=GLbYh&Fqb)KaK?!lp zur3mv!WN88FWyc0X!Be}Bzrk)5S~9gnMyXbDT<3Eiu0fNUPoE+-}KzjsyTVT=4o+h zK402LA=L>|CrF)Ob%NDddExy`Oy&}0A5oD)cfaknUq`Ti(q@r;h)&XT)!vQf()i~y zEuvdl*O_h9t0P#;&lnIP3=v~x>+OS2=|lRgw`IvSF5-*Zb|t&m_(+UkF>1$>zP62x zbXhEUJO`BuhsBdlo;j@cIihJS!)3{jgKc$FRxk~mpLDRU@x>ivMe6IUXrywzuT;Xa zkE$>}zIi6L(Aet0vn|*3T2{Iqg^{IQ+VQ*lL^{-8Q3tpCXBB#Oa&QU1u9buP)AVv) z(_!RZRDfjR_59HeqFk3|#O z*$zwuZ?#@oQD^_82Qgwu$fX1lcChOVAQRsp>ei7|^L$Fgf`7b-{El<^Ky_H{7)C7O zskM$KtCje)%x-wMEa+$mOC4giAcZcmnaJn61nx^SHQ3H{$&E8Naa;dG=n# z^6{71Ez6|D{%~Ua*3}05vEa1$j0*-660BqNr`NjQC!AtLGdb6*zUA#DE1d4@&1Dmt zy3u>2-h|sffkVkgGJ|rpi(9WbsyDwo#gP#mXExt{NNF&ZZ$V+VEKUl3a3h4-hCB%{p9e=^=Ve0 z7dzn(0~l7Er1iPVZcSN14Amo>Ijcc*8Xzp}sV`|`|BN#gz29M$=x3R{rj6!>`hFKI zWm2FQQ~8p|KVfSo$?98KXZZKw?yyI;^pTL!Cb*-0Z>_zAqyH1VS6OmNamz&#m0gOm zN}*F2t6Y_Q5I43njYsMGF%44`lp=1wNEaLt3nUf>@r)8*Mzyy~6~o3UAn3jQfSsG+ z`v-Q4k%dBr{iqRnPK%>gRSE&=Q^T`kg({IQdPM= zJoFovLcyC#HZtoR%S%GGbs-&GN|HkNo_uE4ujn4A5r!H8YJ^hrQ0rFymW8}Idh5_% z&sEaUi}f*~Ya<7_GT9biMfp>Bs2=a^CRdtS&ury+PVc5JC;t|ElR00LP=;RP=KLv3 zn{#=E$U~}o2p@$nXDW&jN{sFWBQcMZZfb5@ROuyy1zqyAp4+-9udpB2`-|FZ$`K?- zpq$5QH?#P+w@a17w24DF``Ztbh$<$vZR(>LM(%};Gy~)rP7c*=WaX{OWQ)JNISw1_ z>>$z$Mc>o1rC*%ITbv}(&u7-q)_bq%9&3RO=7pi9FD;0fv@DTMBD5mvD27#vP5t7= z(&9|IR8g0RVXbBZ!HDN12RB^)d!+^(X7+FLTuT!y?V<1x&fjG54%?4W4>e~}xOLA#W1cpn)2Fl~NSJDn!gjqEzF>EP!@^`f)9Nzpqy|METROV7cm$Nh;B8=V*4JnQv*?!ftA zBZJy1?;gzcEj_=SQ&K@!=|BY|P1fFF2-wjCmJ~wfaR5>HhC|00ChTXr1tv9`p8||3 z=qWLdsC~InU;temPR~C0Zy-mIvx@^roRjpPoPj6*cb2qq&JP{>9&MB?YXHpwUFw+m z@yXxon4hZhuEeyua*JJdXM(@^cYdoJ7JPUN*nEtohb>Ag9_V|K^4 zhQ}O--c0dZnez{?%7xeSr>vwp49xz+>u>*YUw-=!ufBy>#NgHcn#f&Ld4-;A_*#^w z-)X+DwvI*+&4|y7Qu%O~6`sreFk&^(6y<8kwmsMz36&V#p|dEC>b3vH%7MoS#H4bT z_9N?3*OAQE&V)e< z;KvZc%^J0zq1u>U??9_R0VzUB8GgSOC}n|M&;F{kU&ciZtdG1pq8CZLnlpNFgnx&0 zI$bsR@aA+kmaYQMWYcEZ9!R++1jXmsZ=6tzX6#y)*}@dm21CqqdcLF>BP0S?rCe!k;-ZHF;cqWv_ED0kdFcdhu(AL-2&% z?O&EOg6+@kfHT@(X8XTaHs4$S;LI66DvfD!rE9FIM_6 zGrd^akvE?agi!wCjv@J;-ckr<>n$72B-w^83ugml5(1OpULZxuF`tpL1NLSC?{Iw0 z@S2mK>&>C#i~Ykps4u*Gvqb05^={XQ{zRHD2VFM&4eZp4eSjY+hg~6~=Cdl>%V%_T z=<*5a0l*4|6$m@RxA7&rMe-`TWEjj-zEf}LbGxgdOACB3Qg(A8y9rJJzOT{v^ZLYy ze$>h%_^b64reK=o5&V_rD-ozy81@*09uA`& z78bb-b{xYZW1PcbfMcmeb(*K=rmX5$bI1T!N1pvr>AqNBg~E2~r}$~QJt6rou=Eu# z73&T>o!aWPO){l!Gspp#+wPYSmBQ0Mt8AamVbp3ib>OV}zdv7fT;GfHM=I-uZy#}% zr%6<$_%=$~3a7sw!Gk`jAX34kLX*xY@Mn;$5JJ~@T3F7*2xW_`zG}W6?FtJTIF=8e z>`b$;-|sjibe82U?z@_2m(S)HZVXTbp$b419qOb2j|Q0`16->3q1-Wp9dM0)jPB7F zwWIAp?db6Ikw@`dSYM%2;u_zMX@LO=yJwsR&_3-r?rq`PGcpit?wAgLbKEt3JK5%j z>F_sW)AT;?Di`uNyEY|nEROG@qf{e@cmm&1ZL%Zvr=I759ag@%m%PEc#sF4+Ajzgk zPwLDq=#Qn6)`l>diMd9#dOE(vZIY$M`7q_U0uvpWKJp@%3I*AQcbUuoJ8hnt|y>Ha8y*{2bsK zviSfQLwoTetLyf7#UE4Aix-_;rvVWkO>x)9!F)qtFD}UHRh&oAF@H4uNQOfOrw-DK z=Ax{waSpp+tEc9HZ+s1_pZsPS@KB0hvp9n{oB-YJ*b-r*tv;-6$~DC9cAyaods2-T z=Wg=k!=ju@SI<#`C*%B zH;0#K7O&##(QaZvR8|qS@Q-eWC{-`TX-0uBgVePc#YGYwtvA>7r|a1*5~Fu_n%}!S z2O7PYGzIy3w7x7*GmIdp_yHYIcyRxmOjiH;i~FJVOFWa+lYV(y!Tcd}?!o~}2c{G~ zO7o2;H_GY-%pk}O0Ion$zr0W(RvA3WYS;$k5D(Knn$T$Kz?WuoHq23;2?OnB>A<^Z z^qJkg;OEOv)_m$m?XiY{ClS3422Y!CxQfEG5nI$3sNn+bmN%S-8sS9LBY4n}2_zFl zW>ESNP2Gn_nkjAQrp`_c*Jt&i9{w1$Rdz7l_(#A}d_J^RX#?Btm3el)A51Tz2}UzH zSLoJH4O|X^>qd{Ex(7xJT2J_|H?c>2%nx;M_ne<`qdu?;ZYyEFvOJG?l`jZ~_v&pT z6FXa%=w^x_yqANUd`P0(&-g#WfD|31KJumC{F_S`dtfJee`DMU3BKHs0^Z~IEYF(z zkzFw{jbj@Z^0>IP@g4+;78pjasCtE zVFg-Iw^tY~rE}1fUuhA&x96|aZb983M<`snk0Q#`(Kwtmpi_n)^|c7nB3O&Qz)*`| zEerJeH8@G@bCun!m$mO{q{(a?`!$Al@BQt*O;$P!SO2WK4zb^7JNy;vH z8RvMxr^4>5*vO%evO2$_Ma(_W;n$?0Du}xrCFM`STWibmvT$gJ?CD?k4M87BNurY^ z3`xt?`*pp`Q9j_~TGqhpvXv)SCixjdEbQ00T05U*{UyfT%utHxq&!#a+1*}~tA@o# zagszopYlmxl+Ejv2>o_t4a=*fAOV7e6x0fGkdV^D$271uChDVdXM5*#c^+a%$2w5ih9F=B8o)tSQ zBnDDu@Hi?mJgIk+WSlGsVGy~Ba(%S<)a2n&r4{Ktx}9cVd6cF1++H=`YC@RxnCljz zoZ^E_h&ez(s5O}dp<@A9!dUEIAd-(9mio~zK^-1B>Z4TPyL`kX_DlJOns~e#j-&cq z+J>FqMjp@agJZ!^2B0sw)`KGZw z_;Ie%1frkC`GKx;6UsRDAXn7@)88A;md}dgs8%&rW|Q^1(-_x-2WtJ{dm2}`F{Nx7 z9mfT=aFXb#5qFwG_TWT+b32$uW;xSkQz!bH@iE;LvIi&RO@_0GqFdY?&)1cE_gK6h(=o>o=i@^+A+zuqgC%lJ|Jelp69K8H{(f`9Nv#CVJ{Tx7Czu#RjcB_ zX5e2{gXq+w-p-cKYkWSuCkWZ`ZsEq^o}M#?^dF_0JVwD{2$))>&B2TSGQng9r7z)B zEjy%gsz5TD$!Vp%h?4Xg7YWKQ__$2YmeG85I;dDD>ZrO9^m`P}P=cWF-_;6b8eN+& zf|23Ia+kgB1xv=R5hxVnh}w%6l;!~qP+<7tMQW2>Q`x*cpjJi3ug+=*aT|_wGY{f5 zxZcuLcFBr3kLv1-rYEA(Oz9-0=zIgMVxU8?%J}ZcEy% zPtRn*PiSIKoi?!1xorF3TiK|L-rFpIjfZ&&S~7RL%lh-byHp=`)%Wo?6|2=kV4*D-juta>2*^<;Jtsb>7-C7 zg`M0@O_x>pd!%Q0tB+sSVUW{JWY==0Rc>yq%29uKscz@UVJh>{EFKCIAWThqHbjPZ z7z53`m&yI#B?VyybnInsSE3!K^MoPET3{vMyd zjAbhS!gHil8fl&;l<#y7suU(F{>scvNtg24*|26YpwuXu43a|(4!2;JY^3A2MSVAx@ zwTriQ%qB$_hRtu&LhD`;-7=_tZ{aMf_ZSyd;Hyg@6!;-3h6Eh7%AcZ4cvTDz@lASK zV9%_2S0zM%|2nIvKjQ>8ML)oaAryUn`ebVb?tive9)lQEUZh3NwTBI5_G=9@w65}X zkqU42l&s541c867B*9NB6h%kLy@;TkE{f*a{(KB4$T^|!7OJz!tuiFCDUqVs1ae^lS%;t)n4lt3uKP<_gk zlB?_tsio&HxBk}6WCNH$FwudHY1hebZ?Yh)Ywo~5lmU#!!w(v5lGu+4xbQ@Etf+2V zh^u>`@=)7?D`r%ekm_{?SS~7;s#;}Td*dB`aMc5U_YgiQGWxS3o8>%++v3T%qji~RU}mba8ll@opY$lLle>gQK<@9E`GMhzthlrZqI=-#u0 zMMAgnxR)t$xXm{W?QG9evRQ*a5M|?Hoi<#IVQCKgx4x-Z8O31JWV;)7m@@b_%FWF| znT(o+ zzY+BD=C^`AhH>O1I^->(kCBmY4TIN<{$ne5Ee`%}hkaVJ z(r%QWQbNI$rlVB+F?^SgIy3=jg3+dleVNdt*IORjmWSIrpt&Yky=iyo?F=EEvBspU zgr9SmGgTcztzzNVRl4*%paY#vJyA*bSN9mL^g{dKqs^mQHm&5JQ8|C6KH^tulGqtA zNFA*h=ICe&CnGo+w5JcQ|HR;?4DTLc&+$u{qln?fvmNVp-rLt0Kb!U0m^A$EE1EU@ z?rF$iW!|rBJpL!nZ=W$9pWn+WYAd_mDEY%boCqAddhjdTij)c-1Bw6?#k5LSS~b~6 zcD627w~=Ph7{{vw(tZ`^xQL+h?$$)|Ql{E?q+wrGI#5N}U#PtuH#42M@UiFW#7c2kJ0O};kH z(8l=PJVRUJa{~=6UgG@TBZNAaIc}5!pXd*0*p&NS>*yzIK5-k;-p{NZ>i--(8gRI~ zgb4|i5N@wx3gU5LB}lNK!Ou>MWc&77*5+|w?iOd&n?Ka%6yFIe#dGvQ>~@Hnn5V%#3VF~Em8aS~rB{Jxq^ z41@G-VjvvGD)0%xm4Yqosb9?&yLFn{izct5tiYv4B~H|pA9U|I(9eJ4|H!0@OIqEk ztblN-*6#H3!NzPaV|Q<4Izp#|ja);33SuM$h=G7vX?`8diVSwYl=YPucBT6GfN8ZB zowIuz$ovBM1@Ti~?wqiN_7!RKQ&wMUbf&KJ2)7-DnVa~L`~*M19djWm%gG&_!Jk0} z;M_1zYwtL1HFhHt61c}NOzkw`=~GBb!4K8=b}PF@Kc5+{qpzEnvMRd?B*zQ$)YIYH zB(>vklIHRKrA$&gClh&3hi{V{`e+Ve(Yg*AZIABzjdTZ1_ZbH*bh>;738Oi~%-r44 zDGA*-Gr;C+$LX3`V9GWAH9F4g*Rw|o5T<~Epbac@>^(k?Wq$gPZ7f?;IPGqipAss` zDhLmtLZTTa@z@Jo^V{<-Wl6 z(c`6yWGum-JMK-8z_$2lZuE_~rp;%WVCY02H4IvV;!C^%=c{gE>b=F4Oiz9r-$F-u zCX<6rYU49+w{loJX4nO?Q)JAOp<{c~s<^nMFKQaHvuTD7&!*L{;7-jpn8Yi6;A#(o z)02%Ch#auMnPv4>{>;1KvC0z6m5nMd#DBcoh2#>Khg*ZA(!#$7lu2CpuLqeicAUOB z4*qU0m-rl4`!5i#XPX`Ls%HINFdMqad({5i_(`+NCj`S~pXLc%F9IlqMUF~SDR#YEdKU30+cMrbh*DcoD z?PY?(A|A6BEpS`!+k`>3& z1YgB2yDsG3kF^%C4-A%!g+W)EF9$Kq+?(KnEUa#>erapn_ZB;}{Zy7s-Bj^eJ7$ZJckQw`T*;ubc%<`We%?ZeLpV?A zcep}9W*Tx^Zz9OJJmV^%Ad+>WRpNS@ZR*sCvq?CI(8DOwi7+3zdX8OL78Tze!M1*6 zCrAVl2p!Bjh9O;774(&_5Snm<@XrcSZ5NN+nG1zIeCDjHg+$8$ZO5n16pbJn%@`6{ zN0u-{A>-slfnC(+vTD$XXVq&R88lQhqM(ivaFpf-_n^HyHlp}`aPFy!um5rvU3?m`_teC*|2}&En|S!@LcZhyha! zS~{Ur6A9vZZ9Xt5E6$fjCq+AXxP}yT52&y9;U zKep7kRD)ns0KW|^Wx$`}HA_7qn@mu(PY7}466apzPK4Pe&0N4N@RFpXr)9#~6 zsA*}}b(zskdLLQBbemomrK#e?!LQ7H1yNw-Zy+INFJWCIkL4Fytnkks_io+0j5JB?nsfw$QC`&gfgoMWgycTIt;$D5JYf?vmCfXqM z&-vw;6}~OeHTV-kJu+(I4XJ=v;lCn(8is1II&z1&4@ReGbc&1kNd3ZS=u|#sIft{_ zlhiqW5647~@U55v>JH}ecFqv!lm zzeKoRoQ$j*Ze*HsH2)UDtd{i--`k*@^#p_ZK;th6Kr*; z@GKPtd+Yh}rXGvmkn@=rY4kOEF01#rO1XIT%IcFn@yL;VWtt;HjuYIMmYjy7eSAr6 zT4fq;M_Mhz<+a2(jd-2@SbWCHiVRPQ*Xb{(B6>}QrSAYb(Z&`1Wz8{2VtM&o#fN0b zkRekHM|m$Ub`^_n!>1G(n6=`XJeL-g$5s9z<9$Y_OGA$G+8jQW&PZ0p;#Oph91geC zSo&^I6W45r6;Z);u)9xnGpM0&wnGbHOo#tF+|Xk2TS3*LNCvKerIJy~n>foq*E=D3 zs<;}!lPR88G+0`G94dn_>TS#5Q^~IQ8EvD(20apKY8Ow3}%YKOfg)P zTl~(7)IWUgrZ!R>HnPW=uc5bZK!eSztk)x$ELTzV(?7uD&+qDwFV+!Y3R9e>6?}_! zJ;z{NZpD#Fj%udC1oyQhm$}N>FG-eG5jyCuKc)B`whY)Z$<|2rPa^N1Y_}@MUCha7BfhXAw5;X3>h!2F+_q?O>^nAF?xOaR60ZOOVuMG zDKbHEocZE<^XkZ>I*&!&{&N)`$c!mw9iB^zT9nlZBI0nb$-tXdRa2vB#9gLK>snUX z5$CICXXqo9mwnQK6h#(-c5AZcK*}vOmcAQs$D{X5{Nr^JpLwcYuPFBN1F`A50c07F zWswkr-nyk3+ zPo_Iesuc8?rZ;4~j>a%U^YSU1`mHa3i*DwRa9%}b%2BUw5*EelL|}Z@xNKi{lQ5Hn zrtVblmfTx=HT5}q z&g0`Zatjn^Mw~~LSpSC5v~-wV$_tnst?|GF-%oyHXwtss-rgbNeodM6Z`4KPTtGC4 zPuG~}-xk4?0aIvA7_66;+{3BariBGQ?!M04W$SgAe#V*0{k~$waf16=l4pk5PpNli zm_3ZKUQ6lE{80BF=CvN;8EzA|t!p6fiq4AG$qPSmHLoAzoIJzez^Cg8CZcMEL!ams z6_&pPC3HLcxnbJ4l+m=?;9lE=MUi@oG6PFJ}`a zm#V_WCZBxW4K%SO*U_~%H?&ycR><5bbI4m}{Q%pa?Osfg#hY?~e|J^vCOOTL16~+mCR+n{yBZi$QuFtSj%s~c?Fq8`4tx*=w<%%c>qIxOz=aO@=Wp@ za9&4d8GQ2iskK%l2Z9gTucLZXiMo%@H!VM-=!zqWP8>(MFD zZ!jx5|J)^rB-s`reb#HKEvUa`se722U8tPjT zrPZf?go$JO1ot?vC3Ss{--|ZBve+#?rQFD~4KPhShuqhaT@+VOd>0sE8~P-2pjL2p43PcXr3w~fMqfQpX%4JTZc z)~X&-mzOHpYxyR4sM?3*(P0%%mMXy!^w`ZNsK1r(2#$7ZR%{4;znhOE~3 zZYWn8R3zY6W4HR5Oz>lYUx`JvlAoXmnk7}XTQ@Bu+Q|N@~_|Q%_8>re@i#z^-VrA58}#i_U336pw@d3f7`r8|9_F!-Y7$hEf#j4r9Z;! zE-l@OF?C1k&#ZWT&|8kOzQ)h5ar9J`ucT#`KE*XpDWT%;6-1D^4sslK%tzTS^o6&;oZ>L;!XU3}CH>kK2wr>w%Cx9B$ZXQlEM90J+rr z5d|_1BTM$%kizy_0MSr;}V*i6ChvIs9ydCrnqkIp?L0?_IaG8xR52a}}O zsW4e&PYmmC@7n(#)Xyj)*mrUX(3bZL65>h>iD;u@1_ny?okM-jxGi%bn(e!p65Lj(xo{C zVMf>^R6;7%R`;SAy$$Qi)MA#s4Sf^X!y0 z?NU0OQi<N-0=jyd#6J#;7@Kywk$Pc=>rp3t!Cj*=<2*y9gv>PLQ6j-)n(>KQSDY|kl+#E3TW$ye zyYq!;jiL}n9{xgr5km+KG>{Ny(F{LUyw+M!bzddRcL?iz5z>jP+QUC?Hyd5=@8#~T zlCqNQdCVh%)1Y zyp|eaKDrb9J>%T1#a+Sk3r$x&#R<}dUtZwMGF++SlaJ=)H#B0&0%b@2sYx|RDUc}h42 zwon|SIUkl~`F1}j6PRb|ur2CgKA$|z+6EZyBEF=|$z5*!5MP>?=p5IzxWaji&&U7; zF!J-)0gSXgMq{8EfMyzWhr&`4T|^DeXAa(7CDNcf6v9Y^LlSC7O)=d#Re-ZWWptRJ2(&(9){=loK?_y&DmUs{s_ zBMJ;D=nC@~ujN!6y?Hy6arE&Ve=L_1GA-&TiA810a}wm8H5Bj}!dG)3`+SyDPWIo+ zu~Mwc)H_p8j!H-hj3|upT3p4KxPEEk+I=A>Pr*wJVX=SoJ>ndc+VL6?|oq-wdd6sbu3KLSzNDrpMuqsiq(rrwu63|5ydp>DLTRQdX6vo)*BTPBmD$po{Oe$k$gm1 z5uLK6DmN~Igv+vmNw@<7!K9(yWb%Hxaajl|lTbUr1Ir;jMkR60h2%AuX|gxI2YIAF zD1gpP=zT6rDb^$}QJm*=Uv?#!K+XhmdcF=lCdubfzqE`#IHck_eoL zQ}CKS3I$HlTl5_+ON!<>gn4hZB0?{0_7vZ7(sJy9vHsQy7S7i|6`N2rHR&K1TInl<(T2tF`{m;AD97!&#PkD~AS}RG;O7 zD(~)ztq*~_2yhcSs2=w57Po+BugSJj#cO%?-6Zi}scj_&{`Ym9ayL=vze3&Nu$)L< zs~YaB5JGOz2h&Ck(`{Rh7Ny(>$A88Y>~$pwGdsHkFDa+r3@v1ea(MB^^YZNF?5t}W zCF$nouZpx|2xFpV5;gfcGbsdC$Bf*zA9c)tbO)0{V0GraN_P`yo!6?VVsRxbRx6+W z!ILJ+vuAKB9vAyjN^@YufdL1G9H!|7&=DG3xUMRMRMcA?P$77=2p#?p#Mf_3Yc0Nn z&TF)4oG-N{y}N)`;!P;$cJ5qNK)EQ2HvPa$Arx5k{CBX2k);zP)hlS#TAFl_dPkVD zlEJfgKGXp10OFL)oimWAV{V_%HNRuF^a;r*%L(k@5o%V@*+yZWgHy1({uGu z7E!_9jk(FL8uV?03WNN-1re5H8rCd1Tqr}oL$Sc$jcM110T)-9vU8;Cm)hG=QgM;7 z?9Bz)8ezg&C1v_inAU!dARB*avNO{)N$uOHgO(|@@^=9}d^{Q6NeqPQ;jnOAhLq=g zKaWKR?IMpwR}T*#%6HPjF9Phi1{H6rjKA&Xsp#QdwAue8-v>7!oE(%R*uU# z-DinT!1d$P5s?$6w{wU3B5;UDaFKnsdrH; zHG;$=V)|K5mS$g?U_D?#HB=R2e=n9%VUpknbe7W1tYz#^IJ4HI&<_YLCEac+PExGp zYEDuRUFF!{gQP1@5_*qH5XH#mUb_1`Xcy`1@9MGXKHaVu^ZRr4@F+N)e2CLD5|*ov zs42-0rN0nLgf_1LQs^O4bebvPGK9;Tl9CQX27#mj-)Q8SGi&&1)3Sh{H`+p|UF7rS zxYSK~%6{i*{EV^OA;=lj5$dgqG62jJVEMa*CL-Y+3Ez)80G$DJ)aE#o z%vf_Xlwjrz<01XIYi_3adab!xjj#Wjn+d-2*4(VcJII=w2?gdaE?G;3FpEp9Wbj*D zavve)EH0T4gBDkW`V1~30WdUQ>0!fdDh)DV zDcBN+k%`oEegEnLAXv4m-$WilkbXG2wnY-DCdqaM>W8Ik!7~khgwPVYRs5LM4Slb6 zsGJGE)ozs366)2wuJ`$yH1aNyWyC7a9OF2?W$~VrlCT-THYhBkk@yCx zyRef2*pv!p7pQJTRgD}FT1>d2roNqM_OhtED5v1Ji=>8>0&3^)T5ENkf;iNwi!4e% zMDs37AcVntT&1lW3_4k^ADbHpb}}b)3^57AU%Db=(j=26Tkjbrv*0me?KQ=fYj1IW z;h#rMT%$*vT6&HOHc9)2!VYc6iTDWDQC>Pfd)gkT(;D|yLzrmUD}0RbF~HYC4a{7| z&&~>Ql~8u;LdY248tx>K&@##P;pX*GIET=KDB?Nlw2ln(2AjqS(^F-tspG4sFRi|^a(LxjF+!9izk&eL+ri&52vvnO7rE*ksyDAy+NL$=YcMwHp^en8SLzw=A zaq?n=YA~V#qsE+e)}|SuaJP2On4CMT_>M_>@5*p<5!UjoIBvSk(kOL*au0S`Eo5(% z^sWo*zT!09xH7UgN@9lPYk;{+VHuUmy78IVb0>WiTD7r=CTC$hI>*h+K12by4B=|v z%g9od=lEzYK8`%yZQn^JgfaZ;UU4^|qYi6=$k~RI@OL`A??|awX&ue@z z#dQG=WmA2QlW*?9*Qu=nLs9N^dn$h9Uum|CsOP^|S@X@^AC!RR4lyOch%EQILq1O6 z_I^uz9mxd~-D`H--c={kUNh%r=q`PpMb#yDpS8b3$+Zu2y7tJ>$yJFmuUt9KypUOA>tw=7}(oYDf z&+!L3`nnI8vkr4c%<=yn_0PUd z@dfSdp0a!O*nS4#y}Nv8iN8pH+V-Vis!z>N_l0`Zc6J+9>sj-=NlW#u?d&eC*uUm? zpMv$U?d-+`?Pc?mK3qTB&a@BQ*JiDL_};ed3%_uWo4xkXG^RXM9qwDX1JF#SJT&vJ z5bNIvvG-D@(Kpn3z`H=SsqJ7$u)h9*dqj64|6;wO&fR7%T~GG+P|Xcf{iv6rygqz6sBb8o3uimXe-Ifnw$O!ME&4$mfO6lX13HQ7Zi>13nLgALzQR?X_%fE zM2$z$U_|3K(>cN_Mg78ybTm_91=321G$XXgI8(2{BC9a<4=OUsbY4i2H5dm8Ct|@p z3NUeM6$#Xwr!MY*g3MfRw4NYAmla(Rr2Qz?8&;#Oz|}_0tq!S!>}5S=@JS)xSsCB7 zCbE~95HpQGXeK!#MbLe(8(Z}5+=-GpUMvRIpOv0ph2hrd*hn)W1Xq}Eeq3+ za;7n>jYX8f$SM%CVNcSup!HTLapr}bSXYx!;U}(WAy<06@{gKkY*cTe>K500`_jlY zWB{Eh=nSDxk_spsINX-q4k8ngTkCRr`_h3!aRr{8VPvZXe}z~;`PAf@b6nRVy}TS> zwlABSk7I%!ArexieSFtIo29;HGlO)luSBRYo!1nZ<9K{^K>|4;zaob`<{7qgeMRIk zg|`boq%J$D{DCBkR{5GL-NR!6DB<5+=EB4Sl{ryeUaDlPwYf^iW?&L$2XzP;%mO&t zaQ^{FVSYKN6JdaUrX;~!zAoFZ6x`RVoeW{Tyxv8cCEU9@iMGMs$%Q<+I?3Yc%k`eZ z%yHuS9@i%n1%CZ1>~JY?MibtY;cBD`zY>U7E&TVgeB~V4!TOHpu{b;V5UTR}rOeav zjaOBc@`v*5<9W81190zW>Mesd)ZY9l^h~0sp9NLYDSH*FC7`$F%cYYjCFRfYC2t}L zBHD{+T^dZ%P+A(&O%e<)B`@NqoDVaCRa;e-)caDD*bD__ip?#$OX&-zP}vcs9DXXA z@4jo9;S?gpBq^Hb-&gw<`4vQ06DglQ8eV@E(bcR`By~mfQSz&ZE@QWRGSy|Yn%lCE z(GRZsuq;#G;+;u7EVWxAS4ajI6vlYYaq`OL8?!GJVf2d7(*t_w(+RHEjPCaPN;R+R z{gR#^(xR3f)1Zskqa7JlD#$_v!t7&f78EfuaTY^ZW2xSU(|aHpfz+))6LMf7 z#7-kFmuYZ?c$drkf`OR#AX0|_UeOXz0*=yJR7iZkd67aW%v?O+mvg-*8L_+~_)Nhc zp|q%kc*EYnr?1<3_Xwh3GzAV5lnM9PdB__3Ldb)P1ZJdc2g-4+$-*UsmKbQC6@O!4f}UQK5;FBvL8lp;PqPL0sLDz7Ft zLv>IwHyW^AG46XmV9fYf#iwucp0w=8MOoqAKv|o_$qV~l|HOu_4%#D+pMQh_HL-)+^67A2E;69mnx9X#i*D8e5qswI zYDa4+pzAh6;DHpG`@AnG!!i&5OI*J=yO&v$HUMnB$#mcblt8*fafrvVwg%GciCgFt z2kJBq}wd}6g2r8h%jzP+y49y zR*CjO2hu{;HC>n3tSN=64r_>A$YF)>a7b_mEj;}ZxM|?ynBaa|%=JrfQ=7SM$%VxT z+Y0QFZ!Sf_a~RXy?&F{_>W82QI*=A(mr7w#2&+yuqh0bWs_8FLRtQ^falS>NMhKW~ zEhNA+fk|o$%1IfSjlCkGyQvnf^K>uDlALB+wvdl1Yq{7E4yU(%F#-;hEG}#h^a7(E z4qAmC?6{M9%a2^l)pX{7#g=qkWc0yGU$rEoiK`7v5%0o&6a;r+0Jz1zv_3ofdP)aQ0Jz1z zoIVwAs*jzuA`r7~M06M|ERf0PA5K0{(c3EY|45oY6 zRkG2+VR$$cOeSE$*zgGSfTG~iK3b26z9FCDJT4N4P|Rz~gG&f$MI}18Yyn@Tb3?Kt zFEfPwmO&-|At_HM2y^hg+`{APu#``vMQ)AhUM`Ph8$^@&Su2K?n6|{|dN_R&FI>}a zM-M41qf=fc7=%IQ)reb@KEy>?;J3lzX}h~}V+Ih1Keae>ASe6|mi4|q5_%Yk7Y7#sBIBIr^zn-!p1<~ZDT7&?flJv=H#HcTe zv;?{+``*Gx18{x^Abs@GCmJF$(S}k0%DYV~006ZDCfZ;M;G?L|wCsnuj~4X)s54vN zqhkz0>66vxO+O$4pME|JtT*V3vL$a`qU41quMhkUy!O;t@rr#h97LcK@HifZbT~tv z3=^bmBr)@OH1dJ&7!JZW{kNrwEd1RkK`t%;^B6 zW3u|t;lgLDLQ7ovh^Ie}XQT?Q5eW7IiEHHQY%ILUl$lJcjK|r?Q>a#;i7hMODK6rp zbq!$;B1%T8BGDzdw3uJzQhW}L#|RsdpF`8H@-!qyt>nWf-L(|1v){Sh)Zl#y47`lF z!hNz<@?J#y@}#FYJwV$tZ)ZHo1*+h39uBBHF=XFggEa_&ss{?t!3G&;e4an)g^ z+og_dqI2v_X=27CGtJURQD~l3FaxXAQYFchQ<-MZAKKZvx{0w>)YDO6dwHtXrU_%7 zn97oQrgqJdSWH1tCW{fS)Z|QUG*7~MvvA~+I8>KjM8-z5_X?SU5bv8TuRVz z->Xe1y7-6bwx)BxMjOy^)z8kQ%J!-gKo@^Dy70KlKjiZloh}UyuRZklsn=U1=@hkL z*KQom<*~mnF6?LQrW9tQBD5gnb9qR08bl^JFVr z??>Pe0v1(Mo|Y@GP}l*4#|{LLw2R42P$^w&z8mr$F22IBy$=0fz}j-kQ!%`uz&iH; zJtI~;u3@0;r4lE74&~7)Bk^LA*C<;DmEYn#OXH>#D$_-BwQE~G=rejNRNu4aCAt)_ zosvs{ zdmvsVi2c=2XEP0b?*0*1q{xgSHA;0+aUs8R^jyWq6U+#Bu0$=@dW>+x`XVo;croC` zB(HJ8i>fT@>6l4Wz$ZMXI{Bq_hnzbZt5O^x;WwMtNes@~ z>O8JrK25dd3U|6&Sf7#HndGkOG`GaL+g+<)Gbk9VBREx)m_GBJ0=`W0Wt#7Z2=~1s z!}t5s-;`mAcbJgE=GN#HqryVU8;)<8>d+#DIO!V`XkSfY#`>7H2i=P`Z=j2@0#|Nv z9W@oW?Y8S!9mVn3!GTl3JHXs-iC!=(MATWR4ls4UzGp+KvOcQqK@Y2-$|O}5*Gy5h z7G>FcBOhL?nS`5HUmip<2C-!otoT2mPZd8!)Em5iA&C=xeVOx^2e9bs5 zpnqC7Rz|btaaCH(#nj zo{uAWf>~4nsRMBN6SWS%_00nFgsg7E!{9iK%W9}*p%ve=84a_$r>H0&_k!Lz9K*g!zwJjP5QX)>upUluY zbS;mbMU(xuzl4eGW-R~gx_i-qbUMpGCkRmJ9Z7&m0wbgrln}hrfOimhv0(r(k|*p- zNASR-SipE8Q!J+F%~lj=5@yYZ7E&cd?>sxczy~dVvRp%P4=wxnbpliPS^lI}OkCj> zUdZ3ef=BUtT=AVL2*4QvH>4Y5vy5h#iB5Trvm88`K0jv(Oh!1zMV9PF#ts(-T$tnn z6EBF%D8n_RJ!M5CL&V`SjGd|~XsC#HxWwep=e35GP{D?)MaA+dzukAYQ7I)0rdae? z%@(p2tr5}et5nGL_;{?SF}Ov+dT5z}w%rV_2|T^@c>-^H3BSF}ZVTGLOGv1hLOmoB zTr1d*Fd=%+inM&+gH*83Fo7x1eHxxZSTnA zUphI@77xwoIqy(*8AKQn(T9=92)RW>OkT zn~qXnVV_7?nX+P+G7)T3raqCNGKC(yTUV{M#_TH5NQapmPZ{=w;V$h3tbw(8pms)< z^VI`m42_|F8vnADvPso~HLUL=+Hwu*`tnqB?1+$CG`*?fsmMGQ6}?yf;oWseCP61@ ze6~bn2YXs+i{?#fxD4TraR{Z4iZ4sM8&*cmqL)CsxI?M&ETn#llUM0jVZ(ALq|#}_ zp@%$_Ao&w*)I8@<({B}mhj9B(O&@=&8{k(D(-^7c72NzYHZ9)D58cKK?SW2+H)JV2 zP^5;LS5!sAz-Ie<5s3lTJ~42cVlYZ9n2wn+8$&dVO}}>;j1)rbj*o+ZIG70ChBnQN z+OizoP6L|Wbr@w4N}>~Q;xumJXg`Iuf;=dsr?`aD3g>)9r!wUmf2|NGI7;OBW_59g zQll16C69U!=Y~Z=y!}_~Fv=pBMEC=(%a3E0K_NVBFOagXrbm%= zW>{y+x|}EvGp20Uh)t+c@aDa&Ugi1}@Y@9|&j`6iFq8>Jq*V|>ZmRs|sv*tEbt)aa zmLXHL5O`69yA+MAKUg=qr~jfm~JkQ!(8Ojoqpc#LXik6r?HXAvE>y^@}ZxF zwX6B<>A(9?j@a;xRez zDyC6sgq~!pOVMO7Nv$Gxp*T~D(sWY)9A7c|c5up6KmXZ6NsCNb#K}`#MuT$>eHaU> zIj5IsI||zN4yhj4q$~<@Bc~?6KcxASE@)xfaj;PJ!VWDz3TwKY1=J! zH{=9Y=2R=i2o?=LopbO9E3_2*Y8OvXjugT9xjJx5KRB5xrH`h05YODFimJVM=$IX^ z!N?TK44glXny%2OI~;5js`xwDy{3^oWlW_=9Z;HhAe2px?3-hkC1zb5ow$dYnp-r;D>GLXJ|k%=0Mad3>{yKbqg8 zGOufR8i2i!WEkCqYQs#~(bc@`M4;}UGa}M*T1Udx%HB0>tsD%fUMl_;H(Efkz0l() zENj&u$C-i&6I!cD&;cJcOOfV2VE0kU>tXusk|hrqys^UGG+tFaRUs&7^4M2!UJfVd zOu9;=s9PH|Q~-r>;1dTvt){ zpFg*<|7m1 zW6UtdzzAH^rkR@nvWq#G6z90AZ}-05NdgIueu7cPNJrUoBp$unZd2~W9HTU4&rD)8 z5eL(u5m#-LK0F_EoJ41>*)jad20wRk?J>1aG z0^2XWG&MNkAdJYP9c)ncU}y{MVMAt&hI!_}K(VxFND>DWKZSUB8qpb{cuP#_WHU=2 zE|(1BKESU`&UD=o#zC4YPPmM1eN#DY_Yv%&RAAp;7U^p+OBq~OISy|LK=sdq|@7R z1BaWkINu(sBfs$YtSjiSW84PVMPESC(?`QSgm1HGE9IEs41v)K1e-eRigybeg~Ve3 zn-T00D$ZhM1%!ixI7{*n&K;47D$Oxg(onBKF0=rvpY;s&l(F^@%r+V2;T%p0#zj9K zU`_f%33V%6ovzl^f?kjci7HJ!YHH~%e#CC8CUSWMGmpbuK(Aq{b9)c-phfp!jkfgC zVR6+sllBy1(j{i4-nq)kDr;bazh&j6j$}$kBPJ@jO;ul_a|*TEXDq^t+tVsYMwQ*; zNOqv*NV2DxDkr8)veqPAIz@~TG1e06GrgrN1V*bKbhwB!Pcm^*^>8fn7#Azaj##2) z=O~JdJ`RjNl2P3uCiAe@3hlPRE|gtHhT6VX(x&y$dD|D1qYlb*kUW1Uf++V#3U!Y~ zSc&rNsfw$Q==RK=ji@65!%UtQ`!brqpwrG&LC7Y4Pi5+d{DPl5@e}rCsvm_(^y@A% zm@P-mgcqmsK#d|{poT^)A#cTa=tm5-aCO%b=R%07d zB~c2T_F&oN-P(QZ*#!;ySy}6Wvy9O!Q#KgcfJ=sObr-f>@3pfUP*=wqI=VH2%9A+h zv=?!p^?~ToSCd9RPAar?b~D(wB9%mIHL2Z@qCE2=YIx31SX~C!AR=9sDjaJ5LR_3t z6E{tjJzXr?t%f9B^Vpi`X6Pdbm|)rj`UugO8DM6B8C_Ymm?yXdQtDWp6C57P62579 z04P&HP1J#eaDHjlmtcuErQ+BZj8TE)478NzO}y=Qg-|nv8r~R*dXi6Ay5yf^AXbV^WvND{tzDIB&2c3V&bXBCTKISE!fUeM|-hAHfi= zP{>^zD``7S+F@`#o({h)F#YiuVx7nR%v(!QoM6#YZ-hQzQ^c9ZHj3C;gqNl{N8f!1 zGr+#$3l9n3eTTbhq)}frxZYi$PJW%iRdqxTg3M75OtJ62W0(nCca7BG`a>VJv#1$- zwu(59RAw~1K#4aX*Q`k0)g5F^BMyBuGo)3<+M#J2ahRaSMyN5Q2LJA% ztIa2Cr@GpD-J7lJIj-yGr7ADS7d-ZNrzbSeQr1ASrVi zOcN<8VW_HIJ#DCEH>1uctA}BRob;tqmS_a+8k?EbK4iHkTZpRoKSGxD5QPF<0}7% z{JuErOGcm>g2oQy!+^#tp@uK2eyKM6a1CEW{Sh%AtT8IiYGHUKVF3Fw}zZqI78`zoA!OS#fd9wZvjB^Po7u<#Y2nDHKi3a#*iK8CQJw8+8 z*jIhFCgj`rGd3aI63<#g^Ely0x-s_^q@n4WjmtdEp$rX8(1ai9{9M;S4Rmv8&aYwy zXBxhC*d%8fj5WOc2OzGjB7L3391#+7Symff{nt@L&Idg6Dl5~iN+D-4N{}7M?jlf$ znDDB}p0h+)dW-WcMVH62)}!2oe?4cg?$J2aLm(;|>9w7u3zdo2a4RR_gxfszFv(~U z{gNu*xKe;n{4K-&Qa9x(k_XDl|5LK6B8Zx4)cS)n&SnwiaHcneV}lv>QJgn8P*`tV zIr7aSQNs`tBh(XY7F4eJIK#s2UFwI}Csmwawvd)zqc`z!dH#q}e!e{u_wMq2WdxTI z+&=zAL@JB?L**hGEYJ8&iMC(x18yZ3;KsA@b1=zh5&fDQ;jb@hEy3ySt5=hfFyt(O{+e46RH ziVyRT>a>n(o>VjX%r~I@P;(}TF(Rg#{x!(x1Ov{q<~Ie^Z49LvBsb?X4S@&MCCzG_T86>L zpD($%1vS_KM?;s>c~)QEf0F9wm7O;o?I~idaw|iIx%C&QyqLB0=?1Z+Vm9yfo2ds<$YVDzOMA zBbYj4P%y7y4cSQ$J)6TTiZZ}Vbg;rpksX_(%`38gGQqpg(Z zvLKhRRDs?_Ig|26o@63A{{dFt<8XE0&>R=8_)56Fj_%E{@Ab&{JLG4w4%izVn)ssK z(DgZce&(Q~o-VByoxt?Cv^>RGF{>RIQ67W&*dY{}LN90iDfAsK3rR)R=aq_gRf`A& zC`qk!qFeHGDbjr3deM_6iI}Xs^`j}{ao)gPP?2Qh@BJ&yqwrMdnuGie%e{^ZoKC-0 z@&X?td=jWHedv8?KWXMvMw+L5e*wA(I}-{B;Q(_WEsVb6&8c8?ZJT}CftZ7Xq4%;% zH%D6oQyZZOzb5r}f7l*ff<-@}sM<$kU7xR4%vBHOI=JkXCD0vYhcV=VMA_i<^qwLM zPDmF&LejiEKICN*=Q?<+RgAuc2M9DahS+o{Si&}fcfJTV5j9q0jbJQdWLWU3_^A|c z&o=Kaf$QDgF~l=;zXNmzGUz*Z#%7QkC!3uyOh9=M-F=f)4vo=V931mEi9zq5P)x-q@Z7}wb zE(cHI9Q|~I&oZ^fI!a>E&R5(vEKy5?X&UXyafURQqH&Yx66(U{9|l_NLsG=vR^}A; zaY{>x_x+B=`+i)!?^hFV$J&icw~TVK;DY|A+Vb)py{yV}vfIPqegT42RNyOUm_)-mXvQE~G8boM zRau;P(Y(b~27V>PJ5|UjIw^vx%)u3SveCBwe32WQLI&2Hc^(Y2p4h`-=Txd*$4F7Y5lkOO~;?txm!Yi zJDDFy@qy<_c{*n~Us!dOubiS_<~u;*N+l^%z7l%O3jVocT&*B9z|JiMHGJda80oL$ zN;HhTS)2DLB3D{PoV-T(flIj@pgd*uTErlCh9BRQ%K=o(eFBv$x$06teR?G_DmVG- z3RaY3*;+-AC_P19n4=|3_^o;MmL>dQjl$l@&ne>c7~VQ7jyZ3ZE_F=EMwV*pCh#VC zNlRQ``YgTyQH(WD86g`|VzYGzGje3Q#mh>jAG@Z3X)iMe&Sa8F1B?vFxYvEOIH5?R zHKB(UMV*yee`W41j5B)B1lt@`a0ko-<)fv-hF);1qU8QK;2-7Kw}BQN~eliL6!0PL2O2_{c%^S zeON%95iAXGHvcRYb~15cbvLdCgmyC^F}$6V4cc`X;ypwx3RFm<#Z;4KWs-=am}u`f zk%R8!yOuf74~+m6k`R>k8_n3Np_C&J5p&e z=q-Oj@~k`3(Fo|*i;65K@54f6E`R3`z)#OzS>R^}JK<;Xm>g$bjRzu_8hF0VqXbCX z_;4bY;pcN4q8Hn`yj1rp161DlUyB3f#wd5D zbMK+?pz~40r!3Kh#oBx*dtHPb`gq(Km9O`@Ipu46V+<7T6d>&5rCu4m&J=!ZFw?U~ zZBYr_x&vbl>|wrcLj@zaT{_VNL-*)5A0e>P6ElL}`LQWqJIGfrfV%mIDS>*N)_R!*Sy&RU4StG z&x5Gih>YhXPSdrCjy=EtV*=haqG?tq;-*(0RMh996jCXw3eBsktfEvDP}%j~CVeH) zZ4KK|)#p>mKLUgy5GH|`sAdw(imb`RjS=!;<4-vyt?$ZKqGAda)2Jr#&eY-N)UKr| zTg~aRAfknV$#0?=e0i!9(FD6MIei}y6l+0Ep$#j-rP!nlCah`1^QEA2u_%k=?)0hX zCMaEGGigF3qUciHw~z)93_vI}-lXI=_n!BxbFb#zB3*-kI#wEWpY{9FlbCw1=3yLa zXaP3HeMmb1sp?0R70=~;IT9dMQ(^>Y2y!m(I~TN)?2M9j3sw9D zYWMf7&<3c>Ua!I3i^$-s;P*AyMxAV->e#(&VgV~*eJb}T-1~Go<+)!$J}ARsjIk>7 z=$yyJ9UedV*hv(OP;@ZO;C821YEEtW{Km z{?$ztXGK###6=nz`)jeBM#KanW+Lh#48pqI-T+|(OuHGFW}el}!?x~o7hLC-hrZoK z*=+0~7l=BHdkBNDrkL>?A}!iICZI09Ae715Nt^P(zlvvT3*7t2m_$ZD%aEUnzItLyztuc~|=m6n7>~ldp{*vJ`*rSg(avT@bIz_DVq1tGFR1 zc~e$;hFd$`Wo0H3h@R?Z02yNQrg?`~LJ{*XjNqc-4Z1^R)C4rVQJu=@Rk(f%+~M9K zQL7sVo?1jk3802KzNbrrZcc8`Hda@5CVN2wuj{h1M31Y{#5Hi#%-(L&@g(>p=W!L? znF4|!A}t;lI)iz5>ysJG!8_PYyf=wZT(-_6_DD&zE}t$Eq{lRp4lYk_beCy;7D9d11UG?06EiTmv8wO(2)&712FvGeb@Q zMb&H!SpMAZQVI-td6or3KHIoWUUg3iAuEpBmqPtfB+=>Cq{`rg21pnpVThy!sgXqIneiQZgDY{R~cnGQeWVX%ZA<7w);qw&1phT6W_h$=e zm`2mVHiM%w5lMIdSHt_RNml`5Iu3yZHidYG2-~Ph=q{gtRf!(KyO+v6jsYtZJ*M|B zlzUWzRww$7EjIGt2CYy`IPcgF&5%`!KG_XDzQHOL>ilgC!1dA08G;k1=F&%-4tOmS zBXppq1K(f2p>zpQd`WF%_|P@ofbl93^JZ<{dCyMl3!Xy%TL?+*!>^76z zXznVR=7^Y3W^sZkLm0DMABgD4`UDjerVq$#4wE3As8a$y5G&tGBj-T3(d1%2>Gir~ z%y+N*%GKU?}Lxn6vSD)@6UI@(~qC=#;Q)owvM+Tt6{!I-SW_Gdb7Cc z;UhSHE59DsFqhShr%m2jyWY{z{HD<9CsAFk(M`b)u1}}UVH0R$HElYLB!z4YuCpff znI{x?R~2w;)knub=;F6qGaZFCgQ^f%oOmO&%>wLnzG}mcgQ~ykqWNKsy7eGeqeksm zkscCIDXZewnW_y>^u4;{3LozY2%)~-Qw;BMmwLSkR`)6|;}ol*HL;0P+|e}=S$tsP zRW7X|;FDLjeBe5)71zY63qY4@1fTS&HfxxJ260>R>_)txdurGxU}_2kARrAAm>{;EvW7KqTG8<1 z$9_~btZDEggyyMvf$u+6!E05Z(F=h`>if!NqEjUrohs3dAP%lao4%D2e?*oxJ$%W3 zU6bN+QXSkiG;GRuzG-;`Ue!xqGlh4I#A>2Zq{H24CgF}ySW+?kfbcv%*V~o~KY$9S z2|A!SX?cpXVnZrOK=gBqDB9>SRGp?Nhk#G#rmSv%ckn`DL zpaY%TLh3=B!`>yf^FnOrgm=C{4_2$;sKI?oEtx$PRwkAil-k2LXNoUFsy%Gw8`7GN zt`(noCEo436u|9aU2hj?7@`^A3&Q&DL=nJjI`6j^!7WAPU9NbO80OIEu7k7*RJ{Saj|ioavLw>pPG%vZjrzG@yEO;z&Yn=JCgk0bO|}qHe6G zF3GD9wA`eu@V_I-+h3(n7_B}(vQmdjK{8Q!riB#`M_%ws)G@xMwOng){ed7efH|qH zG0WN;E&^%uW-@Ok@@6XU5j_5^gb|rQM{U+rEI?4hkVn-Tiw>wDEb-_5Ea^wFG(m-6 znbl}aJS0}(85X6Qk#AP<5Nj+$cp*sVvH0Fy&Y~G`qyKVIx`2Wy6egq)6mJ%oi&6_v zh_+O$;`J>`$0=Z+EiLQFeT!1_3f*T*vHkAck6`;fJhX5;$laV67LtY4#IFgUcMyHH z#4`Iu7#8rI{{7h%wf#Soc4b8jCbJWdNJxean(V16wsGyp;&aaR5 zIMCvU z8SR5IH4%6a5Wi=hMt9*sd3WiI;PGL#ZgFMpinyubgtv3&ao2TC0A2*Ral>)OJYLhz zm+t^@UkqSL=XNb*4Ni9TlB<;Jb6nJSA|msK|6}-}IfZWfDb7v#nqBGXgOeZOWPJST z&!OddqYS&pDo0uo?yrnumY3c%}k4hJkY>R$~VYAVwcL&t-L@m@%RV zq#^W&6R(cqG0Nk5%HdB3EZB;A#vG6jp)d`FArz*dbb!s{SU_5y{mvtVUp*($_y5;_ z|L6O%)u8Y+CO|O+H34>(0{6YK^`YIIq#+wX7@)blJmp*^@UL;wOyifQ0fpjndg7H0 zYIzX`T{bgglo|eaM=Iza`C3RRGyHFZ^f}96&7`P5Xt-11y!&A3QfwS?N65omuSM&& zz%0I7u+$fK?*N{%XDa&^iwzxkRb&rvf_Mclqb&X-&2DY*oyGd+L6^26v_i9 zqA(CSk`w=`4~%nLuC4IWr5=Dg zc`}`++OUDHCM2pP`bJp6R+l;g@8u}JN(cJ!ft(d@ylU2jEOVqEAIR66b>RWuZs*+q zTk|8VN3^bD1;j10cByCe8DN31CT_|Q|J&e&9u~EWgC`Tq3J0}rajqj$BP$xU5n0JB zy^t#A^=g3I9J>H);FneBk+?oy7UHv_Go(kN9V6@JZ-X7)1?TbuPIQE9A;*76M=Fc$yJ|JgqxU&uo$_oG6 z!0gs%ld@>yY{~j;w=S#mQycD(?Yr3m;C1X~>mtpLMOpDEiEF+VUdobTmPYpO*hRzd zIs3X6Y?&MVxPz_ZXDEVfU%3E)AxHVKhkl_okwbnGcbl~{>f(~G`kX+15_OxkGwQNh6LVQf#41~7ywFPv(*jonEwhx! zI{=*FJCBvkM(;5^raF*}ZYWX2-}u7F6P&^w z-3vgr{&QGE=K|?k~X6#jXFRvXdPitR$+TNWa76Nwv{u2Yw_Sadzi^gPSB5fFcCp@yu)-y}XgDHmtP zKJaUgGz=5rSr%aQ{9C*NR;(TPm4~(mqi*7BQ*LGeQI_K?GizWfZAHGZ?ZLL|GlFa}SwqP@0Se9$zihurZi|8TETI!)d1lL2s zu+&2pfj8=;&V{8aB5f-9rq4AMYvJK3j|*_AUtMy3Lmzpmj?-WXagZmIc{0q?1mFg} z*KBhR~JU=N;}xS{21oPWPWDmPN1`5UC@x9 z25n^S0{VP?TreD3%IJJ`98loHCx#vvsHx`HV~#3}=5uC=&&UXBs(Bkww3DqZR+!X? z`}LAQVJ@Z2rOnV5BAtyTB~V-vCuN0YHD=YzdJOS9JQ-<;eG2hYS;;U?($U6yEXpS3 z@O>1yx*pKu^)e3G4fN_VOzmgOAY1Kx{?Gx@=a8ctk*3O?9`VQ{|Jn%7u-f@CDNitn z4?B;)#m(>8Stpe+<}MnaFR?sAct3HBe!;p6Zg~hXZekvP zvV?Ub+9FuL;Ld!~U)yI{Z2NE?voMqL*Y-VtZOJ%07Cfbw;9tJN${lgj|1|q;D0uT; zRn;Vd?9~BUOvAa%EJjTqU%zIa@yERvj~uQEJf=ZOMX|PCEPyCmsHVvwMD^fVN+- z?x!k!!Igy0-K2g@FHcYO-Gz-1a$YeUSX0W0VFvh_#|`H2a`;;A5gh(en>dey)rpQ>wr4c`a1M>hw|n zmbWE|vFoSOEqk*iLW^zbWbP8+zkK}iA3ufr&YjD;Iaa*Zoaokfll`=EFg;r~WmGFi zPggIQX#6Y_!--gxFbHw_&HQAUDSrCdS)DDNGHQMjJ1MHN1&M$8k8pK{F@D~ahO5RF z(WtF9{7oKRw%$-@=YE}=UZ^>hc`Ku{<*RAH#;nQH`#Dyf4=Pnt#hQ}Xo-5L*xq$Kr zn_3&#Cb+-;?T4_cbZ+uoO-76bRHXBy=nQL)an^rK(RKZubPKK(o^X1 z5xs?Kk7$oCA7}BC#DylNQ|y%THtejJ%88M~Qqnb1Cy z_MOTvA8n{!sDgaeXP#ixnK}JfJ~rJ|qPnFDR;Dwt>Dlto=`G1MZm}KJ*eIZBea~@X zS%&Cz=$}f;$4;CW3i7z(q;^!Hc{+*DJ0%}PI>s@t zZAm?qC9cT9m|lA4HmPgZS&`D$z$xtxW&B6AZ%addw(;wfRmnw^^G_9zua7=jUTZ~1 z)mGvAMn;%4qV5x=9%O3m!;CiF7@5~NGm)E}x{zbNatMifz?@i^m$;IUtKccS|2$J9 z%ia*U&)pEMlw7n*qLerM)B z+L1#e*pomp9oibB9B1Yqk7~JcSv5rVE$cRBitPO$M>Fc!0gu_MP~r0zrztEj zvSB-NfhDU7pHg|HME_S=z{pg0DuW$!1E*$}W_|L!n z`QQp3&rZtR9(?x-C-CZ0H6u>$9?CNX&UVNC6-NWz84Rv-<)P*`bl7~w@j&9_$VJq$ z%KXi)0zYJ8KbMu`EgM|u%m^$nvY>{bM2aXGA;p#p^~OCmFID#3=$Wm5!13?zZqJu> zwqnInb|8{DJrP!#bDRm=VT;w_q~a+o$BuLCeR!{}$ajo^vzH4{eE ztO{|$;fp?@KK$_aUk-o%;V&~7;z`hYPor+Q1xL1%c6^!RNocV3(>$TVpTwz$Kfu;AV zv96U}z0jE(Sm@Ts{^LLY{H1*k-6~}!HmEfk_rRUpqycA=Q28`&Vz2t?$yH9rFlKYu zcW~Ug){3E;4`NIW>2`LgDI%)dz@6Bw*&ynOm}^3s;4+X z7~x2TvLf4KO;QX$+2d zsN-ziPb+1tJ)$HV9#QisDgB_*?FDSAkH-?65GSG_s6HYHW{36QL86UduGTCC5@s1# zU}OQq?qMZ+m?(=kC>*|E`r%(8{qQr?#}k_wa~(40ioa#$rEZyYQ*1@Cv*RF#y1dn* zm!dj?DbZU6XolOYOTKcx9V=Yi*jrVMBMKA+(5tfEMdMUWzj3J#r$N=cL(jExt^w9b zd4`h5@dgbqbK^wU4{sNxBQ59x_PTEkO@^dNF1I<2oNmRXp(&GcE=@a&l@dImS8;;+ zLRUs=GpTj_3OfRDDLR9rOVuhw+B@Lk${jiQj1`TxvT*_5yT}ngUCqkI^jj-RsyzQV zKs_~n5;drQKl9>O3Qgt94M#iaS_g82tK8V(I4NDOo-)b86@H!_UmDRAJUf8zUE>B_ zU+gsNMCUDvf+FEXeW@J)_b+lo++MBf0wUoHCip%yU{bV1I5Y%59EtRxt!`tcs;uU5mMlNUL{*H^2C|Le!!DZy8M+;AXZ zobQI6HoRWluT|{wthcATA%yEx=kniwIs9+-xA=pe5qn>d;{<-s&f*?9P%tlWBSBY6 z8vi%$I6fK|Ih+15FU3bMl2cuyTD<51}!YGd@oE~&Q)lr+=UfTT z$^S&|%XZYv49@5=LKq?^7r9PKrxIhnVVZA^O z5d9?XuWls$@Y65O1sOXS@kl(0jO}Q2|~$CbtWHuXvHS6 zXgoW~V{6V;pUF1~+RSM8`n%*abU0+EUBa5&Bc~8R_Lf{F(q3dc3w^s@7up5CxBbOV ziO`)nrgogzUo+uKyE_|A?{rqqSMAg^hY|fDZrffJt1DfJj`g=F&j6yW%{|w0Pejsv z5?AF}H6eG|ur= zxB2&P;tU0*P5Eoke-`>Ggm?~q7esrv50czf zxN68t1Gv2R9AJ_h6BL^Po&$v4zj-qhm^S6FLH{}EtKk0};3(!>oM&m=l%k<<%8KlC zIYqkV0815mX|}@n+jdY2heq2>m^9cnR$!S#KTWQapk#Y5x&}KQSNTVT{qDvXZ<`vO z%REag!#X$_v(^T$r#^mTXxK<4tDSk1{I?6Qn**Li0|0cAi!{0eTQ(~AnHMQ95^724 zJ-3J#R>rKQ(S72weWLA38B>N$b(UTY^Iyu;Hc1mV&5r=*S#> zYTiVYgwA-en-Bd8dTDdRny)UxtXhALX5nITi>SQnHjLgL_6vH}l_4F;%`Uk~^pFUn zyvJ#LZn8IY1eRQ?N)-xY=^jDk-OW3Th=bb79y9EbyS|Zh*^ML*LFbw}G(*NyhBXZe zK~U0K{eGgCfcA!crIW^(G)B7h6P0K(yYu|g)v1G0VRc;leX0*%;JCli?{$>Nr>8Vl4kT#LN6V|GJk@%y8Cg-TDdb)| z_3|f7m?eO$lSOrQEM%YKNV2CSF2?;ulIEB+r_|~vOWEG*ztMqTDBWAVr?^Ppv$T1M zba!Ph$wM_;qkd{>Q%v`Y_LGzsRGBJ!M*TIavm}Zy&5QJQD)+pUxn{~&4`GFY73oz~ z?I{&R!r=#z;4i@C`I$zPY0HQBs0mNPXaUL6mL!Th-X!%`!}n7(X26f2{+yVf4s3kV zx59lcQWt1iM0r_Kf4$uH;X7%@lXY4!uz|$Yw#og+zp|f1+HCqTKvHLNR)E^^5#Wj; zPYIL}ISwLsbmNKkXEAR(-P%1*?$f6H*|+_7#$Lqx}c~4E^^B0D?8sXPVhyLZhw5c-^e-+EB_NP@>&xlL>`p_C& z?nz5rkF?-d()#6ZfAXhwshjcyJ|E8F7(H^`Bp7)%9ste85t zuDv#*vUCG!iw>Den|2Ydwb$iP#w$q6%~2d+(?@l{i#W9|KS zImv}~JXE`8$WI-Sj*Wod>5sUR;_e@UZPE!nhfK3BZO@ z0JnPr{4CO75K*+CXBIRj`m?qgoywHY)MC`yd`Vif49qU4%$xz7NdtcTM+a~Igrhcp z)}uDwJmHl3qi*;qYMZ;mR38PLOnYhrr;s(Hg*}qmt_k`D)2ZD}56KrhH-S?;kI5N} zJ{+0SJ05~vGvp_aFoI<$j%>9%a-zwXpAWDP-er?{h3S!(88)eL!-x8a0Z+q?4Y6aobf!3#3@xf4C&njv$E*P9~xh1nzg0}%`j|t{Rz&3!c(3#PaxR(E=2qE zrnMQupxLRmDT)HUHjm#ue0TUGo)kipioc0u9$1^}R%dG@&D&}9H##|pX8)+mBEp5X ziTJ`-dc7|0`ATMCbcSMJowfG#N@3b-1!MpA%fJ8i&>os66uMWP|1^!7nIQ&weI0#X z;80dKHc^hRLVo8@I;@k}_GLb5W(IR%Lx+okZG-vW`IA58-{q(AcfU{T;Brrjzsv7x zZ>p*=^GQ0An1RQU@R~Xd0>h z@>!amC1O*y;SO3I%~VrL_G`&>$b*?pJJY#|LpU~nbKW|&4pagJaGS!Pouej3FD$tF zR_}R8aov=k45F}+R&e!hyC;|Jg3A!{A>%6m)eX9jd_T=Y412SF!ce87`xXt-2=>20 za=@*C}(0$GFTuq_@*yg^61h>ukX#sZh zEF5uJXJ3Z17D)#=AIDn`a(&iWH`pe5&&08|>L(8QQ0pvEY?C^3ambOH;X&V9rwhLR z;>Smh-o>7*FIYmrk0f{TpT1VYi}nX}jpjM0Xw-rqGctwF)r=WL|2}K-kSL~8eu@hU zCZTqGcwRB&Nf(wifDvS@cO>iL@FVUYe#}H0Q?$zc2a3un6W>+w+SL_y>6`mLO58N( zsfV<~gdLQ`+?T4zq{A;&GWXlpmO;^Pmy$l~{WK4C?9KYAWRz`aOD)Upq!|%Ie!}wZ zKOlLQRGszY-d}{Pmiz_jEKu*1`~k{*%kU}omwWOY39$0VAN(iXsIZ3L-wuoDX2O%6 z42$@^S3txmgk!5}`e`1-?alg$F}7-FkT$V^XJ)YoG|ZNcx$mjeb#wj-nGL4SeydEV zh7v0Nd=cL`xsX@})H2h2)Uh_}sgktCq%9T9tCQBo^@8MsOjcc8zKSUiov}|>Xa8b9 z_R80%4z=`a?BC0&d^<6L+7&fg!ccqN6d7etCVqi5Z*AQD=H0m5 zkMoqzJP%shj~&Lq$O>3aEWL$yQ{3n}nst^zyTYR$H)T!x=WSDrneBg8i8BgGP3&ru z-=o&(Y}jA3tKA6`y$Mvi4xpQEM5WY$WbS(^cH5-CR=RDLQomGZzt>j#u@0-y>{hIU z$p8v1-kQ~Pa5|}8Y$_-387ZDB*_riM%9tUW&)ixd&H3qJJ_&r;;_^i;|j`Sbc7>@E81 z0;Xj-6uf6*3_q+*(=B`6@}Q-*v^IC8Ye7iyUxB9x1Q{B$GUhGCk^^KKx%U{^v;}{{ z;YP|dI&{k4)+gFsPqsJg`Ey5q|7|VB*fpL1&#tP4A6Rp@>@CuLhSxN~B8!ZegvPF? zU^ndf%T#1$#6xpeiu2b$>o5{wbeUYhJ$cYyGvB;s={dP>eEMLkbsB2@xRRks&Mj@d z<0t7gTi%ihJFP_Rj_l|9^7H(Hta+)j=SCve!%~aP%nz8Qzo3}6lu?hK-Ek)WRZ-6I zM_$HhUzL&MfOo<*)3EnKX4AhJW682#Tpn5Bb_A{Ly5|s9=ryXv9gbl-JuOUz(P^=6 z&klom-F#O}g%P3G(f2DJTc)S4&&Aviqt4J`Fp)a@BU;VS#eZ_iv;}Y80jHQKbibG7 zE9YnNmDe|_m*mIV)~>e}+FJV<^pj)&jI#WnnCf3)y`*OhaYp+p;2G_?$#dJ1vmnAG z#z~XSM;SFu?hQhXd$JdQ@J;S<%3f z~`0FX7g`9HJPm(D+-l9k= z_&Ul>IanEg{fpQe*Tq4Jj*AQa#tR4_QM}@Jge#|hc49b~veu*H83S!Hv`MEce}EmA z8DG$4#!n^siR6c0#Oc0*45M(nD=}rmOM;O=-sTwI44TwjZ~FxNh>lzFrYidoI=u;P zKY|O}HXefMq*B(HWtfEVBbGwU&A?uxHXwuP3$q)iVd6;NQ|7;lLm0uq-A$%JxXw;z zaY~n~%hXmETRu9G(S9fkb5iRsc!rjP!dmFIVp+vUemLisV^+9p)N^AlC;iA>870z~ z4b(m0B4B`w9M$bPOqa8bwEuJpRoPBM>K*7Tol(W$^9t8i=v`Ho%yg5YeuOqAuOND1O!NFZ2@dM|nRZiEiq+=9lt~6AX)TYLpqBDb z+5ys(n%ao3h(;V^x>5$zHJ%`Sio1^P*1e=O;v~~~Dk^aMXzEkc6l^%}|NTGzPkoT* zDzAnAOPwhDshow?>)}MRC*=*zyH$lDp)mq=P=bT9{^s4J)l2U%Uc1akm9|RM?|TWI zcp&l-RffP>oi;S%?H?E5>obOZ5kIrF?3)Uqn=l5~!Vkgw-+%g3aAgLL5t6ce&1C%$ zwj89fwB@GFz>5NzqPiw-sSXX;IXsDiEi}vh^CQkPgXX@;c$qcQ*Fhj$P4C6%EL1MRYFAjf!qrc$6PaB7vwt z*b>deJ16Ics}6Fa)=RS!YJLvZGUC)pofT7AnG@a7EK1u?lb?7w173E>r-z?}&BGu6 zKkmM+J5F3__pkW!oOKrMHM7q;_l})R@{Tj{b~`igdT)B+2DjOvfdM+U^XreQN)lfJ zN`j_4bCI|aQq}W3fDrm9DVyq}d=7=3Msn<|O;>JkWT!b|4jmQcODdX=7kjuV!RCdw zc|n~VRU@F7L&P}ri?SZ8w~;VSQjYD(0;A1(P~c{Q>}Z2D9Txa#cjVsR#o>1v0fS%D zY$PB-xX=AoFSsOA&Q=sven*Iz2N7M$6p8Wzh zBG(5K6&kQPT_-pKdnlDYkZOTAAI0$z1xpVJd^$WviJal24kwB>Vt2Z5V9`U1ayFGd z)Dy~+K>ROKjwfGncq~G!k?Geo(ahE+OGU=8jZ42|ixv;2YqR&u?K5oYK=0*Elw^kz zdo!avP4+N?50iTQM;gD!(>$HrEweRKE>xp}TzEAXo^pZPOTg)>e^lkUP)TnB!J->HqDmKZBy^{3&DR*9F*pr6kr)oP@2jY?^ zF64;|X=)CIX%{W-7F;|llNF*Zx%7rypYG;loF9@h+J~E`+6I;Fv}tPaJxo?gn)RM) zTVi(7WYdC@U8K~3q>=)L{nPk+m|yx?MsG0uv8kWq$>(O3Etw8gmB7R?GU1_BDhEFr z=Lj^Oz$Gf`#AKzmeonox_+u`mMy*a$3aw(rgg*>QZ9OzJ&DDo< z$!6tYnuImmv~_QFXppmZda|ZPy8RZG8|?0V^0)Xv$?@<_`<$JY_Li+RBr~2igA1GF zz<=jlb4CiCzR;)L*Q8{A`Ail3exb#aWi&eV0n+q%i!46aZL(Lzo2wi2}sUOmWNMa zo`=B(Ue94s(0uk+k$iT3&OK@dXy@v1jrfiHbww6QUZN|U+kp$kmS=`Mzr#ji~H#tiWF0ZX2=gx?HkNu>X(HYdk~i~IM#+EK)zPw^hV zJRBmNDgb}E^acuBP@@r9@j{2vc&*{DY*&31Z*eO^RJU*TX%gW>9i%Ub-Y-bC4e)B1 z&v3cV1+6J=MdOuta+b^@09 z+vRgwl$R;Iled22=WiQGzz3+4$W_kCtc7I4z6T{eFy)acOu)ymn<#g*fp`BkreA!7 zbeR5VKiRHL4}LXb@pV(J+MiSHJL`zQ>QNUAsf~6f3JT||@CsN(uqclY>RI@i>v!duJ!kv^Xw^-*};wwf057m6n%H-tD*I|K!jFYg- z{p>jsUu%$7U>mcQ#N+Q^k}U)jk|7wBA&(4UOTCpOo9i<5|Ew|9QJpjklT9gZtUW#~ zP$xH1thM18bWsNEc-juu>dh2cm5aoS2bGBXtrphtDr$u6<01|gc40n4G{iFZxA3fj zWYyKtEkjyM22Dd5We)RiI_G%;%<7CSnDpv3yjHDD?Nvpy!~EUz>LW+xfTi8k9=(w{ z9QvJX6sb?=THpdO+#Tf-)Tfm7U4;6Svg2Kb!iP8&xm-c^;=dJ}G}*}1DS|T$Pn1@9 zprvTw!AldBs#l;v`o)*zVom98!X*Xv&e1q%A^4P%7q=11v0) zx_wwrV~HVCe)**q?@#qssxz_m=I2NU$c)uO-rQj0XG`a{(j>8Z74HAbMk{AKlv zeTd;MUA@vxIj4??R-&tzHr6L7sTa^`1+iPBov6As0i}1 zahhk(e&Wci^z*We@%5{IJ9F%{HHED-gp}29xljWRAL3%#wJm9ApyZA*j9Zw%wxr>N zQja4%*bN{Uz-=??i%{DH;ej*N0c%reZz+I9p|%~OgKFC$`g(uRwcDCz;l1R3mQ8)Q zEy|p z-t!I)iX$+eP?@(aOy^ux+;EwzmWrHFI}_hckwxN#Uj+xV8Y*zNJdZx)q84(j8EfP2 z(}xf5FRn$^5IxagOBHj>LO%>v@8e8roL-u%2=;24HKlHzeCS_c_We2;Wvhna@X@+Q zc2tafHEmm_O6&EY`M!KrzqE1X12pGNX$eF|`vaz9rc&GN-l_Nh-q8l@E$^s_d980@RD7&;5nwbj(&yVUN} zv_%d7GwcoStkt+dSC#NT!>-~reIN$#jN*uvqH2fEARmRM@sseS9tdh*Cd=2JQMcwkMGij4%7L)X>jJYbJo*muaC9SH52X+9`y)Cb{3AoeeuZwN0I%ovX&uMDnTE#H+_8B$hC``3ouj$s2JNG)@Ird}U69&9z80CI} z#!Haypcfl+RI8+$DO@sE<}8?!T~Au^suk9)6eR=Yzqq11x_tJ_iy#e)i|f0u7gPj- z2#iVvD=DIFc);R;Bv{Zg zl(VCx3{ZlkBYviN^n-dU^fhBrl5@SVTPfh&ccub75^7o*^f?6uuTM>R4WeS3ewQ0r z`7EI3B0Q+NEasU6Rv*|xZWVL@UL7<#_jK~b)!irY4D7Qs2@^&X7X>p+f-H>^TrCu` zu7$A#kC7^hx4-R`p#7Qh5W0C}td~~OMrP%Zur$Wx_nju(NE1l2m87!TQGdl9kGG9_ z2NFc>J~(a3#sHHdRlVCr4W=!rsYUX{g**YlZ)__w{YtN*%WiC*KSp^rV=`2WOxu1M znb(nai^4A*)}v;~&%-RYrGk$i{g}zk#;O`aUy+YD7EJ<-ww=&RBWAHMmf-PFMRC5e zZH0l6@ASZ0oU^c+%P`SGOgug07UzZDFvy3MH?j(-bici~&>iNaMcYE|nF&8Fs=Kkm zJbtpM+-VbkHmkGGlxqE~*B1Q#eMo8fN*@2sxnRRZdAw??gV6>~dk{A2OM-84N$|hn z^u=1yyTARn#T5iM=!GzH*>#KXSrI9uQ*;+N7Znd18nCbc@vxr$ZgwVo+_D&;+dlW& zV_}J~aD`adW2C5Fs7uL1X)3|QDI<%4n59Ol0*e`52=@;*Zv+~5MFCk%4EcFH4W3Nj(*GVB!nXug{saJ>Qd%>+9_>u%}M-FkX+595Ynx|h1K;|_L9Qwo0AS;2)M zNi;!{wj&$vlH(P%Ac8TPnl{wxJ8_2Q8|gCzpPq~qA-5&PWpc37&KR}9Vhma;kOl2g zic9IBe6=$v?YIeVRc?sJHZC0Y7>yx2TFQe;k}YJXF9EGL}@J@xU22R1g-RtL%1=u^y^b zJg#stxQhkeT7Emg6h%huhAMtLL#AfNW-D&4%~%L@W?4HbvJC`A8ez=qRm>;1DyAs& z;mFZL1#DU5RXpc49!$6-`C)5!9jXG>un&5r(Hak&VdKV27N~3a?LgBuP~_-O-0jH@ zaxL%$`z6#{h6BA80Hg0U#6k{jRTa9NHVG~L(c62V5Cgj`UE42Ilx}Vo`rz+)@S!2% z7tiC{QTOI%0EXrY?o?9WFdFwNVeTNIor^{f2cyo$1gB@1P}ODwXG3)*6ICO(1vptD z*D%UYQK+H|HsCDGx8ggN0k)Bo2UHeO?`%3^Lld7ByO05_mX8Mu3shSQ?LrGkvIh>r zh>G94Hx@o~Aud?e>8DC}J2BUECbGEh6{tfJl~ydO0=5jGWDPy_Sw29Q)mL$bjlBr> zegc7UzlV9j_!jOh!o#o1da|ruk3h*6GMNp(OuvQ6=J5b?h8fVi*?F+BoJZwUj|n7Q zP|_$|J)H*z=pSLS|LA0(;UdGZ^(-Ga1!WnNdcH+*TwM49i=ZTu^3V@1L}o}AhoKs+ z9GdF$8o<6IIc;@7lQl}?Q$3KekC`aqK@m(;MI@mizdW?tD9FcvR(UuWA-4fu%@Gq> z8pm=jqp0xnQ8~@|S%`H>4@;WRo566I;2fk)=I8#t80XQ=&0`^FKw&u}2LlWq13lP1 z2ELV%gS#3???P_-<)Ix4)!3hpgS8bualok^(oLLh`@+AE=eI&GOAat-fw5$g_0CR}rLdMI-V6+ZO+?>-V?+TYcvi zmJEV)??(xjL_N0+4=ShIFc>QwL}z@Rn_!IebhC=sz#s199_@iL;W6*IHI&l^u-)ca zCa(h##7DE+AZ-Tt7A^9pC=6&n5`lSmRL2CQ_xo)mvkm6%r<|L{yWzzFluD0n%J%e<^dePK`n30)(t5~Rq~N8hPpTS3)9v5x|WCYasXnf zW{>g+03-5!vD>Uf!4D}3E~LKO2T~hOWv}7p zS|nrTGX_V0iQtFG4BtZ?fGrPAnEK>fVM(tUh5z9wGdOqED#F z-*8<5b?Yt2cZAANrI5Pqe-xK;9UxqxG(6`VAX|&%!@Blg9 zs~oiTTMuY3wo>h4s0zYZHDxA!EGp23Z$)L_=>Iy5WwV+II7knVafq)THt^wwOCNND zCnKU8%!hBisZ>^{;pMrITsbg?PgcF!u~7F0ZS_9O$8sO%A;(6f5O_kbzbw>UiXYWX zu2E9{>(?c&XV;Mt&nL*{aLqdAS0^FmIWSzg(fxn_cOix?M}4IJ-~XM1$Lb`cJO_qO zmn~y68|b>~rIigcxN`(6e%W1>DcHT1vtcUYm&0X%HEq;g!lpFRE7M1->&Xb{R%D|t z8#^Lw_+x#HefS4aJ7HCSL7AiD_Ug`tIH8?_o}w@gmbxf)a$dtTm+9&Ql$v}Q zSJf_3i*l~qvI*<$oaSI#{aXBHuDE+m5cWz`zb4-Nl`gQ_JPuENS9on04%)2I#KgO% z%){k0KxZ7L4D{mQ>ROe;uElax_TjOM<_c9B|x<--I3< z9P5MM@L@!OBW8B*mb%duZQz@Szb-++tGtbpSU^ik@1 zG6JGm1kh<{cYQkMSA=tMT3*vHkL2nG3t3WL)xa(X87zK~Uqv&5t6S~j!?UE}2*g^)qw64Z8(-aI!&j}pas*Y?(=->Tn#G2v zs()%6lI%s95t6dlh9vu5Mj+uJ1dWAH9ENX0t6Ofk#8C}e;?)J`2YXT7iCm9q3iqa; z(ZEP>*k{HG*;t?+E=gn@#E1nT!Q?@^f2U5;sLPEwIRJKB3&cJQQe-T6@ai_Tqgupw z9-JU7{U|Pufl8S+fxDq|Rv1_5w#bf;*Q>i3z`pt-@>%gX3TiXPK|~bha14#i#RQ~m zR$ycb1Bk;DyeXc2@d;@M&h#8Bn7qfhV`v5Yg6^ZOJhK#jujO`t&gzD;$}@KCU_IUP zJi>iXhTBlrm^_3l-S7P*fcZNz4)ZWLYPN5ppG}F(z#o4 zPDXkjM0%vRtns1bJdEKsIC{;|@}Ygd7wHZl@sI+pn5K&y2esF6+Cu{mDfUa>RFaC9 z40?L}D`0R`;a4Yl*P?4mRp08=QLjqZy=Qhm#7|Y{>XgfslGXaDNE^Kma;9pYP5C)3 zN(vS_e}&(%t-XE}XDA%$XtfVUjswjEY7~wNNIHT>XXC-CtZGNMDv1N;*fopEQ5Pwz zW-*vjHH$j67zeNIc!-m#P1LDr94TPfsfdzy8@BLZgqD=4@>>3?w)_s#r0UYcsu9~pR8@iL$82!49eW1Hy@XT+f$5{oj1EV7Mwh49_1n*o z>INP`;Uf!!W$wBjUmD9A&s*GqW`pWJtevHk#geqVeiS2w)g4+pOD79^DQJPwiIwOD zF(w49da~1jw$D9US4P)hN3{I)gtsYu|t4|J~QdsS{OuJ_EtrReIw2$@Q>cIeZ^hd|4%f|~pDX3qjzCThq%ykVJZJ4FYJtXPS1M#bmbtU%5KXQK4tvkE!l2y|HZwZ_`AUgg~#GO z2xM3T_|m$9qO^zPXDaGe4aniolU(|??_t(*@YU|Z%Sm8T-38dS9GWo6`OWgOx$sz} zhf-YnJP1pQWwFNuWxlv`9)N%hI8tSCa_a)eNof-*ZHX2Q(o&z4IC-GSu z`%7KAx;GP=4{-j<;*!MvTPR*R^21~S1}%psOt+LpehTq;EJt7nxeH_zWI5dAZU8*0 zZ_j|fc1}UYt9c&dMPEd&6oaVvb`pk_<57Y)BC9Ju*g*Uw{3^$z{O(0o3w~-Zp#>Oe zS0)^4(;_?6`p04~7`^vW%kvN?{ln(PqwE(8k}3vi^tVH0gNfpZ=i5V6KIWnSt#74( z1TRZbXmKpHk;OO!WNC^u-t^f;bxk?m$s7(uRG*p6V7N-a%2jFNBbk^%BZ#jaN9Ef~ z810_RFgT?|y;#@^-AW68NeeRw^dn6||HaS4Q!3L1!ItEIMA0iK>ZApJzsn;SRbPgD z$Lj!F%qx#zRNsbP7!>m7f|s`Aw4dDjppZZ9FJ1gt93G3XT##AIWk0Xm$2Df0%1R#= zA#7Tb<|m-(bue&w>%+AyX(dGHi?%k+`dzeUGCCfh4B6%EWS1If@9*O9`=Sh6F8jHC z#*8kk(mdKli7#53%U(Q_-+q2yWbPZcOPA%a-X;;ZHtj;c2*ZxketLI0>m6pb;g*L_ z@U4*d&D)-vR^4AeqkGDAKQxouL`F0vp;5Z%t=MroKuctW7O%@qn5Ty%$kWGY*^eEA z+W~%%`5rR6Ff8Kqr6~P;$v1u5XFs>K$hn4`_QcBEPl_zf%OZsh)eQlDkHpUCxS!&K z%D%?zJ)wWQ<;Uk@)fiD`OG3@LS2foo6Rv7G(;kQ~8wBo3jsq3wa2&OE4 zs|8?V^_3nnXfgYH+1e1~8B?bw)9LrxMfg?bz1DfhrbqO^ylA&UvX0N&rjw?Eq;iT-kaw@-s|A8Qe!0(9z<~$d1w|) zaSA0Gt_Mle9Gt?IpD~{LQE4fUy^wk%tB;ZB-_U3YLX91c#>frta%4kvV5g&{SvR*n zvXNk9`y(qodN#XRT#O2bl@bFx+_=dwv^Ub}L}_i7Ua#X;{}3Cvq8*%0m7W@%~0)xJwM)Ccxn8r8tK|I#9~mFH2Wy_+%(lRMcH z&V7WPpV6u=!^0vhPe#$x2$)>ya2<}<=Z6y4K5g(h+%O@dGS-b+2p|i3 zSg=}fuhEQwrw06InLd>~CZCrA1j$Z@dq^J8g7&lbg8()?8m60LFNDm>qm_rWT$HT0 zoL*7#IJn&|4|lLnhzwDOi+DI#WndsGV1?N1vQ0qeq7ITZ<{Iat;$tD8Z5kI_Up&d2 zelN*O6||x>?6plz<-Sw7uOat%&liVc-{~+Z^S9IR_XZ(ZiPLxbJ(wC-E{m4C%UizM zG_%O=zJ_Im5OjkN{n0P%5Vu^k5yR;lahi$v(cgYcpPpc1b{X#8c!;rx4d}L)7KnFr zdM!_ngN~Ab(}C<w`w1fP8eFx3C&j(a!mJCFS%Jo)ku!xr%~KD4k%QZq!Shjq zFZKAiEoGyt7M%Bx=abCCZA0sba>uyME8}gb>I5I(Vs)L_V;=#l+7CByC4)!4UAm6C z1aeu?aBmd|F0l{G=QJ4a?k40=VnanXvL~2Lq2;ZG1)^1a#?kK=6~2I50E^5bH8dR`j=i|*=Ll)A1;%|u_Giv0aqY2q1SISD0CeY*O51oc#4 z&RW`2p6;{wu#1v2kZ~BFo1QAtXf5r7uz80z=Mq+C@weObkd*&E_LC%x{d{+l8?5G?sQ{I@%6G*K zlvg+#^0dTh0;fIw70%wAD8h;Bmo5k^8D^*-=qIkdKCKo#KzMr4Ph9^!gC<3BVl7$# zcp9t=EADdOb!@VVTYpJqyyaC6-44z&spDhUPi_c}RS#k0)h;zN)E#$ZtkOw8V(Rka z;8yY2IfyCy2J>4Y*KWF)RiJkpv@$5c^7y^3<9Hug$u0Iz$<>w1~W?J4fTme&Dt z4_oIuu#5|fL8w`1RUU^Z#RYdjij32i2%|F-=ipJfG7_MD7ltPsO5~#I_(jdIIr4-j zqpvo8$fKs|u;n)xmQ9^+;oEo&{JBYaZQl%%vQhMV_%;&j-i9;~$-;aq8gMUZ9u%h( z!E0l25M_0THm-$@zrY4dq+b`jy?-R6l_4}90^;?hwgMteKi*8uR?$W(bcVj@!?K%ZBhdg2<6?tt@ zf_VeNuX!|z?mw`zBi3dX_W}g%Dx(tT_-v^3rYIpQjScdy>lU{Ef(<1x1NYAO7KWK0 zN8dwLBOL3_?mB8A`CVmDwah4X6~XZseIVL)%cqL@I0{aUj2#1qMBBw)8)j!60?`&;A6Hhd4=}w@v(Dtoz`?dzm*ZOXeN@_9 z1?OtB>#YUn9#_4K4D3sghx@b)k2__&EsQb7x$IwL*Cgm{+ zbbR&D(E08tAD7JWDGUR*gnNjR@?XCm5v_D!2K)6BoE*hkY@fru+`J4UKGJs_a|GAr zoipIEg>8*FsjKdxk#n3D2BAFeocyMbHg-J8orzDT_#@|(e?tZAF`Gxv2{Lowyl$vS zzlV7q@jl&VfnIqW;81*#na0V~dO_$Oqdt)3dQf^1wE5NYQHRPJ^Nmt0x0!CP9_e{# zxM;b5*k-yPWgKfW^6W8c(6);<+-5M_al~&~7_`+5{j^YfaNAi$jo+ z9DD+z$VNWVHbE8*SQrFRjyohArFq}+ z(ZcW-oxf4%dqIaukfow!QVU`Y(=Pm?h<3?-r2VD}n@!IZ3_~FG8`Q>&hJih9;?JX*7?Xxry zw@P8lQM)w2c`tukVTagF5q&r_K5UM}#_NI@;0KYPWbUy~AtU43S-%@rCX_vtI*1)qGa8_;;Rip%h%2TGQ_UHFfQVsmU!+%I7?KDq&+E z+`QmdKhB>0U*z!+J1T!kys84$)F zdnqE^(qZI{&?<`k8ZX60l;tp_rD?h}=BPXF$9MHoe8O!chN^!+?TAa8>QaMccuJ7H^_4;;ZmgycuQEsa5bAU>qb_y$1s_$xa^?htREoGFg!x z_d^<}sE7{j6ZL!Xw`0rk06mMhWz%R?-LW=oI4VICmQl`CT|B;_gOv=d-5r+JqqE_I zl`!MGKh(`x&MK`-0Yx3ZJO(D1*1_3KGOd?8kqa<7E^DHpwsKN~gd@~6!@yBZ6l<1RiLK2Sl+_>Tq( zYemQ9Qq;Y4G2k;{3HjKM^5Ao9_Yk8g#wLk#AZ~4d*v34z%V8V!5+V<2%$2zGcSRO% zqo?So5;xp*Q9Ss&wL0C4T1}~-ogSW^L~F*!`9I?ip(=`7Yb!Gh1}ZOY{?K)4K=ux^ zeV}Y3k7m6b{4Jit@qx)XxHO#^(7?FrT4cBz9FweeO%%*jEy8zAwSvanddUIeg6XP+jKxCf8TqV3Lr=w4>|Oj%egime}qfv6yM z3tWk&GXt76W_X|seEhKZAupQu>N1tpnEO7C4_Ku6>{wJ{BW`QwPb@&~yn`0C^DJ4) zXYrIgr}0wvbH|?n(}T)mb5}2ge*pFfTD=0ZwfK|y@u5rLW-u|UnWM|JR)88lI~wR} zYe6}m9{HocRl~T$FIdQl_@M-cfXJuDW~-N}@;CAVRoSvI-$zApBxp73Ex&jAj0MEi zNblQpm|m?btqXNE)K#Y8j?UqBhS`{*t>F#N!`86!DA`7tA0GvoHr@uOPVn)q{6qN+ z^Fg48GB_$)uj#4}`|7Hm&4S6XZdZ-pbhm2noSPN?9s?rUZTY%?cXiDIB<_G9D&usw zi;^9qr+ErU&*aMhT-*a9-!0`E>tI%m>y2z(ub!gUFxWhYex@d^Q4*vu-fk^(mz6m+ z@2-}_Qb1btq(u*l!;Gd(&A{{804=zEfTWKiW$fM9wiPA+3Af@I+o&_zq&lNcIH=Y! zuEp!Rb@Vd%)YP@JXIgpJt=_migN%6>XKxR(y))Z=2_|C~^K7BvKkCu?G9l z+-1icOq%QZU@0iKk*BD?1cD-D zeEZA;-Q=~y0yEvqOk*?Uopo`GbKFWs&7y)A1Uw^45$N&ThNot1-OFrTTFnq~ zi``IWw{39E2zBfmO!r6~pGW1Y4MO*XS>RnQRfSgFdp*^tM!(`#rEL=8tz48*f|HqL zlVy2co&m%dRq-a*Wn?4Dh5cLN@1rdz9PF{Ew-Ai;rUJ0E=w>LIY(ME8OvHk~jc+I5 zTXQ~2^*74rWm~_LR`c}8R$*11lGC~N|EvMnz z0Kg!O`KfAiVJtfwPu4nV`XA*>fodJvB%A<(b5m@G5H!#l4Inr`<&+I9g~6hUrKr{$ zSplz>mLGJBeHt|VbtuWx?9T#T)a1FacVZ~lfR6`JHleQWJ!o}^{7nlmF__B*LE zw0ic7=fz%6m0z#scYw?4qsYd7X&GtNR%g>req|IEr{d}v!SDpAEzG{tWhbbTev85r z>VWRq%pvzgu6|kO(c^M;N+0P8hZXxPRm@oi8h(S7KBtqQj)6&^1_ogPlsoms8gORT zea$#CVRmEA%$2{b(os2^_g)&~&fJs9sKMTUrAT)HBM8p?+`I0FIDhtE*O>ceEZ~ii ztL!S;M|qy+r+9yx01WY!eN~?^zsno_knM6m2$!0xQ0LloJ;d@W<>by{=;fyxEhlH| zHG#0XtHz3z66^?8P15#Ejp3=9qh97-Axn;cIy^ z);$mgXg|adssT#a@fLm{CcRs&M};@`m0z#dHoP@vbg2mYT&%d0B4#J&nPx=v5nQpR zL=2H!J>~vxY#);2vh_3s8``T6veY>LGWWM5`&Z5LY$``>01uQtj`aI;P#mcxfu`I3 z{uGG}R8d|Y{CKf}*OtEyXgbXvSe53WdwVhL=&T1gbexZ)ehH)9a~TGUsor+j2K1La z&(^(VP_4kA0421olnZ>O^o7P6_} zFLftP5bxyqD>y$Gdq)<4c;?VA>u1M;-15+L55dvz!u-L{IACEW%zqw0KS@ksT*)h@$ov2#|f%Am=CJ$$YCs}1|5}L!$+5goNdH{ z%nudn3fLR~RJvyd`ZR)N1@xQ;btg=x5&n2MPFYM;z}bP(bL0xXVZZ763!WP3^J&05 zRz+E}Zi2|EAgMD~v@(zS5;Ayc!dR;e?AK7qSvKy9w`?!@mQEi=*KqxKye~@A%Ja$5$K7`rF%Ilz|H$f3O2ZRRs94X*GI-t2P?wwFr5Ep&ATE zx>_`BXuWOlD7S`Q>t$)+9qMIigTyB45=gO~+6Na~spFVmbKmCKgzQ#OYxdi&|NGu& zKY*2)RS{zM+*1_B!<|}lfcBc+`YEi|l<@;*94GW6%ES|Fp*$6^qS5ft2Jk<4azuZ% zyB=-JxRGm6Hif7^AqXr*U3l^jafun^d3Ab-m$H0mwv4Y~vb^c>%}_!)p?w`aX~I(` zD3U`ze7*=bWCiyYlq+x6@^n{Q+$zrBifXcmMbil5-pK=MhZ=Q*QkOa-?t!IE3`qgy zJe`$d;WfQ=@m%4-Ewr*A2ZsWCdf|LLj4r##c=H9j<)Cua3F67_*${p{J_d(fR39H6 z?)2q(51F`J+gczQLw79b~aR%P za@)rU@3OBdn+2{;LYEcIV&Ij(KKQaK8zGRSahmMleJ3Bgqd(wA zj}1(Jq_>zuKk5CCc#zQEfJssMkMV%`A_w+SCB+7Fj^h;7(%;sFwQs8+HI zs&}IOJP5tI8xz`*WDPAiy!Z020n5*tZ`)E%1a+^IdWnR z``C9&%hL#ayL(0&R(qT-LqmHTp`x}_wWd@0`7T`Onn+(GR30!FpNO258l-zaN{&pV z>8p>3oC|w{xq67G*_CN9(9Yq{3_-wZG9}ooJAsLA!!W9D?76|zEY}@Y9pb~lpsQ0& z4#i=kQG8H>jjn6H?i@xh)yz{IFt)_H+KU&{;5Xtdm_6lY5c}#g1z6!!eJyJbYKq{z~|WD_uXsiRoqd5e38432y$OoUteGHAdY z`X=bY6QAE{G1QYj2TD&m=>faC#?ySv;x0NiVsz1m_g{M5{A|I_Xh=P5kfd*f4Kg)U+qtwyMd~ECu<8`ly?=L#h#TUMoVFH_(6|#4F1DhX( zd4X#i)EWofaczWM*|==lxP-Dm25k*7sZ7Y8c2AnX1rTZ>LD*)Cmg*9x}2&2wAfFVyzQk_LClkd#*N`>kuwse56?IgT*ESt zDCPF7H0gme&f{S_RS}rCyQ!x9w&xB{Q>bYiv=V1v`qud5)_&2zsHcr~<{tA!>{4$` zP@W*FoV12ZID#E6r_{-z{n5NKN6%9SLY0d_EBw?LleFqf}%{Qx0Xs(?)8$+;yu_GiNz+ShPMM0sqsnx)Qgty}QCuG$rDBDRWUyqlP_f$!x7!JeV#oscv2j%fORKQ% z#rU10p+lVa!s2$J4r;%6ON5T%V-t2w(*rc67%(+P5Ek1!%KFy9b>-0|@qMoi+v!qm zPb0GR0vaKL>)7-pXyimfc!bDaj#bZE69-Xr+Tb=-rxK`( z&Ot1l*1zRd*Qy+`!oVn2Z)g6bk**$c{H*S@7a2xm;lEz>v==zirk#7s=M8AD6 zOcJ1dAB;-SreGz)-+tMV(T}rdU!Jkif=V=R2DBy5xJW`Jn|B4;lxHridHoyDum$=d z1Zdr5kYu9^(1|#zwS1>^Hiz5`q>|no82S$JN1LB$f5cwsnxGR%WEKGth*;3D#J{prjPivCBW4a)zqKz_T(36{F z-MoM%V1no;Q69n^$g`5kh>uI| zvn+gHD?G1M7-lj`vP0>|ar)A`Zt0BF^9+!u2#QfIKY_g~uxDN@)$af+Kzv-`M-axo zZQX?hagr*Ua?=gV;&WNSV1WJrKW-rRM9y-*yL002)gf7FA@{?ZsEh8RI=vnh6s``s z`)mgOBHL>ioSPJ04VASj;vFUm&n6Jvsb{B99hSXB^blEVq{gV^CUqDQHp+N&-&^8H zQ1STMp|~$O4{_~)^!*}R>QMlwHCz~6X08Rim=>J(gwFQts07WD85<_p`}V~ zlb{pYaa2gylSv-ht6GaSh;|HX8?ych#aW8sQawNLGYEeJQ8u@SJXb9Nm0nBc$_92X zO?GiEh{>O&j^MykhP;{~8Ybuhw54+U2O0gm%+omL&OJI0S$omj%J7>s zbgd0B1~4WGysG8Hx_ax!+e3`IY~gOW#m{{Sym5YWU8Gg*iNF`vb>#}T2riCz8D%$9 zUjE}Q;1ILfE}iU=yMK6?bkX(^cA;)02{jyC$dOA(v%DHDi*za4ZrrYewBkuCY8bfz z){5$tN|f@>tlUZlIoraz+dT4rDkZ%(_({sHWE{6^Y3;~L;|j8WgzR2}$*k7^x0GG# zfST=662zrGTf-RLTZYv{#tTroAz0yHKBbjovwqj=4oplTC2XL z#+s)?642+Ab-VPX*u?(+G4N$59cux#0aUCq=Oocf;ZboJerHgP2J~sf)oj=E5}<{O zM@7xPbrD2{o8J`CE|C*_MY2qliPqw@j4GAdzFg&!c06eZi*+ztlD>K=WJLWnCC)D+ zck1^!%4wP#?j$Q~rg+2hqn`vXQBXc_^h8meWYyJ*dbX_L?6;N!7ag??Pfv+$fwZ9a zP9^a>U8Sia+HN2)KZzuc;J{KJWrL*A7Tr`4#XTraf+#>d0`K7HN*q`yfFZ1^eSqz# zV-ciHW}GNv(Q1?6i*cwFcgvlmQ@wH&vZJ*`o3wpxpc_9HORpNlbyLs1S*M$DJ?pE? zfVoSmnx%te6Q?On{|*xP>B3oBLE6fq6Z0VPpfHkez~K-vaa(sGz^ zu?W4Vg49KD)f2jQ$Wk@oxH__yq?$8bGwCWyHD|hph)3aCA59lQ4sZuiP7#3}AD*L2 zohG!I3Y?l-DtLB^P* zQtVhat)T!d2Tod`4PE`AEKQIW^sX^U)r5Sd+Uqy#$5tY>m!UQcyU5dCirPTNrE^m| zo=21!KCl**%JM^NNWiHdTZ6LB@WHjLu9hEOgThYz_!?1m`Ulw3(%OB9jc7aZW2`q4 zopa-`Kg^!dm6N`Gf%jPL#a_y8EyiAR6Q;SGQbXoEm|ohhQ(_n~dw((|JHV}}SFwDZ z)J*k2LuxNts#QweWl8nGLV7U{6^9d2K1q>|Qx<;Uq#yAk&7&VUYhAv-tXOMn0GdE$ zzcS*ZH0{D&q~qL+ADGz$j790HUQ)hB=TXPuLt!C#M!pV)^s)UsXxO6p5k***QL-z} zLYWpM77d!*v6f{VwV-j|bCRNoh(!VirzZ*_4)FI`XrRHcpuc}D;qmw}k-1Z+^RX2e z?Qb?JVc*%@FoRoB4(zGCv>0_fECRSqX5V(qLly+otqQ6PJ9WUZeIJUlq+~#=t|~xb z6->Dai9_VQEOn_3T6o%r1E_n|n-L#RMLws2#hua& zVB$Zfx!qUFv~VTJycw*7AdJGr&?HxocM@0mbes!suMWpT1Nlt2>H$Z1a54!h1fyU- zK0MqZa|@!dlS}bdl;M8Uo-_X#KKtJz%f>_3pxTqR<;F=A*q@fnbmrA-w02bKGwhu# zJg;!8$G6Ncil0kAPlBEz!laDDWM{cf>a2txWHr}Cxk`d7bn*$ET$7VqbTab=Gth{hk`S6Ye*X58 zU8fN;lP-_pQha*)L)o7Bs?e6IFVA6;rJ~LG7>7UEl`s&Pr2ZQ1{Uz+(NqbWj$Bo}G zkPCX(WAW1?o;bQAm>k~Xl~(WHCRR+o7Mr$BKlVwMv zwt#y{Xmc)_h|<{6n9u-?1jB-UC38D67YF#;`SClJ3PxZoO22J0=zmV!9;JqNSX3UMl+(i8t|Igie?DSJJbWFfH^D-JFpY3sFaEG24keH@+HfKaCGnX+ z@1r1pgCDNsR==HGQxwJl zWXY=#(3n;w*>#E;txjCXf(uyCrdQddHhK%GHP;W2_P(+(gM4tJ1w2rtww&jMr!Zc35EZyY+;(~UqT&vsw$?z-SVM_1==e=#%~E3c^-FDWm+M_x zw=;)`Xs@WgyHS-Un2QI zryfH@!8%IHG;?hIoKjJ*0e!l>t2?mwC39&U@wAq&N4c>fEav_xb?>Dy!!?la{6DpA zx9b7>mK4+9XJ}@QS{Du3y|FZf39qP@4Cy0`Ih~IT5|51xU$BU!3@_oMdLqS7d;X#j zb_FS0&iqAT^-KW}4rq8r{t6#082^yHUq5Vn_I}77uOF@*UO!}i*N>rIJqd4gyXan0 z^?i0@(fWu_$I|OS;n3CrOhmR@D8<)-dWJXqL+PupBuY{#R$8>Ifh=tYE((ziPuZv@ zk7|$>^r&n5vrL&$DW|6SsT3P+k&h~8-x4>NRKJTe6|KT&K}so9SW8o;n|)aNYA=2?CX=r_3QT}tNG?0LzLCK2F4}x5sz_Q zB43dx+m!xpCxiM&nLMykqp|^7)GWFvAzDqcIOMJX@d$jnd!U2K3ec~!(}8~3szJZ_ zn$X91^MS*!{8Xh_3DY(Q@^oCdh(sL{%_^mZ(}p5;Hl&|r%A9h!ztm2s9q+X^E~Mkj zjiD-T+H1c6?v$F=AWu;AGgz@)D96y#y2)@#B?lV~3=Qp?4h;2*Fb=n6`tv;B8t?=a z*V2ZCk}qX7KdrX!wEfMe3{C8N6n3f^c5Of`7PalKE$^zjmH)haEBxXu**=Bh={~&_ zEj%m^|0T*?L75Lw##smq#1JNBw6*WHI+7M`NK82}I7C&~C279* z9vRoy>ZB)#oPMV*iI1-hEHed1y(oBdsdK6KZ!9*JzTmy(Ug^*R#OH@9q1#HC` z=wXD#lSLN&OB7X$>%}%26Pv}YOt26l-TFdKzYO(;$e5&4>;!3AgL|Y(ny}$B8u7GUDAGb{ z!S5E5?nR2_hR^W<``W}GJ53JQe+Z?Ws2{MG5WJ26yFK_WWSl0|%tmT-mP|#?emYDF zRp+lzoPJqRq$0pFb>5PjY(P#4U>xZe=$rc0ouflD@EA--T7PZ6whg}y>pTpgL(h+k z(^IcuVe_xb%p$^So2K7Hd(q#AXC;VN$K@c->Ke=c0p%@(N;W~b7ay;vRYJe*WKkAS z8Ewuu8d@L21-^&deuHVaP9_^w; zG>D2EBbusXacHMS%?`>@x9g;cG~fvXcx%*vamjp<$LoB{SEOo~ZvH-`G@7bgiA=EbXb<_CYkn#=<*$36P6?R33QSf+(V%q_P zvk}^BvzSZx6&-#G-jU(uF2o9_8~vlMir1iYtV^`Q`BlfiUZQRErZ!RexvMG9NM*L` zb0e&)En^m_(8AX&5-nH1D56~gopw%Pkk9Ah^z)hR3cV*6SK5VrIOX{+(<%=SMHu@< zS)Kc*T{_Ri@%Wc>bq{WDh1=@f5ifC){$n0OBbC`M*dOU}A3f)#-6y;NtmWV1O1muy zAD2{Z{PMHNsqwl?skW`LlTr8oyA5qL1~n!QXEX*f#>X%k=p6dE)i*t!rtqJ#+7CDRa&@03%BXc zI+l!RDQzeLn4;OlUBok6c)?e z+Z3is;IwS==pu|Rl5EqkUm98beejEC49}0h>;%9fNS(|$k;bAJbe|B6LtW!93zG1p zJRRaqK~Y<1Hv&qNNp`$j_E3Sg4<{|85l7AcsgA10!?3B@;2Qe?PXR&CR$6S!QJz00OO zj6%}%V5xyJEW=MIGio=-3aA}P*cSSeO0ZLGw)vu3v%xu|PQ43SHK%E#LsT86j%ssy zx2#miAF#*uvVZvpKz>eT#6|Lb4a+Xp3cBG~dc1!M)|tu!fE7hIv=rgq3Eq zqXE*2-a}m2wK>^uj)^22wkg*O;_k-@+_tJIT@{32{@7jc&VCX;$qtty;^ZTn*QBnyDqE9BBD6GMQ`YmGVVPlG7NayKL&8Fk^Jm$cbP+l07H3dJlt55?4mUY-qJ5tR za7cO1Pl_5g3q|0NrgBUc29M5D7lLLf3pR+RsIm{s=QO}zd=q8C2YDtz>Ztf!K4U2i z2{@A^tIW@RaUAA&2ra4TDVqFb9Z4#$53Q4gt2<>457)}tBxw(E8D();TYPiKts31t{`zaynn^0nLn&l*g;oNw3W9@ZL3uGs~afQfG15={xoI#n$ClVTnn$+WCz)VD<)xU&TK z!GWXL4qX{W)8G83Z#bU{f6T2e8pV)F=w?oJ(IN&OGkKjAmgqLp9y&Gv%4%8Lq{4axNVmITZS$*8PEva|DTu~y*Cw}Y)K`z|7)%9n5!OZ;TykwuG zhb!_h@2kdq(eMoqD`F4-ybIRlHs{5{?~XNEU*bX)VYxlzd6<+mZG0Q%We}Er6#tVd z#UYk+Uc7Z!UXvf1aepoR+>oE+UdA%_w-y__{{)vzF`D!8Jdn-fUW!Au%l#ny$5nex zUUOcSuVi`t>dtM#_}N^8@j2xqQaem9AFxOZp2IAD`-$)E4hc6cX!$mnnfI=B=qo%6 zSx34M2N>J-A`Xd1gDX*>i~pQjc4;_DONKt(yjR8NQ<_$Q?foqKNh{bE1y_a1$Y(S% zm%8G!A`63`wJNC4yRlzm`5CJP38>STU)XH@dX5tJFq$NLXUL*pZzyYbDT<$6`C_`z z$GF7{I`|g8S#Gp(wmN%lLeq7pgWtn9n>{rqUWe6F?(ZEeXf zZS@?AkJTLKp$c!gZxdzXr@fe-k@fMf@aiM_#p$~5mEW_u?5l6z!#K3r&f}kT19%nx z6Wt1{-@m^n1Xec%E>PG!=IJ-W5e1Ka5eAfyhc9^~vRlL*QkS)ES1-~~ejG0#Ez2Mq zP^1^D1>aA>JFxK%6Yj|dJMfT=h_GDyXAu#xP81Oqlur^7kLmCnb*sa(V1?^|NGJz& z_8~@YrkOHNY5wBpL6q#=A5bu;C;rrYOcDt!=m|hAly($9=)#WAWWyS*=86x6$QXoA zQ4+aKwOdNK9<+9pY1tVWxzR>u5fW0)S~~>jAlK)xuv>XOTL4!j(_sA8X>q}v;lx`Fws`|NGWnl`JjFT^<(N5IwId*-4E1UzgHsrp>C9O8i~Bj5OTp9hY6IH2(>EIcd@(dsVe@vbU?!1;ZdJYO4$2 zP=#4_oab3HiS%n8TvY%QwG{>?kbI8!aHt~3y3EGx(agS9Sxl+n`lCl5+e)KPm0EyM z_qt>`r92Z{7xijEP#r6uK}EAPjcxavK9!setj=2A2`g3`M~@4c<>Eu<)opBTNf8@3 zodmo?3l1Y_Jn`J((Va@d@o05CAswRF*~lr*`oT8c@BJjm(m2|_{agZjUHmgiF3$SF zHCetx%g4M3w1_>*^ECfSFGH8Xd>4Wi=cDaeF9rU5@(=M@w-#zBog~GdAE3OQY$@y< zzW=+vFql%@TA~eewAQuZz*RSCrM^744%3R^y-aavn;O(|d0iP)ny@0+B+l4t$smgL z8^|n%TRgyJBpD9@*dvTX@V_2;CM~&NMkV&5`yR2}SKaq$<`iOe8LdVGt@dbSA8U2` zj>t-xV{D47WU|{Ad5D|C-2NiU+7VZ%3)3yAj5b#Vl%ek0X;Ec_*>*%3>ZlzLE7MER zA+AhitIYi>nuBO+i)_>wa~DjQ)M|nG$x;a;ReXmAMr(@KSX{O@XOxU)qo28E5~QLh?!2GdF3t(+&G+QiVV(7(YXCY)z}X(&Ll&I<=T{S>HN~%3 ze7>7b!8z+2t_9Lrv`IIb{$TaD&l7}FB2|VbOQpY35t$X4YbY{RkV8?X`{+j)_)j=r z`RzQ_>B6*=N|(Qqq4J^7=af-8iADyLk4S-hEaH_U^O-@wF zX{4D7*&@|*h0co9I&z6oW)x@QOz)*svY6{_lxxal?>^mL z-_BUA*Ebi9Uf*U?27b6sO8HD=rsXPRdgl)QnH=C&w2DzN*C%R#dg}1 z>1H36e&Cn5c{8P!sY_8Skiv`YIQlzB;Fk*&sjBcFOw(`#z7Irp-Z^VpYfu3O~%K% zgyQpExLHd#3trIK-*)GIJtw{K*q3wOcOLs{&i2+j_p9ji=3`&IdER~V1Whtto|z*l z8Lx$Xc%^X_+`^tT>`vWayrJh@`-?XOwAo&~hnmAGu!beq^~?=%rY^K>h{9RR+@@eZ zzrrYj+w6+tOEEsP!YGKtyov!?<_Ko7lvK81D+c779Hqi2>7$Zc1B~hV7ZeY2dTq^?=6?(^Jd>O`M;yoKhAuRLqi1 znWECG-Rs*~EB5;4q|@uPs=IX9BWw{_Mt1+|=#dB0Zj-5lYR9acZAQ~tv?h(C4NEj} z1Z_dVEQZd8<(n{Q_Tm?N6jA)O)e6dhMrgxPA}EW}fPV(gzr~2G6oMA5l1f5 zh4m!2Q9V|2EBiDlMe#G%$TIqJ*jRJ*v8E-rhgyv{P+6_kg6axOLbbf5TYsghIe;CK~QkB6tHFt_{;{mHJ0 z*CaXb67brCbXqdN(9m(+ zz@ic|77j2JbXh%MFW?*vx@G0GZJlw%o!ls6Nez+y-hL*f8-V8#8e$c`v2;%Jy0@ zRyY(7=~WO|DZck#_fZmHrA0s8Zb(@et1oHNQBYYNtFQ(l9*fWZ>u2$kar22w5tq-K z?Q^*OwhF(8Nx3PainWWe)y8qy)hm^O98mo>xiy2q{-bikB{jCp9xLt`Eq zgExLC7tWYb+ED$78Lr{{dpIpViNdwW!4?o_!@}l1dRrqKm2lcG6w(_QxyXYPdvWU` zwQ11>2QBVNWJQ!lk*mokwp6K9HMPQ(tQ;2CvrH~nm}zRsFyXSyC;%hgA)Df@D8v0m zcEv$5bw!4B0@5*=S9d`6OU!?1wyvHLgI}&v)b%Jz&~rkD>dDj}*+alq3VdAf#7B{i z!5%pkP-VJ^_L(r=v`UHY;&zPE+Ya9y_BxWamV{=A797`@Xs;TEINYdErE33mdAOk- zv}(I~na=C&m`IMWp@eHg1D=P=W5_KUI!n>x-p-v7B0d z)}nhxMsr<>6|_Zuv6-Mg?eZM*DD%tb1fI7z`hk_(KjJD45u5E|ea)jg;pC6H6ZSPr z^Jo_(qInc$!c$3`*Q=L3LBg&mOrT?nI?HJNvSA(D;|+HMwQTyeS(+-l2g`t|7v42nOK1r;_xMzArC&2?S3RQ+4`)Za>VfP7X~C18K|AfDM6~7-{1~K9m6adI>B}aLiZTo~auaxA z9Fr-P5_!*P$Nr_kKB#F(-q`g@C|A{|9NP5ArYCoUhq66XTx7X|GAzG6>PeGDR`zNF8?oC8a3_-{$XfVM;yWf!i(*r3pTj-%pNT|F zqa;^lJGL>1ZaZYMct4$TXESrdW^Z%eHA$vF(njCkCa_K0M_^2gO$Q@V(!H@2G-YW+%rp z12=VRW5pt?u$PoDds1YIA9?p?+PEhMpe;ysZUg(nq)(L8%?Xi^qktU)4;^*N{BzYI}G_C(f*t-II zGl^V?2Cw)C$;)$9v<=XT-C7i0L*WTp#i3u68yUF=?@LHvZj9NdSHma{F*dwRR1b}* zU_*FY)*-uazJ&zNPMLic_x>&pzta>X_~j}bm8L03XmO%cWQ8UoAucN}pYfNIAi19e z>7F9%!7o=qs5FJqLyHpyAuALz4{=#>xyN5_g1`~9m{1dO0Kc78QN~ahZF#CTa0G@> z9tF{s+r2n|CY_r~=^=q0i^nLD%iNLxkDQgN49b;n+Wu6vz?6rklS;5yF;j-eg#$U4tvWg$+2RAhGsTA@$KpiAo-9FL} zR=i@2s3z>mcEu)2vO|fZS5clOdzfhX-OnTcF_wM%oQ9M#df6~~X)+38;%Tmy%C>4n zk!`PL+f%mX_iC$QWyzS)xazT}VuwJFbe4&$g@b!)*cUT73aNsnh|Kb*@;OaJ6)FV_ z^&qz5eN|ae1Ja)$(?mG~(NK#LG#h1SE~Ejg71x`p{HME_#mmyX{CVW7(7FnECoIe& zA$C+3=^@{S=YInlz}g9j2G(d`o?B2Z%!3iXGZ(Le)QTsq%f^w-Ib@@_IElYMch%ZJ ztyogfo^H!+?W3&01M|QUg7T@3@ElpN~&l@?2Z5)JWE7dfA z*)=bxh_s#BDu{kO<)=0j?@5cs0s0GPMLL7@PHI1kcSc@p{X}oZu2WnU)&}&Z^Oci{ zqH05W^Z5#`6WNa{Wp43SdAJK-e=Z3H*MHv(j zIM|p!Wln_iLsCZjaPySQ4P{_hh0;`#s}^n3IKa|&H&G{P&r|krsHFwpQH6U-`yi7B z)m5!HQB5W^V_tT|hpQld)OV=(Jtn4Vt9C72iqiU71glKxR4cWzC`TGXPgv29AYij+ zbQ3?agn=-;d03>e_;Sb2?ojo5hvJkv!y7zM64h;9qA8qv4L>#wf0jZ)>|s?FySYQv z-)+Fk<_bOCp=iV2%QK8&1EayMpVORT{4tMsXbRgP@MAvl&>Xfo%vjl6ui?iG;_V;~ zRFFr>QwnBlhw}(oGYE3l4(AiKW)zAIa=&A(|{PESvIdBb+QVBwZ6Nmu^kId&Ko9izQ+soT8P z`91}|sa))9f{(@qX4v{T3z8COh>ucjl&@RECTYXGm7-YEqmtMP?E^MSi5`C%cwA#D zR~-R3ecI5-3N3{c$8>0+O5F;rd#qWt(F6<1fgW1j*oHGk;0}&yu!pMwDK2V*@F~pm zFxWtwGb{=k>3@Yq!jM6qm!~cC>GMEg)`W*mUk=MfjVL17^0X~a+M@XYV|;AX`2g1F zw!2g3ujn*G{Ay=$QC}6?Jjy8W#A_yNOqDjKvDwfi7{V$M3^D}-Qy%S0UNhx^2|1fb zVd4|H(P_hSJo?*}?(#Wg^ms#@0~mC})}u^|GDwkGN5v6lFLkF^+bp&pTWPA_iLIIN zP<44JYtTms*03RI1a8Ist4#Zp!MOKl$~vqk^s;WkWEUmjq+a}np*cF9(V!;{a(nE@ z=HRszlfH^>Trl~ zkt_U_$q}>3T3NY-()f6QH8w+9)gzinPz>*V1BhYRU&UX!n&%Uz=?UE>@V?vZMWYLA zo*;cmp<`J=g`k|TLKPT^{&iS4F@#ttSj1pcqo}}9Mk|>aKo0sM{(e!Qlmjg`)!ZT8Y)xODHs|vgSS`VlT5sqX)Jxq{B8A|7Ue31Hi>vA`1#ug67c?p zL`uV?3<6hd89{o`?j@AD?AQfOyUy5}3d0^0@xX>hHZTF7@m|0dq`!#7YfSqHNq5SI zm6GslQ{T7&QXx^LaqrQlkPWk1$wsQ>cR*WF{8itZq6fGQcTg0xRGU}W-I+QHjlM!E z#rdXenry`1alIXNz#V-r_MqgFRFQV9Z^A5((mX2PDw{GTYQ`|CQf=-~u#eKb5eR!MeH~7S?(wq3?7m=l+EFM|#`!O_7D$=qZAMs5JI*PLb?;=1GgzE<4sP z$(sItkjA+}qE#ImMqBmBOx4HS6^_Ynf>6aOK49e_{!A08xD+OE?ft=@Dox=DBe>H3 z;PYkCTy77Xx((J?$UPl)xt~2pWw?Q~0^9T|iBdRwSXA~dTlVndtL1{J5agD-S11b} zS-{V+E=V@P-(=DnQ$}~Ls2NMMFxix09PaVK#<2UzH{RV-6RBwi-=a;!_7Z^sPaB|U z`x+US3`k*j`@HxC_Ugi43jrWI(8l8SWT6=3La)QX{t|x&$&|Ujg$D@KsKn4MUW(fZ zOCS7ot6Kir9e$dQFmn{_MwwNg z1UK?5<1VxDQ(I7c{7X|`y}Zc98E5Ax+xyD!Y!|5TVULbn1_yod-wKhJ(xzMrWtL)s zg4s=)Y-H**(Uob60ct{QMfwU&d`Z@n#uJiOtH^0Mg%GZk0+t0suV(2>C`CH)KP0pj zIhCjIpfpKR78fkESK_ZFQQY6`!x-2?O~4 z@=LXc<{$$*F)+IPFSvcC8dK0Kn=FOap+6NS>b9c98Ir;P);UzeNzcDJP%C| zjjE^R-1(|djicK9i=r%G-$WA`n-PZKe25FD zMs?kgx^K@{q8@~7hU)Q}Bw0x#rs9viG$jrFmi#f_t{^21_JV&7&IdoUCAiI~V~`(k z&A?O{LB86eJL@fN2~wDYs*SL4yU=vgh)>%xdctuYxxJFeFA$WXu!Ao2T-XKjHON5^ z#2_{fx$DZnpW47R8E}*fnbE^JNuo)$hSH}EhNTG>qUvEj9U$Q`O2XhM_tP=N@>qyn z6omw|Jpb)iYo?yZEocP9VNz=|PrMazW`49S!fhV9i$TRF>(K_KSEivWgejd++#ltY=DJZf? zd_6&JZn=f*PzAXmtJT(%mJ>_*Md?M$(&_HghY#=J{bI9Gti5!i$U&%=?)~v7;pZiz zS4Tcc($xc4x(`XMQg39ti(>2A91ChROa;mvB}L za6)K*4nlM>G4+u9j6PsNacelrqI;Lmd5bn`xK4nelbd<=+zVF<80SR~4D z8kF|r!z7KjF*!Q6a3Ctk)Lz*#btBDj6%rOaGV756RHp~x7`3lKCS!g4g=rtCoRdbo zB7KB6&^#guYa28-vrJ}`44r-}(sxkMLMYXXOuGcj57OF)ayq_x_IvvN0JgX)I@dU!=+{+q#3Sfw3J0XQ_9LlnsF3GK2L2-gCyWFQZpyM zP=V<(to%cn%14{gk#P!DycR@TkycltysU%9`Y(8zg|DzHPE?+`ZU&gv+P#-HX4UUd zgjKKpnTyzm)mq7~MOckUSj!3TikTZ)@E$xyI}2Wi6Pj5qrjuJ)Rnx)w4Nz+_OuyNx z)&i^cuNnz9b^rHmte3Ni-FK0mFaG*F^L3U6E_H5NoizMLmW4cB zh$fEqcr`(!@Pb#EbSpA{rp(g_V#86V!jinGr&>QcYtIbDLWR2xiV21EnPuWZaK8?W$povhZ6T* z_e&wh;{n4=_YO>HTA~m#QiV2Zq9lu-D2?eImn}?JDnsuu(-;e^++>)y6EN}`?nl_+ zm%EHT=fE@z$114fP$a)&-0s4`TBwiI2`?S{vbkTfYDD@J1nb_%I6mC23hGj%zr{zBtkpr%s;>cDo{P;^tP|;w2x20aY}Mpf@>eVY9Ip+>Qjk^k`!?d==L7l_QL>A}D&^z~bphFG zv_=b6$`Mprg|9;X;_Sro6t1b7t=^*{rgZsGIFq^csGOOgsNhzX$m4YD_igELECSl+ z5RY5RWJw2R_AknX91anIB?J8|M0*tmadB+p0J-^A%Pp5~Ew4*ej^Pw}+0JsZG%#4L7C44ExXai4Wu_fjhC?lkzIh9`1S zGTNt!g*XW{03W{8W@!f6Z4U`~Iru!FbrYX}NaeSO$%D+mqY~}+VG!X4n9DyW2ImHd zpHwcpXGOvi%~4J_!_3SK&J7Unbgt3vhCXJ?Uo?~V5pFh@P2Jz8pw{(m0H~g?aay9z ziDk#Po)tDohYH}t*MK*SNhO-|)GoUIt66C*v=k{mOoA+hZ#p`2Jmfv@x-2N;dzJAc zp0Qv=2{)$lVdbH1y zy-ZL8&SnVAr7$*R43h z?M+5&kX}BF%3@|ht-7I?(O5MAG5MoyQO!b=37jdaGArXiIl`DV`?8Nb>t;O?<<4!}M|AwYt?kYH_aUX7>mCod5GNR< z)-H%~jN`v~k7I2cX)KIg#oFrtw0D^4Q(#93(l7~ASXXX#dVLrMVBd?hTS)phq_|y6 zvs-zIX-@t*=-@R0S&JfX3|dn#yFx4f(NBUjIa9s77QAWdt**%0v9!cfeo;g_Tn=`w zn)bk)s4bql(pINye1I2O9zqPD;*=sbaj;eT3dQcJ*jzHm+pS7J7hSwAFejnpEmfrt zD~EO)l5sv9bY>ESuLsFd7oz>y&;X@rj(#=tabdpAXCq^HbDP+3-q!teRzX;7^C*LD z)5gb!kHF9C0$>qd-RN>-y!bq2l1(~$34sC&tY1W2T7I8z6ei3SsOKM>4sX|LdFc4tD1WQGz%e(HS1L7J_kF66mgI19uUV~yu z7GBrwAT1Te;=hB!mM0x^D)brHwVd^EqQ`!;I`3n`sLp4k(yGk+xQtciRkbM5DPX3yW0L&?7IUeEvSVjVO-bFF(ZXS|TVrz*hRQ@`p_!RT0Z0~0SMwV4 zdh;ZIxW@tq5Q|4v71ig0#RL%U-xok25bNikp|lNACM4dL4Wle!T*-c50}ojPR+thc zPcT|+%8&+H^aC0r%rUWR6Rk-miVjnSvcnytdZwJY2B$)qWQ|NY^UHdFo;sJ4*T|VS z|69~O97-#Ww5sb5m771Lv*J!FU63@Sx9UyW?*Qu!o{JDV6x|&Pt=`tn&cD2EKn`rg z(1_2}0XQJ^R$hNUxSpH?h$Y+nF?q>*=t!`!`_jxjkB0UPZ4t z-fV;V_n3Q~0B*qkxVwpj<-}drSf?BYrl>CVRbJ7p_(N!?9!K2bKIVd7G4-5bl^G+1sxd48ye_AZ=0x<)7vO zcgcMIOggax8T}7Pj~j7L%&cDX_W5%<1rB6WFS<+5qiV7I0Y%mW?9QV~d}^Up)~fTc zs@LU8er(#r%qnXE<|@x8XW7pqly}undn2vhfYsw~!aqU&le2zmuK$R4*oPd-=?QN! zY;H~YZesZGr0h_HF*pXHkr z{vIWMi=?yTs!L=nEm9d15C>y<^)$xGU!cdb4}N_HIZ6BWU>*IumHGf;jQ@AI$bJUY z)yw^HyT#w1fp^}6g>vxnXPFFt`|@HMBBW9Cp?nDBKqpFU#psl@83QmA4yN7m@8v@p zW9AF+lGEyx)D4PJR)Y!aB#;1HjMHH{A8&jF1Mk&+uVHu`xoqOtsW zP-Cwt^tTXBvDkhd{szpSfge?hlpL5>=<{U3qWSY+-eXTD?EGI->^Ff*2RdPpB2Ls! z_9>4BMNRR&gL8f=w~bm-UThB~=vH+>LOKnL($urT{N7%9tFHLz-4*TKq25-jhafLV zCR92WH{#YH{Z*cNi>Ekj=!EDqF{5*!4wiKOlf_ucj#Y=}qHFy-xL)J69}WScU3Unq zFt+?)v|leUWAW?VT3YgyZ%MF8Q|-IO{3+j0;?Q9H0H-2vr!>1*J~0pDknp%zMtD4R z-7u~ZqV<4X_zZfCUAcLP+z*{vnB**0QxF4h7gk^ED+x?ZOSzSqh854pebs#y_PGn# znr6*@61H)8+;e67#_U7hzc?CKBY zUhQ-Vvcq&jC1f$9jt$s*IH$J5Oc~i2L?G;cQ#V~SmXZyi1XBKB!HJh-gCxPZ2@ECi z4P5odJ!;BZ(6RBzm&*Sf1bUl~i|m`XKVpBFF@|-U}l=7W}q17M_OnYY2p4m%0I%uB5*rI|8(}E=tSVx=R z9t~g;?M**(wN|q?Q#{QPm#e|g2|knYx;&9HHAMN}@)5iTEP~AO5z1#$twGQ@Tomhu zJa?g5+DvM=5s1J9IO)5BZE@}$+?U;b-F#3Da}tzSex+f@sco}QFR}P*7;MLLsoSu$ zNu>Y><?waZrw?C?1N&E~ZgWhp{co~X?CsXDk7Scz3r;?}cb%la^)jcfmy zmya~aDM*OWv1rz^>!dqj?K3r84NPue0+eXo`?CBfGa=e)|ABR{2DThxT3ZecasK5?XqG4`% z#CVrQvwaY1E=mNY_CJ4n5i^7Y^<-IY3C6NjH`S&BJ^eNROYf%K}0C@bjO%96Wk9u8eC z0>hh6#ZCd{3NVikY!a5x{JAX~*%}eWdJRM2pM$9d%?)UPEfG2-`f|_rwh)0viWg#d zQ>|Ah8!5_p$uF_t^s2l8#B2rY(Nfi*dFBz7JG5@4A{i{Y*Wj&8Y~ki zwuieyAM!KNm^ty>y394BKxW$N@VHTR>%bE4U2?d$YQUN|oQ|;?;$yHxh+jo_U=hK1 z5z!%6HS6}cdq#cqGkFO&O&Rd-TeodruF9qpc7{4e8I_zse^|EBV7-+*vu*WagB^2- zM_S~7+Rux}IKuJ^_GF2yQ9eQgV_^Hf7u(#+gDv-MjF&#%k~3H&7Yy zh-lw&cu@V?cjdN(QcpgGYV5LR@*6ryV3-dq`)_0qpPYrZ*d4azXC`hz!*qu)#mjYl zzfbEXoI3Svv2%@`6YM0;!F3yt8;hKhytt>gKNblMV?`s<#Hw~vEOqpI*Zs8bApJSA{_Z=W`U$ncR-ZiPcA?+D1 z!j(2029VGyT~iXgHJbAKw~cWc`mmpEX^FS1^vuujR%_-!Aj(@NF4Jk2S_QC7Ac#m& zKKR{&Y5YV|V1h+|+a_?rv3Ep^5`)@X*y=lx-Ow1T<$BYGmo_*FgQL7>5oIlDmQ9n$ zR=s&z`vGbqK0l4e;Z=K-z@Cd2;6-_LtIt^aBp9>sL0yzcDPG?>IU&-NPe1%)r$vKn z;0UjN7-?b({3a(uWUa4$p47Q4ZcO^)Dq0uIB(@1Jf9uwZK%Cnm{F3&x04PL+^B}oT zu1EDeG_z*?WwF^b<;J(Ckw6=ZGiwq3ZV#8o=RH(_&-!6s?5Y(kTDUKowd#nnxjC&< zd1@yBn?Pj0xptzuwjs{tx{c5NnO2L@LKo|*UBT6d2p#Zl0(NHB$S9s?Uw-%P^;dpw z^5w0dt+?JlCr6C}b7$F)>UZ|-&)0rhcInO9BPfyBL>%$43unN6;K{-8WXmpHm|96? z$sZzxkOB%Yfgzu2^^A{516<>OAs$V&>Yll{C=e_y*Z9+2Sjbk78+nhnRl*(>Vtl5I zM>|!t$I!zgn>78d}ounCfwEEithCLs*HzsR<%U5TPA6hntzLbmmE)|45p)^fZmu)A#EbhQ;PyJ z9T$aUuF;|}+V}NxSM96aac4dtEq^Fne-(RQXJcQuaBFlfoSV^hv0PjCwY7W5oMuk! zj-TX1*mec>kl;|rhOsnpG~L$qsy+L(ZiQWG8X_iL<>*;0;D_8;MM7WzS}^+%j-Nn|^c+kqKppI!-l?|THY zaS01bu1kt%KT{}&Z3PpS2{}>*qs)Y6%|hfg`jo7hZqZvM^=g^N?bYOM8Xs=aHCr_` zzMm-G(qdi6;`I@h5A9dS1|(G;lJ>>89H^3I%)yw%1yr>vqm*;TEYoU38@V4IkY zk+sUh=ww0$v}JC{#Mp0f7`ZEL?a_+#4TWAWYEHm&k)JE$A-5j9@9r>n_pR6#yPI{9 zmK|$`{Ou8+@O_(WTn0n41{A{9PxOQsR7duP^XPOEd9w=CP>ST@rr59Vt95rL{1K&n zIT+dE2+;5po4K`9Ym(!vTUp~3$L=nXurX6y%~1kPX_6yujN$S2woFrfQ6|U?fALu< ztO%p$*=+2f!+O70>%G|4^&yRrHbPGNH=ovepCe+78SLI}k7-&h#;9LPr%&+d8Q2&- zSPRuwU74(cgjPlb2@m+>EGUEBvx_u&2zp1jMhtOrz3R%|G}U^OdM79LyvE^(FM3fu zUCkm&JW&(_3>3BS5Zj1Y(?*%J&21VJX`teHH@AEyfD&W!2M89ps0Q>39IQX1E|`OC zWH@-p^px4|A!Xl;xAOu&6U;gAX>RIt`nBa-rxC1UjO0aZfc&0B%37VNMCyP|OZz!wF3sY}%looG{_ghhMW(+GZ2$Q)#0QX1k1lHYJEd zmye`A^zyhYXkRH=`9~`+;_d_xn`_c@F>-*rw(L<_CcsrrxN!L6=CpLyXm;w&R+t=i z_vKAGH)&)w<>c=)Zh#nJcA|0?5**oT-86cNO*f>E1?Ez@TSJ~wotv*!tBWzM4iKSclRC!Po8fwCd&K5g9dd&kVW?`I6U)Nb7}$=_ zu{oatcR)LGJ&*W~(8ZMPy6SIx(c8UjIj%%%xOHp*IrDYT7_MdC-- z7}+M7Il-oAVPo`gL!1qWk;ZaptG3{nQ;Fm3YNuS!BS!m}_8!UfNbkshTL2=$qK2|M zPa>jBaMNqj0KbNc5!wrg?Y&>8aWYm&%N+2j!Fm$}jj=Op*V`to>rM@kBRS^NG)vn7 zJ;IcT-u!l~6YK1;u?bW|&#_q(IUL1=!94v<9=yXRyuIxA78G^Tm_;>vTyy{>#w2M? zLxS$Tiw|r4LR)rvqpR=-OY>@aj*L|_q2W0;UumdMD0NQ`^OKr4)X*?aZq@)>Zj+|{ z?O3#FbF<0>+2JofFNGCj^v5WXl?~t>=_PUC$^+5{a|aF{W-Q7;5Vpk4x)PtL-eWfppCX} z(}+j5Ao^J+v?`~pPi$FES%+JfbVK&12fKPLnfK6KPic`eN!*nLmF2=<-)-ARyr=_f ztcU4|Be`QtU!gY)+C{}g73#h`?7Gk5h(W!JAy)I|R%y;4uclzuoe9xRRm z6T_I1@zkXh_JN$dn!@IQv7o}x9IV#=9ATNyyDi^SC%i4q*qpcF_y3EM zetO~lP~3hf#7$FwNY4|s#i4j|oWdUMa?OEDc~`b~rx6BATXOnS>5P#=obQSK3Pbvd z2I&oTez(H#-_`rgiJwklircgM!xYMB#;#Cy5AAQ;6Y2J*NVh*sq6=nhx`(>$HnJge z8snk0E!D%*DddeMoBzq>`Vm9chHOeHP8qni6cfj;EwAwKwWWk&Iww7MV%p2{BtL94 z<3DK>|Jwa_BIALz8iS{2l6Y%NHuY0S=B>W=6AjLr3?z)t+bkGC?`PKr_fR$x;T*2} z%1Ce|v+YgsCijO)WcQU(@nF(@)OvJE-n%T_doeX*sVme?HbKVmq-sa~k!qBFfZ;P>fE1f6Bo&e1&K(m`E_lNJw}aOTAW#!h_d z>^c16(D(FoLI$;US`PaL=aXdY5sg`$i+-I5gZxQv$bk4MOCb!?*ee22>-!LN&lkpS z8V(*J*lE--Ju|ibsLwtyM%jG4qh<#G>G!2U&b0UBk3Y?<79BaX$8GtzJW@~n5SBVl z&5_>Otyl#!(yJj`MyF*%M=eNt0CvU-;}-btx(NWx5xTdDncDU?y+_2p2WQvWmI1Ae zSyiy*5~^}7p{D#z+*jS5tU1JQ$1*Mz-!q$-s{ZciB#)~!%e7gqXiP~D2flF;MyqCz z9yg}0JgM0uJB<{gJs3z=t3g^di^OG(qm)m=8a>$|-elH_h>LZtT5T%K&&yr$X;;P9 zzK+b!w3&+Bi$B8*Yt@xi*0}+7&_hkSrd*f%t^yT%P*ADDFkx*iGj zR;pshA%*)mZ;G~Dhudm?f({Q!WN|wv+J|a`!A_F$BhI-)oG(_ijls4QV$6=aLARvj z=s-mo^F`pGfT*N3QGW~SLQ``UK|8_;)w0}S55`pBgZbQ45F<{7r6N8u8i|Q>A6iID zd`zobJ^Kv;2nG>sF{j=jWz~;gq}(En=%D6MwJ=jt`Y;sdaiV57(ztMXB6C&43&9*Y zbT7=Tl{s|8hYaKriVZG0c|`t3K1}f?r;_tz>?65RaX8029}_oLDggzTr$=tHRH_OX zxpTD1lCkM>xT!>b(^b4WfYkS2`C#SF>E~?VhbXbA6VaU6Re>mcuAQ1EsmUef>(lM+ zBXT3HArJDu6PW|Nhp%0BuAjbbHbWI%V&rMr#cO$DmlGdn10QO=BRLVxj$PZXOI7bV zH;!^-7t5XL46o|?L&eL?WRbqIRZG|47oWZ9^lEf2*@V|%LbfmZ%rZYA>n-OtOi^DI z+OWqji|w|)S6%kCz%ip}OJU;g%;7}IM@~+Q_$oph9;vT+E!`Yls$Tv&*K`weEPsBi z>SKFyZbuIETS8AgHkZcTi8*YGTlu~=HH2Q2O^$eg!D${RE^uyaeDbmR1cx~~%r((z zP9bFN$6|XdNf#=XA8SK8O}r(n5YQGaNq5OX3dg~3ifyr9fw&Pi4Jvu(O{8kwZ;cZ5 z_&FQjb9t~+VbOPZCe&?$K4C=%h$jOwma#GQ*wk30-MBr+1VAF zZTa!kd?;%*lmWj^VXhhQDL2J_UGGk%tgO|<2mCsPvSyG#8IJB4TLHbSjRPcZ4o>0@ zM|bX>f?oRdaSvavn|P>tZw7i7dK$+Zo|FkN&lMqlHb4}!}JuTywy22;wi zb-22N?!1zRYV~NefCyaU^~SE@fnxnpV#x~9II=&4%%Pj4Ns((z-Ac_hW`Lv$=q{@L z;n=mX)V^x!Jp_G_Uz?^XZsH?kmgd0Knah=mFexvGZn({h@|P=rIq{cUYN3p&?iL>7 zh)7eF=IEIpg6^dm=p3NUQHV0;v}b(xaFGA4iaq-Y3>}~R*27BY&2DgnFFRe zW{_OVW`gW+WU~X%)7sr zy`F^XWyrd1aSjKzFP^}54_zfrO_H*D_BrowWYz4LHqP>jL(vpFcYnN-wdt9zTgi^| zR9c9WYn(VO6NW*JtUN0-6ScuW@yT^fA$zKyhn>aZ(9=^1pXKK1vHFy|r|Es3+o$;@ z+&>&EZN$FV{h=?iqLCvF2I+r>x;>>**M$G*uc@ZY{E%wWP&oeyNFxmf>Eq2msV6-cJvz&}*?oSjeQx-1vXxLdkFuep)WbUJQ<~v=*wSi_`>xuR;+82{ z?jt$&gM)6Pb7>jS*7>bp!rY-7q6Wdrm+Ng*@7bXmqOH%QwH=O-zeB_wS|Qp?4jlZX zp<^RkDD_spZa5TCatTw*Gql86KlmXAUKzBZk7Ls3iUpJMs%YY~cuvY_sp4C~F5E{^ zRxFs5H?aP0-BkPA`Xu7SUa3IX!^dTU@|tGlP;__dg~hK-SMCm5*ilIj+?Dz?Ti{E* z!)MeS*L+Xhh4KN8)Vqq`A7eN?@K^hor>4Abs!>LUxzZ_k+0u!_88eu%tm$-t%vo;J z-2HZ}Rxel0!=bB%T1Kb)6?i2c4GvPB4ear488{4N0L*ihI@fY}x<8@eJ&a)?Oao!w z+pbzs&5dd;aOE;56usi^wne2E6v|;iMe{iDLTlh^o%e)2xs}fATRkjh25?q)MSRK$ zb4abVK3VTA#v2oxhP)oLQP3W)x>tH=%GRb8Vk1lGCga)!=oh!Y4z>wDQv-TFTs}XJD%2gbu;dLu7}Ed5*{qo*-~w zTTE8D5?0wWIwy-gK4&WPU=F0$zO7_sF=6e#yEogEoAQ(Vy{YzP`*Ks) zn=RCaQF-ry9e(w!X^n6W36hQv;sMjFx#O&*zXziR#Rc&yfOb|(25 zB*(`r{||FfqkXdSLIf8FJq97WjupNu>-v6gg;Y)T*T%_>{G7S{;7%Opm2f_E&1Ssk zy^ox-CwrwTo`kE z$(fn1c@TGXJ6P3LLnSNUi9%mvlrHJ!43eHrY*B=Cd@`I9=`4q2bO9T<$d!v+aZ1V! zp5$%=9A{@>Z1XcX%lKjj@`Wb@TZP;ivr8dUpV828u&VOJr(>5E$d_>~D`8DWG} z@#Ugr##f1PcZXbo$y&YFZ(VOxJhXLakD{y^nrhV=G5p{Q3JLU9KIuHy&VwX2O>wxB z9orBSa?^Utvyr6=oi;edq-v~KeD5A$0okW#xMRk15MKMn-c#lI+D~bWteAwfRc^3? z#Og!27hSn6!RjI5F%Av!3pY5!Bnw7)k00dvY=xdvfH?X@h?C;WbEh5TGV=LL1Nq}jVl+uPsaBSWX6F=ZjEwH#7!F3`S<(M)kxDW zI^Hh7ogbmwKCnJB*7h4=PQ(bqmVthsx+TG3{9DnTMphbHp7M7ZML>11Yl;q>W+tPle6HpAWI#}I@A;^dF9x$81%_G42bhEgK$;p zBEhN=DUVQX=%mv3FV!Z+T0bvL%%~;6bKg7-c{LJlfA^yB?r9X@zw5XOiqDF;(Kb)pX20kW{f z`~K6`16Zl@(Z%Xiy)s5y=7`53hD-QnGoCKhl;Oa9FlKJ9=z+yF`naQaHuEZ0P4i*5 zY#IXTd6~FS!+WEXX&JHGJuNLxW}4z&+#F>)uSw5EZ>#pUnWHq`(k|BwID}=Jy6I%g zDxI#e)Ex-?okrDSbBHn`wmBN{tgvlUX71Q+4pOTFFG{O@?qxzqcN{g;jaKIp_ValM=2+UMla9|SgNz) zwt%)z@SBQSf#@n%MuBn4Dc?<7pUhR-iW`_beH2?@;9zPhaHZ{ICed5jgu(`;xliuP zp{9fVUbs!^sIdn-ls!{)l$em2yEmrh(ReNg?t_lSsHN|skIYypy$)?fWA2c}B8NKs zf>>wjLvJWVxlT32qD1kJBV0`)MUS$WgP*M-M15dQ`i=-2u46ZeWNjUj8yZpe6aBF$ zS7~9sJEz}Twg?E-S$9`v($6<BJWmSvB zp`UVWv-&opUOs($owdgUG`V{iXcHEj61EJQfvyqKFa-gVw-4{q{9ojqlK+0r%5L>C z+m*#$;$pV7Z3a65+aC1(PCkhKjyOPvDGcaCRy69-sAmN^k}$2gz2*8}2lwGd*7>)U z&h5>y+OE%Ts(sOz1sVi&c+~)w5)MnrrtC6vap0~7^^dmQdi#ZElpC8xKp_#mHroo#D*OJylu)w_dyNt9nx%9RY{x;!-Hsu!0eDBgs9 zp@wHLNS2XgZFX1e*IW4{9Ju9rQ-%Q{MdSr7+DsNObcOvy7tk$8$jMV(Q*Xn7G^H09 z5@kA(3vOF}EVrV&ufte286$@oCeFa8HNFtkkI&GGfmKV-KO!btG9E(G`>)yG&4O)4*zN#KN#RkEWlawhTv zsOv8OzsuT56!-GoLAAdq)HJ;p6_RF{G}B2HfpvLv+-zXC+np@qT`ktf-2s=73iHja zZ`Egg%q79JrzPtgNS5JZQ=JUHL0GoNl%RsITIE74UoBxLSA}WwduOOA#)IorsNu1fl9+JBUrK&0Rbti5?04+*uYBC_Qd%)LAHfCyB zL&D`T^rzc{_&89{bixgiIr3T`VCxU$R(@LlGyh&igDKbfTWA`~TOe!7VhwNB*BVOJ zpRl4Thh|WrSdq3jez*#~i=HrYBvEXBz}AzjPKwOh%G^WQ$aDmro%|eR#Trv}wSpW< z!GX`!v1!Wv>Otu3tOPumc-X$#|L=eQPnW%Wmw`=YZDcRwP!R5H+M)tEmII3T!+2ju07uaXe=`)=^GwGe2wm0HX?5maN?qoT>tGDY2o2h~3 zAzzH{h*j=Zb|$RMJv3eS^6G6-$@WhziwG`e{ulE$!DLWtz7{~gkez&4pk$_YS+Oaq zJ-B#ql5fflbO*sT?6r?DAEh3B#CFhTRh*D2Is`iM9fPfx%36L_l&+s%IJho14JQL3VB(%7oVGQ{zdB*$S6udoR(HFi`M|@PT9Do!G&gQQJ@tS)+*k4@Lp#nI zzyl*}?yLQ}zHi}waa$c^^{0BGV-CJ_3i;cp zDZ9pw5)rpBd9PM?rpOv!a4+DiV2ud7DIs#3Lo7%#&vOo)Qhr3L?jeL`(l|y< zjCE6jJ}`DKWwBOy1I-ZeaiN!^4u`v(BTQe&v$+}w%e7JBn3KRBzvu@827u$?0K~(S zxw1$Y9uR}R2OVP{^t=Z6fxZ_~8C<8ko2|Yd zXYb94>>5uXtMQmk0C_xWgW%LYaTG@c+fV}{DXl0)OQpzlirl(RY+?yJwiLKAc614$ z(IRb_$pi%-G^q!GzD6}7n$7aY`m~0&n2c@(vOF2zQbZsgg7Lr^@DlEZ$^batR6%Tc zDThmW)v9rQPXk8iCJ450g&AZDWYr8aSribE0Aq-(%^!ObIPx}+p$!M~5K|kbkL!s+ z19AX5XhI^2O{?3J)>vQ{GsQ)&EWR~eQ*`GVRSet**SI2r*pCC(5rbMK$}L z*k8g#a8-L7!I7rfz6&r{(Ih<>5#A4rC~@9msreM@S+$dDoZYmT%xvcvM z@cDSB`}XhQJUSA3L3Vt3H~!Vxz_lmZ5FP7-7yWb74Q=wsRI!uik2v)RFY^l+wkpPJ z6mTHoOkYxnf07Rz4{Zn1n2N#NUC4(%$gRQKro^^!r>T7l?W!iLcEzS_t$+OBRX1$+ zCjA@zKeoPmcat92$R{SZjUnT7VD)3QKYlvTcwCTm(?+0bQw2GY z!1~QE@2&#?ZF3s|kSV+XVEyL$-S>Wgb$x_Zuw(bY7)Yg53t)YKG>t6DaG=@}*_XJa zVE6vV>#W@4oVbARk7)5u57c^ODTF9}GZUxn}C zw|WyCs7iW4Oc%$FQB}fRFI9D-k|fz*l%xacv-!cbd?E|qNI-@u7>}BTIrI~O2+=o9 zHR&u*So8_K6d+c#_O*HU_D!aKyHJ0xVmDo;@Zx~=U*sQ$=YJIC1_jT%>LbdsuO`D1 zV<@MI4_hV9RpQ+E|2I6Fsk3I*-_I_E7LU(}h@XKYO#B#Fg?;znDf@rNzp`oY!C9zq z(5ES3#0r|=PA!Qss@uj#10zJ))K)>_N0<<_r|taEmp{I{{>sS{wiT?g?WYD;L(Q_Y zX>vb3*j1oXsxjE?&)N$NB2C+zK5|v}AcjL|vAgIr3i6c6M-jK=eN^YOU(0H92lE$r zXa8&xxab}u#R}ABd^Du(kMf_lmXZm?M8_ryH8 zFo_Rc+5hx#{U1O2Z$V&B{}^nz^V$PC^!JNAdT)>X)yJW3yG>I@np!5658HZqt9}GC z4&s(LV#Fxo_7hCk!4hhSKE%3nir_}(>3HLurhH1O3dT%qBI()e z>+El8ybnKk|NGzn(~kN~dY>5|sD88zsoWV0G@ZC<# zfUE$4@3UvY;HXS2zLq6kHA!H48Opu%l9@!q0k%j+?sl;X@-g($JCdG%o&81WUzgB) z`s+{Myn)I2rtG*(@$2w>RF!*mgTKz+G$k_4)SlT)g^8=>hZQE5w5v=-tkk{smYEI4gQh!<=67VzRBQNEnfr5T%w1j zULSSn43+g~k?+C_8b0(_L(blQisbb{2Gs-fWROEceKm8qriQdH+5=1RLSYX>PdD#{Z&;mAVvA%r&qq8p$T7mNH!67@~fX;2w z0Es?P1|#S{*rOlyY^R>b^st$%P;l|EYZTlJzz1Mo> zFlSFeB3GV9U$_tD!=b1ewa`;Gqwh?eyRLUIvs>=hhq`jlOi_ySC!;vG4F={LGT9+N z2A2tZz+n3~Su1DX7G1Gvie2{BD3pBpw{PFN9t`ZLWp(Os>+QJjg$9E$V)A|P-`xaH^+T<9C|iWRX8|@YxD8^5K!;-K%pHlI&&L2 zsF*1#9L&R%cpiovpi`#4Y;Zb*(<5rC^WUk}d0c94BNsI_8qleweLXcbE})h&c&Xv0 zmV!#aa`^=;T_jaA9- zvK&aaIaxc_U(fpM5lYP0dtyLfL$7Xc*RLC3!St&K6o_8l0ZmxgRcV(CgnT$QY_kjq ztUluhIv!w8TgkhDCNTjpr<@4n>lE`p{*c8#W9pm=Wmj+O%>xgD7$~6k{fQ9Ho$QO? zO4yz3v~QVMLnDo6PG)%WMcRtj7;t2*Y~QC0mHMfZ6L3d>Y$VxiTAhFfW*o%&1_ro_ zn+hhL&(w%I3>|J5Z(7J!e+1O*n?A=NFgQxX9~>$8U+5_YItcb)7|1pay0_C!B74jL zbx2;Yjr1#Iduk4|zHz>8g=G&RxNKJ`%)Xs|@k%sM5Yy6hDNkQy4gg^8fx_nJ7q ztGmJc504J9GCM#!GjkI|l=mwI3X+@pzUnI3>-oL#*7(G(|Cs>R|I}|w@8tg(-sC8} zJck`+)lO+rNQ_K16@6AWe3}>mt(KO6YlPbn?wvMgWEwV#*}ceTH^qK^U#+`4we5!U za>X1dMt?TvGjpCp2R4&}qAG5Ts&1Nz#MY@k^lsq(lKiUZOB`OCZGEGciJW?)TrPB{ z53gi6O__4Ds)OE3TAB6lMz7k8tTnqW*@^hbRR>Mg3^dulQmdO3IoFt1$nmIuVs}fzb9YAUbqAr{WNefH(Q{rdhZ{6v|t#AL$+aboM)`hip{=;fhaZ=zl*-CpsFhwQDPQPy~i3fePFxCTCD5)y$)a_ z`@h5JSPA60e*HP(df;WVzL8Eqt>Czy`b*AS&|4r&LAxi&$m1Rv-}R|kOn-34{fE6Y zPtA)q^YhdRq8uh7YAgfY_ti%ckao-WZ{*Y8|9Bntit{`jZ)V78L}rG%z_gGJWmIfr zM{NTmAbLAl4ApO`=Pk@>vlA_h9}+c9GSuU2BW0}!xZa`19DDWIX=r`_LgTKzhhY$y zq>N8Y%@CuV^Va9X6xRC|m6Enc&K!fsOpo6Dd@Y+F=3E$73B3v<^vcE^+3D$op=xXx zd93V)8m#d(OEG(W3ye3qgp}{B{3OfNBghzgQ*&x>r;#CtjQLug7hSblc~PQ-?3-U@ zI8G@*K`&<5ceH0X#hu>Ae2*KDPiHl7Lq?2u1=uu=inc0(Ryqc=!Gy!Awnu+m-(uZlaRSWW!taxxAiugk7n#Xk(1GD!BW;41$e zo>-dxoEF_Tr^EQ%e)z;dq5aL6>rO(;L*uT>^BRuOhdN4z#JW6e>jzj2E4qgRXpD_c zOyQ#@ru}QqW_Z>MI2MwNfRW_Gr`gp;E$3gd+g>;8UvRhYnrO=Tn2bfd!bHYOUJf4F zK|XQH#fNOlDM|x-UT=nxmj@aDHj3D;o<)DnK0lEf_7yhZiV0%ayk+n&Au@>V1KU^Zr_{98TUy_iY_vx^L_ka2kYP3!@?IxYZL=9kOX% zuIlw+1Za3G@UzpI4c=;2;6}HsxilCHW{4uyK>PTD-)qu2%g%(|Zm3 zBrp2i2SV+Jhlbo(Om!$nZL9j-W@oGvq25`pPUp%QSu?vUy8`DF20nv${dO3)t;W_D z^O579^!cGvNdz9pL>Kw+kedr;jm-KeD{7^XJX(tB+9fwyc=^Sj=+aB5`HS_g+RK}v z7a|fvI*K$ zp9F@}5oGl35y;AAjxKZUGDrgUk3B`6!4D(P7Ve3vU<=EufQP$rPlxUOgyG1p*hheA zFF`2UY(E{!c;P_7!kg;$;b{qdr*5>*PF*;<<`-!`Ugdf zRZnzWfdUx2=}<%DZE=h-P`~35MAx8$aCIo18di=jB?Z&hbK_^++a$w-k#R#^Y6ioH zraF#~uzGH{se(<_(XLzZp_GmAx^qM=9ffWrYe^d;NGBcS(m`$={CoM3nFDmME7$T< zu{(@|qcH(=t!i9+r~mKn>#wuxZ?BCCYg@uZ;z%kE&zX57y#5wqJ7`uaz^)1++p4)8 zRoaNqgYOoHL>|7-l!^b`_|J)dts(iQPX=<J ze!Pgr5%GKS1s#dS6H#82+0?r_ujja1RC2v5hi-l#ZfK~!fgdW1I1SUM()@O;WFJZW zo=h5elF+Du%BCODnwktFdXacteKfK#Cc}Yal=o{}s&1Qlmkr{Tv03&(!%m?%ie47v z*Qbq=!*_R;LP%LSN#MtD<-nW-jPxW?UjGA5t0WFzdkBjL09kYo8KewYGrEfx6xyRWmZ z>bB+o_re`EG6X;ZOPctR&X|xPAAQisVN^(0#K)i?6A^L{`q35EG3dpFeH_Gh48?2= zd7H#`WCd?LAr@nh1T&N)GfZP@)Wvja<2sKKjnNxqF@!#h#F!9mF%mv8gz1X3=!>Hm z2#y$p7_YpdQ4#~84?{l35fWW75W`Uo8Qy99Lr-|aK+M7*z$mogAb6oGFk#S{30$Zm z6pk-@K6;}By5=`0U$8UDXdpjUOumRyPY^#(a6V_;y%0hl zm~R+>FBnrV^6Amnsbx7xFg_ zJ?D=tH;HnB!Mg&!F@b4yLt5w+n|MvKd0fd)B@RkQ0=on;95G-x@#y>^TY_lAz%H)X zEskg#A&;G1Clij)5)8T$0T@MRi9}!VgbRsbn1~|r_$rQ&AR*@_Vn{ePh$rAj$T5ux zGKu=(4dxN_IEeT`qk2qIR{O^(^|K%z%F~w>t5!rdUIRoKw6&7|%3V6Q~mpB500f9FKrhi#c$;V!xmM>x@8o z={RPhP9mcGOeo-K2RQGZ4@UtV*xHR99?bCTt^zCW>XG8_lm(HZhGv=Q2X+FaxC7e` zy6$20!~t+;4$S{%wOsoda$)}OF()hnj7P&&$y7%}Rso&_DP=6*&AROi&ANSd=57CE zBwBx?^M30li|h8Ya@~%&+7(zlvX3moCs97J47XwrQ7xaJC&Iv=0GrdPB)#?=Dr)NL zv(`omGN+J34i36@*XU!uLFnY~u!E*=ZmwJ_q^9^Vb0+#3wg8LK9Gim%nXMa7zzr~s zyvL*SZ5>&45cS=~@%HGaSGp(LQy;^lpHUpm5)SYNvxEV-!91Y@&H8aoAvzJUbs?Wf z`&7`mb!fNJs}1E=bP#&qCQ*pl&J$7gBv$B5cF(iju{#!O2;fVYY5NK`ewX{(8g>?} zwiRsmC94#1@`vbeM)(Tt;VEh7+NYPj7Bo}M%V`#ZH`x=i_9U|4>n(@`t%2yat6&)w zP*uTU+pOsH_}Z?7cm>6J4Iv#A@9Up`i12*sdkYa{wM#Ai1~;?ldk8FIdOzA^b=xC| z5tn~gKTAsMfB*Y`+QIY9?qvzJA=}J&$dGQozJvXtBP%>yY?a5r(hmm#&(^S~VA5|F z3p7483(w;HC!x{%4mB(_k;k<}SbR=td1x_*M}Pc`pEtNu^FiY27Kpx}q3yt2II!3$ZFn;M_Pf1T-Ig+C#+KI84A-KHh9WajS2f!|Zcg zp^27W{FzrPB=K+GLXQQKhI+F3C|JPWK$moGh^xCyDQXA@to1r4gNOmC_2Yo2K zK#@iZ#d^O!afYW@?*1pL7Xz=33iVX)xr^z&`ZUxTT}}^?2kEt}zxJX#%4Q!d!OpwY z0lU5Z;;oTPPb`eA%-MV6EqnjwEpBB|ivW6*5c)qW_At7k#i7bS zE_C@gnglS>U|YsGfq4?G3H|TkX!HX%uaW62Dspw zHPwZNeutgLU=_7b>(<6T0%&lSTW5Li3>U)L3q=1|da7<8L|bk481XCwI|=a&%X(;# zi7%A5y!WO~T$|%Bzk1=49J6?%i%$8mFZ9XWcrbEg`uN4f$?WAT{6lAfy@}MEF#Pg6 z;=0n#l*O0V<)JHgH}b8R&Hp8vznWcq<${>up9p|TWVz1#j-p&mGQ$-tzWiAhF{HY9 z6{7k|{f!~$WIf$iA#X3){(8=MgTFa6)iygv!mr(gRc51iS10QU&qChfgl$4g81M4G z9k&nJY{nq`l1+I=AZGHO95=GtoXr*-kU1A*PC(|_m%psK8Vbu*2xPAQ#vpXE{?^Y! z-r~33CbX2n$WmU+7@XsG-zFp|Gx8d7?#xT}y_pjfdHcUDSGrz##erFHVHN~tk$w3S zZiK{wG=CL_SaKnj1Y!vTe81C+lCiMaWzHm9q&g>jlX)xg;o;rd;WF&4Y2S{>Bh=vi@B=4|$8fYc`=}e9n8<6_JR#w-nE&c&D$jCmTyf{U>r7_0(h43}JlB|%6mK=F2}O-Mj4=qrtb z`t!LP=C%481JTL)a{4;tEnZG-LQDE}Kk&Q=-4|my<3h{`#Eg0KjqQR9vLGOf)SDkK zNH!sXv28nDXGbv3xftgJ!wgKm4j|xIVC;ofiEQzi`yEBgj!9-{;;^|IUBf2O?9xs1 zmHHd=u9NjtUxmEAWc%y+zM@UgP*rAaXIzjO0hvu^%VIEdF3g<3%( zB^O~y5X`$aTXG=Ixe(_B;@lL|a}LA>7vh3IT+nX*RR~6XwW1GPC+pvadB|J*ZLkR~ z>G`BT5M~^I7i>a;G2;{#M;<$XmP`v^F z)`G@z#)X&>2-4kLut3-unbDlgUg_$CbL$!PV4sqL%!b|b7|NWDGN&l>;YT}y!Umxj zWMSCslJi*D#dSeY=mgK4gRXLSsDRqRa@&jJ$OV779BvGn$*(D^@*aZTXoQ0+A9vwoQpFjFmrAtVvHAD zlm$T<42xcIVA$n$Nnn<|O2n9+b1}{d#`$1PcMRf!3vodpE_juQF}>tsToR1Sq)H@S z8QO#dXs!=wy9OcpmZ=uL*5JOZa>+8g<3s9Ipv&LV_~m zzSP#3otYWU&FmGojRD#{pMaS^Tv!c|HY6|V(t zLIUxc)gHM}e`5eTSzilYguKOTL7UJLz96HJmkh@7I?yI07_-q7X#``=#h4QeGjzv7 zSa1;*1i_5l&6XU9B^P2zAneH91qb4s3vo^$&bglnW6Un47X;;E(zlPGTyjw^2?`y# zQ+=7G8|6~{jbZ3y{rh$q@)m#JY(mTV(hhfpAF;8VaUo^|f_|W99F#d1Wlm7$+-86= zUT{$s1ZBZ}#8>KX3_~aDAMsVlTl^8*gqHKwpo<-BSQ_IQ7iC6JX52gTTK$b-=w$t! zc^&c=zcV(W<@|ck9Z>*PplAQUoDl$^1Sprit;qc3{U;Ff;RaqN;CSd znq|MC>JBr=~lg#iOROrh9QLKw%^{h%{iN5wbiV`)+4AIwv z7a&-&wCRk8FzXTero4A6xkJFihv1O!h{<(`USGp63o=sJ7)yfWv;0;5DnWNc^gVCMnYIa};P5M7EO>`QW%10V6Z?h0-zT>aSTCJF<6+F z0nE!3xeP&+ErQ5HZ3`FjLcO#+$qPZWE?Bs;0Pc(_f+2|d1q*Z@0G;z>F$B@WU|}u- zm5JWSBg?S#pJWmnR5JW|Tg?te}zDSYR5JXplg?t%6zT^vT z2%@yX!o3RMUhyS31X0Tf;tusPT)0bBTf}8F1kul6Va@`WGp4YHAc`6+(0KrK&Xd{@ zL}P=6xd>n`nBp6PsBW-8mjTcvPmV(n9S#=ec>wb~MW90vWeyhdMF9DNFWDi8RtF3B zGJt!@74Z;6ZzG60RNQc(o~yU@ob|SzbKcgW28SDSmLkm|h$aUMc^*KXr-*e3qSC=a zUIdUAeEAMRbURqM%K+|@E9fDJk_QX*Jb-%6llTxsjU$LT)Z=hrUZ|4HlIajcpMwQE z3&742ggXRL>|g=U1Hf~>l!qW19xU8N0C&L^_Yg$YgN3>bpe}iGAA;z7urSX9nCEN( z5J9v$g0Mri4j1gDD#$z$4?)yBSh%wQ?kqvpLl8X=7Vta(Jm(612%_-8LR|z<7d+_? zK{P*Dn9Bg>k}U=S6ASG0B8YBB5O%2C;ex$VC5j{HA&8O(3w0JiopB{T1kv_jq0R%S zbFS!zAZi~h)I|Vw!IJ?IME`?@xeQ<~*}@=#D1xwHF9NU^2~r_~sCoo}hq@jv;Mb}Y zGvq!5(fMG3&H|t_wg8AA${#G)MF4h@APFLf76=P?830~#MM4Bo2VtR}2T;#>vLT|^ z`3OM{ls+7QY8rx}{~?G}M-Xr**5QIwQ(8O?4?)B`Sg^AI>?}dmLl8L+7Vta(JWtU1 z5JcdE1-u9VFSyDdf=GU_P?rJJB~J@P5D^d-=6L|~oT&~Xh%5*T^dbOy!PE;8L@0y> zdKmz{WGaXVBK8pk9m;*UK-GLOQw2m2`41NEEPy*p&;=1h5QGIh4*<`(N+E(sgs@N- z0n`OgJ46uC5EkY#fVpIHp7Xi=(Q;|dvDH0awG61?{YLo~fPQn5`4}hMtRZ9esD`CN21Yj=`bW8*hFkt~- z27oX5$|izHny_%M0=QQRS|@^toUnjj2Y_F5)lUQwDhZ+vrAl0=Y9R+l!9)=05*Fwz z06Jr9ng}Xp!ov;acJ_)Xb0Vm|2@f}z;Tc!?L{P~S9`HN_Jm-p`2%>_*!VSUB`SK`& z=%TQ27XjP_Uob@wr4$zKGJw0}OQ;BUjY5oGI`kh_VX{^dbOy!IgXwMC*lxdKo~y z%JJ+~f+CEdQZPL1V6JBgA~Axf!?2*|0pvMTHbxM=7#8Rv0J`7`$q1q#!$MsKP?t<; z89_8IGMRMiAW@7V2dH^^zw@BZv|W3-c;~dBv2d5k#AY z1^PMw`kEO6ot=ZVt@qDsTU zTm&!|Ot~6CbZS_j%K+$-DPSXrat#agJOFymm$VT?%Z7z}5x~9Ri`)pJZo|U84B%e! zWp4ygs0rc@HELYAY6T@nx<(Mq8W!j*06OD~*$ARy!@`{haOZq^8$on!Sh$M-?t(9P zBZxLl5O=6ffP~lUqmm| z5`r8^wKyPE^MxbeB6`J@5a2*_#Q~^lUxpHk=rvdvcr!oVjH%5cdNGv{;6P5r0jO#q zmdJ|eRZ&8S16>pcqN)R!YAK@ENnv2l{FpPIev0@w!a)_*~C0 z?4Z+if>w#wJ2IcSsV69ec>OE-ZvF`x6<+^y^gnm{PuSh=^}j&>3#b2t?Wtb>OZ2~V z`cK%*=JkK>4t*~Au+*$=Mqd9H?r_zjk0s37bmH}Y>5efh`Y5xk4FO*NSMKPuqK^*B zS{&~6|JofjRD5JDdhDY|x}49QAtgm0?vb@@)$9G0I}D`g!`rbI_j&!#++puTA25uy zOvUSe?hYg-`ba#iiBGTpg*!rz=tEDiM)1AOgz_ zU$`ZdMK2A@>ce^cU%Dk@MXyWB>N9!$U%B;4MX&D3>Z5r5zjmvBijSO?$wVg8<$U4P z&J-Ux3y_JnrOWlwDR?P9a+VnrHAno?WqOh`uj25!b^=qYucwcm@yLG)0#i0^Sd0RH+epL>g%S-vk#o4Of z-&UJ5V_|J}>fX_>`t*zYa=R5D_VxWb50f0V1PeQ|&HAgWfB`;Ya5Xv_V&+Hb^N*B$2a0kvh<4yoN1zn!hi zo8x99)15_)HMhHmylQhymg{6WCd)O(uXhhw)n?y8icF`-{`&s?+p~G5lKiWiX|bp+|iHKUkKNkDVvDlQ^mv6rN@vBUmwn6lDy(_9cj{r@pI9!*F zto1U;seuck!M99)M-IoXCj9-2v~Kz?}3%9H-dLl`7hFn`-u zw^g~$e$rUqy!|1F*T|<_^3X_9h4l_juyWOuoAMJArBA9+-~q-24!~u6a?{o_PuVurvIj*{}?`g`uQVMt@prTlPkk!@d!?cGX z@!B&;f~n3L1L=E3?Q+&1dVdj6+*OC*>tkZ%Zj;_ z&7BTg@1cf$Tdt1K=71kf+4T&Fque_B?o+WlY$g6(QT%^}Q_OWwzQ@l1$bf$`yP^+U)+W=k~szSu2+XoEDqrurjaH;Spundd$INKJ<8PE~@HjaB)bi>rSf# ub1;1W{>P!WMR6RYd?YZq7(#zPg#N#lxz7Oj3DfNW literal 0 HcmV?d00001 diff --git a/docs/versions.yaml b/docs/versions.yaml index 06b7cca3c7d4..43dfa10ac3cb 100644 --- a/docs/versions.yaml +++ b/docs/versions.yaml @@ -19,6 +19,7 @@ "1.23": 1.23.12 "1.24": 1.24.12 "1.25": 1.25.11 -"1.26": 1.26.6 -"1.27": 1.27.2 -"1.28": 1.28.0 +"1.26": 1.26.7 +"1.27": 1.27.3 +"1.28": 1.28.1 +"1.29": 1.29.1 From 4d3275edbd3e7a015b974e0c450e7a4fa715d6a7 Mon Sep 17 00:00:00 2001 From: phlax Date: Thu, 15 Feb 2024 08:06:30 +0000 Subject: [PATCH 013/151] mobile/ci: Ensure core CI is run for only top-level files (#32339) Signed-off-by: Ryan Northey --- .github/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/config.yml b/.github/config.yml index ec426e498fcc..b3bdcd3985c7 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -254,6 +254,7 @@ run: mobile-core: paths: - "**/*" + - "*" mobile-format: paths: - .bazelrc From 3c234770d2b1660fa6f3fdb1f9c3916933ec7b84 Mon Sep 17 00:00:00 2001 From: botengyao Date: Thu, 15 Feb 2024 08:13:17 -0500 Subject: [PATCH 014/151] route: fix a timing issue and remove assertion. (#32336) Remove debug assertion and add more checks for assessing upstream_requests related to timing. Signed-off-by: Boteng Yao --- changelogs/current.yaml | 4 ++ source/common/router/router.cc | 22 ++++++-- source/common/runtime/runtime_features.cc | 1 + test/common/router/router_test.cc | 63 +++++++++++++++++++++++ 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 5e52e2d1bd3d..ea4167e89cc5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -88,6 +88,10 @@ bug_fixes: - area: jwt_authn change: | Fixed JWT extractor, which concatenated headers with a comma, resultig in invalid tokens. +- area: router + change: | + Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is + controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/common/router/router.cc b/source/common/router/router.cc index c4172d2d0906..a41e1c3374dc 100644 --- a/source/common/router/router.cc +++ b/source/common/router/router.cc @@ -873,10 +873,6 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea } } - // If we aren't buffering and there is no active request, an abort should have occurred - // already. - ASSERT(buffering || !upstream_requests_.empty()); - for (auto* shadow_stream : shadow_streams_) { if (end_stream) { shadow_stream->removeDestructorCallback(); @@ -902,7 +898,23 @@ Http::FilterDataStatus Filter::decodeData(Buffer::Instance& data, bool end_strea // this stack for whether `data` is the same buffer as already buffered data. callbacks_->addDecodedData(data, true); } else { - upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + if (!Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request")) { + upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + } else { + if (!upstream_requests_.empty()) { + upstream_requests_.front()->acceptDataFromRouter(data, end_stream); + } else { + // not buffering any data for retry, shadow, and internal redirect, and there will be + // no more upstream request, abort the request and clean up. + cleanup(); + callbacks_->sendLocalReply( + Http::Code::ServiceUnavailable, + "upstream is closed prematurely during decoding data from downstream", modify_headers_, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().EarlyUpstreamReset); + return Http::FilterDataStatus::StopIterationNoBuffer; + } + } } if (end_stream) { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index b445a9cf5435..bc28a7975b41 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -82,6 +82,7 @@ RUNTIME_GUARD(envoy_reloadable_features_quic_fix_filter_manager_uaf); RUNTIME_GUARD(envoy_reloadable_features_quic_send_server_preferred_address_to_all_clients); RUNTIME_GUARD(envoy_reloadable_features_sanitize_te); RUNTIME_GUARD(envoy_reloadable_features_send_header_raw_value); +RUNTIME_GUARD(envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request); RUNTIME_GUARD(envoy_reloadable_features_skip_dns_lookup_for_proxied_requests); RUNTIME_GUARD(envoy_reloadable_features_ssl_transport_failure_reason_format); RUNTIME_GUARD(envoy_reloadable_features_stateful_session_encode_ttl_in_cookie); diff --git a/test/common/router/router_test.cc b/test/common/router/router_test.cc index 1fce4783c62a..841e0380d39e 100644 --- a/test/common/router/router_test.cc +++ b/test/common/router/router_test.cc @@ -2737,6 +2737,69 @@ TEST_F(RouterTest, RetryRequestDuringBodyDataBetweenAttemptsNotEndStream) { EXPECT_TRUE(verifyHostUpstreamStats(1, 1)); } +// Test when the upstream request gets reset while the client is sending the body +// with more data arriving but not buffering any data. +TEST_F(RouterTest, UpstreamResetDuringBodyDataTransferNotBufferingNotEndStream) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request", "true"}}); + + Buffer::OwnedImpl decoding_buffer; + EXPECT_CALL(callbacks_, decodingBuffer()).WillRepeatedly(Return(&decoding_buffer)); + EXPECT_CALL(callbacks_, addDecodedData(_, true)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) { decoding_buffer.move(data); })); + + NiceMock encoder1; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder1, &response_decoder, Http::Protocol::Http10); + + Http::TestRequestHeaderMapImpl headers{{"x-envoy-internal", "true"}, {"myheader", "present"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, false); + const std::string body1("body1"); + Buffer::OwnedImpl buf1(body1); + + // Send data while the upstream request is reset, should not have any failure. + encoder1.stream_.resetStream(Http::StreamResetReason::RemoteReset); + router_->decodeData(buf1, false); + + EXPECT_EQ(callbacks_.details(), "upstream_reset_before_response_started"); + EXPECT_TRUE(verifyHostUpstreamStats(0, 1)); +} + +// Test the original branch when local_reply_when_no_buffer_and_upstream_request runtime is false. +TEST_F(RouterTest, NormalPathUpstreamResetDuringBodyDataTransferNotBuffering) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.send_local_reply_when_no_buffer_and_upstream_request", + "false"}}); + + Buffer::OwnedImpl decoding_buffer; + EXPECT_CALL(callbacks_, decodingBuffer()).WillRepeatedly(Return(&decoding_buffer)); + EXPECT_CALL(callbacks_, addDecodedData(_, true)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) { decoding_buffer.move(data); })); + + NiceMock encoder1; + Http::ResponseDecoder* response_decoder = nullptr; + expectNewStreamWithImmediateEncoder(encoder1, &response_decoder, Http::Protocol::Http10); + + Http::TestRequestHeaderMapImpl headers{{"x-envoy-internal", "true"}, {"myheader", "present"}}; + HttpTestUtility::addDefaultHeaders(headers); + router_->decodeHeaders(headers, false); + + const std::string body1("body1"); + Buffer::OwnedImpl buf1(body1); + router_->decodeData(buf1, true); + EXPECT_EQ(1U, + callbacks_.route_->route_entry_.virtual_cluster_.stats().upstream_rq_total_.value()); + + Http::ResponseHeaderMapPtr response_headers( + new Http::TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder->decodeHeaders(std::move(response_headers), true); + + EXPECT_TRUE(verifyHostUpstreamStats(1, 0)); +} + // Test retrying a request, when the first attempt fails while the client // is sending the body, with the rest of the request arriving in between upstream // request attempts. From e6901fc410ca18db6c1b61ed9fe39c528269283b Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 15 Feb 2024 08:27:55 -0500 Subject: [PATCH 015/151] grid: fixing double drain behavior (#32375) I think somewhere in our journey from drain idle callbacks to drain enums we didn't handle this correctly. I don't know if any call sites do Envoy::ConnectionPool::DrainBehavior::DrainExistingConnections then Envoy::ConnectionPool::DrainBehavior::DrainAndDelete but I think we should pass all drain calls to inner pools. Signed-off-by: Alyssa Wilk --- source/common/http/conn_pool_grid.cc | 5 ----- test/common/http/conn_pool_grid_test.cc | 14 ++++++-------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index ba51e7eee7e1..0711e3fa8f7b 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -344,11 +344,6 @@ void ConnectivityGrid::addIdleCallback(IdleCb cb) { } void ConnectivityGrid::drainConnections(Envoy::ConnectionPool::DrainBehavior drain_behavior) { - if (draining_) { - // A drain callback has already been set, and only needs to happen once. - return; - } - if (drain_behavior == Envoy::ConnectionPool::DrainBehavior::DrainAndDelete) { // Note that no new pools can be created from this point on // as createNextPool fast-fails if `draining_` is true. diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index afb1fb2c9a70..a442a55fdd34 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -599,20 +599,18 @@ TEST_F(ConnectivityGridTest, DrainCallbacks) { // The first time a drain is started, both pools should start draining. { EXPECT_CALL(*grid_->first(), - drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainExistingConnections)); EXPECT_CALL(*grid_->second(), - drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); - grid_->drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete); + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainExistingConnections)); + grid_->drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainExistingConnections); } - // The second time, the pools will not see any change. + // The second time a drain is started, both pools should still be notified. { EXPECT_CALL(*grid_->first(), - drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)) - .Times(0); + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); EXPECT_CALL(*grid_->second(), - drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)) - .Times(0); + drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete)); grid_->drainConnections(Envoy::ConnectionPool::DrainBehavior::DrainAndDelete); } { From 95b8bf1b2794897ca2d413ddad350bd4ae5c2295 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 15 Feb 2024 08:28:22 -0500 Subject: [PATCH 016/151] grid: fixing a teardown bug (#32403) Fixing an issue where the grid didn't always inform newStream subscribers of stream creation fail on teardown. Fixing a second issue where on teardown, we might try to fail from UDP to TCP. Risk Level: low Testing: fixes a mobile RTDS flake, new unit test Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- source/common/http/conn_pool_grid.cc | 22 +++++++++++++++++----- source/common/http/conn_pool_grid.h | 6 ++++++ test/common/http/conn_pool_grid_test.cc | 16 ++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/source/common/http/conn_pool_grid.cc b/source/common/http/conn_pool_grid.cc index 0711e3fa8f7b..1ae84bc56da5 100644 --- a/source/common/http/conn_pool_grid.cc +++ b/source/common/http/conn_pool_grid.cc @@ -96,12 +96,17 @@ void ConnectivityGrid::WrapperCallbacks::onConnectionAttemptFailed( // If this point is reached, all pools have been tried. Pass the pool failure up to the // original caller, if the caller hasn't already been notified. + signalFailureAndDeleteSelf(reason, transport_failure_reason, host); +} + +void ConnectivityGrid::WrapperCallbacks::signalFailureAndDeleteSelf( + ConnectionPool::PoolFailureReason reason, absl::string_view transport_failure_reason, + Upstream::HostDescriptionConstSharedPtr host) { ConnectionPool::Callbacks* callbacks = inner_callbacks_; inner_callbacks_ = nullptr; deleteThis(); if (callbacks != nullptr) { - ENVOY_LOG(trace, "Passing pool failure up to caller.", describePool(attempt->pool()), - host->hostname()); + ENVOY_LOG(trace, "Passing pool failure up to caller."); callbacks->onPoolFailure(reason, transport_failure_reason, host); } } @@ -191,6 +196,9 @@ void ConnectivityGrid::WrapperCallbacks::cancelAllPendingAttempts( absl::optional ConnectivityGrid::WrapperCallbacks::tryAnotherConnection() { + if (grid_.destroying_) { + return {}; + } absl::optional next_pool = grid_.nextPool(current_); if (!next_pool.has_value()) { // If there are no other pools to try, return an empty optional. @@ -236,9 +244,13 @@ ConnectivityGrid::ConnectivityGrid( ConnectivityGrid::~ConnectivityGrid() { // Ignore idle callbacks while the pools are destroyed below. destroying_ = true; - // Callbacks might have pending streams registered with the pools, so cancel and delete - // the callback before deleting the pools. - wrapped_callbacks_.clear(); + while (!wrapped_callbacks_.empty()) { + // Before tearing down the callbacks, make sure they pass up pool failure to + // the caller. We do not call onPoolFailure because it does up-calls to the + // (delete-in-process) grid. + wrapped_callbacks_.front()->signalFailureAndDeleteSelf( + ConnectionPool::PoolFailureReason::LocalConnectionFailure, "grid teardown", host_); + } pools_.clear(); } diff --git a/source/common/http/conn_pool_grid.h b/source/common/http/conn_pool_grid.h index b5b9bb059662..cfc15dc6af72 100644 --- a/source/common/http/conn_pool_grid.h +++ b/source/common/http/conn_pool_grid.h @@ -96,6 +96,12 @@ class ConnectivityGrid : public ConnectionPool::Instance, StreamInfo::StreamInfo& info, absl::optional protocol); + // Called by onConnectionAttemptFailed and on grid deletion destruction to let wrapper + // callback subscribers know the connect attempt failed. + void signalFailureAndDeleteSelf(ConnectionPool::PoolFailureReason reason, + absl::string_view transport_failure_reason, + Upstream::HostDescriptionConstSharedPtr host); + private: // Removes this from the owning list, deleting it. void deleteThis(); diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index a442a55fdd34..807fe5d32edc 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -560,6 +560,22 @@ TEST_F(ConnectivityGridTest, TestCancel) { cancel->cancel(Envoy::ConnectionPool::CancelPolicy::CloseExcess); } +// Test tearing down the grid with active connections. +TEST_F(ConnectivityGridTest, TestTeardown) { + initialize(); + addHttp3AlternateProtocol(); + EXPECT_EQ(grid_->first(), nullptr); + + grid_->newStream(decoder_, callbacks_, + {/*can_send_early_data_=*/false, + /*can_use_http3_=*/true}); + EXPECT_NE(grid_->first(), nullptr); + + // When the grid is reset, pool failure should be called. + EXPECT_CALL(callbacks_.pool_failure_, ready()); + grid_.reset(); +} + // Make sure drains get sent to all active pools. TEST_F(ConnectivityGridTest, Drain) { initialize(); From 11fe00a8f32e04df2adfdd9bf9fc5077792a1d4f Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Thu, 15 Feb 2024 06:49:47 -0800 Subject: [PATCH 017/151] stream_info: Add a move constructor the the ResponseFlag matcher (#32407) --------- Signed-off-by: Ryan Hamilton --- test/mocks/stream_info/mocks.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/mocks/stream_info/mocks.h b/test/mocks/stream_info/mocks.h index 0b166849e32a..c3c8518ac152 100644 --- a/test/mocks/stream_info/mocks.h +++ b/test/mocks/stream_info/mocks.h @@ -20,6 +20,10 @@ class Matcher : public internal::MatcherBase { public: explicit Matcher() = default; + + template ::type::is_gtest_matcher> + Matcher(M&& m) : internal::MatcherBase(std::forward(m)) {} + Matcher(Envoy::StreamInfo::ResponseFlag value) { *this = Eq(value); } Matcher(Envoy::StreamInfo::CoreResponseFlag value) { *this = Eq(Envoy::StreamInfo::ResponseFlag(value)); From 17b1db5bc290a5be36b5065848cec07c26e764f4 Mon Sep 17 00:00:00 2001 From: Paul Ogilby Date: Thu, 15 Feb 2024 10:04:17 -0500 Subject: [PATCH 018/151] Guarantee reset after complete for async streams does not crash. (#32408) --------- Signed-off-by: Paul Ogilby --- source/common/http/async_client_impl.cc | 6 ++ source/common/http/async_client_impl.h | 1 + test/common/http/async_client_impl_test.cc | 88 ++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index 19a7e72adaba..b79c34431ab9 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -314,6 +314,7 @@ void AsyncOngoingRequestImpl::initialize() { } void AsyncRequestSharedImpl::onComplete() { + complete_ = true; callbacks_.onBeforeFinalizeUpstreamSpan(*child_span_, &response_->headers()); Tracing::HttpTracerUtility::finalizeUpstreamSpan(*child_span_, streamInfo(), @@ -335,6 +336,11 @@ void AsyncRequestSharedImpl::onTrailers(ResponseTrailerMapPtr&& trailers) { } void AsyncRequestSharedImpl::onReset() { + if (complete_) { + // This request has already been completed; a reset should be ignored. + return; + } + if (!cancelled_) { // Set "error reason" tag related to reset. The tagging for "error true" is done inside the // Tracing::HttpTracerUtility::finalizeUpstreamSpan. diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 645633adbdf2..7e6354bd931b 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -151,6 +151,7 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, absl::optional destructor_callback_; // Callback to listen for low/high/overflow watermark events. absl::optional> watermark_callbacks_; + bool complete_{}; private: void cleanup(); diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index 1d706dbe24aa..ea7327da21a6 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -369,6 +369,50 @@ TEST_F(AsyncClientImplTest, OngoingRequestWithWatermarkingAndReset) { stream_encoder_.getStream().resetStream(StreamResetReason::RemoteReset); } +TEST_F(AsyncClientImplTest, OngoingRequestWithResetAfterCompletion) { + auto headers = std::make_unique(); + HttpTestUtility::addDefaultHeaders(*headers); + TestRequestHeaderMapImpl headers_copy = *headers; + + Buffer::OwnedImpl data("test data"); + const Buffer::OwnedImpl data_copy(data.toString()); + + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce(Invoke( + [&](ResponseDecoder& decoder, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + response_decoder_ = &decoder; + return nullptr; + })); + + headers_copy.addCopy("x-envoy-internal", "true"); + headers_copy.addCopy("x-forwarded-for", "127.0.0.1"); + + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&headers_copy), false)); + EXPECT_CALL(stream_encoder_, encodeData(BufferEqual(&data_copy), true)); + + AsyncClient::OngoingRequest* request = + client_.startRequest(std::move(headers), callbacks_, AsyncClient::RequestOptions()); + EXPECT_NE(request, nullptr); + + request->sendData(data, true); + + expectSuccess(request, 200); + + ResponseHeaderMapPtr response_headers(new TestResponseHeaderMapImpl{{":status", "200"}}); + response_decoder_->decodeHeaders(std::move(response_headers), true); + + request->reset(); + EXPECT_EQ( + 1UL, + cm_.thread_local_cluster_.cluster_.info_->stats_store_.counter("upstream_rq_200").value()); + EXPECT_EQ(1UL, cm_.thread_local_cluster_.cluster_.info_->stats_store_ + .counter("internal.upstream_rq_200") + .value()); +} + TEST_F(AsyncClientImplTracingTest, Basic) { Tracing::MockSpan* child_span{new Tracing::MockSpan()}; message_->body().add("test body"); @@ -1444,6 +1488,50 @@ TEST_F(AsyncClientImplTracingTest, CancelRequest) { request->cancel(); } +TEST_F(AsyncClientImplTracingTest, CancelRequestAfterComplete) { + Tracing::MockSpan* child_span{new Tracing::MockSpan()}; + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce(Invoke( + [&](StreamDecoder&, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + return nullptr; + })); + + EXPECT_CALL(parent_span_, spawnChild_(_, "async fake_cluster egress", _)) + .WillOnce(Return(child_span)); + + AsyncClient::RequestOptions options = AsyncClient::RequestOptions().setParentSpan(parent_span_); + EXPECT_CALL(*child_span, setSampled(true)); + EXPECT_CALL(*child_span, injectContext(_, _)); + EXPECT_CALL(callbacks_, onBeforeFinalizeUpstreamSpan(_, _)) + .WillOnce(Invoke([](Tracing::Span& span, const Http::ResponseHeaderMap* response_headers) { + span.setTag("onBeforeFinalizeUpstreamSpan", "called"); + // Since this is a failure, we expect no response headers. + ASSERT_EQ(nullptr, response_headers); + })); + AsyncClient::Request* request = client_.send(std::move(message_), callbacks_, options); + + EXPECT_CALL(*child_span, setTag(Eq("onBeforeFinalizeUpstreamSpan"), Eq("called"))); + EXPECT_CALL(*child_span, + setTag(Eq(Tracing::Tags::get().Component), Eq(Tracing::Tags::get().Proxy))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().HttpProtocol), Eq("HTTP/1.1"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().UpstreamAddress), Eq("10.0.0.1:443"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().PeerAddress), Eq("10.0.0.1:443"))); + + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().UpstreamCluster), Eq("fake_cluster"))); + EXPECT_CALL(*child_span, + setTag(Eq(Tracing::Tags::get().UpstreamClusterName), Eq("observability_name"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().HttpStatusCode), Eq("0"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().ResponseFlags), Eq("-"))); + EXPECT_CALL(*child_span, setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(*child_span, + setTag(Eq(Tracing::Tags::get().Canceled), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(*child_span, finishSpan()); + request->cancel(); +} + TEST_F(AsyncClientImplTest, DestroyWithActiveStream) { EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) .WillOnce(Invoke( From 4b860cf34f4c0c9c8ce333a4cbac6becd8d65d68 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Thu, 15 Feb 2024 11:37:45 -0500 Subject: [PATCH 019/151] tooling: tracking open issues (#32281) Signed-off-by: Alyssa Wilk --- tools/repo/notify.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/repo/notify.py b/tools/repo/notify.py index d5a45ec2a37a..18a139f7e0c0 100644 --- a/tools/repo/notify.py +++ b/tools/repo/notify.py @@ -261,10 +261,11 @@ async def post_to_oncall(self): await self.send_message( channel='#envoy-maintainer-oncall', text=(f"*Stalled PRs* (PRs with review out-SLO, please address)\n{stalled}")) + num_issues = await self.track_open_issues() await self.send_message( channel='#envoy-maintainer-oncall', text=( - f"*Untriaged Issues* (please tag and cc area experts)\n<{ISSUE_LINK}|{ISSUE_LINK}>" + f"*{num_issues} Untriaged Issues* (please tag and cc area experts)\n<{ISSUE_LINK}|{ISSUE_LINK}>" )) except SlackApiError as e: self.log.error(f"Unexpected error {e.response['error']}") @@ -278,6 +279,11 @@ def pr_message(self, age, pull): f"<{pull['html_url']}|{html.escape(pull['title'])}> has been waiting " f"{markup}{days} days {hours} hours{markup}") + async def track_open_issues(self): + response = await self.session.get( + "https://api.github.com/repos/envoyproxy/envoy/issues?labels=triage") + return len(await response.json()) + async def run(self): if not self.github_token: self.log.error("Missing GITHUB_TOKEN: please check github workflow configuration") From 35a7cfc8046df1f37bd07aff0409cc8de9b55a9d Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Thu, 15 Feb 2024 13:23:21 -0500 Subject: [PATCH 020/151] owners: updating kbaichoo's email address (#32410) Signed-off-by: Adi Suissa-Peleg --- OWNERS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OWNERS.md b/OWNERS.md index a8137725f596..772b07e8bbfd 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -40,7 +40,7 @@ routing PRs, questions, etc. to the right place. * Stats, abseil, scalability, and performance. * Adi Peleg ([adisuissa](https://github.com/adisuissa)) (adip@google.com) * xDS APIs, configuration, control plane, fuzzing. -* Kevin Baichoo ([KBaichoo](https://github.com/KBaichoo)) (kbaichoo@google.com) +* Kevin Baichoo ([KBaichoo](https://github.com/KBaichoo)) (envoy@kevinbaichoo.com) * Data plane, overload management, flow control. * Keith Smiley ([keith](https://github.com/keith)) (keithbsmiley@gmail.com) * Bazel, CI, compilers, linkers, general build issues, etc. @@ -80,7 +80,7 @@ without further review. * Pradeep Rao ([pradeepcrao](https://github.com/pradeepcrao)) (pcrao@google.com) * Kateryna Nezdolii ([nezdolik](https://github.com/nezdolik)) (kateryna.nezdolii@gmail.com) * Boteng Yao ([botengyao](https://github.com/botengyao)) (boteng@google.com) -* Kevin Baichoo ([KBaichoo](https://github.com/KBaichoo)) (kbaichoo@google.com) +* Kevin Baichoo ([KBaichoo](https://github.com/KBaichoo)) (envoy@kevinbaichoo.com) * Tianyu Xia ([tyxia](https://github.com/tyxia)) (tyxia@google.com) # Emeritus maintainers From ea10ba4ca20b14598ea1b5ad24da87a3bca1bb55 Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:06:12 +0200 Subject: [PATCH 021/151] tcp_tunneling: fix half close on upstream trailers (#32325) When TCP tunneling, if the HTTP upstream is sending trailers, the downstream doesn't get any indication of this. To support half close semantics, fixing this with runtime guard so that FIN is sent downstream in such cases. Risk Level: medium Testing: unit tests, integration tests Docs Changes: none Release Notes: none Platform Specific Features: none Runtime guard: ``envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers`` Signed-off-by: ohadvano --- changelogs/current.yaml | 6 ++++ source/common/runtime/runtime_features.cc | 1 + source/common/tcp_proxy/upstream.h | 7 +++++ test/common/tcp_proxy/upstream_test.cc | 22 +++++++++++++++ .../tcp_tunneling_integration_test.cc | 28 +++++++++++++++++++ 5 files changed, 64 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index ea4167e89cc5..92b295142881 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -85,6 +85,12 @@ bug_fixes: Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is received in an mTLS client cert IP SAN. These SANs will be ignored. This applies only when using formatter ``%DOWNSTREAM_PEER_IP_SAN%``. +- area: tcp_proxy + change: | + When tunneling TCP over HTTP, closed the downstream connection (for writing only) when upstream trailers are read + to support half close semantics during TCP tunneling. + This behavioral change can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers`` to false. - area: jwt_authn change: | Fixed JWT extractor, which concatenated headers with a comma, resultig in invalid tokens. diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index bc28a7975b41..dc560deed2c8 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -87,6 +87,7 @@ RUNTIME_GUARD(envoy_reloadable_features_skip_dns_lookup_for_proxied_requests); RUNTIME_GUARD(envoy_reloadable_features_ssl_transport_failure_reason_format); RUNTIME_GUARD(envoy_reloadable_features_stateful_session_encode_ttl_in_cookie); RUNTIME_GUARD(envoy_reloadable_features_stop_decode_metadata_on_local_reply); +RUNTIME_GUARD(envoy_reloadable_features_tcp_tunneling_send_downstream_fin_on_upstream_trailers); RUNTIME_GUARD(envoy_reloadable_features_test_feature_true); RUNTIME_GUARD(envoy_reloadable_features_thrift_allow_negative_field_ids); RUNTIME_GUARD(envoy_reloadable_features_thrift_connection_draining); diff --git a/source/common/tcp_proxy/upstream.h b/source/common/tcp_proxy/upstream.h index d115bc440cc2..d16126547cc5 100644 --- a/source/common/tcp_proxy/upstream.h +++ b/source/common/tcp_proxy/upstream.h @@ -8,6 +8,7 @@ #include "envoy/upstream/thread_local_cluster.h" #include "envoy/upstream/upstream.h" +#include "source/common/buffer/buffer_impl.h" #include "source/common/common/dump_state_utils.h" #include "source/common/http/codec_client.h" #include "source/common/router/header_parser.h" @@ -196,6 +197,12 @@ class HttpUpstream : public GenericUpstream, protected Http::StreamCallbacks { void decodeTrailers(Http::ResponseTrailerMapPtr&& trailers) override { parent_.config_.propagateResponseTrailers(std::move(trailers), parent_.downstream_info_.filterState()); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers")) { + Buffer::OwnedImpl data; + parent_.upstream_callbacks_.onUpstreamData(data, /* end_stream = */ true); + } + parent_.doneReading(); } void decodeMetadata(Http::MetadataMapPtr&&) override {} diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc index 7b27fecdf266..d48d9690c602 100644 --- a/test/common/tcp_proxy/upstream_test.cc +++ b/test/common/tcp_proxy/upstream_test.cc @@ -201,6 +201,28 @@ TEST_P(HttpUpstreamTest, UpstreamTrailersMarksDoneReading) { this->upstream_->responseDecoder().decodeTrailers(std::move(trailers)); } +TEST_P(HttpUpstreamTest, UpstreamTrailersPropagateFinDownstream) { + setupUpstream(); + EXPECT_CALL(encoder_.stream_, resetStream(_)).Times(0); + upstream_->doneWriting(); + EXPECT_CALL(callbacks_, onUpstreamData(BufferStringEqual(""), true)); + Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; + upstream_->responseDecoder().decodeTrailers(std::move(trailers)); +} + +TEST_P(HttpUpstreamTest, UpstreamTrailersDontPropagateFinDownstreamWhenFeatureDisabled) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.tcp_tunneling_send_downstream_fin_on_upstream_trailers", + "false"}}); + setupUpstream(); + EXPECT_CALL(encoder_.stream_, resetStream(_)).Times(0); + upstream_->doneWriting(); + EXPECT_CALL(callbacks_, onUpstreamData(_, _)).Times(0); + Http::ResponseTrailerMapPtr trailers{new Http::TestResponseTrailerMapImpl{{"key", "value"}}}; + upstream_->responseDecoder().decodeTrailers(std::move(trailers)); +} + class HttpUpstreamRequestEncoderTest : public testing::TestWithParam { public: HttpUpstreamRequestEncoderTest() { diff --git a/test/integration/tcp_tunneling_integration_test.cc b/test/integration/tcp_tunneling_integration_test.cc index 5e0a5a37d664..023121aeef5f 100644 --- a/test/integration/tcp_tunneling_integration_test.cc +++ b/test/integration/tcp_tunneling_integration_test.cc @@ -1313,6 +1313,34 @@ TEST_P(TcpTunnelingIntegrationTest, CopyResponseTrailers) { EXPECT_THAT(waitForAccessLog(access_log_filename), testing::HasSubstr(trailer_value)); } +TEST_P(TcpTunnelingIntegrationTest, DownstreamFinOnUpstreamTrailers) { + if (upstreamProtocol() == Http::CodecType::HTTP1) { + return; + } + + initialize(); + + tcp_client_ = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); + ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); + ASSERT_TRUE(upstream_request_->waitForHeadersComplete()); + + upstream_request_->encodeHeaders(default_response_headers_, false); + sendBidiData(fake_upstream_connection_); + + // Send trailers + const std::string trailer_value = "trailer-value"; + Http::TestResponseTrailerMapImpl response_trailers{{"test-trailer-name", trailer_value}}; + upstream_request_->encodeTrailers(response_trailers); + + // Upstream trailers should close the downstream connection for writing. + tcp_client_->waitForHalfClose(); + + // Close Connection + tcp_client_->close(); + ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); +} + TEST_P(TcpTunnelingIntegrationTest, CloseUpstreamFirst) { initialize(); From 3a43d39256a85eae9d90368161e0a8fbbb5c1952 Mon Sep 17 00:00:00 2001 From: botengyao Date: Thu, 15 Feb 2024 14:13:37 -0500 Subject: [PATCH 022/151] h/2: remove an assertion and add test for malformed frame (#32369) A malformed frame with 1xx and end_stream should not crash Envoy. The assertion should not hold based on the runtime data. --- source/common/http/conn_manager_impl.cc | 2 +- source/common/http/http2/codec_impl.cc | 1 - ...response_header_fuzz_test-6305229339426816 | Bin 0 -> 28 bytes .../multiplexed_integration_test.cc | 36 ++++++++++++++++++ 4 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/common/http/http2/response_header_corpus/clusterfuzz-testcase-minimized-response_header_fuzz_test-6305229339426816 diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index 47e7631c1309..b6725038eddd 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1735,7 +1735,7 @@ void ConnectionManagerImpl::ActiveStream::encode1xxHeaders(ResponseHeaderMap& re // Count both the 1xx and follow-up response code in stats. chargeStats(response_headers); - ENVOY_STREAM_LOG(debug, "encoding 100 continue headers via codec:\n{}", *this, response_headers); + ENVOY_STREAM_LOG(debug, "encoding 1xx continue headers via codec:\n{}", *this, response_headers); // Now actually encode via the codec. response_encoder_->encode1xxHeaders(response_headers); diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index c580718202f7..b744fe6c5c61 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -532,7 +532,6 @@ void ConnectionImpl::ClientStreamImpl::decodeHeaders() { !CodeUtility::is1xx(status) || status == enumToInt(Http::Code::SwitchingProtocols); if (HeaderUtility::isSpecial1xx(*headers)) { - ASSERT(!remote_end_stream_); response_decoder_.decode1xxHeaders(std::move(headers)); } else { response_decoder_.decodeHeaders(std::move(headers), sendEndStream()); diff --git a/test/common/http/http2/response_header_corpus/clusterfuzz-testcase-minimized-response_header_fuzz_test-6305229339426816 b/test/common/http/http2/response_header_corpus/clusterfuzz-testcase-minimized-response_header_fuzz_test-6305229339426816 new file mode 100644 index 0000000000000000000000000000000000000000..f139d0ad01ed95bb8d6d12ef017e2eef2a69235c GIT binary patch literal 28 hcmZQzU}ZF7U|?X>{r8_yfPsOPlNZF}WHvM~1^_Q312F&q literal 0 HcmV?d00001 diff --git a/test/integration/multiplexed_integration_test.cc b/test/integration/multiplexed_integration_test.cc index 88b45501e6f2..1a45e8e551cd 100644 --- a/test/integration/multiplexed_integration_test.cc +++ b/test/integration/multiplexed_integration_test.cc @@ -1944,6 +1944,42 @@ class Http2FrameIntegrationTest : public testing::TestWithParam void { hcm.set_proxy_100_continue(true); }); + beginSession(); + FakeRawConnectionPtr fake_upstream_connection; + + // Start a request and wait for it to reach the upstream. + sendFrame(Http2Frame::makeRequest(1, "host", "/path/to/long/url")); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + const Http2Frame settings_frame = Http2Frame::makeEmptySettingsFrame(); + ASSERT_TRUE(fake_upstream_connection->write(std::string(settings_frame))); + + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 1); + + // A malformed frame is translated to 103 header with END_STREAM by the underlying codec. + // Typically we should get a protocol error, but this should not crash Envoy. + // PAYLOAD_LENGTH: \x05 + // FRAME_TYPE: \x01 + // FLAGS: \x32 + // STREAM_ID: \x01 + // ASCII: \x31, \x30, \x33 for 1, 0, 3 respectively + const std::vector header_frame = { + 0x00, 0x00, 0x05, 0x01, 0x32, 0x00, 0x00, 0x00, 0x01, 0x2d, 0xfe, 0xff, 0x01, 0x10, + 0x00, 0x00, 0x05, 0x09, 0x0d, 0x00, 0x00, 0x00, 0x01, 0x09, 0x03, 0x31, 0x30, 0x33}; + const std::string header_frame_str(reinterpret_cast(header_frame.data()), + header_frame.size()); + ASSERT_TRUE(fake_upstream_connection->write(header_frame_str)); + + const Http2Frame response = readFrame(); + EXPECT_EQ(Http2Frame::Type::Headers, response.type()); + + tcp_client_->close(); + test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0); +} + TEST_P(Http2FrameIntegrationTest, MaxConcurrentStreamsIsRespected) { const int kTotalRequests = 101; config_helper_.addConfigModifier( From ffe2c75d93d8dd643b90a6c473aa68aabcd92a2b Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Thu, 15 Feb 2024 23:00:41 +0100 Subject: [PATCH 023/151] Deprecate global_downstream_max_connections key again :) (#32373) * Deprecate global_downstream_max_connections key Signed-off-by: Kateryna Nezdolii --- changelogs/current.yaml | 4 ++++ source/common/network/tcp_listener_impl.cc | 2 +- source/server/server.cc | 12 ++++++++++-- .../cx_limit_overload_integration_test.cc | 2 +- test/integration/cx_limit_integration_test.cc | 12 ++++++++++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 92b295142881..d28f35a1ab6b 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -190,3 +190,7 @@ new_features: Added support for the ``ECHO`` command. deprecated: +- area: listener + change: | + deprecated runtime key ``overload.global_downstream_max_connections`` in favor of :ref:`downstream connections monitor + `. diff --git a/source/common/network/tcp_listener_impl.cc b/source/common/network/tcp_listener_impl.cc index 29b100928521..5df320f7938d 100644 --- a/source/common/network/tcp_listener_impl.cc +++ b/source/common/network/tcp_listener_impl.cc @@ -31,7 +31,7 @@ bool TcpListenerImpl::rejectCxOverGlobalLimit() const { if (runtime_.threadsafeSnapshot()->get(Runtime::Keys::GlobalMaxCxRuntimeKey)) { ENVOY_LOG_ONCE_MISC( warn, - "Global downstream connections limits is configured via runtime key {} and in " + "Global downstream connections limits is configured via deprecated runtime key {} and in " "{}. Using overload manager config.", Runtime::Keys::GlobalMaxCxRuntimeKey, Server::OverloadProactiveResources::get().GlobalDownstreamMaxConnections); diff --git a/source/server/server.cc b/source/server/server.cc index ce41536f6563..5b73a573b9c6 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -823,6 +823,15 @@ void InstanceBase::onRuntimeReady() { shutdown(); }); } + + // TODO (nezdolik): Fully deprecate this runtime key in the next release. + if (runtime().snapshot().get(Runtime::Keys::GlobalMaxCxRuntimeKey)) { + ENVOY_LOG(warn, + "Usage of the deprecated runtime key {}, consider switching to " + "`envoy.resource_monitors.downstream_connections` instead." + "This runtime key will be removed in future.", + Runtime::Keys::GlobalMaxCxRuntimeKey); + } } void InstanceBase::startWorkers() { @@ -908,8 +917,7 @@ RunHelper::RunHelper(Instance& instance, const Options& options, Event::Dispatch // If there is no global limit to the number of active connections, warn on startup. if (!overload_manager.getThreadLocalOverloadState().isResourceMonitorEnabled( - Server::OverloadProactiveResourceName::GlobalDownstreamMaxConnections) && - !instance.runtime().snapshot().get(Runtime::Keys::GlobalMaxCxRuntimeKey)) { + Server::OverloadProactiveResourceName::GlobalDownstreamMaxConnections)) { ENVOY_LOG(warn, "There is no configured limit to the number of allowed active downstream " "connections. Configure a " "limit in `envoy.resource_monitors.downstream_connections` resource monitor."); diff --git a/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc b/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc index 7242c9f89cd6..b2fa4afe9659 100644 --- a/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc +++ b/test/extensions/resource_monitors/downstream_connections/cx_limit_overload_integration_test.cc @@ -104,7 +104,7 @@ TEST_F(GlobalDownstreamCxLimitIntegrationTest, GlobalLimitSetViaRuntimeKeyAndOve config_helper_.addRuntimeOverride("overload.global_downstream_max_connections", "3"); initializeOverloadManager(2); const std::string log_line = - "Global downstream connections limits is configured via runtime key " + "Global downstream connections limits is configured via deprecated runtime key " "overload.global_downstream_max_connections and in " "envoy.resource_monitors.global_downstream_max_connections. Using overload manager " "config."; diff --git a/test/integration/cx_limit_integration_test.cc b/test/integration/cx_limit_integration_test.cc index c998727d01b9..ccf2b19dc1c6 100644 --- a/test/integration/cx_limit_integration_test.cc +++ b/test/integration/cx_limit_integration_test.cc @@ -136,6 +136,18 @@ TEST_P(ConnectionLimitIntegrationTest, TestListenerLimit) { doTest(init_func, "downstream_cx_overflow"); } +TEST_P(ConnectionLimitIntegrationTest, TestDeprecationWarningForGlobalCxRuntimeLimit) { + std::function init_func = [this]() { + setGlobalLimit(4); + initialize(); + }; + const std::string log_line = + "Usage of the deprecated runtime key overload.global_downstream_max_connections, " + "consider switching to `envoy.resource_monitors.downstream_connections` instead." + "This runtime key will be removed in future."; + EXPECT_LOG_CONTAINS("warn", log_line, { init_func(); }); +} + // TODO (nezdolik) move this test to overload manager test suite, once runtime key is fully // deprecated. TEST_P(ConnectionLimitIntegrationTest, TestEmptyGlobalCxRuntimeLimit) { From 49425f55aa9212a64b3390909160c41dc22ff349 Mon Sep 17 00:00:00 2001 From: Henry Wang Date: Thu, 15 Feb 2024 18:28:13 -0500 Subject: [PATCH 024/151] contrib: Fix build for fips arm (#32382) Previously, the ARM build with FIPS enabled was failing when building on v1.29.0 ``` ERROR: /go/src/github.com/DataDog/envoy/contrib/exe/BUILD:31:16: Illegal ambiguous match on configurable attribute "deps" in //contrib/exe:envoy-static: //bazel:linux_aarch64 //bazel:boringssl_fips ``` This adds `config_setting_group` to make the match explicit - that is, we will skip the `FIPS_LINUX_X86_SKIP_CONTRIB_TARGETS` targets if the target arch is linux x86 and boringssl fips is enabled. Signed-off-by: Henry Wang Signed-off-by: Valerian Roche --- bazel/BUILD | 8 ++++++++ contrib/all_contrib_extensions.bzl | 2 +- contrib/exe/BUILD | 4 ++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/bazel/BUILD b/bazel/BUILD index 775add0417bc..19578ec3c59e 100644 --- a/bazel/BUILD +++ b/bazel/BUILD @@ -481,6 +481,14 @@ config_setting( values = {"define": "boringssl=disabled"}, ) +selects.config_setting_group( + name = "boringssl_fips_x86", + match_all = [ + ":boringssl_fips", + "@platforms//cpu:x86_64", + ], +) + config_setting( name = "zlib_ng", constraint_values = [ diff --git a/contrib/all_contrib_extensions.bzl b/contrib/all_contrib_extensions.bzl index 19c605b700bb..6b7994a2623b 100644 --- a/contrib/all_contrib_extensions.bzl +++ b/contrib/all_contrib_extensions.bzl @@ -28,7 +28,7 @@ PPC_SKIP_CONTRIB_TARGETS = [ "envoy.compression.qatzip.compressor", ] -FIPS_SKIP_CONTRIB_TARGETS = [ +FIPS_LINUX_X86_SKIP_CONTRIB_TARGETS = [ "envoy.compression.qatzip.compressor", ] diff --git a/contrib/exe/BUILD b/contrib/exe/BUILD index 1210422f895d..6c085c76ba80 100644 --- a/contrib/exe/BUILD +++ b/contrib/exe/BUILD @@ -7,7 +7,7 @@ load( load( "//contrib:all_contrib_extensions.bzl", "ARM64_SKIP_CONTRIB_TARGETS", - "FIPS_SKIP_CONTRIB_TARGETS", + "FIPS_LINUX_X86_SKIP_CONTRIB_TARGETS", "PPC_SKIP_CONTRIB_TARGETS", "envoy_all_contrib_extensions", ) @@ -24,7 +24,7 @@ alias( SELECTED_CONTRIB_EXTENSIONS = select({ "//bazel:linux_aarch64": envoy_all_contrib_extensions(ARM64_SKIP_CONTRIB_TARGETS), "//bazel:linux_ppc": envoy_all_contrib_extensions(PPC_SKIP_CONTRIB_TARGETS), - "//bazel:boringssl_fips": envoy_all_contrib_extensions(FIPS_SKIP_CONTRIB_TARGETS), + "//bazel:boringssl_fips_x86": envoy_all_contrib_extensions(FIPS_LINUX_X86_SKIP_CONTRIB_TARGETS), "//conditions:default": envoy_all_contrib_extensions(), }) From 3f27ea3594f3fa713db821b998de3b2affe381c4 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 16 Feb 2024 11:47:31 +0000 Subject: [PATCH 025/151] repo/windows: Enforce `lf` line endings for local checkout (#32418) Signed-off-by: Ryan Northey --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 8bd5b8b56f71..0dd1d568f0b3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ +* text=auto eol=lf + /generated_api_shadow/envoy/** linguist-generated=true /generated_api_shadow/bazel/** linguist-generated=true *.svg binary From fc6fc23548e9638c0dd2725acc9cedd80ceac48e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 11:49:45 +0000 Subject: [PATCH 026/151] build(deps): bump gitpython from 3.1.41 to 3.1.42 in /tools/base (#32435) Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.41 to 3.1.42. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.41...3.1.42) --- updated-dependencies: - dependency-name: gitpython dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index aa097b0eadd1..f0f1302fba3f 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -689,9 +689,9 @@ gitdb==4.0.11 \ --hash=sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4 \ --hash=sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b # via gitpython -gitpython==3.1.41 \ - --hash=sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c \ - --hash=sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048 +gitpython==3.1.42 \ + --hash=sha256:1bf9cd7c9e7255f77778ea54359e54ac22a72a5b51288c457c881057b7bb9ecd \ + --hash=sha256:2d99869e0fef71a73cbd242528105af1d6c1b108c60dfabd994bf292f76c3ceb # via -r requirements.in google-apitools==0.5.32 \ --hash=sha256:b78f74116558e0476e19501b5b4b2ac7c93261a69c5449c861ea95cbc853c688 \ From 79045c2b973ec5c2fad6203d7a4155f1b99fe8e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 11:49:58 +0000 Subject: [PATCH 027/151] build(deps): bump cryptography from 42.0.2 to 42.0.3 in /tools/base (#32434) Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.2 to 42.0.3. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.2...42.0.3) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 66 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index f0f1302fba3f..9176cb67f791 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -419,39 +419,39 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==42.0.2 \ - --hash=sha256:087887e55e0b9c8724cf05361357875adb5c20dec27e5816b653492980d20380 \ - --hash=sha256:09a77e5b2e8ca732a19a90c5bca2d124621a1edb5438c5daa2d2738bfeb02589 \ - --hash=sha256:130c0f77022b2b9c99d8cebcdd834d81705f61c68e91ddd614ce74c657f8b3ea \ - --hash=sha256:141e2aa5ba100d3788c0ad7919b288f89d1fe015878b9659b307c9ef867d3a65 \ - --hash=sha256:28cb2c41f131a5758d6ba6a0504150d644054fd9f3203a1e8e8d7ac3aea7f73a \ - --hash=sha256:2f9f14185962e6a04ab32d1abe34eae8a9001569ee4edb64d2304bf0d65c53f3 \ - --hash=sha256:320948ab49883557a256eab46149df79435a22d2fefd6a66fe6946f1b9d9d008 \ - --hash=sha256:36d4b7c4be6411f58f60d9ce555a73df8406d484ba12a63549c88bd64f7967f1 \ - --hash=sha256:3b15c678f27d66d247132cbf13df2f75255627bcc9b6a570f7d2fd08e8c081d2 \ - --hash=sha256:3dbd37e14ce795b4af61b89b037d4bc157f2cb23e676fa16932185a04dfbf635 \ - --hash=sha256:4383b47f45b14459cab66048d384614019965ba6c1a1a141f11b5a551cace1b2 \ - --hash=sha256:44c95c0e96b3cb628e8452ec060413a49002a247b2b9938989e23a2c8291fc90 \ - --hash=sha256:4b063d3413f853e056161eb0c7724822a9740ad3caa24b8424d776cebf98e7ee \ - --hash=sha256:52ed9ebf8ac602385126c9a2fe951db36f2cb0c2538d22971487f89d0de4065a \ - --hash=sha256:55d1580e2d7e17f45d19d3b12098e352f3a37fe86d380bf45846ef257054b242 \ - --hash=sha256:5ef9bc3d046ce83c4bbf4c25e1e0547b9c441c01d30922d812e887dc5f125c12 \ - --hash=sha256:5fa82a26f92871eca593b53359c12ad7949772462f887c35edaf36f87953c0e2 \ - --hash=sha256:61321672b3ac7aade25c40449ccedbc6db72c7f5f0fdf34def5e2f8b51ca530d \ - --hash=sha256:701171f825dcab90969596ce2af253143b93b08f1a716d4b2a9d2db5084ef7be \ - --hash=sha256:841ec8af7a8491ac76ec5a9522226e287187a3107e12b7d686ad354bb78facee \ - --hash=sha256:8a06641fb07d4e8f6c7dda4fc3f8871d327803ab6542e33831c7ccfdcb4d0ad6 \ - --hash=sha256:8e88bb9eafbf6a4014d55fb222e7360eef53e613215085e65a13290577394529 \ - --hash=sha256:a00aee5d1b6c20620161984f8ab2ab69134466c51f58c052c11b076715e72929 \ - --hash=sha256:a047682d324ba56e61b7ea7c7299d51e61fd3bca7dad2ccc39b72bd0118d60a1 \ - --hash=sha256:a7ef8dd0bf2e1d0a27042b231a3baac6883cdd5557036f5e8df7139255feaac6 \ - --hash=sha256:ad28cff53f60d99a928dfcf1e861e0b2ceb2bc1f08a074fdd601b314e1cc9e0a \ - --hash=sha256:b9097a208875fc7bbeb1286d0125d90bdfed961f61f214d3f5be62cd4ed8a446 \ - --hash=sha256:b97fe7d7991c25e6a31e5d5e795986b18fbbb3107b873d5f3ae6dc9a103278e9 \ - --hash=sha256:e0ec52ba3c7f1b7d813cd52649a5b3ef1fc0d433219dc8c93827c57eab6cf888 \ - --hash=sha256:ea2c3ffb662fec8bbbfce5602e2c159ff097a4631d96235fcf0fb00e59e3ece4 \ - --hash=sha256:fa3dec4ba8fb6e662770b74f62f1a0c7d4e37e25b58b2bf2c1be4c95372b4a33 \ - --hash=sha256:fbeb725c9dc799a574518109336acccaf1303c30d45c075c665c0793c2f79a7f +cryptography==42.0.3 \ + --hash=sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129 \ + --hash=sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe \ + --hash=sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20 \ + --hash=sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec \ + --hash=sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3 \ + --hash=sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd \ + --hash=sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5 \ + --hash=sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b \ + --hash=sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46 \ + --hash=sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504 \ + --hash=sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306 \ + --hash=sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead \ + --hash=sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e \ + --hash=sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938 \ + --hash=sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a \ + --hash=sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b \ + --hash=sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a \ + --hash=sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd \ + --hash=sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54 \ + --hash=sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c \ + --hash=sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857 \ + --hash=sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f \ + --hash=sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f \ + --hash=sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef \ + --hash=sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c \ + --hash=sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548 \ + --hash=sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65 \ + --hash=sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4 \ + --hash=sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4 \ + --hash=sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a \ + --hash=sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151 \ + --hash=sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396 # via # -r requirements.in # aioquic From 6354dcffc83153c96b9dbded64eb98a49fd6ad94 Mon Sep 17 00:00:00 2001 From: Kuat Date: Fri, 16 Feb 2024 05:27:15 -0800 Subject: [PATCH 028/151] cookie session: support 0 ttl (#32093) * cookie session: support 0 ttl Signed-off-by: Kuat Yessenov * add note Signed-off-by: Kuat Yessenov * update changelog Signed-off-by: Kuat Yessenov * review Change-Id: I24b84f52cbe3617e5e298d89b4cafd3c76a62ec8 Signed-off-by: Kuat Yessenov * clarify doc Change-Id: Ib88c98412a6fe696365a01e3f69a9198bf1b90e7 Signed-off-by: Kuat Yessenov * review Change-Id: I81e461948b75eaee9ef909d8d13bf8fea7c66dc9 Signed-off-by: Kuat Yessenov --------- Signed-off-by: Kuat Yessenov --- api/envoy/type/http/v3/cookie.proto | 2 +- changelogs/current.yaml | 3 +++ .../http/stateful_session/cookie/cookie.cc | 9 ++++++--- .../http/stateful_session/cookie/cookie.h | 18 ++++++++++-------- .../stateful_session/cookie/cookie_test.cc | 6 +++--- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/api/envoy/type/http/v3/cookie.proto b/api/envoy/type/http/v3/cookie.proto index 4fe0c2f69df6..0ceda999dfd3 100644 --- a/api/envoy/type/http/v3/cookie.proto +++ b/api/envoy/type/http/v3/cookie.proto @@ -22,7 +22,7 @@ message Cookie { string name = 1 [(validate.rules).string = {min_len: 1}]; // Duration of cookie. This will be used to set the expiry time of a new cookie when it is - // generated. Set this to 0 to use a session cookie. + // generated. Set this to 0s to use a session cookie and disable cookie expiration. google.protobuf.Duration ttl = 2 [(validate.rules).duration = {gte {}}]; // Path of cookie. This will be used to set the path of a new cookie when it is generated. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index d28f35a1ab6b..2a11cafbaf57 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -49,6 +49,9 @@ bug_fixes: - area: tracers change: | use unary RPC calls for OpenTelemetry trace exports, rather than client-side streaming connections. +- area: stateful_session + change: | + Support 0 TTL for proto-encoded cookies, which disables cookie expiration by Envoy. - area: load balancing change: | Added randomization in locality load-balancing initialization. This helps desynchronizing Envoys across diff --git a/source/extensions/http/stateful_session/cookie/cookie.cc b/source/extensions/http/stateful_session/cookie/cookie.cc index 631249b763ea..21a508e7370e 100644 --- a/source/extensions/http/stateful_session/cookie/cookie.cc +++ b/source/extensions/http/stateful_session/cookie/cookie.cc @@ -16,12 +16,15 @@ void CookieBasedSessionStateFactory::SessionStateImpl::onUpdate( if (!upstream_address_.has_value() || host_address != upstream_address_.value()) { if (Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.stateful_session_encode_ttl_in_cookie")) { - auto expiry_time = std::chrono::duration_cast( - (time_source_.monotonicTime() + std::chrono::seconds(factory_.ttl_)).time_since_epoch()); // Build proto message envoy::Cookie cookie; cookie.set_address(std::string(host_address)); - cookie.set_expires(expiry_time.count()); + if (factory_.ttl_ != std::chrono::seconds::zero()) { + const auto expiry_time = std::chrono::duration_cast( + (time_source_.monotonicTime() + std::chrono::seconds(factory_.ttl_)) + .time_since_epoch()); + cookie.set_expires(expiry_time.count()); + } std::string proto_string; cookie.SerializeToString(&proto_string); diff --git a/source/extensions/http/stateful_session/cookie/cookie.h b/source/extensions/http/stateful_session/cookie/cookie.h index dc580395ae00..0ba898bf4c5b 100644 --- a/source/extensions/http/stateful_session/cookie/cookie.h +++ b/source/extensions/http/stateful_session/cookie/cookie.h @@ -69,17 +69,19 @@ class CookieBasedSessionStateFactory : public Envoy::Http::SessionStateFactory { envoy::Cookie cookie; if (cookie.ParseFromString(decoded_value)) { address = cookie.address(); - if (address.empty() || (cookie.expires() == 0)) { + if (address.empty()) { return absl::nullopt; } - std::chrono::seconds expiry_time(cookie.expires()); - auto now = std::chrono::duration_cast( - (time_source_.monotonicTime()).time_since_epoch()); - if (now > expiry_time) { - // Ignore the address extracted from the cookie. This will cause - // upstream cluster to select a new host and new cookie will be generated. - return absl::nullopt; + if (cookie.expires() != 0) { + const std::chrono::seconds expiry_time(cookie.expires()); + const auto now = std::chrono::duration_cast( + (time_source_.monotonicTime()).time_since_epoch()); + if (now > expiry_time) { + // Ignore the address extracted from the cookie. This will cause + // upstream cluster to select a new host and new cookie will be generated. + return absl::nullopt; + } } } else { ENVOY_LOG_ONCE_MISC( diff --git a/test/extensions/http/stateful_session/cookie/cookie_test.cc b/test/extensions/http/stateful_session/cookie/cookie_test.cc index 866edd4fd84a..34f3a04b3ca0 100644 --- a/test/extensions/http/stateful_session/cookie/cookie_test.cc +++ b/test/extensions/http/stateful_session/cookie/cookie_test.cc @@ -52,7 +52,7 @@ TEST(CookieBasedSessionStateFactoryTest, SessionStateTest) { if (use_proto) { envoy::Cookie cookie; cookie.set_address("1.2.3.4:80"); - cookie.set_expires(1000); + // The expiration field is not set in the cookie because TTL is 0 in the config. cookie.SerializeToString(&cookie_content); } else { cookie_content = "1.2.3.4:80"; @@ -176,13 +176,13 @@ TEST(CookieBasedSessionStateFactoryTest, SessionStateProtoCookie) { EXPECT_EQ(absl::nullopt, session_state->upstreamAddress()); // PROTO format - no "expired field" - cookie.set_expires(0); + cookie.clear_expires(); cookie.SerializeToString(&cookie_content); request_headers = {{":path", "/path"}, {"cookie", "override_host=" + Envoy::Base64::encode(cookie_content.c_str(), cookie_content.length())}}; session_state = factory.create(request_headers); - EXPECT_EQ(absl::nullopt, session_state->upstreamAddress()); + EXPECT_EQ("2.3.4.5:80", session_state->upstreamAddress().value()); // PROTO format - pass incorrect format. // The content should be treated as "old" style encoding. From 1584b4f57c615733312871984bd24c5346e10cfb Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 16 Feb 2024 08:29:15 -0500 Subject: [PATCH 029/151] grpc: moving async client creation to statusOr (#32046) Risk Level: low Testing: updated tests Docs Changes: n/a Release Notes: n/a envoyproxy/envoy-mobile#176 Signed-off-by: Alyssa Wilk --- .../filters/network/source/tra/tra_impl.cc | 11 ++-- envoy/grpc/async_client_manager.h | 12 ++-- .../common/grpc/async_client_manager_impl.cc | 57 +++++++++++------ .../common/grpc/async_client_manager_impl.h | 15 ++--- .../grpc/grpc_access_log_impl.cc | 10 +-- .../open_telemetry/grpc_access_log_impl.cc | 9 +-- source/extensions/common/wasm/context.cc | 12 +++- .../common/ratelimit/ratelimit_impl.cc | 8 ++- .../filters/http/ext_authz/config.cc | 8 ++- .../filters/http/ext_proc/client_impl.cc | 6 +- .../http/rate_limit_quota/client_impl.cc | 19 ++++++ .../http/rate_limit_quota/client_impl.h | 10 +-- .../filters/network/ext_authz/config.cc | 12 ++-- .../stat_sinks/metrics_service/config.cc | 9 +-- .../stat_sinks/open_telemetry/config.cc | 10 +-- .../opentelemetry_tracer_impl.cc | 4 +- .../skywalking/skywalking_tracer_impl.cc | 10 +-- .../grpc/async_client_manager_benchmark.cc | 11 ++-- .../grpc/async_client_manager_impl_test.cc | 61 ++++++++++--------- .../common/grpc_access_logger_test.cc | 1 + .../filters/http/ext_authz/config_test.cc | 9 +-- .../filters/server_factory_context_filter.cc | 6 +- test/mocks/grpc/mocks.cc | 1 + test/mocks/grpc/mocks.h | 6 +- tools/code_format/config.yaml | 1 - 25 files changed, 191 insertions(+), 127 deletions(-) diff --git a/contrib/sip_proxy/filters/network/source/tra/tra_impl.cc b/contrib/sip_proxy/filters/network/source/tra/tra_impl.cc index 86c68a071668..95b77c071d5a 100644 --- a/contrib/sip_proxy/filters/network/source/tra/tra_impl.cc +++ b/contrib/sip_proxy/filters/network/source/tra/tra_impl.cc @@ -200,12 +200,13 @@ ClientPtr traClient(Event::Dispatcher& dispatcher, Server::Configuration::Factor const std::chrono::milliseconds timeout) { // TODO(ramaraochavali): register client to singleton when GrpcClientImpl supports concurrent // requests. + auto client_or_error = context.serverFactoryContext() + .clusterManager() + .grpcAsyncClientManager() + .getOrCreateRawAsyncClient(grpc_service, context.scope(), true); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); return std::make_unique( - context.serverFactoryContext() - .clusterManager() - .grpcAsyncClientManager() - .getOrCreateRawAsyncClient(grpc_service, context.scope(), true), - dispatcher, timeout); + client_or_error.value(), dispatcher, timeout); } } // namespace TrafficRoutingAssistant diff --git a/envoy/grpc/async_client_manager.h b/envoy/grpc/async_client_manager.h index aa99f2c23a6e..91c13d7d57ee 100644 --- a/envoy/grpc/async_client_manager.h +++ b/envoy/grpc/async_client_manager.h @@ -81,10 +81,9 @@ class AsyncClientManager { * @param skip_cluster_check if set to true skips checks for cluster presence and being statically * configured. * @param cache_option always use cache or use cache when runtime is enabled. - * @return RawAsyncClientPtr a grpc async client. - * @throws EnvoyException when grpc_service validation fails. + * @return RawAsyncClientPtr a grpc async client or an invalid status. */ - virtual RawAsyncClientSharedPtr + virtual absl::StatusOr getOrCreateRawAsyncClient(const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check) PURE; @@ -100,7 +99,7 @@ class AsyncClientManager { * @return RawAsyncClientPtr a grpc async client. * @throws EnvoyException when grpc_service validation fails. */ - virtual RawAsyncClientSharedPtr + virtual absl::StatusOr getOrCreateRawAsyncClientWithHashKey(const GrpcServiceConfigWithHashKey& grpc_service, Stats::Scope& scope, bool skip_cluster_check) PURE; @@ -111,10 +110,9 @@ class AsyncClientManager { * @param scope stats scope. * @param skip_cluster_check if set to true skips checks for cluster presence and being statically * configured. - * @return AsyncClientFactoryPtr factory for grpc_service. - * @throws EnvoyException when grpc_service validation fails. + * @return AsyncClientFactoryPtr factory for grpc_service or an error status. */ - virtual AsyncClientFactoryPtr + virtual absl::StatusOr factoryForGrpcService(const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check) PURE; }; diff --git a/source/common/grpc/async_client_manager_impl.cc b/source/common/grpc/async_client_manager_impl.cc index 73595f9ea0c1..f2875e30466e 100644 --- a/source/common/grpc/async_client_manager_impl.cc +++ b/source/common/grpc/async_client_manager_impl.cc @@ -45,12 +45,14 @@ bool validateGrpcCompatibleAsciiHeaderValue(absl::string_view h_value) { AsyncClientFactoryImpl::AsyncClientFactoryImpl(Upstream::ClusterManager& cm, const envoy::config::core::v3::GrpcService& config, - bool skip_cluster_check, TimeSource& time_source) + bool skip_cluster_check, TimeSource& time_source, + absl::Status& creation_status) : cm_(cm), config_(config), time_source_(time_source) { if (skip_cluster_check) { - return; + creation_status = absl::OkStatus(); + } else { + creation_status = cm_.checkActiveStaticCluster(config.envoy_grpc().cluster_name()); } - THROW_IF_NOT_OK(cm_.checkActiveStaticCluster(config.envoy_grpc().cluster_name())); } AsyncClientManagerImpl::AsyncClientManagerImpl( @@ -81,11 +83,11 @@ RawAsyncClientPtr AsyncClientFactoryImpl::createUncachedRawAsyncClient() { GoogleAsyncClientFactoryImpl::GoogleAsyncClientFactoryImpl( ThreadLocal::Instance& tls, ThreadLocal::Slot* google_tls_slot, Stats::Scope& scope, - const envoy::config::core::v3::GrpcService& config, Api::Api& api, const StatNames& stat_names) + const envoy::config::core::v3::GrpcService& config, Api::Api& api, const StatNames& stat_names, + absl::Status& creation_status) : tls_(tls), google_tls_slot_(google_tls_slot), scope_(scope.createScope(fmt::format("grpc.{}.", config.google_grpc().stat_prefix()))), config_(config), api_(api), stat_names_(stat_names) { - #ifndef ENVOY_GOOGLE_GRPC UNREFERENCED_PARAMETER(tls_); UNREFERENCED_PARAMETER(google_tls_slot_); @@ -93,26 +95,30 @@ GoogleAsyncClientFactoryImpl::GoogleAsyncClientFactoryImpl( UNREFERENCED_PARAMETER(config_); UNREFERENCED_PARAMETER(api_); UNREFERENCED_PARAMETER(stat_names_); - throwEnvoyExceptionOrPanic("Google C++ gRPC client is not linked"); + creation_status = absl::InvalidArgumentError("Google C++ gRPC client is not linked"); + return; #else ASSERT(google_tls_slot_ != nullptr); #endif + creation_status = absl::OkStatus(); // Check metadata for gRPC API compliance. Uppercase characters are lowered in the HeaderParser. // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md for (const auto& header : config.initial_metadata()) { // Validate key if (!validateGrpcHeaderChars(header.key())) { - throwEnvoyExceptionOrPanic( + creation_status = absl::InvalidArgumentError( fmt::format("Illegal characters in gRPC initial metadata header key: {}.", header.key())); + return; } // Validate value // Binary base64 encoded - handled by the GRPC library if (!::absl::EndsWith(header.key(), "-bin") && !validateGrpcCompatibleAsciiHeaderValue(header.value())) { - throwEnvoyExceptionOrPanic(fmt::format( + creation_status = absl::InvalidArgumentError(fmt::format( "Illegal ASCII value for gRPC initial metadata header key: {}.", header.key())); + return; } } } @@ -128,22 +134,30 @@ RawAsyncClientPtr GoogleAsyncClientFactoryImpl::createUncachedRawAsyncClient() { #endif } -AsyncClientFactoryPtr +absl::StatusOr AsyncClientManagerImpl::factoryForGrpcService(const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) { + absl::Status creation_status = absl::OkStatus(); + AsyncClientFactoryPtr factory; switch (config.target_specifier_case()) { case envoy::config::core::v3::GrpcService::TargetSpecifierCase::kEnvoyGrpc: - return std::make_unique(cm_, config, skip_cluster_check, time_source_); + factory = std::make_unique(cm_, config, skip_cluster_check, + time_source_, creation_status); + break; case envoy::config::core::v3::GrpcService::TargetSpecifierCase::kGoogleGrpc: - return std::make_unique(tls_, google_tls_slot_.get(), scope, - config, api_, stat_names_); + factory = std::make_unique( + tls_, google_tls_slot_.get(), scope, config, api_, stat_names_, creation_status); + break; case envoy::config::core::v3::GrpcService::TargetSpecifierCase::TARGET_SPECIFIER_NOT_SET: PANIC_DUE_TO_PROTO_UNSET; } - return nullptr; + if (!creation_status.ok()) { + return creation_status; + } + return factory; } -RawAsyncClientSharedPtr AsyncClientManagerImpl::getOrCreateRawAsyncClient( +absl::StatusOr AsyncClientManagerImpl::getOrCreateRawAsyncClient( const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) { const GrpcServiceConfigWithHashKey config_with_hash_key = GrpcServiceConfigWithHashKey(config); @@ -151,21 +165,26 @@ RawAsyncClientSharedPtr AsyncClientManagerImpl::getOrCreateRawAsyncClient( if (client != nullptr) { return client; } - client = factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check) - ->createUncachedRawAsyncClient(); + auto factory_or_error = + factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check); + RETURN_IF_STATUS_NOT_OK(factory_or_error); + client = factory_or_error.value()->createUncachedRawAsyncClient(); raw_async_client_cache_->setCache(config_with_hash_key, client); return client; } -RawAsyncClientSharedPtr AsyncClientManagerImpl::getOrCreateRawAsyncClientWithHashKey( +absl::StatusOr +AsyncClientManagerImpl::getOrCreateRawAsyncClientWithHashKey( const GrpcServiceConfigWithHashKey& config_with_hash_key, Stats::Scope& scope, bool skip_cluster_check) { RawAsyncClientSharedPtr client = raw_async_client_cache_->getCache(config_with_hash_key); if (client != nullptr) { return client; } - client = factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check) - ->createUncachedRawAsyncClient(); + auto factory_or_error = + factoryForGrpcService(config_with_hash_key.config(), scope, skip_cluster_check); + RETURN_IF_STATUS_NOT_OK(factory_or_error); + client = factory_or_error.value()->createUncachedRawAsyncClient(); raw_async_client_cache_->setCache(config_with_hash_key, client); return client; } diff --git a/source/common/grpc/async_client_manager_impl.h b/source/common/grpc/async_client_manager_impl.h index a01f02af41a8..54c3cf6b2b00 100644 --- a/source/common/grpc/async_client_manager_impl.h +++ b/source/common/grpc/async_client_manager_impl.h @@ -19,7 +19,8 @@ class AsyncClientFactoryImpl : public AsyncClientFactory { public: AsyncClientFactoryImpl(Upstream::ClusterManager& cm, const envoy::config::core::v3::GrpcService& config, - bool skip_cluster_check, TimeSource& time_source); + bool skip_cluster_check, TimeSource& time_source, + absl::Status& creation_status); RawAsyncClientPtr createUncachedRawAsyncClient() override; private: @@ -33,7 +34,7 @@ class GoogleAsyncClientFactoryImpl : public AsyncClientFactory { GoogleAsyncClientFactoryImpl(ThreadLocal::Instance& tls, ThreadLocal::Slot* google_tls_slot, Stats::Scope& scope, const envoy::config::core::v3::GrpcService& config, Api::Api& api, - const StatNames& stat_names); + const StatNames& stat_names, absl::Status& creation_status); RawAsyncClientPtr createUncachedRawAsyncClient() override; private: @@ -51,17 +52,17 @@ class AsyncClientManagerImpl : public AsyncClientManager { Upstream::ClusterManager& cm, ThreadLocal::Instance& tls, TimeSource& time_source, Api::Api& api, const StatNames& stat_names, const envoy::config::bootstrap::v3::Bootstrap::GrpcAsyncClientManagerConfig& config); - RawAsyncClientSharedPtr + absl::StatusOr getOrCreateRawAsyncClient(const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, bool skip_cluster_check) override; - RawAsyncClientSharedPtr + absl::StatusOr getOrCreateRawAsyncClientWithHashKey(const GrpcServiceConfigWithHashKey& config_with_hash_key, Stats::Scope& scope, bool skip_cluster_check) override; - AsyncClientFactoryPtr factoryForGrpcService(const envoy::config::core::v3::GrpcService& config, - Stats::Scope& scope, - bool skip_cluster_check) override; + absl::StatusOr + factoryForGrpcService(const envoy::config::core::v3::GrpcService& config, Stats::Scope& scope, + bool skip_cluster_check) override; class RawAsyncClientCache : public ThreadLocal::ThreadLocalObject { public: explicit RawAsyncClientCache(Event::Dispatcher& dispatcher, 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 dc2ef0129428..1f74bb653da8 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_impl.cc @@ -55,10 +55,12 @@ GrpcAccessLoggerImpl::SharedPtr GrpcAccessLoggerCacheImpl::createLogger( // exceptions in worker threads. Call sites of this getOrCreateLogger must check the cluster // availability via ClusterManager::checkActiveStaticCluster beforehand, and throw exceptions in // the main thread if necessary. - auto client = async_client_manager_.factoryForGrpcService(config.grpc_service(), scope_, true) - ->createUncachedRawAsyncClient(); - return std::make_shared(std::move(client), config, dispatcher, local_info_, - scope_); + auto factory_or_error = + async_client_manager_.factoryForGrpcService(config.grpc_service(), scope_, true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + return std::make_shared( + factory_or_error.value()->createUncachedRawAsyncClient(), config, dispatcher, local_info_, + scope_); } } // namespace GrpcCommon diff --git a/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc b/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc index 9c4a25d58367..6901c2c344d6 100644 --- a/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc +++ b/source/extensions/access_loggers/open_telemetry/grpc_access_log_impl.cc @@ -88,10 +88,11 @@ GrpcAccessLoggerImpl::SharedPtr GrpcAccessLoggerCacheImpl::createLogger( // We pass skip_cluster_check=true to factoryForGrpcService in order to avoid throwing // exceptions in worker threads. Call sites of this getOrCreateLogger must check the cluster // availability via ClusterManager::checkActiveStaticCluster beforehand, and throw exceptions in - // the main thread if necessary. - auto client = async_client_manager_ - .factoryForGrpcService(config.common_config().grpc_service(), scope_, true) - ->createUncachedRawAsyncClient(); + // the main thread if necessary to ensure it does not throw here. + auto factory_or_error = async_client_manager_.factoryForGrpcService( + config.common_config().grpc_service(), scope_, true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + auto client = factory_or_error.value()->createUncachedRawAsyncClient(); return std::make_shared(std::move(client), config, dispatcher, local_info_, scope_); } diff --git a/source/extensions/common/wasm/context.cc b/source/extensions/common/wasm/context.cc index c1c9bfd2868e..2b13ca94937d 100644 --- a/source/extensions/common/wasm/context.cc +++ b/source/extensions/common/wasm/context.cc @@ -995,8 +995,12 @@ WasmResult Context::grpcCall(std::string_view grpc_service, std::string_view ser auto& handler = grpc_call_request_[token]; handler.context_ = this; handler.token_ = token; - auto grpc_client = clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + auto client_or_error = clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( service_proto, *wasm()->scope_, true /* skip_cluster_check */); + if (!client_or_error.status().ok()) { + return WasmResult::BadArgument; + } + auto grpc_client = client_or_error.value(); grpc_initial_metadata_ = buildRequestHeaderMapFromPairs(initial_metadata); // set default hash policy to be based on :authority to enable consistent hash @@ -1040,8 +1044,12 @@ WasmResult Context::grpcStream(std::string_view grpc_service, std::string_view s auto& handler = grpc_stream_[token]; handler.context_ = this; handler.token_ = token; - auto grpc_client = clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + auto client_or_error = clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( service_proto, *wasm()->scope_, true /* skip_cluster_check */); + if (!client_or_error.status().ok()) { + return WasmResult::BadArgument; + } + auto grpc_client = client_or_error.value(); grpc_initial_metadata_ = buildRequestHeaderMapFromPairs(initial_metadata); // set default hash policy to be based on :authority to enable consistent hash diff --git a/source/extensions/filters/common/ratelimit/ratelimit_impl.cc b/source/extensions/filters/common/ratelimit/ratelimit_impl.cc index 3097f1b62e5e..a72fa1b81661 100644 --- a/source/extensions/filters/common/ratelimit/ratelimit_impl.cc +++ b/source/extensions/filters/common/ratelimit/ratelimit_impl.cc @@ -127,12 +127,14 @@ ClientPtr rateLimitClient(Server::Configuration::FactoryContext& context, const std::chrono::milliseconds timeout) { // TODO(ramaraochavali): register client to singleton when GrpcClientImpl supports concurrent // requests. - return std::make_unique( + auto client_or_error = context.serverFactoryContext() .clusterManager() .grpcAsyncClientManager() - .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true), - timeout); + .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); + return std::make_unique(client_or_error.value(), + timeout); } } // namespace RateLimit diff --git a/source/extensions/filters/http/ext_authz/config.cc b/source/extensions/filters/http/ext_authz/config.cc index bbb99f63cc82..881609481e96 100644 --- a/source/extensions/filters/http/ext_authz/config.cc +++ b/source/extensions/filters/http/ext_authz/config.cc @@ -55,12 +55,14 @@ Http::FilterFactoryCb ExtAuthzFilterConfig::createFilterFactoryFromProtoTyped( Envoy::Grpc::GrpcServiceConfigWithHashKey(proto_config.grpc_service()); callback = [&context, filter_config, timeout_ms, config_with_hash_key](Http::FilterChainFactoryCallbacks& callbacks) { - auto client = std::make_unique( + auto client_or_error = context.serverFactoryContext() .clusterManager() .grpcAsyncClientManager() - .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true), - std::chrono::milliseconds(timeout_ms)); + .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); + auto client = std::make_unique( + client_or_error.value(), std::chrono::milliseconds(timeout_ms)); callbacks.addStreamFilter(std::make_shared(filter_config, std::move(client))); }; } diff --git a/source/extensions/filters/http/ext_proc/client_impl.cc b/source/extensions/filters/http/ext_proc/client_impl.cc index 80ccd31189db..184c2b2ceb35 100644 --- a/source/extensions/filters/http/ext_proc/client_impl.cc +++ b/source/extensions/filters/http/ext_proc/client_impl.cc @@ -15,8 +15,10 @@ ExternalProcessorStreamPtr ExternalProcessorClientImpl::start(ExternalProcessorCallbacks& callbacks, const Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, const StreamInfo::StreamInfo& stream_info) { - Grpc::AsyncClient grpcClient( - client_manager_.getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, scope_, true)); + auto client_or_error = + client_manager_.getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, scope_, true); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); + Grpc::AsyncClient grpcClient(client_or_error.value()); return ExternalProcessorStreamImpl::create(std::move(grpcClient), callbacks, stream_info); } diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.cc b/source/extensions/filters/http/rate_limit_quota/client_impl.cc index 36af6a35b4fb..62154c371de0 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.cc +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.cc @@ -7,6 +7,25 @@ namespace Extensions { namespace HttpFilters { namespace RateLimitQuota { +Grpc::RawAsyncClientSharedPtr +getOrThrow(absl::StatusOr client_or_error) { + THROW_IF_STATUS_NOT_OK(client_or_error, throw); + return client_or_error.value(); +} + +RateLimitClientImpl::RateLimitClientImpl( + const Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, + Server::Configuration::FactoryContext& context, absl::string_view domain_name, + RateLimitQuotaCallbacks* callbacks, BucketsCache& quota_buckets) + : domain_name_(domain_name), + aync_client_(getOrThrow( + context.serverFactoryContext() + .clusterManager() + .grpcAsyncClientManager() + .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true))), + rlqs_callback_(callbacks), quota_buckets_(quota_buckets), + time_source_(context.serverFactoryContext().mainThreadDispatcher().timeSource()) {} + RateLimitQuotaUsageReports RateLimitClientImpl::buildReport(absl::optional bucket_id) { RateLimitQuotaUsageReports report; // Build the report from quota bucket cache. diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.h b/source/extensions/filters/http/rate_limit_quota/client_impl.h index eb7c0f6a6685..4999ef62f9ca 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.h +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.h @@ -34,15 +34,7 @@ class RateLimitClientImpl : public RateLimitClient, public: RateLimitClientImpl(const Grpc::GrpcServiceConfigWithHashKey& config_with_hash_key, Server::Configuration::FactoryContext& context, absl::string_view domain_name, - RateLimitQuotaCallbacks* callbacks, BucketsCache& quota_buckets) - : domain_name_(domain_name), - aync_client_( - context.serverFactoryContext() - .clusterManager() - .grpcAsyncClientManager() - .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context.scope(), true)), - rlqs_callback_(callbacks), quota_buckets_(quota_buckets), - time_source_(context.serverFactoryContext().mainThreadDispatcher().timeSource()) {} + RateLimitQuotaCallbacks* callbacks, BucketsCache& quota_buckets); void onReceiveMessage(RateLimitQuotaResponsePtr&& response) override; diff --git a/source/extensions/filters/network/ext_authz/config.cc b/source/extensions/filters/network/ext_authz/config.cc index fe385b77c24a..28efb8fb9605 100644 --- a/source/extensions/filters/network/ext_authz/config.cc +++ b/source/extensions/filters/network/ext_authz/config.cc @@ -30,13 +30,13 @@ Network::FilterFactoryCb ExtAuthzConfigFactory::createFilterFactoryFromProtoType THROW_IF_NOT_OK(Envoy::Config::Utility::checkTransportVersion(proto_config)); return [grpc_service = proto_config.grpc_service(), &context, ext_authz_config, timeout_ms](Network::FilterManager& filter_manager) -> void { - auto async_client_factory = context.serverFactoryContext() - .clusterManager() - .grpcAsyncClientManager() - .factoryForGrpcService(grpc_service, context.scope(), true); - + auto factory_or_error = context.serverFactoryContext() + .clusterManager() + .grpcAsyncClientManager() + .factoryForGrpcService(grpc_service, context.scope(), true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); auto client = std::make_unique( - async_client_factory->createUncachedRawAsyncClient(), + factory_or_error.value()->createUncachedRawAsyncClient(), std::chrono::milliseconds(timeout_ms)); filter_manager.addReadFilter(Network::ReadFilterSharedPtr{ std::make_shared(ext_authz_config, std::move(client))}); diff --git a/source/extensions/stat_sinks/metrics_service/config.cc b/source/extensions/stat_sinks/metrics_service/config.cc index db823131e0cd..791f81508f31 100644 --- a/source/extensions/stat_sinks/metrics_service/config.cc +++ b/source/extensions/stat_sinks/metrics_service/config.cc @@ -28,12 +28,13 @@ MetricsServiceSinkFactory::createStatsSink(const Protobuf::Message& config, THROW_IF_NOT_OK(Config::Utility::checkTransportVersion(sink_config)); ENVOY_LOG(debug, "Metrics Service gRPC service configuration: {}", grpc_service.DebugString()); + auto client_or_error = server.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + grpc_service, server.scope(), false); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); std::shared_ptr> - grpc_metrics_streamer = std::make_shared( - server.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( - grpc_service, server.scope(), false), - server.localInfo()); + grpc_metrics_streamer = + std::make_shared(client_or_error.value(), server.localInfo()); return std::make_unique>( diff --git a/source/extensions/stat_sinks/open_telemetry/config.cc b/source/extensions/stat_sinks/open_telemetry/config.cc index f7e1487c5ea3..2dc66d339aef 100644 --- a/source/extensions/stat_sinks/open_telemetry/config.cc +++ b/source/extensions/stat_sinks/open_telemetry/config.cc @@ -26,11 +26,13 @@ OpenTelemetrySinkFactory::createStatsSink(const Protobuf::Message& config, case SinkConfig::ProtocolSpecifierCase::kGrpcService: { const auto& grpc_service = sink_config.grpc_service(); + auto client_or_error = + server.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( + grpc_service, server.scope(), false); + THROW_IF_STATUS_NOT_OK(client_or_error, throw); std::shared_ptr grpc_metrics_exporter = - std::make_shared( - otlp_options, - server.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( - grpc_service, server.scope(), false)); + std::make_shared(otlp_options, + client_or_error.value()); return std::make_unique(otlp_metrics_flusher, grpc_metrics_exporter); } diff --git a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc index 46ce3c35ae77..d3fe8624c65d 100644 --- a/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc +++ b/source/extensions/tracers/opentelemetry/opentelemetry_tracer_impl.cc @@ -89,9 +89,11 @@ Driver::Driver(const envoy::config::trace::v3::OpenTelemetryConfig& opentelemetr sampler](Event::Dispatcher& dispatcher) { OpenTelemetryTraceExporterPtr exporter; if (opentelemetry_config.has_grpc_service()) { - Grpc::AsyncClientFactoryPtr&& factory = + auto factory_or_error = factory_context.clusterManager().grpcAsyncClientManager().factoryForGrpcService( opentelemetry_config.grpc_service(), factory_context.scope(), true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + Grpc::AsyncClientFactoryPtr&& factory = std::move(factory_or_error.value()); const Grpc::RawAsyncClientSharedPtr& async_client_shared_ptr = factory->createUncachedRawAsyncClient(); exporter = std::make_unique(async_client_shared_ptr); diff --git a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc index 33c9f67051e3..aa8b6d2d0cee 100644 --- a/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc +++ b/source/extensions/tracers/skywalking/skywalking_tracer_impl.cc @@ -35,11 +35,13 @@ Driver::Driver(const envoy::config::trace::v3::SkyWalkingConfig& proto_config, tracing_context_factory_ = std::make_unique(config_); auto& factory_context = context.serverFactoryContext(); tls_slot_ptr_->set([proto_config, &factory_context, this](Event::Dispatcher& dispatcher) { - TracerPtr tracer = std::make_unique(std::make_unique( + auto factory_or_error = factory_context.clusterManager().grpcAsyncClientManager().factoryForGrpcService( - proto_config.grpc_service(), factory_context.scope(), true), - dispatcher, factory_context.api().randomGenerator(), tracing_stats_, - config_.delayed_buffer_size(), config_.token())); + proto_config.grpc_service(), factory_context.scope(), true); + THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + TracerPtr tracer = std::make_unique(std::make_unique( + std::move(factory_or_error.value()), dispatcher, factory_context.api().randomGenerator(), + tracing_stats_, config_.delayed_buffer_size(), config_.token())); return std::make_shared(std::move(tracer)); }); } diff --git a/test/common/grpc/async_client_manager_benchmark.cc b/test/common/grpc/async_client_manager_benchmark.cc index ca316d777cdf..5b62f6c270b5 100644 --- a/test/common/grpc/async_client_manager_benchmark.cc +++ b/test/common/grpc/async_client_manager_benchmark.cc @@ -53,8 +53,9 @@ void testGetOrCreateAsyncClientWithConfig(::benchmark::State& state) { UNREFERENCED_PARAMETER(_); for (int i = 0; i < 1000; i++) { RawAsyncClientSharedPtr foo_client0 = - async_client_man_test.async_client_manager_.getOrCreateRawAsyncClient( - grpc_service, async_client_man_test.scope_, true); + async_client_man_test.async_client_manager_ + .getOrCreateRawAsyncClient(grpc_service, async_client_man_test.scope_, true) + .value(); } } } @@ -70,8 +71,10 @@ void testGetOrCreateAsyncClientWithHashConfig(::benchmark::State& state) { UNREFERENCED_PARAMETER(_); for (int i = 0; i < 1000; i++) { RawAsyncClientSharedPtr foo_client0 = - async_client_man_test.async_client_manager_.getOrCreateRawAsyncClientWithHashKey( - config_with_hash_key_a, async_client_man_test.scope_, true); + async_client_man_test.async_client_manager_ + .getOrCreateRawAsyncClientWithHashKey(config_with_hash_key_a, + async_client_man_test.scope_, true) + .value(); } } } diff --git a/test/common/grpc/async_client_manager_impl_test.cc b/test/common/grpc/async_client_manager_impl_test.cc index 4e89ac6f296b..8cbcd82c5c5d 100644 --- a/test/common/grpc/async_client_manager_impl_test.cc +++ b/test/common/grpc/async_client_manager_impl_test.cc @@ -231,7 +231,7 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcOk) { envoy::config::core::v3::GrpcService grpc_service; grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); EXPECT_CALL(cm_, checkActiveStaticCluster("foo")).WillOnce(Return(absl::OkStatus())); - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false); + ASSERT_TRUE(async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).ok()); } TEST_F(AsyncClientManagerImplTest, GrpcServiceConfigWithHashKeyTest) { @@ -266,16 +266,16 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCacheWithSecondsConfig) { initialize(aync_manager_config); RawAsyncClientSharedPtr foo_client_0 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); RawAsyncClientSharedPtr foo_client_1 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client_0.get(), foo_client_1.get()); time_system_.advanceTimeAndRun(std::chrono::seconds(19), *dispatcher_, Event::Dispatcher::RunType::NonBlock); RawAsyncClientSharedPtr foo_client_2 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client_1.get(), foo_client_2.get()); // Here we want to test behavior with a specific sequence of events, where each timer @@ -286,7 +286,7 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCacheWithSecondsConfig) { } RawAsyncClientSharedPtr foo_client_3 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_NE(foo_client_2.get(), foo_client_3.get()); } @@ -302,16 +302,16 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCacheWithMilliConfig) { initialize(aync_manager_config); RawAsyncClientSharedPtr foo_client_0 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); RawAsyncClientSharedPtr foo_client_1 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client_0.get(), foo_client_1.get()); time_system_.advanceTimeAndRun(std::chrono::milliseconds(29999), *dispatcher_, Event::Dispatcher::RunType::NonBlock); RawAsyncClientSharedPtr foo_client_2 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client_1.get(), foo_client_2.get()); // Here we want to test behavior with a specific sequence of events, where each timer @@ -322,7 +322,7 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCacheWithMilliConfig) { } RawAsyncClientSharedPtr foo_client_3 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_NE(foo_client_2.get(), foo_client_3.get()); } @@ -333,15 +333,15 @@ TEST_F(AsyncClientManagerImplTest, RawAsyncClientCache) { // Use cache when runtime is enabled. RawAsyncClientSharedPtr foo_client0 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); RawAsyncClientSharedPtr foo_client1 = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_EQ(foo_client0.get(), foo_client1.get()); // Get a different raw async client with different cluster config. grpc_service.mutable_envoy_grpc()->set_cluster_name("bar"); RawAsyncClientSharedPtr bar_client = - async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true); + async_client_manager_->getOrCreateRawAsyncClient(grpc_service, scope_, true).value(); EXPECT_NE(foo_client1.get(), bar_client.get()); } @@ -352,8 +352,8 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcInvalid) { EXPECT_CALL(cm_, checkActiveStaticCluster("foo")).WillOnce(Invoke([](const std::string&) { return absl::InvalidArgumentError("failure"); })); - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "failure"); } @@ -364,10 +364,11 @@ TEST_F(AsyncClientManagerImplTest, GoogleGrpc) { grpc_service.mutable_google_grpc()->set_stat_prefix("foo"); #ifdef ENVOY_GOOGLE_GRPC - EXPECT_NE(nullptr, async_client_manager_->factoryForGrpcService(grpc_service, scope_, false)); + EXPECT_NE(nullptr, + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).value()); #else - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Google C++ gRPC client is not linked"); #endif } @@ -383,12 +384,12 @@ TEST_F(AsyncClientManagerImplTest, GoogleGrpcIllegalCharsInKey) { metadata.set_value("value"); #ifdef ENVOY_GOOGLE_GRPC - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Illegal characters in gRPC initial metadata header key: illegalcharacter;."); #else - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Google C++ gRPC client is not linked"); #endif } @@ -404,10 +405,11 @@ TEST_F(AsyncClientManagerImplTest, LegalGoogleGrpcChar) { metadata.set_value("value"); #ifdef ENVOY_GOOGLE_GRPC - EXPECT_NE(nullptr, async_client_manager_->factoryForGrpcService(grpc_service, scope_, false)); + EXPECT_NE(nullptr, + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).value()); #else - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Google C++ gRPC client is not linked"); #endif } @@ -423,12 +425,12 @@ TEST_F(AsyncClientManagerImplTest, GoogleGrpcIllegalCharsInValue) { metadata.set_value("NonAsciValue.भारत"); #ifdef ENVOY_GOOGLE_GRPC - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Illegal ASCII value for gRPC initial metadata header key: legal-key."); #else - EXPECT_THROW_WITH_MESSAGE( - async_client_manager_->factoryForGrpcService(grpc_service, scope_, false), EnvoyException, + EXPECT_EQ( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, false).status().message(), "Google C++ gRPC client is not linked"); #endif } @@ -439,7 +441,8 @@ TEST_F(AsyncClientManagerImplTest, EnvoyGrpcUnknownSkipClusterCheck) { grpc_service.mutable_envoy_grpc()->set_cluster_name("foo"); EXPECT_CALL(cm_, checkActiveStaticCluster(_)).Times(0); - ASSERT_NO_THROW(async_client_manager_->factoryForGrpcService(grpc_service, scope_, true)); + ASSERT_TRUE( + async_client_manager_->factoryForGrpcService(grpc_service, scope_, true).status().ok()); } } // namespace diff --git a/test/extensions/access_loggers/common/grpc_access_logger_test.cc b/test/extensions/access_loggers/common/grpc_access_logger_test.cc index ad9f890b76ad..0c151f01192d 100644 --- a/test/extensions/access_loggers/common/grpc_access_logger_test.cc +++ b/test/extensions/access_loggers/common/grpc_access_logger_test.cc @@ -484,6 +484,7 @@ class MockGrpcAccessLoggerCache createLogger(const envoy::extensions::access_loggers::grpc::v3::CommonGrpcAccessLogConfig& config, Event::Dispatcher& dispatcher) override { auto client = async_client_manager_.factoryForGrpcService(config.grpc_service(), scope_, true) + .value() ->createUncachedRawAsyncClient(); return std::make_shared(std::move(client), config, dispatcher, scope_, "mock_access_log_prefix.", diff --git a/test/extensions/filters/http/ext_authz/config_test.cc b/test/extensions/filters/http/ext_authz/config_test.cc index c9d716c2dab2..62909863a5ec 100644 --- a/test/extensions/filters/http/ext_authz/config_test.cc +++ b/test/extensions/filters/http/ext_authz/config_test.cc @@ -35,8 +35,8 @@ class TestAsyncClientManagerImpl : public Grpc::AsyncClientManagerImpl { const Grpc::StatNames& stat_names, const Bootstrap::GrpcAsyncClientManagerConfig& config) : Grpc::AsyncClientManagerImpl(cm, tls, time_source, api, stat_names, config) {} - Grpc::AsyncClientFactoryPtr factoryForGrpcService(const envoy::config::core::v3::GrpcService&, - Stats::Scope&, bool) override { + absl::StatusOr + factoryForGrpcService(const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) override { return std::make_unique>(); } }; @@ -211,8 +211,9 @@ class ExtAuthzFilterGrpcTest : public ExtAuthzFilterTest { Envoy::Grpc::GrpcServiceConfigWithHashKey config_with_hash_key = Envoy::Grpc::GrpcServiceConfigWithHashKey(ext_authz_config.grpc_service()); Grpc::RawAsyncClientSharedPtr async_client = - async_client_manager_->getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, - context_.scope(), false); + async_client_manager_ + ->getOrCreateRawAsyncClientWithHashKey(config_with_hash_key, context_.scope(), false) + .value(); Grpc::MockAsyncClient* mock_async_client = dynamic_cast(async_client.get()); EXPECT_NE(mock_async_client, nullptr); diff --git a/test/integration/filters/server_factory_context_filter.cc b/test/integration/filters/server_factory_context_filter.cc index cea3f7ee5457..610df7941993 100644 --- a/test/integration/filters/server_factory_context_filter.cc +++ b/test/integration/filters/server_factory_context_filter.cc @@ -29,8 +29,10 @@ class TestGrpcClient : public Grpc::AsyncStreamCallbacks public: TestGrpcClient(Server::Configuration::ServerFactoryContext& context, const envoy::config::core::v3::GrpcService& grpc_service) - : client_(context.clusterManager().grpcAsyncClientManager().getOrCreateRawAsyncClient( - grpc_service, context.scope(), true)), + : client_(context.clusterManager() + .grpcAsyncClientManager() + .getOrCreateRawAsyncClient(grpc_service, context.scope(), true) + .value()), method_descriptor_(helloworld::Greeter::descriptor()->FindMethodByName("SayHello")) {} // AsyncStreamCallbacks diff --git a/test/mocks/grpc/mocks.cc b/test/mocks/grpc/mocks.cc index 122563cf3340..c375a51226c7 100644 --- a/test/mocks/grpc/mocks.cc +++ b/test/mocks/grpc/mocks.cc @@ -39,6 +39,7 @@ MockAsyncClientFactory::MockAsyncClientFactory() { MockAsyncClientFactory::~MockAsyncClientFactory() = default; MockAsyncClientManager::MockAsyncClientManager() { + ON_CALL(*this, getOrCreateRawAsyncClient(_, _, _)).WillByDefault(Return(nullptr)); ON_CALL(*this, factoryForGrpcService(_, _, _)) .WillByDefault(Invoke([](const envoy::config::core::v3::GrpcService&, Stats::Scope&, bool) { return std::make_unique>(); diff --git a/test/mocks/grpc/mocks.h b/test/mocks/grpc/mocks.h index 920ccb7aa7d7..d0126823e671 100644 --- a/test/mocks/grpc/mocks.h +++ b/test/mocks/grpc/mocks.h @@ -111,15 +111,15 @@ class MockAsyncClientManager : public AsyncClientManager { MockAsyncClientManager(); ~MockAsyncClientManager() override; - MOCK_METHOD(AsyncClientFactoryPtr, factoryForGrpcService, + MOCK_METHOD(absl::StatusOr, factoryForGrpcService, (const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check)); - MOCK_METHOD(RawAsyncClientSharedPtr, getOrCreateRawAsyncClient, + MOCK_METHOD(absl::StatusOr, getOrCreateRawAsyncClient, (const envoy::config::core::v3::GrpcService& grpc_service, Stats::Scope& scope, bool skip_cluster_check)); - MOCK_METHOD(RawAsyncClientSharedPtr, getOrCreateRawAsyncClientWithHashKey, + MOCK_METHOD(absl::StatusOr, getOrCreateRawAsyncClientWithHashKey, (const GrpcServiceConfigWithHashKey& config_with_hash_key, Stats::Scope& scope, bool skip_cluster_check)); }; diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 9cd42aba6c74..f0151c712844 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -129,7 +129,6 @@ paths: - source/common/protobuf/message_validator_impl.cc - source/common/quic/quic_server_transport_socket_factory.cc - source/common/secret/secret_manager_impl.cc - - source/common/grpc/async_client_manager_impl.cc - source/common/grpc/google_grpc_utils.cc - source/common/tcp_proxy/tcp_proxy.cc - source/common/config/subscription_factory_impl.cc From ab2bf64324e825a3f915fd13d71c3a6cb29130bc Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Fri, 16 Feb 2024 08:57:49 -0500 Subject: [PATCH 030/151] xDS rate-limit: rejecting inf and nan fill rate values (#32203) Rejecting a config with invalid Inf or NaN values for the fill-rate of the xDS streams. Risk Level: low Testing: Added unit tests and fuzz corpus Docs Changes: N/A Release Notes: Added Platform Specific Features: N/A Fixes fuzz bug 66327 Signed-off-by: Adi Suissa-Peleg --- changelogs/current.yaml | 4 ++ source/common/config/utility.cc | 8 ++- source/common/config/utility.h | 6 +- .../grpc_collection_subscription_factory.cc | 5 +- .../config_subscription/grpc/grpc_mux_impl.cc | 5 +- .../grpc/grpc_subscription_factory.cc | 10 +++- .../grpc/new_grpc_mux_impl.cc | 5 +- .../grpc/xds_mux/grpc_mux_impl.cc | 10 +++- test/common/config/utility_test.cc | 56 +++++++++++++++---- .../grpc/grpc_mux_impl_test.cc | 18 ++++++ .../grpc/new_grpc_mux_impl_test.cc | 20 +++++++ .../grpc/xds_grpc_mux_impl_test.cc | 36 ++++++++++++ test/server/server_corpus/rate_limit_nan | 44 +++++++++++++++ 13 files changed, 204 insertions(+), 23 deletions(-) create mode 100644 test/server/server_corpus/rate_limit_nan diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2a11cafbaf57..d980de92bc1f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -62,6 +62,10 @@ bug_fixes: fixed a bug where second HTTP response headers received would cause Envoy to crash in cases where ``propagate_response_headers`` and retry configurations are enabled at the same time, and an upstream request is retried multiple times. +- area: xds + change: | + Reject xDS configurations where the rate-limit's :ref:`fill_rate ` + is set to Infinity or NaN. - area: tracing change: | Prevent Envoy from crashing at start up when the OpenTelemetry environment resource detector cannot detect any attributes. diff --git a/source/common/config/utility.cc b/source/common/config/utility.cc index eff142670f25..7728019c228e 100644 --- a/source/common/config/utility.cc +++ b/source/common/config/utility.cc @@ -183,7 +183,7 @@ std::chrono::milliseconds Utility::configSourceInitialFetchTimeout( PROTOBUF_GET_MS_OR_DEFAULT(config_source, initial_fetch_timeout, 15000)); } -RateLimitSettings +absl::StatusOr Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& api_config_source) { RateLimitSettings rate_limit_settings; if (api_config_source.has_rate_limit_settings()) { @@ -194,6 +194,12 @@ Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& rate_limit_settings.fill_rate_ = PROTOBUF_GET_WRAPPED_OR_DEFAULT(api_config_source.rate_limit_settings(), fill_rate, Envoy::Config::RateLimitSettings::DefaultFillRate); + // Reject the NaN and Inf values. + if (std::isnan(rate_limit_settings.fill_rate_) || std::isinf(rate_limit_settings.fill_rate_)) { + return absl::InvalidArgumentError( + fmt::format("The value of fill_rate in RateLimitSettings ({}) must not be NaN nor Inf", + rate_limit_settings.fill_rate_)); + } } return rate_limit_settings; } diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 5dfa959b1f5e..27971e06ac85 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -178,9 +178,10 @@ class Utility { * Parses RateLimit configuration from envoy::config::core::v3::ApiConfigSource to * RateLimitSettings. * @param api_config_source ApiConfigSource. - * @return RateLimitSettings. + * @return absl::StatusOr - returns an error when an + * invalid RateLimit config settings are provided. */ - static RateLimitSettings + static absl::StatusOr parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& api_config_source); /** @@ -490,7 +491,6 @@ class Utility { const envoy::config::core::v3::ApiConfigSource& api_config_source, Random::RandomGenerator& random, const uint32_t default_base_interval_ms, absl::optional default_max_interval_ms) { - auto& grpc_services = api_config_source.grpc_services(); if (!grpc_services.empty() && grpc_services[0].has_envoy_grpc()) { return prepareJitteredExponentialBackOffStrategy( diff --git a/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc b/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc index ca0266efa642..a356e7a8f6c3 100644 --- a/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc +++ b/source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.cc @@ -26,12 +26,15 @@ SubscriptionPtr DeltaGrpcCollectionConfigSubscriptionFactory::create( auto factory_or_error = Config::Utility::factoryForGrpcApiConfigSource( data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(api_config_source); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ factory_or_error.value()->createUncachedRawAsyncClient(), /*dispatcher_=*/data.dispatcher_, /*service_method_=*/deltaGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/data.scope_, /*config_validators_=*/std::move(custom_config_validators), /*xds_resources_delegate_=*/{}, diff --git a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc index 3c9473dd5105..910dab4006e0 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/grpc_mux_impl.cc @@ -571,6 +571,9 @@ class GrpcMuxFactory : public MuxFactory { const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, XdsResourcesDelegateOptRef xds_resources_delegate, bool use_eds_resources_cache) override { + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(ads_config); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/std::move(async_client), /*dispatcher_=*/dispatcher, @@ -578,7 +581,7 @@ class GrpcMuxFactory : public MuxFactory { *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), /*local_info_=*/local_info, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/scope, /*config_validators_=*/std::move(config_validators), /*xds_resources_delegate_=*/xds_resources_delegate, diff --git a/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc b/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc index aea80ca7b8f1..20efc1d418b4 100644 --- a/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc +++ b/source/extensions/config_subscription/grpc/grpc_subscription_factory.cc @@ -29,12 +29,15 @@ GrpcConfigSubscriptionFactory::create(ConfigSubscriptionFactory::SubscriptionDat auto factory_or_error = Utility::factoryForGrpcApiConfigSource( data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(api_config_source); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/factory_or_error.value()->createUncachedRawAsyncClient(), /*dispatcher_=*/data.dispatcher_, /*service_method_=*/sotwGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/data.scope_, /*config_validators_=*/std::move(custom_config_validators), /*xds_resources_delegate_=*/data.xds_resources_delegate_, @@ -74,12 +77,15 @@ DeltaGrpcConfigSubscriptionFactory::create(ConfigSubscriptionFactory::Subscripti auto factory_or_error = Utility::factoryForGrpcApiConfigSource( data.cm_.grpcAsyncClientManager(), api_config_source, data.scope_, true); THROW_IF_STATUS_NOT_OK(factory_or_error, throw); + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(api_config_source); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/factory_or_error.value()->createUncachedRawAsyncClient(), /*dispatcher_=*/data.dispatcher_, /*service_method_=*/deltaGrpcMethod(data.type_url_), /*local_info_=*/data.local_info_, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(api_config_source), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/data.scope_, /*config_validators_=*/std::move(custom_config_validators), /*xds_resources_delegate_=*/{}, diff --git a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc index e0f73f807f0b..571699f2c4c4 100644 --- a/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/new_grpc_mux_impl.cc @@ -357,6 +357,9 @@ class NewGrpcMuxFactory : public MuxFactory { const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, OptRef, bool use_eds_resources_cache) override { + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(ads_config); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/std::move(async_client), /*dispatcher_=*/dispatcher, @@ -364,7 +367,7 @@ class NewGrpcMuxFactory : public MuxFactory { *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.DeltaAggregatedResources"), /*local_info_=*/local_info, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/scope, /*config_validators_=*/std::move(config_validators), /*xds_resources_delegate_=*/absl::nullopt, diff --git a/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc b/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc index 7f15728b26ef..52f1eedbe909 100644 --- a/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc +++ b/source/extensions/config_subscription/grpc/xds_mux/grpc_mux_impl.cc @@ -415,6 +415,9 @@ class DeltaGrpcMuxFactory : public MuxFactory { const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, XdsResourcesDelegateOptRef, bool use_eds_resources_cache) override { + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(ads_config); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/std::move(async_client), /*dispatcher_=*/dispatcher, @@ -422,7 +425,7 @@ class DeltaGrpcMuxFactory : public MuxFactory { *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.DeltaAggregatedResources"), /*local_info_=*/local_info, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/scope, /*config_validators_=*/std::move(config_validators), /*xds_resources_delegate_=*/absl::nullopt, @@ -450,6 +453,9 @@ class SotwGrpcMuxFactory : public MuxFactory { const LocalInfo::LocalInfo& local_info, CustomConfigValidatorsPtr&& config_validators, BackOffStrategyPtr&& backoff_strategy, XdsConfigTrackerOptRef xds_config_tracker, XdsResourcesDelegateOptRef, bool use_eds_resources_cache) override { + absl::StatusOr rate_limit_settings_or_error = + Utility::parseRateLimitSettings(ads_config); + THROW_IF_STATUS_NOT_OK(rate_limit_settings_or_error, throw); GrpcMuxContext grpc_mux_context{ /*async_client_=*/std::move(async_client), /*dispatcher_=*/dispatcher, @@ -457,7 +463,7 @@ class SotwGrpcMuxFactory : public MuxFactory { *Protobuf::DescriptorPool::generated_pool()->FindMethodByName( "envoy.service.discovery.v3.AggregatedDiscoveryService.StreamAggregatedResources"), /*local_info_=*/local_info, - /*rate_limit_settings_=*/Utility::parseRateLimitSettings(ads_config), + /*rate_limit_settings_=*/rate_limit_settings_or_error.value(), /*scope_=*/scope, /*config_validators_=*/std::move(config_validators), /*xds_resources_delegate_=*/absl::nullopt, diff --git a/test/common/config/utility_test.cc b/test/common/config/utility_test.cc index b4f13d5195c8..767c0e7db1a9 100644 --- a/test/common/config/utility_test.cc +++ b/test/common/config/utility_test.cc @@ -86,19 +86,23 @@ TEST(UtilityTest, CheckFilesystemSubscriptionBackingPath) { TEST(UtilityTest, ParseDefaultRateLimitSettings) { envoy::config::core::v3::ApiConfigSource api_config_source; - const RateLimitSettings& rate_limit_settings = Utility::parseRateLimitSettings(api_config_source); - EXPECT_EQ(false, rate_limit_settings.enabled_); - EXPECT_EQ(100, rate_limit_settings.max_tokens_); - EXPECT_EQ(10, rate_limit_settings.fill_rate_); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_TRUE(rate_limit_settings.ok()); + EXPECT_EQ(false, rate_limit_settings->enabled_); + EXPECT_EQ(100, rate_limit_settings->max_tokens_); + EXPECT_EQ(10, rate_limit_settings->fill_rate_); } TEST(UtilityTest, ParseEmptyRateLimitSettings) { envoy::config::core::v3::ApiConfigSource api_config_source; api_config_source.mutable_rate_limit_settings(); - const RateLimitSettings& rate_limit_settings = Utility::parseRateLimitSettings(api_config_source); - EXPECT_EQ(true, rate_limit_settings.enabled_); - EXPECT_EQ(100, rate_limit_settings.max_tokens_); - EXPECT_EQ(10, rate_limit_settings.fill_rate_); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_TRUE(rate_limit_settings.ok()); + EXPECT_EQ(true, rate_limit_settings->enabled_); + EXPECT_EQ(100, rate_limit_settings->max_tokens_); + EXPECT_EQ(10, rate_limit_settings->fill_rate_); } TEST(UtilityTest, ParseRateLimitSettings) { @@ -107,10 +111,38 @@ TEST(UtilityTest, ParseRateLimitSettings) { api_config_source.mutable_rate_limit_settings(); rate_limits->mutable_max_tokens()->set_value(500); rate_limits->mutable_fill_rate()->set_value(4); - const RateLimitSettings& rate_limit_settings = Utility::parseRateLimitSettings(api_config_source); - EXPECT_EQ(true, rate_limit_settings.enabled_); - EXPECT_EQ(500, rate_limit_settings.max_tokens_); - EXPECT_EQ(4, rate_limit_settings.fill_rate_); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_TRUE(rate_limit_settings.ok()); + EXPECT_EQ(true, rate_limit_settings->enabled_); + EXPECT_EQ(500, rate_limit_settings->max_tokens_); + EXPECT_EQ(4, rate_limit_settings->fill_rate_); +} + +TEST(UtilityTest, ParseNanFillRateLimitSettings) { + envoy::config::core::v3::ApiConfigSource api_config_source; + envoy::config::core::v3::RateLimitSettings* rate_limits = + api_config_source.mutable_rate_limit_settings(); + rate_limits->mutable_max_tokens()->set_value(500); + rate_limits->mutable_fill_rate()->set_value(std::numeric_limits::quiet_NaN()); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_FALSE(rate_limit_settings.ok()); + EXPECT_EQ(rate_limit_settings.status().message(), + "The value of fill_rate in RateLimitSettings (nan) must not be NaN nor Inf"); +} + +TEST(UtilityTest, ParseInfiniteFillRateLimitSettings) { + envoy::config::core::v3::ApiConfigSource api_config_source; + envoy::config::core::v3::RateLimitSettings* rate_limits = + api_config_source.mutable_rate_limit_settings(); + rate_limits->mutable_max_tokens()->set_value(500); + rate_limits->mutable_fill_rate()->set_value(std::numeric_limits::infinity()); + const absl::StatusOr rate_limit_settings = + Utility::parseRateLimitSettings(api_config_source); + EXPECT_FALSE(rate_limit_settings.ok()); + EXPECT_EQ(rate_limit_settings.status().message(), + "The value of fill_rate in RateLimitSettings (inf) must not be NaN nor Inf"); } // TEST(UtilityTest, FactoryForGrpcApiConfigSource) should catch misconfigured diff --git a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc index 0dec36a101b0..65e31acc9947 100644 --- a/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/grpc_mux_impl_test.cc @@ -1386,6 +1386,24 @@ TEST_F(NullGrpcMuxImplTest, OnDiscoveryResponseImplemented) { EXPECT_NO_THROW(null_mux_.onDiscoveryResponse(std::move(response), cp_stats)); } +TEST(GrpcMuxFactoryTest, InvalidRateLimit) { + auto* factory = + Config::Utility::getFactoryByName("envoy.config_mux.grpc_mux_factory"); + NiceMock dispatcher; + NiceMock random; + NiceMock store; + Stats::MockScope& scope{store.mockScope()}; + NiceMock local_info; + envoy::config::core::v3::ApiConfigSource ads_config; + ads_config.mutable_rate_limit_settings()->mutable_max_tokens()->set_value(100); + ads_config.mutable_rate_limit_settings()->mutable_fill_rate()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(factory->create(std::make_unique(), dispatcher, random, scope, + ads_config, local_info, nullptr, nullptr, absl::nullopt, + absl::nullopt, false), + EnvoyException); +} + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc index 9888a26b6e45..fe04f30f7a8a 100644 --- a/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/new_grpc_mux_impl_test.cc @@ -1,5 +1,6 @@ #include +#include "envoy/common/exception.h" #include "envoy/config/endpoint/v3/endpoint.pb.h" #include "envoy/config/endpoint/v3/endpoint.pb.validate.h" #include "envoy/config/xds_config_tracker.h" @@ -23,6 +24,7 @@ #include "test/mocks/grpc/mocks.h" #include "test/mocks/local_info/mocks.h" #include "test/mocks/runtime/mocks.h" +#include "test/mocks/stats/mocks.h" #include "test/test_common/logging.h" #include "test/test_common/resources.h" #include "test/test_common/simulated_time_system.h" @@ -763,6 +765,24 @@ TEST_P(NewGrpcMuxImplTest, AddRemoveSubscriptions) { } } +TEST(NewGrpcMuxFactoryTest, InvalidRateLimit) { + auto* factory = Config::Utility::getFactoryByName( + "envoy.config_mux.new_grpc_mux_factory"); + NiceMock dispatcher; + NiceMock random; + NiceMock store; + Stats::MockScope& scope{store.mockScope()}; + NiceMock local_info; + envoy::config::core::v3::ApiConfigSource ads_config; + ads_config.mutable_rate_limit_settings()->mutable_max_tokens()->set_value(100); + ads_config.mutable_rate_limit_settings()->mutable_fill_rate()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(factory->create(std::make_unique(), dispatcher, random, scope, + ads_config, local_info, nullptr, nullptr, absl::nullopt, + absl::nullopt, false), + EnvoyException); +} + } // namespace } // namespace Config } // namespace Envoy diff --git a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc index 819c55381c20..ec78e9d4bed4 100644 --- a/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc +++ b/test/extensions/config_subscription/grpc/xds_grpc_mux_impl_test.cc @@ -1300,6 +1300,42 @@ TEST_F(NullGrpcMuxImplTest, AddWatchRaisesException) { TEST_F(NullGrpcMuxImplTest, NoEdsResourcesCache) { EXPECT_EQ({}, null_mux_->edsResourcesCache()); } +TEST(UnifiedSotwGrpcMuxFactoryTest, InvalidRateLimit) { + auto* factory = Config::Utility::getFactoryByName( + "envoy.config_mux.sotw_grpc_mux_factory"); + NiceMock dispatcher; + NiceMock random; + NiceMock store; + Stats::MockScope& scope{store.mockScope()}; + NiceMock local_info; + envoy::config::core::v3::ApiConfigSource ads_config; + ads_config.mutable_rate_limit_settings()->mutable_max_tokens()->set_value(100); + ads_config.mutable_rate_limit_settings()->mutable_fill_rate()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(factory->create(std::make_unique(), dispatcher, random, scope, + ads_config, local_info, nullptr, nullptr, absl::nullopt, + absl::nullopt, false), + EnvoyException); +} + +TEST(UnifiedDeltaGrpcMuxFactoryTest, InvalidRateLimit) { + auto* factory = Config::Utility::getFactoryByName( + "envoy.config_mux.delta_grpc_mux_factory"); + NiceMock dispatcher; + NiceMock random; + NiceMock store; + Stats::MockScope& scope{store.mockScope()}; + NiceMock local_info; + envoy::config::core::v3::ApiConfigSource ads_config; + ads_config.mutable_rate_limit_settings()->mutable_max_tokens()->set_value(100); + ads_config.mutable_rate_limit_settings()->mutable_fill_rate()->set_value( + std::numeric_limits::quiet_NaN()); + EXPECT_THROW(factory->create(std::make_unique(), dispatcher, random, scope, + ads_config, local_info, nullptr, nullptr, absl::nullopt, + absl::nullopt, false), + EnvoyException); +} + } // namespace } // namespace XdsMux } // namespace Config diff --git a/test/server/server_corpus/rate_limit_nan b/test/server/server_corpus/rate_limit_nan new file mode 100644 index 000000000000..4c9b4bb59ec5 --- /dev/null +++ b/test/server/server_corpus/rate_limit_nan @@ -0,0 +1,44 @@ +node { + id: "\016\000\317@" + cluster: "@" +} +dynamic_resources { + ads_config { + api_type: DELTA_GRPC + grpc_services { + google_grpc { + target_uri: "7.0z1" + stat_prefix: "Ny" + } + } + rate_limit_settings { + max_tokens { + } + fill_rate { + value: nan + } + } + transport_api_version: V3 + } +} +layered_runtime { + layers { + name: "0" + rtds_layer { + name: "1=" + rtds_config { + ads { + } + initial_fetch_timeout { + seconds: 9472 + } + } + } + } + layers { + name: "q" + static_layer { + } + } +} +default_socket_interface: "#" From 61f199119e19d200ee9ae49982a7b81f08c9a825 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 16 Feb 2024 09:01:07 -0500 Subject: [PATCH 031/151] datasource: moving read to statusor (#32253) Signed-off-by: Alyssa Wilk --- .../source/cryptomb_private_key_provider.cc | 3 +- .../test/fake_factory.cc | 6 +- .../source/qat_private_key_provider.cc | 3 +- .../sxg/filters/http/source/filter_config.h | 6 +- envoy/common/exception.h | 8 ++ source/common/config/datasource.cc | 20 ++--- source/common/config/datasource.h | 27 +----- .../formatter/substitution_format_string.h | 5 +- source/common/grpc/google_grpc_creds_impl.cc | 9 +- source/common/local_reply/local_reply.cc | 4 +- source/common/router/config_utility.cc | 5 +- ...tificate_validation_context_config_impl.cc | 9 +- .../common/ssl/tls_certificate_config_impl.cc | 15 ++-- source/extensions/common/wasm/wasm.cc | 3 +- .../zstd/common/dictionary_manager.h | 3 +- .../filters/http/basic_auth/config.cc | 5 +- .../filters/http/jwt_authn/filter_factory.cc | 3 +- .../filters/http/jwt_authn/jwks_cache.cc | 6 +- .../extensions/filters/http/lua/lua_filter.cc | 10 ++- .../extensions/filters/http/oauth2/filter.h | 6 +- .../filters/network/direct_response/config.cc | 5 +- .../filters/network/redis_proxy/config.h | 4 +- .../network/redis_proxy/proxy_filter.cc | 11 +-- .../file_based_metadata/config.cc | 3 +- .../local_response_policy.cc | 8 +- .../lua/lua_cluster_specifier.cc | 3 +- .../environment_resource_detector.cc | 3 +- source/extensions/tracers/xray/config.cc | 6 +- .../cert_validator/spiffe/spiffe_validator.cc | 3 +- .../tls/context_config_impl.cc | 3 +- test/common/config/datasource_test.cc | 85 +++---------------- test/common/router/config_impl_test.cc | 15 ++-- test/common/secret/sds_api_test.cc | 2 +- .../sds_generic_secret_integration_test.cc | 5 +- tools/code_format/config.yaml | 3 + 35 files changed, 145 insertions(+), 170 deletions(-) diff --git a/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.cc b/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.cc index 313f93099b90..7ed6246805ee 100644 --- a/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.cc +++ b/contrib/cryptomb/private_key_providers/source/cryptomb_private_key_provider.cc @@ -657,7 +657,8 @@ CryptoMbPrivateKeyMethodProvider::CryptoMbPrivateKeyMethodProvider( std::chrono::milliseconds poll_delay = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(conf, poll_delay, 200)); - std::string private_key = Config::DataSource::read(conf.private_key(), false, api_); + std::string private_key = + THROW_OR_RETURN_VALUE(Config::DataSource::read(conf.private_key(), false, api_), std::string); bssl::UniquePtr bio( BIO_new_mem_buf(const_cast(private_key.data()), private_key.size())); diff --git a/contrib/cryptomb/private_key_providers/test/fake_factory.cc b/contrib/cryptomb/private_key_providers/test/fake_factory.cc index b5a1a0cdae0d..eaa64bf4cc8e 100644 --- a/contrib/cryptomb/private_key_providers/test/fake_factory.cc +++ b/contrib/cryptomb/private_key_providers/test/fake_factory.cc @@ -168,8 +168,10 @@ FakeCryptoMbPrivateKeyMethodFactory::createPrivateKeyMethodProviderInstance( std::make_shared(supported_instruction_set_); // We need to get more RSA key params in order to be able to use BoringSSL signing functions. - std::string private_key = Config::DataSource::read( - conf.private_key(), false, private_key_provider_context.serverFactoryContext().api()); + std::string private_key = THROW_OR_RETURN_VALUE( + Config::DataSource::read(conf.private_key(), false, + private_key_provider_context.serverFactoryContext().api()), + std::string); bssl::UniquePtr bio( BIO_new_mem_buf(const_cast(private_key.data()), private_key.size())); diff --git a/contrib/qat/private_key_providers/source/qat_private_key_provider.cc b/contrib/qat/private_key_providers/source/qat_private_key_provider.cc index b6a4b0b51658..f90615efcf25 100644 --- a/contrib/qat/private_key_providers/source/qat_private_key_provider.cc +++ b/contrib/qat/private_key_providers/source/qat_private_key_provider.cc @@ -363,7 +363,8 @@ QatPrivateKeyMethodProvider::QatPrivateKeyMethodProvider( std::chrono::milliseconds poll_delay = std::chrono::milliseconds(PROTOBUF_GET_MS_OR_DEFAULT(conf, poll_delay, 5)); - std::string private_key = Config::DataSource::read(conf.private_key(), false, api_); + std::string private_key = + THROW_OR_RETURN_VALUE(Config::DataSource::read(conf.private_key(), false, api_), std::string); bssl::UniquePtr bio( BIO_new_mem_buf(const_cast(private_key.data()), private_key.size())); diff --git a/contrib/sxg/filters/http/source/filter_config.h b/contrib/sxg/filters/http/source/filter_config.h index 11532d2e2145..ca3cbfdb5300 100644 --- a/contrib/sxg/filters/http/source/filter_config.h +++ b/contrib/sxg/filters/http/source/filter_config.h @@ -52,13 +52,15 @@ class SDSSecretReader : public SecretReader { Secret::GenericSecretConfigProviderSharedPtr& secret_provider, Api::Api& api) { const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api); + value = + THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), std::string); } return secret_provider->addUpdateCallback([secret_provider, &api, &value]() { const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api); + value = THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), + std::string); } }); } diff --git a/envoy/common/exception.h b/envoy/common/exception.h index 48240a1d232b..4d5d9fd45bc0 100644 --- a/envoy/common/exception.h +++ b/envoy/common/exception.h @@ -60,4 +60,12 @@ class EnvoyException : public std::runtime_error { if (!variable.ok()) { \ return variable; \ } + +template Type returnOrThrow(absl::StatusOr type_or_error) { + THROW_IF_STATUS_NOT_OK(type_or_error, throw); + return type_or_error.value(); +} + +#define THROW_OR_RETURN_VALUE(expression, type) returnOrThrow(expression) + } // namespace Envoy diff --git a/source/common/config/datasource.cc b/source/common/config/datasource.cc index 1935600c8a43..4c3765b3b7f8 100644 --- a/source/common/config/datasource.cc +++ b/source/common/config/datasource.cc @@ -15,28 +15,28 @@ static constexpr uint32_t RetryInitialDelayMilliseconds = 1000; static constexpr uint32_t RetryMaxDelayMilliseconds = 10 * 1000; static constexpr uint32_t RetryCount = 1; -std::string read(const envoy::config::core::v3::DataSource& source, bool allow_empty, Api::Api& api, - uint64_t max_size) { +absl::StatusOr read(const envoy::config::core::v3::DataSource& source, + bool allow_empty, Api::Api& api, uint64_t max_size) { std::string data; absl::StatusOr file_or_error; switch (source.specifier_case()) { case envoy::config::core::v3::DataSource::SpecifierCase::kFilename: if (max_size > 0) { if (!api.fileSystem().fileExists(source.filename())) { - throwEnvoyExceptionOrPanic(fmt::format("file {} does not exist", source.filename())); + return absl::InvalidArgumentError(fmt::format("file {} does not exist", source.filename())); } const ssize_t size = api.fileSystem().fileSize(source.filename()); if (size < 0) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( absl::StrCat("cannot determine size of file ", source.filename())); } if (static_cast(size) > max_size) { - throwEnvoyExceptionOrPanic(fmt::format("file {} size is {} bytes; maximum is {}", - source.filename(), size, max_size)); + return absl::InvalidArgumentError(fmt::format("file {} size is {} bytes; maximum is {}", + source.filename(), size, max_size)); } } file_or_error = api.fileSystem().fileReadToEnd(source.filename()); - THROW_IF_STATUS_NOT_OK(file_or_error, throw); + RETURN_IF_STATUS_NOT_OK(file_or_error); data = file_or_error.value(); break; case envoy::config::core::v3::DataSource::SpecifierCase::kInlineBytes: @@ -48,7 +48,7 @@ std::string read(const envoy::config::core::v3::DataSource& source, bool allow_e case envoy::config::core::v3::DataSource::SpecifierCase::kEnvironmentVariable: { const char* environment_variable = std::getenv(source.environment_variable().c_str()); if (environment_variable == nullptr) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("Environment variable doesn't exist: {}", source.environment_variable())); } data = environment_variable; @@ -56,12 +56,12 @@ std::string read(const envoy::config::core::v3::DataSource& source, bool allow_e } default: if (!allow_empty) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("Unexpected DataSource::specifier_case(): {}", source.specifier_case())); } } if (!allow_empty && data.empty()) { - throwEnvoyExceptionOrPanic("DataSource cannot be empty"); + return absl::InvalidArgumentError("DataSource cannot be empty"); } return data; } diff --git a/source/common/config/datasource.h b/source/common/config/datasource.h index 326c45a3d149..888794623eea 100644 --- a/source/common/config/datasource.h +++ b/source/common/config/datasource.h @@ -26,11 +26,11 @@ namespace DataSource { * @param api reference to the Api object * @param max_size max size limit of file to read, default 0 means no limit, and if the file data * would exceed the limit, it will throw a EnvoyException. - * @return std::string with DataSource contents. - * @throw EnvoyException if no DataSource case is specified and !allow_empty. + * @return std::string with DataSource contents. or an error status if no DataSource case is + * specified and !allow_empty. */ -std::string read(const envoy::config::core::v3::DataSource& source, bool allow_empty, Api::Api& api, - uint64_t max_size = 0); +absl::StatusOr read(const envoy::config::core::v3::DataSource& source, + bool allow_empty, Api::Api& api, uint64_t max_size = 0); /** * @param source data source. @@ -43,25 +43,6 @@ absl::optional getPath(const envoy::config::core::v3::DataSource& s */ using AsyncDataSourceCb = std::function; -class LocalAsyncDataProvider { -public: - LocalAsyncDataProvider(Init::Manager& manager, const envoy::config::core::v3::DataSource& source, - bool allow_empty, Api::Api& api, AsyncDataSourceCb&& callback) - : init_target_("LocalAsyncDataProvider", [this, &source, allow_empty, &api, callback]() { - callback(DataSource::read(source, allow_empty, api)); - init_target_.ready(); - }) { - manager.add(init_target_); - } - - ~LocalAsyncDataProvider() { init_target_.ready(); } - -private: - Init::TargetImpl init_target_; -}; - -using LocalAsyncDataProviderPtr = std::unique_ptr; - class RemoteAsyncDataProvider : public Event::DeferredDeletable, public Config::DataFetcher::RemoteDataFetcherCallback, public Logger::Loggable { diff --git a/source/common/formatter/substitution_format_string.h b/source/common/formatter/substitution_format_string.h index b929fef5d7b3..3cdd032a9eee 100644 --- a/source/common/formatter/substitution_format_string.h +++ b/source/common/formatter/substitution_format_string.h @@ -58,8 +58,9 @@ class SubstitutionFormatStringUtils { commands); case envoy::config::core::v3::SubstitutionFormatString::FormatCase::kTextFormatSource: return std::make_unique>( - Config::DataSource::read(config.text_format_source(), true, - context.serverFactoryContext().api()), + THROW_OR_RETURN_VALUE(Config::DataSource::read(config.text_format_source(), true, + context.serverFactoryContext().api()), + std::string), config.omit_empty_values(), commands); case envoy::config::core::v3::SubstitutionFormatString::FormatCase::FORMAT_NOT_SET: PANIC_DUE_TO_PROTO_UNSET; diff --git a/source/common/grpc/google_grpc_creds_impl.cc b/source/common/grpc/google_grpc_creds_impl.cc index ae49d3257a7f..d185db79d16d 100644 --- a/source/common/grpc/google_grpc_creds_impl.cc +++ b/source/common/grpc/google_grpc_creds_impl.cc @@ -16,9 +16,12 @@ std::shared_ptr CredsUtility::getChannelCredentials( CredentialSpecifierCase::kSslCredentials: { const auto& ssl_credentials = google_grpc.channel_credentials().ssl_credentials(); const grpc::SslCredentialsOptions ssl_credentials_options = { - Config::DataSource::read(ssl_credentials.root_certs(), true, api), - Config::DataSource::read(ssl_credentials.private_key(), true, api), - Config::DataSource::read(ssl_credentials.cert_chain(), true, api), + THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.root_certs(), true, api), + std::string), + THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.private_key(), true, api), + std::string), + THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.cert_chain(), true, api), + std::string), }; return grpc::SslCredentials(ssl_credentials_options); } diff --git a/source/common/local_reply/local_reply.cc b/source/common/local_reply/local_reply.cc index 1f745e69a8d1..ae1efcc6385b 100644 --- a/source/common/local_reply/local_reply.cc +++ b/source/common/local_reply/local_reply.cc @@ -61,7 +61,9 @@ class ResponseMapper { status_code_ = static_cast(config.status_code().value()); } if (config.has_body()) { - body_ = Config::DataSource::read(config.body(), true, context.serverFactoryContext().api()); + body_ = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.body(), true, context.serverFactoryContext().api()), + std::string); } if (config.has_body_format_override()) { diff --git a/source/common/router/config_utility.cc b/source/common/router/config_utility.cc index f70bcc217a8f..b261f4a353a0 100644 --- a/source/common/router/config_utility.cc +++ b/source/common/router/config_utility.cc @@ -114,8 +114,9 @@ ConfigUtility::parseDirectResponseBody(const envoy::config::route::v3::Route& ro } const auto& body = route.direct_response().body(); - const std::string string_body = - Envoy::Config::DataSource::read(body, true, api, max_body_size_bytes); + auto body_or_error = Envoy::Config::DataSource::read(body, true, api, max_body_size_bytes); + RETURN_IF_STATUS_NOT_OK(body_or_error); + const std::string& string_body = body_or_error.value(); if (string_body.length() > max_body_size_bytes) { return absl::InvalidArgumentError(fmt::format("response body size is {} bytes; maximum is {}", string_body.length(), max_body_size_bytes)); diff --git a/source/common/ssl/certificate_validation_context_config_impl.cc b/source/common/ssl/certificate_validation_context_config_impl.cc index b1249969f8cf..4f030296efcd 100644 --- a/source/common/ssl/certificate_validation_context_config_impl.cc +++ b/source/common/ssl/certificate_validation_context_config_impl.cc @@ -20,10 +20,15 @@ static const std::string INLINE_STRING = ""; CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl( const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext& config, Api::Api& api) - : ca_cert_(Config::DataSource::read(config.trusted_ca(), true, api)), + : ca_cert_(THROW_OR_RETURN_VALUE( + THROW_OR_RETURN_VALUE(Config::DataSource::read(config.trusted_ca(), true, api), + std::string), + std::string)), ca_cert_path_(Config::DataSource::getPath(config.trusted_ca()) .value_or(ca_cert_.empty() ? EMPTY_STRING : INLINE_STRING)), - certificate_revocation_list_(Config::DataSource::read(config.crl(), true, api)), + certificate_revocation_list_(THROW_OR_RETURN_VALUE( + THROW_OR_RETURN_VALUE(Config::DataSource::read(config.crl(), true, api), std::string), + std::string)), certificate_revocation_list_path_( Config::DataSource::getPath(config.crl()) .value_or(certificate_revocation_list_.empty() ? EMPTY_STRING : INLINE_STRING)), diff --git a/source/common/ssl/tls_certificate_config_impl.cc b/source/common/ssl/tls_certificate_config_impl.cc index 0b131494b5da..bf5855797f75 100644 --- a/source/common/ssl/tls_certificate_config_impl.cc +++ b/source/common/ssl/tls_certificate_config_impl.cc @@ -14,7 +14,8 @@ namespace Ssl { namespace { std::vector readOcspStaple(const envoy::config::core::v3::DataSource& source, Api::Api& api) { - std::string staple = Config::DataSource::read(source, true, api); + std::string staple = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source, true, api), std::string); if (source.specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kInlineString) { throwEnvoyExceptionOrPanic("OCSP staple cannot be provided via inline_string"); @@ -29,17 +30,21 @@ static const std::string INLINE_STRING = ""; TlsCertificateConfigImpl::TlsCertificateConfigImpl( const envoy::extensions::transport_sockets::tls::v3::TlsCertificate& config, Server::Configuration::TransportSocketFactoryContext& factory_context, Api::Api& api) - : certificate_chain_(Config::DataSource::read(config.certificate_chain(), true, api)), + : certificate_chain_(THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.certificate_chain(), true, api), std::string)), certificate_chain_path_( Config::DataSource::getPath(config.certificate_chain()) .value_or(certificate_chain_.empty() ? EMPTY_STRING : INLINE_STRING)), - private_key_(Config::DataSource::read(config.private_key(), true, api)), + private_key_(THROW_OR_RETURN_VALUE(Config::DataSource::read(config.private_key(), true, api), + std::string)), private_key_path_(Config::DataSource::getPath(config.private_key()) .value_or(private_key_.empty() ? EMPTY_STRING : INLINE_STRING)), - pkcs12_(Config::DataSource::read(config.pkcs12(), true, api)), + pkcs12_( + THROW_OR_RETURN_VALUE(Config::DataSource::read(config.pkcs12(), true, api), std::string)), pkcs12_path_(Config::DataSource::getPath(config.pkcs12()) .value_or(pkcs12_.empty() ? EMPTY_STRING : INLINE_STRING)), - password_(Config::DataSource::read(config.password(), true, api)), + password_(THROW_OR_RETURN_VALUE(Config::DataSource::read(config.password(), true, api), + std::string)), password_path_(Config::DataSource::getPath(config.password()) .value_or(password_.empty() ? EMPTY_STRING : INLINE_STRING)), ocsp_staple_(readOcspStaple(config.ocsp_staple(), api)), diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index c8e969f0793b..d2de6db4d3c8 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -374,7 +374,8 @@ bool createWasm(const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scop stats_handler.onEvent(WasmEvent::RemoteLoadCacheMiss); } } else if (vm_config.code().has_local()) { - code = Config::DataSource::read(vm_config.code().local(), true, api); + code = THROW_OR_RETURN_VALUE(Config::DataSource::read(vm_config.code().local(), true, api), + std::string); source = Config::DataSource::getPath(vm_config.code().local()) .value_or(code.empty() ? EMPTY_STRING : INLINE_STRING); } diff --git a/source/extensions/compression/zstd/common/dictionary_manager.h b/source/extensions/compression/zstd/common/dictionary_manager.h index c70f2aba25b0..45501345e5ec 100644 --- a/source/extensions/compression/zstd/common/dictionary_manager.h +++ b/source/extensions/compression/zstd/common/dictionary_manager.h @@ -33,7 +33,8 @@ template class dictionary_map->reserve(dictionaries.size()); for (const auto& source : dictionaries) { - const auto data = Config::DataSource::read(source, false, api); + const auto data = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source, false, api), std::string); auto dictionary = DictionarySharedPtr(builder_(data.data(), data.length())); auto id = getDictId(dictionary.get()); // If id == 0, the dictionary is not conform to Zstd specification, or empty. diff --git a/source/extensions/filters/http/basic_auth/config.cc b/source/extensions/filters/http/basic_auth/config.cc index d7064730bccb..0b0332d8ec99 100644 --- a/source/extensions/filters/http/basic_auth/config.cc +++ b/source/extensions/filters/http/basic_auth/config.cc @@ -63,8 +63,9 @@ UserMap readHtpasswd(const std::string& htpasswd) { Http::FilterFactoryCb BasicAuthFilterFactory::createFilterFactoryFromProtoTyped( const BasicAuth& proto_config, const std::string& stats_prefix, Server::Configuration::FactoryContext& context) { - UserMap users = readHtpasswd( - Config::DataSource::read(proto_config.users(), false, context.serverFactoryContext().api())); + UserMap users = readHtpasswd(THROW_OR_RETURN_VALUE( + Config::DataSource::read(proto_config.users(), false, context.serverFactoryContext().api()), + std::string)); FilterConfigConstSharedPtr config = std::make_unique(std::move(users), stats_prefix, context.scope()); return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { diff --git a/source/extensions/filters/http/jwt_authn/filter_factory.cc b/source/extensions/filters/http/jwt_authn/filter_factory.cc index 5bb39e47477e..abbf4ac34d21 100644 --- a/source/extensions/filters/http/jwt_authn/filter_factory.cc +++ b/source/extensions/filters/http/jwt_authn/filter_factory.cc @@ -24,7 +24,8 @@ namespace { */ void validateJwtConfig(const JwtAuthentication& proto_config, Api::Api& api) { for (const auto& [name, provider] : proto_config.providers()) { - const auto inline_jwks = Config::DataSource::read(provider.local_jwks(), true, api); + const auto inline_jwks = THROW_OR_RETURN_VALUE( + Config::DataSource::read(provider.local_jwks(), true, api), std::string); if (!inline_jwks.empty()) { auto jwks_obj = Jwks::createFrom(inline_jwks, Jwks::JWKS); if (jwks_obj->getStatus() != Status::Ok) { diff --git a/source/extensions/filters/http/jwt_authn/jwks_cache.cc b/source/extensions/filters/http/jwt_authn/jwks_cache.cc index 818108701001..924e62cfc935 100644 --- a/source/extensions/filters/http/jwt_authn/jwks_cache.cc +++ b/source/extensions/filters/http/jwt_authn/jwks_cache.cc @@ -62,8 +62,10 @@ class JwksDataImpl : public JwksCache::JwksData, public Logger::Loggable(enable_jwt_cache, config, dispatcher.timeSource()); }); - const auto inline_jwks = Config::DataSource::read(jwt_provider_.local_jwks(), true, - context.serverFactoryContext().api()); + const auto inline_jwks = + THROW_OR_RETURN_VALUE(Config::DataSource::read(jwt_provider_.local_jwks(), true, + context.serverFactoryContext().api()), + std::string); if (!inline_jwks.empty()) { auto jwks = ::google::jwt_verify::Jwks::createFrom(inline_jwks, ::google::jwt_verify::Jwks::JWKS); diff --git a/source/extensions/filters/http/lua/lua_filter.cc b/source/extensions/filters/http/lua/lua_filter.cc index 0f4b53f22934..a941b889a505 100644 --- a/source/extensions/filters/http/lua/lua_filter.cc +++ b/source/extensions/filters/http/lua/lua_filter.cc @@ -808,15 +808,16 @@ FilterConfig::FilterConfig(const envoy::extensions::filters::http::lua::v3::Lua& "for the Lua filter."); } - const std::string code = - Config::DataSource::read(proto_config.default_source_code(), true, api); + const std::string code = THROW_OR_RETURN_VALUE( + Config::DataSource::read(proto_config.default_source_code(), true, api), std::string); default_lua_code_setup_ = std::make_unique(code, tls); } else if (!proto_config.inline_code().empty()) { default_lua_code_setup_ = std::make_unique(proto_config.inline_code(), tls); } for (const auto& source : proto_config.source_codes()) { - const std::string code = Config::DataSource::read(source.second, true, api); + const std::string code = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source.second, true, api), std::string); auto per_lua_code_setup_ptr = std::make_unique(code, tls); if (!per_lua_code_setup_ptr) { continue; @@ -834,7 +835,8 @@ FilterConfigPerRoute::FilterConfigPerRoute( return; } // Read and parse the inline Lua code defined in the route configuration. - const std::string code_str = Config::DataSource::read(config.source_code(), true, context.api()); + const std::string code_str = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.source_code(), true, context.api()), std::string); per_lua_code_setup_ptr_ = std::make_unique(code_str, context.threadLocal()); } diff --git a/source/extensions/filters/http/oauth2/filter.h b/source/extensions/filters/http/oauth2/filter.h index 3e86351fc98b..d874f9f1bf66 100644 --- a/source/extensions/filters/http/oauth2/filter.h +++ b/source/extensions/filters/http/oauth2/filter.h @@ -60,13 +60,15 @@ class SDSSecretReader : public SecretReader { Secret::GenericSecretConfigProviderSharedPtr& secret_provider, Api::Api& api) { const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api); + value = + THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), std::string); } return secret_provider->addUpdateCallback([secret_provider, &api, &value]() { const auto* secret = secret_provider->secret(); if (secret != nullptr) { - value = Config::DataSource::read(secret->secret(), true, api); + value = THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), + std::string); } }); } diff --git a/source/extensions/filters/network/direct_response/config.cc b/source/extensions/filters/network/direct_response/config.cc index 5d39492f590a..37f08ce2687b 100644 --- a/source/extensions/filters/network/direct_response/config.cc +++ b/source/extensions/filters/network/direct_response/config.cc @@ -25,8 +25,9 @@ class DirectResponseConfigFactory Network::FilterFactoryCb createFilterFactoryFromProtoTyped( const envoy::extensions::filters::network::direct_response::v3::Config& config, Server::Configuration::FactoryContext& context) override { - auto content = - Config::DataSource::read(config.response(), true, context.serverFactoryContext().api()); + auto content = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.response(), true, context.serverFactoryContext().api()), + std::string); return [content](Network::FilterManager& filter_manager) -> void { filter_manager.addReadFilter(std::make_shared(content)); diff --git a/source/extensions/filters/network/redis_proxy/config.h b/source/extensions/filters/network/redis_proxy/config.h index fc32de5163bc..ac2a873ed951 100644 --- a/source/extensions/filters/network/redis_proxy/config.h +++ b/source/extensions/filters/network/redis_proxy/config.h @@ -27,11 +27,11 @@ class ProtocolOptionsConfigImpl : public Upstream::ProtocolOptionsConfig { } std::string authUsername(Api::Api& api) const { - return Config::DataSource::read(auth_username_, true, api); + return THROW_OR_RETURN_VALUE(Config::DataSource::read(auth_username_, true, api), std::string); } std::string authPassword(Api::Api& api) const { - return Config::DataSource::read(auth_password_, true, api); + return THROW_OR_RETURN_VALUE(Config::DataSource::read(auth_password_, true, api), std::string); } static const std::string authUsername(const Upstream::ClusterInfoConstSharedPtr info, diff --git a/source/extensions/filters/network/redis_proxy/proxy_filter.cc b/source/extensions/filters/network/redis_proxy/proxy_filter.cc index eff64deae428..3426239991cd 100644 --- a/source/extensions/filters/network/redis_proxy/proxy_filter.cc +++ b/source/extensions/filters/network/redis_proxy/proxy_filter.cc @@ -24,8 +24,8 @@ ProxyFilterConfig::ProxyFilterConfig( : drain_decision_(drain_decision), runtime_(runtime), stat_prefix_(fmt::format("redis.{}.", config.stat_prefix())), stats_(generateStats(stat_prefix_, scope)), - downstream_auth_username_( - Config::DataSource::read(config.downstream_auth_username(), true, api)), + downstream_auth_username_(THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.downstream_auth_username(), true, api), std::string)), dns_cache_manager_(cache_manager_factory.get()), dns_cache_(getCache(config)) { if (config.settings().enable_redirection() && !config.settings().has_dns_cache_config()) { @@ -33,8 +33,8 @@ ProxyFilterConfig::ProxyFilterConfig( "dns_cache_config field within the connection pool settings to avoid them"); } - auto downstream_auth_password = - Config::DataSource::read(config.downstream_auth_password(), true, api); + auto downstream_auth_password = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.downstream_auth_password(), true, api), std::string); if (!downstream_auth_password.empty()) { downstream_auth_passwords_.emplace_back(downstream_auth_password); } @@ -43,7 +43,8 @@ ProxyFilterConfig::ProxyFilterConfig( downstream_auth_passwords_.reserve(downstream_auth_passwords_.size() + config.downstream_auth_passwords().size()); for (const auto& source : config.downstream_auth_passwords()) { - const auto p = Config::DataSource::read(source, true, api); + const auto p = + THROW_OR_RETURN_VALUE(Config::DataSource::read(source, true, api), std::string); if (!p.empty()) { downstream_auth_passwords_.emplace_back(p); } diff --git a/source/extensions/grpc_credentials/file_based_metadata/config.cc b/source/extensions/grpc_credentials/file_based_metadata/config.cc index 1fa300a2f711..266adbf96443 100644 --- a/source/extensions/grpc_credentials/file_based_metadata/config.cc +++ b/source/extensions/grpc_credentials/file_based_metadata/config.cc @@ -72,7 +72,8 @@ FileBasedMetadataAuthenticator::GetMetadata(grpc::string_ref, grpc::string_ref, // TODO(#14320): avoid using an exception here or find some way of doing this // in the main thread. TRY_NEEDS_AUDIT { - std::string header_value = Envoy::Config::DataSource::read(config_.secret_data(), true, api_); + std::string header_value = THROW_OR_RETURN_VALUE( + Envoy::Config::DataSource::read(config_.secret_data(), true, api_), std::string); metadata->insert(std::make_pair(header_key, header_prefix + header_value)); } END_TRY diff --git a/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc b/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc index 80c98d28b2a3..8f94f6373fe0 100644 --- a/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc +++ b/source/extensions/http/custom_response/local_response_policy/local_response_policy.cc @@ -21,9 +21,11 @@ LocalResponsePolicy::LocalResponsePolicy( const envoy::extensions::http::custom_response::local_response_policy::v3::LocalResponsePolicy& config, Server::Configuration::ServerFactoryContext& context) - : local_body_{config.has_body() ? absl::optional(Config::DataSource::read( - config.body(), true, context.api())) - : absl::optional{}}, + : local_body_{config.has_body() + ? absl::optional(THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.body(), true, context.api()), + std::string)) + : absl::optional{}}, status_code_{config.has_status_code() ? absl::optional( static_cast(config.status_code().value())) diff --git a/source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc b/source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc index c226a78c4a0a..5c33df45813b 100644 --- a/source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc +++ b/source/extensions/router/cluster_specifiers/lua/lua_cluster_specifier.cc @@ -48,7 +48,8 @@ LuaClusterSpecifierConfig::LuaClusterSpecifierConfig( Server::Configuration::CommonFactoryContext& context) : main_thread_dispatcher_(context.mainThreadDispatcher()), default_cluster_(config.default_cluster()) { - const std::string code_str = Config::DataSource::read(config.source_code(), true, context.api()); + const std::string code_str = THROW_OR_RETURN_VALUE( + Config::DataSource::read(config.source_code(), true, context.api()), std::string); per_lua_code_setup_ptr_ = std::make_unique(code_str, context.threadLocal()); } diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc b/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc index 0a5592df94f6..70ddb8defd92 100644 --- a/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc +++ b/source/extensions/tracers/opentelemetry/resource_detectors/environment/environment_resource_detector.cc @@ -30,7 +30,8 @@ Resource EnvironmentResourceDetector::detect() { std::string attributes_str = ""; TRY_NEEDS_AUDIT { - attributes_str = Config::DataSource::read(ds, true, context_.serverFactoryContext().api()); + attributes_str = THROW_OR_RETURN_VALUE( + Config::DataSource::read(ds, true, context_.serverFactoryContext().api()), std::string); } END_TRY catch (const EnvoyException& e) { ENVOY_LOG(warn, "Failed to detect resource attributes from the environment: {}.", e.what()); diff --git a/source/extensions/tracers/xray/config.cc b/source/extensions/tracers/xray/config.cc index 9421eb0678e0..805a900a1f3d 100644 --- a/source/extensions/tracers/xray/config.cc +++ b/source/extensions/tracers/xray/config.cc @@ -23,8 +23,10 @@ XRayTracerFactory::createTracerDriverTyped(const envoy::config::trace::v3::XRayC Server::Configuration::TracerFactoryContext& context) { std::string sampling_rules_json; TRY_NEEDS_AUDIT { - sampling_rules_json = Config::DataSource::read(proto_config.sampling_rule_manifest(), true, - context.serverFactoryContext().api()); + sampling_rules_json = + THROW_OR_RETURN_VALUE(Config::DataSource::read(proto_config.sampling_rule_manifest(), true, + context.serverFactoryContext().api()), + std::string); } END_TRY catch (EnvoyException& e) { ENVOY_LOG(error, "Failed to read sampling rules manifest because of {}.", e.what()); diff --git a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc index a5c67993712c..5ae1ebde5c55 100644 --- a/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc +++ b/source/extensions/transport_sockets/tls/cert_validator/spiffe/spiffe_validator.cc @@ -61,7 +61,8 @@ SPIFFEValidator::SPIFFEValidator(const Envoy::Ssl::CertificateValidationContextC "Multiple trust bundles are given for one trust domain for ", domain.name())); } - auto cert = Config::DataSource::read(domain.trust_bundle(), true, config->api()); + auto cert = THROW_OR_RETURN_VALUE( + Config::DataSource::read(domain.trust_bundle(), true, config->api()), std::string); bssl::UniquePtr bio(BIO_new_mem_buf(const_cast(cert.data()), cert.size())); RELEASE_ASSERT(bio != nullptr, ""); bssl::UniquePtr list( diff --git a/source/extensions/transport_sockets/tls/context_config_impl.cc b/source/extensions/transport_sockets/tls/context_config_impl.cc index 6322e594871f..b71736a12153 100644 --- a/source/extensions/transport_sockets/tls/context_config_impl.cc +++ b/source/extensions/transport_sockets/tls/context_config_impl.cc @@ -460,7 +460,8 @@ ServerContextConfigImpl::getSessionTicketKeys( const envoy::extensions::transport_sockets::tls::v3::TlsSessionTicketKeys& keys) { std::vector result; for (const auto& datasource : keys.keys()) { - result.emplace_back(getSessionTicketKey(Config::DataSource::read(datasource, false, api_))); + result.emplace_back(getSessionTicketKey( + THROW_OR_RETURN_VALUE(Config::DataSource::read(datasource, false, api_), std::string))); } return result; } diff --git a/test/common/config/datasource_test.cc b/test/common/config/datasource_test.cc index 070ff1631024..b58e5e0228d3 100644 --- a/test/common/config/datasource_test.cc +++ b/test/common/config/datasource_test.cc @@ -37,7 +37,6 @@ class AsyncDataSourceTest : public testing::Test { Event::TimerCb retry_timer_cb_; NiceMock request_{&cm_.thread_local_cluster_.async_client_}; - Config::DataSource::LocalAsyncDataProviderPtr local_data_provider_; Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; using AsyncClientSendFunc = std::function( - init_manager_, config.local(), true, *api_, [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, "xxxxxx"); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - - init_target_handle_->initialize(init_watcher_); - EXPECT_EQ(async_data, "xxxxxx"); -} - -TEST_F(AsyncDataSourceTest, LoadLocalEmptyDataSource) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - local: - inline_string: "" - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_local()); - - std::string async_data; - - EXPECT_CALL(init_manager_, add(_)).WillOnce(Invoke([this](const Init::Target& target) { - init_target_handle_ = target.createHandle("test"); - })); - - local_data_provider_ = std::make_unique( - init_manager_, config.local(), true, *api_, [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, ""); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - - init_target_handle_->initialize(init_watcher_); - EXPECT_EQ(async_data, ""); -} - TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceNoCluster) { AsyncDataSourcePb config; @@ -601,7 +539,7 @@ TEST(DataSourceTest, WellKnownEnvironmentVariableTest) { config.specifier_case()); EXPECT_EQ(config.environment_variable(), "PATH"); Api::ApiPtr api = Api::createApiForTest(); - const auto path_data = DataSource::read(config, false, *api); + const auto path_data = DataSource::read(config, false, *api).value(); EXPECT_FALSE(path_data.empty()); } @@ -618,10 +556,10 @@ TEST(DataSourceTest, MissingEnvironmentVariableTest) { config.specifier_case()); EXPECT_EQ(config.environment_variable(), "ThisVariableDoesntExist"); Api::ApiPtr api = Api::createApiForTest(); - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, false, *api), EnvoyException, - "Environment variable doesn't exist: ThisVariableDoesntExist"); - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, true, *api), EnvoyException, - "Environment variable doesn't exist: ThisVariableDoesntExist"); + EXPECT_EQ(DataSource::read(config, false, *api).status().message(), + "Environment variable doesn't exist: ThisVariableDoesntExist"); + EXPECT_EQ(DataSource::read(config, true, *api).status().message(), + "Environment variable doesn't exist: ThisVariableDoesntExist"); } TEST(DataSourceTest, EmptyEnvironmentVariableTest) { @@ -641,14 +579,13 @@ TEST(DataSourceTest, EmptyEnvironmentVariableTest) { Api::ApiPtr api = Api::createApiForTest(); #ifdef WIN32 // Windows doesn't support empty environment variables. - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, false, *api), EnvoyException, - "Environment variable doesn't exist: ThisVariableIsEmpty"); - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, true, *api), EnvoyException, - "Environment variable doesn't exist: ThisVariableIsEmpty"); + EXPECT_EQ(DataSource::read(config, false, *api).status().message(), + "Environment variable doesn't exist: ThisVariableIsEmpty"); + EXPECT_EQ(DataSource::read(config, true, *api).status().message(), + "Environment variable doesn't exist: ThisVariableIsEmpty"); #else - EXPECT_THROW_WITH_MESSAGE(DataSource::read(config, false, *api), EnvoyException, - "DataSource cannot be empty"); - const auto environment_variable = DataSource::read(config, true, *api); + EXPECT_EQ(DataSource::read(config, false, *api).status().message(), "DataSource cannot be empty"); + const auto environment_variable = DataSource::read(config, true, *api).value(); EXPECT_TRUE(environment_variable.empty()); #endif } diff --git a/test/common/router/config_impl_test.cc b/test/common/router/config_impl_test.cc index 32485d71500c..e867650b01e9 100644 --- a/test/common/router/config_impl_test.cc +++ b/test/common/router/config_impl_test.cc @@ -7570,9 +7570,10 @@ TEST_F(ConfigUtilityTest, ParseDirectResponseBody) { ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes).value()); route.mutable_direct_response()->mutable_body()->set_filename("missing_file"); - EXPECT_THROW_WITH_MESSAGE( - ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes).IgnoreError(), - EnvoyException, "file missing_file does not exist"); + EXPECT_EQ(ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes) + .status() + .message(), + "file missing_file does not exist"); // The default max body size in bytes is 4096 (MaxResponseBodySizeBytes). const std::string body(MaxResponseBodySizeBytes + 1, '*'); @@ -7587,10 +7588,10 @@ TEST_F(ConfigUtilityTest, ParseDirectResponseBody) { auto filename = TestEnvironment::writeStringToFileForTest("body", body); route.mutable_direct_response()->mutable_body()->set_filename(filename); expected_message = "file " + filename + " size is 4097 bytes; maximum is 2048"; - EXPECT_THROW_WITH_MESSAGE( - ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes / 2) - .IgnoreError(), - EnvoyException, expected_message); + EXPECT_EQ(ConfigUtility::parseDirectResponseBody(route, *api_, MaxResponseBodySizeBytes / 2) + .status() + .message(), + expected_message); // Update the max body size to 4098 bytes (MaxResponseBodySizeBytes + 2), hence the parsing is // successful. diff --git a/test/common/secret/sds_api_test.cc b/test/common/secret/sds_api_test.cc index 7ea7109e68e9..2cb819a4c02a 100644 --- a/test/common/secret/sds_api_test.cc +++ b/test/common/secret/sds_api_test.cc @@ -793,7 +793,7 @@ name: "encryption_key" const std::string secret_path = "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/aes_128_key"; EXPECT_EQ(TestEnvironment::readFileToStringForTest(TestEnvironment::substitute(secret_path)), - Config::DataSource::read(generic_secret.secret(), true, *api_)); + Config::DataSource::read(generic_secret.secret(), true, *api_).value()); } // Validate that SdsApi throws exception if an empty secret is passed to onConfigUpdate(). diff --git a/test/integration/sds_generic_secret_integration_test.cc b/test/integration/sds_generic_secret_integration_test.cc index 2d1b3aed08a9..0bd26ff6c3d2 100644 --- a/test/integration/sds_generic_secret_integration_test.cc +++ b/test/integration/sds_generic_secret_integration_test.cc @@ -31,8 +31,9 @@ class SdsGenericSecretTestFilter : public Http::StreamDecoderFilter { // Http::StreamDecoderFilter Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override { - headers.addCopy(Http::LowerCaseString("secret"), - Config::DataSource::read(config_provider_->secret()->secret(), true, api_)); + headers.addCopy( + Http::LowerCaseString("secret"), + Config::DataSource::read(config_provider_->secret()->secret(), true, api_).value()); return Http::FilterHeadersStatus::Continue; } diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index f0151c712844..bab4737b0ece 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -168,6 +168,9 @@ paths: - source/common/common/logger_delegates.cc - source/common/upstream/health_checker_event_logger.h - source/common/upstream/outlier_detection_impl.h + - source/common/ssl/certificate_validation_context_config_impl.cc + - source/common/grpc/google_grpc_creds_impl.cc + - source/common/local_reply/local_reply.cc # Only one C++ file should instantiate grpc_init grpc_init: From 6e71eb87e5d1c5b1853763afce64738bce13b586 Mon Sep 17 00:00:00 2001 From: Christoph Pakulski Date: Fri, 16 Feb 2024 09:03:42 -0500 Subject: [PATCH 032/151] upstream: API proposal for extensions to outlier detection (#31205) * API for defining HTTP errors, locally originated errors and database errors. Signed-off-by: Christoph Pakulski * Adjusted next free field. Signed-off-by: Christoph Pakulski * Use Any for monitor extensions. Moved proto for errors and consecutive errors monitor to envoy/extensions. Signed-off-by: Christoph Pakulski * Adjusted main api's BUILD file. Signed-off-by: Christoph Pakulski * Renamed common to error_types. Signed-off-by: Christoph Pakulski * Fixed docs. Signed-off-by: Christoph Pakulski * Used TypedExtensionConfig instead of user-define message. Signed-off-by: Christoph Pakulski * Redesign ErrorBucket to avoid using oneof. Signed-off-by: Christoph Pakulski * Renamed error buckets. Signed-off-by: Christoph Pakulski --------- Signed-off-by: Christoph Pakulski --- api/BUILD | 2 + .../config/cluster/v3/outlier_detection.proto | 8 +++- .../common/v3/BUILD | 12 +++++ .../common/v3/error_types.proto | 44 +++++++++++++++++++ .../consecutive_errors/v3/BUILD | 12 +++++ .../v3/consecutive_errors.proto | 34 ++++++++++++++ api/versioning/BUILD | 2 + 7 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 api/envoy/extensions/outlier_detection_monitors/common/v3/BUILD create mode 100644 api/envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto create mode 100644 api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/BUILD create mode 100644 api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/consecutive_errors.proto diff --git a/api/BUILD b/api/BUILD index 2eadff1f606f..45ad652f544b 100644 --- a/api/BUILD +++ b/api/BUILD @@ -292,6 +292,8 @@ proto_library( "//envoy/extensions/network/dns_resolver/cares/v3:pkg", "//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg", "//envoy/extensions/network/socket_interface/v3:pkg", + "//envoy/extensions/outlier_detection_monitors/common/v3:pkg", + "//envoy/extensions/outlier_detection_monitors/consecutive_errors/v3:pkg", "//envoy/extensions/path/match/uri_template/v3:pkg", "//envoy/extensions/path/rewrite/uri_template/v3:pkg", "//envoy/extensions/quic/connection_id_generator/v3:pkg", diff --git a/api/envoy/config/cluster/v3/outlier_detection.proto b/api/envoy/config/cluster/v3/outlier_detection.proto index 11289e26b4f4..622486e41cfe 100644 --- a/api/envoy/config/cluster/v3/outlier_detection.proto +++ b/api/envoy/config/cluster/v3/outlier_detection.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package envoy.config.cluster.v3; +import "envoy/config/core/v3/extension.proto"; + import "google/protobuf/duration.proto"; import "google/protobuf/wrappers.proto"; @@ -19,7 +21,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // See the :ref:`architecture overview ` for // more information on outlier detection. -// [#next-free-field: 24] +// [#next-free-field: 25] message OutlierDetection { option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.cluster.OutlierDetection"; @@ -167,4 +169,8 @@ message OutlierDetection { // To change this default behavior set this config to ``false`` where active health checking will not uneject the host. // Defaults to true. google.protobuf.BoolValue successful_active_health_check_uneject_host = 23; + + // Set of host's passive monitors. + // [#not-implemented-hide:] + repeated core.v3.TypedExtensionConfig monitors = 24; } diff --git a/api/envoy/extensions/outlier_detection_monitors/common/v3/BUILD b/api/envoy/extensions/outlier_detection_monitors/common/v3/BUILD new file mode 100644 index 000000000000..ef19132f9180 --- /dev/null +++ b/api/envoy/extensions/outlier_detection_monitors/common/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/type/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto b/api/envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto new file mode 100644 index 000000000000..7ea14dde2839 --- /dev/null +++ b/api/envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; + +package envoy.extensions.outlier_detection_monitors.common.v3; + +import "envoy/type/v3/range.proto"; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.outlier_detection_monitors.common.v3"; +option java_outer_classname = "ErrorTypesProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/outlier_detection_monitors/common/v3;commonv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Outlier detection error buckets] +// Error bucket for HTTP codes. +// [#not-implemented-hide:] +message HttpErrors { + type.v3.Int32Range range = 1; +} + +// Error bucket for locally originated errors. +// [#not-implemented-hide:] +message LocalOriginErrors { +} + +// Error bucket for database errors. +// Sub-parameters may be added later, like malformed response, error on write, etc. +// [#not-implemented-hide:] +message DatabaseErrors { +} + +// Union of possible error buckets. +// [#not-implemented-hide:] +message ErrorBuckets { + // List of buckets "catching" HTTP codes. + repeated HttpErrors http_errors = 1; + + // List of buckets "catching" locally originated errors. + repeated LocalOriginErrors local_origin_errors = 2; + + // List of buckets "catching" database errors. + repeated DatabaseErrors database_errors = 3; +} diff --git a/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/BUILD b/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/BUILD new file mode 100644 index 000000000000..60022c6b3a07 --- /dev/null +++ b/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/BUILD @@ -0,0 +1,12 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "//envoy/extensions/outlier_detection_monitors/common/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + ], +) diff --git a/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/consecutive_errors.proto b/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/consecutive_errors.proto new file mode 100644 index 000000000000..8c74f6b574c7 --- /dev/null +++ b/api/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3/consecutive_errors.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package envoy.extensions.outlier_detection_monitors.consecutive_errors.v3; + +import "envoy/extensions/outlier_detection_monitors/common/v3/error_types.proto"; + +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.outlier_detection_monitors.consecutive_errors.v3"; +option java_outer_classname = "ConsecutiveErrorsProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/outlier_detection_monitors/consecutive_errors/v3;consecutive_errorsv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// Monitor which counts consecutive errors. +// If number of consecutive errors exceeds the threshold, monitor will report that the host +// is unhealthy. +// [#not-implemented-hide:] +message ConsecutiveErrors { + // Monitor name. + string name = 1; + + // The number of consecutive errors before ejection occurs. + google.protobuf.UInt32Value threshold = 2 [(validate.rules).uint32 = {lte: 100}]; + + // The % chance that a host is actually ejected. Defaults to 100. + google.protobuf.UInt32Value enforcing = 3 [(validate.rules).uint32 = {lte: 100}]; + + // Error buckets. + common.v3.ErrorBuckets error_buckets = 4; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index 706d7d6f9d96..edb89aa6fe28 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -231,6 +231,8 @@ proto_library( "//envoy/extensions/network/dns_resolver/cares/v3:pkg", "//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg", "//envoy/extensions/network/socket_interface/v3:pkg", + "//envoy/extensions/outlier_detection_monitors/common/v3:pkg", + "//envoy/extensions/outlier_detection_monitors/consecutive_errors/v3:pkg", "//envoy/extensions/path/match/uri_template/v3:pkg", "//envoy/extensions/path/rewrite/uri_template/v3:pkg", "//envoy/extensions/quic/connection_id_generator/v3:pkg", From d99f4aaa8fdfcc5d7bdbae891ac70fbfd42965c9 Mon Sep 17 00:00:00 2001 From: yanavlasov Date: Fri, 16 Feb 2024 09:47:29 -0500 Subject: [PATCH 033/151] Disable test/common/router:route_fuzz_test under fuzz-coverage (#32402) Disable a fuzz test under fuzz-coverage that uses a statically linked binary that can no longer link with coverage instrumentation due to size. Risk Level: Low Testing: Unit Test Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Yan Avlasov --- test/common/router/BUILD | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/common/router/BUILD b/test/common/router/BUILD index 23037518759d..3a5d4e8ec5c8 100644 --- a/test/common/router/BUILD +++ b/test/common/router/BUILD @@ -276,6 +276,8 @@ envoy_cc_fuzz_test( size = "large", srcs = ["route_fuzz_test.cc"], corpus = ":route_corpus", + # The :config_impl_test_static target does not build with coverage + tags = ["nocoverage"], deps = [ ":route_fuzz_proto_cc_proto", "//source/common/router:config_lib", From 4994e59af25898d56f496fece3ed24dacbdfa87b Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Fri, 16 Feb 2024 16:40:19 +0100 Subject: [PATCH 034/151] Configurable heap memory release rate (#30353) --------- Signed-off-by: Kateryna Nezdolii --- api/envoy/config/bootstrap/v3/bootstrap.proto | 16 +- source/common/memory/BUILD | 5 + source/common/memory/stats.cc | 146 +++++++++++------- source/common/memory/stats.h | 50 ++++++ source/server/server.cc | 4 +- source/server/server.h | 4 +- test/common/memory/BUILD | 12 ++ test/common/memory/memory_release_test.cc | 139 +++++++++++++++++ 8 files changed, 316 insertions(+), 60 deletions(-) create mode 100644 test/common/memory/memory_release_test.cc diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index b5f36f273bcc..5e0cab225582 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -41,7 +41,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // ` for more detail. // Bootstrap :ref:`configuration overview `. -// [#next-free-field: 41] +// [#next-free-field: 42] message Bootstrap { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v2.Bootstrap"; @@ -411,6 +411,9 @@ message Bootstrap { // Optional gRPC async manager config. GrpcAsyncClientManagerConfig grpc_async_client_manager_config = 40; + + // Optional configuration for memory allocation manager. + MemoryAllocatorManager memory_allocator_manager = 41; } // Administration interface :ref:`operations documentation @@ -734,3 +737,14 @@ message CustomInlineHeader { // The type of the header that is expected to be set as the inline header. InlineHeaderType inline_header_type = 2 [(validate.rules).enum = {defined_only: true}]; } + +message MemoryAllocatorManager { + // Configures tcmalloc to perform background release of free memory in amount of bytes per ``memory_release_interval`` interval. + // If equals to ``0``, no memory release will occur. Defaults to ``0``. + uint64 bytes_to_release = 1 [(validate.rules).uint64 = {gte: 1}]; + + // Interval in milliseconds for memory releasing. If specified, during every + // interval Envoy will try to ``release bytes_to_release`` of free memory back to operating system for reuse. + // Defaults to 1000 milliseconds. + google.protobuf.Duration memory_release_interval = 2; +} diff --git a/source/common/memory/BUILD b/source/common/memory/BUILD index e6e528c96000..efd001d8d3da 100644 --- a/source/common/memory/BUILD +++ b/source/common/memory/BUILD @@ -14,7 +14,12 @@ envoy_cc_library( hdrs = ["stats.h"], tcmalloc_dep = 1, deps = [ + "//envoy/stats:stats_macros", + "//source/common/common:assert_lib", "//source/common/common:logger_lib", + "//source/common/common:thread_lib", + "//source/common/protobuf:utility_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], ) diff --git a/source/common/memory/stats.cc b/source/common/memory/stats.cc index 3c3abb00aae4..c306210b727c 100644 --- a/source/common/memory/stats.cc +++ b/source/common/memory/stats.cc @@ -2,121 +2,155 @@ #include +#include "source/common/common/assert.h" #include "source/common/common/logger.h" #if defined(TCMALLOC) - #include "tcmalloc/malloc_extension.h" +#elif defined(GPERFTOOLS_TCMALLOC) +#include "gperftools/malloc_extension.h" +#endif namespace Envoy { namespace Memory { uint64_t Stats::totalCurrentlyAllocated() { +#if defined(TCMALLOC) return tcmalloc::MallocExtension::GetNumericProperty("generic.current_allocated_bytes") .value_or(0); +#elif defined(GPERFTOOLS_TCMALLOC) + size_t value = 0; + MallocExtension::instance()->GetNumericProperty("generic.current_allocated_bytes", &value); + return value; +#else + return 0; +#endif } uint64_t Stats::totalCurrentlyReserved() { +#if defined(TCMALLOC) // In Google's tcmalloc the semantics of generic.heap_size has // changed: it doesn't include unmapped bytes. return tcmalloc::MallocExtension::GetNumericProperty("generic.heap_size").value_or(0) + tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") .value_or(0); -} - -uint64_t Stats::totalThreadCacheBytes() { - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.current_total_thread_cache_bytes") - .value_or(0); -} - -uint64_t Stats::totalPageHeapFree() { - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_free_bytes").value_or(0); -} - -uint64_t Stats::totalPageHeapUnmapped() { - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") - .value_or(0); -} - -uint64_t Stats::totalPhysicalBytes() { - return tcmalloc::MallocExtension::GetProperties()["generic.physical_memory_used"].value; -} - -void Stats::dumpStatsToLog() { - ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", tcmalloc::MallocExtension::GetStats()); -} - -} // namespace Memory -} // namespace Envoy - #elif defined(GPERFTOOLS_TCMALLOC) - -#include "gperftools/malloc_extension.h" - -namespace Envoy { -namespace Memory { - -uint64_t Stats::totalCurrentlyAllocated() { - size_t value = 0; - MallocExtension::instance()->GetNumericProperty("generic.current_allocated_bytes", &value); - return value; -} - -uint64_t Stats::totalCurrentlyReserved() { size_t value = 0; MallocExtension::instance()->GetNumericProperty("generic.heap_size", &value); return value; +#else + return 0; +#endif } uint64_t Stats::totalThreadCacheBytes() { +#if defined(TCMALLOC) + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.current_total_thread_cache_bytes") + .value_or(0); +#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.current_total_thread_cache_bytes", &value); return value; +#else + return 0; +#endif } uint64_t Stats::totalPageHeapFree() { +#if defined(TCMALLOC) + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_free_bytes").value_or(0); +#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.pageheap_free_bytes", &value); return value; +#else + return 0; +#endif } uint64_t Stats::totalPageHeapUnmapped() { +#if defined(TCMALLOC) + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") + .value_or(0); +#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.pageheap_unmapped_bytes", &value); return value; +#else + return 0; +#endif } uint64_t Stats::totalPhysicalBytes() { +#if defined(TCMALLOC) + return tcmalloc::MallocExtension::GetProperties()["generic.physical_memory_used"].value; +#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("generic.total_physical_bytes", &value); return value; +#else + return 0; +#endif } void Stats::dumpStatsToLog() { +#if defined(TCMALLOC) + ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", tcmalloc::MallocExtension::GetStats()); +#elif defined(GPERFTOOLS_TCMALLOC) constexpr int buffer_size = 100000; auto buffer = std::make_unique(buffer_size); MallocExtension::instance()->GetStats(buffer.get(), buffer_size); ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", buffer.get()); +#else + return; +#endif } -} // namespace Memory -} // namespace Envoy - +void AllocatorManager::tcmallocRelease() { +#if defined(TCMALLOC) + tcmalloc::MallocExtension::ReleaseMemoryToSystem(bytes_to_release_); +#elif defined(GPERFTOOLS_TCMALLOC) + MallocExtension::instance()->ReleaseToSystem(bytes_to_release_); #else + return; +#endif +} -namespace Envoy { -namespace Memory { - -uint64_t Stats::totalCurrentlyAllocated() { return 0; } -uint64_t Stats::totalThreadCacheBytes() { return 0; } -uint64_t Stats::totalCurrentlyReserved() { return 0; } -uint64_t Stats::totalPageHeapUnmapped() { return 0; } -uint64_t Stats::totalPageHeapFree() { return 0; } -uint64_t Stats::totalPhysicalBytes() { return 0; } -void Stats::dumpStatsToLog() {} +/** + * Configures tcmalloc release rate from the page heap. If `bytes_to_release_` + * has been initialized to `0`, no heap memory will be released in background. + */ +void AllocatorManager::configureBackgroundMemoryRelease(Api::Api& api) { + RELEASE_ASSERT(!tcmalloc_thread_, "Invalid state, tcmalloc has already been initialised"); + if (bytes_to_release_ > 0) { + tcmalloc_routine_dispatcher_ = api.allocateDispatcher(std::string(TCMALLOC_ROUTINE_THREAD_ID)); + memory_release_timer_ = tcmalloc_routine_dispatcher_->createTimer([this]() -> void { + const uint64_t unmapped_bytes_before_release = Stats::totalPageHeapUnmapped(); + tcmallocRelease(); + const uint64_t unmapped_bytes_after_release = Stats::totalPageHeapUnmapped(); + if (unmapped_bytes_after_release > unmapped_bytes_before_release) { + // Only increment stats if memory was actually released. As tcmalloc releases memory on a + // span granularity, during some release rounds there may be no memory released, if during + // past round too much memory was released. + // https://github.com/google/tcmalloc/blob/master/tcmalloc/tcmalloc.cc#L298 + allocator_manager_stats_.released_by_timer_.inc(); + } + memory_release_timer_->enableTimer(memory_release_interval_msec_); + }); + tcmalloc_thread_ = api.threadFactory().createThread( + [this]() -> void { + ENVOY_LOG_MISC(debug, "Started {}", TCMALLOC_ROUTINE_THREAD_ID); + memory_release_timer_->enableTimer(memory_release_interval_msec_); + tcmalloc_routine_dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); + }, + Thread::Options{std::string(TCMALLOC_ROUTINE_THREAD_ID)}); + ENVOY_LOG_MISC( + info, fmt::format( + "Configured tcmalloc with background release rate: {} bytes per {} milliseconds", + bytes_to_release_, memory_release_interval_msec_.count())); + } +} } // namespace Memory } // namespace Envoy - -#endif // #if defined(TCMALLOC) diff --git a/source/common/memory/stats.h b/source/common/memory/stats.h index 71bc261ae175..03c0b0a0dba0 100644 --- a/source/common/memory/stats.h +++ b/source/common/memory/stats.h @@ -2,9 +2,24 @@ #include +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/stats/store.h" + +#include "source/common/common/thread.h" +#include "source/common/protobuf/utility.h" + namespace Envoy { + +#define MEMORY_ALLOCATOR_MANAGER_STATS(COUNTER) COUNTER(released_by_timer) + +struct MemoryAllocatorManagerStats { + MEMORY_ALLOCATOR_MANAGER_STATS(GENERATE_COUNTER_STRUCT) +}; + namespace Memory { +constexpr absl::string_view TCMALLOC_ROUTINE_THREAD_ID = "TcmallocProcessBackgroundActions"; + /** * Runtime stats for process memory usage. */ @@ -51,5 +66,40 @@ class Stats { static void dumpStatsToLog(); }; +class AllocatorManager { +public: + AllocatorManager(Api::Api& api, Envoy::Stats::Scope& scope, + const envoy::config::bootstrap::v3::MemoryAllocatorManager& config) + : bytes_to_release_(config.bytes_to_release()), + memory_release_interval_msec_(std::chrono::milliseconds( + PROTOBUF_GET_MS_OR_DEFAULT(config, memory_release_interval, 1000))), + allocator_manager_stats_(MemoryAllocatorManagerStats{ + MEMORY_ALLOCATOR_MANAGER_STATS(POOL_COUNTER_PREFIX(scope, "tcmalloc."))}) { + configureBackgroundMemoryRelease(api); + }; + + ~AllocatorManager() { + if (tcmalloc_routine_dispatcher_) { + tcmalloc_routine_dispatcher_->exit(); + } + if (tcmalloc_thread_) { + tcmalloc_thread_->join(); + tcmalloc_thread_.reset(); + } + } + +private: + const uint64_t bytes_to_release_; + const std::chrono::milliseconds memory_release_interval_msec_; + MemoryAllocatorManagerStats allocator_manager_stats_; + Thread::ThreadPtr tcmalloc_thread_; + Event::DispatcherPtr tcmalloc_routine_dispatcher_; + Event::TimerPtr memory_release_timer_; + void configureBackgroundMemoryRelease(Api::Api& api); + void tcmallocRelease(); + // Used for testing. + friend class AllocatorManagerPeer; +}; + } // namespace Memory } // namespace Envoy diff --git a/source/server/server.cc b/source/server/server.cc index 5b73a573b9c6..cbcd3ed80b80 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -35,7 +35,6 @@ #include "source/common/http/codes.h" #include "source/common/http/headers.h" #include "source/common/local_info/local_info_impl.h" -#include "source/common/memory/stats.h" #include "source/common/network/address_impl.h" #include "source/common/network/dns_resolver/dns_factory_util.h" #include "source/common/network/socket_interface.h" @@ -522,6 +521,9 @@ void InstanceBase::initializeOrThrow(Network::Address::InstanceConstSharedPtr lo server_stats_->dynamic_unknown_fields_, server_stats_->wip_protos_); + memory_allocator_ = std::make_unique( + *api_, *stats_store_.rootScope(), bootstrap_.memory_allocator_manager()); + initialization_timer_ = std::make_unique( server_stats_->initialization_time_ms_, timeSource()); server_stats_->concurrency_.set(options_.concurrency()); diff --git a/source/server/server.h b/source/server/server.h index dcfd5be7e5a9..48a56e1171a7 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -32,6 +32,7 @@ #include "source/common/grpc/context_impl.h" #include "source/common/http/context_impl.h" #include "source/common/init/manager_impl.h" +#include "source/common/memory/stats.h" #include "source/common/protobuf/message_validator_impl.h" #include "source/common/quic/quic_stat_names.h" #include "source/common/router/context_impl.h" @@ -336,7 +337,6 @@ class InstanceBase : Logger::Loggable, Stage stage, std::function completion_cb = [] {}); void onRuntimeReady(); void onClusterManagerPrimaryInitializationComplete(); - using LifecycleNotifierCallbacks = std::list; using LifecycleNotifierCompletionCallbacks = std::list; @@ -414,8 +414,8 @@ class InstanceBase : Logger::Loggable, ServerFactoryContextImpl server_contexts_; bool enable_reuse_port_default_{false}; Regex::EnginePtr regex_engine_; - bool stats_flush_in_progress_ : 1; + std::unique_ptr memory_allocator_; template class LifecycleCallbackHandle : public ServerLifecycleNotifier::Handle, RaiiListElement { diff --git a/test/common/memory/BUILD b/test/common/memory/BUILD index 3123d827b949..368cd14bf420 100644 --- a/test/common/memory/BUILD +++ b/test/common/memory/BUILD @@ -14,6 +14,18 @@ envoy_cc_test( deps = ["//source/common/memory:stats_lib"], ) +envoy_cc_test( + name = "memory_release_test", + srcs = ["memory_release_test.cc"], + deps = [ + "//source/common/event:dispatcher_lib", + "//source/common/memory:stats_lib", + "//test/common/stats:stat_test_utility_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:utility_lib", + ], +) + envoy_cc_test( name = "heap_shrinker_test", srcs = ["heap_shrinker_test.cc"], diff --git a/test/common/memory/memory_release_test.cc b/test/common/memory/memory_release_test.cc new file mode 100644 index 000000000000..041233baebd1 --- /dev/null +++ b/test/common/memory/memory_release_test.cc @@ -0,0 +1,139 @@ +#include "source/common/event/dispatcher_impl.h" +#include "source/common/memory/stats.h" + +#include "test/common/stats/stat_test_utility.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Memory { + +class AllocatorManagerPeer { +public: + static std::chrono::milliseconds + memoryReleaseInterval(const AllocatorManager& allocator_manager) { + return allocator_manager.memory_release_interval_msec_; + } + static uint64_t bytesToRelease(const AllocatorManager& allocator_manager) { + return allocator_manager.bytes_to_release_; + } +}; + +namespace { + +static const int MB = 1048576; + +class MemoryReleaseTest : public testing::Test { +protected: + MemoryReleaseTest() + : api_(Api::createApiForTest(stats_, time_system_)), + dispatcher_("test_thread", *api_, time_system_), scope_("memory_release_test.", stats_) {} + + void initialiseAllocatorManager(uint64_t bytes_to_release, float release_interval_s) { + const std::string yaml_config = (release_interval_s > 0) + ? fmt::format(R"EOF( + bytes_to_release: {} + memory_release_interval: {}s +)EOF", + bytes_to_release, release_interval_s) + : fmt::format(R"EOF( + bytes_to_release: {} +)EOF", + bytes_to_release); + const auto proto_config = + TestUtility::parseYaml(yaml_config); + allocator_manager_ = std::make_unique(*api_, scope_, proto_config); + } + + void step(const std::chrono::milliseconds& step) { time_system_.advanceTimeWait(step); } + + Envoy::Stats::TestUtil::TestStore stats_; + Event::SimulatedTimeSystem time_system_; + Api::ApiPtr api_; + Event::DispatcherImpl dispatcher_; + Envoy::Stats::TestUtil::TestScope scope_; + std::unique_ptr allocator_manager_; +}; + +TEST_F(MemoryReleaseTest, ReleaseRateAboveZeroDefaultIntervalMemoryReleased) { + size_t initial_allocated_bytes = Stats::totalCurrentlyAllocated(); + auto a = std::make_unique(MB); + auto b = std::make_unique(MB); + if (Stats::totalCurrentlyAllocated() <= initial_allocated_bytes) { + GTEST_SKIP() << "Skipping test, cannot measure memory usage precisely on this platform."; + } + auto initial_unmapped_bytes = Stats::totalPageHeapUnmapped(); + EXPECT_LOG_CONTAINS( + "info", + "Configured tcmalloc with background release rate: 1048576 bytes per 1000 milliseconds", + initialiseAllocatorManager(MB /*bytes per second*/, 0)); + EXPECT_EQ(MB, AllocatorManagerPeer::bytesToRelease(*allocator_manager_)); + EXPECT_EQ(std::chrono::milliseconds(1000), + AllocatorManagerPeer::memoryReleaseInterval(*allocator_manager_)); + a.reset(); + // Release interval was configured to default value (1 second). + step(std::chrono::milliseconds(1000)); + TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 1UL, + time_system_); + auto released_bytes_before_next_run = Stats::totalPageHeapUnmapped(); + b.reset(); + step(std::chrono::milliseconds(1000)); + TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 2UL, + time_system_); + auto final_released_bytes = Stats::totalPageHeapUnmapped(); + +#if defined(TCMALLOC) || defined(GPERFTOOLS_TCMALLOC) + EXPECT_LT(released_bytes_before_next_run, final_released_bytes); + EXPECT_LT(initial_unmapped_bytes, final_released_bytes); +#else + EXPECT_LE(released_bytes_before_next_run, final_released_bytes); + EXPECT_LE(initial_unmapped_bytes, final_released_bytes); +#endif +} + +TEST_F(MemoryReleaseTest, ReleaseRateZeroNoRelease) { + auto a = std::make_unique(MB); + EXPECT_LOG_NOT_CONTAINS( + "info", "Configured tcmalloc with background release rate: 0 bytes 1000 milliseconds", + initialiseAllocatorManager(0 /*bytes per second*/, 0)); + a.reset(); + // Release interval was configured to default value (1 second). + step(std::chrono::milliseconds(3000)); + EXPECT_EQ(0UL, stats_.counter("memory_release_test.tcmalloc.released_by_timer").value()); +} + +TEST_F(MemoryReleaseTest, ReleaseRateAboveZeroCustomIntervalMemoryReleased) { + size_t initial_allocated_bytes = Stats::totalCurrentlyAllocated(); + auto a = std::make_unique(40 * MB); + auto b = std::make_unique(40 * MB); + if (Stats::totalCurrentlyAllocated() <= initial_allocated_bytes) { + GTEST_SKIP() << "Skipping test, cannot measure memory usage precisely on this platform."; + } + auto initial_unmapped_bytes = Stats::totalPageHeapUnmapped(); + EXPECT_LOG_CONTAINS( + "info", + "Configured tcmalloc with background release rate: 16777216 bytes per 2000 milliseconds", + initialiseAllocatorManager(16 * MB /*bytes per second*/, 2)); + EXPECT_EQ(16 * MB, AllocatorManagerPeer::bytesToRelease(*allocator_manager_)); + EXPECT_EQ(std::chrono::milliseconds(2000), + AllocatorManagerPeer::memoryReleaseInterval(*allocator_manager_)); + a.reset(); + step(std::chrono::milliseconds(2000)); + b.reset(); + step(std::chrono::milliseconds(2000)); + TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 2UL, + time_system_); + auto final_released_bytes = Stats::totalPageHeapUnmapped(); +#if defined(TCMALLOC) || defined(GPERFTOOLS_TCMALLOC) + EXPECT_LT(initial_unmapped_bytes, final_released_bytes); +#else + EXPECT_LE(initial_unmapped_bytes, final_released_bytes); +#endif +} + +} // namespace +} // namespace Memory +} // namespace Envoy From c11574972860a40de36acf3ab8d930273f5ece65 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 16:52:24 +0000 Subject: [PATCH 035/151] deps: Bump `com_github_intel_qatzip` -> 1.2.0 (#32362) Created by Envoy dependency bot for @phlax Fix #32274 Signed-off-by: Ryan Northey Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 1aaa11ebc889..6ee8c9f333c2 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -455,12 +455,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "qatzip", project_desc = "Intel QuickAssist Technology QATzip Library", project_url = "https://github.com/intel/qatzip", - version = "1.1.2", - sha256 = "31419fa4b42d217b3e55a70a34545582cbf401a4f4d44738d21b4a3944b1e1ef", + version = "1.2.0", + sha256 = "345a37a7e5b70635cadf9b9e581a9affec09e837ac1abac04d38355b672b3304", strip_prefix = "QATzip-{version}", urls = ["https://github.com/intel/QATzip/archive/v{version}.tar.gz"], use_category = ["dataplane_ext"], - release_date = "2023-03-24", + release_date = "2024-02-08", extensions = ["envoy.compression.qatzip.compressor"], cpe = "N/A", license = "BSD-3-Clause", From e10d2538b43b99ba3a92fe4d414cc43748581187 Mon Sep 17 00:00:00 2001 From: birenroy Date: Fri, 16 Feb 2024 18:02:05 -0500 Subject: [PATCH 036/151] deps: updates QUICHE from 2c1f10f46 to 9e8759380 (#32446) Update QUICHE from 2c1f10f46 to 9e8759380 https://github.com/google/quiche/compare/2c1f10f46..9e8759380 ``` $ git log 2c1f10f46..9e8759380 --date=short --no-merges --format="%ad %al %s" 2024-02-15 birenroy Fixes handling of DATA frame padding in OgHttp2Session. 2024-02-15 birenroy Adds a test case demonstrating that OgHttp2Adapter does not consider DATA frame padding as "consumed". 2024-02-14 quiche-dev Fix issues when building more Quiche tests for iOS and Windows 2024-02-14 martinduke Fix test flake in MoqtSubscribeWindows. 2024-02-14 martinduke Remove #ifdef MOQT_AUTH_INFO. Also eliminate an old reference to subscribe_request 2024-02-14 martinduke Fix error in MoQT Relative Location encoding. 2024-02-14 quiche-dev Prospectively fix Chromium build 2024-02-13 quiche-dev No public description ``` Signed-off-by: Biren Roy --- bazel/repository_locations.bzl | 6 +++--- changelogs/current.yaml | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 6ee8c9f333c2..91b635e1a8cd 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1175,12 +1175,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "2c1f10f46ce16d42fcf692d324799d859e8478cc", - sha256 = "8754391ff9d75aa1ff3dddbb57c73830c63932f01cfb30d4ca158720b2eadeba", + version = "9e875938052443c7f51b25012093be59e20598ac", + sha256 = "ae1d45c62b1a0e43384697c1becd8f8d3bf6624ba0dd05a0334b319aafecb1ef", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2024-02-13", + release_date = "2024-02-15", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index d980de92bc1f..9e35431cbdd9 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -105,6 +105,9 @@ bug_fixes: change: | Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. +- area: deps + change: | + Updated QUICHE dependencies to incorporate fixes for https://github.com/envoyproxy/envoy/issues/32401. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` From 678de631676e30a86f330b9d5aa9305a22c733b4 Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Sat, 17 Feb 2024 01:25:14 +0200 Subject: [PATCH 037/151] tcp_proxy: add per-connection idle timeout override by filter state (#32422) --------- Signed-off-by: ohadvano --- .../network/tcp_proxy/v3/tcp_proxy.proto | 2 + changelogs/current.yaml | 5 ++ .../advanced/well_known_filter_state.rst | 5 ++ .../common/stream_info/uint64_accessor_impl.h | 2 + source/common/tcp_proxy/BUILD | 1 + source/common/tcp_proxy/tcp_proxy.cc | 48 +++++++++++---- source/common/tcp_proxy/tcp_proxy.h | 10 +++- .../stream_info/uint64_accessor_impl_test.cc | 8 +++ test/common/tcp_proxy/tcp_proxy_test.cc | 60 +++++++++++++++++++ 9 files changed, 129 insertions(+), 12 deletions(-) diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index d9f0db325373..b0f7602a8e08 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -184,6 +184,8 @@ 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, the default idle timeout is 1 hour. If set // to 0s, the timeout will be disabled. + // It is possible to dynamically override this configuration by setting a per-connection filter + // state object for the key ``envoy.tcp_proxy.per_connection_idle_timeout_ms``. // // .. warning:: // Disabling this timeout has a highly likelihood of yielding connection leaks due to lost TCP diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9e35431cbdd9..e7f8bc3329f5 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -198,6 +198,11 @@ new_features: - area: redis change: | Added support for the ``ECHO`` command. +- area: tcp_proxy + change: | + added an option to dynamically set a per downstream connection idle timeout period object under the key + ``envoy.tcp_proxy.per_connection_idle_timeout_ms``. If this filter state value exists, it will override the idle timeout + specified in the filter configuration and the default idle timeout. deprecated: - area: listener diff --git a/docs/root/configuration/advanced/well_known_filter_state.rst b/docs/root/configuration/advanced/well_known_filter_state.rst index 50c4f3476077..f238dd207854 100644 --- a/docs/root/configuration/advanced/well_known_filter_state.rst +++ b/docs/root/configuration/advanced/well_known_filter_state.rst @@ -63,6 +63,11 @@ The following lists the filter state object keys used by the Envoy extensions: A special generic string object factory, to be used as a :ref:`factory lookup key `. +``envoy.tcp_proxy.per_connection_idle_timeout_ms`` + :ref:`TCP proxy idle timeout duration + ` override on a per-connection + basis. Accepts a count of milliseconds number string as a constructor. + Filter state object fields -------------------------- diff --git a/source/common/stream_info/uint64_accessor_impl.h b/source/common/stream_info/uint64_accessor_impl.h index 5859263135ab..ff14bbe7844c 100644 --- a/source/common/stream_info/uint64_accessor_impl.h +++ b/source/common/stream_info/uint64_accessor_impl.h @@ -19,6 +19,8 @@ class UInt64AccessorImpl : public UInt64Accessor { return message; } + absl::optional serializeAsString() const override { return std::to_string(value_); } + // From UInt64Accessor. void increment() override { value_++; } uint64_t value() const override { return value_; } diff --git a/source/common/tcp_proxy/BUILD b/source/common/tcp_proxy/BUILD index 07d5c5995d29..4d1309a5a4b7 100644 --- a/source/common/tcp_proxy/BUILD +++ b/source/common/tcp_proxy/BUILD @@ -78,6 +78,7 @@ envoy_cc_library( "//source/common/router:metadatamatchcriteria_lib", "//source/common/stream_info:stream_id_provider_lib", "//source/common/stream_info:stream_info_lib", + "//source/common/stream_info:uint64_accessor_lib", "//source/common/upstream:load_balancer_lib", "//source/extensions/upstreams/tcp/generic:config", "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index 4f87dd6ae37a..4e49e16597a8 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -34,6 +34,7 @@ #include "source/common/network/upstream_socket_options_filter_state.h" #include "source/common/router/metadatamatchcriteria_impl.h" #include "source/common/stream_info/stream_id_provider_impl.h" +#include "source/common/stream_info/uint64_accessor_impl.h" namespace Envoy { namespace TcpProxy { @@ -53,6 +54,22 @@ class PerConnectionClusterFactory : public StreamInfo::FilterState::ObjectFactor REGISTER_FACTORY(PerConnectionClusterFactory, StreamInfo::FilterState::ObjectFactory); +class PerConnectionIdleTimeoutMsObjectFactory : public StreamInfo::FilterState::ObjectFactory { +public: + std::string name() const override { return std::string(PerConnectionIdleTimeoutMs); } + std::unique_ptr + createFromBytes(absl::string_view data) const override { + uint64_t duration_in_milliseconds = 0; + if (absl::SimpleAtoi(data, &duration_in_milliseconds)) { + return std::make_unique(duration_in_milliseconds); + } + + return nullptr; + } +}; + +REGISTER_FACTORY(PerConnectionIdleTimeoutMsObjectFactory, StreamInfo::FilterState::ObjectFactory); + Config::SimpleRouteImpl::SimpleRouteImpl(const Config& parent, absl::string_view cluster_name) : parent_(parent), cluster_name_(cluster_name) {} @@ -755,7 +772,7 @@ void Filter::onDownstreamEvent(Network::ConnectionEvent event) { conn_data->connection().state() != Network::Connection::State::Closed) { config_->drainManager().add(config_->sharedConfig(), std::move(conn_data), std::move(upstream_callbacks_), std::move(idle_timer_), - read_callbacks_->upstreamHost()); + idle_timeout_, read_callbacks_->upstreamHost()); } if (event == Network::ConnectionEvent::LocalClose || event == Network::ConnectionEvent::RemoteClose) { @@ -829,7 +846,15 @@ void Filter::onUpstreamConnection() { read_callbacks_->connection(), getStreamInfo().downstreamAddressProvider().requestedServerName()); - if (config_->idleTimeout()) { + idle_timeout_ = config_->idleTimeout(); + if (const auto* per_connection_idle_timeout = + getStreamInfo().filterState()->getDataReadOnly( + PerConnectionIdleTimeoutMs); + per_connection_idle_timeout != nullptr) { + idle_timeout_ = std::chrono::milliseconds(per_connection_idle_timeout->value()); + } + + if (idle_timeout_) { // The idle_timer_ can be moved to a Drainer, so related callbacks call into // the UpstreamCallbacks, which has the same lifetime as the timer, and can dispatch // the call to either TcpProxy or to Drainer, depending on the current state. @@ -905,8 +930,8 @@ void Filter::disableAccessLogFlushTimer() { void Filter::resetIdleTimer() { if (idle_timer_ != nullptr) { - ASSERT(config_->idleTimeout()); - idle_timer_->enableTimer(config_->idleTimeout().value()); + ASSERT(idle_timeout_); + idle_timer_->enableTimer(idle_timeout_.value()); } } @@ -943,9 +968,10 @@ void UpstreamDrainManager::add(const Config::SharedConfigSharedPtr& config, Tcp::ConnectionPool::ConnectionDataPtr&& upstream_conn_data, const std::shared_ptr& callbacks, Event::TimerPtr&& idle_timer, + absl::optional idle_timeout, const Upstream::HostDescriptionConstSharedPtr& upstream_host) { DrainerPtr drainer(new Drainer(*this, config, callbacks, std::move(upstream_conn_data), - std::move(idle_timer), upstream_host)); + std::move(idle_timer), idle_timeout, upstream_host)); callbacks->drain(*drainer); // Use temporary to ensure we get the pointer before we move it out of drainer @@ -963,9 +989,11 @@ void UpstreamDrainManager::remove(Drainer& drainer, Event::Dispatcher& dispatche Drainer::Drainer(UpstreamDrainManager& parent, const Config::SharedConfigSharedPtr& config, const std::shared_ptr& callbacks, Tcp::ConnectionPool::ConnectionDataPtr&& conn_data, Event::TimerPtr&& idle_timer, + absl::optional idle_timeout, const Upstream::HostDescriptionConstSharedPtr& upstream_host) : parent_(parent), callbacks_(callbacks), upstream_conn_data_(std::move(conn_data)), - timer_(std::move(idle_timer)), upstream_host_(upstream_host), config_(config) { + idle_timer_(std::move(idle_timer)), idle_timeout_(idle_timeout), + upstream_host_(upstream_host), config_(config) { ENVOY_CONN_LOG(trace, "draining the upstream connection", upstream_conn_data_->connection()); config_->stats().upstream_flush_total_.inc(); config_->stats().upstream_flush_active_.inc(); @@ -974,8 +1002,8 @@ Drainer::Drainer(UpstreamDrainManager& parent, const Config::SharedConfigSharedP void Drainer::onEvent(Network::ConnectionEvent event) { if (event == Network::ConnectionEvent::RemoteClose || event == Network::ConnectionEvent::LocalClose) { - if (timer_ != nullptr) { - timer_->disableTimer(); + if (idle_timer_ != nullptr) { + idle_timer_->disableTimer(); } config_->stats().upstream_flush_active_.dec(); parent_.remove(*this, upstream_conn_data_->connection().dispatcher()); @@ -998,8 +1026,8 @@ void Drainer::onIdleTimeout() { } void Drainer::onBytesSent() { - if (timer_ != nullptr) { - timer_->enableTimer(config_->idleTimeout().value()); + if (idle_timer_ != nullptr) { + idle_timer_->enableTimer(idle_timeout_.value()); } } diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 82ebcb8fb9d9..a7b5e491191d 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -38,6 +38,9 @@ namespace Envoy { namespace TcpProxy { +constexpr absl::string_view PerConnectionIdleTimeoutMs = + "envoy.tcp_proxy.per_connection_idle_timeout_ms"; + /** * All tcp proxy stats. @see stats_macros.h */ @@ -547,6 +550,7 @@ class Filter : public Network::ReadFilter, Router::MetadataMatchCriteriaConstPtr metadata_match_criteria_; Network::TransportSocketOptionsConstSharedPtr transport_socket_options_; Network::Socket::OptionsSharedPtr upstream_options_; + absl::optional idle_timeout_; uint32_t connect_attempts_{}; bool connecting_{}; bool downstream_closed_{}; @@ -560,6 +564,7 @@ class Drainer : public Event::DeferredDeletable, protected Logger::Loggable& callbacks, Tcp::ConnectionPool::ConnectionDataPtr&& conn_data, Event::TimerPtr&& idle_timer, + absl::optional idle_timeout, const Upstream::HostDescriptionConstSharedPtr& upstream_host); void onEvent(Network::ConnectionEvent event); @@ -573,7 +578,8 @@ class Drainer : public Event::DeferredDeletable, protected Logger::Loggable callbacks_; Tcp::ConnectionPool::ConnectionDataPtr upstream_conn_data_; - Event::TimerPtr timer_; + Event::TimerPtr idle_timer_; + absl::optional idle_timeout_; Upstream::HostDescriptionConstSharedPtr upstream_host_; Config::SharedConfigSharedPtr config_; }; @@ -586,7 +592,7 @@ class UpstreamDrainManager : public ThreadLocal::ThreadLocalObject { void add(const Config::SharedConfigSharedPtr& config, Tcp::ConnectionPool::ConnectionDataPtr&& upstream_conn_data, const std::shared_ptr& callbacks, - Event::TimerPtr&& idle_timer, + Event::TimerPtr&& idle_timer, absl::optional idle_timeout, const Upstream::HostDescriptionConstSharedPtr& upstream_host); void remove(Drainer& drainer, Event::Dispatcher& dispatcher); diff --git a/test/common/stream_info/uint64_accessor_impl_test.cc b/test/common/stream_info/uint64_accessor_impl_test.cc index 8f143429339e..876700191938 100644 --- a/test/common/stream_info/uint64_accessor_impl_test.cc +++ b/test/common/stream_info/uint64_accessor_impl_test.cc @@ -31,6 +31,14 @@ TEST(UInt64AccessorImplTest, TestProto) { EXPECT_EQ(init_value, uint64_struct->value()); } +TEST(UInt64AccessorImplTest, TestString) { + uint64_t init_value = 0xdeadbeefdeadbeef; + UInt64AccessorImpl accessor(init_value); + absl::optional value = accessor.serializeAsString(); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value, std::to_string(init_value)); +} + } // namespace } // namespace StreamInfo } // namespace Envoy diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 41a2ce805c99..32376a18b31d 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -21,6 +21,7 @@ #include "source/common/network/upstream_socket_options_filter_state.h" #include "source/common/network/win32_redirect_records_option_impl.h" #include "source/common/router/metadatamatchcriteria_impl.h" +#include "source/common/stream_info/uint64_accessor_impl.h" #include "source/common/tcp_proxy/tcp_proxy.h" #include "source/common/upstream/upstream_impl.h" @@ -815,6 +816,65 @@ TEST_F(TcpProxyTest, UpstreamConnectionLimit) { EXPECT_EQ(access_log_data_, "UO"); } +TEST_F(TcpProxyTest, IdleTimeoutObjectFactory) { + const std::string name = "envoy.tcp_proxy.per_connection_idle_timeout_ms"; + auto* factory = + Registry::FactoryRegistry::getFactory(name); + ASSERT_NE(nullptr, factory); + EXPECT_EQ(name, factory->name()); + const std::string duration_in_milliseconds = std::to_string(1234); + auto object = factory->createFromBytes(duration_in_milliseconds); + ASSERT_NE(nullptr, object); + EXPECT_EQ(duration_in_milliseconds, object->serializeAsString()); +} + +TEST_F(TcpProxyTest, InvalidIdleTimeoutObjectFactory) { + const std::string name = "envoy.tcp_proxy.per_connection_idle_timeout_ms"; + auto* factory = + Registry::FactoryRegistry::getFactory(name); + ASSERT_NE(nullptr, factory); + EXPECT_EQ(name, factory->name()); + ASSERT_EQ(nullptr, factory->createFromBytes("not_a_number")); +} + +TEST_F(TcpProxyTest, IdleTimeoutWithFilterStateOverride) { + envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy config = defaultConfig(); + config.mutable_idle_timeout()->set_seconds(1); + setup(1, config); + + uint64_t idle_timeout_override = 5000; + + // Although the configured idle timeout is 1 second, overriding the value through filter state + // so the expected idle timeout is 5 seconds instead. + filter_callbacks_.connection_.streamInfo().filterState()->setData( + TcpProxy::PerConnectionIdleTimeoutMs, + std::make_unique(idle_timeout_override), + StreamInfo::FilterState::StateType::ReadOnly, StreamInfo::FilterState::LifeSpan::Connection); + + Event::MockTimer* idle_timer = new Event::MockTimer(&filter_callbacks_.connection_.dispatcher_); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(idle_timeout_override), _)); + raiseEventUpstreamConnected(0); + + Buffer::OwnedImpl buffer("hello"); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(idle_timeout_override), _)); + filter_->onData(buffer, false); + + buffer.add("hello2"); + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(idle_timeout_override), _)); + upstream_callbacks_->onUpstreamData(buffer, false); + + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(idle_timeout_override), _)); + filter_callbacks_.connection_.raiseBytesSentCallbacks(1); + + EXPECT_CALL(*idle_timer, enableTimer(std::chrono::milliseconds(idle_timeout_override), _)); + upstream_connections_.at(0)->raiseBytesSentCallbacks(2); + + EXPECT_CALL(*upstream_connections_.at(0), close(Network::ConnectionCloseType::NoFlush, _)); + EXPECT_CALL(filter_callbacks_.connection_, close(Network::ConnectionCloseType::NoFlush, _)); + EXPECT_CALL(*idle_timer, disableTimer()); + idle_timer->invokeCallback(); +} + // Tests that the idle timer closes both connections, and gets updated when either // connection has activity. TEST_F(TcpProxyTest, IdleTimeout) { From f301eebf7acc680e27e03396a1be6be77e1ae3a5 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Fri, 16 Feb 2024 19:05:21 -0500 Subject: [PATCH 038/151] mobile: Add Apple proxy resolution APIs to the C++ EngineBuilder (#32426) Previously, Apple proxy resolution API registration only happened in the Objective-C engine. For those Apple/iOS users using the C++ APIs directly, we need to support proxy resolution API registration in the C++ EngineBuilder as well. This commit also moves the Objective-C API registration to the same place where other API registration (e.g. key-value store) takes place. Lastly, to add tests for this, we had to add the Apple CFNetwork and CoreFoundation frameworks to the linkopts, which required envoy_cc_test being able to take in an optional linkopts to add to its defaults. Signed-off-by: Ali Beyad --- bazel/envoy_test.bzl | 3 ++- mobile/library/cc/BUILD | 7 ++++++- mobile/library/cc/engine_builder.cc | 17 +++++++++++++++++ mobile/library/cc/engine_builder.h | 12 ++++++++++++ mobile/library/objective-c/EnvoyConfiguration.h | 2 ++ .../library/objective-c/EnvoyConfiguration.mm | 3 +++ mobile/library/objective-c/EnvoyEngine.h | 5 +---- mobile/library/objective-c/EnvoyEngineImpl.mm | 11 +++++------ mobile/library/swift/EngineBuilder.swift | 7 ++++--- .../library/swift/mocks/MockEnvoyEngine.swift | 3 +-- mobile/test/cc/unit/BUILD | 8 ++++++++ mobile/test/common/integration/BUILD | 8 ++++++++ .../integration/client_integration_test.cc | 8 ++++++++ 13 files changed, 77 insertions(+), 17 deletions(-) diff --git a/bazel/envoy_test.bzl b/bazel/envoy_test.bzl index 73908fc6f1b3..fe01c07d90d6 100644 --- a/bazel/envoy_test.bzl +++ b/bazel/envoy_test.bzl @@ -154,6 +154,7 @@ def envoy_cc_test( tags = [], args = [], copts = [], + linkopts = [], condition = None, shard_count = None, coverage = True, @@ -170,7 +171,7 @@ def envoy_cc_test( data = data, copts = envoy_copts(repository, test = True) + copts + envoy_pch_copts(repository, "//test:test_pch"), additional_linker_inputs = envoy_exported_symbols_input(), - linkopts = _envoy_test_linkopts(), + linkopts = _envoy_test_linkopts() + linkopts, linkstatic = envoy_linkstatic(), malloc = tcmalloc_external_dep(repository), deps = envoy_stdlib_deps() + deps + [envoy_external_dep_path(dep) for dep in external_deps + ["googletest"]] + [ diff --git a/mobile/library/cc/BUILD b/mobile/library/cc/BUILD index 96f01b436fcd..516f8c372a5e 100644 --- a/mobile/library/cc/BUILD +++ b/mobile/library/cc/BUILD @@ -47,7 +47,12 @@ envoy_cc_library( "@envoy_mobile//library/common/extensions/filters/http/network_configuration:filter_cc_proto", "@envoy_mobile//library/common/extensions/filters/http/socket_tag:filter_cc_proto", "@envoy_mobile//library/common/types:matcher_data_lib", - ], + ] + select({ + "@envoy//bazel:apple": [ + "@envoy_mobile//library/common/network:apple_proxy_resolution_lib", + ], + "//conditions:default": [], + }), ) envoy_cc_library( diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 9406fc2b9cc3..96bf8b0a0b33 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -35,6 +35,10 @@ #include "library/common/extensions/filters/http/socket_tag/filter.pb.h" #include "library/common/extensions/key_value/platform/platform.pb.h" +#if defined(__APPLE__) +#include "library/common/network/apple_proxy_resolution.h" +#endif + namespace Envoy { namespace Platform { @@ -374,6 +378,13 @@ EngineBuilder& EngineBuilder::setRuntimeGuard(std::string guard, bool value) { return *this; } +#if defined(__APPLE__) +EngineBuilder& EngineBuilder::respectSystemProxySettings(bool value) { + respect_system_proxy_settings_ = value; + return *this; +} +#endif + std::unique_ptr EngineBuilder::generateBootstrap() const { // The yaml utilities have non-relevant thread asserts. Thread::SkipAsserts skip; @@ -894,6 +905,12 @@ EngineSharedPtr EngineBuilder::build() { } } +#if defined(__APPLE__) + if (respect_system_proxy_settings_) { + registerAppleProxyResolver(); + } +#endif + Engine* engine = new Engine(envoy_engine); auto options = std::make_unique(); diff --git a/mobile/library/cc/engine_builder.h b/mobile/library/cc/engine_builder.h index 8db9dc45f0f2..8a77b8d9ad5e 100644 --- a/mobile/library/cc/engine_builder.h +++ b/mobile/library/cc/engine_builder.h @@ -190,6 +190,14 @@ class EngineBuilder { EngineBuilder& addKeyValueStore(std::string name, KeyValueStoreSharedPtr key_value_store); EngineBuilder& addStringAccessor(std::string name, StringAccessorSharedPtr accessor); +#if defined(__APPLE__) + // Right now, this API is only used by Apple (iOS) to register the Apple proxy resolver API for + // use in reading and using the system proxy settings. + // If/when we move Android system proxy registration to the C++ Engine Builder, we will make this + // API available on all platforms. + EngineBuilder& respectSystemProxySettings(bool value); +#endif + // This is separated from build() for the sake of testability virtual std::unique_ptr generateBootstrap() const; @@ -242,6 +250,10 @@ class EngineBuilder { std::vector quic_suffixes_; bool enable_port_migration_ = false; bool always_use_v6_ = false; +#if defined(__APPLE__) + // TODO(abeyad): once stable, consider setting the default to true. + bool respect_system_proxy_settings_ = false; +#endif int dns_min_refresh_seconds_ = 60; int max_connections_per_host_ = 7; diff --git a/mobile/library/objective-c/EnvoyConfiguration.h b/mobile/library/objective-c/EnvoyConfiguration.h index d38a25d23a82..d6932bfc89f4 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.h +++ b/mobile/library/objective-c/EnvoyConfiguration.h @@ -32,6 +32,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, assign) BOOL enforceTrustChainVerification; @property (nonatomic, assign) BOOL forceIPv6; @property (nonatomic, assign) BOOL enablePlatformCertificateValidation; +@property (nonatomic, assign) BOOL respectSystemProxySettings; @property (nonatomic, assign) UInt32 h2ConnectionKeepaliveIdleIntervalMilliseconds; @property (nonatomic, assign) UInt32 h2ConnectionKeepaliveTimeoutSeconds; @property (nonatomic, assign) UInt32 maxConnectionsPerHost; @@ -81,6 +82,7 @@ NS_ASSUME_NONNULL_BEGIN enforceTrustChainVerification:(BOOL)enforceTrustChainVerification forceIPv6:(BOOL)forceIPv6 enablePlatformCertificateValidation:(BOOL)enablePlatformCertificateValidation + respectSystemProxySettings:(BOOL)respectSystemProxySettings h2ConnectionKeepaliveIdleIntervalMilliseconds: (UInt32)h2ConnectionKeepaliveIdleIntervalMilliseconds h2ConnectionKeepaliveTimeoutSeconds:(UInt32)h2ConnectionKeepaliveTimeoutSeconds diff --git a/mobile/library/objective-c/EnvoyConfiguration.mm b/mobile/library/objective-c/EnvoyConfiguration.mm index 02b8cf8a253b..2a2b481ac5d0 100644 --- a/mobile/library/objective-c/EnvoyConfiguration.mm +++ b/mobile/library/objective-c/EnvoyConfiguration.mm @@ -85,6 +85,7 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds enforceTrustChainVerification:(BOOL)enforceTrustChainVerification forceIPv6:(BOOL)forceIPv6 enablePlatformCertificateValidation:(BOOL)enablePlatformCertificateValidation + respectSystemProxySettings:(BOOL)respectSystemProxySettings h2ConnectionKeepaliveIdleIntervalMilliseconds: (UInt32)h2ConnectionKeepaliveIdleIntervalMilliseconds h2ConnectionKeepaliveTimeoutSeconds:(UInt32)h2ConnectionKeepaliveTimeoutSeconds @@ -143,6 +144,7 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds self.enforceTrustChainVerification = enforceTrustChainVerification; self.forceIPv6 = forceIPv6; self.enablePlatformCertificateValidation = enablePlatformCertificateValidation; + self.respectSystemProxySettings = respectSystemProxySettings; self.h2ConnectionKeepaliveIdleIntervalMilliseconds = h2ConnectionKeepaliveIdleIntervalMilliseconds; self.h2ConnectionKeepaliveTimeoutSeconds = h2ConnectionKeepaliveTimeoutSeconds; @@ -236,6 +238,7 @@ - (instancetype)initWithConnectTimeoutSeconds:(UInt32)connectTimeoutSeconds builder.setAppId([self.appId toCXXString]); builder.setDeviceOs("iOS"); builder.enablePlatformCertificatesValidation(self.enablePlatformCertificateValidation); + builder.respectSystemProxySettings(self.respectSystemProxySettings); builder.enableDnsCache(self.enableDNSCache, self.dnsCacheSaveIntervalSeconds); if (self.nodeRegion != nil) { diff --git a/mobile/library/objective-c/EnvoyEngine.h b/mobile/library/objective-c/EnvoyEngine.h index bb765e0a470a..bddcd0775507 100644 --- a/mobile/library/objective-c/EnvoyEngine.h +++ b/mobile/library/objective-c/EnvoyEngine.h @@ -33,14 +33,11 @@ NS_ASSUME_NONNULL_BEGIN @param logger Logging interface. @param eventTracker Event tracking interface. @param networkMonitoringMode Configure how the engines observe network reachability. - @param respectSystemProxySettings Whether to respect system proxy settings when performing - network requests. */ - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning logger:(nullable void (^)(NSInteger, NSString *))logger eventTracker:(nullable void (^)(EnvoyEvent *))eventTracker - networkMonitoringMode:(int)networkMonitoringMode - respectSystemProxySettings:(BOOL)respectSystemProxySettings; + networkMonitoringMode:(int)networkMonitoringMode; /** Run the Envoy engine with the provided configuration and log level. diff --git a/mobile/library/objective-c/EnvoyEngineImpl.mm b/mobile/library/objective-c/EnvoyEngineImpl.mm index 0395f1b8641b..57f9f3fb2b9f 100644 --- a/mobile/library/objective-c/EnvoyEngineImpl.mm +++ b/mobile/library/objective-c/EnvoyEngineImpl.mm @@ -407,8 +407,7 @@ @implementation EnvoyEngineImpl { - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning logger:(nullable void (^)(NSInteger, NSString *))logger eventTracker:(nullable void (^)(EnvoyEvent *))eventTracker - networkMonitoringMode:(int)networkMonitoringMode - respectSystemProxySettings:(BOOL)respectSystemProxySettings { + networkMonitoringMode:(int)networkMonitoringMode { self = [super init]; if (!self) { return nil; @@ -439,10 +438,6 @@ - (instancetype)initWithRunningCallback:(nullable void (^)())onEngineRunning _engine = new Envoy::InternalEngine(native_callbacks, native_logger, native_event_tracker); _engineHandle = reinterpret_cast(_engine); - if (respectSystemProxySettings) { - registerAppleProxyResolver(); - } - if (networkMonitoringMode == 1) { [_networkMonitor startReachability]; } else if (networkMonitoringMode == 2) { @@ -518,6 +513,10 @@ - (void)performRegistrationsForConfig:(EnvoyConfiguration *)config { for (NSString *name in config.keyValueStores) { [self registerKeyValueStore:name keyValueStore:config.keyValueStores[name]]; } + + if (config.respectSystemProxySettings) { + registerAppleProxyResolver(); + } } - (int)runWithConfig:(EnvoyConfiguration *)config logLevel:(NSString *)logLevel { diff --git a/mobile/library/swift/EngineBuilder.swift b/mobile/library/swift/EngineBuilder.swift index 7a754d517c8a..a71949aa2986 100644 --- a/mobile/library/swift/EngineBuilder.swift +++ b/mobile/library/swift/EngineBuilder.swift @@ -154,9 +154,9 @@ open class EngineBuilder: NSObject { private var quicHints: [String: Int] = [:] private var quicCanonicalSuffixes: [String] = [] private var enableInterfaceBinding: Bool = false - private var respectSystemProxySettings: Bool = false private var enforceTrustChainVerification: Bool = true private var enablePlatformCertificateValidation: Bool = false + private var respectSystemProxySettings: Bool = false private var enableDrainPostDnsRefresh: Bool = false private var forceIPv6: Bool = false private var h2ConnectionKeepaliveIdleIntervalMilliseconds: UInt32 = 1 @@ -717,8 +717,7 @@ open class EngineBuilder: NSObject { } }, eventTracker: self.eventTracker, - networkMonitoringMode: Int32(self.monitoringMode.rawValue), - respectSystemProxySettings: self.respectSystemProxySettings) + networkMonitoringMode: Int32(self.monitoringMode.rawValue)) let config = self.makeConfig() #if canImport(EnvoyCxxSwiftInterop) if self.enableSwiftBootstrap { @@ -794,6 +793,7 @@ open class EngineBuilder: NSObject { enforceTrustChainVerification: self.enforceTrustChainVerification, forceIPv6: self.forceIPv6, enablePlatformCertificateValidation: self.enablePlatformCertificateValidation, + respectSystemProxySettings: self.respectSystemProxySettings, h2ConnectionKeepaliveIdleIntervalMilliseconds: self.h2ConnectionKeepaliveIdleIntervalMilliseconds, h2ConnectionKeepaliveTimeoutSeconds: self.h2ConnectionKeepaliveTimeoutSeconds, @@ -865,6 +865,7 @@ private extension EngineBuilder { cxxBuilder.enforceTrustChainVerification(self.enforceTrustChainVerification) cxxBuilder.setForceAlwaysUsev6(self.forceIPv6) cxxBuilder.enablePlatformCertificatesValidation(self.enablePlatformCertificateValidation) + cxxBuilder.respectSystemProxySettings(self.respectSystemProxySettings) cxxBuilder.addH2ConnectionKeepaliveIdleIntervalMilliseconds( Int32(self.h2ConnectionKeepaliveIdleIntervalMilliseconds) ) diff --git a/mobile/library/swift/mocks/MockEnvoyEngine.swift b/mobile/library/swift/mocks/MockEnvoyEngine.swift index f02a3cf8d6c2..057699ef05a0 100644 --- a/mobile/library/swift/mocks/MockEnvoyEngine.swift +++ b/mobile/library/swift/mocks/MockEnvoyEngine.swift @@ -5,8 +5,7 @@ import Foundation final class MockEnvoyEngine: NSObject { init(runningCallback onEngineRunning: (() -> Void)? = nil, logger: ((Int, String) -> Void)? = nil, - eventTracker: (([String: String]) -> Void)? = nil, networkMonitoringMode: Int32 = 0, - respectSystemProxySettings: Bool = false) {} + eventTracker: (([String: String]) -> Void)? = nil, networkMonitoringMode: Int32 = 0) {} /// Closure called when `run(withConfig:)` is called. static var onRunWithConfig: ((_ config: EnvoyConfiguration, _ logLevel: String?) -> Void)? diff --git a/mobile/test/cc/unit/BUILD b/mobile/test/cc/unit/BUILD index 6087469f874c..212204bf519c 100644 --- a/mobile/test/cc/unit/BUILD +++ b/mobile/test/cc/unit/BUILD @@ -11,6 +11,14 @@ envoy_cc_test( ["envoy_config_test.cc"], "@envoy", ), + linkopts = select({ + "@envoy//bazel:apple": [ + # For the TestProxyResolutionApi test. + "-Wl,-framework,CoreFoundation", + "-Wl,-framework,CFNetwork", + ], + "//conditions:default": [], + }), repository = "@envoy", deps = [ "//library/cc:engine_builder_lib", diff --git a/mobile/test/common/integration/BUILD b/mobile/test/common/integration/BUILD index 3b65dd43cbad..eb8f61380637 100644 --- a/mobile/test/common/integration/BUILD +++ b/mobile/test/common/integration/BUILD @@ -18,6 +18,14 @@ envoy_cc_test( # TODO(willengflow): Remove this once the sandboxNetwork=off works for ipv4 localhost addresses. "sandboxNetwork": "standard", }, + linkopts = select({ + "@envoy//bazel:apple": [ + # For the TestProxyResolutionApi test. + "-Wl,-framework,CoreFoundation", + "-Wl,-framework,CFNetwork", + ], + "//conditions:default": [], + }), repository = "@envoy", shard_count = 6, deps = [ diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index a3934d66eb3b..8e441f61ce42 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -1020,5 +1020,13 @@ TEST_P(ClientIntegrationTest, TestStats) { } } +#if defined(__APPLE__) +TEST_P(ClientIntegrationTest, TestProxyResolutionApi) { + builder_.respectSystemProxySettings(true); + initialize(); + ASSERT_TRUE(Envoy::Api::External::retrieveApi("envoy_proxy_resolver") != nullptr); +} +#endif + } // namespace } // namespace Envoy From 295e925766b7ebfa813a021892c1d54a41d06e11 Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Sat, 17 Feb 2024 21:35:51 +0100 Subject: [PATCH 039/151] Revert "Configurable heap memory release rate (#30353)" (#32453) This reverts commit 4994e59af25898d56f496fece3ed24dacbdfa87b. Signed-off-by: Kateryna Nezdolii --- api/envoy/config/bootstrap/v3/bootstrap.proto | 16 +- source/common/memory/BUILD | 5 - source/common/memory/stats.cc | 146 +++++++----------- source/common/memory/stats.h | 50 ------ source/server/server.cc | 4 +- source/server/server.h | 4 +- test/common/memory/BUILD | 12 -- test/common/memory/memory_release_test.cc | 139 ----------------- 8 files changed, 60 insertions(+), 316 deletions(-) delete mode 100644 test/common/memory/memory_release_test.cc diff --git a/api/envoy/config/bootstrap/v3/bootstrap.proto b/api/envoy/config/bootstrap/v3/bootstrap.proto index 5e0cab225582..b5f36f273bcc 100644 --- a/api/envoy/config/bootstrap/v3/bootstrap.proto +++ b/api/envoy/config/bootstrap/v3/bootstrap.proto @@ -41,7 +41,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // ` for more detail. // Bootstrap :ref:`configuration overview `. -// [#next-free-field: 42] +// [#next-free-field: 41] message Bootstrap { option (udpa.annotations.versioning).previous_message_type = "envoy.config.bootstrap.v2.Bootstrap"; @@ -411,9 +411,6 @@ message Bootstrap { // Optional gRPC async manager config. GrpcAsyncClientManagerConfig grpc_async_client_manager_config = 40; - - // Optional configuration for memory allocation manager. - MemoryAllocatorManager memory_allocator_manager = 41; } // Administration interface :ref:`operations documentation @@ -737,14 +734,3 @@ message CustomInlineHeader { // The type of the header that is expected to be set as the inline header. InlineHeaderType inline_header_type = 2 [(validate.rules).enum = {defined_only: true}]; } - -message MemoryAllocatorManager { - // Configures tcmalloc to perform background release of free memory in amount of bytes per ``memory_release_interval`` interval. - // If equals to ``0``, no memory release will occur. Defaults to ``0``. - uint64 bytes_to_release = 1 [(validate.rules).uint64 = {gte: 1}]; - - // Interval in milliseconds for memory releasing. If specified, during every - // interval Envoy will try to ``release bytes_to_release`` of free memory back to operating system for reuse. - // Defaults to 1000 milliseconds. - google.protobuf.Duration memory_release_interval = 2; -} diff --git a/source/common/memory/BUILD b/source/common/memory/BUILD index efd001d8d3da..e6e528c96000 100644 --- a/source/common/memory/BUILD +++ b/source/common/memory/BUILD @@ -14,12 +14,7 @@ envoy_cc_library( hdrs = ["stats.h"], tcmalloc_dep = 1, deps = [ - "//envoy/stats:stats_macros", - "//source/common/common:assert_lib", "//source/common/common:logger_lib", - "//source/common/common:thread_lib", - "//source/common/protobuf:utility_lib", - "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", ], ) diff --git a/source/common/memory/stats.cc b/source/common/memory/stats.cc index c306210b727c..3c3abb00aae4 100644 --- a/source/common/memory/stats.cc +++ b/source/common/memory/stats.cc @@ -2,155 +2,121 @@ #include -#include "source/common/common/assert.h" #include "source/common/common/logger.h" #if defined(TCMALLOC) + #include "tcmalloc/malloc_extension.h" -#elif defined(GPERFTOOLS_TCMALLOC) -#include "gperftools/malloc_extension.h" -#endif namespace Envoy { namespace Memory { uint64_t Stats::totalCurrentlyAllocated() { -#if defined(TCMALLOC) return tcmalloc::MallocExtension::GetNumericProperty("generic.current_allocated_bytes") .value_or(0); -#elif defined(GPERFTOOLS_TCMALLOC) - size_t value = 0; - MallocExtension::instance()->GetNumericProperty("generic.current_allocated_bytes", &value); - return value; -#else - return 0; -#endif } uint64_t Stats::totalCurrentlyReserved() { -#if defined(TCMALLOC) // In Google's tcmalloc the semantics of generic.heap_size has // changed: it doesn't include unmapped bytes. return tcmalloc::MallocExtension::GetNumericProperty("generic.heap_size").value_or(0) + tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") .value_or(0); +} + +uint64_t Stats::totalThreadCacheBytes() { + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.current_total_thread_cache_bytes") + .value_or(0); +} + +uint64_t Stats::totalPageHeapFree() { + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_free_bytes").value_or(0); +} + +uint64_t Stats::totalPageHeapUnmapped() { + return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") + .value_or(0); +} + +uint64_t Stats::totalPhysicalBytes() { + return tcmalloc::MallocExtension::GetProperties()["generic.physical_memory_used"].value; +} + +void Stats::dumpStatsToLog() { + ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", tcmalloc::MallocExtension::GetStats()); +} + +} // namespace Memory +} // namespace Envoy + #elif defined(GPERFTOOLS_TCMALLOC) + +#include "gperftools/malloc_extension.h" + +namespace Envoy { +namespace Memory { + +uint64_t Stats::totalCurrentlyAllocated() { + size_t value = 0; + MallocExtension::instance()->GetNumericProperty("generic.current_allocated_bytes", &value); + return value; +} + +uint64_t Stats::totalCurrentlyReserved() { size_t value = 0; MallocExtension::instance()->GetNumericProperty("generic.heap_size", &value); return value; -#else - return 0; -#endif } uint64_t Stats::totalThreadCacheBytes() { -#if defined(TCMALLOC) - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.current_total_thread_cache_bytes") - .value_or(0); -#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.current_total_thread_cache_bytes", &value); return value; -#else - return 0; -#endif } uint64_t Stats::totalPageHeapFree() { -#if defined(TCMALLOC) - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_free_bytes").value_or(0); -#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.pageheap_free_bytes", &value); return value; -#else - return 0; -#endif } uint64_t Stats::totalPageHeapUnmapped() { -#if defined(TCMALLOC) - return tcmalloc::MallocExtension::GetNumericProperty("tcmalloc.pageheap_unmapped_bytes") - .value_or(0); -#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("tcmalloc.pageheap_unmapped_bytes", &value); return value; -#else - return 0; -#endif } uint64_t Stats::totalPhysicalBytes() { -#if defined(TCMALLOC) - return tcmalloc::MallocExtension::GetProperties()["generic.physical_memory_used"].value; -#elif defined(GPERFTOOLS_TCMALLOC) size_t value = 0; MallocExtension::instance()->GetNumericProperty("generic.total_physical_bytes", &value); return value; -#else - return 0; -#endif } void Stats::dumpStatsToLog() { -#if defined(TCMALLOC) - ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", tcmalloc::MallocExtension::GetStats()); -#elif defined(GPERFTOOLS_TCMALLOC) constexpr int buffer_size = 100000; auto buffer = std::make_unique(buffer_size); MallocExtension::instance()->GetStats(buffer.get(), buffer_size); ENVOY_LOG_MISC(debug, "TCMalloc stats:\n{}", buffer.get()); -#else - return; -#endif } -void AllocatorManager::tcmallocRelease() { -#if defined(TCMALLOC) - tcmalloc::MallocExtension::ReleaseMemoryToSystem(bytes_to_release_); -#elif defined(GPERFTOOLS_TCMALLOC) - MallocExtension::instance()->ReleaseToSystem(bytes_to_release_); +} // namespace Memory +} // namespace Envoy + #else - return; -#endif -} -/** - * Configures tcmalloc release rate from the page heap. If `bytes_to_release_` - * has been initialized to `0`, no heap memory will be released in background. - */ -void AllocatorManager::configureBackgroundMemoryRelease(Api::Api& api) { - RELEASE_ASSERT(!tcmalloc_thread_, "Invalid state, tcmalloc has already been initialised"); - if (bytes_to_release_ > 0) { - tcmalloc_routine_dispatcher_ = api.allocateDispatcher(std::string(TCMALLOC_ROUTINE_THREAD_ID)); - memory_release_timer_ = tcmalloc_routine_dispatcher_->createTimer([this]() -> void { - const uint64_t unmapped_bytes_before_release = Stats::totalPageHeapUnmapped(); - tcmallocRelease(); - const uint64_t unmapped_bytes_after_release = Stats::totalPageHeapUnmapped(); - if (unmapped_bytes_after_release > unmapped_bytes_before_release) { - // Only increment stats if memory was actually released. As tcmalloc releases memory on a - // span granularity, during some release rounds there may be no memory released, if during - // past round too much memory was released. - // https://github.com/google/tcmalloc/blob/master/tcmalloc/tcmalloc.cc#L298 - allocator_manager_stats_.released_by_timer_.inc(); - } - memory_release_timer_->enableTimer(memory_release_interval_msec_); - }); - tcmalloc_thread_ = api.threadFactory().createThread( - [this]() -> void { - ENVOY_LOG_MISC(debug, "Started {}", TCMALLOC_ROUTINE_THREAD_ID); - memory_release_timer_->enableTimer(memory_release_interval_msec_); - tcmalloc_routine_dispatcher_->run(Event::Dispatcher::RunType::RunUntilExit); - }, - Thread::Options{std::string(TCMALLOC_ROUTINE_THREAD_ID)}); - ENVOY_LOG_MISC( - info, fmt::format( - "Configured tcmalloc with background release rate: {} bytes per {} milliseconds", - bytes_to_release_, memory_release_interval_msec_.count())); - } -} +namespace Envoy { +namespace Memory { + +uint64_t Stats::totalCurrentlyAllocated() { return 0; } +uint64_t Stats::totalThreadCacheBytes() { return 0; } +uint64_t Stats::totalCurrentlyReserved() { return 0; } +uint64_t Stats::totalPageHeapUnmapped() { return 0; } +uint64_t Stats::totalPageHeapFree() { return 0; } +uint64_t Stats::totalPhysicalBytes() { return 0; } +void Stats::dumpStatsToLog() {} } // namespace Memory } // namespace Envoy + +#endif // #if defined(TCMALLOC) diff --git a/source/common/memory/stats.h b/source/common/memory/stats.h index 03c0b0a0dba0..71bc261ae175 100644 --- a/source/common/memory/stats.h +++ b/source/common/memory/stats.h @@ -2,24 +2,9 @@ #include -#include "envoy/config/bootstrap/v3/bootstrap.pb.h" -#include "envoy/stats/store.h" - -#include "source/common/common/thread.h" -#include "source/common/protobuf/utility.h" - namespace Envoy { - -#define MEMORY_ALLOCATOR_MANAGER_STATS(COUNTER) COUNTER(released_by_timer) - -struct MemoryAllocatorManagerStats { - MEMORY_ALLOCATOR_MANAGER_STATS(GENERATE_COUNTER_STRUCT) -}; - namespace Memory { -constexpr absl::string_view TCMALLOC_ROUTINE_THREAD_ID = "TcmallocProcessBackgroundActions"; - /** * Runtime stats for process memory usage. */ @@ -66,40 +51,5 @@ class Stats { static void dumpStatsToLog(); }; -class AllocatorManager { -public: - AllocatorManager(Api::Api& api, Envoy::Stats::Scope& scope, - const envoy::config::bootstrap::v3::MemoryAllocatorManager& config) - : bytes_to_release_(config.bytes_to_release()), - memory_release_interval_msec_(std::chrono::milliseconds( - PROTOBUF_GET_MS_OR_DEFAULT(config, memory_release_interval, 1000))), - allocator_manager_stats_(MemoryAllocatorManagerStats{ - MEMORY_ALLOCATOR_MANAGER_STATS(POOL_COUNTER_PREFIX(scope, "tcmalloc."))}) { - configureBackgroundMemoryRelease(api); - }; - - ~AllocatorManager() { - if (tcmalloc_routine_dispatcher_) { - tcmalloc_routine_dispatcher_->exit(); - } - if (tcmalloc_thread_) { - tcmalloc_thread_->join(); - tcmalloc_thread_.reset(); - } - } - -private: - const uint64_t bytes_to_release_; - const std::chrono::milliseconds memory_release_interval_msec_; - MemoryAllocatorManagerStats allocator_manager_stats_; - Thread::ThreadPtr tcmalloc_thread_; - Event::DispatcherPtr tcmalloc_routine_dispatcher_; - Event::TimerPtr memory_release_timer_; - void configureBackgroundMemoryRelease(Api::Api& api); - void tcmallocRelease(); - // Used for testing. - friend class AllocatorManagerPeer; -}; - } // namespace Memory } // namespace Envoy diff --git a/source/server/server.cc b/source/server/server.cc index cbcd3ed80b80..5b73a573b9c6 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -35,6 +35,7 @@ #include "source/common/http/codes.h" #include "source/common/http/headers.h" #include "source/common/local_info/local_info_impl.h" +#include "source/common/memory/stats.h" #include "source/common/network/address_impl.h" #include "source/common/network/dns_resolver/dns_factory_util.h" #include "source/common/network/socket_interface.h" @@ -521,9 +522,6 @@ void InstanceBase::initializeOrThrow(Network::Address::InstanceConstSharedPtr lo server_stats_->dynamic_unknown_fields_, server_stats_->wip_protos_); - memory_allocator_ = std::make_unique( - *api_, *stats_store_.rootScope(), bootstrap_.memory_allocator_manager()); - initialization_timer_ = std::make_unique( server_stats_->initialization_time_ms_, timeSource()); server_stats_->concurrency_.set(options_.concurrency()); diff --git a/source/server/server.h b/source/server/server.h index 48a56e1171a7..dcfd5be7e5a9 100644 --- a/source/server/server.h +++ b/source/server/server.h @@ -32,7 +32,6 @@ #include "source/common/grpc/context_impl.h" #include "source/common/http/context_impl.h" #include "source/common/init/manager_impl.h" -#include "source/common/memory/stats.h" #include "source/common/protobuf/message_validator_impl.h" #include "source/common/quic/quic_stat_names.h" #include "source/common/router/context_impl.h" @@ -337,6 +336,7 @@ class InstanceBase : Logger::Loggable, Stage stage, std::function completion_cb = [] {}); void onRuntimeReady(); void onClusterManagerPrimaryInitializationComplete(); + using LifecycleNotifierCallbacks = std::list; using LifecycleNotifierCompletionCallbacks = std::list; @@ -414,8 +414,8 @@ class InstanceBase : Logger::Loggable, ServerFactoryContextImpl server_contexts_; bool enable_reuse_port_default_{false}; Regex::EnginePtr regex_engine_; + bool stats_flush_in_progress_ : 1; - std::unique_ptr memory_allocator_; template class LifecycleCallbackHandle : public ServerLifecycleNotifier::Handle, RaiiListElement { diff --git a/test/common/memory/BUILD b/test/common/memory/BUILD index 368cd14bf420..3123d827b949 100644 --- a/test/common/memory/BUILD +++ b/test/common/memory/BUILD @@ -14,18 +14,6 @@ envoy_cc_test( deps = ["//source/common/memory:stats_lib"], ) -envoy_cc_test( - name = "memory_release_test", - srcs = ["memory_release_test.cc"], - deps = [ - "//source/common/event:dispatcher_lib", - "//source/common/memory:stats_lib", - "//test/common/stats:stat_test_utility_lib", - "//test/test_common:simulated_time_system_lib", - "//test/test_common:utility_lib", - ], -) - envoy_cc_test( name = "heap_shrinker_test", srcs = ["heap_shrinker_test.cc"], diff --git a/test/common/memory/memory_release_test.cc b/test/common/memory/memory_release_test.cc deleted file mode 100644 index 041233baebd1..000000000000 --- a/test/common/memory/memory_release_test.cc +++ /dev/null @@ -1,139 +0,0 @@ -#include "source/common/event/dispatcher_impl.h" -#include "source/common/memory/stats.h" - -#include "test/common/stats/stat_test_utility.h" -#include "test/test_common/simulated_time_system.h" -#include "test/test_common/utility.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace Envoy { -namespace Memory { - -class AllocatorManagerPeer { -public: - static std::chrono::milliseconds - memoryReleaseInterval(const AllocatorManager& allocator_manager) { - return allocator_manager.memory_release_interval_msec_; - } - static uint64_t bytesToRelease(const AllocatorManager& allocator_manager) { - return allocator_manager.bytes_to_release_; - } -}; - -namespace { - -static const int MB = 1048576; - -class MemoryReleaseTest : public testing::Test { -protected: - MemoryReleaseTest() - : api_(Api::createApiForTest(stats_, time_system_)), - dispatcher_("test_thread", *api_, time_system_), scope_("memory_release_test.", stats_) {} - - void initialiseAllocatorManager(uint64_t bytes_to_release, float release_interval_s) { - const std::string yaml_config = (release_interval_s > 0) - ? fmt::format(R"EOF( - bytes_to_release: {} - memory_release_interval: {}s -)EOF", - bytes_to_release, release_interval_s) - : fmt::format(R"EOF( - bytes_to_release: {} -)EOF", - bytes_to_release); - const auto proto_config = - TestUtility::parseYaml(yaml_config); - allocator_manager_ = std::make_unique(*api_, scope_, proto_config); - } - - void step(const std::chrono::milliseconds& step) { time_system_.advanceTimeWait(step); } - - Envoy::Stats::TestUtil::TestStore stats_; - Event::SimulatedTimeSystem time_system_; - Api::ApiPtr api_; - Event::DispatcherImpl dispatcher_; - Envoy::Stats::TestUtil::TestScope scope_; - std::unique_ptr allocator_manager_; -}; - -TEST_F(MemoryReleaseTest, ReleaseRateAboveZeroDefaultIntervalMemoryReleased) { - size_t initial_allocated_bytes = Stats::totalCurrentlyAllocated(); - auto a = std::make_unique(MB); - auto b = std::make_unique(MB); - if (Stats::totalCurrentlyAllocated() <= initial_allocated_bytes) { - GTEST_SKIP() << "Skipping test, cannot measure memory usage precisely on this platform."; - } - auto initial_unmapped_bytes = Stats::totalPageHeapUnmapped(); - EXPECT_LOG_CONTAINS( - "info", - "Configured tcmalloc with background release rate: 1048576 bytes per 1000 milliseconds", - initialiseAllocatorManager(MB /*bytes per second*/, 0)); - EXPECT_EQ(MB, AllocatorManagerPeer::bytesToRelease(*allocator_manager_)); - EXPECT_EQ(std::chrono::milliseconds(1000), - AllocatorManagerPeer::memoryReleaseInterval(*allocator_manager_)); - a.reset(); - // Release interval was configured to default value (1 second). - step(std::chrono::milliseconds(1000)); - TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 1UL, - time_system_); - auto released_bytes_before_next_run = Stats::totalPageHeapUnmapped(); - b.reset(); - step(std::chrono::milliseconds(1000)); - TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 2UL, - time_system_); - auto final_released_bytes = Stats::totalPageHeapUnmapped(); - -#if defined(TCMALLOC) || defined(GPERFTOOLS_TCMALLOC) - EXPECT_LT(released_bytes_before_next_run, final_released_bytes); - EXPECT_LT(initial_unmapped_bytes, final_released_bytes); -#else - EXPECT_LE(released_bytes_before_next_run, final_released_bytes); - EXPECT_LE(initial_unmapped_bytes, final_released_bytes); -#endif -} - -TEST_F(MemoryReleaseTest, ReleaseRateZeroNoRelease) { - auto a = std::make_unique(MB); - EXPECT_LOG_NOT_CONTAINS( - "info", "Configured tcmalloc with background release rate: 0 bytes 1000 milliseconds", - initialiseAllocatorManager(0 /*bytes per second*/, 0)); - a.reset(); - // Release interval was configured to default value (1 second). - step(std::chrono::milliseconds(3000)); - EXPECT_EQ(0UL, stats_.counter("memory_release_test.tcmalloc.released_by_timer").value()); -} - -TEST_F(MemoryReleaseTest, ReleaseRateAboveZeroCustomIntervalMemoryReleased) { - size_t initial_allocated_bytes = Stats::totalCurrentlyAllocated(); - auto a = std::make_unique(40 * MB); - auto b = std::make_unique(40 * MB); - if (Stats::totalCurrentlyAllocated() <= initial_allocated_bytes) { - GTEST_SKIP() << "Skipping test, cannot measure memory usage precisely on this platform."; - } - auto initial_unmapped_bytes = Stats::totalPageHeapUnmapped(); - EXPECT_LOG_CONTAINS( - "info", - "Configured tcmalloc with background release rate: 16777216 bytes per 2000 milliseconds", - initialiseAllocatorManager(16 * MB /*bytes per second*/, 2)); - EXPECT_EQ(16 * MB, AllocatorManagerPeer::bytesToRelease(*allocator_manager_)); - EXPECT_EQ(std::chrono::milliseconds(2000), - AllocatorManagerPeer::memoryReleaseInterval(*allocator_manager_)); - a.reset(); - step(std::chrono::milliseconds(2000)); - b.reset(); - step(std::chrono::milliseconds(2000)); - TestUtility::waitForCounterEq(stats_, "memory_release_test.tcmalloc.released_by_timer", 2UL, - time_system_); - auto final_released_bytes = Stats::totalPageHeapUnmapped(); -#if defined(TCMALLOC) || defined(GPERFTOOLS_TCMALLOC) - EXPECT_LT(initial_unmapped_bytes, final_released_bytes); -#else - EXPECT_LE(initial_unmapped_bytes, final_released_bytes); -#endif -} - -} // namespace -} // namespace Memory -} // namespace Envoy From bbc339afe2d65b40d3bacd55d242653d02df1bb5 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Sun, 18 Feb 2024 10:21:59 -0500 Subject: [PATCH 040/151] LB: fix randomization in host LB scheduler initialization (#32233) Signed-off-by: Adi Suissa-Peleg --- changelogs/current.yaml | 5 ++ source/common/runtime/runtime_features.cc | 1 + source/common/upstream/load_balancer_impl.cc | 66 +++++++++++++------ source/common/upstream/load_balancer_impl.h | 2 +- .../upstream/load_balancer_impl_test.cc | 66 ++++++++++++++++++- 5 files changed, 116 insertions(+), 24 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e7f8bc3329f5..379d8c919851 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -57,6 +57,11 @@ bug_fixes: Added randomization in locality load-balancing initialization. This helps desynchronizing Envoys across a fleet by randomizing the scheduler starting point. This can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.edf_lb_locality_scheduler_init_fix`` to false. +- area: load balancing + change: | + Added randomization in host load-balancing initialization. This helps desynchronizing Envoys across + a fleet by randomizing the scheduler starting point. This can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.edf_lb_host_scheduler_init_fix`` to false. - area: UDP and TCP tunneling change: | fixed a bug where second HTTP response headers received would cause Envoy to crash in cases where diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index dc560deed2c8..553528718a5a 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -39,6 +39,7 @@ RUNTIME_GUARD(envoy_reloadable_features_defer_processing_backedup_streams); RUNTIME_GUARD(envoy_reloadable_features_detect_and_raise_rst_tcp_connection); RUNTIME_GUARD(envoy_reloadable_features_dfp_mixed_scheme); RUNTIME_GUARD(envoy_reloadable_features_dns_cache_set_first_resolve_complete); +RUNTIME_GUARD(envoy_reloadable_features_edf_lb_host_scheduler_init_fix); RUNTIME_GUARD(envoy_reloadable_features_edf_lb_locality_scheduler_init_fix); RUNTIME_GUARD(envoy_reloadable_features_enable_compression_bomb_protection); RUNTIME_GUARD(envoy_reloadable_features_enable_connect_udp_support); diff --git a/source/common/upstream/load_balancer_impl.cc b/source/common/upstream/load_balancer_impl.cc index 83f22f7adc80..ea76e579b293 100644 --- a/source/common/upstream/load_balancer_impl.cc +++ b/source/common/upstream/load_balancer_impl.cc @@ -1084,28 +1084,52 @@ void EdfLoadBalancerBase::refresh(uint32_t priority) { // Skip edf creation. return; } - scheduler.edf_ = std::make_unique>(); - - // Populate scheduler with host list. - // TODO(mattklein123): We must build the EDF schedule even if all of the hosts are currently - // weighted 1. This is because currently we don't refresh host sets if only weights change. - // We should probably change this to refresh at all times. See the comment in - // BaseDynamicClusterImpl::updateDynamicHostList about this. - for (const auto& host : hosts) { - // We use a fixed weight here. While the weight may change without - // notification, this will only be stale until this host is next picked, - // at which point it is reinserted into the EdfScheduler with its new - // weight in chooseHost(). - scheduler.edf_->add(hostWeight(*host), host); - } - // Cycle through hosts to achieve the intended offset behavior. - // TODO(htuch): Consider how we can avoid biasing towards earlier hosts in the schedule across - // refreshes for the weighted case. - if (!hosts.empty()) { - for (uint32_t i = 0; i < seed_ % hosts.size(); ++i) { - auto host = - scheduler.edf_->pickAndAdd([this](const Host& host) { return hostWeight(host); }); + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.edf_lb_host_scheduler_init_fix")) { + // If there are no hosts or a single one, there is no need for an EDF scheduler + // (thus lowering memory and CPU overhead), as the (possibly) single host + // will be the one always selected by the scheduler. + if (hosts.size() <= 1) { + return; + } + + // Populate the scheduler with the host list with a randomized starting point. + // TODO(mattklein123): We must build the EDF schedule even if all of the hosts are currently + // weighted 1. This is because currently we don't refresh host sets if only weights change. + // We should probably change this to refresh at all times. See the comment in + // BaseDynamicClusterImpl::updateDynamicHostList about this. + scheduler.edf_ = std::make_unique>(EdfScheduler::createWithPicks( + hosts, + // We use a fixed weight here. While the weight may change without + // notification, this will only be stale until this host is next picked, + // at which point it is reinserted into the EdfScheduler with its new + // weight in chooseHost(). + [this](const Host& host) { return hostWeight(host); }, seed_)); + } else { + scheduler.edf_ = std::make_unique>(); + + // Populate scheduler with host list. + // TODO(mattklein123): We must build the EDF schedule even if all of the hosts are currently + // weighted 1. This is because currently we don't refresh host sets if only weights change. + // We should probably change this to refresh at all times. See the comment in + // BaseDynamicClusterImpl::updateDynamicHostList about this. + for (const auto& host : hosts) { + // We use a fixed weight here. While the weight may change without + // notification, this will only be stale until this host is next picked, + // at which point it is reinserted into the EdfScheduler with its new + // weight in chooseHost(). + scheduler.edf_->add(hostWeight(*host), host); + } + + // Cycle through hosts to achieve the intended offset behavior. + // TODO(htuch): Consider how we can avoid biasing towards earlier hosts in the schedule across + // refreshes for the weighted case. + if (!hosts.empty()) { + for (uint32_t i = 0; i < seed_ % hosts.size(); ++i) { + auto host = + scheduler.edf_->pickAndAdd([this](const Host& host) { return hostWeight(host); }); + } } } }; diff --git a/source/common/upstream/load_balancer_impl.h b/source/common/upstream/load_balancer_impl.h index 55927ed4a1f0..ef23f188722c 100644 --- a/source/common/upstream/load_balancer_impl.h +++ b/source/common/upstream/load_balancer_impl.h @@ -525,7 +525,7 @@ class EdfLoadBalancerBase : public ZoneAwareLoadBalancerBase { // EdfScheduler for weighted LB. The edf_ is only created when the original // host weights of 2 or more hosts differ. When not present, the // implementation of chooseHostOnce falls back to unweightedHostPick. - std::unique_ptr> edf_; + std::unique_ptr> edf_; }; void initialize(); diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/common/upstream/load_balancer_impl_test.cc index 4ed88b7652e0..54831d77d75d 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/common/upstream/load_balancer_impl_test.cc @@ -1097,12 +1097,12 @@ TEST_P(RoundRobinLoadBalancerTest, Weighted) { hostSet().healthy_hosts_[1]->weight(1); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); // Add a host, it should participate in next round of scheduling. hostSet().healthy_hosts_.push_back(makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), 3)); hostSet().hosts_.push_back(hostSet().healthy_hosts_.back()); @@ -1116,9 +1116,9 @@ TEST_P(RoundRobinLoadBalancerTest, Weighted) { EXPECT_EQ(hostSet().healthy_hosts_[2], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[2], lb_->chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[2], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[2], lb_->chooseHost(nullptr)); // Remove last two hosts, add a new one with different weights. HostVector removed_hosts = {hostSet().hosts_[1], hostSet().hosts_[2]}; hostSet().healthy_hosts_.pop_back(); @@ -1157,6 +1157,68 @@ TEST_P(RoundRobinLoadBalancerTest, WeightedSeed) { EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); } +// Validate that the RNG seed influences pick order when weighted RR without +// the envoy.reloadable_features.edf_lb_host_scheduler_init_fix. +// This test should be removed once +// envoy.reloadable_features.edf_lb_host_scheduler_init_fix is deprecated. +TEST_P(RoundRobinLoadBalancerTest, WeightedSeedOldInit) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.edf_lb_host_scheduler_init_fix", "false"}}); + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), 1), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), 2)}; + hostSet().hosts_ = hostSet().healthy_hosts_; + EXPECT_CALL(random_, random()).WillRepeatedly(Return(1)); + init(false); + // Initial weights respected. + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); +} + +// Validate that low weighted hosts will be chosen when the LB is created. +TEST_P(RoundRobinLoadBalancerTest, WeightedInitializationPicksAllHosts) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.locality_routing_use_new_routing_logic", + GetParam().use_new_locality_routing ? "true" : "false"}}); + // This test should be kept just the runtime override removed once the + // feature-flag is deprecated. + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.edf_lb_host_scheduler_init_fix", "true"}}); + // Add 3 hosts with weights {6, 3, 1}. Out of 10 refreshes with consecutive + // random value, 6 times the first host will be chosen, 3 times the second + // host will be chosen, and 1 time the third host will be chosen. + hostSet().healthy_hosts_ = { + makeTestHost(info_, "tcp://127.0.0.1:80", simTime(), 6), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime(), 3), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime(), 1), + }; + hostSet().hosts_ = hostSet().healthy_hosts_; + absl::flat_hash_map host_picked_count_map; + for (const auto& host : hostSet().healthy_hosts_) { + host_picked_count_map[host] = 0; + } + // When random returns x, the initialization of the lb will invoke pickAndAdd + // x times. The test validates the result of the next call (the x+1 call). + // Initiate 10 load-balancers with different random values. + for (int i = 0; i < 10; ++i) { + EXPECT_CALL(random_, random()).Times(2).WillRepeatedly(Return(i)); + RoundRobinLoadBalancer lb(priority_set_, local_priority_set_.get(), stats_, runtime_, random_, + common_config_, round_robin_lb_config_, simTime()); + const auto& host = lb.chooseHost(nullptr); + host_picked_count_map[host]++; + } + // Ensure that the number of times each host was picked is as expected. + { + EXPECT_EQ(host_picked_count_map[hostSet().healthy_hosts_[0]], 6); + EXPECT_EQ(host_picked_count_map[hostSet().healthy_hosts_[1]], 3); + EXPECT_EQ(host_picked_count_map[hostSet().healthy_hosts_[2]], 1); + } +} + TEST_P(RoundRobinLoadBalancerTest, MaxUnhealthyPanic) { hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}; From 81db677b8f6650ae0785db6931cb9bb538928c90 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 19 Feb 2024 10:09:45 +0000 Subject: [PATCH 041/151] clang/tidy: Assorted fixes (#32363) Signed-off-by: Ryan Northey --- api/bazel/cc_proto_descriptor_library/BUILD | 6 +- .../filters/network/source/upstream.cc | 2 + .../network/test/router/router_test.cc | 4 +- .../filters/network/source/broker/filter.cc | 2 +- .../network/source/broker/filter_config.cc | 2 +- .../network/source/broker/filter_config.h | 2 +- .../filters/network/source/broker/rewriter.h | 2 +- .../network/test/broker/rewriter_unit_test.cc | 4 +- .../qatzip/compressor/source/config.cc | 2 +- .../qatzip/compressor/source/config.h | 2 +- .../filesystem/posix/filesystem_impl.cc | 3 +- source/common/http/async_client_impl.cc | 4 +- source/common/network/address_impl.cc | 4 +- source/common/quic/envoy_quic_dispatcher.cc | 2 +- .../quic_filter_manager_connection_impl.h | 2 +- source/common/router/upstream_request.cc | 2 +- source/common/router/upstream_request.h | 13 ++-- source/common/stats/symbol_table.cc | 6 ++ source/common/upstream/edf_scheduler.h | 4 +- source/common/upstream/upstream_impl.cc | 5 +- source/exe/stripped_main_base.cc | 10 +-- source/exe/stripped_main_base.h | 2 +- .../clusters/dynamic_forward_proxy/cluster.cc | 2 +- .../extensions/common/aws/metadata_fetcher.cc | 3 +- .../common/aws/region_provider_impl.cc | 7 +- .../grpc/grpc_mux_failover.h | 10 +-- .../grpc/grpc_stream_interface.h | 2 +- .../http/aws_lambda/aws_lambda_filter.cc | 2 +- .../filters/http/composite/filter.cc | 14 ++-- .../filters/http/composite/filter.h | 2 +- .../grpc_http1_bridge/http1_bridge_filter.cc | 2 +- .../filters/http/jwt_authn/authenticator.cc | 2 +- .../extensions/filters/http/oauth2/filter.cc | 4 +- .../extensions/filters/http/oauth2/filter.h | 2 +- .../payload_to_metadata_filter.cc | 4 +- .../filters/udp/udp_proxy/config.cc | 4 +- .../udp/udp_proxy/session_filters/filter.h | 4 +- .../filters/udp/udp_proxy/udp_proxy_filter.cc | 4 +- .../filters/udp/udp_proxy/udp_proxy_filter.h | 2 +- .../geoip_providers/maxmind/geoip_provider.h | 2 +- .../grpc_credentials/aws_iam/config.cc | 4 +- source/extensions/tracers/datadog/tracer.cc | 9 ++- source/extensions/tracers/datadog/tracer.h | 6 +- .../samplers/always_on/always_on_sampler.cc | 2 +- .../samplers/always_on/always_on_sampler.h | 2 +- .../tracers/opentelemetry/samplers/sampler.h | 10 +-- source/extensions/upstreams/http/config.cc | 2 +- source/server/config_validation/server.cc | 2 +- source/server/server.cc | 2 +- test/config_test/config_test.cc | 2 +- .../grpc/grpc_mux_failover_test.cc | 2 +- .../config_subscription/grpc/mocks.h | 2 +- .../compressor_filter_speed_test.cc | 45 ++++++++----- .../filters/http/ext_proc/filter_test.cc | 10 +-- .../ext_proc/streaming_integration_test.cc | 4 +- .../geoip/geoip_filter_integration_test.cc | 3 - .../header_mutation_integration_test.cc | 10 +-- .../http/jwt_authn/filter_config_test.cc | 1 - .../dynamic_forward_proxy/BUILD | 2 +- .../extensions/tracers/datadog/config_test.cc | 4 +- .../dynatrace_metadata_file_reader_test.cc | 2 - .../always_on/always_on_sampler_test.cc | 4 +- .../opentelemetry/samplers/sampler_test.cc | 14 ++-- .../alts/alts_channel_pool_test.cc | 1 - .../alts/alts_integration_test.cc | 65 +++++++++---------- .../alts/tsi_handshaker_test.cc | 3 +- .../transport_sockets/alts/tsi_socket_test.cc | 2 +- .../buffer_accounting_integration_test.cc | 2 - test/integration/integration_stream_decoder.h | 2 +- .../load_stats_integration_test.cc | 4 +- .../udp_tunneling_integration_test.cc | 2 +- test/mocks/server/factory_context.cc | 1 - test/server/hot_restarting_child_test.cc | 2 - test/server/server_test.cc | 6 +- 74 files changed, 203 insertions(+), 188 deletions(-) diff --git a/api/bazel/cc_proto_descriptor_library/BUILD b/api/bazel/cc_proto_descriptor_library/BUILD index fcab612aa49c..993d10801d30 100644 --- a/api/bazel/cc_proto_descriptor_library/BUILD +++ b/api/bazel/cc_proto_descriptor_library/BUILD @@ -12,10 +12,12 @@ cc_library( cc_library( name = "text_format_transcoder", srcs = [ - "create_dynamic_message.h", "text_format_transcoder.cc", ], - hdrs = ["text_format_transcoder.h"], + hdrs = [ + "create_dynamic_message.h", + "text_format_transcoder.h", + ], visibility = ["//visibility:public"], deps = [ ":file_descriptor_info", diff --git a/contrib/generic_proxy/filters/network/source/upstream.cc b/contrib/generic_proxy/filters/network/source/upstream.cc index d92ef81d1456..3af4c0b712a1 100644 --- a/contrib/generic_proxy/filters/network/source/upstream.cc +++ b/contrib/generic_proxy/filters/network/source/upstream.cc @@ -8,6 +8,8 @@ namespace GenericProxy { UpstreamConnection::~UpstreamConnection() { // Do clean up here again to ensure the cleanUp is called. This is safe to call // multiple times because of the is_cleand_up_ flag. + // TODO(wbpcode): Clarify/resolve bypassing of virtual dispatch + // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) this->cleanUp(true); } diff --git a/contrib/generic_proxy/filters/network/test/router/router_test.cc b/contrib/generic_proxy/filters/network/test/router/router_test.cc index 8d98a4229f85..aa04214b75d4 100644 --- a/contrib/generic_proxy/filters/network/test/router/router_test.cc +++ b/contrib/generic_proxy/filters/network/test/router/router_test.cc @@ -175,10 +175,10 @@ class RouterFilterTest : public testing::TestWithParam { ASSERT(!filter_->upstreamRequestsForTest().empty()); auto upstream_request = filter_->upstreamRequestsForTest().begin()->get(); + auto stream_frame = std::make_shared(std::move(response)); EXPECT_CALL(*mock_client_codec_, decode(BufferStringEqual("test_1"), _)) - .WillOnce(Invoke([this, resp = std::make_shared(std::move(response))]( - Buffer::Instance& buffer, bool) { + .WillOnce(Invoke([this, resp = std::move(stream_frame)](Buffer::Instance& buffer, bool) { buffer.drain(buffer.length()); const bool end_stream = (*resp)->frameFlags().endStream(); diff --git a/contrib/kafka/filters/network/source/broker/filter.cc b/contrib/kafka/filters/network/source/broker/filter.cc index 616ac6560364..027ab530affe 100644 --- a/contrib/kafka/filters/network/source/broker/filter.cc +++ b/contrib/kafka/filters/network/source/broker/filter.cc @@ -72,7 +72,7 @@ absl::flat_hash_map& KafkaMetricsFacadeImpl::getRequestA KafkaBrokerFilter::KafkaBrokerFilter(Stats::Scope& scope, TimeSource& time_source, const BrokerFilterConfig& filter_config) : KafkaBrokerFilter{filter_config, std::make_shared( - scope, time_source, filter_config.stat_prefix())} {}; + scope, time_source, filter_config.statPrefix())} {}; KafkaBrokerFilter::KafkaBrokerFilter(const BrokerFilterConfig& filter_config, const KafkaMetricsFacadeSharedPtr& metrics) diff --git a/contrib/kafka/filters/network/source/broker/filter_config.cc b/contrib/kafka/filters/network/source/broker/filter_config.cc index 2167aae35c5f..40e72979063e 100644 --- a/contrib/kafka/filters/network/source/broker/filter_config.cc +++ b/contrib/kafka/filters/network/source/broker/filter_config.cc @@ -48,7 +48,7 @@ BrokerFilterConfig::findBrokerAddressOverride(const uint32_t broker_id) const { return absl::nullopt; } -const std::string& BrokerFilterConfig::stat_prefix() const { return stat_prefix_; } +const std::string& BrokerFilterConfig::statPrefix() const { return stat_prefix_; } } // namespace Broker } // namespace Kafka diff --git a/contrib/kafka/filters/network/source/broker/filter_config.h b/contrib/kafka/filters/network/source/broker/filter_config.h index 4d9526ba711c..9cea074205de 100644 --- a/contrib/kafka/filters/network/source/broker/filter_config.h +++ b/contrib/kafka/filters/network/source/broker/filter_config.h @@ -44,7 +44,7 @@ class BrokerFilterConfig { /** * Returns the prefix for stats. */ - virtual const std::string& stat_prefix() const; + virtual const std::string& statPrefix() const; /** * Whether this configuration means that rewrite should be happening. diff --git a/contrib/kafka/filters/network/source/broker/rewriter.h b/contrib/kafka/filters/network/source/broker/rewriter.h index 82f4613c4c0e..f6a98c1045c9 100644 --- a/contrib/kafka/filters/network/source/broker/rewriter.h +++ b/contrib/kafka/filters/network/source/broker/rewriter.h @@ -21,7 +21,7 @@ namespace Broker { */ class ResponseRewriter : public ResponseCallback { public: - virtual ~ResponseRewriter() = default; + ~ResponseRewriter() override = default; /** * Performs any desired payload changes. diff --git a/contrib/kafka/filters/network/test/broker/rewriter_unit_test.cc b/contrib/kafka/filters/network/test/broker/rewriter_unit_test.cc index cf5195bd9e76..48bfb3fab956 100644 --- a/contrib/kafka/filters/network/test/broker/rewriter_unit_test.cc +++ b/contrib/kafka/filters/network/test/broker/rewriter_unit_test.cc @@ -6,9 +6,7 @@ #include "contrib/kafka/filters/network/test/broker/mock_filter_config.h" #include "gtest/gtest.h" -using testing::_; using testing::Return; -using testing::Throw; namespace Envoy { namespace Extensions { @@ -34,7 +32,7 @@ class FakeResponse : public AbstractResponse { uint32_t computeSize() const override { return size_; }; - virtual uint32_t encode(Buffer::Instance& dst) const override { + uint32_t encode(Buffer::Instance& dst) const override { putBytesIntoBuffer(dst, size_); return size_; }; diff --git a/contrib/qat/compression/qatzip/compressor/source/config.cc b/contrib/qat/compression/qatzip/compressor/source/config.cc index 48144e706b10..47904f927b34 100644 --- a/contrib/qat/compression/qatzip/compressor/source/config.cc +++ b/contrib/qat/compression/qatzip/compressor/source/config.cc @@ -82,7 +82,7 @@ Envoy::Compression::Compressor::CompressorPtr QatzipCompressorFactory::createCom } QatzipCompressorFactory::QatzipThreadLocal::QatzipThreadLocal(QzSessionParams_T params) - : params_(params), session_{}, initialized_(false) {} + : params_(params), session_{} {} QatzipCompressorFactory::QatzipThreadLocal::~QatzipThreadLocal() { if (initialized_) { diff --git a/contrib/qat/compression/qatzip/compressor/source/config.h b/contrib/qat/compression/qatzip/compressor/source/config.h index 4df242bd8a1f..5d638e69ee69 100644 --- a/contrib/qat/compression/qatzip/compressor/source/config.h +++ b/contrib/qat/compression/qatzip/compressor/source/config.h @@ -53,7 +53,7 @@ class QatzipCompressorFactory : public Envoy::Compression::Compressor::Compresso QzSessionParams_T params_; QzSession_T session_; - bool initialized_; + bool initialized_{false}; }; const uint32_t chunk_size_; diff --git a/source/common/filesystem/posix/filesystem_impl.cc b/source/common/filesystem/posix/filesystem_impl.cc index 567eb43e15ff..d9714ce37b9b 100644 --- a/source/common/filesystem/posix/filesystem_impl.cc +++ b/source/common/filesystem/posix/filesystem_impl.cc @@ -158,7 +158,7 @@ static Api::IoCallResult infoFromStat(absl::string_view path, const st } static Api::IoCallResult infoFromStat(absl::string_view path, const struct stat& s) { - return infoFromStat(path, s, typeFromStat(s)); + return infoFromStat(path, s, typeFromStat(s)); // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) } Api::IoCallResult FileImplPosix::info() { @@ -180,6 +180,7 @@ Api::IoCallResult InstanceImplPosix::stat(absl::string_view path) { // but the reference is broken as the target could not be stat()'ed. // After confirming this with an lstat, treat this file entity as // a regular file, which may be unlink()'ed. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) return infoFromStat(path, s, FileType::Regular); } } diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index b79c34431ab9..467f55b09799 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -99,8 +99,8 @@ AsyncStreamImpl::AsyncStreamImpl(AsyncClientImpl& parent, AsyncClient::StreamCal retry_policy_(createRetryPolicy(parent, options)), route_(std::make_shared( parent_.cluster_->name(), - retry_policy_ != nullptr ? *retry_policy_.get() : *options.parsed_retry_policy, - options.timeout, options.hash_policy)), + retry_policy_ != nullptr ? *retry_policy_ : *options.parsed_retry_policy, options.timeout, + options.hash_policy)), account_(options.account_), buffer_limit_(options.buffer_limit_), send_xff_(options.send_xff) { stream_info_.dynamicMetadata().MergeFrom(options.metadata); diff --git a/source/common/network/address_impl.cc b/source/common/network/address_impl.cc index ca18dc9a34af..cb6b233cdc7a 100644 --- a/source/common/network/address_impl.cc +++ b/source/common/network/address_impl.cc @@ -219,7 +219,7 @@ bool force_ipv4_unsupported_for_test = false; Cleanup Ipv4Instance::forceProtocolUnsupportedForTest(bool new_val) { bool old_val = force_ipv4_unsupported_for_test; force_ipv4_unsupported_for_test = new_val; - return Cleanup([old_val]() { force_ipv4_unsupported_for_test = old_val; }); + return {[old_val]() { force_ipv4_unsupported_for_test = old_val; }}; } absl::Status Ipv4Instance::validateProtocolSupported() { @@ -341,7 +341,7 @@ bool force_ipv6_unsupported_for_test = false; Cleanup Ipv6Instance::forceProtocolUnsupportedForTest(bool new_val) { bool old_val = force_ipv6_unsupported_for_test; force_ipv6_unsupported_for_test = new_val; - return Cleanup([old_val]() { force_ipv6_unsupported_for_test = old_val; }); + return {[old_val]() { force_ipv6_unsupported_for_test = old_val; }}; } absl::Status Ipv6Instance::validateProtocolSupported() { diff --git a/source/common/quic/envoy_quic_dispatcher.cc b/source/common/quic/envoy_quic_dispatcher.cc index dd4f14183b0a..d15b175d7604 100644 --- a/source/common/quic/envoy_quic_dispatcher.cc +++ b/source/common/quic/envoy_quic_dispatcher.cc @@ -184,7 +184,7 @@ void EnvoyQuicDispatcher::closeConnectionsWithFilterChain( // from the map as well. connection.close(Network::ConnectionCloseType::NoFlush); if (!delete_sessions_immediately && - dynamic_cast(connection).fix_quic_lifetime_issues()) { + dynamic_cast(connection).fixQuicLifetimeIssues()) { // If `envoy.reloadable_features.quic_fix_filter_manager_uaf` is true, the closed sessions // need to be deleted right away to consistently handle quic lifetimes. Because upon // returning the filter chain configs will be destroyed, and no longer safe to be accessed. diff --git a/source/common/quic/quic_filter_manager_connection_impl.h b/source/common/quic/quic_filter_manager_connection_impl.h index 4a1a57a093d3..432f2779673b 100644 --- a/source/common/quic/quic_filter_manager_connection_impl.h +++ b/source/common/quic/quic_filter_manager_connection_impl.h @@ -171,7 +171,7 @@ class QuicFilterManagerConnectionImpl : public Network::ConnectionImplBase, max_headers_count_ = max_headers_count; } - bool fix_quic_lifetime_issues() const { return fix_quic_lifetime_issues_; } + bool fixQuicLifetimeIssues() const { return fix_quic_lifetime_issues_; } protected: // Propagate connection close to network_connection_callbacks_. diff --git a/source/common/router/upstream_request.cc b/source/common/router/upstream_request.cc index 8496745a80cc..7c9079e58a44 100644 --- a/source/common/router/upstream_request.cc +++ b/source/common/router/upstream_request.cc @@ -134,7 +134,7 @@ UpstreamRequest::UpstreamRequest(RouterFilterInterface& parent, // Set up the upstream HTTP filter manager. filter_manager_callbacks_ = std::make_unique(*this); filter_manager_ = std::make_unique( - *filter_manager_callbacks_, parent_.callbacks()->dispatcher(), connection(), + *filter_manager_callbacks_, parent_.callbacks()->dispatcher(), UpstreamRequest::connection(), parent_.callbacks()->streamId(), parent_.callbacks()->account(), true, parent_.callbacks()->decoderBufferLimit(), *parent_.cluster(), *this); // Attempt to create custom cluster-specified filter chain diff --git a/source/common/router/upstream_request.h b/source/common/router/upstream_request.h index b2369c8cb125..9e88bfefda31 100644 --- a/source/common/router/upstream_request.h +++ b/source/common/router/upstream_request.h @@ -61,12 +61,15 @@ class UpstreamCodecFilter; * There is some required communication between the UpstreamRequest and * UpstreamCodecFilter. This is accomplished via the UpstreamStreamFilterCallbacks * interface, with the UpstreamFilterManager acting as intermediary. + * + * UpstreamRequest is marked as final because no subclasses are expected. + * This class is intended to be used as-is without any specialized inheritance. */ -class UpstreamRequest : public Logger::Loggable, - public UpstreamToDownstream, - public LinkedObject, - public GenericConnectionPoolCallbacks, - public Event::DeferredDeletable { +class UpstreamRequest final : public Logger::Loggable, + public UpstreamToDownstream, + public LinkedObject, + public GenericConnectionPoolCallbacks, + public Event::DeferredDeletable { public: UpstreamRequest(RouterFilterInterface& parent, std::unique_ptr&& conn_pool, bool can_send_early_data, bool can_use_http3); diff --git a/source/common/stats/symbol_table.cc b/source/common/stats/symbol_table.cc index 8dce2c77d7c0..6a3d87f310b7 100644 --- a/source/common/stats/symbol_table.cc +++ b/source/common/stats/symbol_table.cc @@ -620,12 +620,16 @@ StatNameStorage::~StatNameStorage() { } void StatNameStorage::free(SymbolTable& table) { + // nolint: https://github.com/llvm/llvm-project/issues/81597 + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) table.free(statName()); clear(); } void StatNamePool::clear() { for (StatNameStorage& storage : storage_vector_) { + // nolint: https://github.com/llvm/llvm-project/issues/81597 + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) storage.free(symbol_table_); } storage_vector_.clear(); @@ -733,6 +737,8 @@ void StatNameList::iterate(const std::function& f) const { void StatNameList::clear(SymbolTable& symbol_table) { iterate([&symbol_table](StatName stat_name) -> bool { + // nolint: https://github.com/llvm/llvm-project/issues/81597 + // NOLINTNEXTLINE(clang-analyzer-unix.Malloc) symbol_table.free(stat_name); return true; }); diff --git a/source/common/upstream/edf_scheduler.h b/source/common/upstream/edf_scheduler.h index a625d72a98df..207b8ff744c7 100644 --- a/source/common/upstream/edf_scheduler.h +++ b/source/common/upstream/edf_scheduler.h @@ -27,7 +27,7 @@ namespace Upstream { // weights and an O(log n) pick time. template class EdfScheduler : public Scheduler { public: - EdfScheduler() {} + EdfScheduler() = default; // See scheduler.h for an explanation of each public method. std::shared_ptr peekAgain(std::function calculate_weight) override { @@ -89,7 +89,7 @@ template class EdfScheduler : public Scheduler { })); // Nothing to do if there are no entries. - if (entries.size() == 0) { + if (entries.empty()) { return EdfScheduler(); } diff --git a/source/common/upstream/upstream_impl.cc b/source/common/upstream/upstream_impl.cc index 66a0d339f83e..43ac5fb9ef2d 100644 --- a/source/common/upstream/upstream_impl.cc +++ b/source/common/upstream/upstream_impl.cc @@ -1167,7 +1167,8 @@ ClusterInfoImpl::ClusterInfoImpl( // Both LoadStatsReporter and per_endpoint_stats need to `latch()` the counters, so if both are // configured they will interfere with each other and both get incorrect values. - if (perEndpointStatsEnabled() && + // TODO(ggreenway): Verify that bypassing virtual dispatch here was intentional + if (ClusterInfoImpl::perEndpointStatsEnabled() && server_context.bootstrap().cluster_manager().has_load_stats_config()) { throwEnvoyExceptionOrPanic("Only one of cluster per_endpoint_stats and cluster manager " "load_stats_config can be specified"); @@ -1728,7 +1729,7 @@ absl::Status ClusterImplBase::parseDropOverloadConfig( return absl::OkStatus(); } const auto& policy = cluster_load_assignment.policy(); - if (policy.drop_overloads().size() == 0) { + if (policy.drop_overloads().empty()) { return absl::OkStatus(); } if (policy.drop_overloads().size() > kDropOverloadSize) { diff --git a/source/exe/stripped_main_base.cc b/source/exe/stripped_main_base.cc index 502963e897d1..0e493eeed050 100644 --- a/source/exe/stripped_main_base.cc +++ b/source/exe/stripped_main_base.cc @@ -47,7 +47,7 @@ StrippedMainBase::StrippedMainBase(const Server::Options& options, Event::TimeSy std::unique_ptr platform_impl, std::unique_ptr&& random_generator, std::unique_ptr process_context, - CreateInstanceFunction createInstance) + CreateInstanceFunction create_instance) : platform_impl_(std::move(platform_impl)), options_(options), component_factory_(component_factory), stats_allocator_(symbol_table_) { // Process the option to disable extensions as early as possible, @@ -84,10 +84,10 @@ StrippedMainBase::StrippedMainBase(const Server::Options& options, Event::TimeSy stats_store_ = std::make_unique(stats_allocator_); - server_ = createInstance(*init_manager_, options_, time_system, listener_hooks, *restarter_, - *stats_store_, access_log_lock, component_factory, - std::move(random_generator), *tls_, platform_impl_->threadFactory(), - platform_impl_->fileSystem(), std::move(process_context), nullptr); + server_ = create_instance(*init_manager_, options_, time_system, listener_hooks, *restarter_, + *stats_store_, access_log_lock, component_factory, + std::move(random_generator), *tls_, platform_impl_->threadFactory(), + platform_impl_->fileSystem(), std::move(process_context), nullptr); break; } case Server::Mode::Validate: diff --git a/source/exe/stripped_main_base.h b/source/exe/stripped_main_base.h index 0da36500ff44..81b518e0147e 100644 --- a/source/exe/stripped_main_base.h +++ b/source/exe/stripped_main_base.h @@ -53,7 +53,7 @@ class StrippedMainBase { std::unique_ptr platform_impl, std::unique_ptr&& random_generator, std::unique_ptr process_context, - CreateInstanceFunction createInstance); + CreateInstanceFunction create_instance); void runServer() { ASSERT(options_.mode() == Server::Mode::Serve); diff --git a/source/extensions/clusters/dynamic_forward_proxy/cluster.cc b/source/extensions/clusters/dynamic_forward_proxy/cluster.cc index b6c69d79e5b6..4ce4077521cf 100644 --- a/source/extensions/clusters/dynamic_forward_proxy/cluster.cc +++ b/source/extensions/clusters/dynamic_forward_proxy/cluster.cc @@ -280,7 +280,7 @@ void Cluster::addOrUpdateHost( hosts_added.emplace_back(new_host); } - ASSERT(hosts_added.size() > 0); + ASSERT(!hosts_added.empty()); updatePriorityState(hosts_added, hosts_removed); } diff --git a/source/extensions/common/aws/metadata_fetcher.cc b/source/extensions/common/aws/metadata_fetcher.cc index d5b260a16112..f5dbe85eea55 100644 --- a/source/extensions/common/aws/metadata_fetcher.cc +++ b/source/extensions/common/aws/metadata_fetcher.cc @@ -23,7 +23,8 @@ class MetadataFetcherImpl : public MetadataFetcher, MetadataFetcherImpl(Upstream::ClusterManager& cm, absl::string_view cluster_name) : cm_(cm), cluster_name_(std::string(cluster_name)) {} - ~MetadataFetcherImpl() override { cancel(); } + // TODO(suniltheta): Verify that bypassing virtual dispatch here was intentional + ~MetadataFetcherImpl() override { MetadataFetcherImpl::cancel(); } void cancel() override { if (request_ && !complete_) { diff --git a/source/extensions/common/aws/region_provider_impl.cc b/source/extensions/common/aws/region_provider_impl.cc index 4c7bdde18d70..ca7255e00843 100644 --- a/source/extensions/common/aws/region_provider_impl.cc +++ b/source/extensions/common/aws/region_provider_impl.cc @@ -134,9 +134,10 @@ absl::optional AWSConfigFileRegionProvider::getRegionSet() { // https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html // RegionProviderChain::RegionProviderChain() { - add(createEnvironmentRegionProvider()); - add(createAWSCredentialsFileRegionProvider()); - add(createAWSConfigFileRegionProvider()); + // TODO(nbaws): Verify that bypassing virtual dispatch here was intentional + add(RegionProviderChain::createEnvironmentRegionProvider()); + add(RegionProviderChain::createAWSCredentialsFileRegionProvider()); + add(RegionProviderChain::createAWSConfigFileRegionProvider()); } absl::optional RegionProviderChain::getRegion() { diff --git a/source/extensions/config_subscription/grpc/grpc_mux_failover.h b/source/extensions/config_subscription/grpc/grpc_mux_failover.h index 1700340aed11..90c5bf6648cf 100644 --- a/source/extensions/config_subscription/grpc/grpc_mux_failover.h +++ b/source/extensions/config_subscription/grpc/grpc_mux_failover.h @@ -102,26 +102,26 @@ template class GrpcMuxFailover { public: PrimaryGrpcStreamCallbacks(GrpcMuxFailover& parent) : parent_(parent) {} - virtual void onStreamEstablished() override { + void onStreamEstablished() override { // TODO(adisuissa): At the moment this is a pass-through method. Once the // implementation matures, this call will be updated. parent_.grpc_mux_callbacks_.onStreamEstablished(); } - virtual void onEstablishmentFailure() override { + void onEstablishmentFailure() override { // TODO(adisuissa): At the moment this is a pass-through method. Once the // implementation matures, this call will be updated. parent_.grpc_mux_callbacks_.onEstablishmentFailure(); } - virtual void onDiscoveryResponse(ResponseProtoPtr&& message, - ControlPlaneStats& control_plane_stats) override { + void onDiscoveryResponse(ResponseProtoPtr&& message, + ControlPlaneStats& control_plane_stats) override { // TODO(adisuissa): At the moment this is a pass-through method. Once the // implementation matures, this call will be updated. parent_.grpc_mux_callbacks_.onDiscoveryResponse(std::move(message), control_plane_stats); } - virtual void onWriteable() override { + void onWriteable() override { // TODO(adisuissa): At the moment this is a pass-through method. Once the // implementation matures, this call will be updated. parent_.grpc_mux_callbacks_.onWriteable(); diff --git a/source/extensions/config_subscription/grpc/grpc_stream_interface.h b/source/extensions/config_subscription/grpc/grpc_stream_interface.h index 3c65e3efbce5..9b49877e13b8 100644 --- a/source/extensions/config_subscription/grpc/grpc_stream_interface.h +++ b/source/extensions/config_subscription/grpc/grpc_stream_interface.h @@ -11,7 +11,7 @@ namespace Config { template class GrpcStreamInterface : public Grpc::AsyncStreamCallbacks { public: - virtual ~GrpcStreamInterface() = default; + ~GrpcStreamInterface() override = default; // Attempt to establish a new gRPC stream to the xDS server. virtual void establishNewStream() PURE; diff --git a/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc b/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc index f291f35ae700..7258f7d4006d 100644 --- a/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc +++ b/source/extensions/filters/http/aws_lambda/aws_lambda_filter.cc @@ -135,7 +135,7 @@ void Filter::resolveSettings() { if (auto route_settings = getRouteSpecificSettings()) { payload_passthrough_ = route_settings->payloadPassthrough(); invocation_mode_ = route_settings->invocationMode(); - arn_ = std::move(route_settings)->arn(); + arn_ = route_settings->arn(); host_rewrite_ = route_settings->hostRewrite(); } else { payload_passthrough_ = settings_.payloadPassthrough(); diff --git a/source/extensions/filters/http/composite/filter.cc b/source/extensions/filters/http/composite/filter.cc index 77475283214e..99c28e0bec52 100644 --- a/source/extensions/filters/http/composite/filter.cc +++ b/source/extensions/filters/http/composite/filter.cc @@ -105,26 +105,26 @@ void Filter::onMatchCallback(const Matcher::Action& action) { wrapper.errors_, [](const auto& status) { return status.ToString(); })); return; } - std::string actionName = composite_action.actionName(); + const std::string& action_name = composite_action.actionName(); if (wrapper.filter_to_inject_.has_value()) { stats_.filter_delegation_success_.inc(); auto createDelegatedFilterFn = Overloaded{ - [this, actionName](Http::StreamDecoderFilterSharedPtr filter) { + [this, action_name](Http::StreamDecoderFilterSharedPtr filter) { delegated_filter_ = std::make_shared(std::move(filter)); updateFilterState(decoder_callbacks_, std::string(decoder_callbacks_->filterConfigName()), - actionName); + action_name); }, - [this, actionName](Http::StreamEncoderFilterSharedPtr filter) { + [this, action_name](Http::StreamEncoderFilterSharedPtr filter) { delegated_filter_ = std::make_shared(std::move(filter)); updateFilterState(encoder_callbacks_, std::string(encoder_callbacks_->filterConfigName()), - actionName); + action_name); }, - [this, actionName](Http::StreamFilterSharedPtr filter) { + [this, action_name](Http::StreamFilterSharedPtr filter) { delegated_filter_ = std::move(filter); updateFilterState(decoder_callbacks_, std::string(decoder_callbacks_->filterConfigName()), - actionName); + action_name); }}; absl::visit(createDelegatedFilterFn, std::move(wrapper.filter_to_inject_.value())); diff --git a/source/extensions/filters/http/composite/filter.h b/source/extensions/filters/http/composite/filter.h index 76d2010430a5..3a07d7015c31 100644 --- a/source/extensions/filters/http/composite/filter.h +++ b/source/extensions/filters/http/composite/filter.h @@ -39,7 +39,7 @@ class MatchedActionInfo : public StreamInfo::FilterState::Object { ProtobufTypes::MessagePtr serializeAsProto() const override { return buildProtoStruct(); } absl::optional serializeAsString() const override { - return Json::Factory::loadFromProtobufStruct(*buildProtoStruct().get())->asJsonString(); + return Json::Factory::loadFromProtobufStruct(*buildProtoStruct())->asJsonString(); } void setFilterAction(const std::string& filter, const std::string& action) { diff --git a/source/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter.cc b/source/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter.cc index 56d1eebe5a62..a97d79bd9eac 100644 --- a/source/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter.cc +++ b/source/extensions/filters/http/grpc_http1_bridge/http1_bridge_filter.cc @@ -24,7 +24,7 @@ namespace GrpcHttp1Bridge { // query params here. void Http1BridgeFilter::ignoreQueryParams(Http::RequestHeaderMap& headers) { absl::string_view path = headers.getPathValue(); - size_t pos = path.find("?"); + size_t pos = path.find('?'); if (pos != absl::string_view::npos) { absl::string_view new_path = path.substr(0, pos); headers.setPath(new_path); diff --git a/source/extensions/filters/http/jwt_authn/authenticator.cc b/source/extensions/filters/http/jwt_authn/authenticator.cc index 4239e0ebc6d3..3e04ca79bd16 100644 --- a/source/extensions/filters/http/jwt_authn/authenticator.cc +++ b/source/extensions/filters/http/jwt_authn/authenticator.cc @@ -405,7 +405,7 @@ void AuthenticatorImpl::handleGoodJwt(bool cache_hit) { void AuthenticatorImpl::setPayloadMetadata(const ProtobufWkt::Struct& jwt_payload) { const auto& provider = jwks_data_->getJwtProvider(); const auto& normalize = provider.normalize_payload_in_metadata(); - if (normalize.space_delimited_claims().size() == 0) { + if (normalize.space_delimited_claims().empty()) { set_extracted_jwt_data_cb_(provider.payload_in_metadata(), jwt_payload); } // Make a temporary copy to normalize the JWT struct. diff --git a/source/extensions/filters/http/oauth2/filter.cc b/source/extensions/filters/http/oauth2/filter.cc index d451be546e9d..ddf129b802df 100644 --- a/source/extensions/filters/http/oauth2/filter.cc +++ b/source/extensions/filters/http/oauth2/filter.cc @@ -258,8 +258,8 @@ bool OAuth2CookieValidator::isValid() const { return hmacIsValid() && timestampI OAuth2Filter::OAuth2Filter(FilterConfigSharedPtr config, std::unique_ptr&& oauth_client, TimeSource& time_source) : validator_(std::make_shared(time_source, config->cookieNames())), - was_refresh_token_flow_(false), oauth_client_(std::move(oauth_client)), - config_(std::move(config)), time_source_(time_source) { + oauth_client_(std::move(oauth_client)), config_(std::move(config)), + time_source_(time_source) { oauth_client_->setCallbacks(*this); } diff --git a/source/extensions/filters/http/oauth2/filter.h b/source/extensions/filters/http/oauth2/filter.h index d874f9f1bf66..715cd3230cdd 100644 --- a/source/extensions/filters/http/oauth2/filter.h +++ b/source/extensions/filters/http/oauth2/filter.h @@ -287,7 +287,7 @@ class OAuth2Filter : public Http::PassThroughFilter, absl::string_view host_; std::string state_; Http::RequestHeaderMap* request_headers_{nullptr}; - bool was_refresh_token_flow_; + bool was_refresh_token_flow_{false}; std::unique_ptr oauth_client_; FilterConfigSharedPtr config_; diff --git a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.cc b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.cc index 64d30c4a3d77..da4af15771af 100644 --- a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.cc +++ b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.cc @@ -314,7 +314,7 @@ FilterStatus PayloadToMetadataFilter::messageBegin(MessageMetadataSharedPtr meta return FilterStatus::Continue; } -static std::string get_hex_representation(Buffer::Instance& data) { +static std::string getHexRepresentation(Buffer::Instance& data) { void* buf = data.linearize(static_cast(data.length())); const unsigned char* linearized_data = static_cast(buf); std::stringstream hex_stream; @@ -348,7 +348,7 @@ FilterStatus PayloadToMetadataFilter::passthroughData(Buffer::Instance& data) { END_TRY CATCH(const EnvoyException& e, { IS_ENVOY_BUG(fmt::format("decoding error, error_message: {}, payload: {}", e.what(), - get_hex_representation(data))); + getHexRepresentation(data))); if (!handler.isComplete()) { handleOnMissing(); } diff --git a/source/extensions/filters/udp/udp_proxy/config.cc b/source/extensions/filters/udp/udp_proxy/config.cc index 3f8e76230464..5675cdc4c537 100644 --- a/source/extensions/filters/udp/udp_proxy/config.cc +++ b/source/extensions/filters/udp/udp_proxy/config.cc @@ -33,7 +33,7 @@ const std::string& TunnelResponseTrailers::key() { TunnelingConfigImpl::TunnelingConfigImpl(const TunnelingConfig& config, Server::Configuration::FactoryContext& context) : header_parser_(Envoy::Router::HeaderParser::configure(config.headers_to_add())), - proxy_port_(), target_port_(config.default_target_port()), use_post_(config.use_post()), + target_port_(config.default_target_port()), use_post_(config.use_post()), post_path_(config.post_path()), max_connect_attempts_(config.has_retry_options() ? PROTOBUF_GET_WRAPPED_OR_DEFAULT(config.retry_options(), @@ -59,7 +59,7 @@ TunnelingConfigImpl::TunnelingConfigImpl(const TunnelingConfig& config, if (post_path_.empty()) { post_path_ = "/"; - } else if (post_path_.rfind("/", 0) != 0) { + } else if (!absl::StartsWith(post_path_, "/")) { throw EnvoyException("Path must start with '/'"); } diff --git a/source/extensions/filters/udp/udp_proxy/session_filters/filter.h b/source/extensions/filters/udp/udp_proxy/session_filters/filter.h index f9d5af16215e..8cedad217b15 100644 --- a/source/extensions/filters/udp/udp_proxy/session_filters/filter.h +++ b/source/extensions/filters/udp/udp_proxy/session_filters/filter.h @@ -90,7 +90,7 @@ class FilterBase { */ class ReadFilter : public virtual FilterBase { public: - virtual ~ReadFilter() = default; + ~ReadFilter() override = default; /** * Called when a new UDP session is first established. Filters should do one time long term @@ -136,7 +136,7 @@ enum class WriteFilterStatus { */ class WriteFilter : public virtual FilterBase { public: - virtual ~WriteFilter() = default; + ~WriteFilter() override = default; /** * Called when data is to be written on the UDP session. diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc index 65402058363f..7e8ba256ebc5 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.cc @@ -284,7 +284,7 @@ UdpProxyFilter::ActiveSession::ActiveSession(ClusterInfo& cluster, idle_timer_(cluster.filter_.read_callbacks_->udpListener().dispatcher().createTimer( [this] { onIdleTimer(); })), udp_session_info_(StreamInfo::StreamInfoImpl(cluster_.filter_.config_->timeSource(), - CreateDownstreamConnectionInfoProvider())) { + createDownstreamConnectionInfoProvider())) { udp_session_info_.setUpstreamInfo(std::make_shared()); cluster_.filter_.config_->stats().downstream_sess_total_.inc(); cluster_.filter_.config_->stats().downstream_sess_active_.inc(); @@ -338,7 +338,7 @@ void UdpProxyFilter::ActiveSession::onSessionComplete() { } std::shared_ptr -UdpProxyFilter::ActiveSession::CreateDownstreamConnectionInfoProvider() { +UdpProxyFilter::ActiveSession::createDownstreamConnectionInfoProvider() { auto downstream_connection_info_provider = std::make_shared(addresses_.local_, addresses_.peer_); downstream_connection_info_provider->setConnectionID(session_id_); diff --git a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h index 9f7da664bc97..01d76a739ae0 100644 --- a/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h +++ b/source/extensions/filters/udp/udp_proxy/udp_proxy_filter.h @@ -612,7 +612,7 @@ class UdpProxyFilter : public Network::UdpListenerReadFilter, std::list write_filters_; private: - std::shared_ptr CreateDownstreamConnectionInfoProvider(); + std::shared_ptr createDownstreamConnectionInfoProvider(); void onAccessLogFlushInterval(); void rearmAccessLogFlushTimer(); void disableAccessLogFlushTimer(); diff --git a/source/extensions/geoip_providers/maxmind/geoip_provider.h b/source/extensions/geoip_providers/maxmind/geoip_provider.h index db5ae83f72c9..25f5c11b3a51 100644 --- a/source/extensions/geoip_providers/maxmind/geoip_provider.h +++ b/source/extensions/geoip_providers/maxmind/geoip_provider.h @@ -88,7 +88,7 @@ class GeoipProvider : public Envoy::Geolocation::Driver, anon_db_ = initMaxMindDb(config_->anonDbPath()); }; - ~GeoipProvider(); + ~GeoipProvider() override; // Envoy::Geolocation::Driver void lookup(Geolocation::LookupRequest&&, Geolocation::LookupGeoHeadersCallback&&) const override; diff --git a/source/extensions/grpc_credentials/aws_iam/config.cc b/source/extensions/grpc_credentials/aws_iam/config.cc index e1daa71d3b79..4e8127a19749 100644 --- a/source/extensions/grpc_credentials/aws_iam/config.cc +++ b/source/extensions/grpc_credentials/aws_iam/config.cc @@ -105,9 +105,7 @@ AwsIamHeaderAuthenticator::GetMetadata(grpc::string_ref service_url, grpc::strin absl::string_view(method_name.data(), method_name.length())); TRY_NEEDS_AUDIT { signer_->sign(message, false); } - END_TRY catch (const EnvoyException& e) { - return grpc::Status(grpc::StatusCode::INTERNAL, e.what()); - } + END_TRY catch (const EnvoyException& e) { return {grpc::StatusCode::INTERNAL, e.what()}; } signedHeadersToMetadata(message.headers(), *metadata); diff --git a/source/extensions/tracers/datadog/tracer.cc b/source/extensions/tracers/datadog/tracer.cc index e01c30911899..280647595558 100644 --- a/source/extensions/tracers/datadog/tracer.cc +++ b/source/extensions/tracers/datadog/tracer.cc @@ -100,7 +100,7 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext TraceContextReader reader{trace_context}; datadog::tracing::Span span = - extract_or_create_span(*thread_local_tracer.tracer, span_config, reader); + extractOrCreateSpan(*thread_local_tracer.tracer, span_config, reader); // If we did not extract a sampling decision, and if Envoy is telling us to // drop the trace, then we treat that as a "user drop" (manual override). @@ -115,10 +115,9 @@ Tracing::SpanPtr Tracer::startSpan(const Tracing::Config&, Tracing::TraceContext return std::make_unique(std::move(span)); } -datadog::tracing::Span -Tracer::extract_or_create_span(datadog::tracing::Tracer& tracer, - const datadog::tracing::SpanConfig& span_config, - const datadog::tracing::DictReader& reader) { +datadog::tracing::Span Tracer::extractOrCreateSpan(datadog::tracing::Tracer& tracer, + const datadog::tracing::SpanConfig& span_config, + const datadog::tracing::DictReader& reader) { datadog::tracing::Expected maybe_span = tracer.extract_span(reader, span_config); if (datadog::tracing::Error* error = maybe_span.if_error()) { diff --git a/source/extensions/tracers/datadog/tracer.h b/source/extensions/tracers/datadog/tracer.h index 7baf3d6c1277..697fd09f5d9b 100644 --- a/source/extensions/tracers/datadog/tracer.h +++ b/source/extensions/tracers/datadog/tracer.h @@ -98,9 +98,9 @@ class Tracer : public Tracing::Driver, private Logger::Loggable thread_local_slot_; diff --git a/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc index 8d06116cceb3..d1965b7812de 100644 --- a/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc +++ b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.cc @@ -18,7 +18,7 @@ SamplingResult AlwaysOnSampler::shouldSample(const absl::optional p OptRef /*trace_context*/, const std::vector& /*links*/) { SamplingResult result; - result.decision = Decision::RECORD_AND_SAMPLE; + result.decision = Decision::RecordAndSample; if (parent_context.has_value()) { result.tracestate = parent_context.value().tracestate(); } diff --git a/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h index c70864b078a0..cb0bb42deefe 100644 --- a/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler.h @@ -14,7 +14,7 @@ namespace OpenTelemetry { /** * @brief A sampler which samples every span. * https://opentelemetry.io/docs/specs/otel/trace/sdk/#alwayson - * - Returns RECORD_AND_SAMPLE always. + * - Returns RecordAndSample always. * - Description MUST be AlwaysOnSampler. * */ diff --git a/source/extensions/tracers/opentelemetry/samplers/sampler.h b/source/extensions/tracers/opentelemetry/samplers/sampler.h index 33794645344e..e20fc0207e77 100644 --- a/source/extensions/tracers/opentelemetry/samplers/sampler.h +++ b/source/extensions/tracers/opentelemetry/samplers/sampler.h @@ -23,11 +23,11 @@ class SpanContext; enum class Decision { // IsRecording will be false, the Span will not be recorded and all events and attributes will be // dropped. - DROP, + Drop, // IsRecording will be true, but the Sampled flag MUST NOT be set. - RECORD_ONLY, + RecordOnly, // IsRecording will be true and the Sampled flag MUST be set. - RECORD_AND_SAMPLE + RecordAndSample }; /** @@ -48,10 +48,10 @@ struct SamplingResult { std::string tracestate; inline bool isRecording() const { - return decision == Decision::RECORD_ONLY || decision == Decision::RECORD_AND_SAMPLE; + return decision == Decision::RecordOnly || decision == Decision::RecordAndSample; } - inline bool isSampled() const { return decision == Decision::RECORD_AND_SAMPLE; } + inline bool isSampled() const { return decision == Decision::RecordAndSample; } }; /** diff --git a/source/extensions/upstreams/http/config.cc b/source/extensions/upstreams/http/config.cc index b1e8deaaedbd..66b31e48c632 100644 --- a/source/extensions/upstreams/http/config.cc +++ b/source/extensions/upstreams/http/config.cc @@ -228,7 +228,7 @@ ProtocolOptionsConfigImpl::ProtocolOptionsConfigImpl( use_downstream_protocol_(options.has_use_downstream_protocol_config()), use_http2_(useHttp2(options)), use_http3_(useHttp3(options)), use_alpn_(options.has_auto_config()) { - ASSERT(Http2::Utility::initializeAndValidateOptions(http2_options).status().ok()); + ASSERT(Http2::Utility::initializeAndValidateOptions(http2_options_).status().ok()); } ProtocolOptionsConfigImpl::ProtocolOptionsConfigImpl( diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index 47cc069270bd..a8685dbd28eb 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -114,7 +114,7 @@ void ValidationInstance::initialize(const Options& options, messageValidationContext().staticValidationVisitor(), *api_, options_); absl::Status creation_status = absl::OkStatus(); Configuration::InitialImpl initial_config(bootstrap_, creation_status); - THROW_IF_NOT_OK(creation_status); + THROW_IF_NOT_OK_REF(creation_status); AdminFactoryContext factory_context(*this, std::make_shared()); initial_config.initAdminAccessLog(bootstrap_, factory_context); admin_ = std::make_unique(initial_config.admin().address()); diff --git a/source/server/server.cc b/source/server/server.cc index 5b73a573b9c6..205823a43f6c 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -585,7 +585,7 @@ void InstanceBase::initializeOrThrow(Network::Address::InstanceConstSharedPtr lo absl::Status creation_status; Configuration::InitialImpl initial_config(bootstrap_, creation_status); - THROW_IF_NOT_OK(creation_status); + THROW_IF_NOT_OK_REF(creation_status); // Learn original_start_time_ if our parent is still around to inform us of it. const auto parent_admin_shutdown_response = restarter_.sendParentAdminShutdownRequest(); diff --git a/test/config_test/config_test.cc b/test/config_test/config_test.cc index c61c3168e8b8..c1e484675815 100644 --- a/test/config_test/config_test.cc +++ b/test/config_test/config_test.cc @@ -101,7 +101,7 @@ class ConfigTest { bootstrap, options_, server_.messageValidationContext().staticValidationVisitor(), *api_); absl::Status creation_status; Server::Configuration::InitialImpl initial_config(bootstrap, creation_status); - THROW_IF_NOT_OK(creation_status); + THROW_IF_NOT_OK_REF(creation_status); Server::Configuration::MainImpl main_config; // Emulate main implementation of initializing bootstrap extensions. diff --git a/test/extensions/config_subscription/grpc/grpc_mux_failover_test.cc b/test/extensions/config_subscription/grpc/grpc_mux_failover_test.cc index a4e5ff3a71a0..660d21cbe577 100644 --- a/test/extensions/config_subscription/grpc/grpc_mux_failover_test.cc +++ b/test/extensions/config_subscription/grpc/grpc_mux_failover_test.cc @@ -24,7 +24,7 @@ class GrpcMuxFailoverTest : public testing::Test { GrpcMuxFailoverTest() // The GrpcMuxFailover test uses a the GrpcMuxFailover with mocked GrpcStream objects. : primary_stream_owner_(std::make_unique>()), - primary_stream_(*primary_stream_owner_.get()), + primary_stream_(*primary_stream_owner_), grpc_mux_failover_( /*primary_stream_creator=*/ [this](GrpcStreamCallbacks* callbacks) diff --git a/test/extensions/config_subscription/grpc/mocks.h b/test/extensions/config_subscription/grpc/mocks.h index a4dea348a144..dededb9937d6 100644 --- a/test/extensions/config_subscription/grpc/mocks.h +++ b/test/extensions/config_subscription/grpc/mocks.h @@ -10,7 +10,7 @@ namespace Config { template class MockGrpcStream : public GrpcStreamInterface { public: - MockGrpcStream() {} + MockGrpcStream() = default; ~MockGrpcStream() override = default; MOCK_METHOD(void, establishNewStream, ()); diff --git a/test/extensions/filters/http/compressor/compressor_filter_speed_test.cc b/test/extensions/filters/http/compressor/compressor_filter_speed_test.cc index f4a5d75d824d..cdb1f93aeb3d 100644 --- a/test/extensions/filters/http/compressor/compressor_filter_speed_test.cc +++ b/test/extensions/filters/http/compressor/compressor_filter_speed_test.cc @@ -353,7 +353,8 @@ static void compressFullWithGzip(benchmark::State& state) { const auto idx = state.range(0); const auto& params = gzip_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(1, 122880); compressWith(CompressorLibs::Gzip, std::move(chunks), params, decoder_callbacks, state); } @@ -369,7 +370,8 @@ static void compressChunks16384WithGzip(benchmark::State& state) { const auto idx = state.range(0); const auto& params = gzip_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(7, 16384); compressWith(CompressorLibs::Gzip, std::move(chunks), params, decoder_callbacks, state); } @@ -385,7 +387,8 @@ static void compressChunks8192WithGzip(benchmark::State& state) { const auto idx = state.range(0); const auto& params = gzip_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(15, 8192); compressWith(CompressorLibs::Gzip, std::move(chunks), params, decoder_callbacks, state); } @@ -401,7 +404,8 @@ static void compressChunks4096WithGzip(benchmark::State& state) { const auto idx = state.range(0); const auto& params = gzip_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(30, 4096); compressWith(CompressorLibs::Gzip, std::move(chunks), params, decoder_callbacks, state); } @@ -417,7 +421,8 @@ static void compressChunks1024WithGzip(benchmark::State& state) { const auto idx = state.range(0); const auto& params = gzip_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(120, 1024); compressWith(CompressorLibs::Gzip, std::move(chunks), params, decoder_callbacks, state); } @@ -500,7 +505,8 @@ static void compressFullWithZstd(benchmark::State& state) { const auto idx = state.range(0); const auto& params = zstd_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(1, 122880); compressWith(CompressorLibs::Zstd, std::move(chunks), params, decoder_callbacks, state); } @@ -516,7 +522,8 @@ static void compressChunks16384WithZstd(benchmark::State& state) { const auto idx = state.range(0); const auto& params = zstd_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(7, 16384); compressWith(CompressorLibs::Zstd, std::move(chunks), params, decoder_callbacks, state); } @@ -532,7 +539,8 @@ static void compressChunks8192WithZstd(benchmark::State& state) { const auto idx = state.range(0); const auto& params = zstd_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(15, 8192); compressWith(CompressorLibs::Zstd, std::move(chunks), params, decoder_callbacks, state); } @@ -548,7 +556,8 @@ static void compressChunks4096WithZstd(benchmark::State& state) { const auto idx = state.range(0); const auto& params = zstd_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(30, 4096); compressWith(CompressorLibs::Zstd, std::move(chunks), params, decoder_callbacks, state); } @@ -564,7 +573,8 @@ static void compressChunks1024WithZstd(benchmark::State& state) { const auto idx = state.range(0); const auto& params = zstd_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(120, 1024); compressWith(CompressorLibs::Zstd, std::move(chunks), params, decoder_callbacks, state); } @@ -614,7 +624,8 @@ static void compressFullWithBrotli(benchmark::State& state) { const auto idx = state.range(0); const auto& params = brotli_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(1, 122880); compressWith(CompressorLibs::Brotli, std::move(chunks), params, decoder_callbacks, state); } @@ -630,7 +641,8 @@ static void compressChunks16384WithBrotli(benchmark::State& state) { const auto idx = state.range(0); const auto& params = brotli_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(7, 16384); compressWith(CompressorLibs::Brotli, std::move(chunks), params, decoder_callbacks, state); } @@ -646,7 +658,8 @@ static void compressChunks8192WithBrotli(benchmark::State& state) { const auto idx = state.range(0); const auto& params = brotli_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(15, 8192); compressWith(CompressorLibs::Brotli, std::move(chunks), params, decoder_callbacks, state); } @@ -662,7 +675,8 @@ static void compressChunks4096WithBrotli(benchmark::State& state) { const auto idx = state.range(0); const auto& params = brotli_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(30, 4096); compressWith(CompressorLibs::Brotli, std::move(chunks), params, decoder_callbacks, state); } @@ -678,7 +692,8 @@ static void compressChunks1024WithBrotli(benchmark::State& state) { const auto idx = state.range(0); const auto& params = brotli_compression_params[idx]; - for (auto _ : state) { // NOLINT + for (auto _ : state) { + UNREFERENCED_PARAMETER(_); std::vector chunks = generateChunks(120, 1024); compressWith(CompressorLibs::Brotli, std::move(chunks), params, decoder_callbacks, state); } diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index 907f52aef5c6..e319a9aef369 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -470,7 +470,7 @@ class HttpFilterTest : public testing::Test { } } - void StreamingSmallChunksWithBodyMutation(bool empty_last_chunk, bool mutate_last_chunk) { + void streamingSmallChunksWithBodyMutation(bool empty_last_chunk, bool mutate_last_chunk) { initialize(R"EOF( grpc_service: envoy_grpc: @@ -1502,19 +1502,19 @@ TEST_F(HttpFilterTest, StreamingDataSmallChunk) { } TEST_F(HttpFilterTest, StreamingBodyMutateLastEmptyChunk) { - StreamingSmallChunksWithBodyMutation(true, true); + streamingSmallChunksWithBodyMutation(true, true); } TEST_F(HttpFilterTest, StreamingBodyNotMutateLastEmptyChunk) { - StreamingSmallChunksWithBodyMutation(true, false); + streamingSmallChunksWithBodyMutation(true, false); } TEST_F(HttpFilterTest, StreamingBodyMutateLastChunk) { - StreamingSmallChunksWithBodyMutation(false, true); + streamingSmallChunksWithBodyMutation(false, true); } TEST_F(HttpFilterTest, StreamingBodyNotMutateLastChunk) { - StreamingSmallChunksWithBodyMutation(false, false); + streamingSmallChunksWithBodyMutation(false, false); } // gRPC call fails when streaming sends small chunk request data. diff --git a/test/extensions/filters/http/ext_proc/streaming_integration_test.cc b/test/extensions/filters/http/ext_proc/streaming_integration_test.cc index 5ebbc8ab3225..e8b4182ecd6f 100644 --- a/test/extensions/filters/http/ext_proc/streaming_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/streaming_integration_test.cc @@ -36,7 +36,9 @@ class StreamingIntegrationTest : public HttpIntegrationTest, protected: StreamingIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP2, ipVersion()) {} - ~StreamingIntegrationTest() { TearDown(); } + // TODO(yanjunxiang-google): Verify that bypassing virtual dispatch here was intentional + // NOLINTNEXTLINE(clang-analyzer-optin.cplusplus.VirtualCall) + ~StreamingIntegrationTest() override { StreamingIntegrationTest::TearDown(); } void TearDown() override { cleanupUpstreamAndDownstream(); diff --git a/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc b/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc index d315b6078472..c561e5d6e815 100644 --- a/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc +++ b/test/extensions/filters/http/geoip/geoip_filter_integration_test.cc @@ -7,9 +7,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::DoAll; -using testing::SaveArg; - namespace Envoy { namespace Extensions { namespace HttpFilters { diff --git a/test/extensions/filters/http/header_mutation/header_mutation_integration_test.cc b/test/extensions/filters/http/header_mutation/header_mutation_integration_test.cc index 2b758fe86d33..781c8d2d0c7a 100644 --- a/test/extensions/filters/http/header_mutation/header_mutation_integration_test.cc +++ b/test/extensions/filters/http/header_mutation/header_mutation_integration_test.cc @@ -429,7 +429,7 @@ INSTANTIATE_TEST_SUITE_P(IpVersions, HeaderMutationIntegrationTest, testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), TestUtility::ipTestParamsToString); -void TestResponseHeaderMutation(IntegrationStreamDecoder* response, RouteLevelFlag route_level) { +void testResponseHeaderMutation(IntegrationStreamDecoder* response, RouteLevelFlag route_level) { if (route_level.test(RouteLevel::PerRoute)) { EXPECT_EQ("downstream-per-route-flag-header-value", response->headers() @@ -545,7 +545,7 @@ TEST_P(HeaderMutationIntegrationTest, TestHeaderMutationAllLevelsApplied) { ->value() .getStringView()); - TestResponseHeaderMutation(response.get(), AllRoutesLevel); + testResponseHeaderMutation(response.get(), AllRoutesLevel); EXPECT_EQ("GET", response->headers() .get(Http::LowerCaseString("request-method-in-upstream-filter"))[0] @@ -653,7 +653,7 @@ TEST_P(HeaderMutationIntegrationTest, TestHeaderMutationPerRoute) { ->value() .getStringView()); - TestResponseHeaderMutation(response.get(), PerRouteLevel); + testResponseHeaderMutation(response.get(), PerRouteLevel); EXPECT_EQ("GET", response->headers() .get(Http::LowerCaseString("request-method-in-upstream-filter"))[0] @@ -696,7 +696,7 @@ TEST_P(HeaderMutationIntegrationTest, TestHeaderMutationPerVirtualHost) { .get(Http::LowerCaseString("upstream-global-flag-header"))[0] ->value() .getStringView()); - TestResponseHeaderMutation(response.get(), VirtualHostLevel); + testResponseHeaderMutation(response.get(), VirtualHostLevel); EXPECT_EQ("GET", response->headers() .get(Http::LowerCaseString("request-method-in-upstream-filter"))[0] @@ -740,7 +740,7 @@ TEST_P(HeaderMutationIntegrationTest, TestHeaderMutationPerRouteTable) { ->value() .getStringView()); - TestResponseHeaderMutation(response.get(), RouteTableLevel); + testResponseHeaderMutation(response.get(), RouteTableLevel); EXPECT_EQ("GET", response->headers() .get(Http::LowerCaseString("request-method-in-upstream-filter"))[0] diff --git a/test/extensions/filters/http/jwt_authn/filter_config_test.cc b/test/extensions/filters/http/jwt_authn/filter_config_test.cc index b3969774d375..c5668a038c19 100644 --- a/test/extensions/filters/http/jwt_authn/filter_config_test.cc +++ b/test/extensions/filters/http/jwt_authn/filter_config_test.cc @@ -14,7 +14,6 @@ using envoy::extensions::filters::http::jwt_authn::v3::JwtAuthentication; using envoy::extensions::filters::http::jwt_authn::v3::PerRouteConfig; using testing::HasSubstr; -using testing::ReturnRef; namespace Envoy { namespace Extensions { diff --git a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD index c5a27baefdf6..d1a7fada9d12 100644 --- a/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD +++ b/test/extensions/filters/udp/udp_proxy/session_filters/dynamic_forward_proxy/BUILD @@ -20,7 +20,7 @@ envoy_proto_library( envoy_cc_test_library( name = "dfp_setter_filter_config_lib", - srcs = ["dfp_setter.h"], + hdrs = ["dfp_setter.h"], deps = [ ":dfp_setter_filter_proto_cc_proto", "//envoy/registry", diff --git a/test/extensions/tracers/datadog/config_test.cc b/test/extensions/tracers/datadog/config_test.cc index 59e1e17ecea8..2d11bd74f979 100644 --- a/test/extensions/tracers/datadog/config_test.cc +++ b/test/extensions/tracers/datadog/config_test.cc @@ -251,8 +251,8 @@ TEST_F(DatadogConfigTest, CollectorHostname) { timer_->invokeCallback(); - msg.reset(new Http::ResponseMessageImpl( - Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + msg = std::make_unique( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}}); msg->body().add("{}"); callbacks->onSuccess(request, std::move(msg)); diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_metadata_file_reader_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_metadata_file_reader_test.cc index 58132e907064..be39a3341f2b 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_metadata_file_reader_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_metadata_file_reader_test.cc @@ -8,8 +8,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -using testing::Return; - namespace Envoy { namespace Extensions { namespace Tracers { diff --git a/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_test.cc index 79d37ca9bfe8..81c15ff7f0e4 100644 --- a/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/always_on/always_on_sampler_test.cc @@ -25,7 +25,7 @@ TEST(AlwaysOnSamplerTest, TestWithInvalidParentContext) { auto sampling_result = sampler->shouldSample(absl::nullopt, "operation_name", "12345", ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); - EXPECT_EQ(sampling_result.decision, Decision::RECORD_AND_SAMPLE); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes, nullptr); EXPECT_STREQ(sampling_result.tracestate.c_str(), ""); EXPECT_TRUE(sampling_result.isRecording()); @@ -43,7 +43,7 @@ TEST(AlwaysOnSamplerTest, TestWithValidParentContext) { auto sampling_result = sampler->shouldSample(span_context, "operation_name", "12345", ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); - EXPECT_EQ(sampling_result.decision, Decision::RECORD_AND_SAMPLE); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); EXPECT_EQ(sampling_result.attributes, nullptr); EXPECT_STREQ(sampling_result.tracestate.c_str(), "some_tracestate"); EXPECT_TRUE(sampling_result.isRecording()); diff --git a/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc index a8ff62c71d85..687e63617050 100644 --- a/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc @@ -132,13 +132,13 @@ TEST_F(SamplerFactoryTest, TestWithSampler) { auto driver = std::make_unique(opentelemetry_config, context); - // shouldSample returns a result without additional attributes and Decision::RECORD_AND_SAMPLE + // shouldSample returns a result without additional attributes and Decision::RecordAndSample EXPECT_CALL(*test_sampler, shouldSample(_, _, _, _, _, _)) .WillOnce([](const absl::optional, const std::string&, const std::string&, OTelSpanKind, OptRef, const std::vector&) { SamplingResult res; - res.decision = Decision::RECORD_AND_SAMPLE; + res.decision = Decision::RecordAndSample; res.tracestate = "this_is=tracesate"; return res; }); @@ -152,13 +152,13 @@ TEST_F(SamplerFactoryTest, TestWithSampler) { EXPECT_TRUE(span->sampled()); EXPECT_STREQ(span->tracestate().c_str(), "this_is=tracesate"); - // shouldSamples return a result containing additional attributes and Decision::DROP + // shouldSamples return a result containing additional attributes and Decision::Drop EXPECT_CALL(*test_sampler, shouldSample(_, _, _, _, _, _)) .WillOnce([](const absl::optional, const std::string&, const std::string&, OTelSpanKind, OptRef, const std::vector&) { SamplingResult res; - res.decision = Decision::DROP; + res.decision = Decision::Drop; std::map attributes; attributes["key"] = "value"; attributes["another_key"] = "another_value"; @@ -208,13 +208,13 @@ TEST_F(SamplerFactoryTest, TestInitialAttributes) { // Test sampling result decision TEST(SamplingResultTest, TestSamplingResult) { SamplingResult result; - result.decision = Decision::RECORD_AND_SAMPLE; + result.decision = Decision::RecordAndSample; EXPECT_TRUE(result.isRecording()); EXPECT_TRUE(result.isSampled()); - result.decision = Decision::RECORD_ONLY; + result.decision = Decision::RecordOnly; EXPECT_TRUE(result.isRecording()); EXPECT_FALSE(result.isSampled()); - result.decision = Decision::DROP; + result.decision = Decision::Drop; EXPECT_FALSE(result.isRecording()); EXPECT_FALSE(result.isSampled()); } diff --git a/test/extensions/transport_sockets/alts/alts_channel_pool_test.cc b/test/extensions/transport_sockets/alts/alts_channel_pool_test.cc index 86c76e0b4e2d..a84b13695e80 100644 --- a/test/extensions/transport_sockets/alts/alts_channel_pool_test.cc +++ b/test/extensions/transport_sockets/alts/alts_channel_pool_test.cc @@ -31,7 +31,6 @@ using ::grpc::gcp::HandshakerReq; using ::grpc::gcp::HandshakerResp; using ::grpc::gcp::HandshakerService; using ::testing::NotNull; -using ::testing::Test; class FakeHandshakerService final : public HandshakerService::Service { public: diff --git a/test/extensions/transport_sockets/alts/alts_integration_test.cc b/test/extensions/transport_sockets/alts/alts_integration_test.cc index 0b86fe77e5b0..22080ad3b0b2 100644 --- a/test/extensions/transport_sockets/alts/alts_integration_test.cc +++ b/test/extensions/transport_sockets/alts/alts_integration_test.cc @@ -127,12 +127,14 @@ class FakeHandshakerService : public HandshakerService::Service { grpc::gcp::HandshakerReq request; grpc::gcp::HandshakerResp response; while (stream->Read(&request)) { - status = ProcessRequest(&context, request, &response); - if (!status.ok()) - return WriteErrorResponse(stream, status); + status = processRequest(&context, request, &response); + if (!status.ok()) { + return writeErrorResponse(stream, status); + } stream->Write(response); - if (context.state == HandshakeState::COMPLETED) + if (context.state == HandshakeState::COMPLETED) { return grpc::Status::OK; + } request.Clear(); } return grpc::Status::OK; @@ -152,33 +154,31 @@ class FakeHandshakerService : public HandshakerService::Service { HandshakeState state = HandshakeState::INITIAL; }; - grpc::Status ProcessRequest(HandshakerContext* context, const grpc::gcp::HandshakerReq& request, + grpc::Status processRequest(HandshakerContext* context, const grpc::gcp::HandshakerReq& request, grpc::gcp::HandshakerResp* response) { response->Clear(); if (request.has_client_start()) { - return ProcessClientStart(context, request.client_start(), response); + return processClientStart(context, request.client_start(), response); } else if (request.has_server_start()) { - return ProcessServerStart(context, request.server_start(), response); + return processServerStart(context, request.server_start(), response); } else if (request.has_next()) { - return ProcessNext(context, request.next(), response); + return processNext(context, request.next(), response); } - return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Request is empty."); + return {grpc::StatusCode::INVALID_ARGUMENT, "Request is empty."}; } - grpc::Status ProcessClientStart(HandshakerContext* context, + grpc::Status processClientStart(HandshakerContext* context, const grpc::gcp::StartClientHandshakeReq& request, grpc::gcp::HandshakerResp* response) { // Checks request. if (context->state != HandshakeState::INITIAL) { - return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, kWrongStateError); + return {grpc::StatusCode::FAILED_PRECONDITION, kWrongStateError}; } if (request.application_protocols_size() == 0) { - return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "At least one application protocol needed."); + return {grpc::StatusCode::INVALID_ARGUMENT, "At least one application protocol needed."}; } if (request.record_protocols_size() == 0) { - return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "At least one record protocol needed."); + return {grpc::StatusCode::INVALID_ARGUMENT, "At least one record protocol needed."}; } // Sets response. response->set_out_frames(kClientInitFrame); @@ -190,20 +190,19 @@ class FakeHandshakerService : public HandshakerService::Service { return grpc::Status::OK; } - grpc::Status ProcessServerStart(HandshakerContext* context, + grpc::Status processServerStart(HandshakerContext* context, const grpc::gcp::StartServerHandshakeReq& request, grpc::gcp::HandshakerResp* response) { // Checks request. if (context->state != HandshakeState::INITIAL) { - return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, kWrongStateError); + return {grpc::StatusCode::FAILED_PRECONDITION, kWrongStateError}; } if (request.application_protocols_size() == 0) { - return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "At least one application protocol needed."); + return {grpc::StatusCode::INVALID_ARGUMENT, "At least one application protocol needed."}; } if (request.handshake_parameters().empty()) { - return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, - "At least one set of handshake parameters needed."); + return {grpc::StatusCode::INVALID_ARGUMENT, + "At least one set of handshake parameters needed."}; } // Sets response. if (request.in_bytes().empty()) { @@ -217,7 +216,7 @@ class FakeHandshakerService : public HandshakerService::Service { response->set_bytes_consumed(strlen(kClientInitFrame)); context->state = HandshakeState::SENT; } else { - return grpc::Status(grpc::StatusCode::UNKNOWN, kInvalidFrameError); + return {grpc::StatusCode::UNKNOWN, kInvalidFrameError}; } } response->mutable_status()->set_code(grpc::StatusCode::OK); @@ -225,16 +224,16 @@ class FakeHandshakerService : public HandshakerService::Service { return grpc::Status::OK; } - grpc::Status ProcessNext(HandshakerContext* context, + grpc::Status processNext(HandshakerContext* context, const grpc::gcp::NextHandshakeMessageReq& request, grpc::gcp::HandshakerResp* response) { if (context->is_client) { // Processes next request on client side. if (context->state != HandshakeState::SENT) { - return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, kWrongStateError); + return {grpc::StatusCode::FAILED_PRECONDITION, kWrongStateError}; } if (request.in_bytes() != kServerFrame) { - return grpc::Status(grpc::StatusCode::UNKNOWN, kInvalidFrameError); + return {grpc::StatusCode::UNKNOWN, kInvalidFrameError}; } response->set_out_frames(kClientFinishFrame); response->set_bytes_consumed(strlen(kServerFrame)); @@ -244,7 +243,7 @@ class FakeHandshakerService : public HandshakerService::Service { HandshakeState current_state = context->state; if (current_state == HandshakeState::STARTED) { if (request.in_bytes() != kClientInitFrame) { - return grpc::Status(grpc::StatusCode::UNKNOWN, kInvalidFrameError); + return {grpc::StatusCode::UNKNOWN, kInvalidFrameError}; } response->set_out_frames(kServerFrame); response->set_bytes_consumed(strlen(kClientInitFrame)); @@ -253,23 +252,23 @@ class FakeHandshakerService : public HandshakerService::Service { // Client finish frame may be sent along with the first payload from the // client, handshaker only consumes the client finish frame. if (request.in_bytes().substr(0, strlen(kClientFinishFrame)) != kClientFinishFrame) { - return grpc::Status(grpc::StatusCode::UNKNOWN, kInvalidFrameError); + return {grpc::StatusCode::UNKNOWN, kInvalidFrameError}; } response->set_bytes_consumed(strlen(kClientFinishFrame)); context->state = HandshakeState::COMPLETED; } else { - return grpc::Status(grpc::StatusCode::FAILED_PRECONDITION, kWrongStateError); + return {grpc::StatusCode::FAILED_PRECONDITION, kWrongStateError}; } } // At this point, processing next request succeeded. response->mutable_status()->set_code(grpc::StatusCode::OK); if (context->state == HandshakeState::COMPLETED) { - *response->mutable_result() = GetHandshakerResult(); + *response->mutable_result() = getHandshakerResult(); } return grpc::Status::OK; } - grpc::Status WriteErrorResponse( + grpc::Status writeErrorResponse( grpc::ServerReaderWriter* stream, const grpc::Status& status) { EXPECT_TRUE(status.ok()); @@ -280,7 +279,7 @@ class FakeHandshakerService : public HandshakerService::Service { return status; } - grpc::gcp::HandshakerResult GetHandshakerResult() { + grpc::gcp::HandshakerResult getHandshakerResult() { grpc::gcp::HandshakerResult result; result.set_application_protocol("grpc"); result.set_record_protocol("ALTSRP_GCM_AES128_REKEY"); @@ -299,7 +298,7 @@ class FakeHandshakerService : public HandshakerService::Service { const std::string peer_identity_; }; -std::unique_ptr CreateFakeHandshakerService(const std::string& peer_identity) { +std::unique_ptr createFakeHandshakerService(const std::string& peer_identity) { return std::unique_ptr{new FakeHandshakerService(peer_identity)}; } @@ -347,7 +346,7 @@ class AltsIntegrationTestBase : public Event::TestUsingSimulatedTime, service = std::unique_ptr{capturing_handshaker_service_}; } else { capturing_handshaker_service_ = nullptr; - service = CreateFakeHandshakerService("peer_identity"); + service = createFakeHandshakerService("peer_identity"); } std::string server_address = Network::Test::getLoopbackAddressUrlString(version_) + ":0"; diff --git a/test/extensions/transport_sockets/alts/tsi_handshaker_test.cc b/test/extensions/transport_sockets/alts/tsi_handshaker_test.cc index 32476c23f426..977fb9ef5178 100644 --- a/test/extensions/transport_sockets/alts/tsi_handshaker_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_handshaker_test.cc @@ -48,7 +48,6 @@ using ::grpc::gcp::HandshakerResult; using ::grpc::gcp::HandshakerService; using ::testing::IsNull; using ::testing::NotNull; -using ::testing::Test; constexpr absl::string_view ClientInit = "CLIENT_INIT"; constexpr absl::string_view ServerInit = "SERVER_INIT"; @@ -141,7 +140,7 @@ class ErrorHandshakerService final : public HandshakerService::Service { response.mutable_status()->set_details("Internal error."); EXPECT_TRUE(stream->Write(response)); } - return grpc::Status(grpc::StatusCode::INTERNAL, "DoHandshake internal error."); + return {grpc::StatusCode::INTERNAL, "DoHandshake internal error."}; } }; diff --git a/test/extensions/transport_sockets/alts/tsi_socket_test.cc b/test/extensions/transport_sockets/alts/tsi_socket_test.cc index a8b9b675b744..a0ca23e81e71 100644 --- a/test/extensions/transport_sockets/alts/tsi_socket_test.cc +++ b/test/extensions/transport_sockets/alts/tsi_socket_test.cc @@ -151,7 +151,7 @@ class ErrorHandshakerService final : public HandshakerService::Service { break; } } - return grpc::Status(grpc::StatusCode::INTERNAL, "DoHandshake internal error."); + return {grpc::StatusCode::INTERNAL, "DoHandshake internal error."}; } const bool keep_stream_alive_; diff --git a/test/integration/buffer_accounting_integration_test.cc b/test/integration/buffer_accounting_integration_test.cc index 46d5194893d1..8ad89494b9ac 100644 --- a/test/integration/buffer_accounting_integration_test.cc +++ b/test/integration/buffer_accounting_integration_test.cc @@ -37,8 +37,6 @@ namespace { #define ASANITIZED /* Sanitized by GCC */ #endif -using testing::HasSubstr; - std::string protocolTestParamsAndBoolToString( const ::testing::TestParamInfo>& params) { return fmt::format("{}_{}", diff --git a/test/integration/integration_stream_decoder.h b/test/integration/integration_stream_decoder.h index 080b5cbbf915..0fd9343dc3ba 100644 --- a/test/integration/integration_stream_decoder.h +++ b/test/integration/integration_stream_decoder.h @@ -24,7 +24,7 @@ namespace Envoy { class IntegrationStreamDecoder : public Http::ResponseDecoder, public Http::StreamCallbacks { public: IntegrationStreamDecoder(Event::Dispatcher& dispatcher); - ~IntegrationStreamDecoder(); + ~IntegrationStreamDecoder() override; const std::string& body() { return body_; } bool complete() { return saw_end_stream_; } diff --git a/test/integration/load_stats_integration_test.cc b/test/integration/load_stats_integration_test.cc index 668cdb0fcf52..e98aa4b752d4 100644 --- a/test/integration/load_stats_integration_test.cc +++ b/test/integration/load_stats_integration_test.cc @@ -191,11 +191,11 @@ class LoadStatsIntegrationTest : public Grpc::GrpcClientIntegrationParamTest, cluster_stats->set_total_dropped_requests(cluster_stats->total_dropped_requests() + local_cluster_stats.total_dropped_requests()); - if (local_cluster_stats.dropped_requests().size() > 0) { + if (!local_cluster_stats.dropped_requests().empty()) { const uint64_t local_drop_count = local_cluster_stats.dropped_requests(0).dropped_count(); if (local_drop_count > 0) { envoy::config::endpoint::v3::ClusterStats::DroppedRequests* drop_request; - if (cluster_stats->dropped_requests().size() > 0) { + if (!cluster_stats->dropped_requests().empty()) { drop_request = cluster_stats->mutable_dropped_requests(0); drop_request->set_dropped_count(drop_request->dropped_count() + local_drop_count); } else { diff --git a/test/integration/udp_tunneling_integration_test.cc b/test/integration/udp_tunneling_integration_test.cc index 10aa40480d63..bca91a8f2d64 100644 --- a/test/integration/udp_tunneling_integration_test.cc +++ b/test/integration/udp_tunneling_integration_test.cc @@ -443,7 +443,7 @@ name: udp_proxy const std::string expectedCapsules(std::vector raw_datagrams) { std::string expected_capsules = ""; - for (std::string datagram : raw_datagrams) { + for (std::string& datagram : raw_datagrams) { expected_capsules += encapsulate(datagram); } diff --git a/test/mocks/server/factory_context.cc b/test/mocks/server/factory_context.cc index 945df5ef2fbd..98a28f435e21 100644 --- a/test/mocks/server/factory_context.cc +++ b/test/mocks/server/factory_context.cc @@ -11,7 +11,6 @@ namespace Envoy { namespace Server { namespace Configuration { -using ::testing::Return; using ::testing::ReturnRef; MockFactoryContext::MockFactoryContext() { diff --git a/test/server/hot_restarting_child_test.cc b/test/server/hot_restarting_child_test.cc index da12147f5cf8..f2455034f054 100644 --- a/test/server/hot_restarting_child_test.cc +++ b/test/server/hot_restarting_child_test.cc @@ -15,10 +15,8 @@ #include "gtest/gtest.h" -using testing::AllOf; using testing::DoAll; using testing::Eq; -using testing::InSequence; using testing::Return; using testing::ReturnRef; using testing::SaveArg; diff --git a/test/server/server_test.cc b/test/server/server_test.cc index 5f088d5aba8f..3c67fcb4bf3d 100644 --- a/test/server/server_test.cc +++ b/test/server/server_test.cc @@ -668,7 +668,7 @@ class ServerInstanceImplWorkersTest : public ServerInstanceImplTest { EXPECT_CALL(restart_, drainParentListeners); } - ~ServerInstanceImplWorkersTest() { + ~ServerInstanceImplWorkersTest() override { server_->dispatcher().post([&] { server_->shutdown(); }); server_thread_->join(); } @@ -1345,10 +1345,10 @@ TEST_P(ServerInstanceImplTest, LogToFileError) { TEST_P(ServerInstanceImplTest, NoOptionsPassed) { thread_local_ = std::make_unique(); init_manager_ = std::make_unique("Server"); - server_.reset(new InstanceImpl( + server_ = std::make_unique( *init_manager_, options_, time_system_, hooks_, restart_, stats_store_, fakelock_, std::make_unique>(), *thread_local_, - Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr)); + Thread::threadFactoryForTest(), Filesystem::fileSystemForTest(), nullptr); EXPECT_THROW_WITH_MESSAGE( server_->initialize(std::make_shared("127.0.0.1"), component_factory_), From 1cd2aaa8b7306ed55e7a03df0fc2c9790fe5f37b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:17:01 +0000 Subject: [PATCH 042/151] build(deps): bump the examples-grpc-bridge group in /examples/grpc-bridge/client with 1 update (#32438) build(deps): bump the examples-grpc-bridge group Bumps the examples-grpc-bridge group in /examples/grpc-bridge/client with 1 update: [protobuf](https://github.com/protocolbuffers/protobuf). Updates `protobuf` from 4.25.2 to 4.25.3 - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v4.25.2...v4.25.3) --- updated-dependencies: - dependency-name: protobuf dependency-type: direct:production update-type: version-update:semver-patch dependency-group: examples-grpc-bridge ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/grpc-bridge/client/requirements.txt | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index c256b9ee6aef..27c967211094 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -218,18 +218,18 @@ idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 # via requests -protobuf==4.25.2 \ - --hash=sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62 \ - --hash=sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d \ - --hash=sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61 \ - --hash=sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62 \ - --hash=sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3 \ - --hash=sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9 \ - --hash=sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830 \ - --hash=sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6 \ - --hash=sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0 \ - --hash=sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020 \ - --hash=sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e +protobuf==4.25.3 \ + --hash=sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4 \ + --hash=sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8 \ + --hash=sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c \ + --hash=sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d \ + --hash=sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4 \ + --hash=sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa \ + --hash=sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c \ + --hash=sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019 \ + --hash=sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9 \ + --hash=sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c \ + --hash=sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2 # via # -r requirements.in # grpcio-tools From 100ea32d4edcc519e80763c05a72a1bf350c2c7e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:17:11 +0000 Subject: [PATCH 043/151] build(deps): bump protobuf from 4.25.2 to 4.25.3 in /tools/base (#32433) Bumps [protobuf](https://github.com/protocolbuffers/protobuf) from 4.25.2 to 4.25.3. - [Release notes](https://github.com/protocolbuffers/protobuf/releases) - [Changelog](https://github.com/protocolbuffers/protobuf/blob/main/protobuf_release.bzl) - [Commits](https://github.com/protocolbuffers/protobuf/compare/v4.25.2...v4.25.3) --- updated-dependencies: - dependency-name: protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 9176cb67f791..51f82ab1349d 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1004,18 +1004,18 @@ ply==3.11 \ --hash=sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3 \ --hash=sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce # via -r requirements.in -protobuf==4.25.2 \ - --hash=sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62 \ - --hash=sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d \ - --hash=sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61 \ - --hash=sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62 \ - --hash=sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3 \ - --hash=sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9 \ - --hash=sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830 \ - --hash=sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6 \ - --hash=sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0 \ - --hash=sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020 \ - --hash=sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e +protobuf==4.25.3 \ + --hash=sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4 \ + --hash=sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8 \ + --hash=sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c \ + --hash=sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d \ + --hash=sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4 \ + --hash=sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa \ + --hash=sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c \ + --hash=sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019 \ + --hash=sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9 \ + --hash=sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c \ + --hash=sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2 # via # -r requirements.in # envoy-base-utils From a08797a675a6b4870e781672830c5007e474be39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:17:28 +0000 Subject: [PATCH 044/151] build(deps): bump the examples-kafka group in /examples/kafka with 2 updates (#32413) build(deps): bump the examples-kafka group Bumps the examples-kafka group in /examples/kafka with 2 updates: confluentinc/cp-kafka and confluentinc/cp-zookeeper. Updates `confluentinc/cp-kafka` from `7e332fd` to `24cdd3a` Updates `confluentinc/cp-zookeeper` from `0140b97` to `9babd1c` --- updated-dependencies: - dependency-name: confluentinc/cp-kafka dependency-type: direct:production dependency-group: examples-kafka - dependency-name: confluentinc/cp-zookeeper dependency-type: direct:production dependency-group: examples-kafka ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/kafka/Dockerfile-kafka | 2 +- examples/kafka/Dockerfile-zookeeper | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/kafka/Dockerfile-kafka b/examples/kafka/Dockerfile-kafka index c44ea13f7391..7fbafda86e9b 100644 --- a/examples/kafka/Dockerfile-kafka +++ b/examples/kafka/Dockerfile-kafka @@ -1 +1 @@ -FROM confluentinc/cp-kafka:latest@sha256:7e332fd046a1b3ef292dcd063d6e7bee4f487ac4d235da1692b15a44daf2c080 +FROM confluentinc/cp-kafka:latest@sha256:24cdd3a7fa89d2bed150560ebea81ff1943badfa61e51d66bb541a6b0d7fb047 diff --git a/examples/kafka/Dockerfile-zookeeper b/examples/kafka/Dockerfile-zookeeper index 9fa15d8028ea..924109d54c93 100644 --- a/examples/kafka/Dockerfile-zookeeper +++ b/examples/kafka/Dockerfile-zookeeper @@ -1 +1 @@ -FROM confluentinc/cp-zookeeper:latest@sha256:0140b9767dc900af6c4a1a0d0cf0d16548152e924f2939a038d8ca8793f69b86 +FROM confluentinc/cp-zookeeper:latest@sha256:9babd1c0beaf93189982bdbb9fe4bf194a2730298b640c057817746c19838866 From d03326ba48f3a1927588add853f44fc15b91f5ce Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:17:39 +0000 Subject: [PATCH 045/151] build(deps): bump actions/dependency-review-action from 4.0.0 to 4.1.0 (#32412) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.0.0 to 4.1.0. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/4901385134134e04cec5fbe5ddfe3b2c5bd5d976...80f10bf419f34980065523f5efca7ebed17576aa) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_precheck_deps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_precheck_deps.yml b/.github/workflows/_precheck_deps.yml index d50d62f0ed12..8b2d80df07cd 100644 --- a/.github/workflows/_precheck_deps.yml +++ b/.github/workflows/_precheck_deps.yml @@ -55,4 +55,4 @@ jobs: ref: ${{ fromJSON(inputs.request).request.sha }} persist-credentials: false - name: Dependency Review - uses: actions/dependency-review-action@4901385134134e04cec5fbe5ddfe3b2c5bd5d976 # v4.0.0 + uses: actions/dependency-review-action@80f10bf419f34980065523f5efca7ebed17576aa # v4.1.0 From 724968a7dcf7c47a969d9b6d3924d7374d33dafe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:17:56 +0000 Subject: [PATCH 046/151] build(deps): bump mysql from `2a9ef10` to `ff5ab9c` in /examples/mysql (#32414) Bumps mysql from `2a9ef10` to `ff5ab9c`. --- updated-dependencies: - dependency-name: mysql dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/mysql/Dockerfile-mysql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/mysql/Dockerfile-mysql b/examples/mysql/Dockerfile-mysql index 46078228d2a4..d6ad147ea99b 100644 --- a/examples/mysql/Dockerfile-mysql +++ b/examples/mysql/Dockerfile-mysql @@ -1 +1 @@ -FROM mysql:8.3.0@sha256:2a9ef1075ff30c65bbcf4f96b25a03ea3b3f492c284e6c4a612c269ce4c5bb19 +FROM mysql:8.3.0@sha256:ff5ab9cdce0b4c59704b4e2a09deed5ab8467be795e0ea20228b8528f53fcf82 From 1f6942d19f434f88997ed55fd73524a00883da0c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:18:06 +0000 Subject: [PATCH 047/151] build(deps): bump node from `f2eadc7` to `6b35c9b` in /examples/shared/node (#32398) build(deps): bump node in /examples/shared/node Bumps node from `f2eadc7` to `6b35c9b`. --- updated-dependencies: - dependency-name: node dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/node/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index fbd63382cd9a..51ff42fecd66 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:21.6-bookworm-slim@sha256:f2eadc7a5287fc42ca6c4e9177b8120ec4321c9f2d75506210b948857a22b825 as node-base +FROM node:21.6-bookworm-slim@sha256:6b35c9b34836d7d4a686d8a899cb503533040d43d8cb0dcb51ef832108573a86 as node-base FROM node-base as node-http-auth From 0cdb1d956928ea8ec70f67843af62391feebfdc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:18:15 +0000 Subject: [PATCH 048/151] build(deps): bump the examples-load-reporting group in /examples/load-reporting-service with 1 update (#32395) build(deps): bump the examples-load-reporting group Bumps the examples-load-reporting group in /examples/load-reporting-service with 1 update: [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `google.golang.org/grpc` from 1.61.0 to 1.61.1 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.61.0...v1.61.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-patch dependency-group: examples-load-reporting ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/load-reporting-service/go.mod | 2 +- examples/load-reporting-service/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/load-reporting-service/go.mod b/examples/load-reporting-service/go.mod index 72b460b37f3d..ba48ee0ce07a 100644 --- a/examples/load-reporting-service/go.mod +++ b/examples/load-reporting-service/go.mod @@ -5,5 +5,5 @@ go 1.13 require ( github.com/envoyproxy/go-control-plane v0.12.0 github.com/golang/protobuf v1.5.3 - google.golang.org/grpc v1.61.0 + google.golang.org/grpc v1.61.1 ) diff --git a/examples/load-reporting-service/go.sum b/examples/load-reporting-service/go.sum index 52de68a07117..6b10f8e54563 100644 --- a/examples/load-reporting-service/go.sum +++ b/examples/load-reporting-service/go.sum @@ -2157,8 +2157,8 @@ google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 41193cbf6e84442c5724e9c2cc6a921d79aab9eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:18:24 +0000 Subject: [PATCH 049/151] build(deps): bump golang from `874c267` to `925fe3f` in /examples/shared/golang (#32392) build(deps): bump golang in /examples/shared/golang Bumps golang from `874c267` to `925fe3f`. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/golang/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/golang/Dockerfile b/examples/shared/golang/Dockerfile index fb3547febddf..5a940c392906 100644 --- a/examples/shared/golang/Dockerfile +++ b/examples/shared/golang/Dockerfile @@ -3,7 +3,7 @@ RUN rm -f /etc/apt/apt.conf.d/docker-clean \ && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' | tee /etc/apt/apt.conf.d/keep-cache -FROM golang:1.22.0-bookworm@sha256:874c2677e43be36a429823f2742af85844772664f273c1c8c8235f10aba51cd3 as golang-base +FROM golang:1.22.0-bookworm@sha256:925fe3fa28ba428cf67a7947ae838f8a1523117b40e3e6b5106c378e3f97fa29 as golang-base FROM golang-base as golang-control-plane-builder From 68dd5aaae4515efa94691b262f91e5b1c58658ab Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:18:42 +0000 Subject: [PATCH 050/151] build(deps): bump the examples-grpc-bridge group in /examples/grpc-bridge/server with 1 update (#32400) build(deps): bump the examples-grpc-bridge group Bumps the examples-grpc-bridge group in /examples/grpc-bridge/server with 1 update: [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `google.golang.org/grpc` from 1.61.0 to 1.61.1 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.61.0...v1.61.1) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-patch dependency-group: examples-grpc-bridge ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/grpc-bridge/server/go.mod | 2 +- examples/grpc-bridge/server/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/grpc-bridge/server/go.mod b/examples/grpc-bridge/server/go.mod index 5cfddbe8f873..606f3422bdb0 100644 --- a/examples/grpc-bridge/server/go.mod +++ b/examples/grpc-bridge/server/go.mod @@ -5,5 +5,5 @@ go 1.13 require ( github.com/golang/protobuf v1.5.3 golang.org/x/net v0.21.0 - google.golang.org/grpc v1.61.0 + google.golang.org/grpc v1.61.1 ) diff --git a/examples/grpc-bridge/server/go.sum b/examples/grpc-bridge/server/go.sum index 15e85741a9aa..5dc0623cfe6d 100644 --- a/examples/grpc-bridge/server/go.sum +++ b/examples/grpc-bridge/server/go.sum @@ -2152,8 +2152,8 @@ google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= +google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 213f87f7e3f64cb17646963a722a32784d56a4c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:18:57 +0000 Subject: [PATCH 051/151] build(deps): bump postgres from `2881f97` to `4d1580c` in /examples/shared/postgres (#32399) build(deps): bump postgres in /examples/shared/postgres Bumps postgres from `2881f97` to `4d1580c`. --- updated-dependencies: - dependency-name: postgres dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/postgres/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index 0d01aa6f10ba..7c3a5618a7f5 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:2881f973a604ea35d149c129fb98a676945d00ea7cb281ef1ee7ab2d9e22b309 +FROM postgres:latest@sha256:4d1580c68015bf5dbeb4a2739ba8a3cff2641edd38089c518bbc2f5ccb900867 COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] From 9601427b3080b447a9fd58cee15712cf6e43748d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:19:44 +0000 Subject: [PATCH 052/151] build(deps): bump openzipkin/zipkin from `bb7f0dd` to `8a2d619` in /examples/zipkin (#32351) build(deps): bump openzipkin/zipkin in /examples/zipkin Bumps openzipkin/zipkin from `bb7f0dd` to `8a2d619`. --- updated-dependencies: - dependency-name: openzipkin/zipkin dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/zipkin/Dockerfile-zipkin | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/zipkin/Dockerfile-zipkin b/examples/zipkin/Dockerfile-zipkin index a38690c9abd6..08d46c836de9 100644 --- a/examples/zipkin/Dockerfile-zipkin +++ b/examples/zipkin/Dockerfile-zipkin @@ -1 +1 @@ -FROM openzipkin/zipkin:latest@sha256:bb7f0ddd2aeaace94f3d4e4d370fffe5dbf959ce49522eb5458cac295f808139 +FROM openzipkin/zipkin:latest@sha256:8a2d619189caab1b9d4cede5da177fa88fd383c333dff7c56708effe6822fa60 From 73b8adcb80325b146e0df65cb2edab71c5c40dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20B=C3=A9ky?= Date: Mon, 19 Feb 2024 05:29:27 -0500 Subject: [PATCH 053/151] Add changelog entry for #31168. (#32423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bence Béky Co-authored-by: Bence Béky --- changelogs/current.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 379d8c919851..e6019f1755d2 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -43,6 +43,10 @@ minor_behavior_changes: AWS region string is now retrieved from environment and profile consistently within aws_request_signer and grpc_credentials/aws_iam extensions. Region field in aws_request_signer is now optional, explicitly configured xDS region will take preference. aws_request_signer documentation now reflects the region chain. +- area: http + change: | + Enable obsolete line folding in BalsaParser (for behavior parity with http-parser, the + previously used HTTP/1 parser). bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* From e9558d3f784b4378f7bab5cf8a83941452b98f97 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Mon, 19 Feb 2024 13:42:02 +0100 Subject: [PATCH 054/151] docs: fix the license URL of QATzip (#32462) Signed-off-by: Michael Kaufmann Co-authored-by: Michael Kaufmann --- bazel/repository_locations.bzl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 91b635e1a8cd..42ff52c85dc3 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -464,7 +464,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( extensions = ["envoy.compression.qatzip.compressor"], cpe = "N/A", license = "BSD-3-Clause", - license_url = "https://github.com/intel/QATzip/blob/{version}/LICENSE", + license_url = "https://github.com/intel/QATzip/blob/v{version}/LICENSE", ), com_github_luajit_luajit = dict( project_name = "LuaJIT", From 530e8a1a6e32f9c9b3fd09008bb4cdb540f59eb5 Mon Sep 17 00:00:00 2001 From: "Antonio V. Leonti" <53806445+antoniovleonti@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:06:23 -0500 Subject: [PATCH 055/151] fuzz: Check createFilterFactoryFromProto is ok before accessing its value (#32449) Signed-off-by: Antonio Leonti --- .../filter_corpus/create_filter_factory_from_proto_not_ok | 7 +++++++ test/extensions/filters/http/common/fuzz/uber_filter.cc | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 test/extensions/filters/http/common/fuzz/filter_corpus/create_filter_factory_from_proto_not_ok diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/create_filter_factory_from_proto_not_ok b/test/extensions/filters/http/common/fuzz/filter_corpus/create_filter_factory_from_proto_not_ok new file mode 100644 index 000000000000..869ab118fa71 --- /dev/null +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/create_filter_factory_from_proto_not_ok @@ -0,0 +1,7 @@ +config { + name: "envoy.filters.http.decompressor" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.filters.http.decompressor.v3.Decompressor" + value: "\nU\n\001t\022P\nNtype.googleapis.com/envoy.extensions.filters.http.decompressor.v3.Decompressor" + } +} diff --git a/test/extensions/filters/http/common/fuzz/uber_filter.cc b/test/extensions/filters/http/common/fuzz/uber_filter.cc index cfabe6ffa186..712e61d54903 100644 --- a/test/extensions/filters/http/common/fuzz/uber_filter.cc +++ b/test/extensions/filters/http/common/fuzz/uber_filter.cc @@ -73,7 +73,9 @@ void UberFilterFuzzer::fuzz( proto_config, factory_context_.messageValidationVisitor(), factory); // Clean-up config with filter-specific logic before it runs through validations. cleanFuzzedConfig(proto_config.name(), message.get()); - cb_ = factory.createFilterFactoryFromProto(*message, "stats", factory_context_).value(); + auto cb_or = factory.createFilterFactoryFromProto(*message, "stats", factory_context_); + THROW_IF_STATUS_NOT_OK(cb_or, throw); + cb_ = cb_or.value(); cb_(filter_callback_); } catch (const EnvoyException& e) { ENVOY_LOG_MISC(debug, "Controlled exception {}", e.what()); From 7c410102b1a97b439c8b86bc10c0c5059a408ca3 Mon Sep 17 00:00:00 2001 From: Raven Black Date: Tue, 20 Feb 2024 11:39:30 -0500 Subject: [PATCH 056/151] [http-cache-filter] limit cache read chunk size to buffer size (#32307) * [cache-filter] limit cache read chunk size --------- Signed-off-by: Raven Black --- .../filters/http/cache/cache_filter.cc | 25 +++++++- .../filters/http/cache/cache_filter_test.cc | 63 +++++++++++++++++++ 2 files changed, 86 insertions(+), 2 deletions(-) diff --git a/source/extensions/filters/http/cache/cache_filter.cc b/source/extensions/filters/http/cache/cache_filter.cc index c17486d360c2..a920ac3ddbb2 100644 --- a/source/extensions/filters/http/cache/cache_filter.cc +++ b/source/extensions/filters/http/cache/cache_filter.cc @@ -23,6 +23,16 @@ namespace { inline bool isResponseNotModified(const Http::ResponseHeaderMap& response_headers) { return Http::Utility::getResponseStatus(response_headers) == enumToInt(Http::Code::NotModified); } + +// This value is only used if there is no encoderBufferLimit on the stream; +// without *some* constraint here, a very large chunk can be requested and +// attempt to load into a memory buffer. +// +// This default is quite large to minimize the chance of being a surprise +// behavioral change when a constraint is added. +// +// And everyone knows 64MB should be enough for anyone. +static const size_t MAX_BYTES_TO_FETCH_FROM_CACHE_PER_REQUEST = 64 * 1024 * 1024; } // namespace struct CacheResponseCodeDetailValues { @@ -326,10 +336,21 @@ void CacheFilter::getBody() { // posted callback. CacheFilterWeakPtr self = weak_from_this(); + // We don't want to request more than a buffer-size at a time from the cache. + uint64_t fetch_size_limit = encoder_callbacks_->encoderBufferLimit(); + // If there is no buffer size limit, we still want *some* constraint. + if (fetch_size_limit == 0) { + fetch_size_limit = MAX_BYTES_TO_FETCH_FROM_CACHE_PER_REQUEST; + } + AdjustedByteRange fetch_range = {remaining_ranges_[0].begin(), + (remaining_ranges_[0].length() > fetch_size_limit) + ? (remaining_ranges_[0].begin() + fetch_size_limit) + : remaining_ranges_[0].end()}; + // The dispatcher needs to be captured because there's no guarantee that // decoder_callbacks_->dispatcher() is thread-safe. - lookup_->getBody(remaining_ranges_[0], [self, &dispatcher = decoder_callbacks_->dispatcher()]( - Buffer::InstancePtr&& body) { + lookup_->getBody(fetch_range, [self, &dispatcher = decoder_callbacks_->dispatcher()]( + Buffer::InstancePtr&& body) { // The callback is posted to the dispatcher to make sure it is called on the worker thread. dispatcher.post([self, body = std::move(body)]() mutable { if (CacheFilterSharedPtr cache_filter = self.lock()) { diff --git a/test/extensions/filters/http/cache/cache_filter_test.cc b/test/extensions/filters/http/cache/cache_filter_test.cc index d5a91f1a719b..9c4e1afb273d 100644 --- a/test/extensions/filters/http/cache/cache_filter_test.cc +++ b/test/extensions/filters/http/cache/cache_filter_test.cc @@ -490,6 +490,69 @@ TEST_F(CacheFilterTest, FilterDestroyedWhileWatermarkedSendsLowWatermarkEvent) { } } +MATCHER_P2(RangeMatcher, begin, end, "") { + return testing::ExplainMatchResult(begin, arg.begin(), result_listener) && + testing::ExplainMatchResult(end, arg.end(), result_listener); +} + +TEST_F(CacheFilterTest, BodyReadFromCacheLimitedToBufferSizeChunks) { + request_headers_.setHost("CacheHitWithBody"); + // Set the buffer limit to 5 bytes, and we will have the file be of size + // 8 bytes. + EXPECT_CALL(encoder_callbacks_, encoderBufferLimit()).WillRepeatedly(::testing::Return(5)); + auto mock_http_cache = std::make_shared(); + auto mock_lookup_context = std::make_unique(); + EXPECT_CALL(*mock_http_cache, makeLookupContext(_, _)) + .WillOnce([&](LookupRequest&&, + Http::StreamDecoderFilterCallbacks&) -> std::unique_ptr { + return std::move(mock_lookup_context); + }); + EXPECT_CALL(*mock_lookup_context, getHeaders(_)).WillOnce([&](LookupHeadersCallback&& cb) { + std::unique_ptr response_headers = + std::make_unique(response_headers_); + cb(LookupResult{CacheEntryStatus::Ok, std::move(response_headers), 8, absl::nullopt}); + }); + EXPECT_CALL(*mock_lookup_context, getBody(RangeMatcher(0, 5), _)) + .WillOnce([&](const AdjustedByteRange&, LookupBodyCallback&& cb) { + cb(std::make_unique("abcde")); + }); + EXPECT_CALL(*mock_lookup_context, getBody(RangeMatcher(5, 8), _)) + .WillOnce([&](const AdjustedByteRange&, LookupBodyCallback&& cb) { + cb(std::make_unique("fgh")); + }); + EXPECT_CALL(*mock_lookup_context, onDestroy()); + + CacheFilterSharedPtr filter = makeFilter(mock_http_cache, false); + + // The filter should encode cached headers. + EXPECT_CALL(decoder_callbacks_, encodeHeaders_(IsSupersetOfHeaders(response_headers_), false)); + + // The filter should encode cached data in two pieces. + EXPECT_CALL( + decoder_callbacks_, + encodeData(testing::Property(&Buffer::Instance::toString, testing::Eq("abcde")), false)); + EXPECT_CALL(decoder_callbacks_, + encodeData(testing::Property(&Buffer::Instance::toString, testing::Eq("fgh")), true)); + + // The filter should stop decoding iteration when decodeHeaders is called as a cache lookup is + // in progress. + EXPECT_EQ(filter->decodeHeaders(request_headers_, true), + Http::FilterHeadersStatus::StopAllIterationAndWatermark); + + // The filter should not continue decoding when the cache lookup result is ready, as the + // expected result is a hit. + EXPECT_CALL(decoder_callbacks_, continueDecoding).Times(0); + + // The cache lookup callback should be posted to the dispatcher. + // Run events on the dispatcher so that the callback is invoked. + // The posted lookup callback will cause another callback to be posted (when getBody() is + // called) which should also be invoked. + dispatcher_->run(Event::Dispatcher::RunType::Block); + + filter->onDestroy(); + filter.reset(); +} + TEST_F(CacheFilterTest, CacheInsertAbortedByCache) { request_headers_.setHost("CacheHitWithBody"); const std::string body = "abc"; From 0ff3fcb12808bc5906b89644304a4980216e4797 Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Wed, 21 Feb 2024 00:10:56 -0500 Subject: [PATCH 057/151] tls: Fix Envoy reaching into private structures in io_handle_bio.cc (#32317) Rather than reaching into private structures, Envoy should stick to supported public APIs. This covers both the implementation and the tests. I did my best to salvage io_handle_bio_test.cc, as it was previously testing mostly incoherent things. However, it warrants another pass by Envoy folks. What you all should be testing is that public BIO APIs correctly call the underlying io_handle operations. Instead, the old tests mostly forcibly called the underlying function pointers with cases that will never be called in the first place. (E.g. the destroy callback will never be called with a null BIO.) I've also removed the BIO_CTRL_SET_CLOSE (BIO_set_close) bits as it seems to be entirely dead code. Envoy never actually configures the BIO to close the io_handle when destroyed. If you all don't actually need this, there's no point in bothering. That seems to have been copy-pasted from the built-in BIOs, which have BIO_CLOSE and BIO_NOCLOSE modes that allow the caller to optionally pass ownership of the io_handle. From what I can tell, Envoy never wants to pass ownership. Additional Description: Risk Level: Low Testing: bazelisk test test/extensions/transport_sockets/tls/... Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a Signed-off-by: David Benjamin --- .../transport_sockets/tls/io_handle_bio.cc | 67 +++++-------------- .../transport_sockets/tls/io_handle_bio.h | 3 +- .../tls/io_handle_bio_test.cc | 22 ++---- 3 files changed, 21 insertions(+), 71 deletions(-) diff --git a/source/extensions/transport_sockets/tls/io_handle_bio.cc b/source/extensions/transport_sockets/tls/io_handle_bio.cc index 08ab2655b9a6..f5faf5fec27b 100644 --- a/source/extensions/transport_sockets/tls/io_handle_bio.cc +++ b/source/extensions/transport_sockets/tls/io_handle_bio.cc @@ -15,32 +15,7 @@ namespace { // NOLINTNEXTLINE(readability-identifier-naming) inline Envoy::Network::IoHandle* bio_io_handle(BIO* bio) { - return reinterpret_cast(bio->ptr); -} - -// NOLINTNEXTLINE(readability-identifier-naming) -int io_handle_new(BIO* bio) { - bio->init = 0; - bio->num = -1; - bio->ptr = nullptr; - bio->flags = 0; - return 1; -} - -// NOLINTNEXTLINE(readability-identifier-naming) -int io_handle_free(BIO* bio) { - if (bio == nullptr) { - return 0; - } - - if (bio->shutdown) { - if (bio->init) { - bio_io_handle(bio)->close(); - } - bio->init = 0; - bio->flags = 0; - } - return 1; + return reinterpret_cast(BIO_get_data(bio)); } // NOLINTNEXTLINE(readability-identifier-naming) @@ -86,22 +61,10 @@ int io_handle_write(BIO* b, const char* in, int inl) { } // NOLINTNEXTLINE(readability-identifier-naming) -long io_handle_ctrl(BIO* b, int cmd, long num, void*) { +long io_handle_ctrl(BIO*, int cmd, long, void*) { long ret = 1; switch (cmd) { - case BIO_C_SET_FD: - RELEASE_ASSERT(false, "should not be called"); - break; - case BIO_C_GET_FD: - RELEASE_ASSERT(false, "should not be called"); - break; - case BIO_CTRL_GET_CLOSE: - ret = b->shutdown; - break; - case BIO_CTRL_SET_CLOSE: - b->shutdown = int(num); - break; case BIO_CTRL_FLUSH: ret = 1; break; @@ -112,16 +75,18 @@ long io_handle_ctrl(BIO* b, int cmd, long num, void*) { return ret; } -const BIO_METHOD methods_io_handlep = { - BIO_TYPE_SOCKET, "io_handle", - io_handle_write, io_handle_read, - nullptr /* puts */, nullptr /* gets, */, - io_handle_ctrl, io_handle_new, - io_handle_free, nullptr /* callback_ctrl */, -}; - // NOLINTNEXTLINE(readability-identifier-naming) -const BIO_METHOD* BIO_s_io_handle(void) { return &methods_io_handlep; } +const BIO_METHOD* BIO_s_io_handle(void) { + static const BIO_METHOD* method = [&] { + BIO_METHOD* ret = BIO_meth_new(BIO_TYPE_SOCKET, "io_handle"); + RELEASE_ASSERT(ret != nullptr, ""); + RELEASE_ASSERT(BIO_meth_set_read(ret, io_handle_read), ""); + RELEASE_ASSERT(BIO_meth_set_write(ret, io_handle_write), ""); + RELEASE_ASSERT(BIO_meth_set_ctrl(ret, io_handle_ctrl), ""); + return ret; + }(); + return method; +} } // namespace @@ -133,10 +98,8 @@ BIO* BIO_new_io_handle(Envoy::Network::IoHandle* io_handle) { RELEASE_ASSERT(b != nullptr, ""); // Initialize the BIO - b->num = -1; - b->ptr = io_handle; - b->shutdown = 0; - b->init = 1; + BIO_set_data(b, io_handle); + BIO_set_init(b, 1); return b; } diff --git a/source/extensions/transport_sockets/tls/io_handle_bio.h b/source/extensions/transport_sockets/tls/io_handle_bio.h index 03c1c9636566..e2fb14512b8b 100644 --- a/source/extensions/transport_sockets/tls/io_handle_bio.h +++ b/source/extensions/transport_sockets/tls/io_handle_bio.h @@ -11,7 +11,8 @@ namespace Tls { /** * Creates a custom BIO that can read from/write to an IoHandle. It's equivalent to a socket BIO - * but instead of relying on access to an fd, it relies on IoHandle APIs for all interactions. + * but instead of relying on access to an fd, it relies on IoHandle APIs for all interactions. The + * IoHandle must remain valid for the lifetime of the BIO. */ // NOLINTNEXTLINE(readability-identifier-naming) BIO* BIO_new_io_handle(Envoy::Network::IoHandle* io_handle); diff --git a/test/extensions/transport_sockets/tls/io_handle_bio_test.cc b/test/extensions/transport_sockets/tls/io_handle_bio_test.cc index 0f744ae48879..8c9600b7b71d 100644 --- a/test/extensions/transport_sockets/tls/io_handle_bio_test.cc +++ b/test/extensions/transport_sockets/tls/io_handle_bio_test.cc @@ -29,34 +29,20 @@ TEST_F(IoHandleBioTest, WriteError) { EXPECT_CALL(io_handle_, writev(_, 1)) .WillOnce( Return(testing::ByMove(Api::IoCallUint64Result(0, Network::IoSocketError::create(100))))); - EXPECT_EQ(-1, bio_->method->bwrite(bio_, nullptr, 10)); + EXPECT_EQ(-1, BIO_write(bio_, nullptr, 10)); const int err = ERR_get_error(); EXPECT_EQ(ERR_GET_LIB(err), ERR_LIB_SYS); EXPECT_EQ(ERR_GET_REASON(err), 100); } TEST_F(IoHandleBioTest, TestMiscApis) { - EXPECT_EQ(bio_->method->destroy(nullptr), 0); - EXPECT_EQ(bio_->method->bread(nullptr, nullptr, 0), 0); + EXPECT_EQ(BIO_read(bio_, nullptr, 0), 0); - EXPECT_DEATH(bio_->method->ctrl(bio_, BIO_C_GET_FD, 0, nullptr), "should not be called"); - EXPECT_DEATH(bio_->method->ctrl(bio_, BIO_C_SET_FD, 0, nullptr), "should not be called"); - - int ret = bio_->method->ctrl(bio_, BIO_CTRL_RESET, 0, nullptr); + int ret = BIO_reset(bio_); EXPECT_EQ(ret, 0); - ret = bio_->method->ctrl(bio_, BIO_CTRL_FLUSH, 0, nullptr); - EXPECT_EQ(ret, 1); - - ret = bio_->method->ctrl(bio_, BIO_CTRL_SET_CLOSE, 1, nullptr); + ret = BIO_flush(bio_); EXPECT_EQ(ret, 1); - - ret = bio_->method->ctrl(bio_, BIO_CTRL_GET_CLOSE, 0, nullptr); - EXPECT_EQ(ret, 1); - - EXPECT_CALL(io_handle_, close()) - .WillOnce(Return(testing::ByMove(Api::IoCallUint64Result{0, Api::IoError::none()}))); - bio_->init = 1; } } // namespace Tls From 1ad862ddf4856bc87df001ef5e889ba5fdbd3a45 Mon Sep 17 00:00:00 2001 From: phlax Date: Wed, 21 Feb 2024 08:51:38 +0000 Subject: [PATCH 058/151] deps: Deprecate/disable `opentracing` by default (#32421) Signed-off-by: Ryan Northey --- api/envoy/config/trace/v3/dynamic_ot.proto | 10 +- docs/root/start/sandboxes/index.rst | 1 - .../start/sandboxes/jaeger_native_tracing.rst | 121 ------------------ docs/root/start/sandboxes/jaeger_tracing.rst | 3 - examples/jaeger-native-tracing/README.md | 2 - .../jaeger-native-tracing/docker-compose.yaml | 44 ------- examples/jaeger-native-tracing/envoy.yaml | 69 ---------- .../install-jaeger-plugin.sh | 3 - .../service1-envoy-jaeger.yaml | 105 --------------- .../service2-envoy-jaeger.yaml | 64 --------- examples/jaeger-native-tracing/verify.sh | 19 --- 11 files changed, 7 insertions(+), 434 deletions(-) delete mode 100644 docs/root/start/sandboxes/jaeger_native_tracing.rst delete mode 100644 examples/jaeger-native-tracing/README.md delete mode 100644 examples/jaeger-native-tracing/docker-compose.yaml delete mode 100644 examples/jaeger-native-tracing/envoy.yaml delete mode 100755 examples/jaeger-native-tracing/install-jaeger-plugin.sh delete mode 100644 examples/jaeger-native-tracing/service1-envoy-jaeger.yaml delete mode 100644 examples/jaeger-native-tracing/service2-envoy-jaeger.yaml delete mode 100755 examples/jaeger-native-tracing/verify.sh diff --git a/api/envoy/config/trace/v3/dynamic_ot.proto b/api/envoy/config/trace/v3/dynamic_ot.proto index 35971f30dfbd..d2664ef717e6 100644 --- a/api/envoy/config/trace/v3/dynamic_ot.proto +++ b/api/envoy/config/trace/v3/dynamic_ot.proto @@ -33,11 +33,15 @@ message DynamicOtConfig { string library = 1 [ deprecated = true, (validate.rules).string = {min_len: 1}, - (envoy.annotations.deprecated_at_minor_version) = "3.0" + (envoy.annotations.deprecated_at_minor_version) = "3.0", + (envoy.annotations.disallowed_by_default) = true ]; // The configuration to use when creating a tracer from the given dynamic // library. - google.protobuf.Struct config = 2 - [deprecated = true, (envoy.annotations.deprecated_at_minor_version) = "3.0"]; + google.protobuf.Struct config = 2 [ + deprecated = true, + (envoy.annotations.deprecated_at_minor_version) = "3.0", + (envoy.annotations.disallowed_by_default) = true + ]; } diff --git a/docs/root/start/sandboxes/index.rst b/docs/root/start/sandboxes/index.rst index d23d536c2447..450fd5176fd2 100644 --- a/docs/root/start/sandboxes/index.rst +++ b/docs/root/start/sandboxes/index.rst @@ -59,7 +59,6 @@ The following sandboxes are available: golang-network grpc_bridge gzip - jaeger_native_tracing jaeger_tracing kafka load_reporting_service diff --git a/docs/root/start/sandboxes/jaeger_native_tracing.rst b/docs/root/start/sandboxes/jaeger_native_tracing.rst deleted file mode 100644 index 5b1295ffba6c..000000000000 --- a/docs/root/start/sandboxes/jaeger_native_tracing.rst +++ /dev/null @@ -1,121 +0,0 @@ -.. _install_sandboxes_jaeger_native_tracing: - -Jaeger native tracing -===================== - -.. sidebar:: Requirements - - .. include:: _include/docker-env-setup-link.rst - - :ref:`curl ` - Used to make ``HTTP`` requests. - -.. sidebar:: Compatibility - - The jaeger native tracing sandbox uses a binary built for ``x86_64``, and will therefore - only work on that architecture. - -The Jaeger tracing sandbox demonstrates Envoy's :ref:`request tracing ` -capabilities using `Jaeger `_ as the tracing provider and Jaeger's native -`C++ client `_ as a plugin. Using Jaeger with its -native client instead of with Envoy's builtin Zipkin client has the following advantages: - -- Trace propagation will work with the other services using Jaeger without needing to make - configuration `changes `_. -- A variety of different `sampling strategies `_ - can be used, including probabilistic or remote where sampling can be centrally controlled from Jaeger's backend. -- Spans are sent to the collector in a more efficient binary encoding. - -This sandbox is very similar to the front proxy architecture described above, with one difference: -service1 makes an API call to service2 before returning a response. -The three containers will be deployed inside a virtual network called ``envoymesh``. - -All incoming requests are routed via the front Envoy, which is acting as a reverse proxy -sitting on the edge of the ``envoymesh`` network. Port ``8000`` is exposed -by docker compose (see :download:`docker-compose.yaml <_include/jaeger-native-tracing/docker-compose.yaml>`). Notice that -all Envoys are configured to collect request traces (e.g., http_connection_manager/config/tracing setup in -:download:`envoy.yaml <_include/jaeger-native-tracing/envoy.yaml>`) and setup to propagate the spans generated -by the Jaeger tracer to a Jaeger cluster (trace driver setup -in :download:`envoy.yaml <_include/jaeger-native-tracing/envoy.yaml>`). - -Before routing a request to the appropriate service Envoy or the application, Envoy will take -care of generating the appropriate spans for tracing (parent/child context spans). -At a high-level, each span records the latency of upstream API calls as well as information -needed to correlate the span with other related spans (e.g., the trace ID). - -One of the most important benefits of tracing from Envoy is that it will take care of -propagating the traces to the Jaeger service cluster. However, in order to fully take advantage -of tracing, the application has to propagate trace headers that Envoy generates, while making -calls to other services. In the sandbox we have provided, the simple ``aiohttp`` app -(see trace function in :download:`examples/shared/python/tracing/service.py <_include/shared/python/tracing/service.py>`) acting as service1 propagates -the trace headers while making an outbound call to service2. - -Step 3: Build the sandbox -************************* - -To build this sandbox example, and start the example apps run the following commands: - -.. code-block:: console - - $ pwd - envoy/examples/jaeger-native-tracing - $ docker compose pull - $ docker compose up --build -d - $ docker compose ps - - Name Command State Ports - ------------------------------------------------------------------------------------------------------------------------------------------------------------------- - jaeger-native-tracing_front-envoy_1 /start-front.sh Up 10000/tcp, 0.0.0.0:8000->8000/tcp - jaeger-native-tracing_jaeger_1 /go/bin/all-in-one-linux - ... Up 14250/tcp, 14268/tcp, 0.0.0.0:16686->16686/tcp, 5775/udp, 5778/tcp, 6831/udp, 6832/udp - jaeger-native-tracing_service1_1 /start-service.sh Up 10000/tcp - jaeger-native-tracing_service2_1 /start-service.sh Up 10000/tcp - -Step 4: Generate some load -************************** - -You can now send a request to service1 via the front-envoy as follows: - -.. code-block:: console - - $ curl -v localhost:8000/trace/1 - * Trying 192.168.99.100... - * Connected to 192.168.99.100 (192.168.99.100) port 8000 (#0) - > GET /trace/1 HTTP/1.1 - > Host: 192.168.99.100:8000 - > User-Agent: curl/7.54.0 - > Accept: */* - > - < HTTP/1.1 200 OK - < content-type: text/html; charset=utf-8 - < content-length: 89 - < x-envoy-upstream-service-time: 9 - < server: envoy - < date: Fri, 26 Aug 2018 19:39:19 GMT - < - Hello from behind Envoy (service 1)! hostname: f26027f1ce28 resolvedhostname: 172.19.0.6 - * Connection #0 to host 192.168.99.100 left intact - -Step 5: View the traces in Jaeger UI -************************************ - -Point your browser to http://localhost:16686 . You should see the Jaeger dashboard. -Set the service to "front-proxy" and hit 'Find Traces'. You should see traces from the front-proxy. -Click on a trace to explore the path taken by the request from front-proxy to service1 -to service2, as well as the latency incurred at each hop. - -.. seealso:: - - :ref:`Request tracing ` - Learn more about using Envoy's request tracing. - - `Jaeger `_ - Jaeger tracing website. - - `Jaeger C++ client `_ - The Jaeger C++ cient. - - `Jaeger Go client `_ - The Jaeger Go client. - - `Jaeger sampling strategies `_ - More information about Jaeger sampling strategies. diff --git a/docs/root/start/sandboxes/jaeger_tracing.rst b/docs/root/start/sandboxes/jaeger_tracing.rst index f9ac3bc2a6b5..83c5bbf3a621 100644 --- a/docs/root/start/sandboxes/jaeger_tracing.rst +++ b/docs/root/start/sandboxes/jaeger_tracing.rst @@ -94,8 +94,5 @@ to service2, as well as the latency incurred at each hop. :ref:`Request tracing ` Learn more about using Envoy's request tracing. - :ref:`Jaeger native tracing sandbox ` - An example of using Jaeger natively with Envoy. - `Jaeger `_ Jaeger tracing website. diff --git a/examples/jaeger-native-tracing/README.md b/examples/jaeger-native-tracing/README.md deleted file mode 100644 index e21ecf00ca08..000000000000 --- a/examples/jaeger-native-tracing/README.md +++ /dev/null @@ -1,2 +0,0 @@ -To learn about this sandbox and for instructions on how to run it please head over -to the [envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/jaeger_native_tracing) diff --git a/examples/jaeger-native-tracing/docker-compose.yaml b/examples/jaeger-native-tracing/docker-compose.yaml deleted file mode 100644 index 4279e0339ed7..000000000000 --- a/examples/jaeger-native-tracing/docker-compose.yaml +++ /dev/null @@ -1,44 +0,0 @@ -services: - - # jaeger - front-envoy: - build: - context: . - dockerfile: ../shared/envoy/Dockerfile - target: envoy-jaeger-native - depends_on: - service1: - condition: service_healthy - service2: - condition: service_healthy - jaeger: - condition: service_healthy - ports: - - "${PORT_PROXY:-10000}:8000" - - service1: - build: - context: ../shared/python - target: aiohttp-jaeger-service - volumes: - - ./service1-envoy-jaeger.yaml:/etc/service-envoy.yaml - environment: - - SERVICE_NAME=1 - - service2: - build: - context: ../shared/python - target: aiohttp-jaeger-service - volumes: - - ./service2-envoy-jaeger.yaml:/etc/service-envoy.yaml - environment: - - SERVICE_NAME=2 - - jaeger: - build: - context: . - dockerfile: ../shared/jaeger/Dockerfile - environment: - - COLLECTOR_ZIPKIN_HOST_PORT=9411 - ports: - - "${PORT_UI:-10000}:16686" diff --git a/examples/jaeger-native-tracing/envoy.yaml b/examples/jaeger-native-tracing/envoy.yaml deleted file mode 100644 index 3ee3d6944ebe..000000000000 --- a/examples/jaeger-native-tracing/envoy.yaml +++ /dev/null @@ -1,69 +0,0 @@ -node: - cluster: front-proxy - -static_resources: - listeners: - - address: - socket_address: - address: 0.0.0.0 - port_value: 8000 - traffic_direction: OUTBOUND - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - generate_request_id: true - tracing: - provider: - name: envoy.tracers.dynamic_ot - typed_config: - "@type": type.googleapis.com/envoy.config.trace.v3.DynamicOtConfig - library: /usr/local/lib/libjaegertracing_plugin.so - config: - service_name: front-proxy - sampler: - type: const - param: 1 - reporter: - localAgentHostPort: jaeger:6831 - headers: - jaegerDebugHeader: jaeger-debug-id - jaegerBaggageHeader: jaeger-baggage - traceBaggageHeaderPrefix: uberctx- - baggage_restrictions: - denyBaggageOnInitializationFailure: false - hostPort: "" - codec_type: auto - stat_prefix: ingress_http - route_config: - name: local_route - virtual_hosts: - - name: backend - domains: - - "*" - routes: - - match: - prefix: "/" - route: - cluster: service1 - decorator: - operation: checkAvailability - http_filters: - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - use_remote_address: true - clusters: - - name: service1 - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: service1 - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: service1 - port_value: 8000 diff --git a/examples/jaeger-native-tracing/install-jaeger-plugin.sh b/examples/jaeger-native-tracing/install-jaeger-plugin.sh deleted file mode 100755 index 74576be5247e..000000000000 --- a/examples/jaeger-native-tracing/install-jaeger-plugin.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash -JAEGER_VERSION=v0.4.2 -curl -Lo /usr/local/lib/libjaegertracing_plugin.so https://github.com/jaegertracing/jaeger-client-cpp/releases/download/$JAEGER_VERSION/libjaegertracing_plugin.linux_amd64.so diff --git a/examples/jaeger-native-tracing/service1-envoy-jaeger.yaml b/examples/jaeger-native-tracing/service1-envoy-jaeger.yaml deleted file mode 100644 index 2c619d649849..000000000000 --- a/examples/jaeger-native-tracing/service1-envoy-jaeger.yaml +++ /dev/null @@ -1,105 +0,0 @@ -static_resources: - listeners: - - address: - socket_address: - address: 0.0.0.0 - port_value: 8000 - traffic_direction: INBOUND - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - codec_type: auto - stat_prefix: ingress_http - route_config: - name: service1_route - virtual_hosts: - - name: service1 - domains: - - "*" - routes: - - match: - prefix: "/" - route: - cluster: local_service - decorator: - operation: checkAvailability - http_filters: - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - - address: - socket_address: - address: 0.0.0.0 - port_value: 9000 - traffic_direction: OUTBOUND - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - tracing: - provider: - name: envoy.tracers.dynamic_ot - typed_config: - "@type": type.googleapis.com/envoy.config.trace.v3.DynamicOtConfig - library: /usr/local/lib/libjaegertracing_plugin.so - config: - service_name: service1 - sampler: - type: const - param: 1 - reporter: - localAgentHostPort: jaeger:6831 - headers: - jaegerDebugHeader: jaeger-debug-id - jaegerBaggageHeader: jaeger-baggage - traceBaggageHeaderPrefix: uberctx- - baggage_restrictions: - denyBaggageOnInitializationFailure: false - hostPort: "" - codec_type: auto - stat_prefix: egress_http - route_config: - name: service2_route - virtual_hosts: - - name: service2 - domains: - - "*" - routes: - - match: - prefix: "/trace/2" - route: - cluster: service2 - decorator: - operation: checkStock - http_filters: - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - clusters: - - name: local_service - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: local_service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: 127.0.0.1 - port_value: 8080 - - name: service2 - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: service2 - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: service2 - port_value: 8000 diff --git a/examples/jaeger-native-tracing/service2-envoy-jaeger.yaml b/examples/jaeger-native-tracing/service2-envoy-jaeger.yaml deleted file mode 100644 index d0640d85e558..000000000000 --- a/examples/jaeger-native-tracing/service2-envoy-jaeger.yaml +++ /dev/null @@ -1,64 +0,0 @@ -static_resources: - listeners: - - address: - socket_address: - address: 0.0.0.0 - port_value: 8000 - traffic_direction: INBOUND - filter_chains: - - filters: - - name: envoy.filters.network.http_connection_manager - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager - tracing: - provider: - name: envoy.tracers.dynamic_ot - typed_config: - "@type": type.googleapis.com/envoy.config.trace.v3.DynamicOtConfig - library: /usr/local/lib/libjaegertracing_plugin.so - config: - service_name: service2 - sampler: - type: const - param: 1 - reporter: - localAgentHostPort: jaeger:6831 - headers: - jaegerDebugHeader: jaeger-debug-id - jaegerBaggageHeader: jaeger-baggage - traceBaggageHeaderPrefix: uberctx- - baggage_restrictions: - denyBaggageOnInitializationFailure: false - hostPort: "" - codec_type: auto - stat_prefix: ingress_http - route_config: - name: local_route - virtual_hosts: - - name: service2 - domains: - - "*" - routes: - - match: - prefix: "/" - route: - cluster: local_service - decorator: - operation: checkStock - http_filters: - - name: envoy.filters.http.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router - clusters: - - name: local_service - type: strict_dns - lb_policy: round_robin - load_assignment: - cluster_name: local_service - endpoints: - - lb_endpoints: - - endpoint: - address: - socket_address: - address: 127.0.0.1 - port_value: 8080 diff --git a/examples/jaeger-native-tracing/verify.sh b/examples/jaeger-native-tracing/verify.sh deleted file mode 100755 index 85f4e59e6946..000000000000 --- a/examples/jaeger-native-tracing/verify.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -e - -export NAME=jaeger-native -export PORT_PROXY="${JAEGER_NATIVE_PORT_PROXY:-11000}" -export PORT_UI="${JAEGER_NATIVE_PORT_UI:-11001}" - -# shellcheck source=examples/verify-common.sh -. "$(dirname "${BASH_SOURCE[0]}")/../verify-common.sh" - - -run_log "Test services" -responds_with \ - Hello \ - "http://localhost:${PORT_PROXY}/trace/1" - -run_log "Test Jaeger UI" -responds_with \ - "" \ - "http://localhost:${PORT_UI}" From 35d0c8e6536efb07d933303105e78e1bd2373a52 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 09:52:08 +0000 Subject: [PATCH 059/151] build(deps): bump cryptography from 42.0.3 to 42.0.4 in /tools/base (#32495) Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.3 to 42.0.4. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.3...42.0.4) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 66 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 51f82ab1349d..286ac90158f7 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -419,39 +419,39 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==42.0.3 \ - --hash=sha256:04859aa7f12c2b5f7e22d25198ddd537391f1695df7057c8700f71f26f47a129 \ - --hash=sha256:069d2ce9be5526a44093a0991c450fe9906cdf069e0e7cd67d9dee49a62b9ebe \ - --hash=sha256:0d3ec384058b642f7fb7e7bff9664030011ed1af8f852540c76a1317a9dd0d20 \ - --hash=sha256:0fab2a5c479b360e5e0ea9f654bcebb535e3aa1e493a715b13244f4e07ea8eec \ - --hash=sha256:0fea01527d4fb22ffe38cd98951c9044400f6eff4788cf52ae116e27d30a1ba3 \ - --hash=sha256:1b797099d221df7cce5ff2a1d272761d1554ddf9a987d3e11f6459b38cd300fd \ - --hash=sha256:1e935c2900fb53d31f491c0de04f41110351377be19d83d908c1fd502ae8daa5 \ - --hash=sha256:20100c22b298c9eaebe4f0b9032ea97186ac2555f426c3e70670f2517989543b \ - --hash=sha256:20180da1b508f4aefc101cebc14c57043a02b355d1a652b6e8e537967f1e1b46 \ - --hash=sha256:25b09b73db78facdfd7dd0fa77a3f19e94896197c86e9f6dc16bce7b37a96504 \ - --hash=sha256:2619487f37da18d6826e27854a7f9d4d013c51eafb066c80d09c63cf24505306 \ - --hash=sha256:2eb6368d5327d6455f20327fb6159b97538820355ec00f8cc9464d617caecead \ - --hash=sha256:35772a6cffd1f59b85cb670f12faba05513446f80352fe811689b4e439b5d89e \ - --hash=sha256:39d5c93e95bcbc4c06313fc6a500cee414ee39b616b55320c1904760ad686938 \ - --hash=sha256:3d96ea47ce6d0055d5b97e761d37b4e84195485cb5a38401be341fabf23bc32a \ - --hash=sha256:4dcab7c25e48fc09a73c3e463d09ac902a932a0f8d0c568238b3696d06bf377b \ - --hash=sha256:5fbf0f3f0fac7c089308bd771d2c6c7b7d53ae909dce1db52d8e921f6c19bb3a \ - --hash=sha256:6c25e1e9c2ce682d01fc5e2dde6598f7313027343bd14f4049b82ad0402e52cd \ - --hash=sha256:762f3771ae40e111d78d77cbe9c1035e886ac04a234d3ee0856bf4ecb3749d54 \ - --hash=sha256:90147dad8c22d64b2ff7331f8d4cddfdc3ee93e4879796f837bdbb2a0b141e0c \ - --hash=sha256:935cca25d35dda9e7bd46a24831dfd255307c55a07ff38fd1a92119cffc34857 \ - --hash=sha256:93fbee08c48e63d5d1b39ab56fd3fdd02e6c2431c3da0f4edaf54954744c718f \ - --hash=sha256:9541c69c62d7446539f2c1c06d7046aef822940d248fa4b8962ff0302862cc1f \ - --hash=sha256:c23f03cfd7d9826cdcbad7850de67e18b4654179e01fe9bc623d37c2638eb4ef \ - --hash=sha256:c3d1f5a1d403a8e640fa0887e9f7087331abb3f33b0f2207d2cc7f213e4a864c \ - --hash=sha256:d1998e545081da0ab276bcb4b33cce85f775adb86a516e8f55b3dac87f469548 \ - --hash=sha256:d5cf11bc7f0b71fb71af26af396c83dfd3f6eed56d4b6ef95d57867bf1e4ba65 \ - --hash=sha256:db0480ffbfb1193ac4e1e88239f31314fe4c6cdcf9c0b8712b55414afbf80db4 \ - --hash=sha256:de4ae486041878dc46e571a4c70ba337ed5233a1344c14a0790c4c4be4bbb8b4 \ - --hash=sha256:de5086cd475d67113ccb6f9fae6d8fe3ac54a4f9238fd08bfdb07b03d791ff0a \ - --hash=sha256:df34312149b495d9d03492ce97471234fd9037aa5ba217c2a6ea890e9166f151 \ - --hash=sha256:ead69ba488f806fe1b1b4050febafdbf206b81fa476126f3e16110c818bac396 +cryptography==42.0.4 \ + --hash=sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b \ + --hash=sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce \ + --hash=sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88 \ + --hash=sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7 \ + --hash=sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20 \ + --hash=sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9 \ + --hash=sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff \ + --hash=sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1 \ + --hash=sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764 \ + --hash=sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b \ + --hash=sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298 \ + --hash=sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1 \ + --hash=sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824 \ + --hash=sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257 \ + --hash=sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a \ + --hash=sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129 \ + --hash=sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb \ + --hash=sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929 \ + --hash=sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854 \ + --hash=sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52 \ + --hash=sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923 \ + --hash=sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885 \ + --hash=sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0 \ + --hash=sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd \ + --hash=sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2 \ + --hash=sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18 \ + --hash=sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b \ + --hash=sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992 \ + --hash=sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74 \ + --hash=sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660 \ + --hash=sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925 \ + --hash=sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449 # via # -r requirements.in # aioquic From 949c953f91dfa8054c66e191e614bd1d7bee4785 Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Wed, 21 Feb 2024 13:55:49 +0200 Subject: [PATCH 060/151] access_loggers: add Fluentd access logger extension (#32320) * initial fluentd access logger implementation Signed-off-by: ohadvano * fix Signed-off-by: ohadvano * fixes Signed-off-by: ohadvano * fix Signed-off-by: ohadvano * fix format Signed-off-by: ohadvano * fix format Signed-off-by: ohadvano * test Signed-off-by: ohadvano * format Signed-off-by: ohadvano * format and test fix Signed-off-by: ohadvano * revert Signed-off-by: ohadvano * fix compile_time_options Signed-off-by: ohadvano * add docs reference Signed-off-by: ohadvano * use exception safe and add tests Signed-off-by: ohadvano * fix format Signed-off-by: ohadvano * const and extension status Signed-off-by: ohadvano * comment and remove redundant build tags Signed-off-by: ohadvano * fix format Signed-off-by: ohadvano * use weak_ptr and pin Signed-off-by: ohadvano * fix format Signed-off-by: ohadvano * recrate if expired Signed-off-by: ohadvano --------- Signed-off-by: ohadvano Signed-off-by: ohadvano <49730675+ohadvano@users.noreply.github.com> --- CODEOWNERS | 1 + api/BUILD | 1 + .../access_loggers/fluentd/v3/BUILD | 9 + .../access_loggers/fluentd/v3/fluentd.proto | 70 ++++ api/versioning/BUILD | 1 + bazel/external/msgpack.BUILD | 16 + bazel/repositories.bzl | 11 + bazel/repository_locations.bzl | 15 + changelogs/current.yaml | 4 + .../observability/access_log/stats.rst | 14 + .../observability/access_logging.rst | 10 + source/common/json/json_internal.cc | 4 + source/common/json/json_internal.h | 7 + source/common/json/json_loader.cc | 4 + source/common/json/json_loader.h | 7 + .../extensions/access_loggers/fluentd/BUILD | 51 +++ .../access_loggers/fluentd/config.cc | 81 +++++ .../access_loggers/fluentd/config.h | 27 ++ .../fluentd/fluentd_access_log_impl.cc | 163 +++++++++ .../fluentd/fluentd_access_log_impl.h | 163 +++++++++ .../fluentd/substitution_formatter.cc | 24 ++ .../fluentd/substitution_formatter.h | 47 +++ source/extensions/extensions_build_config.bzl | 1 + source/extensions/extensions_metadata.yaml | 7 + test/common/json/json_loader_test.cc | 15 + test/extensions/access_loggers/fluentd/BUILD | 58 ++++ .../fluentd/fluentd_access_log_impl_test.cc | 321 ++++++++++++++++++ .../fluentd_access_log_integration_test.cc | 214 ++++++++++++ .../fluentd/substitution_formatter_test.cc | 46 +++ test/mocks/tcp/mocks.h | 16 + tools/spelling/spelling_dictionary.txt | 3 + 31 files changed, 1411 insertions(+) create mode 100644 api/envoy/extensions/access_loggers/fluentd/v3/BUILD create mode 100644 api/envoy/extensions/access_loggers/fluentd/v3/fluentd.proto create mode 100644 bazel/external/msgpack.BUILD create mode 100644 source/extensions/access_loggers/fluentd/BUILD create mode 100644 source/extensions/access_loggers/fluentd/config.cc create mode 100644 source/extensions/access_loggers/fluentd/config.h create mode 100644 source/extensions/access_loggers/fluentd/fluentd_access_log_impl.cc create mode 100644 source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h create mode 100644 source/extensions/access_loggers/fluentd/substitution_formatter.cc create mode 100644 source/extensions/access_loggers/fluentd/substitution_formatter.h create mode 100644 test/extensions/access_loggers/fluentd/BUILD create mode 100644 test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc create mode 100644 test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc create mode 100644 test/extensions/access_loggers/fluentd/substitution_formatter_test.cc diff --git a/CODEOWNERS b/CODEOWNERS index bd937e7a08ad..d08360957e56 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -258,6 +258,7 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123 # proxy protocol /*/extensions/filters/listener/proxy_protocol @ggreenway @soulxu # access loggers +/*/extensions/access_loggers/fluentd @ohadvano @wbpcode /*/extensions/access_loggers/grpc @wbpcode @cpakulski @giantcroc @gyohuangxin # stats /*/extensions/stat_sinks/statsd @mattklein123 @suniltheta diff --git a/api/BUILD b/api/BUILD index 45ad652f544b..33de22a6811c 100644 --- a/api/BUILD +++ b/api/BUILD @@ -134,6 +134,7 @@ proto_library( "//envoy/data/tap/v3:pkg", "//envoy/extensions/access_loggers/file/v3:pkg", "//envoy/extensions/access_loggers/filters/cel/v3:pkg", + "//envoy/extensions/access_loggers/fluentd/v3:pkg", "//envoy/extensions/access_loggers/grpc/v3:pkg", "//envoy/extensions/access_loggers/open_telemetry/v3:pkg", "//envoy/extensions/access_loggers/stream/v3:pkg", diff --git a/api/envoy/extensions/access_loggers/fluentd/v3/BUILD b/api/envoy/extensions/access_loggers/fluentd/v3/BUILD new file mode 100644 index 000000000000..29ebf0741406 --- /dev/null +++ b/api/envoy/extensions/access_loggers/fluentd/v3/BUILD @@ -0,0 +1,9 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = ["@com_github_cncf_xds//udpa/annotations:pkg"], +) diff --git a/api/envoy/extensions/access_loggers/fluentd/v3/fluentd.proto b/api/envoy/extensions/access_loggers/fluentd/v3/fluentd.proto new file mode 100644 index 000000000000..e6b2adfdc9c0 --- /dev/null +++ b/api/envoy/extensions/access_loggers/fluentd/v3/fluentd.proto @@ -0,0 +1,70 @@ +syntax = "proto3"; + +package envoy.extensions.access_loggers.fluentd.v3; + +import "google/protobuf/duration.proto"; +import "google/protobuf/struct.proto"; +import "google/protobuf/wrappers.proto"; + +import "udpa/annotations/status.proto"; +import "validate/validate.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.access_loggers.fluentd.v3"; +option java_outer_classname = "FluentdProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/access_loggers/fluentd/v3;fluentdv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Fluentd access log] + +// Configuration for the *envoy.access_loggers.fluentd* :ref:`AccessLog `. +// This access log extension will send the emitted access logs over a TCP connection to an upstream that is accepting +// the Fluentd Forward Protocol as described in: `Fluentd Forward Protocol Specification +// `_. +// [#extension: envoy.access_loggers.fluentd] +// [#next-free-field: 7] +message FluentdAccessLogConfig { + // The upstream cluster to connect to for streaming the Fluentd messages. + string cluster = 1 [(validate.rules).string = {min_len: 1}]; + + // A tag is a string separated with '.' (e.g. log.type) to categorize events. + // See: https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#message-modes + string tag = 2 [(validate.rules).string = {min_len: 1}]; + + // The prefix to use when emitting :ref:`statistics `. + string stat_prefix = 3 [(validate.rules).string = {min_len: 1}]; + + // Interval for flushing access logs to the TCP 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 = 4 [(validate.rules).duration = {gt {}}]; + + // Soft size limit in bytes for access log entries buffer. The logger will buffer requests until + // this limit it hit, or every time flush interval is elapsed, whichever comes first. When the buffer + // limit is hit, the logger will immediately flush the buffer contents. Setting it to zero effectively + // disables the batching. Defaults to 16384. + google.protobuf.UInt32Value buffer_size_bytes = 5; + + // A struct that represents the record that is sent for each log entry. + // https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#entry + // Values are rendered as strings, numbers, or boolean values as appropriate. + // Nested JSON objects may be produced by some command operators (e.g. FILTER_STATE or DYNAMIC_METADATA). + // See :ref:`format string` documentation for a specific command operator details. + // + // .. validated-code-block:: yaml + // :type-name: envoy.extensions.access_loggers.fluentd.v3.FluentdAccessLogConfig + // + // record: + // status: "%RESPONSE_CODE%" + // message: "%LOCAL_REPLY_BODY%" + // + // The following msgpack record would be created: + // + // .. code-block:: json + // + // { + // "status": 500, + // "message": "My error message" + // } + google.protobuf.Struct record = 6 [(validate.rules).message = {required: true}]; +} diff --git a/api/versioning/BUILD b/api/versioning/BUILD index edb89aa6fe28..86061fda4ab7 100644 --- a/api/versioning/BUILD +++ b/api/versioning/BUILD @@ -72,6 +72,7 @@ proto_library( "//envoy/data/tap/v3:pkg", "//envoy/extensions/access_loggers/file/v3:pkg", "//envoy/extensions/access_loggers/filters/cel/v3:pkg", + "//envoy/extensions/access_loggers/fluentd/v3:pkg", "//envoy/extensions/access_loggers/grpc/v3:pkg", "//envoy/extensions/access_loggers/open_telemetry/v3:pkg", "//envoy/extensions/access_loggers/stream/v3:pkg", diff --git a/bazel/external/msgpack.BUILD b/bazel/external/msgpack.BUILD new file mode 100644 index 000000000000..5f2d42163398 --- /dev/null +++ b/bazel/external/msgpack.BUILD @@ -0,0 +1,16 @@ +licenses(["notice"]) # Apache 2 + +cc_library( + name = "msgpack", + srcs = glob([ + "src/*.c", + "include/**/*.h", + "include/**/*.hpp", + ]), + defines = ["MSGPACK_NO_BOOST"], + includes = [ + "include", + ], + strip_include_prefix = "include", + visibility = ["//visibility:public"], +) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 1bb053efa516..6394e3ce7450 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -318,6 +318,7 @@ def envoy_dependencies(skip_targets = []): _com_github_libevent_libevent() _com_github_luajit_luajit() _com_github_nghttp2_nghttp2() + _com_github_msgpack_cpp() _com_github_skyapm_cpp2sky() _com_github_nodejs_http_parser() _com_github_alibaba_hessian2_codec() @@ -735,6 +736,16 @@ def _com_github_nghttp2_nghttp2(): actual = "@envoy//bazel/foreign_cc:nghttp2", ) +def _com_github_msgpack_cpp(): + external_http_archive( + name = "com_github_msgpack_cpp", + build_file = "@envoy//bazel/external:msgpack.BUILD", + ) + native.bind( + name = "msgpack", + actual = "@com_github_msgpack_cpp//:msgpack", + ) + def _io_hyperscan(): external_http_archive( name = "io_hyperscan", diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 42ff52c85dc3..24a59a120340 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -778,6 +778,21 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "MIT", license_url = "https://github.com/jbeder/yaml-cpp/blob/{version}/LICENSE", ), + com_github_msgpack_cpp = dict( + project_name = "msgpack for C/C++", + project_desc = "MessagePack is an efficient binary serialization format", + project_url = "https://github.com/msgpack/msgpack-c", + version = "6.1.0", + sha256 = "23ede7e93c8efee343ad8c6514c28f3708207e5106af3b3e4969b3a9ed7039e7", + strip_prefix = "msgpack-cxx-{version}", + urls = ["https://github.com/msgpack/msgpack-c/releases/download/cpp-{version}/msgpack-cxx-{version}.tar.gz"], + use_category = ["observability_ext"], + extensions = ["envoy.access_loggers.fluentd"], + release_date = "2023-07-08", + cpe = "cpe:2.3:a:messagepack:messagepack:*", + license = "Boost", + license_url = "https://github.com/msgpack/msgpack-c/blob/cpp-{version}/LICENSE_1_0.txt", + ), com_github_google_jwt_verify = dict( project_name = "jwt_verify_lib", project_desc = "JWT verification library for C++", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index e6019f1755d2..347b9f6c4eb3 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -204,6 +204,10 @@ new_features: Envoy will select the host with the fewest active requests from the entire host set rather than :ref:`choice_count ` random choices. +- area: access_loggers + change: | + Added :ref:`Fluentd access logger ` + to support flushing access logs in `Fluentd format `_. - area: redis change: | Added support for the ``ECHO`` command. diff --git a/docs/root/configuration/observability/access_log/stats.rst b/docs/root/configuration/observability/access_log/stats.rst index 632ebaf48283..0a61df4e4518 100644 --- a/docs/root/configuration/observability/access_log/stats.rst +++ b/docs/root/configuration/observability/access_log/stats.rst @@ -33,3 +33,17 @@ The file access log has statistics rooted at the *filesystem.* namespace. 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 + +Fluentd access log statistics +----------------------------- + +The Fluentd access log has statistics rooted at the *access_logs.fluentd..* namespace. + +.. csv-table:: + :header: Name, Type, Description + :widths: 1, 1, 2 + + entries_lost, Counter, Total number of times an access log entry was discarded due to unavailable connection. + entries_buffered, Counter, Total number of entries (access log record) that was buffered/ + events_sent, Counter, Total number of events (Fluentd Forward Mode events) sent to the upstream. + connections_closed, Counter, Total number of times a connection to the upstream cluster was closed. diff --git a/docs/root/intro/arch_overview/observability/access_logging.rst b/docs/root/intro/arch_overview/observability/access_logging.rst index 0371c385f52d..130f70642ce9 100644 --- a/docs/root/intro/arch_overview/observability/access_logging.rst +++ b/docs/root/intro/arch_overview/observability/access_logging.rst @@ -138,6 +138,15 @@ Stderr response headers. * Writes to the standard error of the process. It works in all platforms. +Fluentd +******** + +* Flush access logs over a TCP connection to an upstream that is accepting the Fluentd Forward Protocol as described in: + `Fluentd Forward Protocol Specification `_. +* The data sent over the wire is a stream of + `Fluentd Forward Mode events `_ + which may contain one or more access log entries (depending on the flushing interval and other configuration parameters). + Further reading --------------- @@ -148,3 +157,4 @@ Further reading * OpenTelemetry (gRPC) :ref:`LogsService ` * Stdout :ref:`access log sink ` * Stderr :ref:`access log sink ` +* Fluentd :ref:`access log sink ` diff --git a/source/common/json/json_internal.cc b/source/common/json/json_internal.cc index e7de7479247d..f7e624ed7881 100644 --- a/source/common/json/json_internal.cc +++ b/source/common/json/json_internal.cc @@ -788,6 +788,10 @@ std::string Factory::serialize(absl::string_view str) { return j.dump(); } +std::vector Factory::jsonToMsgpack(const std::string& json_string) { + return nlohmann::json::to_msgpack(nlohmann::json::parse(json_string, nullptr, false)); +} + } // namespace Nlohmann } // namespace Json } // namespace Envoy diff --git a/source/common/json/json_internal.h b/source/common/json/json_internal.h index bdb2010d1071..f9e9425ff906 100644 --- a/source/common/json/json_internal.h +++ b/source/common/json/json_internal.h @@ -38,6 +38,13 @@ class Factory { * @return A string suitable for inclusion in a JSON stream, including double-quotes. */ static std::string serialize(absl::string_view str); + + /* + * Serializes a JSON string to a byte vector using the MessagePack serialization format. + * If the provided JSON string is invalid, an empty vector will be returned. + * See: https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static std::vector jsonToMsgpack(const std::string& json); }; } // namespace Nlohmann diff --git a/source/common/json/json_loader.cc b/source/common/json/json_loader.cc index f755480634b9..be0a5f04815a 100644 --- a/source/common/json/json_loader.cc +++ b/source/common/json/json_loader.cc @@ -18,5 +18,9 @@ ObjectSharedPtr Factory::loadFromProtobufStruct(const ProtobufWkt::Struct& proto return Nlohmann::Factory::loadFromProtobufStruct(protobuf_struct); } +std::vector Factory::jsonToMsgpack(const std::string& json) { + return Nlohmann::Factory::jsonToMsgpack(json); +} + } // namespace Json } // namespace Envoy diff --git a/source/common/json/json_loader.h b/source/common/json/json_loader.h index 5a427d5d1b4a..34df5376791a 100644 --- a/source/common/json/json_loader.h +++ b/source/common/json/json_loader.h @@ -27,6 +27,13 @@ class Factory { * Constructs a Json Object from a Protobuf struct. */ static ObjectSharedPtr loadFromProtobufStruct(const ProtobufWkt::Struct& protobuf_struct); + + /* + * Serializes a JSON string to a byte vector using the MessagePack serialization format. + * If the provided JSON string is invalid, an empty vector will be returned. + * See: https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static std::vector jsonToMsgpack(const std::string& json); }; } // namespace Json diff --git a/source/extensions/access_loggers/fluentd/BUILD b/source/extensions/access_loggers/fluentd/BUILD new file mode 100644 index 000000000000..3a7151731baf --- /dev/null +++ b/source/extensions/access_loggers/fluentd/BUILD @@ -0,0 +1,51 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_library( + name = "substitution_formatter_lib", + srcs = ["substitution_formatter.cc"], + hdrs = ["substitution_formatter.h"], + deps = [ + "//envoy/formatter:substitution_formatter_interface", + "//source/common/json:json_loader_lib", + ], +) + +envoy_cc_library( + name = "fluentd_access_log_lib", + srcs = ["fluentd_access_log_impl.cc"], + hdrs = ["fluentd_access_log_impl.h"], + external_deps = [ + "msgpack", + ], + deps = [ + ":substitution_formatter_lib", + "//envoy/access_log:access_log_interface", + "//source/common/access_log:access_log_lib", + "//source/extensions/access_loggers/common:access_log_base", + "@envoy_api//envoy/extensions/access_loggers/fluentd/v3:pkg_cc_proto", + ], +) + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":fluentd_access_log_lib", + ":substitution_formatter_lib", + "//envoy/access_log:access_log_config_interface", + "//envoy/registry", + "//source/common/config:config_provider_lib", + "//source/common/formatter:substitution_format_string_lib", + "//source/common/protobuf", + ], +) diff --git a/source/extensions/access_loggers/fluentd/config.cc b/source/extensions/access_loggers/fluentd/config.cc new file mode 100644 index 000000000000..e9929ab0c318 --- /dev/null +++ b/source/extensions/access_loggers/fluentd/config.cc @@ -0,0 +1,81 @@ +#include "source/extensions/access_loggers/fluentd/config.h" + +#include + +#include "envoy/registry/registry.h" +#include "envoy/server/filter_config.h" + +#include "source/common/common/logger.h" +#include "source/common/config/utility.h" +#include "source/common/formatter/substitution_format_string.h" +#include "source/common/formatter/substitution_formatter.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace Fluentd { + +// Singleton registration via macro defined in envoy/singleton/manager.h +SINGLETON_MANAGER_REGISTRATION(fluentd_access_logger_cache); + +FluentdAccessLoggerCacheSharedPtr +getAccessLoggerCacheSingleton(Server::Configuration::ServerFactoryContext& context) { + return context.singletonManager().getTyped( + SINGLETON_MANAGER_REGISTERED_NAME(fluentd_access_logger_cache), + [&context] { + return std::make_shared( + context.clusterManager(), context.scope(), context.threadLocal()); + }, + /* pin = */ true); +} + +AccessLog::InstanceSharedPtr +FluentdAccessLogFactory::createAccessLogInstance(const Protobuf::Message& config, + AccessLog::FilterPtr&& filter, + Server::Configuration::FactoryContext& context) { + const auto& proto_config = MessageUtil::downcastAndValidate< + const envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig&>( + config, context.messageValidationVisitor()); + + absl::Status status = context.serverFactoryContext().clusterManager().checkActiveStaticCluster( + proto_config.cluster()); + if (!status.ok()) { + throw EnvoyException(fmt::format("cluster '{}' was not found", proto_config.cluster())); + } + + // Supporting nested object serialization is more complex with MessagePack. + // Using an already existing JSON formatter, and later converting the JSON string to a msgpack + // payload. + // TODO(ohadvano): Improve the formatting operation by creating a dedicated formatter that + // will directly serialize the record to msgpack payload. + Formatter::FormatterPtr json_formatter = + Formatter::SubstitutionFormatStringUtils::createJsonFormatter(proto_config.record(), true, + false, false); + FluentdFormatterPtr fluentd_formatter = + std::make_unique(std::move(json_formatter)); + + return std::make_shared( + std::move(filter), std::move(fluentd_formatter), + std::make_shared(proto_config), + context.serverFactoryContext().threadLocal(), + getAccessLoggerCacheSingleton(context.serverFactoryContext())); +} + +ProtobufTypes::MessagePtr FluentdAccessLogFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +std::string FluentdAccessLogFactory::name() const { return "envoy.access_loggers.fluentd"; } + +/** + * Static registration for the fluentd access log. @see RegisterFactory. + */ +REGISTER_FACTORY(FluentdAccessLogFactory, + AccessLog::AccessLogInstanceFactory){"envoy.fluentd_access_log"}; + +} // namespace Fluentd +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/fluentd/config.h b/source/extensions/access_loggers/fluentd/config.h new file mode 100644 index 000000000000..b7dfff974b1a --- /dev/null +++ b/source/extensions/access_loggers/fluentd/config.h @@ -0,0 +1,27 @@ +#pragma once + +#include "envoy/access_log/access_log_config.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace Fluentd { + +/** + * Config registration for the fluentd access log. @see AccessLogInstanceFactory. + */ +class FluentdAccessLogFactory : public AccessLog::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 Fluentd +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.cc b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.cc new file mode 100644 index 000000000000..ce1a96694d90 --- /dev/null +++ b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.cc @@ -0,0 +1,163 @@ +#include "source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h" + +#include "source/common/buffer/buffer_impl.h" + +#include "msgpack.hpp" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace Fluentd { + +using MessagePackBuffer = msgpack::sbuffer; +using MessagePackPacker = msgpack::packer; + +FluentdAccessLoggerImpl::FluentdAccessLoggerImpl(Tcp::AsyncTcpClientPtr client, + Event::Dispatcher& dispatcher, + const FluentdAccessLogConfig& config, + Stats::Scope& parent_scope) + : tag_(config.tag()), id_(dispatcher.name()), + stats_scope_(parent_scope.createScope(config.stat_prefix())), + fluentd_stats_( + {ACCESS_LOG_FLUENTD_STATS(POOL_COUNTER(*stats_scope_), POOL_GAUGE(*stats_scope_))}), + client_(std::move(client)), + buffer_flush_interval_msec_(PROTOBUF_GET_MS_OR_DEFAULT(config, buffer_flush_interval, 1000)), + max_buffer_size_bytes_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, buffer_size_bytes, 16384)), + flush_timer_(dispatcher.createTimer([this]() { + flush(); + flush_timer_->enableTimer(buffer_flush_interval_msec_); + })) { + client_->setAsyncTcpClientCallbacks(*this); + flush_timer_->enableTimer(buffer_flush_interval_msec_); +} + +void FluentdAccessLoggerImpl::onEvent(Network::ConnectionEvent event) { + connecting_ = false; + + if (event == Network::ConnectionEvent::Connected) { + flush(); + } else if (event == Network::ConnectionEvent::LocalClose || + event == Network::ConnectionEvent::RemoteClose) { + ENVOY_LOG(debug, "upstream connection was closed"); + // TODO(ohadvano): add an option to reconnect to the upstream, if configured + + fluentd_stats_.connections_closed_.inc(); + disconnected_ = true; + clearBuffer(); + + ASSERT(flush_timer_ != nullptr); + flush_timer_->disableTimer(); + } +} + +void FluentdAccessLoggerImpl::log(EntryPtr&& entry) { + if (disconnected_) { + fluentd_stats_.entries_lost_.inc(); + // We will lose the data deliberately so the buffer doesn't grow infinitely. + // Since the client is disconnected, there's nothing much we can do with the data anyway. + // TODO(ohadvano): add an option to reconnect to the upstream, if configured + return; + } + + approximate_message_size_bytes_ += sizeof(entry->time_) + entry->record_.size(); + entries_.push_back(std::move(entry)); + fluentd_stats_.entries_buffered_.inc(); + if (approximate_message_size_bytes_ >= max_buffer_size_bytes_) { + flush(); + } +} + +void FluentdAccessLoggerImpl::flush() { + ASSERT(!disconnected_); + + if (entries_.size() == 0 || connecting_) { + // nothing to send, or we're still waiting for an upstream connection. + return; + } + + if (!client_->connected()) { + connecting_ = true; + client_->connect(); + return; + } + + // Creating a Fluentd Forward Protocol Specification (v1) forward mode event as specified in: + // https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#forward-mode + MessagePackBuffer buffer; + MessagePackPacker packer(buffer); + packer.pack_array(2); // 1 - tag field, 2 - entries array. + packer.pack(tag_); + packer.pack_array(entries_.size()); + + for (auto& entry : entries_) { + packer.pack_array(2); // 1 - time, 2 - record. + packer.pack(entry->time_); + const char* record_bytes = reinterpret_cast(&entry->record_[0]); + packer.pack_bin_body(record_bytes, entry->record_.size()); + } + + Buffer::OwnedImpl data(buffer.data(), buffer.size()); + client_->write(data, false); + fluentd_stats_.events_sent_.inc(); + clearBuffer(); +} + +void FluentdAccessLoggerImpl::clearBuffer() { + entries_.clear(); + approximate_message_size_bytes_ = 0; +} + +FluentdAccessLoggerCacheImpl::FluentdAccessLoggerCacheImpl( + Upstream::ClusterManager& cluster_manager, Stats::Scope& parent_scope, + ThreadLocal::SlotAllocator& tls) + : cluster_manager_(cluster_manager), + stats_scope_(parent_scope.createScope("access_logs.fluentd")), tls_slot_(tls.allocateSlot()) { + tls_slot_->set( + [](Event::Dispatcher& dispatcher) { return std::make_shared(dispatcher); }); +} + +FluentdAccessLoggerSharedPtr +FluentdAccessLoggerCacheImpl::getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config) { + auto& cache = tls_slot_->getTyped(); + const auto cache_key = MessageUtil::hash(*config); + const auto it = cache.access_loggers_.find(cache_key); + if (it != cache.access_loggers_.end() && !it->second.expired()) { + return it->second.lock(); + } + + auto client = + cluster_manager_.getThreadLocalCluster(config->cluster()) + ->tcpAsyncClient(nullptr, std::make_shared(false)); + + const auto logger = std::make_shared( + std::move(client), cache.dispatcher_, *config, *stats_scope_); + cache.access_loggers_.emplace(cache_key, logger); + return logger; +} + +FluentdAccessLog::FluentdAccessLog(AccessLog::FilterPtr&& filter, FluentdFormatterPtr&& formatter, + const FluentdAccessLogConfigSharedPtr config, + ThreadLocal::SlotAllocator& tls, + FluentdAccessLoggerCacheSharedPtr access_logger_cache) + : ImplBase(std::move(filter)), formatter_(std::move(formatter)), tls_slot_(tls.allocateSlot()), + config_(config), access_logger_cache_(access_logger_cache) { + tls_slot_->set( + [config = config_, access_logger_cache = access_logger_cache_](Event::Dispatcher&) { + return std::make_shared(access_logger_cache->getOrCreateLogger(config)); + }); +} + +void FluentdAccessLog::emitLog(const Formatter::HttpFormatterContext& context, + const StreamInfo::StreamInfo& stream_info) { + auto msgpack = formatter_->format(context, stream_info); + uint64_t time = std::chrono::duration_cast( + stream_info.timeSource().systemTime().time_since_epoch()) + .count(); + tls_slot_->getTyped().logger_->log( + std::make_unique(time, std::move(msgpack))); +} + +} // namespace Fluentd +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h new file mode 100644 index 000000000000..11a03530d32d --- /dev/null +++ b/source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h @@ -0,0 +1,163 @@ +#pragma once + +#include + +#include "envoy/extensions/access_loggers/fluentd/v3/fluentd.pb.h" +#include "envoy/extensions/access_loggers/fluentd/v3/fluentd.pb.validate.h" + +#include "source/common/formatter/substitution_formatter.h" +#include "source/extensions/access_loggers/common/access_log_base.h" +#include "source/extensions/access_loggers/fluentd/substitution_formatter.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace Fluentd { + +using FluentdAccessLogConfig = + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig; +using FluentdAccessLogConfigSharedPtr = std::shared_ptr; + +// Entry represents a single Fluentd message, msgpack format based, as specified in: +// https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1#entry +class Entry { +public: + Entry(const Entry&) = delete; + Entry& operator=(const Entry&) = delete; + Entry(uint64_t time, std::vector&& record) : time_(time), record_(record) {} + + const uint64_t time_; + const std::vector record_; +}; + +using EntryPtr = std::unique_ptr; + +class FluentdAccessLogger { +public: + virtual ~FluentdAccessLogger() = default; + + /** + * Send the Fluentd formatted message over the upstream TCP connection. + */ + virtual void log(EntryPtr&& entry) PURE; +}; + +using FluentdAccessLoggerWeakPtr = std::weak_ptr; +using FluentdAccessLoggerSharedPtr = std::shared_ptr; + +#define ACCESS_LOG_FLUENTD_STATS(COUNTER, GAUGE) \ + COUNTER(entries_lost) \ + COUNTER(entries_buffered) \ + COUNTER(events_sent) \ + COUNTER(connections_closed) + +struct AccessLogFluentdStats { + ACCESS_LOG_FLUENTD_STATS(GENERATE_COUNTER_STRUCT, GENERATE_GAUGE_STRUCT) +}; + +class FluentdAccessLoggerImpl : public Tcp::AsyncTcpClientCallbacks, + public FluentdAccessLogger, + public Logger::Loggable { +public: + FluentdAccessLoggerImpl(Tcp::AsyncTcpClientPtr client, Event::Dispatcher& dispatcher, + const FluentdAccessLogConfig& config, Stats::Scope& parent_scope); + + // Tcp::AsyncTcpClientCallbacks + void onEvent(Network::ConnectionEvent event) override; + void onAboveWriteBufferHighWatermark() override {} + void onBelowWriteBufferLowWatermark() override {} + void onData(Buffer::Instance&, bool) override {} + + // FluentdAccessLogger + void log(EntryPtr&& entry) override; + +private: + void flush(); + void clearBuffer(); + + bool disconnected_ = false; + bool connecting_ = false; + std::string tag_; + std::string id_; + const Stats::ScopeSharedPtr stats_scope_; + AccessLogFluentdStats fluentd_stats_; + std::vector entries_; + uint64_t approximate_message_size_bytes_ = 0; + const Tcp::AsyncTcpClientPtr client_; + const std::chrono::milliseconds buffer_flush_interval_msec_; + const uint64_t max_buffer_size_bytes_; + const Event::TimerPtr flush_timer_; +}; + +class FluentdAccessLoggerCache { +public: + virtual ~FluentdAccessLoggerCache() = default; + + /** + * Get existing logger or create a new one for the given configuration. + * @return FluentdAccessLoggerSharedPtr ready for logging requests. + */ + virtual FluentdAccessLoggerSharedPtr + getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config) PURE; +}; + +using FluentdAccessLoggerCacheSharedPtr = std::shared_ptr; + +class FluentdAccessLoggerCacheImpl : public Singleton::Instance, public FluentdAccessLoggerCache { +public: + FluentdAccessLoggerCacheImpl(Upstream::ClusterManager& cluster_manager, + Stats::Scope& parent_scope, ThreadLocal::SlotAllocator& tls); + + FluentdAccessLoggerSharedPtr + getOrCreateLogger(const FluentdAccessLogConfigSharedPtr config) override; + +private: + /** + * Per-thread cache. + */ + struct ThreadLocalCache : public ThreadLocal::ThreadLocalObject { + 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_; + }; + + Upstream::ClusterManager& cluster_manager_; + const Stats::ScopeSharedPtr stats_scope_; + ThreadLocal::SlotPtr tls_slot_; +}; + +/** + * Access log Instance that writes logs to a Fluentd. + */ +class FluentdAccessLog : public Common::ImplBase { +public: + FluentdAccessLog(AccessLog::FilterPtr&& filter, FluentdFormatterPtr&& formatter, + const FluentdAccessLogConfigSharedPtr config, ThreadLocal::SlotAllocator& tls, + FluentdAccessLoggerCacheSharedPtr access_logger_cache); + +private: + /** + * Per-thread cached logger. + */ + struct ThreadLocalLogger : public ThreadLocal::ThreadLocalObject { + ThreadLocalLogger(FluentdAccessLoggerSharedPtr logger) : logger_(std::move(logger)) {} + + const FluentdAccessLoggerSharedPtr logger_; + }; + + // Common::ImplBase + void emitLog(const Formatter::HttpFormatterContext& context, + const StreamInfo::StreamInfo& stream_info) override; + + FluentdFormatterPtr formatter_; + const ThreadLocal::SlotPtr tls_slot_; + const FluentdAccessLogConfigSharedPtr config_; + const FluentdAccessLoggerCacheSharedPtr access_logger_cache_; +}; + +} // namespace Fluentd +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/fluentd/substitution_formatter.cc b/source/extensions/access_loggers/fluentd/substitution_formatter.cc new file mode 100644 index 000000000000..923719296e19 --- /dev/null +++ b/source/extensions/access_loggers/fluentd/substitution_formatter.cc @@ -0,0 +1,24 @@ +#include "source/extensions/access_loggers/fluentd/substitution_formatter.h" + +#include "envoy/stream_info/stream_info.h" + +#include "source/common/json/json_loader.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace Fluentd { + +FluentdFormatterImpl::FluentdFormatterImpl(Formatter::FormatterPtr json_formatter) + : json_formatter_(std::move(json_formatter)) {} + +std::vector FluentdFormatterImpl::format(const Formatter::HttpFormatterContext& context, + const StreamInfo::StreamInfo& stream_info) const { + auto json_string = json_formatter_->formatWithContext(context, stream_info); + return Json::Factory::jsonToMsgpack(json_string); +} + +} // namespace Fluentd +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/access_loggers/fluentd/substitution_formatter.h b/source/extensions/access_loggers/fluentd/substitution_formatter.h new file mode 100644 index 000000000000..6c2e173ad3c6 --- /dev/null +++ b/source/extensions/access_loggers/fluentd/substitution_formatter.h @@ -0,0 +1,47 @@ +#pragma once + +#include "envoy/formatter/substitution_formatter.h" +#include "envoy/stream_info/stream_info.h" + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace Fluentd { + +/** + * A formatter for Fluentd logs + */ +class FluentdFormatter { +public: + virtual ~FluentdFormatter() = default; + + /** + * @return a vector of bytes representing the Fluentd MessagePack record + */ + virtual std::vector format(const Formatter::HttpFormatterContext& context, + const StreamInfo::StreamInfo& stream_info) const PURE; +}; + +using FluentdFormatterPtr = std::unique_ptr; + +/** + * A formatter for Fluentd logs. It is expecting to receive a JSON formatter, and converts the + * JSON formatter message to MessagePack format. + * TODO(ohadvano): Improve the formatting operation by creating a dedicated formatter that + * will directly serialize the record to msgpack payload. + */ +class FluentdFormatterImpl : public FluentdFormatter { +public: + FluentdFormatterImpl(Formatter::FormatterPtr json_formatter); + + std::vector format(const Formatter::HttpFormatterContext& context, + const StreamInfo::StreamInfo& stream_info) const override; + +private: + Formatter::FormatterPtr json_formatter_; +}; + +} // namespace Fluentd +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index a67bf3be4744..a3dc5309a637 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.extension_filters.cel": "//source/extensions/access_loggers/filters/cel:config", + "envoy.access_loggers.fluentd" : "//source/extensions/access_loggers/fluentd: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.open_telemetry": "//source/extensions/access_loggers/open_telemetry:config", diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index 7efa3acdb2ae..bf75bb02917f 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -12,6 +12,13 @@ envoy.access_loggers.extension_filters.cel: status: alpha type_urls: - envoy.extensions.access_loggers.filters.cel.v3.ExpressionFilter +envoy.access_loggers.fluentd: + categories: + - envoy.access_loggers + security_posture: robust_to_untrusted_downstream + status: alpha + type_urls: + - envoy.extensions.access_loggers.fluentd.v3.FluentdAccessLogConfig envoy.access_loggers.http_grpc: categories: - envoy.access_loggers diff --git a/test/common/json/json_loader_test.cc b/test/common/json/json_loader_test.cc index ecdf90d7135b..ad85fd3e0c21 100644 --- a/test/common/json/json_loader_test.cc +++ b/test/common/json/json_loader_test.cc @@ -510,6 +510,21 @@ TEST_F(JsonLoaderTest, LoadFromStructUnknownValueCase) { "Protobuf value case not implemented"); } +TEST_F(JsonLoaderTest, JsonToMsgpack) { + std::vector msgpack = Factory::jsonToMsgpack("{\"hello\":\"world\"}"); + std::vector expected_bytes = {0x81, 0xA5, 0x68, 0x65, 0x6C, 0x6C, 0x6F, + 0xA5, 0x77, 0x6F, 0x72, 0x6C, 0x64}; + EXPECT_EQ(msgpack, expected_bytes); +} + +TEST_F(JsonLoaderTest, InvalidJsonToMsgpack) { + EXPECT_EQ(0, Factory::jsonToMsgpack("").size()); + EXPECT_EQ(0, Factory::jsonToMsgpack("{").size()); + EXPECT_EQ(0, Factory::jsonToMsgpack("{\"hello\":}").size()); + EXPECT_EQ(0, Factory::jsonToMsgpack("\"hello\":\"world\"}").size()); + EXPECT_EQ(0, Factory::jsonToMsgpack("{\"hello\":\"world\"").size()); +} + } // namespace } // namespace Json } // namespace Envoy diff --git a/test/extensions/access_loggers/fluentd/BUILD b/test/extensions/access_loggers/fluentd/BUILD new file mode 100644 index 000000000000..288f23d47fb7 --- /dev/null +++ b/test/extensions/access_loggers/fluentd/BUILD @@ -0,0 +1,58 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "fluentd_access_log_impl_test", + srcs = ["fluentd_access_log_impl_test.cc"], + extension_names = ["envoy.access_loggers.fluentd"], + external_deps = [ + "msgpack", + ], + deps = [ + "//source/extensions/access_loggers/fluentd:config", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:environment_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/accesslog/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/access_loggers/fluentd/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "substitution_formatter_test", + srcs = ["substitution_formatter_test.cc"], + extension_names = ["envoy.access_loggers.fluentd"], + deps = [ + "//source/extensions/access_loggers/fluentd:substitution_formatter_lib", + "//test/mocks/server:factory_context_mocks", + "//test/test_common:environment_lib", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "fluentd_access_log_integration_test", + size = "large", + srcs = ["fluentd_access_log_integration_test.cc"], + extension_names = ["envoy.access_loggers.fluentd"], + deps = [ + "//source/extensions/access_loggers/fluentd:config", + "//source/extensions/filters/network/tcp_proxy:config", + "//test/integration:integration_lib", + "//test/test_common:registry_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/access_loggers/fluentd/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/filters/network/tcp_proxy/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc b/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc new file mode 100644 index 000000000000..a058c22dcf07 --- /dev/null +++ b/test/extensions/access_loggers/fluentd/fluentd_access_log_impl_test.cc @@ -0,0 +1,321 @@ +#include "envoy/common/time.h" +#include "envoy/config/accesslog/v3/accesslog.pb.h" +#include "envoy/extensions/access_loggers/fluentd/v3/fluentd.pb.h" +#include "envoy/registry/registry.h" + +#include "source/common/access_log/access_log_impl.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/access_loggers/fluentd/config.h" +#include "source/extensions/access_loggers/fluentd/fluentd_access_log_impl.h" + +#include "test/mocks/access_log/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/test_time.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "msgpack.hpp" + +using testing::Return; +using testing::ReturnRef; + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace Fluentd { +namespace { + +class FluentdAccessLoggerImplTest : public testing::Test { +public: + FluentdAccessLoggerImplTest() + : async_client_(new Tcp::AsyncClient::MockAsyncTcpClient()), + timer_(new Event::MockTimer(&dispatcher_)) {} + + void init(int buffer_size_bytes = 0) { + EXPECT_CALL(*async_client_, setAsyncTcpClientCallbacks(_)); + EXPECT_CALL(*timer_, enableTimer(_, _)); + + config_.set_tag(tag_); + config_.mutable_buffer_size_bytes()->set_value(buffer_size_bytes); + logger_ = std::make_unique( + Tcp::AsyncTcpClientPtr{async_client_}, dispatcher_, config_, *stats_store_.rootScope()); + } + + std::string getExpectedMsgpackPayload(int entries_count) { + msgpack::sbuffer buffer; + msgpack::packer packer(buffer); + packer.pack_array(2); + packer.pack(tag_); + packer.pack_array(entries_count); + for (int idx = 0; idx < entries_count; idx++) { + packer.pack_array(2); + packer.pack(time_); + const char* record_bytes = reinterpret_cast(&data_[0]); + packer.pack_bin_body(record_bytes, data_.size()); + } + + return std::string(buffer.data(), buffer.size()); + } + + std::string tag_ = "test.tag"; + uint64_t time_ = 123; + std::vector data_ = {10, 20}; + Tcp::AsyncClient::MockAsyncTcpClient* async_client_; + Stats::IsolatedStoreImpl stats_store_; + Event::MockDispatcher dispatcher_; + Event::MockTimer* timer_; + std::unique_ptr logger_; + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config_; +}; + +TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfNotConnectedToUpstream) { + init(); + EXPECT_CALL(*async_client_, connect()).WillOnce(Return(true)); + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + logger_->log(std::make_unique(time_, std::move(data_))); +} + +TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfBufferLimitNotPassed) { + init(100); + EXPECT_CALL(*async_client_, connect()).Times(0); + EXPECT_CALL(*async_client_, connected()).Times(0); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + logger_->log(std::make_unique(time_, std::move(data_))); +} + +TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfDisconnectedByRemote) { + init(); + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); + EXPECT_CALL(*async_client_, connect()).WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::RemoteClose); + return true; + })); + + logger_->log(std::make_unique(time_, std::move(data_))); +} + +TEST_F(FluentdAccessLoggerImplTest, NoWriteOnLogIfDisconnectedByLocal) { + init(); + EXPECT_CALL(*timer_, disableTimer()); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); + EXPECT_CALL(*async_client_, connect()).WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::LocalClose); + return true; + })); + + logger_->log(std::make_unique(time_, std::move(data_))); +} + +TEST_F(FluentdAccessLoggerImplTest, LogSingleEntry) { + init(); // Default buffer limit is 0 so single entry should be flushed immediately. + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)).WillOnce(Return(true)); + EXPECT_CALL(*async_client_, connect()).WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::Connected); + return true; + })); + EXPECT_CALL(*async_client_, write(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool end_stream) { + EXPECT_FALSE(end_stream); + std::string expected_payload = getExpectedMsgpackPayload(1); + EXPECT_EQ(expected_payload, buffer.toString()); + })); + + logger_->log(std::make_unique(time_, std::move(data_))); +} + +TEST_F(FluentdAccessLoggerImplTest, LogTwoEntries) { + init(12); // First entry is 10 bytes, so first entry should not cause the logger to flush. + + // First log should not be flushed. + EXPECT_CALL(*async_client_, connected()).Times(0); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + logger_->log(std::make_unique(time_, std::move(data_))); + + // Expect second entry to cause all entries to flush. + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)).WillOnce(Return(true)); + EXPECT_CALL(*async_client_, connect()).WillOnce(Invoke([this]() -> bool { + logger_->onEvent(Network::ConnectionEvent::Connected); + return true; + })); + EXPECT_CALL(*async_client_, write(_, _)) + .WillOnce(Invoke([&](Buffer::Instance& buffer, bool end_stream) { + EXPECT_FALSE(end_stream); + std::string expected_payload = getExpectedMsgpackPayload(2); + EXPECT_EQ(expected_payload, buffer.toString()); + })); + logger_->log(std::make_unique(time_, std::move(data_))); +} + +TEST_F(FluentdAccessLoggerImplTest, CallbacksTest) { + init(); + EXPECT_CALL(*async_client_, connect()).WillOnce(Return(true)); + EXPECT_CALL(*async_client_, connected()).WillOnce(Return(false)); + EXPECT_CALL(*async_client_, write(_, _)).Times(0); + logger_->log(std::make_unique(time_, std::move(data_))); + EXPECT_NO_THROW(logger_->onAboveWriteBufferHighWatermark()); + EXPECT_NO_THROW(logger_->onBelowWriteBufferLowWatermark()); + Buffer::OwnedImpl buffer; + EXPECT_NO_THROW(logger_->onData(buffer, false)); +} + +class FluentdAccessLoggerCacheImplTest : public testing::Test { +public: + FluentdAccessLoggerCacheImplTest() : logger_cache_(cluster_manager_, scope_, tls_) {} + + void init(bool second_logger = false) { + async_client1_ = new Tcp::AsyncClient::MockAsyncTcpClient(); + EXPECT_CALL(*async_client1_, setAsyncTcpClientCallbacks(_)); + + if (second_logger) { + async_client2_ = new Tcp::AsyncClient::MockAsyncTcpClient(); + EXPECT_CALL(*async_client2_, setAsyncTcpClientCallbacks(_)); + } + } + + std::string cluster_name_ = "test_cluster"; + NiceMock cluster_; + NiceMock cluster_manager_; + Tcp::AsyncClient::MockAsyncTcpClient* async_client1_; + Tcp::AsyncClient::MockAsyncTcpClient* async_client2_; + NiceMock store_; + Stats::Scope& scope_{*store_.rootScope()}; + NiceMock tls_; + FluentdAccessLoggerCacheImpl logger_cache_; +}; + +TEST_F(FluentdAccessLoggerCacheImplTest, CreateNonExistingLogger) { + init(); + EXPECT_CALL(cluster_manager_, getThreadLocalCluster(cluster_name_)).WillOnce(Return(&cluster_)); + EXPECT_CALL(cluster_, tcpAsyncClient(_, _)).WillOnce(Invoke([&] { + return Tcp::AsyncTcpClientPtr{async_client1_}; + })); + + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config; + config.set_cluster(cluster_name_); + config.set_tag("test.tag"); + config.mutable_buffer_size_bytes()->set_value(123); + auto logger = logger_cache_.getOrCreateLogger(std::make_shared(config)); + EXPECT_TRUE(logger != nullptr); +} + +TEST_F(FluentdAccessLoggerCacheImplTest, CreateTwoLoggersSameHash) { + init(); + EXPECT_CALL(cluster_manager_, getThreadLocalCluster(cluster_name_)).WillOnce(Return(&cluster_)); + EXPECT_CALL(cluster_, tcpAsyncClient(_, _)).WillRepeatedly(Invoke([&] { + return Tcp::AsyncTcpClientPtr{async_client1_}; + })); + + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config1; + config1.set_cluster(cluster_name_); + config1.set_tag("test.tag"); + config1.mutable_buffer_size_bytes()->set_value(123); + auto logger1 = logger_cache_.getOrCreateLogger(std::make_shared(config1)); + EXPECT_TRUE(logger1 != nullptr); + + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config2; + config2.set_cluster(cluster_name_); // config hash will be different than config1 + config2.set_tag("test.tag"); + config2.mutable_buffer_size_bytes()->set_value(123); + auto logger2 = logger_cache_.getOrCreateLogger(std::make_shared(config2)); + EXPECT_TRUE(logger2 != nullptr); + + // Make sure we got the same logger + EXPECT_EQ(logger1, logger2); +} + +TEST_F(FluentdAccessLoggerCacheImplTest, CreateTwoLoggersDifferentHash) { + init(true); + EXPECT_CALL(cluster_manager_, getThreadLocalCluster(_)) + .WillOnce(Return(&cluster_)) + .WillOnce(Return(&cluster_)); + + EXPECT_CALL(cluster_, tcpAsyncClient(_, _)) + .WillOnce(Invoke([&] { return Tcp::AsyncTcpClientPtr{async_client1_}; })) + .WillOnce(Invoke([&] { return Tcp::AsyncTcpClientPtr{async_client2_}; })); + + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config1; + config1.set_cluster(cluster_name_); + config1.set_tag("test.tag"); + config1.mutable_buffer_size_bytes()->set_value(123); + auto logger1 = logger_cache_.getOrCreateLogger(std::make_shared(config1)); + EXPECT_TRUE(logger1 != nullptr); + + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config2; + config2.set_cluster("different_cluster"); // config hash will be different than config1 + config2.set_tag("test.tag"); + config2.mutable_buffer_size_bytes()->set_value(123); + auto logger2 = logger_cache_.getOrCreateLogger(std::make_shared(config2)); + EXPECT_TRUE(logger2 != nullptr); + + // Make sure we got two different loggers + EXPECT_NE(logger1, logger2); +} + +class MockFluentdAccessLogger : public FluentdAccessLogger { +public: + MOCK_METHOD(void, log, (EntryPtr &&)); +}; + +class MockFluentdAccessLoggerCache : public FluentdAccessLoggerCache { +public: + MOCK_METHOD(FluentdAccessLoggerSharedPtr, getOrCreateLogger, + (const FluentdAccessLogConfigSharedPtr)); +}; + +class MockFluentdFormatter : public FluentdFormatter { +public: + MOCK_METHOD(std::vector, format, + (const Formatter::HttpFormatterContext& context, + const StreamInfo::StreamInfo& stream_info), + (const)); +}; + +using FilterPtr = Envoy::AccessLog::FilterPtr; + +class FluentdAccessLogTest : public testing::Test { +public: + FluentdAccessLogTest() { + ON_CALL(*filter_, evaluate(_, _)).WillByDefault(Return(true)); + EXPECT_CALL(*logger_cache_, getOrCreateLogger(_)).WillOnce(Return(logger_)); + } + + AccessLog::MockFilter* filter_{new NiceMock()}; + NiceMock tls_; + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig config_; + MockFluentdFormatter* formatter_{new NiceMock()}; + std::shared_ptr logger_{new MockFluentdAccessLogger()}; + std::shared_ptr logger_cache_{new MockFluentdAccessLoggerCache()}; +}; + +TEST_F(FluentdAccessLogTest, CreateAndLog) { + auto access_log = + FluentdAccessLog(AccessLog::FilterPtr{filter_}, FluentdFormatterPtr{formatter_}, + std::make_shared(config_), tls_, logger_cache_); + + MockTimeSystem time_system; + EXPECT_CALL(time_system, systemTime).WillOnce(Return(SystemTime(std::chrono::seconds(200)))); + NiceMock stream_info; + EXPECT_CALL(stream_info, timeSource()).WillOnce(ReturnRef(time_system)); + + EXPECT_CALL(*formatter_, format(_, _)).WillOnce(Return(std::vector{10, 20})); + EXPECT_CALL(*logger_, log(_)).WillOnce(Invoke([](EntryPtr&& entry) { + EXPECT_EQ(200, entry->time_); + ASSERT_EQ(2, entry->record_.size()); + EXPECT_EQ(uint8_t(10), entry->record_[0]); + EXPECT_EQ(uint8_t(20), entry->record_[1]); + })); + + access_log.log({}, stream_info); +} + +} // namespace +} // namespace Fluentd +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc b/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc new file mode 100644 index 000000000000..933ac3fe1369 --- /dev/null +++ b/test/extensions/access_loggers/fluentd/fluentd_access_log_integration_test.cc @@ -0,0 +1,214 @@ +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/extensions/access_loggers/fluentd/v3/fluentd.pb.h" +#include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" + +#include "source/common/network/utility.h" +#include "source/extensions/filters/network/common/factory_base.h" + +#include "test/integration/integration.h" +#include "test/integration/utility.h" +#include "test/test_common/registry.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" +#include "msgpack.hpp" + +using testing::AssertionResult; + +namespace Envoy { +namespace { + +constexpr char default_cluster_name[] = "fluentd_cluster"; +constexpr char default_tag[] = "fluentd_cluster"; +constexpr char default_stat_prefix[] = "fluentd_1"; + +class FluentdAccessLogIntegrationTest : public testing::Test, public BaseIntegrationTest { +public: + FluentdAccessLogIntegrationTest() + : BaseIntegrationTest(Network::Address::IpVersion::v4, ConfigHelper::tcpProxyConfig()) { + skip_tag_extraction_rule_check_ = true; + enableHalfClose(true); + } + + void init(const std::string cluster_name = default_cluster_name, + bool flush_access_log_on_connected = false, + absl::optional buffer_size_bytes = absl::nullopt) { + setUpstreamCount(2); + config_helper_.renameListener("tcp_proxy"); + config_helper_.addConfigModifier( + [&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* access_log_cluster = bootstrap.mutable_static_resources()->add_clusters(); + access_log_cluster->MergeFrom(bootstrap.static_resources().clusters()[0]); + access_log_cluster->set_name(default_cluster_name); + + 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_typed_config(); + + ASSERT_TRUE( + config_blob->Is()); + auto tcp_proxy_config = + MessageUtil::anyConvert( + *config_blob); + + tcp_proxy_config.mutable_access_log_options()->set_flush_access_log_on_connected( + flush_access_log_on_connected); + auto* access_log = tcp_proxy_config.add_access_log(); + access_log->set_name("access_log.fluentd"); + envoy::extensions::access_loggers::fluentd::v3::FluentdAccessLogConfig access_log_config; + access_log_config.set_cluster(cluster_name); + access_log_config.set_tag(default_tag); + access_log_config.set_stat_prefix(default_stat_prefix); + + if (buffer_size_bytes.has_value()) { + access_log_config.mutable_buffer_size_bytes()->set_value(buffer_size_bytes.value()); + } + + auto* record = access_log_config.mutable_record(); + (*record->mutable_fields())["Message"].set_string_value("SomeValue"); + (*record->mutable_fields())["LogType"].set_string_value("%ACCESS_LOG_TYPE%"); + + access_log->mutable_typed_config()->PackFrom(access_log_config); + config_blob->PackFrom(tcp_proxy_config); + }); + + BaseIntegrationTest::initialize(); + } + + // The Fluentd records are msgpack serialized, but for testing convenience, we expect + // the records as JSON strings and later converting while comparing the values. + void validateFluentdPayload(const std::string& tcp_data, bool* validated, + std::vector> expected_entries) { + msgpack::unpacker unpacker; + unpacker.reserve_buffer(tcp_data.size()); + std::memcpy(unpacker.buffer(), tcp_data.data(), tcp_data.size()); + unpacker.buffer_consumed(tcp_data.size()); + + size_t entry_index = 0; + msgpack::object_handle handle; + while (unpacker.next(handle)) { + auto& expected_records_as_json = expected_entries[entry_index++]; + + msgpack::object message = handle.get(); + ASSERT_EQ(msgpack::type::object_type::ARRAY, message.type); + ASSERT_EQ(msgpack::type::STR, message.via.array.ptr[0].type); + ASSERT_EQ(default_tag, message.via.array.ptr[0].as()); + ASSERT_EQ(msgpack::type::object_type::ARRAY, message.via.array.ptr[1].type); + + ASSERT_EQ(expected_records_as_json.size(), message.via.array.ptr[1].via.array.size); + for (size_t idx = 0; idx < expected_records_as_json.size(); idx++) { + auto& record = message.via.array.ptr[1].via.array.ptr[idx]; + ASSERT_EQ(msgpack::type::object_type::ARRAY, record.type); + ASSERT_EQ(msgpack::type::object_type::POSITIVE_INTEGER, record.via.array.ptr[0].type); + ASSERT_GT(record.via.array.ptr[0].as(), 0); + ASSERT_EQ(msgpack::type::object_type::MAP, record.via.array.ptr[1].type); + + std::stringstream stream; // msgpack will stream map type of fields as JSON string + stream << record.via.array.ptr[1]; + std::string record_as_json = stream.str(); + + ASSERT_TRUE(TestUtility::jsonStringEqual(expected_records_as_json[idx], record_as_json)) + << fmt::format("expected: {}, actual: {}", expected_records_as_json[idx], + record_as_json); + } + } + + if (expected_entries.size() == entry_index) { + *validated = true; + } + } + + void sendBidirectionalData() { + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy")); + ASSERT_TRUE(tcp_client->write("hello", true)); + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_tcp_connection_)); + ASSERT_TRUE(fake_tcp_connection_->waitForData(5)); + ASSERT_TRUE(fake_tcp_connection_->write("world", true)); + tcp_client->waitForData("world"); + ASSERT_TRUE(fake_tcp_connection_->waitForDisconnect()); + tcp_client->waitForDisconnect(); + } + + FakeRawConnectionPtr fake_tcp_connection_; + FakeRawConnectionPtr fake_access_log_connection_; +}; + +TEST_F(FluentdAccessLogIntegrationTest, UnknownCluster) { + EXPECT_DEATH(init("unknown_cluster"), ""); +} + +TEST_F(FluentdAccessLogIntegrationTest, SingleEntrySingleRecord) { + init(); + sendBidirectionalData(); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 1); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + + ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { + bool validated = false; + validateFluentdPayload(tcp_data, &validated, + {{"{\"Message\":\"SomeValue\",\"LogType\":\"TcpConnectionEnd\"}"}}); + return validated; + })); +} + +TEST_F(FluentdAccessLogIntegrationTest, SingleEntryTwoRecords) { + init(default_cluster_name, /*flush_access_log_on_connected = */ true); + sendBidirectionalData(); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 2); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + + ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { + bool validated = false; + validateFluentdPayload(tcp_data, &validated, + {{"{\"Message\":\"SomeValue\",\"LogType\":\"TcpUpstreamConnected\"}", + "{\"Message\":\"SomeValue\",\"LogType\":\"TcpConnectionEnd\"}"}}); + return validated; + })); +} + +TEST_F(FluentdAccessLogIntegrationTest, TwoEntries) { + init(default_cluster_name, /*flush_access_log_on_connected = */ true, /*buffer_size_bytes = */ 0); + sendBidirectionalData(); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 2); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 2); + + ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { + bool validated = false; + validateFluentdPayload(tcp_data, &validated, + {{"{\"Message\":\"SomeValue\",\"LogType\":\"TcpUpstreamConnected\"}"}, + {"{\"Message\":\"SomeValue\",\"LogType\":\"TcpConnectionEnd\"}"}}); + return validated; + })); +} + +TEST_F(FluentdAccessLogIntegrationTest, UpstreamConnectionClosed) { + init(); + sendBidirectionalData(); + + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_buffered", 1); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.events_sent", 1); + + ASSERT_TRUE(fake_upstreams_[1]->waitForRawConnection(fake_access_log_connection_)); + EXPECT_TRUE(fake_access_log_connection_->waitForData([&](const std::string& tcp_data) -> bool { + bool validated = false; + validateFluentdPayload(tcp_data, &validated, + {{"{\"Message\":\"SomeValue\",\"LogType\":\"TcpConnectionEnd\"}"}}); + return validated; + })); + + ASSERT_TRUE(fake_access_log_connection_->close()); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.connections_closed", 1); + + // New access log would be discarded because the connection is closed. + sendBidirectionalData(); + test_server_->waitForCounterEq("access_logs.fluentd.fluentd_1.entries_lost", 1); +} + +} // namespace +} // namespace Envoy diff --git a/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc b/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc new file mode 100644 index 000000000000..a98a0e2b58f6 --- /dev/null +++ b/test/extensions/access_loggers/fluentd/substitution_formatter_test.cc @@ -0,0 +1,46 @@ +#include "envoy/registry/registry.h" + +#include "source/common/formatter/substitution_format_string.h" +#include "source/common/json/json_loader.h" +#include "source/extensions/access_loggers/fluentd/substitution_formatter.h" + +#include "test/mocks/access_log/mocks.h" +#include "test/mocks/server/factory_context.h" +#include "test/mocks/stream_info/mocks.h" +#include "test/test_common/utility.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::Return; + +namespace Envoy { +namespace Extensions { +namespace AccessLoggers { +namespace Fluentd { +namespace { + +TEST(FluentdFormatterImplTest, FormatMsgpack) { + ProtobufWkt::Struct log_struct; + (*log_struct.mutable_fields())["Message"].set_string_value("SomeValue"); + (*log_struct.mutable_fields())["LogType"].set_string_value("%ACCESS_LOG_TYPE%"); + + auto json_formatter = + Formatter::SubstitutionFormatStringUtils::createJsonFormatter(log_struct, true, false, true); + + auto fluentd_formatter = FluentdFormatterImpl(std::move(json_formatter)); + auto expected_json = "{\"Message\":\"SomeValue\",\"LogType\":\"NotSet\"}"; + auto expected_msgpack = Json::Factory::jsonToMsgpack(expected_json); + + NiceMock stream_info; + std::vector msgpack = fluentd_formatter.format({}, stream_info); + std::string expected_msgpack_str(expected_msgpack.begin(), expected_msgpack.end()); + std::string msgpack_str(msgpack.begin(), msgpack.end()); + EXPECT_EQ(expected_msgpack_str, msgpack_str); +} + +} // namespace +} // namespace Fluentd +} // namespace AccessLoggers +} // namespace Extensions +} // namespace Envoy diff --git a/test/mocks/tcp/mocks.h b/test/mocks/tcp/mocks.h index 829e39152301..24e0ff38339f 100644 --- a/test/mocks/tcp/mocks.h +++ b/test/mocks/tcp/mocks.h @@ -99,6 +99,22 @@ class MockAsyncTcpClientCallbacks : public AsyncTcpClientCallbacks { MOCK_METHOD(void, onBelowWriteBufferLowWatermark, ()); }; +class MockAsyncTcpClient : public AsyncTcpClient { +public: + MockAsyncTcpClient() = default; + ~MockAsyncTcpClient() override = default; + + MOCK_METHOD(bool, connect, ()); + MOCK_METHOD(void, close, (Network::ConnectionCloseType type)); + MOCK_METHOD(Network::DetectedCloseType, detectedCloseType, (), (const)); + MOCK_METHOD(void, write, (Buffer::Instance & data, bool end_stream)); + MOCK_METHOD(void, readDisable, (bool disable)); + MOCK_METHOD(void, setAsyncTcpClientCallbacks, (AsyncTcpClientCallbacks & callbacks)); + MOCK_METHOD(Event::Dispatcher&, dispatcher, ()); + MOCK_METHOD(bool, connected, ()); + MOCK_METHOD(OptRef, getStreamInfo, ()); +}; + } // namespace AsyncClient } // namespace Tcp } // namespace Envoy diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index c0016586ceeb..37e1296ac3d3 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -769,6 +769,8 @@ fixdate fixup flatbuffer flatc +fluentd +Fluentd fmt fmtlib fn @@ -965,6 +967,7 @@ moveable msec msg msghdr +msgpack multi multicast multikill From f29c560425aec5ad50a8c5a24d94ec78a9c0875c Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Wed, 21 Feb 2024 09:51:58 -0500 Subject: [PATCH 061/151] Sockets: allowing for graceful client socket creation errors (#32370) Allowing for graceful failure if an upstream socket is being established. This should be a no-op for listening sockets (which still crash rather than rejecting config) For Envoy mobile (or badly configured ports?) upstream socket failure can result in permission errors. Instead of crashing the process with various release asserts/segfaults, attempt to gracefully fail the request with ENVOY_BUGs along the way. this is off by default. Risk Level: high, but the highest part is flag guarded and default-off Testing: added client socket fail integration test. Docs Changes: n/a Release Notes: n/a Optional Runtime guard: envoy.restart_features.allow_client_socket_creation_failure Signed-off-by: Alyssa Wilk --- source/common/network/connection_impl.cc | 28 +++++++---- source/common/network/socket_impl.cc | 6 +++ source/common/network/socket_impl.h | 4 +- .../common/network/socket_interface_impl.cc | 26 +++++++++-- .../quic/envoy_quic_client_connection.cc | 4 +- source/common/quic/envoy_quic_utils.cc | 4 ++ source/common/runtime/runtime_features.cc | 2 + .../upstreams/http/udp/upstream_request.h | 11 +++-- test/common/network/connection_impl_test.cc | 4 +- test/integration/protocol_integration_test.cc | 46 +++++++++++++++++++ 10 files changed, 112 insertions(+), 23 deletions(-) diff --git a/source/common/network/connection_impl.cc b/source/common/network/connection_impl.cc index 0f23f22da1ca..acce9f66e086 100644 --- a/source/common/network/connection_impl.cc +++ b/source/common/network/connection_impl.cc @@ -88,7 +88,10 @@ ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPt // Keep it as a bool flag to reduce the times calling runtime method.. enable_rst_detect_send_ = Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.detect_and_raise_rst_tcp_connection"); - + if (!socket_->isOpen()) { + IS_ENVOY_BUG("Client socket failure"); + return; + } if (!connected) { connecting_ = true; } @@ -110,7 +113,7 @@ ConnectionImpl::ConnectionImpl(Event::Dispatcher& dispatcher, ConnectionSocketPt } ConnectionImpl::~ConnectionImpl() { - ASSERT(!ioHandle().isOpen() && delayed_close_timer_ == nullptr, + ASSERT(!socket_->isOpen() && delayed_close_timer_ == nullptr, "ConnectionImpl was unexpectedly torn down without being closed."); // In general we assume that owning code has called close() previously to the destructor being @@ -137,7 +140,7 @@ void ConnectionImpl::removeReadFilter(ReadFilterSharedPtr filter) { bool ConnectionImpl::initializeReadFilters() { return filter_manager_.initializeReadFilters(); } void ConnectionImpl::close(ConnectionCloseType type) { - if (!ioHandle().isOpen()) { + if (!socket_->isOpen()) { return; } @@ -230,7 +233,7 @@ void ConnectionImpl::close(ConnectionCloseType type) { } Connection::State ConnectionImpl::state() const { - if (!ioHandle().isOpen()) { + if (!socket_->isOpen()) { return State::Closed; } else if (inDelayedClose()) { return State::Closing; @@ -265,7 +268,7 @@ void ConnectionImpl::setDetectedCloseType(DetectedCloseType close_type) { } void ConnectionImpl::closeSocket(ConnectionEvent close_type) { - if (!ConnectionImpl::ioHandle().isOpen()) { + if (!socket_->isOpen()) { return; } @@ -322,7 +325,7 @@ void ConnectionImpl::noDelay(bool enable) { // invalid. For this call instead of plumbing through logic that will immediately indicate that a // connect failed, we will just ignore the noDelay() call if the socket is invalid since error is // going to be raised shortly anyway and it makes the calling code simpler. - if (!ioHandle().isOpen()) { + if (!socket_->isOpen()) { return; } @@ -360,7 +363,7 @@ void ConnectionImpl::onRead(uint64_t read_buffer_size) { if (inDelayedClose() || !filterChainWantsData()) { return; } - ASSERT(ioHandle().isOpen()); + ASSERT(socket_->isOpen()); if (read_buffer_size == 0 && !read_end_stream_) { return; @@ -646,7 +649,7 @@ void ConnectionImpl::onFileEvent(uint32_t events) { // It's possible for a write event callback to close the socket (which will cause fd_ to be -1). // In this case ignore read event processing. - if (ioHandle().isOpen() && (events & Event::FileReadyType::Read)) { + if (socket_->isOpen() && (events & Event::FileReadyType::Read)) { onReadReady(); } } @@ -815,7 +818,7 @@ void ConnectionImpl::onWriteReady() { } // If a callback closes the socket, stop iterating. - if (!ioHandle().isOpen()) { + if (!socket_->isOpen()) { return; } } @@ -956,6 +959,13 @@ ClientConnectionImpl::ClientConnectionImpl( : ConnectionImpl(dispatcher, std::move(socket), std::move(transport_socket), stream_info_, false), stream_info_(dispatcher_.timeSource(), socket_->connectionInfoProviderSharedPtr()) { + if (!socket_->isOpen()) { + setFailureReason("socket creation failure"); + // Set up the dispatcher to "close" the connection on the next loop after + // the owner has a chance to add callbacks. + dispatcher_.post([this]() { raiseEvent(ConnectionEvent::LocalClose); }); + return; + } stream_info_.setUpstreamInfo(std::make_shared()); diff --git a/source/common/network/socket_impl.cc b/source/common/network/socket_impl.cc index 8b0c40a88ff7..4c1ede5de5e7 100644 --- a/source/common/network/socket_impl.cc +++ b/source/common/network/socket_impl.cc @@ -36,6 +36,12 @@ SocketImpl::SocketImpl(IoHandlePtr&& io_handle, return; } + if (!io_handle_) { + // This can happen iff system socket creation fails. + ENVOY_LOG_MISC(warn, "Created socket with null io handle"); + return; + } + // Should not happen but some tests inject -1 fds if (!io_handle_->isOpen()) { return; diff --git a/source/common/network/socket_impl.h b/source/common/network/socket_impl.h index 2becca95532d..1121cebb1fe2 100644 --- a/source/common/network/socket_impl.h +++ b/source/common/network/socket_impl.h @@ -126,11 +126,11 @@ class SocketImpl : public virtual Socket { IoHandle& ioHandle() override { return *io_handle_; } const IoHandle& ioHandle() const override { return *io_handle_; } void close() override { - if (io_handle_->isOpen()) { + if (io_handle_ && io_handle_->isOpen()) { io_handle_->close(); } } - bool isOpen() const override { return io_handle_->isOpen(); } + bool isOpen() const override { return io_handle_ && io_handle_->isOpen(); } void ensureOptions() { if (!options_) { options_ = std::make_shared>(); diff --git a/source/common/network/socket_interface_impl.cc b/source/common/network/socket_interface_impl.cc index 31440cd2b7df..aead17ecc3ab 100644 --- a/source/common/network/socket_interface_impl.cc +++ b/source/common/network/socket_interface_impl.cc @@ -68,15 +68,31 @@ IoHandlePtr SocketInterfaceImpl::socket(Socket::Type socket_type, Address::Type const Api::SysCallSocketResult result = Api::OsSysCallsSingleton::get().socket(domain, flags, protocol); - RELEASE_ASSERT(SOCKET_VALID(result.return_value_), - fmt::format("socket(2) failed, got error: {}", errorDetails(result.errno_))); + if (!SOCKET_VALID(result.return_value_)) { + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.allow_client_socket_creation_failure")) { + IS_ENVOY_BUG(fmt::format("socket(2) failed, got error: {}", errorDetails(result.errno_))); + return nullptr; + } else { + RELEASE_ASSERT(!SOCKET_VALID(result.return_value_), + fmt::format("socket(2) failed, got error: {}", errorDetails(result.errno_))); + } + } IoHandlePtr io_handle = makeSocket(result.return_value_, socket_v6only, domain); #if defined(__APPLE__) || defined(WIN32) // Cannot set SOCK_NONBLOCK as a ::socket flag. const int rc = io_handle->setBlocking(false).return_value_; - RELEASE_ASSERT(!SOCKET_FAILURE(rc), - fmt::format("Unable to set socket non-blocking: got error: {}", rc)); + if (SOCKET_FAILURE(result.return_value_)) { + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.allow_client_socket_creation_failure")) { + IS_ENVOY_BUG(fmt::format("Unable to set socket non-blocking: got error: {}", rc)); + return nullptr; + } else { + RELEASE_ASSERT(SOCKET_FAILURE(rc), + fmt::format("Unable to set socket non-blocking: got error: {}", rc)); + } + } #endif return io_handle; @@ -93,7 +109,7 @@ IoHandlePtr SocketInterfaceImpl::socket(Socket::Type socket_type, IoHandlePtr io_handle = SocketInterfaceImpl::socket(socket_type, addr->type(), ip_version, v6only, options); - if (addr->type() == Address::Type::Ip && ip_version == Address::IpVersion::v6 && + if (io_handle && addr->type() == Address::Type::Ip && ip_version == Address::IpVersion::v6 && !Address::forceV6()) { // Setting IPV6_V6ONLY restricts the IPv6 socket to IPv6 connections only. const Api::SysCallIntResult result = io_handle->setOption( diff --git a/source/common/quic/envoy_quic_client_connection.cc b/source/common/quic/envoy_quic_client_connection.cc index 0616b93808a4..9890990091ca 100644 --- a/source/common/quic/envoy_quic_client_connection.cc +++ b/source/common/quic/envoy_quic_client_connection.cc @@ -87,7 +87,7 @@ uint64_t EnvoyQuicClientConnection::maxDatagramSize() const { void EnvoyQuicClientConnection::setUpConnectionSocket(Network::ConnectionSocket& connection_socket, OptRef delegate) { delegate_ = delegate; - if (connection_socket.ioHandle().isOpen()) { + if (connection_socket.isOpen()) { connection_socket.ioHandle().initializeFileEvent( dispatcher_, [this, &connection_socket](uint32_t events) -> void { @@ -102,7 +102,7 @@ void EnvoyQuicClientConnection::setUpConnectionSocket(Network::ConnectionSocket& connection_socket.close(); } } - if (!connection_socket.ioHandle().isOpen()) { + if (!connection_socket.isOpen()) { CloseConnection(quic::QUIC_CONNECTION_CANCELLED, "Fail to set up connection socket.", quic::ConnectionCloseBehavior::SILENT_CLOSE); } diff --git a/source/common/quic/envoy_quic_utils.cc b/source/common/quic/envoy_quic_utils.cc index 0a90b562b04a..1d4b0bc68aa6 100644 --- a/source/common/quic/envoy_quic_utils.cc +++ b/source/common/quic/envoy_quic_utils.cc @@ -143,6 +143,10 @@ createConnectionSocket(const Network::Address::InstanceConstSharedPtr& peer_addr } auto connection_socket = std::make_unique( Network::Socket::Type::Datagram, local_addr, peer_addr, Network::SocketCreationOptions{}); + if (!connection_socket->isOpen()) { + ENVOY_LOG_MISC(error, "Failed to create socket"); + return connection_socket; + } connection_socket->addOptions(Network::SocketOptionFactory::buildIpPacketInfoOptions()); connection_socket->addOptions(Network::SocketOptionFactory::buildRxQueueOverFlowOptions()); if (options != nullptr) { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 553528718a5a..310a72a98352 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -106,6 +106,8 @@ RUNTIME_GUARD(envoy_restart_features_udp_read_normalize_addresses); // Begin false flags. Most of them should come with a TODO to flip true. +// TODO(alyssawilk) flip true after server side is handled. +FALSE_RUNTIME_GUARD(envoy_restart_features_allow_client_socket_creation_failure); // Execution context is optional and must be enabled explicitly. // See https://github.com/envoyproxy/envoy/issues/32012. FALSE_RUNTIME_GUARD(envoy_restart_features_enable_execution_context); diff --git a/source/extensions/upstreams/http/udp/upstream_request.h b/source/extensions/upstreams/http/udp/upstream_request.h index eed57dbb6eec..4d4a132411e1 100644 --- a/source/extensions/upstreams/http/udp/upstream_request.h +++ b/source/extensions/upstreams/http/udp/upstream_request.h @@ -44,9 +44,14 @@ class UdpConnPool : public Router::GenericConnPool { Upstream::HostDescriptionConstSharedPtr host() const override { return host_; } Network::SocketPtr createSocket(const Upstream::HostConstSharedPtr& host) { - return std::make_unique(Network::Socket::Type::Datagram, host->address(), - /*remote_address=*/nullptr, - Network::SocketCreationOptions{}); + auto ret = std::make_unique( + Network::Socket::Type::Datagram, host->address(), + /*remote_address=*/nullptr, Network::SocketCreationOptions{}); + if (Runtime::runtimeFeatureEnabled( + "envoy.restart_features.allow_client_socket_creation_failure")) { + RELEASE_ASSERT(ret->isOpen(), "Socket creation fail"); + } + return ret; } bool valid() const override { return host_ != nullptr; } diff --git a/test/common/network/connection_impl_test.cc b/test/common/network/connection_impl_test.cc index ee72edec49b4..99661b7e46ca 100644 --- a/test/common/network/connection_impl_test.cc +++ b/test/common/network/connection_impl_test.cc @@ -136,11 +136,11 @@ TEST_P(ConnectionImplDeathTest, BadFd) { Event::DispatcherPtr dispatcher(api->allocateDispatcher("test_thread")); IoHandlePtr io_handle = std::make_unique(); StreamInfo::StreamInfoImpl stream_info(dispatcher->timeSource(), nullptr); - EXPECT_DEATH( + EXPECT_ENVOY_BUG( ConnectionImpl(*dispatcher, std::make_unique(std::move(io_handle), nullptr, nullptr), Network::Test::createRawBufferSocket(), stream_info, false), - ".*assert failure: SOCKET_VALID\\(fd\\)"); + "Client socket failure"); } class TestClientConnectionImpl : public Network::ClientConnectionImpl { diff --git a/test/integration/protocol_integration_test.cc b/test/integration/protocol_integration_test.cc index f4e305689202..e13e5a493b72 100644 --- a/test/integration/protocol_integration_test.cc +++ b/test/integration/protocol_integration_test.cc @@ -4451,6 +4451,52 @@ TEST_P(ProtocolIntegrationTest, HandleUpstreamSocketFail) { cleanupUpstreamAndDownstream(); } +// TODO(alyssawilk) fix windows build before flipping flag. +#ifndef WIN32 +// A singleton which will fail creation of the Nth socket +class AllowForceFail : public Api::OsSysCallsImpl { +public: + void startFailing() { + absl::MutexLock m(&mutex_); + fail_ = true; + } + Api::SysCallSocketResult socket(int domain, int type, int protocol) override { + absl::MutexLock m(&mutex_); + if (fail_) { + return {-1, 1}; + } + return Api::OsSysCallsImpl::socket(domain, type, protocol); + } + +private: + absl::Mutex mutex_; + bool fail_ = false; +}; + +TEST_P(ProtocolIntegrationTest, HandleUpstreamSocketCreationFail) { + config_helper_.addRuntimeOverride("envoy.restart_features.allow_client_socket_creation_failure", + "true"); + AllowForceFail fail_socket_n_; + TestThreadsafeSingletonInjector os_calls{&fail_socket_n_}; + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + + EXPECT_ENVOY_BUG( + { + fail_socket_n_.startFailing(); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ("503", response->headers().getStatusValue()); + }, + ""); + + test_server_.reset(); + cleanupUpstreamAndDownstream(); + fake_upstreams_.clear(); +} +#endif + TEST_P(ProtocolIntegrationTest, NoLocalInterfaceNameForUpstreamConnection) { config_helper_.prependFilter(R"EOF( name: stream-info-to-headers-filter From 2aa97379ddb920a3ff7709c514bbdf2fca225428 Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Wed, 21 Feb 2024 10:31:40 -0500 Subject: [PATCH 062/151] tap: rejecting invalid config that requires an admin (#32384) Signed-off-by: Adi Suissa-Peleg --- .../extensions/common/tap/tap_config_base.cc | 10 +++- test/extensions/common/tap/admin_test.cc | 58 ++++++++++++++++++- .../fuzz/filter_corpus/tap_filter_admin | 7 +++ 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 test/extensions/filters/http/common/fuzz/filter_corpus/tap_filter_admin diff --git a/source/extensions/common/tap/tap_config_base.cc b/source/extensions/common/tap/tap_config_base.cc index 953d96f1c669..920505782344 100644 --- a/source/extensions/common/tap/tap_config_base.cc +++ b/source/extensions/common/tap/tap_config_base.cc @@ -64,7 +64,10 @@ TapConfigBaseImpl::TapConfigBaseImpl(const envoy::config::tap::v3::TapConfig& pr switch (sink_type_) { case ProtoOutputSink::OutputSinkTypeCase::kBufferedAdmin: - ASSERT(admin_streamer != nullptr, "admin output must be configured via admin"); + if (admin_streamer == nullptr) { + throw EnvoyException(fmt::format("Output sink type BufferedAdmin requires that the admin " + "output will be configured via admin")); + } // TODO(mattklein123): Graceful failure, error message, and test if someone specifies an // admin stream output with the wrong format. RELEASE_ASSERT( @@ -75,7 +78,10 @@ TapConfigBaseImpl::TapConfigBaseImpl(const envoy::config::tap::v3::TapConfig& pr sink_to_use_ = admin_streamer; break; case ProtoOutputSink::OutputSinkTypeCase::kStreamingAdmin: - ASSERT(admin_streamer != nullptr, "admin output must be configured via admin"); + if (admin_streamer == nullptr) { + throw EnvoyException(fmt::format("Output sink type StreamingAdmin requires that the admin " + "output will be configured via admin")); + } // TODO(mattklein123): Graceful failure, error message, and test if someone specifies an // admin stream output with the wrong format. // TODO(davidpeet8): Simple change to enable PROTO_BINARY_LENGTH_DELIMITED format - diff --git a/test/extensions/common/tap/admin_test.cc b/test/extensions/common/tap/admin_test.cc index df3aa2783c15..8978529f5606 100644 --- a/test/extensions/common/tap/admin_test.cc +++ b/test/extensions/common/tap/admin_test.cc @@ -170,7 +170,6 @@ class TestConfigImpl : public TapConfigBaseImpl { }; TEST(TypedExtensionConfigTest, AddTestConfigHttpContext) { - const std::string tap_config_yaml = R"EOF( match: @@ -204,7 +203,6 @@ TEST(TypedExtensionConfigTest, AddTestConfigHttpContext) { } TEST(TypedExtensionConfigTest, AddTestConfigTransportSocketContext) { - const std::string tap_config_yaml = R"EOF( match: @@ -237,6 +235,62 @@ TEST(TypedExtensionConfigTest, AddTestConfigTransportSocketContext) { TestConfigImpl(tap_config, nullptr, factory_context); } +// Validates that a BufferedAdmin tap config that is passed without an admin +// streamer is rejected. +TEST(TypedExtensionConfigTest, BufferedAdminNoAdminStreamerRejected) { + const std::string tap_config_yaml = + R"EOF( + match: + any_match: true + output_config: + sinks: + - buffered_admin: {} +)EOF"; + envoy::config::tap::v3::TapConfig tap_config; + TestUtility::loadFromYaml(tap_config_yaml, tap_config); + + MockTapSinkFactory factory_impl; + EXPECT_CALL(factory_impl, name).Times(AtLeast(1)); + EXPECT_CALL(factory_impl, createEmptyConfigProto) + .WillRepeatedly(Invoke([]() -> ProtobufTypes::MessagePtr { + return std::make_unique(); + })); + Registry::InjectFactory factory(factory_impl); + + NiceMock factory_context; + EXPECT_THROW_WITH_MESSAGE( + TestConfigImpl(tap_config, nullptr, factory_context), EnvoyException, + "Output sink type BufferedAdmin requires that the admin output will be configured via admin"); +} + +// Validates that a StreamingAdmin tap config that is passed without an admin +// streamer is rejected. +TEST(TypedExtensionConfigTest, StreamingAdminNoAdminStreamerRejected) { + const std::string tap_config_yaml = + R"EOF( + match: + any_match: true + output_config: + sinks: + - streaming_admin: {} +)EOF"; + envoy::config::tap::v3::TapConfig tap_config; + TestUtility::loadFromYaml(tap_config_yaml, tap_config); + + MockTapSinkFactory factory_impl; + EXPECT_CALL(factory_impl, name).Times(AtLeast(1)); + EXPECT_CALL(factory_impl, createEmptyConfigProto) + .WillRepeatedly(Invoke([]() -> ProtobufTypes::MessagePtr { + return std::make_unique(); + })); + Registry::InjectFactory factory(factory_impl); + + NiceMock factory_context; + EXPECT_THROW_WITH_MESSAGE(TestConfigImpl(tap_config, nullptr, factory_context), EnvoyException, + "Output sink type StreamingAdmin requires that the admin output will " + "be configured via admin"); +} + // Make sure warn if using a pipe address for the admin handler. TEST_F(AdminHandlerTest, AdminWithPipeSocket) { EXPECT_LOG_CONTAINS( diff --git a/test/extensions/filters/http/common/fuzz/filter_corpus/tap_filter_admin b/test/extensions/filters/http/common/fuzz/filter_corpus/tap_filter_admin new file mode 100644 index 000000000000..983d9999d725 --- /dev/null +++ b/test/extensions/filters/http/common/fuzz/filter_corpus/tap_filter_admin @@ -0,0 +1,7 @@ +config { + name: "envoy.filters.http.match_delegate" + typed_config { + type_url: "type.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher" + value: "\022\213\002\n\001:\022\205\002\nLtype.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher\022\264\001\022\261\001\n\001:\022\253\001\nLtype.googleapis.com/envoy.extensions.common.matching.v3.ExtensionWithMatcher\022[\022Y\n\001:\022T\n Date: Wed, 21 Feb 2024 09:59:41 -0800 Subject: [PATCH 063/151] google_grpc: add a runtime flag to disable TLSv1.3 (#32315) Change-Id: Id88723a81d4b1586bf12be6f4dc7a81ae7b0d9c4 Commit Message: Adds a temporary runtime flag to disable TLSv1.3 by gRPC SDK until a proper xDS extension can be added. Additional Description: Risk Level: low, default false Testing: regression Signed-off-by: Kuat Yessenov --- source/common/grpc/BUILD | 1 + source/common/grpc/google_grpc_creds_impl.cc | 44 ++++++++++++++----- source/common/runtime/runtime_features.cc | 4 ++ test/common/grpc/BUILD | 1 + .../grpc/grpc_client_integration_test.cc | 24 ++++++++++ .../grpc_client_integration_test_harness.h | 16 ++++++- 6 files changed, 79 insertions(+), 11 deletions(-) diff --git a/source/common/grpc/BUILD b/source/common/grpc/BUILD index 9ab2790a7c1c..6fa6e2a21c8d 100644 --- a/source/common/grpc/BUILD +++ b/source/common/grpc/BUILD @@ -212,6 +212,7 @@ envoy_cc_library( "//envoy/grpc:google_grpc_creds_interface", "//envoy/registry", "//source/common/config:datasource_lib", + "//source/common/runtime:runtime_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", ], alwayslink = LEGACY_ALWAYSLINK, diff --git a/source/common/grpc/google_grpc_creds_impl.cc b/source/common/grpc/google_grpc_creds_impl.cc index d185db79d16d..e70617355a76 100644 --- a/source/common/grpc/google_grpc_creds_impl.cc +++ b/source/common/grpc/google_grpc_creds_impl.cc @@ -4,6 +4,9 @@ #include "envoy/grpc/google_grpc_creds.h" #include "source/common/config/datasource.h" +#include "source/common/runtime/runtime_features.h" + +#include "grpcpp/security/tls_certificate_provider.h" namespace Envoy { namespace Grpc { @@ -15,15 +18,32 @@ std::shared_ptr CredsUtility::getChannelCredentials( case envoy::config::core::v3::GrpcService::GoogleGrpc::ChannelCredentials:: CredentialSpecifierCase::kSslCredentials: { const auto& ssl_credentials = google_grpc.channel_credentials().ssl_credentials(); - const grpc::SslCredentialsOptions ssl_credentials_options = { - THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.root_certs(), true, api), - std::string), - THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.private_key(), true, api), - std::string), - THROW_OR_RETURN_VALUE(Config::DataSource::read(ssl_credentials.cert_chain(), true, api), - std::string), - }; - return grpc::SslCredentials(ssl_credentials_options); + const auto root_certs = THROW_OR_RETURN_VALUE( + Config::DataSource::read(ssl_credentials.root_certs(), true, api), std::string); + const auto private_key = THROW_OR_RETURN_VALUE( + Config::DataSource::read(ssl_credentials.private_key(), true, api), std::string); + const auto cert_chain = THROW_OR_RETURN_VALUE( + Config::DataSource::read(ssl_credentials.cert_chain(), true, api), std::string); + grpc::experimental::TlsChannelCredentialsOptions options; + if (!private_key.empty() || !cert_chain.empty()) { + options.set_certificate_provider( + std::make_shared( + root_certs, + std::vector{{private_key, cert_chain}})); + } else if (!root_certs.empty()) { + options.set_certificate_provider( + std::make_shared(root_certs)); + } + if (!root_certs.empty()) { + options.watch_root_certs(); + } + if (!private_key.empty() || !cert_chain.empty()) { + options.watch_identity_key_cert_pairs(); + } + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.google_grpc_disable_tls_13")) { + options.set_max_tls_version(grpc_tls_version::TLS1_2); + } + return grpc::experimental::TlsCredentials(options); } case envoy::config::core::v3::GrpcService::GoogleGrpc::ChannelCredentials:: CredentialSpecifierCase::kLocalCredentials: { @@ -46,7 +66,11 @@ std::shared_ptr CredsUtility::defaultSslChannelCredent if (creds != nullptr) { return creds; } - return grpc::SslCredentials({}); + grpc::experimental::TlsChannelCredentialsOptions options; + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.google_grpc_disable_tls_13")) { + options.set_max_tls_version(grpc_tls_version::TLS1_2); + } + return grpc::experimental::TlsCredentials(options); } std::vector> diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 310a72a98352..8af9ef1034b5 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -147,6 +147,10 @@ FALSE_RUNTIME_GUARD(envoy_restart_features_use_fast_protobuf_hash); // TODO(panting): flip this to true after some test time. FALSE_RUNTIME_GUARD(envoy_reloadable_features_use_config_in_happy_eyeballs); +// A flag to set the maximum TLS version for google_grpc client to TLS1.2, when needed for +// compliance restrictions. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_google_grpc_disable_tls_13); + // Block of non-boolean flags. Use of int flags is deprecated. Do not add more. ABSL_FLAG(uint64_t, re2_max_program_size_error_level, 100, ""); // NOLINT ABSL_FLAG(uint64_t, re2_max_program_size_warn_level, // NOLINT diff --git a/test/common/grpc/BUILD b/test/common/grpc/BUILD index a40a85d8fb8b..cbf20b2ab3c4 100644 --- a/test/common/grpc/BUILD +++ b/test/common/grpc/BUILD @@ -179,6 +179,7 @@ envoy_cc_test( ":grpc_client_integration_test_harness_lib", "//source/common/grpc:async_client_lib", "//source/extensions/grpc_credentials/example:config", + "//test/test_common:test_runtime_lib", ] + envoy_select_google_grpc(["//source/common/grpc:google_async_client_lib"]), ) diff --git a/test/common/grpc/grpc_client_integration_test.cc b/test/common/grpc/grpc_client_integration_test.cc index 8cf420e1e1a1..f3fe4bea09ac 100644 --- a/test/common/grpc/grpc_client_integration_test.cc +++ b/test/common/grpc/grpc_client_integration_test.cc @@ -5,6 +5,7 @@ #endif +#include "test/test_common/test_runtime.h" #include "test/common/grpc/grpc_client_integration_test_harness.h" using testing::Eq; @@ -499,6 +500,29 @@ TEST_P(GrpcSslClientIntegrationTest, BasicSslRequestWithClientCert) { dispatcher_helper_.runDispatcher(); } +// Validate TLS version mismatch between the client and the server. +TEST_P(GrpcSslClientIntegrationTest, BasicSslRequestHandshakeFailure) { + SKIP_IF_GRPC_CLIENT(ClientType::EnvoyGrpc); + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.google_grpc_disable_tls_13", "true"}}); + use_server_tls_13_ = true; + initialize(); + auto request = createRequest(empty_metadata_, false); + EXPECT_CALL(*request->child_span_, setTag(Eq(Tracing::Tags::get().GrpcStatusCode), Eq("13"))); + EXPECT_CALL(*request->child_span_, + setTag(Eq(Tracing::Tags::get().Error), Eq(Tracing::Tags::get().True))); + EXPECT_CALL(*request, onFailure(Status::Internal, "", _)).WillOnce(InvokeWithoutArgs([this]() { + dispatcher_helper_.dispatcher_.exit(); + })); + EXPECT_CALL(*request->child_span_, finishSpan()); + FakeRawConnectionPtr fake_connection; + ASSERT_TRUE(fake_upstream_->waitForRawConnection(fake_connection)); + if (fake_connection->connected()) { + ASSERT_TRUE(fake_connection->waitForDisconnect()); + } + dispatcher_helper_.dispatcher_.run(Event::Dispatcher::RunType::Block); +} + #ifdef ENVOY_GOOGLE_GRPC // AccessToken credential validation tests. class GrpcAccessTokenClientIntegrationTest : public GrpcSslClientIntegrationTest { diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index 8b5a9c444227..799bd4f8da4b 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -394,7 +394,8 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { virtual void expectExtraHeaders(FakeStream&) {} - HelloworldRequestPtr createRequest(const TestMetadata& initial_metadata) { + HelloworldRequestPtr createRequest(const TestMetadata& initial_metadata, + bool expect_upstream_request = true) { auto request = std::make_unique(dispatcher_helper_); EXPECT_CALL(*request, onCreateInitialMetadata(_)) .WillOnce(Invoke([&initial_metadata](Http::HeaderMap& headers) { @@ -421,6 +422,10 @@ class GrpcClientIntegrationTest : public GrpcClientIntegrationParamTest { active_span, Http::AsyncClient::RequestOptions()); EXPECT_NE(request->grpc_request_, nullptr); + if (!expect_upstream_request) { + return request; + } + if (!fake_connection_) { AssertionResult result = fake_upstream_->waitForHttpConnection(*dispatcher_, fake_connection_); @@ -556,6 +561,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { tls_cert->mutable_private_key()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/clientkey.pem")); } + auto cfg = std::make_unique( tls_context, factory_context_); @@ -587,6 +593,13 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { validation_context->mutable_trusted_ca()->set_filename( TestEnvironment::runfilesPath("test/config/integration/certs/cacert.pem")); } + if (use_server_tls_13_) { + auto* tls_params = common_tls_context->mutable_tls_params(); + tls_params->set_tls_minimum_protocol_version( + envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3); + tls_params->set_tls_maximum_protocol_version( + envoy::extensions::transport_sockets::tls::v3::TlsParameters::TLSv1_3); + } auto cfg = std::make_unique( tls_context, factory_context_); @@ -598,6 +611,7 @@ class GrpcSslClientIntegrationTest : public GrpcClientIntegrationTest { } bool use_client_cert_{}; + bool use_server_tls_13_{false}; testing::NiceMock factory_context_; }; From 557be579cc4b237e8a42ba7fed79d4b2025fc995 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Wed, 21 Feb 2024 13:37:12 -0500 Subject: [PATCH 064/151] mobile: Set the allow_client_socket_creation_failure runtime guard (#32501) https://github.com/envoyproxy/envoy/pull/32370 introduced the allow_client_socket_creation_failure runtime guard, but had to default it to off due to test failures. In Envoy Mobile, we are turning it on as it's been the source of assert failures. Signed-off-by: Ali Beyad --- mobile/library/cc/engine_builder.cc | 14 +++++++++++--- mobile/test/cc/unit/envoy_config_test.cc | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index 96bf8b0a0b33..cd93efb440f4 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -845,12 +845,20 @@ std::unique_ptr EngineBuilder::generate ProtobufWkt::Struct envoy_layer; ProtobufWkt::Struct& runtime_values = *(*envoy_layer.mutable_fields())["envoy"].mutable_struct_value(); - ProtobufWkt::Struct& flags = + ProtobufWkt::Struct& reloadable_features = *(*runtime_values.mutable_fields())["reloadable_features"].mutable_struct_value(); for (auto& guard_and_value : runtime_guards_) { - (*flags.mutable_fields())[guard_and_value.first].set_bool_value(guard_and_value.second); + (*reloadable_features.mutable_fields())[guard_and_value.first].set_bool_value( + guard_and_value.second); } - (*flags.mutable_fields())["always_use_v6"].set_bool_value(always_use_v6_); + (*reloadable_features.mutable_fields())["always_use_v6"].set_bool_value(always_use_v6_); + ProtobufWkt::Struct& restart_features = + *(*runtime_values.mutable_fields())["restart_features"].mutable_struct_value(); + // TODO(abeyad): This runtime flag is set because https://github.com/envoyproxy/envoy/pull/32370 + // needed to be merged with the default off due to unresolved test issues. Once those are fixed, + // and the default for `allow_client_socket_creation_failure` is true, we can remove this. + (*restart_features.mutable_fields())["allow_client_socket_creation_failure"].set_bool_value(true); + (*runtime_values.mutable_fields())["disallow_global_stats"].set_bool_value(true); ProtobufWkt::Struct& overload_values = *(*envoy_layer.mutable_fields())["overload"].mutable_struct_value(); diff --git a/mobile/test/cc/unit/envoy_config_test.cc b/mobile/test/cc/unit/envoy_config_test.cc index 020d6e97559a..38eaffc6e108 100644 --- a/mobile/test/cc/unit/envoy_config_test.cc +++ b/mobile/test/cc/unit/envoy_config_test.cc @@ -99,6 +99,7 @@ TEST(TestConfig, ConfigIsApplied) { "key: \"dns_persistent_cache\" save_interval { seconds: 101 }", "key: \"always_use_v6\" value { bool_value: true }", "key: \"test_feature_false\" value { bool_value: true }", + "key: \"allow_client_socket_creation_failure\" value { bool_value: true }", "key: \"device_os\" value { string_value: \"probably-ubuntu-on-CI\" } }", "key: \"app_version\" value { string_value: \"1.2.3\" } }", "key: \"app_id\" value { string_value: \"1234-1234-1234\" } }", From ba0986c6873d6441e203e82df0de0f8fc0259673 Mon Sep 17 00:00:00 2001 From: "Antonio V. Leonti" <53806445+antoniovleonti@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:43:12 -0500 Subject: [PATCH 065/151] filter_metadata: Reject unset keys in filter_metadata and typed_filter_metadata (#32409) Signed-off-by: Antonio Leonti --- api/envoy/config/core/v3/base.proto | 6 +++-- .../empty_filter_metadata_key | 22 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 test/common/router/header_parser_corpus/empty_filter_metadata_key diff --git a/api/envoy/config/core/v3/base.proto b/api/envoy/config/core/v3/base.proto index 97131e4b8c66..38a18ccecec3 100644 --- a/api/envoy/config/core/v3/base.proto +++ b/api/envoy/config/core/v3/base.proto @@ -245,7 +245,8 @@ message Metadata { // :ref:`typed_filter_metadata ` // fields are present in the metadata with same keys, // only ``typed_filter_metadata`` field will be parsed. - map filter_metadata = 1; + map filter_metadata = 1 + [(validate.rules).map = {keys {string {min_len: 1}}}]; // Key is the reverse DNS filter name, e.g. com.acme.widget. The ``envoy.*`` // namespace is reserved for Envoy's built-in filters. @@ -253,7 +254,8 @@ message Metadata { // If both :ref:`filter_metadata ` // and ``typed_filter_metadata`` fields are present in the metadata with same keys, // only ``typed_filter_metadata`` field will be parsed. - map typed_filter_metadata = 2; + map typed_filter_metadata = 2 + [(validate.rules).map = {keys {string {min_len: 1}}}]; } // Runtime derived uint32 with a default when not specified. diff --git a/test/common/router/header_parser_corpus/empty_filter_metadata_key b/test/common/router/header_parser_corpus/empty_filter_metadata_key new file mode 100644 index 000000000000..14515621b572 --- /dev/null +++ b/test/common/router/header_parser_corpus/empty_filter_metadata_key @@ -0,0 +1,22 @@ +headers_to_add { + header { + key: "@" + value: "%START_TIME()%%DYNAMIC_METADATA(:)%" + } +} +stream_info { + dynamic_metadata { + filter_metadata { + key: "" + value { + fields { + key: "" + value { + string_value: "\n#\022\034type.geco_nosode {\n lueList\n\001\'80\n%\032\001]\022\034type.geco_nedsoo {\n lueList\n\00080\n\"\022\034type.geco_nosode {\n lueList\n\00080" + } + } + } + } + } + requested_server_name: "file.Optio^s" +} From d6e06ddc98dfdf633027ac0a6259d7e7a557d10b Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Wed, 21 Feb 2024 16:53:07 -0600 Subject: [PATCH 066/151] mobile: Remove manual DeleteLocalRef calls (#32503) Signed-off-by: Fredy Wijaya --- mobile/library/jni/jni_impl.cc | 135 +++++++++++++++------------------ 1 file changed, 61 insertions(+), 74 deletions(-) diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index 58f2dac10755..b0d306744660 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -225,8 +225,9 @@ static void passHeaders(const char* method, const Envoy::Types::ManagedEnvoyHead // These methods call jvm methods which means the local references created will not be // released automatically. Manual bookkeeping is required for these methods. -static void* jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& headers, - bool end_stream, envoy_stream_intel stream_intel, void* context) { +static Envoy::JNI::LocalRefUniquePtr +jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& headers, + bool end_stream, envoy_stream_intel stream_intel, void* context) { jni_log("[Envoy]", "jvm_on_headers"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); @@ -241,7 +242,7 @@ static void* jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoy Envoy::JNI::envoyStreamIntelToJavaLongArray(jni_helper, stream_intel); // Note: be careful of JVM types. Before we casted to jlong we were getting integer problems. // TODO: make this cast safer. - Envoy::JNI::LocalRefUniquePtr result = jni_helper.callObjectMethod( + Envoy::JNI::LocalRefUniquePtr result = jni_helper.callObjectMethod( j_context, jmid_onHeaders, static_cast(headers.get().length), end_stream ? JNI_TRUE : JNI_FALSE, j_stream_intel.get()); // TODO(Augustyniak): Pass the name of the filter in here so that we can instrument the origin of @@ -249,7 +250,7 @@ static void* jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoy bool exception_cleared = Envoy::JNI::Exception::checkAndClear(method); if (!exception_cleared) { - return result.release(); + return result; } // Create a "no operation" result: @@ -273,13 +274,14 @@ static void* jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoy Envoy::JNI::envoyHeadersToJavaArrayOfObjectArray(jni_helper, headers); jni_helper.setObjectArrayElement(noopResult.get(), 1, j_headers.get()); - return noopResult.release(); + return noopResult; } static void* jvm_on_response_headers(envoy_headers headers, bool end_stream, envoy_stream_intel stream_intel, void* context) { const auto managed_headers = Envoy::Types::ManagedEnvoyHeaders(headers); - return jvm_on_headers("onResponseHeaders", managed_headers, end_stream, stream_intel, context); + return jvm_on_headers("onResponseHeaders", managed_headers, end_stream, stream_intel, context) + .release(); } static envoy_filter_headers_status @@ -287,25 +289,22 @@ jvm_http_filter_on_request_headers(envoy_headers input_headers, bool end_stream, envoy_stream_intel stream_intel, const void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); const auto headers = Envoy::Types::ManagedEnvoyHeaders(input_headers); - jobjectArray result = static_cast(jvm_on_headers( - "onRequestHeaders", headers, end_stream, stream_intel, const_cast(context))); + Envoy::JNI::LocalRefUniquePtr result = jvm_on_headers( + "onRequestHeaders", headers, end_stream, stream_intel, const_cast(context)); - if (result == NULL || jni_helper.getArrayLength(result) < 2) { - jni_helper.getEnv()->DeleteLocalRef(result); + if (result == nullptr || jni_helper.getArrayLength(result.get()) < 2) { return (envoy_filter_headers_status){/*status*/ kEnvoyFilterHeadersStatusStopIteration, /*headers*/ {}}; } - Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result, 0); + Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result.get(), 0); Envoy::JNI::LocalRefUniquePtr j_headers = - jni_helper.getObjectArrayElement(result, 1); + jni_helper.getObjectArrayElement(result.get(), 1); int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); envoy_headers native_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeaders(jni_helper, j_headers.get()); - jni_helper.getEnv()->DeleteLocalRef(result); - return (envoy_filter_headers_status){/*status*/ unboxed_status, /*headers*/ native_headers}; } @@ -315,31 +314,30 @@ jvm_http_filter_on_response_headers(envoy_headers input_headers, bool end_stream envoy_stream_intel stream_intel, const void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); const auto headers = Envoy::Types::ManagedEnvoyHeaders(input_headers); - jobjectArray result = static_cast(jvm_on_headers( - "onResponseHeaders", headers, end_stream, stream_intel, const_cast(context))); + Envoy::JNI::LocalRefUniquePtr result = jvm_on_headers( + "onResponseHeaders", headers, end_stream, stream_intel, const_cast(context)); - if (result == NULL || jni_helper.getArrayLength(result) < 2) { - jni_helper.getEnv()->DeleteLocalRef(result); + if (result == nullptr || jni_helper.getArrayLength(result.get()) < 2) { return (envoy_filter_headers_status){/*status*/ kEnvoyFilterHeadersStatusStopIteration, /*headers*/ {}}; } - Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result, 0); + Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result.get(), 0); Envoy::JNI::LocalRefUniquePtr j_headers = - jni_helper.getObjectArrayElement(result, 1); + jni_helper.getObjectArrayElement(result.get(), 1); int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); envoy_headers native_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeaders(jni_helper, j_headers.get()); - jni_helper.getEnv()->DeleteLocalRef(result); - return (envoy_filter_headers_status){/*status*/ unboxed_status, /*headers*/ native_headers}; } -static void* jvm_on_data(const char* method, envoy_data data, bool end_stream, - envoy_stream_intel stream_intel, void* context) { +static Envoy::JNI::LocalRefUniquePtr jvm_on_data(const char* method, envoy_data data, + bool end_stream, + envoy_stream_intel stream_intel, + void* context) { jni_log("[Envoy]", "jvm_on_data"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); @@ -353,10 +351,9 @@ static void* jvm_on_data(const char* method, envoy_data data, bool end_stream, Envoy::JNI::envoyDataToJavaByteArray(jni_helper, data); Envoy::JNI::LocalRefUniquePtr j_stream_intel = Envoy::JNI::envoyStreamIntelToJavaLongArray(jni_helper, stream_intel); - jobject result = jni_helper - .callObjectMethod(j_context, jmid_onData, j_data.get(), - end_stream ? JNI_TRUE : JNI_FALSE, j_stream_intel.get()) - .release(); + Envoy::JNI::LocalRefUniquePtr result = jni_helper.callObjectMethod( + j_context, jmid_onData, j_data.get(), end_stream ? JNI_TRUE : JNI_FALSE, + j_stream_intel.get()); release_envoy_data(data); @@ -365,26 +362,25 @@ static void* jvm_on_data(const char* method, envoy_data data, bool end_stream, static void* jvm_on_response_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, void* context) { - return jvm_on_data("onResponseData", data, end_stream, stream_intel, context); + return jvm_on_data("onResponseData", data, end_stream, stream_intel, context).release(); } static envoy_filter_data_status jvm_http_filter_on_request_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, const void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); - jobjectArray result = static_cast( - jvm_on_data("onRequestData", data, end_stream, stream_intel, const_cast(context))); + Envoy::JNI::LocalRefUniquePtr result = + jvm_on_data("onRequestData", data, end_stream, stream_intel, const_cast(context)); - if (result == NULL || jni_helper.getArrayLength(result) < 2) { - jni_helper.getEnv()->DeleteLocalRef(result); + if (result == nullptr || jni_helper.getArrayLength(result.get()) < 2) { return (envoy_filter_data_status){/*status*/ kEnvoyFilterHeadersStatusStopIteration, /*data*/ {}, /*pending_headers*/ {}}; } - Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result, 0); + Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result.get(), 0); Envoy::JNI::LocalRefUniquePtr j_data = - jni_helper.getObjectArrayElement(result, 1); + jni_helper.getObjectArrayElement(result.get(), 1); int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); envoy_data native_data = Envoy::JNI::javaByteBufferToEnvoyData(jni_helper, j_data.get()); @@ -393,13 +389,11 @@ static envoy_filter_data_status jvm_http_filter_on_request_data(envoy_data data, // Avoid out-of-bounds access to array when checking for optional pending entities. if (unboxed_status == kEnvoyFilterDataStatusResumeIteration) { Envoy::JNI::LocalRefUniquePtr j_headers = - jni_helper.getObjectArrayElement(result, 2); + jni_helper.getObjectArrayElement(result.get(), 2); pending_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeadersPtr(jni_helper, j_headers.get()); } - jni_helper.getEnv()->DeleteLocalRef(result); - return (envoy_filter_data_status){/*status*/ unboxed_status, /*data*/ native_data, /*pending_headers*/ pending_headers}; @@ -409,19 +403,18 @@ static envoy_filter_data_status jvm_http_filter_on_response_data(envoy_data data envoy_stream_intel stream_intel, const void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); - jobjectArray result = static_cast( - jvm_on_data("onResponseData", data, end_stream, stream_intel, const_cast(context))); + Envoy::JNI::LocalRefUniquePtr result = + jvm_on_data("onResponseData", data, end_stream, stream_intel, const_cast(context)); - if (result == NULL || jni_helper.getArrayLength(result) < 2) { - jni_helper.getEnv()->DeleteLocalRef(result); + if (result == nullptr || jni_helper.getArrayLength(result.get()) < 2) { return (envoy_filter_data_status){/*status*/ kEnvoyFilterHeadersStatusStopIteration, /*data*/ {}, /*pending_headers*/ {}}; } - Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result, 0); + Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result.get(), 0); Envoy::JNI::LocalRefUniquePtr j_data = - jni_helper.getObjectArrayElement(result, 1); + jni_helper.getObjectArrayElement(result.get(), 1); int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); envoy_data native_data = Envoy::JNI::javaByteBufferToEnvoyData(jni_helper, j_data.get()); @@ -430,13 +423,11 @@ static envoy_filter_data_status jvm_http_filter_on_response_data(envoy_data data // Avoid out-of-bounds access to array when checking for optional pending entities. if (unboxed_status == kEnvoyFilterDataStatusResumeIteration) { Envoy::JNI::LocalRefUniquePtr j_headers = - jni_helper.getObjectArrayElement(result, 2); + jni_helper.getObjectArrayElement(result.get(), 2); pending_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeadersPtr(jni_helper, j_headers.get()); } - jni_helper.getEnv()->DeleteLocalRef(result); - return (envoy_filter_data_status){/*status*/ unboxed_status, /*data*/ native_data, /*pending_headers*/ pending_headers}; @@ -449,8 +440,10 @@ static void* jvm_on_metadata(envoy_headers metadata, envoy_stream_intel /*stream return nullptr; } -static void* jvm_on_trailers(const char* method, envoy_headers trailers, - envoy_stream_intel stream_intel, void* context) { +static Envoy::JNI::LocalRefUniquePtr jvm_on_trailers(const char* method, + envoy_headers trailers, + envoy_stream_intel stream_intel, + void* context) { jni_log("[Envoy]", "jvm_on_trailers"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); @@ -466,37 +459,34 @@ static void* jvm_on_trailers(const char* method, envoy_headers trailers, Envoy::JNI::envoyStreamIntelToJavaLongArray(jni_helper, stream_intel); // Note: be careful of JVM types. Before we casted to jlong we were getting integer problems. // TODO: make this cast safer. - jobject result = jni_helper - .callObjectMethod(j_context, jmid_onTrailers, - static_cast(trailers.length), j_stream_intel.get()) - .release(); + Envoy::JNI::LocalRefUniquePtr result = jni_helper.callObjectMethod( + j_context, jmid_onTrailers, static_cast(trailers.length), j_stream_intel.get()); return result; } static void* jvm_on_response_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, void* context) { - return jvm_on_trailers("onResponseTrailers", trailers, stream_intel, context); + return jvm_on_trailers("onResponseTrailers", trailers, stream_intel, context).release(); } static envoy_filter_trailers_status jvm_http_filter_on_request_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, const void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); - jobjectArray result = static_cast( - jvm_on_trailers("onRequestTrailers", trailers, stream_intel, const_cast(context))); + Envoy::JNI::LocalRefUniquePtr result = + jvm_on_trailers("onRequestTrailers", trailers, stream_intel, const_cast(context)); - if (result == NULL || jni_helper.getArrayLength(result) < 2) { - jni_helper.getEnv()->DeleteLocalRef(result); + if (result == nullptr || jni_helper.getArrayLength(result.get()) < 2) { return (envoy_filter_trailers_status){/*status*/ kEnvoyFilterHeadersStatusStopIteration, /*trailers*/ {}, /*pending_headers*/ {}, /*pending_data*/ {}}; } - Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result, 0); + Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result.get(), 0); Envoy::JNI::LocalRefUniquePtr j_trailers = - jni_helper.getObjectArrayElement(result, 1); + jni_helper.getObjectArrayElement(result.get(), 1); int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); envoy_headers native_trailers = @@ -507,16 +497,15 @@ jvm_http_filter_on_request_trailers(envoy_headers trailers, envoy_stream_intel s // Avoid out-of-bounds access to array when checking for optional pending entities. if (unboxed_status == kEnvoyFilterTrailersStatusResumeIteration) { Envoy::JNI::LocalRefUniquePtr j_headers = - jni_helper.getObjectArrayElement(result, 2); + jni_helper.getObjectArrayElement(result.get(), 2); pending_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeadersPtr(jni_helper, j_headers.get()); - Envoy::JNI::LocalRefUniquePtr j_data = jni_helper.getObjectArrayElement(result, 3); + Envoy::JNI::LocalRefUniquePtr j_data = + jni_helper.getObjectArrayElement(result.get(), 3); pending_data = Envoy::JNI::javaByteBufferToEnvoyDataPtr(jni_helper, j_data.get()); } - jni_helper.getEnv()->DeleteLocalRef(result); - return (envoy_filter_trailers_status){/*status*/ unboxed_status, /*trailers*/ native_trailers, /*pending_headers*/ pending_headers, @@ -527,20 +516,19 @@ static envoy_filter_trailers_status jvm_http_filter_on_response_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, const void* context) { Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); - jobjectArray result = static_cast( - jvm_on_trailers("onResponseTrailers", trailers, stream_intel, const_cast(context))); + Envoy::JNI::LocalRefUniquePtr result = + jvm_on_trailers("onResponseTrailers", trailers, stream_intel, const_cast(context)); - if (result == NULL || jni_helper.getArrayLength(result) < 2) { - jni_helper.getEnv()->DeleteLocalRef(result); + if (result == nullptr || jni_helper.getArrayLength(result.get()) < 2) { return (envoy_filter_trailers_status){/*status*/ kEnvoyFilterHeadersStatusStopIteration, /*trailers*/ {}, /*pending_headers*/ {}, /*pending_data*/ {}}; } - Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result, 0); + Envoy::JNI::LocalRefUniquePtr status = jni_helper.getObjectArrayElement(result.get(), 0); Envoy::JNI::LocalRefUniquePtr j_trailers = - jni_helper.getObjectArrayElement(result, 1); + jni_helper.getObjectArrayElement(result.get(), 1); int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); envoy_headers native_trailers = @@ -551,16 +539,15 @@ jvm_http_filter_on_response_trailers(envoy_headers trailers, envoy_stream_intel // Avoid out-of-bounds access to array when checking for optional pending entities. if (unboxed_status == kEnvoyFilterTrailersStatusResumeIteration) { Envoy::JNI::LocalRefUniquePtr j_headers = - jni_helper.getObjectArrayElement(result, 2); + jni_helper.getObjectArrayElement(result.get(), 2); pending_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeadersPtr(jni_helper, j_headers.get()); - Envoy::JNI::LocalRefUniquePtr j_data = jni_helper.getObjectArrayElement(result, 3); + Envoy::JNI::LocalRefUniquePtr j_data = + jni_helper.getObjectArrayElement(result.get(), 3); pending_data = Envoy::JNI::javaByteBufferToEnvoyDataPtr(jni_helper, j_data.get()); } - jni_helper.getEnv()->DeleteLocalRef(result); - return (envoy_filter_trailers_status){/*status*/ unboxed_status, /*trailers*/ native_trailers, /*pending_headers*/ pending_headers, From ab40233b004b20ca57dbc9767a4b085113f57d58 Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 22 Feb 2024 00:27:18 -0500 Subject: [PATCH 067/151] quic: loosen transport socket factory downcast type (#32492) Commit Message: change EnvoyQuicClientSession to downcast transport socket factory to QuicTransportSocketFactoryBase instead of QuicClientTransportSocketFactory. Additional Description: this will enable the usage of other upstream transport socket extensions for QUIC. Risk Level: low Testing: existing test passed Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Dan Zhang --- source/common/quic/envoy_quic_client_session.cc | 2 +- source/common/quic/envoy_quic_client_session.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/source/common/quic/envoy_quic_client_session.cc b/source/common/quic/envoy_quic_client_session.cc index 46bc95d74ebf..51aa33aaf5ad 100644 --- a/source/common/quic/envoy_quic_client_session.cc +++ b/source/common/quic/envoy_quic_client_session.cc @@ -91,7 +91,7 @@ EnvoyQuicClientSession::EnvoyQuicClientSession( quic_stat_names_(quic_stat_names), rtt_cache_(rtt_cache), scope_(scope), transport_socket_options_(transport_socket_options), transport_socket_factory_(makeOptRefFromPtr( - dynamic_cast(transport_socket_factory.ptr()))) { + dynamic_cast(transport_socket_factory.ptr()))) { streamInfo().setUpstreamInfo(std::make_shared()); if (transport_socket_options_ != nullptr && !transport_socket_options_->applicationProtocolListOverride().empty()) { diff --git a/source/common/quic/envoy_quic_client_session.h b/source/common/quic/envoy_quic_client_session.h index 7128c43d8171..1414a5a5f94d 100644 --- a/source/common/quic/envoy_quic_client_session.h +++ b/source/common/quic/envoy_quic_client_session.h @@ -5,9 +5,9 @@ #include "source/common/quic/envoy_quic_client_connection.h" #include "source/common/quic/envoy_quic_client_crypto_stream_factory.h" #include "source/common/quic/envoy_quic_client_stream.h" -#include "source/common/quic/quic_client_transport_socket_factory.h" #include "source/common/quic/quic_filter_manager_connection_impl.h" #include "source/common/quic/quic_stat_names.h" +#include "source/common/quic/quic_transport_socket_factory.h" #include "quiche/quic/core/http/quic_spdy_client_session.h" @@ -130,7 +130,7 @@ class EnvoyQuicClientSession : public QuicFilterManagerConnectionImpl, Stats::Scope& scope_; bool disable_keepalive_{false}; Network::TransportSocketOptionsConstSharedPtr transport_socket_options_; - OptRef transport_socket_factory_; + OptRef transport_socket_factory_; std::vector configured_alpns_; }; From 3d0f5fd2e3a98227db0fa99aa0646f743ad6ff3e Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 22 Feb 2024 00:28:14 -0500 Subject: [PATCH 068/151] quiche: move overridden flag list and QUIC flag prefix definition into a stand alone file (#32026) Commit Message: created a new file source/common/quic/platform/quiche_flags_constants.h and moves the definition of EnvoyQuicheReloadableFlagPrefix and EnvoyFeaturePrefix there. Also define a list of quic flags that needs to be overridden there. Risk Level: low, as long as it compile, there should be no behavior change Testing: existing test pass Docs Changes: N/A Release Notes: n/a Platform Specific Features: N/A Signed-off-by: Dan Zhang --- source/common/quic/platform/BUILD | 8 +++ .../quic/platform/quiche_flags_constants.h | 46 ++++++++++++++ .../common/quic/platform/quiche_flags_impl.cc | 63 ++++++++++--------- .../common/quic/platform/quiche_flags_impl.h | 6 +- source/common/runtime/BUILD | 2 + source/common/runtime/runtime_impl.cc | 18 +++--- test/common/runtime/runtime_impl_test.cc | 16 ++--- 7 files changed, 111 insertions(+), 48 deletions(-) create mode 100644 source/common/quic/platform/quiche_flags_constants.h diff --git a/source/common/quic/platform/BUILD b/source/common/quic/platform/BUILD index d10213a60dc9..4d6061b2e072 100644 --- a/source/common/quic/platform/BUILD +++ b/source/common/quic/platform/BUILD @@ -1,5 +1,6 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_cc_library", "envoy_package", ) load( @@ -36,6 +37,12 @@ envoy_package() # TODO: add build target for quic_platform_impl_lib +envoy_cc_library( + name = "quiche_flags_constants", + hdrs = ["quiche_flags_constants.h"], + deps = ["//source/common/http:utility_lib"], +) + envoy_quiche_platform_impl_cc_library( name = "quiche_flags_impl_lib", srcs = ["quiche_flags_impl.cc"], @@ -45,6 +52,7 @@ envoy_quiche_platform_impl_cc_library( "abseil_synchronization", ], deps = [ + ":quiche_flags_constants", "//source/common/common:assert_lib", "//source/common/http:utility_lib", "@com_github_google_quiche//:quic_core_flags_list_lib", diff --git a/source/common/quic/platform/quiche_flags_constants.h b/source/common/quic/platform/quiche_flags_constants.h new file mode 100644 index 000000000000..cff579bad68c --- /dev/null +++ b/source/common/quic/platform/quiche_flags_constants.h @@ -0,0 +1,46 @@ +#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. +// 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 "source/common/http/utility.h" + +#define OVERRIDDEN_RELOADABLE_FLAGS(KEY_VALUE_PAIR) \ + /* Envoy only supports RFC-v1 in the long term, so disable IETF draft 29 implementation by \ + * default. */ \ + KEY_VALUE_PAIR(quic_disable_version_draft_29, true) \ + /* This flag enables BBR, otherwise QUIC will use Cubic which is less performant */ \ + KEY_VALUE_PAIR(quic_default_to_bbr, true) + +#define OVERRIDDEN_PROTOCOL_FLAGS(KEY_VALUE_PAIR) \ + /* Do not include 32-byte per-entry overhead while counting header size. */ \ + KEY_VALUE_PAIR(quic_header_size_limit_includes_overhead, false) \ + /* Set send buffer twice of max flow control window to ensure that stream send buffer always \ + * takes all the data. The max amount of data buffered is the per-stream high watermark + the \ + * max flow control window of upstream. The per-stream high watermark should be smaller than max \ + * flow control window to make sure upper stream can be flow control blocked early enough not to \ + * send more than the threshold allows. */ \ + /* TODO(#8826) Ideally we should use the negotiated value from upstream which is not accessible \ + * for now. 512MB is way too large, but the actual bytes buffered should be bound by the \ + * negotiated upstream flow control window. */ \ + KEY_VALUE_PAIR(quic_buffered_data_threshold, \ + 2 * ::Envoy::Http2::Utility::OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE) \ + /* Envoy should send server preferred address without a client option by default. */ \ + KEY_VALUE_PAIR(quic_always_support_server_preferred_address, true) + +namespace quiche { + +inline constexpr absl::string_view EnvoyQuicheReloadableFlagPrefix = + "envoy.reloadable_features.FLAGS_envoy_quic_reloadable_flag_"; + +inline constexpr absl::string_view EnvoyFeaturePrefix = "envoy.reloadable_features."; + +} // namespace quiche diff --git a/source/common/quic/platform/quiche_flags_impl.cc b/source/common/quic/platform/quiche_flags_impl.cc index aeeb9bb0143a..f46ef6303e23 100644 --- a/source/common/quic/platform/quiche_flags_impl.cc +++ b/source/common/quic/platform/quiche_flags_impl.cc @@ -5,9 +5,11 @@ // porting layer for QUICHE. #include +#include +#include +#include #include "source/common/common/assert.h" -#include "source/common/http/utility.h" #include "absl/flags/flag.h" #include "absl/strings/ascii.h" @@ -17,6 +19,17 @@ namespace { +#define QUICHE_RELOADABLE_FLAG_OVERRIDE(flag_name, value) \ + {STRINGIFY(quic_reloadable_flag_##flag_name), value}, +constexpr std::pair quiche_reloadable_flag_overrides[]{ + OVERRIDDEN_RELOADABLE_FLAGS(QUICHE_RELOADABLE_FLAG_OVERRIDE)}; +#undef QUICHE_RELOADABLE_FLAG_OVERRIDE + +#define QUICHE_PROTOCOL_FLAG_OVERRIDE(flag_name, value) {STRINGIFY(flag_name), value}, +constexpr std::pair> + quiche_protocol_flag_overrides[]{OVERRIDDEN_PROTOCOL_FLAGS(QUICHE_PROTOCOL_FLAG_OVERRIDE)}; +#undef QUICHE_PROTOCOL_FLAG_OVERRIDE + // Envoy uses different default values for some QUICHE flags. The following methods // ensure that the absl::Flag objects are created with the correct values for // these flags. This ensures that the absl::FlagSaver finds the correct values @@ -25,39 +38,28 @@ namespace { template constexpr T maybeOverride(absl::string_view /*name*/, T val) { return val; } template <> constexpr bool maybeOverride(absl::string_view name, bool val) { - if (name == "quic_reloadable_flag_quic_disable_version_draft_29") { - // Envoy only supports RFC-v1 in the long term, so disable IETF draft 29 implementation by - // default. - return true; - } - if (name == "quic_reloadable_flag_quic_default_to_bbr") { - // This flag enables BBR, otherwise QUIC will use Cubic which is less performant. - return true; - } - if (name == "quic_header_size_limit_includes_overhead") { - // Do not include 32-byte per-entry overhead while counting header size. - return false; + for (const auto& [flag_name, new_value] : quiche_reloadable_flag_overrides) { + if (flag_name == name) { + return new_value; + } } - if (name == "quic_always_support_server_preferred_address") { - // Envoy should send server preferred address without a client option by default. - return true; + for (const auto& [flag_name, new_value_variant] : quiche_protocol_flag_overrides) { + if (flag_name == name) { + if (absl::holds_alternative(new_value_variant)) { + return absl::get(new_value_variant); + } + } } - return val; } template <> constexpr int32_t maybeOverride(absl::string_view name, int32_t val) { - if (name == "quic_buffered_data_threshold") { - // Set send buffer twice of max flow control window to ensure that stream send - // buffer always takes all the data. - // The max amount of data buffered is the per-stream high watermark + the max - // flow control window of upstream. The per-stream high watermark should be - // smaller than max flow control window to make sure upper stream can be flow - // control blocked early enough not to send more than the threshold allows. - // TODO(#8826) Ideally we should use the negotiated value from upstream which is not accessible - // for now. 512MB is way to large, but the actual bytes buffered should be bound by the - // negotiated upstream flow control window. - return 2 * ::Envoy::Http2::Utility::OptionsLimits::DEFAULT_INITIAL_STREAM_WINDOW_SIZE; // 512MB + for (const auto& [flag_name, new_value_variant] : quiche_protocol_flag_overrides) { + if (flag_name == name) { + if (absl::holds_alternative(new_value_variant)) { + return absl::get(new_value_variant); + } + } } return val; } @@ -122,6 +124,11 @@ FlagRegistry::FlagRegistry() : reloadable_flags_(makeReloadableFlagMap()) {} // static FlagRegistry& FlagRegistry::getInstance() { static auto* instance = new FlagRegistry(); + ASSERT(sizeof(quiche_reloadable_flag_overrides) / sizeof(std::pair) == + 2); + ASSERT(sizeof(quiche_protocol_flag_overrides) / + sizeof(std::pair>) == + 3); return *instance; } diff --git a/source/common/quic/platform/quiche_flags_impl.h b/source/common/quic/platform/quiche_flags_impl.h index e3a8d3f87d7c..77c31aea9432 100644 --- a/source/common/quic/platform/quiche_flags_impl.h +++ b/source/common/quic/platform/quiche_flags_impl.h @@ -9,6 +9,8 @@ #include #include +#include "source/common/quic/platform/quiche_flags_constants.h" + #include "absl/container/flat_hash_map.h" #include "absl/flags/declare.h" #include "absl/flags/flag.h" @@ -20,10 +22,6 @@ namespace quiche { -const std::string EnvoyQuicheReloadableFlagPrefix = - "envoy.reloadable_features.FLAGS_envoy_quic_reloadable_flag_"; -const std::string EnvoyFeaturePrefix = "envoy.reloadable_features."; - using ReloadableFlag = absl::Flag; // Registry of QUICHE flags. Can be used to update reloadable flag values. diff --git a/source/common/runtime/BUILD b/source/common/runtime/BUILD index b5b1dc888386..1240f73bd005 100644 --- a/source/common/runtime/BUILD +++ b/source/common/runtime/BUILD @@ -94,6 +94,7 @@ envoy_cc_library( "//source/common/config:subscription_base_interface", "//source/common/filesystem:directory_lib", "//source/common/grpc:common_lib", + "//source/common/http:utility_lib", "//source/common/init:manager_lib", "//source/common/init:target_lib", "//source/common/init:watcher_lib", @@ -106,5 +107,6 @@ envoy_cc_library( "@envoy_api//envoy/type/v3:pkg_cc_proto", ] + envoy_select_enable_http3([ "//source/common/quic/platform:quiche_flags_impl_lib", + "@com_github_google_quiche//:quic_platform_base", ]), ) diff --git a/source/common/runtime/runtime_impl.cc b/source/common/runtime/runtime_impl.cc index c40bcd8a463d..17511407813a 100644 --- a/source/common/runtime/runtime_impl.cc +++ b/source/common/runtime/runtime_impl.cc @@ -17,6 +17,7 @@ #include "source/common/config/api_version.h" #include "source/common/filesystem/directory.h" #include "source/common/grpc/common.h" +#include "source/common/http/utility.h" #include "source/common/protobuf/message_validator_impl.h" #include "source/common/protobuf/utility.h" #include "source/common/runtime/runtime_features.h" @@ -45,20 +46,21 @@ void countDeprecatedFeatureUseInternal(const RuntimeStats& stats) { } void refreshReloadableFlags(const Snapshot::EntryMap& flag_map) { - absl::flat_hash_map quiche_flags_override; for (const auto& it : flag_map) { + if (it.second.bool_value_.has_value() && isRuntimeFeature(it.first)) { + maybeSetRuntimeGuard(it.first, it.second.bool_value_.value()); + } + } #ifdef ENVOY_ENABLE_QUIC + absl::flat_hash_map quiche_flags_override; + for (const auto& it : flag_map) { if (absl::StartsWith(it.first, quiche::EnvoyQuicheReloadableFlagPrefix) && it.second.bool_value_.has_value()) { quiche_flags_override[it.first.substr(quiche::EnvoyFeaturePrefix.length())] = it.second.bool_value_.value(); } -#endif - if (it.second.bool_value_.has_value() && isRuntimeFeature(it.first)) { - maybeSetRuntimeGuard(it.first, it.second.bool_value_.value()); - } } -#ifdef ENVOY_ENABLE_QUIC + quiche::FlagRegistry::getInstance().updateReloadableFlags(quiche_flags_override); // Because this is a QUICHE protocol flag, this behavior can't be flipped with the above @@ -662,8 +664,8 @@ absl::Status RtdsSubscription::validateUpdateSize(uint32_t added_resources_num, if (added_resources_num + removed_resources_num != 1) { init_target_.ready(); return absl::InvalidArgumentError( - fmt::format("Unexpected RTDS resource length, number of added recources " - "{}, number of removed recources {}", + fmt::format("Unexpected RTDS resource length, number of added resources " + "{}, number of removed resources {}", added_resources_num, removed_resources_num)); } return absl::OkStatus(); diff --git a/test/common/runtime/runtime_impl_test.cc b/test/common/runtime/runtime_impl_test.cc index f040aedfbcf8..38616f014735 100644 --- a/test/common/runtime/runtime_impl_test.cc +++ b/test/common/runtime/runtime_impl_test.cc @@ -550,12 +550,12 @@ TEST_F(StaticLoaderImplTest, QuicheReloadableFlags) { envoy.reloadable_features.FLAGS_envoy_quic_reloadable_flag_quic_testonly_default_true: false envoy.reloadable_features.FLAGS_envoy_quic_reloadable_flag_spdy_testonly_default_false: false )EOF"); - SetQuicReloadableFlag(spdy_testonly_default_false, true); - EXPECT_TRUE(GetQuicReloadableFlag(spdy_testonly_default_false)); + SetQuicheReloadableFlag(spdy, spdy_testonly_default_false, true); + EXPECT_TRUE(GetQuicheReloadableFlag(spdy, spdy_testonly_default_false)); setup(); EXPECT_TRUE(GetQuicReloadableFlag(quic_testonly_default_false)); EXPECT_FALSE(GetQuicReloadableFlag(quic_testonly_default_true)); - EXPECT_FALSE(GetQuicReloadableFlag(spdy_testonly_default_false)); + EXPECT_FALSE(GetQuicheReloadableFlag(spdy, spdy_testonly_default_false)); // Test that Quiche flags can be overwritten again. base_ = TestUtility::parseYaml(R"EOF( @@ -564,7 +564,7 @@ TEST_F(StaticLoaderImplTest, QuicheReloadableFlags) { setup(); EXPECT_TRUE(GetQuicReloadableFlag(quic_testonly_default_false)); EXPECT_TRUE(GetQuicReloadableFlag(quic_testonly_default_true)); - EXPECT_FALSE(GetQuicReloadableFlag(spdy_testonly_default_false)); + EXPECT_FALSE(GetQuicheReloadableFlag(spdy, spdy_testonly_default_false)); } #endif @@ -987,8 +987,8 @@ TEST_F(RtdsLoaderImplTest, UnexpectedSizeEmpty) { EXPECT_CALL(rtds_init_callback_, Call()); EXPECT_EQ(rtds_callbacks_[0]->onConfigUpdate({}, "").message(), - "Unexpected RTDS resource length, number of added recources 0, number " - "of removed recources 0"); + "Unexpected RTDS resource length, number of added resources 0, number of removed " + "resources 0"); EXPECT_EQ(0, store_.counter("runtime.load_error").value()); EXPECT_EQ(1, store_.counter("runtime.load_success").value()); @@ -1005,8 +1005,8 @@ TEST_F(RtdsLoaderImplTest, UnexpectedSizeTooMany) { EXPECT_CALL(rtds_init_callback_, Call()); EXPECT_EQ(rtds_callbacks_[0]->onConfigUpdate(decoded_resources.refvec_, "").message(), - "Unexpected RTDS resource length, number of added recources 2, number " - "of removed recources 0"); + "Unexpected RTDS resource length, number of added resources 2, number of removed " + "resources 0"); EXPECT_EQ(0, store_.counter("runtime.load_error").value()); EXPECT_EQ(1, store_.counter("runtime.load_success").value()); From 5b674b5a1d33fb01727aceb132ee3c7b06c4201c Mon Sep 17 00:00:00 2001 From: Kateryna Nezdolii Date: Thu, 22 Feb 2024 10:38:39 +0100 Subject: [PATCH 069/151] Execute geoip tests on osx (#32489) Signed-off-by: Kateryna Nezdolii --- test/extensions/filters/http/geoip/BUILD | 1 + test/extensions/geoip_providers/maxmind/BUILD | 2 ++ 2 files changed, 3 insertions(+) diff --git a/test/extensions/filters/http/geoip/BUILD b/test/extensions/filters/http/geoip/BUILD index 6e626debcd4b..d0e9ec7b20a8 100644 --- a/test/extensions/filters/http/geoip/BUILD +++ b/test/extensions/filters/http/geoip/BUILD @@ -59,6 +59,7 @@ envoy_extension_cc_test( size = "large", srcs = select({ "//bazel:linux": ["geoip_filter_integration_test.cc"], + "//bazel:darwin_any": ["geoip_filter_integration_test.cc"], "//conditions:default": [], }), data = [ diff --git a/test/extensions/geoip_providers/maxmind/BUILD b/test/extensions/geoip_providers/maxmind/BUILD index 1757c2c53c8b..c39b4eb33382 100644 --- a/test/extensions/geoip_providers/maxmind/BUILD +++ b/test/extensions/geoip_providers/maxmind/BUILD @@ -16,6 +16,7 @@ envoy_extension_cc_test( size = "small", srcs = select({ "//bazel:linux": ["config_test.cc"], + "//bazel:darwin_any": ["config_test.cc"], "//conditions:default": [], }), data = [ @@ -36,6 +37,7 @@ envoy_extension_cc_test( size = "small", srcs = select({ "//bazel:linux": ["geoip_provider_test.cc"], + "//bazel:darwin_any": ["geoip_provider_test.cc"], "//conditions:default": [], }), data = [ From 5d4dccb842ce6688afc35d15a1a28f22cc7612ba Mon Sep 17 00:00:00 2001 From: Eric Chung Date: Thu, 22 Feb 2024 01:49:26 -0800 Subject: [PATCH 070/151] ci/coverage: revise regex for extracting clang and LLVM versions (#32509) ci/coverage: make regex for extracting clang and LLVM versions more strict The regular expressions currently used to extract clang and LLVM versions in `run_envoy_bazel_coverage.sh` can include spurious non-version characters which can result in an accidental mismatch. This change revises the expressions assuming that clang and LLVM versions will always match `[0-9.]*`, the characters expected in semantic versioning, which should be a reasonable assumption. Signed-off-by: Eric Chung --- test/run_envoy_bazel_coverage.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/run_envoy_bazel_coverage.sh b/test/run_envoy_bazel_coverage.sh index 3261eeded5ab..feee13a07f11 100755 --- a/test/run_envoy_bazel_coverage.sh +++ b/test/run_envoy_bazel_coverage.sh @@ -3,8 +3,8 @@ set -e -o pipefail LLVM_VERSION=${LLVM_VERSION:-"14.0.0"} -CLANG_VERSION=$(clang --version | grep version | sed -e 's/\ *clang version \(.*\)\ */\1/') -LLVM_COV_VERSION=$(llvm-cov --version | grep version | sed -e 's/\ *LLVM version \(.*\)/\1/') +CLANG_VERSION=$(clang --version | grep version | sed -e 's/\ *clang version \([0-9.]*\).*/\1/') +LLVM_COV_VERSION=$(llvm-cov --version | grep version | sed -e 's/\ *LLVM version \([0-9.]*\).*/\1/') LLVM_PROFDATA_VERSION=$(llvm-profdata show --version | grep version | sed -e 's/\ *LLVM version \(.*\)/\1/') if [ "${CLANG_VERSION}" != "${LLVM_VERSION}" ] From c82c08396f035ad6e09e5182bbdb350aea245a60 Mon Sep 17 00:00:00 2001 From: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:40:55 +0100 Subject: [PATCH 071/151] opentelemetry tracer: Add TraceState types from OTel C++ SDK (#32333) Signed-off-by: Joao Grassi Signed-off-by: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> --- bazel/repositories.bzl | 8 ++++++++ bazel/repository_locations.bzl | 18 ++++++++++++++++++ source/extensions/tracers/opentelemetry/BUILD | 6 ++++++ test/extensions/tracers/opentelemetry/BUILD | 10 +++++++++- 4 files changed, 41 insertions(+), 1 deletion(-) diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 6394e3ce7450..b5ddba3eeacf 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -338,6 +338,7 @@ def envoy_dependencies(skip_targets = []): _io_hyperscan() _io_vectorscan() _io_opentracing_cpp() + _io_opentelemetry_api_cpp() _net_colm_open_source_colm() _net_colm_open_source_ragel() _net_zlib() @@ -775,6 +776,13 @@ def _io_opentracing_cpp(): actual = "@io_opentracing_cpp//:opentracing", ) +def _io_opentelemetry_api_cpp(): + external_http_archive("io_opentelemetry_cpp") + native.bind( + name = "opentelemetry_api", + actual = "@io_opentelemetry_cpp//api:api", + ) + def _com_github_datadog_dd_trace_cpp(): external_http_archive("com_github_datadog_dd_trace_cpp") native.bind( diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 24a59a120340..526603c8f5ad 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -553,6 +553,24 @@ REPOSITORY_LOCATIONS_SPEC = dict( license = "Apache-2.0", license_url = "https://github.com/opentracing/opentracing-cpp/blob/v{version}/LICENSE", ), + io_opentelemetry_cpp = dict( + project_name = "OpenTelemetry", + project_desc = "Observability framework and toolkit designed to create and manage telemetry data such as traces, metrics, and logs.", + project_url = "https://opentelemetry.io", + version = "1.14.0", + sha256 = "9a67561b8f4dba8cf8bb323b404069cbcc2f59974e1330b1ba192c5c8d68c28e", + strip_prefix = "opentelemetry-cpp-{version}", + urls = ["https://github.com/open-telemetry/opentelemetry-cpp/archive/refs/tags/v{version}.tar.gz"], + use_category = ["observability_ext"], + extensions = [ + "envoy.tracers.opentelemetry", + "envoy.tracers.opentelemetry.samplers.always_on", + ], + release_date = "2024-02-17", + cpe = "N/A", + license = "Apache-2.0", + license_url = "https://github.com/open-telemetry/opentelemetry-cpp/blob/v{version}/LICENSE", + ), skywalking_data_collect_protocol = dict( project_name = "skywalking-data-collect-protocol", project_desc = "Data Collect Protocols of Apache SkyWalking", diff --git a/source/extensions/tracers/opentelemetry/BUILD b/source/extensions/tracers/opentelemetry/BUILD index e84423ccf272..a4ee2ed5e51b 100644 --- a/source/extensions/tracers/opentelemetry/BUILD +++ b/source/extensions/tracers/opentelemetry/BUILD @@ -35,6 +35,12 @@ envoy_cc_library( "span_context_extractor.h", "tracer.h", ], + copts = [ + # Make sure that headers included from opentelemetry-api use Abseil from Envoy + # https://github.com/open-telemetry/opentelemetry-cpp/blob/v1.14.0/api/BUILD#L32 + "-DHAVE_ABSEIL", + ], + external_deps = ["opentelemetry_api"], deps = [ ":trace_exporter", "//envoy/thread_local:thread_local_interface", diff --git a/test/extensions/tracers/opentelemetry/BUILD b/test/extensions/tracers/opentelemetry/BUILD index 96e7e5d91537..f96040354c42 100644 --- a/test/extensions/tracers/opentelemetry/BUILD +++ b/test/extensions/tracers/opentelemetry/BUILD @@ -16,8 +16,16 @@ envoy_extension_cc_test( srcs = [ "opentelemetry_tracer_impl_test.cc", ], + copts = [ + # Make sure that headers included from opentelemetry-api use Abseil from Envoy + # https://github.com/open-telemetry/opentelemetry-cpp/blob/v1.14.0/api/BUILD#L32 + "-DHAVE_ABSEIL", + ], extension_names = ["envoy.tracers.opentelemetry"], - external_deps = ["abseil_optional"], + external_deps = [ + "abseil_optional", + "opentelemetry_api", + ], deps = [ "//envoy/common:time_interface", "//envoy/runtime:runtime_interface", From ffbf1889ef788ccb4b1d0e402ed2c3976d7204fd Mon Sep 17 00:00:00 2001 From: botengyao Date: Thu, 22 Feb 2024 10:10:17 -0500 Subject: [PATCH 072/151] OM: move lsp names to one place (#32508) Signed-off-by: Boteng Yao --- envoy/server/overload/load_shed_point.h | 21 +++++++++++++++++++++ source/common/http/conn_manager_impl.cc | 4 ++-- source/common/http/http1/codec_impl.cc | 4 ++-- source/common/http/http2/codec_impl.cc | 2 +- source/common/network/tcp_listener_impl.cc | 2 +- 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/envoy/server/overload/load_shed_point.h b/envoy/server/overload/load_shed_point.h index ef19210a34a7..50018a2906fc 100644 --- a/envoy/server/overload/load_shed_point.h +++ b/envoy/server/overload/load_shed_point.h @@ -9,6 +9,27 @@ namespace Envoy { namespace Server { +class LoadShedPointNameValues { +public: + // Envoy will reject (close) new TCP connections. + // This occurs before the Listener Filter Chain is created. + const std::string TcpListenerAccept = "envoy.load_shed_points.tcp_listener_accept"; + + // Envoy will reject new HTTP streams by sending a local reply. + const std::string HcmDecodeHeaders = + "envoy.load_shed_points.http_connection_manager_decode_headers"; + + // Envoy will reject processing HTTP1 at the codec level. + const std::string H1ServerAbortDispatch = "envoy.load_shed_points.http1_server_abort_dispatch"; + + // Envoy will send a GOAWAY while processing HTTP2 requests at the codec level + // which will eventually drain the HTTP/2 connection. + const std::string H2ServerGoAwayOnDispatch = + "envoy.load_shed_points.http2_server_go_away_on_dispatch"; +}; + +using LoadShedPointName = ConstSingleton; + /** * A point within the connection or request lifecycle that provides context on * whether to shed load at that given stage for the current entity at the point. diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index b6725038eddd..f8a3bed13a6b 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -114,8 +114,8 @@ ConnectionManagerImpl::ConnectionManagerImpl(ConnectionManagerConfig& config, cluster_manager_(cluster_manager), listener_stats_(config_.listenerStats()), overload_manager_(overload_manager), overload_state_(overload_manager.getThreadLocalOverloadState()), - accept_new_http_stream_(overload_manager.getLoadShedPoint( - "envoy.load_shed_points.http_connection_manager_decode_headers")), + accept_new_http_stream_( + overload_manager.getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)), overload_stop_accepting_requests_ref_( overload_state_.getState(Server::OverloadActionNames::get().StopAcceptingRequests)), overload_disable_keepalive_ref_( diff --git a/source/common/http/http1/codec_impl.cc b/source/common/http/http1/codec_impl.cc index fcd62e115024..be3445209e80 100644 --- a/source/common/http/http1/codec_impl.cc +++ b/source/common/http/http1/codec_impl.cc @@ -1071,8 +1071,8 @@ ServerConnectionImpl::ServerConnectionImpl( [&]() -> void { this->onAboveHighWatermark(); }, []() -> void { /* TODO(adisuissa): handle overflow watermark */ })), headers_with_underscores_action_(headers_with_underscores_action), - abort_dispatch_( - overload_manager.getLoadShedPoint("envoy.load_shed_points.http1_server_abort_dispatch")) { + abort_dispatch_(overload_manager.getLoadShedPoint( + Server::LoadShedPointName::get().H1ServerAbortDispatch)) { ENVOY_LOG_ONCE_IF(trace, abort_dispatch_ == nullptr, "LoadShedPoint envoy.load_shed_points.http1_server_abort_dispatch is not " "found. Is it configured?"); diff --git a/source/common/http/http2/codec_impl.cc b/source/common/http/http2/codec_impl.cc index b744fe6c5c61..c0e4f569d1bb 100644 --- a/source/common/http/http2/codec_impl.cc +++ b/source/common/http/http2/codec_impl.cc @@ -2121,7 +2121,7 @@ ServerConnectionImpl::ServerConnectionImpl( max_request_headers_count), callbacks_(callbacks), headers_with_underscores_action_(headers_with_underscores_action), should_send_go_away_on_dispatch_(overload_manager.getLoadShedPoint( - "envoy.load_shed_points.http2_server_go_away_on_dispatch")) { + Server::LoadShedPointName::get().H2ServerGoAwayOnDispatch)) { ENVOY_LOG_ONCE_IF(trace, should_send_go_away_on_dispatch_ == nullptr, "LoadShedPoint envoy.load_shed_points.http2_server_go_away_on_dispatch is not " "found. Is it configured?"); diff --git a/source/common/network/tcp_listener_impl.cc b/source/common/network/tcp_listener_impl.cc index 5df320f7938d..513f3a688618 100644 --- a/source/common/network/tcp_listener_impl.cc +++ b/source/common/network/tcp_listener_impl.cc @@ -164,7 +164,7 @@ void TcpListenerImpl::setRejectFraction(const UnitFloat reject_fraction) { void TcpListenerImpl::configureLoadShedPoints( Server::LoadShedPointProvider& load_shed_point_provider) { listener_accept_ = - load_shed_point_provider.getLoadShedPoint("envoy.load_shed_points.tcp_listener_accept"); + load_shed_point_provider.getLoadShedPoint(Server::LoadShedPointName::get().TcpListenerAccept); ENVOY_LOG_ONCE_MISC_IF( trace, listener_accept_ == nullptr, "LoadShedPoint envoy.load_shed_points.tcp_listener_accept is not found. Is it configured?"); From 213cc97bb2c8de69b90c99331123309b1cf51055 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Thu, 22 Feb 2024 11:51:00 -0600 Subject: [PATCH 073/151] mobile: Change the return type to void from void* in the callbacks (#32522) Signed-off-by: Fredy Wijaya --- mobile/library/cc/stream_callbacks.cc | 24 ++--- mobile/library/common/types/c_types.h | 37 +++----- mobile/library/jni/jni_impl.cc | 91 ++++++++----------- .../objective-c/EnvoyHTTPStreamImpl.mm | 39 +++----- mobile/test/common/http/client_test.cc | 39 +++----- mobile/test/common/main_interface_test.cc | 9 +- 6 files changed, 90 insertions(+), 149 deletions(-) diff --git a/mobile/library/cc/stream_callbacks.cc b/mobile/library/cc/stream_callbacks.cc index 31caeafa5a57..eaab4f20ffc9 100644 --- a/mobile/library/cc/stream_callbacks.cc +++ b/mobile/library/cc/stream_callbacks.cc @@ -10,8 +10,7 @@ namespace Platform { namespace { -void* c_on_headers(envoy_headers headers, bool end_stream, envoy_stream_intel intel, - void* context) { +void c_on_headers(envoy_headers headers, bool end_stream, envoy_stream_intel intel, void* context) { auto stream_callbacks = *static_cast(context); if (stream_callbacks->on_headers.has_value()) { auto raw_headers = envoyHeadersAsRawHeaderMap(headers); @@ -27,10 +26,9 @@ void* c_on_headers(envoy_headers headers, bool end_stream, envoy_stream_intel in } else { release_envoy_headers(headers); } - return context; } -void* c_on_data(envoy_data data, bool end_stream, envoy_stream_intel, void* context) { +void c_on_data(envoy_data data, bool end_stream, envoy_stream_intel, void* context) { auto stream_callbacks = *static_cast(context); if (stream_callbacks->on_data.has_value()) { auto on_data = stream_callbacks->on_data.value(); @@ -38,10 +36,9 @@ void* c_on_data(envoy_data data, bool end_stream, envoy_stream_intel, void* cont } else { release_envoy_data(data); } - return context; } -void* c_on_trailers(envoy_headers metadata, envoy_stream_intel intel, void* context) { +void c_on_trailers(envoy_headers metadata, envoy_stream_intel intel, void* context) { auto stream_callbacks = *static_cast(context); if (stream_callbacks->on_trailers.has_value()) { auto raw_headers = envoyHeadersAsRawHeaderMap(metadata); @@ -54,11 +51,10 @@ void* c_on_trailers(envoy_headers metadata, envoy_stream_intel intel, void* cont } else { release_envoy_headers(metadata); } - return context; } -void* c_on_error(envoy_error raw_error, envoy_stream_intel intel, - envoy_final_stream_intel final_intel, void* context) { +void c_on_error(envoy_error raw_error, envoy_stream_intel intel, + envoy_final_stream_intel final_intel, void* context) { auto stream_callbacks_ptr = static_cast(context); auto stream_callbacks = *stream_callbacks_ptr; if (stream_callbacks->on_error.has_value()) { @@ -71,10 +67,9 @@ void* c_on_error(envoy_error raw_error, envoy_stream_intel intel, } release_envoy_error(raw_error); delete stream_callbacks_ptr; - return nullptr; } -void* c_on_complete(envoy_stream_intel intel, envoy_final_stream_intel final_intel, void* context) { +void c_on_complete(envoy_stream_intel intel, envoy_final_stream_intel final_intel, void* context) { auto stream_callbacks_ptr = static_cast(context); auto stream_callbacks = *stream_callbacks_ptr; if (stream_callbacks->on_complete.has_value()) { @@ -82,10 +77,9 @@ void* c_on_complete(envoy_stream_intel intel, envoy_final_stream_intel final_int on_complete(intel, final_intel); } delete stream_callbacks_ptr; - return nullptr; } -void* c_on_cancel(envoy_stream_intel intel, envoy_final_stream_intel final_intel, void* context) { +void c_on_cancel(envoy_stream_intel intel, envoy_final_stream_intel final_intel, void* context) { auto stream_callbacks_ptr = static_cast(context); auto stream_callbacks = *stream_callbacks_ptr; if (stream_callbacks->on_cancel.has_value()) { @@ -93,17 +87,15 @@ void* c_on_cancel(envoy_stream_intel intel, envoy_final_stream_intel final_intel on_cancel(intel, final_intel); } delete stream_callbacks_ptr; - return nullptr; } -void* c_on_send_window_available(envoy_stream_intel intel, void* context) { +void c_on_send_window_available(envoy_stream_intel intel, void* context) { auto stream_callbacks_ptr = static_cast(context); auto stream_callbacks = *stream_callbacks_ptr; if (stream_callbacks->on_send_window_available.has_value()) { auto on_send_window_available = stream_callbacks->on_send_window_available.value(); on_send_window_available(intel); } - return nullptr; } } // namespace diff --git a/mobile/library/common/types/c_types.h b/mobile/library/common/types/c_types.h index 1fa9ab779509..c942e11d298f 100644 --- a/mobile/library/common/types/c_types.h +++ b/mobile/library/common/types/c_types.h @@ -322,10 +322,9 @@ extern "C" { // function pointers * @param stream_intel, contains internal stream metrics, context, and other details. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. - * @return void*, return context (may be unused). */ -typedef void* (*envoy_on_headers_f)(envoy_headers headers, bool end_stream, - envoy_stream_intel stream_intel, void* context); +typedef void (*envoy_on_headers_f)(envoy_headers headers, bool end_stream, + envoy_stream_intel stream_intel, void* context); /** * Callback signature for data on an HTTP stream. @@ -337,10 +336,9 @@ typedef void* (*envoy_on_headers_f)(envoy_headers headers, bool end_stream, * @param stream_intel, contains internal stream metrics, context, and other details. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. - * @return void*, return context (may be unused). */ -typedef void* (*envoy_on_data_f)(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, - void* context); +typedef void (*envoy_on_data_f)(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, + void* context); /** * Callback signature for metadata on an HTTP stream. @@ -353,8 +351,8 @@ typedef void* (*envoy_on_data_f)(envoy_data data, bool end_stream, envoy_stream_ * execution. * @return void*, return context (may be unused). */ -typedef void* (*envoy_on_metadata_f)(envoy_headers metadata, envoy_stream_intel stream_intel, - void* context); +typedef void (*envoy_on_metadata_f)(envoy_headers metadata, envoy_stream_intel stream_intel, + void* context); /** * Callback signature for trailers on an HTTP stream. @@ -365,10 +363,9 @@ typedef void* (*envoy_on_metadata_f)(envoy_headers metadata, envoy_stream_intel * @param stream_intel, contains internal stream metrics, context, and other details. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. - * @return void*, return context (may be unused). */ -typedef void* (*envoy_on_trailers_f)(envoy_headers trailers, envoy_stream_intel stream_intel, - void* context); +typedef void (*envoy_on_trailers_f)(envoy_headers trailers, envoy_stream_intel stream_intel, + void* context); /** * Callback signature for errors with an HTTP stream. @@ -380,10 +377,9 @@ typedef void* (*envoy_on_trailers_f)(envoy_headers trailers, envoy_stream_intel * @param final_stream_intel, contains final internal stream metrics, context, and other details. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. - * @return void*, return context (may be unused). */ -typedef void* (*envoy_on_error_f)(envoy_error error, envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context); +typedef void (*envoy_on_error_f)(envoy_error error, envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context); /** * Callback signature for when an HTTP stream bi-directionally completes without error. @@ -394,10 +390,9 @@ typedef void* (*envoy_on_error_f)(envoy_error error, envoy_stream_intel stream_i * @param final_stream_intel, contains final internal stream metrics, context, and other details. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. - * @return void*, return context (may be unused). */ -typedef void* (*envoy_on_complete_f)(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context); +typedef void (*envoy_on_complete_f)(envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context); /** * Callback signature for when an HTTP stream is cancelled. @@ -408,10 +403,9 @@ typedef void* (*envoy_on_complete_f)(envoy_stream_intel stream_intel, * @param final_stream_intel, contains final internal stream metrics, context, and other details. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. - * @return void*, return context (may be unused). */ -typedef void* (*envoy_on_cancel_f)(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context); +typedef void (*envoy_on_cancel_f)(envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context); /** * Called when the envoy engine is exiting. @@ -456,9 +450,8 @@ typedef void (*envoy_logger_release_f)(const void* context); * @param stream_intel, contains internal stream metrics, context, and other details. * @param context, contains the necessary state to carry out platform-specific dispatch and * execution. - * @return void*, return context (may be unused). */ -typedef void* (*envoy_on_send_window_available_f)(envoy_stream_intel stream_intel, void* context); +typedef void (*envoy_on_send_window_available_f)(envoy_stream_intel stream_intel, void* context); /** * Called when envoy's event tracker tracks an event. diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index b0d306744660..0e929f40246c 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -277,11 +277,10 @@ jvm_on_headers(const char* method, const Envoy::Types::ManagedEnvoyHeaders& head return noopResult; } -static void* jvm_on_response_headers(envoy_headers headers, bool end_stream, - envoy_stream_intel stream_intel, void* context) { +static void jvm_on_response_headers(envoy_headers headers, bool end_stream, + envoy_stream_intel stream_intel, void* context) { const auto managed_headers = Envoy::Types::ManagedEnvoyHeaders(headers); - return jvm_on_headers("onResponseHeaders", managed_headers, end_stream, stream_intel, context) - .release(); + jvm_on_headers("onResponseHeaders", managed_headers, end_stream, stream_intel, context); } static envoy_filter_headers_status @@ -360,9 +359,9 @@ static Envoy::JNI::LocalRefUniquePtr jvm_on_data(const char* metho return result; } -static void* jvm_on_response_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, - void* context) { - return jvm_on_data("onResponseData", data, end_stream, stream_intel, context).release(); +static void jvm_on_response_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, + void* context) { + jvm_on_data("onResponseData", data, end_stream, stream_intel, context); } static envoy_filter_data_status jvm_http_filter_on_request_data(envoy_data data, bool end_stream, @@ -433,11 +432,10 @@ static envoy_filter_data_status jvm_http_filter_on_response_data(envoy_data data /*pending_headers*/ pending_headers}; } -static void* jvm_on_metadata(envoy_headers metadata, envoy_stream_intel /*stream_intel*/, - void* /*context*/) { +static void jvm_on_metadata(envoy_headers metadata, envoy_stream_intel /*stream_intel*/, + void* /*context*/) { jni_log("[Envoy]", "jvm_on_metadata"); jni_log("[Envoy]", std::to_string(metadata.length).c_str()); - return nullptr; } static Envoy::JNI::LocalRefUniquePtr jvm_on_trailers(const char* method, @@ -465,9 +463,9 @@ static Envoy::JNI::LocalRefUniquePtr jvm_on_trailers(const char* m return result; } -static void* jvm_on_response_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, - void* context) { - return jvm_on_trailers("onResponseTrailers", trailers, stream_intel, context).release(); +static void jvm_on_response_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, + void* context) { + jvm_on_trailers("onResponseTrailers", trailers, stream_intel, context); } static envoy_filter_trailers_status @@ -666,8 +664,8 @@ jvm_http_filter_on_resume_response(envoy_headers* headers, envoy_data* data, stream_intel, context); } -static void* call_jvm_on_complete(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { +static void call_jvm_on_complete(envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context) { jni_log("[Envoy]", "jvm_on_complete"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); @@ -682,16 +680,12 @@ static void* call_jvm_on_complete(envoy_stream_intel stream_intel, Envoy::JNI::envoyStreamIntelToJavaLongArray(jni_helper, stream_intel); Envoy::JNI::LocalRefUniquePtr j_final_stream_intel = Envoy::JNI::envoyFinalStreamIntelToJavaLongArray(jni_helper, final_stream_intel); - jobject result = jni_helper - .callObjectMethod(j_context, jmid_onComplete, j_stream_intel.get(), - j_final_stream_intel.get()) - .release(); - - return result; + Envoy::JNI::LocalRefUniquePtr unused = jni_helper.callObjectMethod( + j_context, jmid_onComplete, j_stream_intel.get(), j_final_stream_intel.get()); } -static void* call_jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { +static void call_jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context) { jni_log("[Envoy]", "jvm_on_error"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); jobject j_context = static_cast(context); @@ -708,25 +702,21 @@ static void* call_jvm_on_error(envoy_error error, envoy_stream_intel stream_inte Envoy::JNI::LocalRefUniquePtr j_final_stream_intel = Envoy::JNI::envoyFinalStreamIntelToJavaLongArray(jni_helper, final_stream_intel); - jobject result = - jni_helper - .callObjectMethod(j_context, jmid_onError, error.error_code, j_error_message.get(), - error.attempt_count, j_stream_intel.get(), j_final_stream_intel.get()) - .release(); + Envoy::JNI::LocalRefUniquePtr unused = jni_helper.callObjectMethod( + j_context, jmid_onError, error.error_code, j_error_message.get(), error.attempt_count, + j_stream_intel.get(), j_final_stream_intel.get()); release_envoy_error(error); - return result; } -static void* jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { - void* result = call_jvm_on_error(error, stream_intel, final_stream_intel, context); +static void jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context) { + call_jvm_on_error(error, stream_intel, final_stream_intel, context); Envoy::JNI::jniDeleteGlobalRef(context); - return result; } -static void* call_jvm_on_cancel(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { +static void call_jvm_on_cancel(envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context) { jni_log("[Envoy]", "jvm_on_cancel"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); @@ -742,26 +732,20 @@ static void* call_jvm_on_cancel(envoy_stream_intel stream_intel, Envoy::JNI::LocalRefUniquePtr j_final_stream_intel = Envoy::JNI::envoyFinalStreamIntelToJavaLongArray(jni_helper, final_stream_intel); - jobject result = jni_helper - .callObjectMethod(j_context, jmid_onCancel, j_stream_intel.get(), - j_final_stream_intel.get()) - .release(); - - return result; + Envoy::JNI::LocalRefUniquePtr unused = jni_helper.callObjectMethod( + j_context, jmid_onCancel, j_stream_intel.get(), j_final_stream_intel.get()); } -static void* jvm_on_complete(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { - void* result = call_jvm_on_complete(stream_intel, final_stream_intel, context); +static void jvm_on_complete(envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context) { + call_jvm_on_complete(stream_intel, final_stream_intel, context); Envoy::JNI::jniDeleteGlobalRef(context); - return result; } -static void* jvm_on_cancel(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void* context) { - void* result = call_jvm_on_cancel(stream_intel, final_stream_intel, context); +static void jvm_on_cancel(envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void* context) { + call_jvm_on_cancel(stream_intel, final_stream_intel, context); Envoy::JNI::jniDeleteGlobalRef(context); - return result; } static void jvm_http_filter_on_error(envoy_error error, envoy_stream_intel stream_intel, @@ -776,7 +760,7 @@ static void jvm_http_filter_on_cancel(envoy_stream_intel stream_intel, call_jvm_on_cancel(stream_intel, final_stream_intel, const_cast(context)); } -static void* jvm_on_send_window_available(envoy_stream_intel stream_intel, void* context) { +static void jvm_on_send_window_available(envoy_stream_intel stream_intel, void* context) { jni_log("[Envoy]", "jvm_on_send_window_available"); Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); @@ -790,11 +774,8 @@ static void* jvm_on_send_window_available(envoy_stream_intel stream_intel, void* Envoy::JNI::LocalRefUniquePtr j_stream_intel = Envoy::JNI::envoyStreamIntelToJavaLongArray(jni_helper, stream_intel); - jobject result = - jni_helper.callObjectMethod(j_context, jmid_onSendWindowAvailable, j_stream_intel.get()) - .release(); - - return result; + Envoy::JNI::LocalRefUniquePtr unused = + jni_helper.callObjectMethod(j_context, jmid_onSendWindowAvailable, j_stream_intel.get()); } // JvmKeyValueStoreContext diff --git a/mobile/library/objective-c/EnvoyHTTPStreamImpl.mm b/mobile/library/objective-c/EnvoyHTTPStreamImpl.mm index 732eeac0e4c4..d83137c4af33 100644 --- a/mobile/library/objective-c/EnvoyHTTPStreamImpl.mm +++ b/mobile/library/objective-c/EnvoyHTTPStreamImpl.mm @@ -21,8 +21,8 @@ #pragma mark - C callbacks -static void *ios_on_headers(envoy_headers headers, bool end_stream, envoy_stream_intel stream_intel, - void *context) { +static void ios_on_headers(envoy_headers headers, bool end_stream, envoy_stream_intel stream_intel, + void *context) { ios_context *c = (ios_context *)context; EnvoyHTTPCallbacks *callbacks = c->callbacks; dispatch_async(callbacks.dispatchQueue, ^{ @@ -30,11 +30,10 @@ callbacks.onHeaders(to_ios_headers(headers), end_stream, stream_intel); } }); - return NULL; } -static void *ios_on_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, - void *context) { +static void ios_on_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, + void *context) { ios_context *c = (ios_context *)context; EnvoyHTTPCallbacks *callbacks = c->callbacks; dispatch_async(callbacks.dispatchQueue, ^{ @@ -42,16 +41,13 @@ callbacks.onData(to_ios_data(data), end_stream, stream_intel); } }); - return NULL; } -static void *ios_on_metadata(envoy_headers metadata, envoy_stream_intel stream_intel, - void *context) { - return NULL; -} +static void ios_on_metadata(envoy_headers metadata, envoy_stream_intel stream_intel, + void *context) {} -static void *ios_on_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, - void *context) { +static void ios_on_trailers(envoy_headers trailers, envoy_stream_intel stream_intel, + void *context) { ios_context *c = (ios_context *)context; EnvoyHTTPCallbacks *callbacks = c->callbacks; dispatch_async(callbacks.dispatchQueue, ^{ @@ -59,10 +55,9 @@ callbacks.onTrailers(to_ios_headers(trailers), stream_intel); } }); - return NULL; } -static void *ios_on_send_window_available(envoy_stream_intel stream_intel, void *context) { +static void ios_on_send_window_available(envoy_stream_intel stream_intel, void *context) { ios_context *c = (ios_context *)context; EnvoyHTTPCallbacks *callbacks = c->callbacks; dispatch_async(callbacks.dispatchQueue, ^{ @@ -70,11 +65,10 @@ callbacks.onSendWindowAvailable(stream_intel); } }); - return NULL; } -static void *ios_on_complete(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void *context) { +static void ios_on_complete(envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void *context) { ios_context *c = (ios_context *)context; EnvoyHTTPCallbacks *callbacks = c->callbacks; EnvoyHTTPStreamImpl *stream = c->stream; @@ -86,11 +80,10 @@ assert(stream); [stream cleanUp]; }); - return NULL; } -static void *ios_on_cancel(envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void *context) { +static void ios_on_cancel(envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void *context) { // This call is atomically gated at the call-site and will only happen once. It may still fire // after a complete response or error callback, but no other callbacks for the stream will ever // fire AFTER the cancellation callback. @@ -106,11 +99,10 @@ assert(stream); [stream cleanUp]; }); - return NULL; } -static void *ios_on_error(envoy_error error, envoy_stream_intel stream_intel, - envoy_final_stream_intel final_stream_intel, void *context) { +static void ios_on_error(envoy_error error, envoy_stream_intel stream_intel, + envoy_final_stream_intel final_stream_intel, void *context) { ios_context *c = (ios_context *)context; EnvoyHTTPCallbacks *callbacks = c->callbacks; EnvoyHTTPStreamImpl *stream = c->stream; @@ -128,7 +120,6 @@ assert(stream); [stream cleanUp]; }); - return NULL; } #pragma mark - EnvoyHTTPStreamImpl diff --git a/mobile/test/common/http/client_test.cc b/mobile/test/common/http/client_test.cc index beaf5f3ef936..7ba8ba41bf43 100644 --- a/mobile/test/common/http/client_test.cc +++ b/mobile/test/common/http/client_test.cc @@ -78,52 +78,45 @@ class ClientTest : public testing::TestWithParam { // Set up default bridge callbacks. Indivividual tests can override. bridge_callbacks_.on_complete = [](envoy_stream_intel, envoy_final_stream_intel, - void* context) -> void* { + void* context) -> void { callbacks_called* cc = static_cast(context); cc->on_complete_calls++; - return nullptr; }; bridge_callbacks_.on_headers = [](envoy_headers c_headers, bool end_stream, envoy_stream_intel, - void* context) -> void* { + void* context) -> void { ResponseHeaderMapPtr response_headers = toResponseHeaders(c_headers); callbacks_called* cc = static_cast(context); EXPECT_EQ(end_stream, cc->end_stream_with_headers_); EXPECT_EQ(response_headers->Status()->value().getStringView(), cc->expected_status_); cc->on_headers_calls++; - return nullptr; }; bridge_callbacks_.on_error = [](envoy_error, envoy_stream_intel, envoy_final_stream_intel, - void* context) -> void* { + void* context) -> void { callbacks_called* cc = static_cast(context); cc->on_error_calls++; - return nullptr; }; bridge_callbacks_.on_data = [](envoy_data c_data, bool, envoy_stream_intel, - void* context) -> void* { + void* context) -> void { callbacks_called* cc = static_cast(context); cc->on_data_calls++; cc->body_data_ += Data::Utility::copyToString(c_data); release_envoy_data(c_data); - return nullptr; }; bridge_callbacks_.on_cancel = [](envoy_stream_intel, envoy_final_stream_intel, - void* context) -> void* { + void* context) -> void { callbacks_called* cc = static_cast(context); cc->on_cancel_calls++; - return nullptr; }; - bridge_callbacks_.on_send_window_available = [](envoy_stream_intel, void* context) -> void* { + bridge_callbacks_.on_send_window_available = [](envoy_stream_intel, void* context) -> void { callbacks_called* cc = static_cast(context); cc->on_send_window_available_calls++; - return nullptr; }; bridge_callbacks_.on_trailers = [](envoy_headers c_trailers, envoy_stream_intel, - void* context) -> void* { + void* context) -> void { ResponseHeaderMapPtr response_trailers = toResponseHeaders(c_trailers); EXPECT_TRUE(response_trailers.get() != nullptr); callbacks_called* cc = static_cast(context); cc->on_trailers_calls++; - return nullptr; }; helper_handle_ = test::SystemHelperPeer::replaceSystemHelper(); EXPECT_CALL(helper_handle_->mock_helper(), isCleartextPermitted(_)) @@ -212,13 +205,12 @@ TEST_P(ClientTest, BasicStreamData) { cc_.end_stream_with_headers_ = false; bridge_callbacks_.on_data = [](envoy_data c_data, bool end_stream, envoy_stream_intel, - void* context) -> void* { + void* context) -> void { EXPECT_TRUE(end_stream); EXPECT_EQ(Data::Utility::copyToString(c_data), "response body"); callbacks_called* cc = static_cast(context); cc->on_data_calls++; release_envoy_data(c_data); - return nullptr; }; // Build body data @@ -249,13 +241,12 @@ TEST_P(ClientTest, BasicStreamData) { TEST_P(ClientTest, BasicStreamTrailers) { bridge_callbacks_.on_trailers = [](envoy_headers c_trailers, envoy_stream_intel, - void* context) -> void* { + void* context) -> void { ResponseHeaderMapPtr response_trailers = toResponseHeaders(c_trailers); EXPECT_EQ(response_trailers->get(LowerCaseString("x-test-trailer"))[0]->value().getStringView(), "test_trailer"); callbacks_called* cc = static_cast(context); cc->on_trailers_calls++; - return nullptr; }; // Build a set of request trailers. @@ -419,19 +410,17 @@ TEST_P(ClientTest, MultipleStreams) { callbacks_called cc2 = {0, 0, 0, 0, 0, 0, 0, "200", true, ""}; bridge_callbacks_2.context = &cc2; bridge_callbacks_2.on_headers = [](envoy_headers c_headers, bool end_stream, envoy_stream_intel, - void* context) -> void* { + void* context) -> void { EXPECT_TRUE(end_stream); ResponseHeaderMapPtr response_headers = toResponseHeaders(c_headers); EXPECT_EQ(response_headers->Status()->value().getStringView(), "200"); bool* on_headers_called2 = static_cast(context); *on_headers_called2 = true; - return nullptr; }; bridge_callbacks_2.on_complete = [](envoy_stream_intel, envoy_final_stream_intel, - void* context) -> void* { + void* context) -> void { callbacks_called* cc = static_cast(context); cc->on_complete_calls++; - return nullptr; }; envoy_headers c_headers2 = defaultRequestHeaders(); @@ -478,13 +467,12 @@ TEST_P(ClientTest, MultipleStreams) { TEST_P(ClientTest, EnvoyLocalError) { // Override the on_error default with some custom checks. bridge_callbacks_.on_error = [](envoy_error error, envoy_stream_intel, envoy_final_stream_intel, - void* context) -> void* { + void* context) -> void { EXPECT_EQ(error.error_code, ENVOY_CONNECTION_FAILURE); EXPECT_EQ(error.attempt_count, 123); callbacks_called* cc = static_cast(context); cc->on_error_calls++; release_envoy_error(error); - return nullptr; }; // Create a stream, and set up request_decoder_ and response_encoder_ @@ -552,7 +540,7 @@ TEST_P(ClientTest, RemoteResetAfterStreamStart) { cc_.end_stream_with_headers_ = false; bridge_callbacks_.on_error = [](envoy_error error, envoy_stream_intel, envoy_final_stream_intel, - void* context) -> void* { + void* context) -> void { EXPECT_EQ(error.error_code, ENVOY_STREAM_RESET); EXPECT_EQ(error.message.length, 0); EXPECT_EQ(error.attempt_count, 0); @@ -560,7 +548,6 @@ TEST_P(ClientTest, RemoteResetAfterStreamStart) { release_envoy_error(error); callbacks_called* cc = static_cast(context); cc->on_error_calls++; - return nullptr; }; // Create a stream, and set up request_decoder_ and response_encoder_ diff --git a/mobile/test/common/main_interface_test.cc b/mobile/test/common/main_interface_test.cc index 62d002cd341d..32bdf1521da8 100644 --- a/mobile/test/common/main_interface_test.cc +++ b/mobile/test/common/main_interface_test.cc @@ -167,20 +167,18 @@ TEST_F(MainInterfaceTest, BasicStream) { absl::Notification on_complete_notification; envoy_http_callbacks stream_cbs{ - [](envoy_headers c_headers, bool end_stream, envoy_stream_intel, void*) -> void* { + [](envoy_headers c_headers, bool end_stream, envoy_stream_intel, void*) -> void { auto response_headers = toResponseHeaders(c_headers); EXPECT_EQ(response_headers->Status()->value().getStringView(), "200"); EXPECT_TRUE(end_stream); - return nullptr; } /* on_headers */, nullptr /* on_data */, nullptr /* on_metadata */, nullptr /* on_trailers */, nullptr /* on_error */, - [](envoy_stream_intel, envoy_final_stream_intel, void* context) -> void* { + [](envoy_stream_intel, envoy_final_stream_intel, void* context) -> void { auto* on_complete_notification = static_cast(context); on_complete_notification->Notify(); - return nullptr; } /* on_complete */, nullptr /* on_cancel */, nullptr /* on_send_window_available*/, @@ -239,10 +237,9 @@ TEST_F(MainInterfaceTest, ResetStream) { nullptr /* on_trailers */, nullptr /* on_error */, nullptr /* on_complete */, - [](envoy_stream_intel, envoy_final_stream_intel, void* context) -> void* { + [](envoy_stream_intel, envoy_final_stream_intel, void* context) -> void { auto* on_cancel_notification = static_cast(context); on_cancel_notification->Notify(); - return nullptr; } /* on_cancel */, nullptr /* on_send_window_available */, &on_cancel_notification /* context */}; From ea716cec74023727cc0cd2801864445afc1c81b5 Mon Sep 17 00:00:00 2001 From: Fredy Wijaya Date: Thu, 22 Feb 2024 14:53:42 -0600 Subject: [PATCH 074/151] mobile: Move tests in main_interface_test.cc to internal_engine_test.cc (#32526) Signed-off-by: Fredy Wijaya --- mobile/test/common/BUILD | 14 - mobile/test/common/internal_engine_test.cc | 539 ++++++++++++++++++++- mobile/test/common/main_interface_test.cc | 531 -------------------- 3 files changed, 522 insertions(+), 562 deletions(-) delete mode 100644 mobile/test/common/main_interface_test.cc diff --git a/mobile/test/common/BUILD b/mobile/test/common/BUILD index cc3f3f7a2009..91686f69ecef 100644 --- a/mobile/test/common/BUILD +++ b/mobile/test/common/BUILD @@ -25,20 +25,6 @@ envoy_cc_test( repository = "@envoy", deps = [ "//library/cc:engine_builder_lib", - "//library/common:internal_engine_lib_no_stamp", - "//library/common/types:c_types_lib", - "@envoy//test/common/http:common_lib", - ], -) - -envoy_cc_test( - name = "main_interface_test", - srcs = envoy_select_enable_yaml( - ["main_interface_test.cc"], - "@envoy", - ), - repository = "@envoy", - deps = [ "//library/common:internal_engine_lib_no_stamp", "//library/common/data:utility_lib", "//library/common/http:header_utility_lib", diff --git a/mobile/test/common/internal_engine_test.cc b/mobile/test/common/internal_engine_test.cc index 57586f328e15..c31f8ffc72ae 100644 --- a/mobile/test/common/internal_engine_test.cc +++ b/mobile/test/common/internal_engine_test.cc @@ -1,27 +1,129 @@ +#include "source/common/common/assert.h" + +#include "test/common/http/common.h" +#include "test/common/mocks/common/mocks.h" + #include "absl/synchronization/notification.h" #include "gtest/gtest.h" #include "library/cc/engine_builder.h" +#include "library/common/api/external.h" +#include "library/common/bridge/utility.h" +#include "library/common/data/utility.h" +#include "library/common/http/header_utility.h" #include "library/common/internal_engine.h" namespace Envoy { +using testing::_; +using testing::HasSubstr; +using testing::Return; +using testing::ReturnRef; + +// This config is the minimal envoy mobile config that allows for running the engine. +const std::string MINIMAL_TEST_CONFIG = R"( +listener_manager: + name: envoy.listener_manager_impl.api + typed_config: + "@type": type.googleapis.com/envoy.config.listener.v3.ApiListenerManager +static_resources: + listeners: + - name: base_api_listener + address: + socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 10000 } + api_listener: + api_listener: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager + config: + stat_prefix: hcm + route_config: + name: api_router + virtual_hosts: + - name: api + include_attempt_count_in_response: true + domains: ["*"] + routes: + - match: { prefix: "/" } + route: + cluster_header: x-envoy-mobile-cluster + retry_policy: + retry_back_off: { base_interval: 0.25s, max_interval: 60s } + http_filters: + - name: envoy.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router +layered_runtime: + layers: + - name: static_layer_0 + static_layer: + overload: { global_downstream_max_connections: 50000 } +)"; + +const std::string BUFFERED_TEST_CONFIG = R"( +listener_manager: + name: envoy.listener_manager_impl.api + typed_config: + "@type": type.googleapis.com/envoy.config.listener.v3.ApiListenerManager +static_resources: + listeners: + - name: base_api_listener + address: + socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 10000 } + api_listener: + api_listener: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager + config: + stat_prefix: hcm + route_config: + name: api_router + virtual_hosts: + - name: api + include_attempt_count_in_response: true + domains: ["*"] + routes: + - match: { prefix: "/" } + direct_response: { status: 200 } + http_filters: + - name: buffer + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer + max_request_bytes: 65000 + - name: envoy.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router +layered_runtime: + layers: + - name: static_layer_0 + static_layer: + overload: { global_downstream_max_connections: 50000 } +)"; + +const std::string LEVEL_DEBUG = "debug"; + +struct EngineTestContext { + absl::Notification on_engine_running; + absl::Notification on_exit; + absl::Notification on_log; + absl::Notification on_logger_release; + absl::Notification on_event; +}; + // RAII wrapper for the engine, ensuring that we properly shut down the engine. If the engine // thread is not torn down, we end up with TSAN failures during shutdown due to a data race // between the main thread and the engine thread both writing to the // Envoy::Logger::current_log_context global. struct TestEngine { std::unique_ptr engine_; - envoy_engine_t handle() { return reinterpret_cast(engine_.get()); } + envoy_engine_t handle() const { return reinterpret_cast(engine_.get()); } TestEngine(envoy_engine_callbacks callbacks, const std::string& level) { engine_.reset(new Envoy::InternalEngine(callbacks, {}, {})); Platform::EngineBuilder builder; auto bootstrap = builder.generateBootstrap(); std::string yaml = Envoy::MessageUtil::getYamlStringFromMessage(*bootstrap); - engine_->run(yaml.c_str(), level.c_str()); + engine_->run(yaml, level); } - envoy_status_t terminate() { return engine_->terminate(); } - bool isTerminated() const { return engine_->isTerminated(); } + envoy_status_t terminate() const { return engine_->terminate(); } + [[nodiscard]] bool isTerminated() const { return engine_->isTerminated(); } ~TestEngine() { if (!engine_->isTerminated()) { @@ -30,27 +132,58 @@ struct TestEngine { } }; -class EngineTest : public testing::Test { +// Transform C map to C++ map. +[[maybe_unused]] static inline std::map toMap(envoy_map map) { + std::map new_map; + for (envoy_map_size_t i = 0; i < map.length; i++) { + envoy_map_entry header = map.entries[i]; + const auto key = Data::Utility::copyToString(header.key); + const auto value = Data::Utility::copyToString(header.value); + new_map.emplace(key, value); + } + + release_envoy_map(map); + return new_map; +} + +// Based on Http::Utility::toRequestHeaders() but only used for these tests. +Http::ResponseHeaderMapPtr toResponseHeaders(envoy_headers headers) { + Http::ResponseHeaderMapPtr transformed_headers = Http::ResponseHeaderMapImpl::create(); + for (envoy_map_size_t i = 0; i < headers.length; i++) { + transformed_headers->addCopy( + Http::LowerCaseString(Data::Utility::copyToString(headers.entries[i].key)), + Data::Utility::copyToString(headers.entries[i].value)); + } + // The C envoy_headers struct can be released now because the headers have been copied. + release_envoy_headers(headers); + return transformed_headers; +} + +class InternalEngineTest : public testing::Test { public: + void SetUp() override { + helper_handle_ = test::SystemHelperPeer::replaceSystemHelper(); + EXPECT_CALL(helper_handle_->mock_helper(), isCleartextPermitted(_)) + .WillRepeatedly(Return(true)); + } + std::unique_ptr engine_; -}; -typedef struct { - absl::Notification on_engine_running; - absl::Notification on_exit; -} engine_test_context; +private: + std::unique_ptr helper_handle_; +}; -TEST_F(EngineTest, EarlyExit) { +TEST_F(InternalEngineTest, EarlyExit) { const std::string level = "debug"; - engine_test_context test_context{}; + EngineTestContext test_context{}; envoy_engine_callbacks callbacks{[](void* context) -> void { auto* engine_running = - static_cast(context); + static_cast(context); engine_running->on_engine_running.Notify(); } /*on_engine_running*/, [](void* context) -> void { - auto* exit = static_cast(context); + auto* exit = static_cast(context); exit->on_exit.Notify(); } /*on_exit*/, &test_context /*context*/}; @@ -67,13 +200,13 @@ TEST_F(EngineTest, EarlyExit) { engine_.reset(); } -TEST_F(EngineTest, AccessEngineAfterInitialization) { +TEST_F(InternalEngineTest, AccessEngineAfterInitialization) { const std::string level = "debug"; - engine_test_context test_context{}; + EngineTestContext test_context{}; envoy_engine_callbacks callbacks{[](void* context) -> void { auto* engine_running = - static_cast(context); + static_cast(context); engine_running->on_engine_running.Notify(); } /*on_engine_running*/, [](void*) -> void {} /*on_exit*/, &test_context /*context*/}; @@ -95,4 +228,376 @@ TEST_F(EngineTest, AccessEngineAfterInitialization) { engine_.reset(); } +TEST_F(InternalEngineTest, RecordCounter) { + EngineTestContext test_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* engine_running = + static_cast(context); + engine_running->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* exit = static_cast(context); + exit->on_exit.Notify(); + } /*on_exit*/, + &test_context /*context*/}; + std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); + + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); + EXPECT_EQ(ENVOY_SUCCESS, engine->recordCounterInc("counter", envoy_stats_notags, 1)); + + engine->terminate(); + ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); +} + +TEST_F(InternalEngineTest, Logger) { + EngineTestContext test_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_exit.Notify(); + } /*on_exit*/, + &test_context /*context*/}; + + envoy_logger logger{[](envoy_log_level, envoy_data data, const void* context) -> void { + auto* test_context = + static_cast(const_cast(context)); + release_envoy_data(data); + if (!test_context->on_log.HasBeenNotified()) { + test_context->on_log.Notify(); + } + } /* log */, + [](const void* context) -> void { + auto* test_context = + static_cast(const_cast(context)); + test_context->on_logger_release.Notify(); + } /* release */, + &test_context}; + std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, logger, {})); + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); + + ASSERT_TRUE(test_context.on_log.WaitForNotificationWithTimeout(absl::Seconds(3))); + + engine->terminate(); + engine.reset(); + ASSERT_TRUE(test_context.on_logger_release.WaitForNotificationWithTimeout(absl::Seconds(3))); + ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); +} + +TEST_F(InternalEngineTest, EventTrackerRegistersDefaultAPI) { + EngineTestContext test_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_exit.Notify(); + } /*on_exit*/, + &test_context /*context*/}; + + std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + + // A default event tracker is registered in external API registry. + const auto registered_event_tracker = + static_cast(Api::External::retrieveApi(envoy_event_tracker_api_name)); + EXPECT_TRUE(registered_event_tracker != nullptr); + EXPECT_TRUE(registered_event_tracker->track == nullptr); + EXPECT_TRUE(registered_event_tracker->context == nullptr); + + ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); + // Simulate a failed assertion by invoking a debug assertion failure + // record action. + // Verify that no crash if the assertion fails when no real event + // tracker is passed at engine's initialization time. + Assert::invokeDebugAssertionFailureRecordActionForAssertMacroUseOnly("foo_location"); + + engine->terminate(); + ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); +} + +TEST_F(InternalEngineTest, EventTrackerRegistersAPI) { + EngineTestContext test_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_exit.Notify(); + } /*on_exit*/, + &test_context /*context*/}; + envoy_event_tracker event_tracker{[](envoy_map map, const void* context) -> void { + const auto new_map = toMap(map); + if (new_map.count("foo") && new_map.at("foo") == "bar") { + auto* test_context = static_cast( + const_cast(context)); + test_context->on_event.Notify(); + } + } /*track*/, + &test_context /*context*/}; + + std::unique_ptr engine( + new Envoy::InternalEngine(engine_cbs, {}, event_tracker)); + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + + ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); + const auto registered_event_tracker = + static_cast(Api::External::retrieveApi(envoy_event_tracker_api_name)); + EXPECT_TRUE(registered_event_tracker != nullptr); + EXPECT_EQ(event_tracker.track, registered_event_tracker->track); + EXPECT_EQ(event_tracker.context, registered_event_tracker->context); + + event_tracker.track(Bridge::Utility::makeEnvoyMap({{"foo", "bar"}}), + registered_event_tracker->context); + + ASSERT_TRUE(test_context.on_event.WaitForNotificationWithTimeout(absl::Seconds(3))); + engine->terminate(); + ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); +} + +TEST_F(InternalEngineTest, EventTrackerRegistersAssertionFailureRecordAction) { + EngineTestContext test_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_exit.Notify(); + } /*on_exit*/, + &test_context /*context*/}; + + envoy_event_tracker event_tracker{ + [](envoy_map map, const void* context) -> void { + const auto new_map = toMap(map); + if (new_map.count("name") && new_map.at("name") == "assertion") { + EXPECT_EQ(new_map.at("location"), "foo_location"); + auto* test_context = static_cast(const_cast(context)); + test_context->on_event.Notify(); + } + } /*track*/, + &test_context /*context*/}; + + std::unique_ptr engine( + new Envoy::InternalEngine(engine_cbs, {}, event_tracker)); + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + + ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); + // Simulate a failed assertion by invoking a debug assertion failure + // record action. + // Verify that an envoy event is emitted when an event tracker is passed + // at engine's initialization time. + Assert::invokeDebugAssertionFailureRecordActionForAssertMacroUseOnly("foo_location"); + + ASSERT_TRUE(test_context.on_event.WaitForNotificationWithTimeout(absl::Seconds(3))); + engine->terminate(); + ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); +} + +TEST_F(InternalEngineTest, EventTrackerRegistersEnvoyBugRecordAction) { + EngineTestContext test_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* test_context = static_cast(context); + test_context->on_exit.Notify(); + } /*on_exit*/, + &test_context /*context*/}; + + envoy_event_tracker event_tracker{[](envoy_map map, const void* context) -> void { + const auto new_map = toMap(map); + if (new_map.count("name") && new_map.at("name") == "bug") { + EXPECT_EQ(new_map.at("location"), "foo_location"); + auto* test_context = static_cast( + const_cast(context)); + test_context->on_event.Notify(); + } + } /*track*/, + &test_context /*context*/}; + + std::unique_ptr engine( + new Envoy::InternalEngine(engine_cbs, {}, event_tracker)); + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + + ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); + // Simulate an envoy bug by invoking an Envoy bug failure + // record action. + // Verify that an envoy event is emitted when an event tracker is passed + // at engine's initialization time. + Assert::invokeEnvoyBugFailureRecordActionForEnvoyBugMacroUseOnly("foo_location"); + + ASSERT_TRUE(test_context.on_event.WaitForNotificationWithTimeout(absl::Seconds(3))); + engine->terminate(); + ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); +} + +TEST_F(InternalEngineTest, BasicStream) { + const std::string level = "debug"; + EngineTestContext engine_cbs_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* engine_running = + static_cast(context); + engine_running->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* exit = static_cast(context); + exit->on_exit.Notify(); + } /*on_exit*/, + &engine_cbs_context /*context*/}; + std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); + engine->run(BUFFERED_TEST_CONFIG, level); + + ASSERT_TRUE( + engine_cbs_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(10))); + + absl::Notification on_complete_notification; + envoy_http_callbacks stream_cbs{ + [](envoy_headers c_headers, bool end_stream, envoy_stream_intel, void*) -> void { + auto response_headers = toResponseHeaders(c_headers); + EXPECT_EQ(response_headers->Status()->value().getStringView(), "200"); + EXPECT_TRUE(end_stream); + } /* on_headers */, + nullptr /* on_data */, + nullptr /* on_metadata */, + nullptr /* on_trailers */, + nullptr /* on_error */, + [](envoy_stream_intel, envoy_final_stream_intel, void* context) -> void { + auto* on_complete_notification = static_cast(context); + on_complete_notification->Notify(); + } /* on_complete */, + nullptr /* on_cancel */, + nullptr /* on_send_window_available*/, + &on_complete_notification /* context */}; + Http::TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + envoy_headers c_headers = Http::Utility::toBridgeHeaders(headers); + + Buffer::OwnedImpl request_data = Buffer::OwnedImpl("request body"); + envoy_data c_data = Data::Utility::toBridgeData(request_data); + + Http::TestRequestTrailerMapImpl trailers; + envoy_headers c_trailers = Http::Utility::toBridgeHeaders(trailers); + + envoy_stream_t stream = engine->initStream(); + + engine->startStream(stream, stream_cbs, false); + + engine->sendHeaders(stream, c_headers, false); + engine->sendData(stream, c_data, false); + engine->sendTrailers(stream, c_trailers); + + ASSERT_TRUE(on_complete_notification.WaitForNotificationWithTimeout(absl::Seconds(10))); + + engine->terminate(); + + ASSERT_TRUE(engine_cbs_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(10))); +} + +TEST_F(InternalEngineTest, ResetStream) { + EngineTestContext engine_cbs_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* engine_running = + static_cast(context); + engine_running->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* exit = static_cast(context); + exit->on_exit.Notify(); + } /*on_exit*/, + &engine_cbs_context /*context*/}; + + // There is nothing functional about the config used to run the engine, as the created stream is + // immediately reset. + std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + + ASSERT_TRUE( + engine_cbs_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(10))); + + absl::Notification on_cancel_notification; + envoy_http_callbacks stream_cbs{ + nullptr /* on_headers */, + nullptr /* on_data */, + nullptr /* on_metadata */, + nullptr /* on_trailers */, + nullptr /* on_error */, + nullptr /* on_complete */, + [](envoy_stream_intel, envoy_final_stream_intel, void* context) -> void { + auto* on_cancel_notification = static_cast(context); + on_cancel_notification->Notify(); + } /* on_cancel */, + nullptr /* on_send_window_available */, + &on_cancel_notification /* context */}; + + envoy_stream_t stream = engine->initStream(); + + engine->startStream(stream, stream_cbs, false); + + engine->cancelStream(stream); + + ASSERT_TRUE(on_cancel_notification.WaitForNotificationWithTimeout(absl::Seconds(10))); + + engine->terminate(); + + ASSERT_TRUE(engine_cbs_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(10))); +} + +TEST_F(InternalEngineTest, RegisterPlatformApi) { + EngineTestContext engine_cbs_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* engine_running = + static_cast(context); + engine_running->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* exit = static_cast(context); + exit->on_exit.Notify(); + } /*on_exit*/, + &engine_cbs_context /*context*/}; + + // Using the minimal envoy mobile config that allows for running the engine. + std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + + ASSERT_TRUE( + engine_cbs_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(10))); + + uint64_t fake_api; + Envoy::Api::External::registerApi("api", &fake_api); + + engine->terminate(); + + ASSERT_TRUE(engine_cbs_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(10))); +} + +TEST_F(InternalEngineTest, ResetConnectivityState) { + EngineTestContext test_context{}; + envoy_engine_callbacks engine_cbs{[](void* context) -> void { + auto* engine_running = + static_cast(context); + engine_running->on_engine_running.Notify(); + } /*on_engine_running*/, + [](void* context) -> void { + auto* exit = static_cast(context); + exit->on_exit.Notify(); + } /*on_exit*/, + &test_context /*context*/}; + std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); + engine->run(MINIMAL_TEST_CONFIG, LEVEL_DEBUG); + ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); + + ASSERT_EQ(ENVOY_SUCCESS, engine->resetConnectivityState()); + + engine->terminate(); + ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); +} + } // namespace Envoy diff --git a/mobile/test/common/main_interface_test.cc b/mobile/test/common/main_interface_test.cc deleted file mode 100644 index 32bdf1521da8..000000000000 --- a/mobile/test/common/main_interface_test.cc +++ /dev/null @@ -1,531 +0,0 @@ -#include "source/common/common/assert.h" - -#include "test/common/http/common.h" -#include "test/common/mocks/common/mocks.h" - -#include "absl/synchronization/notification.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "library/common/api/external.h" -#include "library/common/bridge/utility.h" -#include "library/common/data/utility.h" -#include "library/common/http/header_utility.h" -#include "library/common/internal_engine.h" - -using testing::_; -using testing::HasSubstr; -using testing::Return; -using testing::ReturnRef; - -namespace Envoy { - -typedef struct { - absl::Notification on_engine_running; - absl::Notification on_exit; - absl::Notification on_log; - absl::Notification on_logger_release; - absl::Notification on_event; -} engine_test_context; - -// This config is the minimal envoy mobile config that allows for running the engine. -const std::string MINIMAL_TEST_CONFIG = R"( -listener_manager: - name: envoy.listener_manager_impl.api - typed_config: - "@type": type.googleapis.com/envoy.config.listener.v3.ApiListenerManager -static_resources: - listeners: - - name: base_api_listener - address: - socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 10000 } - api_listener: - api_listener: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager - config: - stat_prefix: hcm - route_config: - name: api_router - virtual_hosts: - - name: api - include_attempt_count_in_response: true - domains: ["*"] - routes: - - match: { prefix: "/" } - route: - cluster_header: x-envoy-mobile-cluster - retry_policy: - retry_back_off: { base_interval: 0.25s, max_interval: 60s } - http_filters: - - name: envoy.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router -layered_runtime: - layers: - - name: static_layer_0 - static_layer: - overload: { global_downstream_max_connections: 50000 } -)"; - -const std::string BUFFERED_TEST_CONFIG = R"( -listener_manager: - name: envoy.listener_manager_impl.api - typed_config: - "@type": type.googleapis.com/envoy.config.listener.v3.ApiListenerManager -static_resources: - listeners: - - name: base_api_listener - address: - socket_address: { protocol: TCP, address: 0.0.0.0, port_value: 10000 } - api_listener: - api_listener: - "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.EnvoyMobileHttpConnectionManager - config: - stat_prefix: hcm - route_config: - name: api_router - virtual_hosts: - - name: api - include_attempt_count_in_response: true - domains: ["*"] - routes: - - match: { prefix: "/" } - direct_response: { status: 200 } - http_filters: - - name: buffer - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.buffer.v3.Buffer - max_request_bytes: 65000 - - name: envoy.router - typed_config: - "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router -layered_runtime: - layers: - - name: static_layer_0 - static_layer: - overload: { global_downstream_max_connections: 50000 } -)"; - -const std::string LEVEL_DEBUG = "debug"; - -// Transform C map to C++ map. -[[maybe_unused]] static inline std::map toMap(envoy_map map) { - std::map new_map; - for (envoy_map_size_t i = 0; i < map.length; i++) { - envoy_map_entry header = map.entries[i]; - const auto key = Data::Utility::copyToString(header.key); - const auto value = Data::Utility::copyToString(header.value); - new_map.insert({std::move(key), std::move(value)}); - } - - release_envoy_map(map); - return new_map; -} - -// Based on Http::Utility::toRequestHeaders() but only used for these tests. -Http::ResponseHeaderMapPtr toResponseHeaders(envoy_headers headers) { - Http::ResponseHeaderMapPtr transformed_headers = Http::ResponseHeaderMapImpl::create(); - for (envoy_map_size_t i = 0; i < headers.length; i++) { - transformed_headers->addCopy( - Http::LowerCaseString(Data::Utility::copyToString(headers.entries[i].key)), - Data::Utility::copyToString(headers.entries[i].value)); - } - // The C envoy_headers struct can be released now because the headers have been copied. - release_envoy_headers(headers); - return transformed_headers; -} - -class MainInterfaceTest : public testing::Test { -public: - void SetUp() override { - helper_handle_ = test::SystemHelperPeer::replaceSystemHelper(); - EXPECT_CALL(helper_handle_->mock_helper(), isCleartextPermitted(_)) - .WillRepeatedly(Return(true)); - } - -protected: - std::unique_ptr helper_handle_; -}; - -TEST_F(MainInterfaceTest, BasicStream) { - const std::string level = "debug"; - engine_test_context engine_cbs_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* engine_running = - static_cast(context); - engine_running->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* exit = static_cast(context); - exit->on_exit.Notify(); - } /*on_exit*/, - &engine_cbs_context /*context*/}; - std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); - engine->run(BUFFERED_TEST_CONFIG.c_str(), level.c_str()); - - ASSERT_TRUE( - engine_cbs_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(10))); - - absl::Notification on_complete_notification; - envoy_http_callbacks stream_cbs{ - [](envoy_headers c_headers, bool end_stream, envoy_stream_intel, void*) -> void { - auto response_headers = toResponseHeaders(c_headers); - EXPECT_EQ(response_headers->Status()->value().getStringView(), "200"); - EXPECT_TRUE(end_stream); - } /* on_headers */, - nullptr /* on_data */, - nullptr /* on_metadata */, - nullptr /* on_trailers */, - nullptr /* on_error */, - [](envoy_stream_intel, envoy_final_stream_intel, void* context) -> void { - auto* on_complete_notification = static_cast(context); - on_complete_notification->Notify(); - } /* on_complete */, - nullptr /* on_cancel */, - nullptr /* on_send_window_available*/, - &on_complete_notification /* context */}; - Http::TestRequestHeaderMapImpl headers; - HttpTestUtility::addDefaultHeaders(headers); - envoy_headers c_headers = Http::Utility::toBridgeHeaders(headers); - - Buffer::OwnedImpl request_data = Buffer::OwnedImpl("request body"); - envoy_data c_data = Data::Utility::toBridgeData(request_data); - - Http::TestRequestTrailerMapImpl trailers; - envoy_headers c_trailers = Http::Utility::toBridgeHeaders(trailers); - - envoy_stream_t stream = engine->initStream(); - - engine->startStream(stream, stream_cbs, false); - - engine->sendHeaders(stream, c_headers, false); - engine->sendData(stream, c_data, false); - engine->sendTrailers(stream, c_trailers); - - ASSERT_TRUE(on_complete_notification.WaitForNotificationWithTimeout(absl::Seconds(10))); - - engine->terminate(); - - ASSERT_TRUE(engine_cbs_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(10))); -} - -TEST_F(MainInterfaceTest, ResetStream) { - engine_test_context engine_cbs_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* engine_running = - static_cast(context); - engine_running->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* exit = static_cast(context); - exit->on_exit.Notify(); - } /*on_exit*/, - &engine_cbs_context /*context*/}; - - // There is nothing functional about the config used to run the engine, as the created stream is - // immediately reset. - std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - - ASSERT_TRUE( - engine_cbs_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(10))); - - absl::Notification on_cancel_notification; - envoy_http_callbacks stream_cbs{ - nullptr /* on_headers */, - nullptr /* on_data */, - nullptr /* on_metadata */, - nullptr /* on_trailers */, - nullptr /* on_error */, - nullptr /* on_complete */, - [](envoy_stream_intel, envoy_final_stream_intel, void* context) -> void { - auto* on_cancel_notification = static_cast(context); - on_cancel_notification->Notify(); - } /* on_cancel */, - nullptr /* on_send_window_available */, - &on_cancel_notification /* context */}; - - envoy_stream_t stream = engine->initStream(); - - engine->startStream(stream, stream_cbs, false); - - engine->cancelStream(stream); - - ASSERT_TRUE(on_cancel_notification.WaitForNotificationWithTimeout(absl::Seconds(10))); - - engine->terminate(); - - ASSERT_TRUE(engine_cbs_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(10))); -} - -TEST_F(MainInterfaceTest, RegisterPlatformApi) { - engine_test_context engine_cbs_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* engine_running = - static_cast(context); - engine_running->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* exit = static_cast(context); - exit->on_exit.Notify(); - } /*on_exit*/, - &engine_cbs_context /*context*/}; - - // Using the minimal envoy mobile config that allows for running the engine. - std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - - ASSERT_TRUE( - engine_cbs_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(10))); - - uint64_t fake_api; - Envoy::Api::External::registerApi("api", &fake_api); - - engine->terminate(); - - ASSERT_TRUE(engine_cbs_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(10))); -} - -TEST(EngineTest, RecordCounter) { - engine_test_context test_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* engine_running = - static_cast(context); - engine_running->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* exit = static_cast(context); - exit->on_exit.Notify(); - } /*on_exit*/, - &test_context /*context*/}; - std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); - - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); - EXPECT_EQ(ENVOY_SUCCESS, engine->recordCounterInc("counter", envoy_stats_notags, 1)); - - engine->terminate(); - ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); -} - -TEST(EngineTest, Logger) { - engine_test_context test_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_exit.Notify(); - } /*on_exit*/, - &test_context /*context*/}; - - envoy_logger logger{[](envoy_log_level, envoy_data data, const void* context) -> void { - auto* test_context = - static_cast(const_cast(context)); - release_envoy_data(data); - if (!test_context->on_log.HasBeenNotified()) { - test_context->on_log.Notify(); - } - } /* log */, - [](const void* context) -> void { - auto* test_context = - static_cast(const_cast(context)); - test_context->on_logger_release.Notify(); - } /* release */, - &test_context}; - std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, logger, {})); - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); - - ASSERT_TRUE(test_context.on_log.WaitForNotificationWithTimeout(absl::Seconds(3))); - - engine->terminate(); - engine.reset(); - ASSERT_TRUE(test_context.on_logger_release.WaitForNotificationWithTimeout(absl::Seconds(3))); - ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); -} - -TEST(EngineTest, EventTrackerRegistersDefaultAPI) { - engine_test_context test_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_exit.Notify(); - } /*on_exit*/, - &test_context /*context*/}; - - std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - - // A default event tracker is registered in external API registry. - const auto registered_event_tracker = - static_cast(Api::External::retrieveApi(envoy_event_tracker_api_name)); - EXPECT_TRUE(registered_event_tracker != nullptr); - EXPECT_TRUE(registered_event_tracker->track == nullptr); - EXPECT_TRUE(registered_event_tracker->context == nullptr); - - ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); - // Simulate a failed assertion by invoking a debug assertion failure - // record action. - // Verify that no crash if the assertion fails when no real event - // tracker is passed at engine's initialization time. - Assert::invokeDebugAssertionFailureRecordActionForAssertMacroUseOnly("foo_location"); - - engine->terminate(); - ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); -} - -TEST(EngineTest, EventTrackerRegistersAPI) { - engine_test_context test_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_exit.Notify(); - } /*on_exit*/, - &test_context /*context*/}; - envoy_event_tracker event_tracker{[](envoy_map map, const void* context) -> void { - const auto new_map = toMap(map); - if (new_map.count("foo") && new_map.at("foo") == "bar") { - auto* test_context = static_cast( - const_cast(context)); - test_context->on_event.Notify(); - } - } /*track*/, - &test_context /*context*/}; - - std::unique_ptr engine( - new Envoy::InternalEngine(engine_cbs, {}, event_tracker)); - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - - ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); - const auto registered_event_tracker = - static_cast(Api::External::retrieveApi(envoy_event_tracker_api_name)); - EXPECT_TRUE(registered_event_tracker != nullptr); - EXPECT_EQ(event_tracker.track, registered_event_tracker->track); - EXPECT_EQ(event_tracker.context, registered_event_tracker->context); - - event_tracker.track(Bridge::Utility::makeEnvoyMap({{"foo", "bar"}}), - registered_event_tracker->context); - - ASSERT_TRUE(test_context.on_event.WaitForNotificationWithTimeout(absl::Seconds(3))); - engine->terminate(); - ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); -} - -TEST(EngineTest, EventTrackerRegistersAssertionFailureRecordAction) { - engine_test_context test_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_exit.Notify(); - } /*on_exit*/, - &test_context /*context*/}; - - envoy_event_tracker event_tracker{ - [](envoy_map map, const void* context) -> void { - const auto new_map = toMap(map); - if (new_map.count("name") && new_map.at("name") == "assertion") { - EXPECT_EQ(new_map.at("location"), "foo_location"); - auto* test_context = static_cast(const_cast(context)); - test_context->on_event.Notify(); - } - } /*track*/, - &test_context /*context*/}; - - std::unique_ptr engine( - new Envoy::InternalEngine(engine_cbs, {}, event_tracker)); - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - - ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); - // Simulate a failed assertion by invoking a debug assertion failure - // record action. - // Verify that an envoy event is emitted when an event tracker is passed - // at engine's initialization time. - Assert::invokeDebugAssertionFailureRecordActionForAssertMacroUseOnly("foo_location"); - - ASSERT_TRUE(test_context.on_event.WaitForNotificationWithTimeout(absl::Seconds(3))); - engine->terminate(); - ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); -} - -TEST(EngineTest, EventTrackerRegistersEnvoyBugRecordAction) { - engine_test_context test_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* test_context = - static_cast(context); - test_context->on_exit.Notify(); - } /*on_exit*/, - &test_context /*context*/}; - - envoy_event_tracker event_tracker{[](envoy_map map, const void* context) -> void { - const auto new_map = toMap(map); - if (new_map.count("name") && new_map.at("name") == "bug") { - EXPECT_EQ(new_map.at("location"), "foo_location"); - auto* test_context = static_cast( - const_cast(context)); - test_context->on_event.Notify(); - } - } /*track*/, - &test_context /*context*/}; - - std::unique_ptr engine( - new Envoy::InternalEngine(engine_cbs, {}, event_tracker)); - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - - ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); - // Simulate an envoy bug by invoking an Envoy bug failure - // record action. - // Verify that an envoy event is emitted when an event tracker is passed - // at engine's initialization time. - Assert::invokeEnvoyBugFailureRecordActionForEnvoyBugMacroUseOnly("foo_location"); - - ASSERT_TRUE(test_context.on_event.WaitForNotificationWithTimeout(absl::Seconds(3))); - engine->terminate(); - ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); -} - -TEST_F(MainInterfaceTest, ResetConnectivityState) { - engine_test_context test_context{}; - envoy_engine_callbacks engine_cbs{[](void* context) -> void { - auto* engine_running = - static_cast(context); - engine_running->on_engine_running.Notify(); - } /*on_engine_running*/, - [](void* context) -> void { - auto* exit = static_cast(context); - exit->on_exit.Notify(); - } /*on_exit*/, - &test_context /*context*/}; - std::unique_ptr engine(new Envoy::InternalEngine(engine_cbs, {}, {})); - engine->run(MINIMAL_TEST_CONFIG.c_str(), LEVEL_DEBUG.c_str()); - ASSERT_TRUE(test_context.on_engine_running.WaitForNotificationWithTimeout(absl::Seconds(3))); - - ASSERT_EQ(ENVOY_SUCCESS, engine->resetConnectivityState()); - - engine->terminate(); - ASSERT_TRUE(test_context.on_exit.WaitForNotificationWithTimeout(absl::Seconds(3))); -} - -} // namespace Envoy From 6e069a4c3055e0a8a1caef29cedef6dc438a7dd9 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Thu, 22 Feb 2024 16:11:06 -0500 Subject: [PATCH 075/151] mobile: Skip iOS proxy tests while investigating iOS CI (#32529) Signed-off-by: Ali Beyad --- .../integration/proxying/HTTPRequestUsingProxyTest.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift b/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift index 424159b9e6e4..dfee784991b1 100644 --- a/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift +++ b/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift @@ -13,6 +13,7 @@ final class HTTPRequestUsingProxyTest: XCTestCase { } func testHTTPRequestUsingProxy() throws { + throw XCTSkip("Disabled while investigating https://github.com/envoyproxy/envoy/issues/31957") EnvoyTestServer.startHttpProxyServer() let port = EnvoyTestServer.getEnvoyPort() @@ -66,6 +67,7 @@ final class HTTPRequestUsingProxyTest: XCTestCase { } func testHTTPSRequestUsingProxy() throws { + throw XCTSkip("Disabled while investigating https://github.com/envoyproxy/envoy/issues/31957") EnvoyTestServer.startHttpsProxyServer() let port = EnvoyTestServer.getEnvoyPort() @@ -121,6 +123,7 @@ final class HTTPRequestUsingProxyTest: XCTestCase { } func testHTTPSRequestUsingPacFileUrlResolver() throws { + throw XCTSkip("Disabled while investigating https://github.com/envoyproxy/envoy/issues/31957") EnvoyTestServer.startHttpsProxyServer() let port = EnvoyTestServer.getEnvoyPort() @@ -176,6 +179,7 @@ final class HTTPRequestUsingProxyTest: XCTestCase { } func testHTTPRequestUsingProxyCancelStream() throws { + throw XCTSkip("Disabled while investigating https://github.com/envoyproxy/envoy/issues/31957") EnvoyTestServer.startHttpProxyServer() let port = EnvoyTestServer.getEnvoyPort() From 790abeba40a62cd0bfecad43862ef79e12ce421a Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Thu, 22 Feb 2024 23:17:21 +0200 Subject: [PATCH 076/151] add unit tests for uint32_accessor (#32519) found some non-tested functions in UInt32AccessorImpl Risk Level: low Testing: unit tests Signed-off-by: ohadvano --- .../stream_info/uint32_accessor_impl_test.cc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/common/stream_info/uint32_accessor_impl_test.cc b/test/common/stream_info/uint32_accessor_impl_test.cc index 156d89a3e3ca..62f5b542e233 100644 --- a/test/common/stream_info/uint32_accessor_impl_test.cc +++ b/test/common/stream_info/uint32_accessor_impl_test.cc @@ -19,6 +19,25 @@ TEST(UInt32AccessorImplTest, IncrementValue) { EXPECT_EQ(0xdeadbef0, accessor.value()); } +TEST(UInt32AccessorImplTest, TestProto) { + uint32_t init_value = 0xdeadbeef; + UInt32AccessorImpl accessor(init_value); + auto message = accessor.serializeAsProto(); + EXPECT_NE(nullptr, message); + + auto* uint32_struct = dynamic_cast(message.get()); + EXPECT_NE(nullptr, uint32_struct); + EXPECT_EQ(init_value, uint32_struct->value()); +} + +TEST(UInt32AccessorImplTest, TestString) { + uint32_t init_value = 0xdeadbeef; + UInt32AccessorImpl accessor(init_value); + absl::optional value = accessor.serializeAsString(); + ASSERT_TRUE(value.has_value()); + EXPECT_EQ(value, std::to_string(init_value)); +} + } // namespace } // namespace StreamInfo } // namespace Envoy From 66e6ce82a2125809667500ea3a056c19bca8fe7c Mon Sep 17 00:00:00 2001 From: RenjieTang Date: Thu, 22 Feb 2024 18:19:02 -0800 Subject: [PATCH 077/151] [mobile] Add QUIC connection and stream error stats to allow list (#32444) Signed-off-by: Renjie Tang --- mobile/library/cc/engine_builder.cc | 2 ++ .../common/integration/client_integration_test.cc | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/mobile/library/cc/engine_builder.cc b/mobile/library/cc/engine_builder.cc index cd93efb440f4..c3437d435584 100644 --- a/mobile/library/cc/engine_builder.cc +++ b/mobile/library/cc/engine_builder.cc @@ -813,6 +813,8 @@ std::unique_ptr EngineBuilder::generate list->add_patterns()->mutable_safe_regex()->set_regex( "^vhost\\.[\\w]+\\.vcluster\\.[\\w]+?\\.upstream_rq_(?:[12345]xx|[3-5][0-9][0-9]|retry|" "total)"); + list->add_patterns()->set_contains("quic_connection_close_error_code"); + list->add_patterns()->set_contains("quic_reset_stream_error_code"); bootstrap->mutable_stats_config()->mutable_use_all_default_tags()->set_value(false); // Set up watchdog diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index 8e441f61ce42..a9761afeb3c5 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -701,6 +701,17 @@ TEST_P(ClientIntegrationTest, CancelDuringResponse) { if (upstreamProtocol() != Http::CodecType::HTTP1) { ASSERT_TRUE(upstream_request_->waitForReset()); } + + // Close the HTTP3 connection and verify stats are dumped properly. + if (getCodecType() == Http::CodecType::HTTP3) { + ASSERT_TRUE(upstream_connection_->close()); + ASSERT_TRUE(upstream_connection_->waitForDisconnect()); + upstream_connection_.reset(); + ASSERT_TRUE( + waitForCounterGe("http3.upstream.tx.quic_connection_close_error_code_QUIC_NO_ERROR", 1)); + ASSERT_TRUE(waitForCounterGe( + "http3.upstream.tx.quic_reset_stream_error_code_QUIC_STREAM_CANCELLED", 1)); + } } TEST_P(ClientIntegrationTest, BasicCancelWithCompleteStream) { From ddb1e05e7b2a823ef2754c55fb7e03832d788965 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 23 Feb 2024 09:10:36 +0000 Subject: [PATCH 078/151] ci/macos: Use Engflow for bazel cache (#32520) Signed-off-by: Ryan Northey --- .bazelrc | 20 +++++++++++--------- .github/workflows/envoy-macos.yml | 11 ++++------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.bazelrc b/.bazelrc index 3c783e1a62b0..edec083a41d1 100644 --- a/.bazelrc +++ b/.bazelrc @@ -510,16 +510,18 @@ build:rbe-engflow --remote_timeout=3600s build:rbe-engflow --bes_timeout=3600s build:rbe-engflow --bes_upload_mode=fully_async -build:rbe-envoy-engflow --google_default_credentials=false -build:rbe-envoy-engflow --remote_cache=grpcs://morganite.cluster.engflow.com +build:cache-envoy-engflow --google_default_credentials=false +build:cache-envoy-engflow --remote_cache=grpcs://morganite.cluster.engflow.com +build:cache-envoy-engflow --remote_timeout=3600s +build:cache-envoy-engflow --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh +build:cache-envoy-engflow --grpc_keepalive_time=30s +build:bes-envoy-engflow --bes_backend=grpcs://morganite.cluster.engflow.com/ +build:bes-envoy-engflow --bes_results_url=https://morganite.cluster.engflow.com/invocation/ +build:bes-envoy-engflow --bes_timeout=3600s +build:bes-envoy-engflow --bes_upload_mode=fully_async +build:rbe-envoy-engflow --config=cache-envoy-engflow +build:rbe-envoy-engflow --config=bes-envoy-engflow build:rbe-envoy-engflow --remote_executor=grpcs://morganite.cluster.engflow.com -build:rbe-envoy-engflow --bes_backend=grpcs://morganite.cluster.engflow.com/ -build:rbe-envoy-engflow --bes_results_url=https://morganite.cluster.engflow.com/invocation/ -build:rbe-envoy-engflow --credential_helper=*.engflow.com=%workspace%/bazel/engflow-bazel-credential-helper.sh -build:rbe-envoy-engflow --grpc_keepalive_time=30s -build:rbe-envoy-engflow --remote_timeout=3600s -build:rbe-envoy-engflow --bes_timeout=3600s -build:rbe-envoy-engflow --bes_upload_mode=fully_async build:rbe-envoy-engflow --remote_default_exec_properties=container-image=docker://docker.io/envoyproxy/envoy-build-ubuntu:0ca52447572ee105a4730da5e76fe47c9c5a7c64@sha256:d736c58f06f36848e7966752cc7e01519cc1b5101a178d5c6634807e8ac3deab ############################################################################# diff --git a/.github/workflows/envoy-macos.yml b/.github/workflows/envoy-macos.yml index 22d975afd7ce..ea82618bd54f 100644 --- a/.github/workflows/envoy-macos.yml +++ b/.github/workflows/envoy-macos.yml @@ -37,8 +37,6 @@ jobs: permissions: contents: read packages: read - secrets: - rbe-key: ${{ secrets.GCP_SERVICE_ACCOUNT_KEY }} if: ${{ fromJSON(needs.load.outputs.request).run.build-macos }} needs: - load @@ -49,6 +47,7 @@ jobs: container-command: request: ${{ needs.load.outputs.request }} runs-on: macos-14-xlarge + source: ${{ matrix.source }} steps-post: steps-pre: ${{ matrix.steps-pre }} target: ${{ matrix.target }} @@ -64,14 +63,12 @@ jobs: shell: bash name: Setup macos source: | - GCP_SERVICE_ACCOUNT_KEY_PATH=$(mktemp -t gcp_service_account.XXXXXX.json) - bash -c "echo \"${RBE_KEY}\" | base64 --decode > \"${GCP_SERVICE_ACCOUNT_KEY_PATH}\"" _BAZEL_BUILD_EXTRA_OPTIONS=( --remote_download_toplevel --flaky_test_attempts=2 - --config=cache-google - --config=ci - --google_credentials=${GCP_SERVICE_ACCOUNT_KEY_PATH}) + --config=bes-envoy-engflow + --config=cache-envoy-engflow + --config=ci) export BAZEL_BUILD_EXTRA_OPTIONS=${_BAZEL_BUILD_EXTRA_OPTIONS[*]} request: From 04e888d8af7a0730d1569040e3dbe4b31696e81e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:14:04 +0000 Subject: [PATCH 079/151] build(deps): bump actions/dependency-review-action from 4.1.0 to 4.1.3 (#32494) Bumps [actions/dependency-review-action](https://github.com/actions/dependency-review-action) from 4.1.0 to 4.1.3. - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/80f10bf419f34980065523f5efca7ebed17576aa...9129d7d40b8c12c1ed0f60400d00c92d437adcce) --- updated-dependencies: - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/_precheck_deps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/_precheck_deps.yml b/.github/workflows/_precheck_deps.yml index 8b2d80df07cd..8864b7beada4 100644 --- a/.github/workflows/_precheck_deps.yml +++ b/.github/workflows/_precheck_deps.yml @@ -55,4 +55,4 @@ jobs: ref: ${{ fromJSON(inputs.request).request.sha }} persist-credentials: false - name: Dependency Review - uses: actions/dependency-review-action@80f10bf419f34980065523f5efca7ebed17576aa # v4.1.0 + uses: actions/dependency-review-action@9129d7d40b8c12c1ed0f60400d00c92d437adcce # v4.1.3 From 8974156aea0742071f4931ad39db94b1ddda57a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:14:17 +0000 Subject: [PATCH 080/151] build(deps): bump postgres from `4d1580c` to `1bd17d3` in /examples/shared/postgres (#32481) build(deps): bump postgres in /examples/shared/postgres Bumps postgres from `4d1580c` to `1bd17d3`. --- updated-dependencies: - dependency-name: postgres dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/postgres/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index 7c3a5618a7f5..cf9e3a129a33 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:4d1580c68015bf5dbeb4a2739ba8a3cff2641edd38089c518bbc2f5ccb900867 +FROM postgres:latest@sha256:1bd17d36d605b63fd62f03800a932bae292250659ffc417cf8c29836cc353b5f COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] From b4fc50e3b014262a77d57435527b43f9f36ff2cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:14:28 +0000 Subject: [PATCH 081/151] build(deps): bump node from `6b35c9b` to `2254c33` in /examples/shared/node (#32480) build(deps): bump node in /examples/shared/node Bumps node from `6b35c9b` to `2254c33`. --- updated-dependencies: - dependency-name: node dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/node/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index 51ff42fecd66..80cd4cb18cad 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:21.6-bookworm-slim@sha256:6b35c9b34836d7d4a686d8a899cb503533040d43d8cb0dcb51ef832108573a86 as node-base +FROM node:21.6-bookworm-slim@sha256:2254c337b6f7a239620b3876f8d941c65b7834fb38cdf137decc6191a73502bf as node-base FROM node-base as node-http-auth From 69513e2290cbbaab23159b45cae69f4dbd7af410 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 09:14:42 +0000 Subject: [PATCH 082/151] build(deps): bump the examples-local-ratelimit group in /examples/local_ratelimit with 1 update (#32460) build(deps): bump the examples-local-ratelimit group Bumps the examples-local-ratelimit group in /examples/local_ratelimit with 1 update: nginx. Updates `nginx` from `84c52df` to `c26ae74` --- updated-dependencies: - dependency-name: nginx dependency-type: direct:production dependency-group: examples-local-ratelimit ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/local_ratelimit/Dockerfile-nginx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/local_ratelimit/Dockerfile-nginx b/examples/local_ratelimit/Dockerfile-nginx index 7e7488a884ad..eff6acc5fa7d 100644 --- a/examples/local_ratelimit/Dockerfile-nginx +++ b/examples/local_ratelimit/Dockerfile-nginx @@ -1 +1 @@ -FROM nginx@sha256:84c52dfd55c467e12ef85cad6a252c0990564f03c4850799bf41dd738738691f +FROM nginx@sha256:c26ae7472d624ba1fafd296e73cecc4f93f853088e6a9c13c0d52f6ca5865107 From 5c056957f8742dc1cd056bc5f20a02d28ab480b2 Mon Sep 17 00:00:00 2001 From: Tam Mach Date: Sat, 24 Feb 2024 02:04:48 +1100 Subject: [PATCH 083/151] Remove envoy_reloadable_features_initialize_upstream_filters (#32463) Fixes: #30421 Signed-off-by: Tam Mach --- changelogs/current.yaml | 3 + source/common/conn_pool/conn_pool_base.cc | 5 +- source/common/network/filter_manager_impl.cc | 25 +++--- source/common/runtime/runtime_features.cc | 1 - .../cluster_filter_integration_test.cc | 84 +++++-------------- 5 files changed, 37 insertions(+), 81 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 347b9f6c4eb3..24e0ec2f9d30 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -141,6 +141,9 @@ removed_config_or_runtime: - area: aws change: | Removed ``envoy.reloadable_features.enable_aws_credentials_file`` runtime flag and legacy code paths. +- area: upstream + change: | + removed ``envoy_reloadable_features_initialize_upstream_filters`` and legacy code paths. new_features: - area: aws_request_signing diff --git a/source/common/conn_pool/conn_pool_base.cc b/source/common/conn_pool/conn_pool_base.cc index 9ff006726811..1eb4bc77ee1e 100644 --- a/source/common/conn_pool/conn_pool_base.cc +++ b/source/common/conn_pool/conn_pool_base.cc @@ -593,9 +593,8 @@ void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view client.connection_duration_timer_->enableTimer(max_connection_duration.value()); } // Initialize client read filters - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.initialize_upstream_filters")) { - client.initializeReadFilters(); - } + client.initializeReadFilters(); + // At this point, for the mixed ALPN pool, the client may be deleted. Do not // refer to client after this point. onConnected(client); diff --git a/source/common/network/filter_manager_impl.cc b/source/common/network/filter_manager_impl.cc index b26b8a39036b..bc621a36139f 100644 --- a/source/common/network/filter_manager_impl.cc +++ b/source/common/network/filter_manager_impl.cc @@ -42,23 +42,20 @@ bool FilterManagerImpl::initializeReadFilters() { if (upstream_filters_.empty()) { return false; } - if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.initialize_upstream_filters")) { - // Initialize read filters without calling onData() afterwards. - // This is called just after an connection has been established and nothing may have been read - // yet. onData() will be called separately as data is read from the connection. - for (auto& entry : upstream_filters_) { - if (entry->filter_ && !entry->initialized_) { - entry->initialized_ = true; - FilterStatus status = entry->filter_->onNewConnection(); - if (status == FilterStatus::StopIteration || - connection_.state() != Connection::State::Open) { - break; - } + + // Initialize read filters without calling onData() afterwards. + // This is called just after an connection has been established and nothing may have been read + // yet. onData() will be called separately as data is read from the connection. + for (auto& entry : upstream_filters_) { + if (entry->filter_ && !entry->initialized_) { + entry->initialized_ = true; + FilterStatus status = entry->filter_->onNewConnection(); + if (status == FilterStatus::StopIteration || connection_.state() != Connection::State::Open) { + break; } } - } else { - onContinueReading(nullptr, connection_); } + return true; } diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 8af9ef1034b5..8ea3e31f731b 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -65,7 +65,6 @@ RUNTIME_GUARD(envoy_reloadable_features_http_filter_avoid_reentrant_local_reply) // Delay deprecation and decommission until UHV is enabled. RUNTIME_GUARD(envoy_reloadable_features_http_reject_path_with_fragment); RUNTIME_GUARD(envoy_reloadable_features_immediate_response_use_filter_mutation_rule); -RUNTIME_GUARD(envoy_reloadable_features_initialize_upstream_filters); RUNTIME_GUARD(envoy_reloadable_features_locality_routing_use_new_routing_logic); RUNTIME_GUARD(envoy_reloadable_features_lowercase_scheme); RUNTIME_GUARD(envoy_reloadable_features_no_downgrade_to_canonical_name); diff --git a/test/integration/cluster_filter_integration_test.cc b/test/integration/cluster_filter_integration_test.cc index f9450fc37592..a363d9f512c2 100644 --- a/test/integration/cluster_filter_integration_test.cc +++ b/test/integration/cluster_filter_integration_test.cc @@ -96,28 +96,10 @@ class PoliteFilterConfigFactory TestParent& test_parent_; }; -std::string ipInitializeUpstreamFiltersTestParamsToString( - const testing::TestParamInfo>& params) { - return fmt::format( - "{}_{}", - TestUtility::ipTestParamsToString( - testing::TestParamInfo(std::get<0>(params.param), 0)), - std::get<1>(params.param) ? "do_initialize_upstream_filters" - : "dont_initialize_upstream_filters"); -} - -class ClusterFilterIntegrationTestBase - : public testing::TestWithParam>, - public TestParent { +class ClusterFilterIntegrationTestBase : public testing::TestWithParam, + public TestParent { public: - ClusterFilterIntegrationTestBase() : factory_(*this), registration_(factory_) { - Runtime::maybeSetRuntimeGuard("envoy.reloadable_features.initialize_upstream_filters", - std::get<1>(GetParam())); - } - - // Get the test parameter whether upstream network filters are initialized right after the - // upstream connection has been established - bool upstreamFiltersInitializedWhenConnected() const { return std::get<1>(GetParam()); } + ClusterFilterIntegrationTestBase() : factory_(*this), registration_(factory_) {} void initialize() { on_new_connection_called_after_on_write_.store(absl::optional{}); } @@ -149,7 +131,7 @@ class ClusterFilterTcpIntegrationTest : public ClusterFilterIntegrationTestBase, public BaseIntegrationTest { public: ClusterFilterTcpIntegrationTest() - : BaseIntegrationTest(std::get<0>(GetParam()), ConfigHelper::tcpProxyConfig()) {} + : BaseIntegrationTest(GetParam(), ConfigHelper::tcpProxyConfig()) {} void initialize() override { enableHalfClose(true); @@ -166,10 +148,8 @@ class ClusterFilterTcpIntegrationTest : public ClusterFilterIntegrationTestBase, } }; -INSTANTIATE_TEST_SUITE_P( - IpVersionsInitializeUpstreamFilters, ClusterFilterTcpIntegrationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::Bool()), - ipInitializeUpstreamFiltersTestParamsToString); +INSTANTIATE_TEST_SUITE_P(IpVersionsInitializeUpstreamFilters, ClusterFilterTcpIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); TEST_P(ClusterFilterTcpIntegrationTest, TestClusterFilter) { initialize(); @@ -183,19 +163,12 @@ TEST_P(ClusterFilterTcpIntegrationTest, TestClusterFilter) { ASSERT_TRUE(fake_upstream_connection->waitForData(11, &observed_data)); EXPECT_EQ("please test", observed_data); - // Upstream read filters are expected to be initialized at this point after connection has been - // established but nothing has been read from the connection yet, but only if runtime feature - // flag 'initialize_upstream_filters' is true. - if (upstreamFiltersInitializedWhenConnected()) { - // Note that we need to have written on the connection and waited for the written data to be - // received so that we know the upstream connection has had chance to schedule the - // onNewConnection() callbacks. This test will be flaky if we expect onNewConnection() having - // been called before the waitForData() above. - ASSERT_TRUE(wasOnNewConnectionCalled()); - ASSERT_TRUE(wasOnNewConnectionCalledFirst()); - } else { - ASSERT_FALSE(wasOnNewConnectionCalled()); - } + // Note that we need to have written on the connection and waited for the written data to be + // received so that we know the upstream connection has had chance to schedule the + // onNewConnection() callbacks. This test will be flaky if we expect onNewConnection() having + // been called before the waitForData() above. + ASSERT_TRUE(wasOnNewConnectionCalled()); + ASSERT_TRUE(wasOnNewConnectionCalledFirst()); observed_data.clear(); ASSERT_TRUE(tcp_client->write(" everything")); @@ -206,12 +179,8 @@ TEST_P(ClusterFilterTcpIntegrationTest, TestClusterFilter) { tcp_client->waitForData("surely yes"); // Finally after reading from the upstream connection onNewConnection() has been called in all - // cases, but onWrite was called before it if runtime feature flag 'initialize_upstream_filters' - // is false. + // cases, but onWrite was called before it ASSERT_TRUE(wasOnNewConnectionCalled()); - if (!upstreamFiltersInitializedWhenConnected()) { - ASSERT_FALSE(wasOnNewConnectionCalledFirst()); - } ASSERT_TRUE(tcp_client->write("", true)); ASSERT_TRUE(fake_upstream_connection->waitForHalfClose()); @@ -223,8 +192,7 @@ TEST_P(ClusterFilterTcpIntegrationTest, TestClusterFilter) { class ClusterFilterHttpIntegrationTest : public ClusterFilterIntegrationTestBase, public HttpIntegrationTest { public: - ClusterFilterHttpIntegrationTest() - : HttpIntegrationTest(Http::CodecType::HTTP1, std::get<0>(GetParam())) {} + ClusterFilterHttpIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) {} void initialize() override { config_helper_.addConfigModifier([](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { @@ -240,10 +208,8 @@ class ClusterFilterHttpIntegrationTest : public ClusterFilterIntegrationTestBase } }; -INSTANTIATE_TEST_SUITE_P( - IpVersionsInitializeUpstreamFilters, ClusterFilterHttpIntegrationTest, - testing::Combine(testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), testing::Bool()), - ipInitializeUpstreamFiltersTestParamsToString); +INSTANTIATE_TEST_SUITE_P(IpVersionsInitializeUpstreamFilters, ClusterFilterHttpIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest())); TEST_P(ClusterFilterHttpIntegrationTest, TestClusterFilter) { initialize(); @@ -257,13 +223,9 @@ TEST_P(ClusterFilterHttpIntegrationTest, TestClusterFilter) { waitForNextUpstreamRequest(); // Upstream read filters are expected to be initialized at this point after only the request has - // been sent, but only if runtime feature flag 'initialize_upstream_filters' is true. - if (upstreamFiltersInitializedWhenConnected()) { - ASSERT_TRUE(wasOnNewConnectionCalled()); - ASSERT_TRUE(wasOnNewConnectionCalledFirst()); - } else { - ASSERT_FALSE(wasOnNewConnectionCalled()); - } + // been sent + ASSERT_TRUE(wasOnNewConnectionCalled()); + ASSERT_TRUE(wasOnNewConnectionCalledFirst()); EXPECT_TRUE(upstream_request_->complete()); EXPECT_EQ("hello!", upstream_request_->body().toString()); @@ -276,13 +238,9 @@ TEST_P(ClusterFilterHttpIntegrationTest, TestClusterFilter) { ASSERT_TRUE(response->complete()); // Finally after receiving the response onNewConnection() has been called in all cases, but - // onWrite was called before it if runtime feature flag 'initialize_upstream_filters' is false. + // onWrite was called before it ASSERT_TRUE(wasOnNewConnectionCalled()); - if (upstreamFiltersInitializedWhenConnected()) { - ASSERT_TRUE(wasOnNewConnectionCalledFirst()); - } else { - ASSERT_FALSE(wasOnNewConnectionCalledFirst()); - } + ASSERT_TRUE(wasOnNewConnectionCalledFirst()); EXPECT_EQ("200", response->headers().getStatusValue()); EXPECT_EQ("greetings", response->body()); From 66ed82767e6d9a8b33d09f481ded689021c2668b Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Fri, 23 Feb 2024 11:50:40 -0800 Subject: [PATCH 084/151] Revert "mobile/ci: Fix iOS build failures (#31756)" (#32558) * Revert "mobile/ci: Fix iOS build failures (#31756)" This reverts commit f37ef3ff03bb78035e80b65159c0ec35b7544e0d. and removes unsed code path Signed-off-by: Keith Smiley --- .github/workflows/envoy-macos.yml | 5 +---- .github/workflows/mobile-compile_time_options.yml | 5 +---- .github/workflows/mobile-ios_build.yml | 15 +++------------ .github/workflows/mobile-ios_tests.yml | 5 +---- .github/workflows/mobile-release_validation.yml | 5 +---- mobile/ci/mac_ci_setup.sh | 15 +++------------ 6 files changed, 10 insertions(+), 40 deletions(-) diff --git a/.github/workflows/envoy-macos.yml b/.github/workflows/envoy-macos.yml index ea82618bd54f..087aa8a3433d 100644 --- a/.github/workflows/envoy-macos.yml +++ b/.github/workflows/envoy-macos.yml @@ -58,11 +58,8 @@ jobs: include: - target: ci/mac_ci_steps.sh name: macOS - steps-pre: | - - run: ./ci/mac_ci_setup.sh - shell: bash - name: Setup macos source: | + source ./ci/mac_ci_setup.sh _BAZEL_BUILD_EXTRA_OPTIONS=( --remote_download_toplevel --flaky_test_attempts=2 diff --git a/.github/workflows/mobile-compile_time_options.yml b/.github/workflows/mobile-compile_time_options.yml index be3d009d37d9..081acec27cb2 100644 --- a/.github/workflows/mobile-compile_time_options.yml +++ b/.github/workflows/mobile-compile_time_options.yml @@ -103,10 +103,7 @@ jobs: --config=mobile-remote-ci-macos-swift //library/swift:ios_framework source: | - # TODO(fredyw): A workaround since mobile/WORKSPACE requires Android SDK to be available - # and the GitHub Action runner image no longer includes Android SDK 30: - # https://github.com/actions/runner-images/issues/8952 - ./ci/mac_ci_setup.sh --android + source ./ci/mac_ci_setup.sh ./bazelw shutdown request: diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 327c815f58d8..61bce4bd5026 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -44,10 +44,7 @@ jobs: request: ${{ needs.load.outputs.request }} runs-on: macos-12 source: | - # TODO(fredyw): A workaround since mobile/WORKSPACE requires Android SDK to be available - # and the GitHub Action runner image no longer includes Android SDK 30: - # https://github.com/actions/runner-images/issues/8952 - ./ci/mac_ci_setup.sh --android + source ./ci/mac_ci_setup.sh ./bazelw shutdown steps-post: ${{ matrix.steps-post }} target: ${{ matrix.target }} @@ -86,10 +83,7 @@ jobs: request: ${{ needs.load.outputs.request }} runs-on: macos-12 source: | - # TODO(fredyw): A workaround since mobile/WORKSPACE requires Android SDK to be available - # and the GitHub Action runner image no longer includes Android SDK 30: - # https://github.com/actions/runner-images/issues/8952 - ./ci/mac_ci_setup.sh --android + source ./ci/mac_ci_setup.sh ./bazelw shutdown steps-post: | - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 @@ -131,10 +125,7 @@ jobs: request: ${{ needs.load.outputs.request }} runs-on: macos-12 source: | - # TODO(fredyw): A workaround since mobile/WORKSPACE requires Android SDK to be available - # and the GitHub Action runner image no longer includes Android SDK 30: - # https://github.com/actions/runner-images/issues/8952 - ./ci/mac_ci_setup.sh --android + source ./ci/mac_ci_setup.sh steps-post: | - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 with: diff --git a/.github/workflows/mobile-ios_tests.yml b/.github/workflows/mobile-ios_tests.yml index d77c8a2b94b7..b07e6672e2f3 100644 --- a/.github/workflows/mobile-ios_tests.yml +++ b/.github/workflows/mobile-ios_tests.yml @@ -44,10 +44,7 @@ jobs: request: ${{ needs.load.outputs.request }} runs-on: macos-12 source: | - # TODO(fredyw): A workaround since mobile/WORKSPACE requires Android SDK to be available - # and the GitHub Action runner image no longer includes Android SDK 30: - # https://github.com/actions/runner-images/issues/8952 - ./ci/mac_ci_setup.sh --android + source ./ci/mac_ci_setup.sh steps-post: ${{ matrix.steps-post }} target: ${{ matrix.target }} timeout-minutes: ${{ matrix.timeout-minutes }} diff --git a/.github/workflows/mobile-release_validation.yml b/.github/workflows/mobile-release_validation.yml index 6179cf99fd1c..9fa0847f88ca 100644 --- a/.github/workflows/mobile-release_validation.yml +++ b/.github/workflows/mobile-release_validation.yml @@ -51,10 +51,7 @@ jobs: request: ${{ needs.load.outputs.request }} runs-on: macos-12 source: | - # TODO(fredyw): A workaround since mobile/WORKSPACE always requires Android SDK to be available - # and the GitHub Action runner image no longer includes Android SDK 30: - # https://github.com/actions/runner-images/issues/8952 - ./ci/mac_ci_setup.sh --android + source ./ci/mac_ci_setup.sh # Ignore errors: Bad CRC when unzipping large files: https://bbs.archlinux.org/viewtopic.php?id=153011 steps-post: | - run: | diff --git a/mobile/ci/mac_ci_setup.sh b/mobile/ci/mac_ci_setup.sh index f01a7ba449f1..eb0fb8ad7a69 100755 --- a/mobile/ci/mac_ci_setup.sh +++ b/mobile/ci/mac_ci_setup.sh @@ -56,15 +56,6 @@ sudo xcode-select --switch /Applications/Xcode_14.1.app retry ./bazelw version -if [[ "${1:-}" == "--android" ]]; then - # Download and set up ndk 21 after GitHub update - # https://github.com/actions/virtual-environments/issues/5595 - ANDROID_HOME=$ANDROID_SDK_ROOT - SDKMANAGER="${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager" - "${SDKMANAGER}" --install "platform-tools" "platforms;android-30" - "${SDKMANAGER}" --uninstall "ndk-bundle" - "${SDKMANAGER}" --install "ndk;21.4.7075529" - "${SDKMANAGER}" --install "build-tools;30.0.2" - ANDROID_NDK_HOME="${ANDROID_HOME}/ndk/21.4.7075529" - export ANDROID_NDK_HOME -fi +# Unset default variables so we don't have to install Android SDK/NDK. +unset ANDROID_HOME +unset ANDROID_NDK_HOME From e66a8ade702c7cd43cf1027b89f2a65b48b374a4 Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Fri, 23 Feb 2024 15:09:24 -0500 Subject: [PATCH 085/151] mobile/ci: Tag the Swift tests with `no-remote-exec` (#32552) This will turn off RBE for the Swift tests. This will make the Swift tests go slower, but right now they are timing out at a regular rate on CI, seemingly due to DNS timeouts on the MacOS machines. See https://github.com/envoyproxy/envoy/issues/31957 for details. Opened https://github.com/envoyproxy/envoy/issues/32551 to find a fix that allows us to use RBE with the Swift tests. Signed-off-by: Ali Beyad --- mobile/test/swift/BUILD | 1 + mobile/test/swift/cxx/BUILD | 1 + mobile/test/swift/integration/BUILD | 15 +++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/mobile/test/swift/BUILD b/mobile/test/swift/BUILD index 090b92a24be2..80b407888809 100644 --- a/mobile/test/swift/BUILD +++ b/mobile/test/swift/BUILD @@ -21,6 +21,7 @@ envoy_mobile_swift_test( "RetryPolicyTests.swift", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_engine_objc_lib", diff --git a/mobile/test/swift/cxx/BUILD b/mobile/test/swift/cxx/BUILD index 3599e37554e6..fc24ff829083 100644 --- a/mobile/test/swift/cxx/BUILD +++ b/mobile/test/swift/cxx/BUILD @@ -10,6 +10,7 @@ envoy_mobile_swift_test( srcs = [ "LogLevelCxxTests.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/swift/EnvoyCxxSwiftInterop", diff --git a/mobile/test/swift/integration/BUILD b/mobile/test/swift/integration/BUILD index 3fb55499ee22..dfcfd42dee9e 100644 --- a/mobile/test/swift/integration/BUILD +++ b/mobile/test/swift/integration/BUILD @@ -28,6 +28,7 @@ envoy_mobile_swift_test( srcs = [ "CancelStreamTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -40,6 +41,7 @@ envoy_mobile_swift_test( srcs = [ "EngineApiTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -52,6 +54,7 @@ envoy_mobile_swift_test( srcs = [ "FilterResetIdleTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -64,6 +67,7 @@ envoy_mobile_swift_test( srcs = [ "GRPCReceiveErrorTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -94,6 +98,7 @@ envoy_mobile_swift_test( srcs = [ "KeyValueStoreTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -106,6 +111,7 @@ envoy_mobile_swift_test( srcs = [ "ReceiveDataTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -118,6 +124,7 @@ envoy_mobile_swift_test( srcs = [ "ReceiveErrorTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -130,6 +137,7 @@ envoy_mobile_swift_test( srcs = [ "ResetConnectivityStateTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -142,6 +150,7 @@ envoy_mobile_swift_test( srcs = [ "SendDataTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -154,6 +163,7 @@ envoy_mobile_swift_test( srcs = [ "SendHeadersTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -166,6 +176,7 @@ envoy_mobile_swift_test( srcs = [ "SendTrailersTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -178,6 +189,7 @@ envoy_mobile_swift_test( srcs = [ "SetEventTrackerTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -190,6 +202,7 @@ envoy_mobile_swift_test( srcs = [ "SetEventTrackerTestNoTracker.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -202,6 +215,7 @@ envoy_mobile_swift_test( srcs = [ "SetLoggerTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", @@ -214,6 +228,7 @@ envoy_mobile_swift_test( srcs = [ "CancelGRPCStreamTest.swift", ], + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ ":test_extensions", From 4a34aff94eb0add8b211049d8167f874a2ec233c Mon Sep 17 00:00:00 2001 From: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> Date: Fri, 23 Feb 2024 22:26:05 +0100 Subject: [PATCH 086/151] dynatraceresourcedetector: Only log message when no enrichment file is found (#32486) Today, the resource detector logs a warn message, whenever any of the Dynatrace enrichment files is not present. This is a bug, because it is normal for a file to not exist. The files are injected depending on the environment (k8s or not for ex). It is only a problem when ALL files are not present. Then in that case, Dynatrace is not properly deployed and the user should be able to know via the Dynatrace UI/Deployment page. Risk Level: Low Testing: Unit tests added Signed-off-by: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> --- changelogs/current.yaml | 3 ++ .../dynatrace/dynatrace_resource_detector.cc | 13 ++++- .../dynatrace_resource_detector_test.cc | 51 +++++++++++++++++-- 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 24e0ec2f9d30..93cb5a10059d 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -117,6 +117,9 @@ bug_fixes: - area: deps change: | Updated QUICHE dependencies to incorporate fixes for https://github.com/envoyproxy/envoy/issues/32401. +- area: tracing + change: | + Dynatrace resource detector: Only log warning message when no enrichment attributes are found. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` diff --git a/source/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector.cc b/source/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector.cc index b8c98d81201b..fdfd03b7c385 100644 --- a/source/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector.cc +++ b/source/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector.cc @@ -35,10 +35,19 @@ Resource DynatraceResourceDetector::detect() { addAttributes(content, resource); } } - END_TRY catch (const EnvoyException&) { failure_count++; } + END_TRY catch (const EnvoyException& e) { + failure_count++; + ENVOY_LOG( + warn, + "Failed to detect resource attributes from the Dynatrace enrichment file: {}, error: {}.", + file_name, e.what()); + } } - if (failure_count > 0) { + // Only log if it failed to detect attributes from all enrichment files + // This means Dynatrace is not correctly deployed. + if (static_cast::size_type>(failure_count) == + DynatraceResourceDetector::dynatraceMetadataFiles().size()) { ENVOY_LOG( warn, "Dynatrace OpenTelemetry resource detector is configured but could not detect attributes. " diff --git a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector_test.cc b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector_test.cc index 2db7439d0021..9f1d94409ee4 100644 --- a/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector_test.cc +++ b/test/extensions/tracers/opentelemetry/resource_detectors/dynatrace/dynatrace_resource_detector_test.cc @@ -27,8 +27,6 @@ class MockDynatraceFileReader : public DynatraceMetadataFileReader { }; TEST(DynatraceResourceDetectorTest, DynatraceNotDeployed) { - NiceMock context; - auto dt_file_reader = std::make_unique>(); EXPECT_CALL(*dt_file_reader, readEnrichmentFile(_)).WillRepeatedly(Return("")); @@ -43,7 +41,6 @@ TEST(DynatraceResourceDetectorTest, DynatraceNotDeployed) { } TEST(DynatraceResourceDetectorTest, OnlyOneAgentInstalled) { - NiceMock context; ResourceAttributes expected_attributes = { {"dt.entity.host", "HOST-abc"}, {"dt.entity.process_group_instance", "PROCESS_GROUP_INSTANCE-abc"}}; @@ -88,7 +85,6 @@ dt.entity.process_group_instance=PROCESS_GROUP_INSTANCE-abc } TEST(DynatraceResourceDetectorTest, Dynatracek8sOperator) { - NiceMock context; ResourceAttributes expected_attributes = {{"k8s.pod.uid", "123"}, {"k8s.pod.name", "envoy"}}; auto dt_file_reader = std::make_unique>(); @@ -125,6 +121,53 @@ k8s.pod.name=envoy } } +TEST(DynatraceResourceDetectorTest, TestFailureDetectionLog) { + // No enrichment file found, should log a line + { + auto dt_file_reader = std::make_unique>(); + + EXPECT_CALL(*dt_file_reader, readEnrichmentFile(_)).WillRepeatedly(Return("")); + + envoy::extensions::tracers::opentelemetry::resource_detectors::v3:: + DynatraceResourceDetectorConfig config; + + auto detector = std::make_shared(config, std::move(dt_file_reader)); + EXPECT_LOG_CONTAINS( + "warn", + "Dynatrace OpenTelemetry resource detector is configured but could not detect attributes.", + detector->detect()); + } + + // At least one enrichment file found, should NOT log a line + { + auto dt_file_reader = std::make_unique>(); + + std::string k8s_attrs = fmt::format(R"EOF( + k8s.pod.uid=123 + k8s.pod.name=envoy + )EOF"); + + EXPECT_CALL(*dt_file_reader, + readEnrichmentFile("dt_metadata_e617c525669e072eebe3d0f08212e8f2.properties")) + .WillRepeatedly(Return("")); + EXPECT_CALL(*dt_file_reader, + readEnrichmentFile("/var/lib/dynatrace/enrichment/dt_host_metadata.properties")) + .WillRepeatedly(Return("")); + EXPECT_CALL(*dt_file_reader, + readEnrichmentFile("/var/lib/dynatrace/enrichment/dt_metadata.properties")) + .WillRepeatedly(Return(k8s_attrs)); + + envoy::extensions::tracers::opentelemetry::resource_detectors::v3:: + DynatraceResourceDetectorConfig config; + + auto detector = std::make_shared(config, std::move(dt_file_reader)); + EXPECT_LOG_NOT_CONTAINS( + "warn", + "Dynatrace OpenTelemetry resource detector is configured but could not detect attributes.", + detector->detect()); + } +} + } // namespace OpenTelemetry } // namespace Tracers } // namespace Extensions From c8572f3ab9eb90e5984f79dfec16d060b34f15ae Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Fri, 23 Feb 2024 18:28:08 -0500 Subject: [PATCH 087/151] mobile: Re-enable the iOS proxy tests (#32548) Signed-off-by: Ali Beyad --- .../integration/proxying/HTTPRequestUsingProxyTest.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift b/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift index dfee784991b1..424159b9e6e4 100644 --- a/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift +++ b/mobile/test/swift/integration/proxying/HTTPRequestUsingProxyTest.swift @@ -13,7 +13,6 @@ final class HTTPRequestUsingProxyTest: XCTestCase { } func testHTTPRequestUsingProxy() throws { - throw XCTSkip("Disabled while investigating https://github.com/envoyproxy/envoy/issues/31957") EnvoyTestServer.startHttpProxyServer() let port = EnvoyTestServer.getEnvoyPort() @@ -67,7 +66,6 @@ final class HTTPRequestUsingProxyTest: XCTestCase { } func testHTTPSRequestUsingProxy() throws { - throw XCTSkip("Disabled while investigating https://github.com/envoyproxy/envoy/issues/31957") EnvoyTestServer.startHttpsProxyServer() let port = EnvoyTestServer.getEnvoyPort() @@ -123,7 +121,6 @@ final class HTTPRequestUsingProxyTest: XCTestCase { } func testHTTPSRequestUsingPacFileUrlResolver() throws { - throw XCTSkip("Disabled while investigating https://github.com/envoyproxy/envoy/issues/31957") EnvoyTestServer.startHttpsProxyServer() let port = EnvoyTestServer.getEnvoyPort() @@ -179,7 +176,6 @@ final class HTTPRequestUsingProxyTest: XCTestCase { } func testHTTPRequestUsingProxyCancelStream() throws { - throw XCTSkip("Disabled while investigating https://github.com/envoyproxy/envoy/issues/31957") EnvoyTestServer.startHttpProxyServer() let port = EnvoyTestServer.getEnvoyPort() From ec3c4fa41e0b05866c478eddedada115a2c9430e Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Mon, 26 Feb 2024 16:54:33 +0200 Subject: [PATCH 088/151] source_docs: add tagged log macros documentation (#32565) Update logging.md Signed-off-by: ohadvano <49730675+ohadvano@users.noreply.github.com> --- source/docs/logging.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/source/docs/logging.md b/source/docs/logging.md index 965a50f77048..388b8e09d2cc 100644 --- a/source/docs/logging.md +++ b/source/docs/logging.md @@ -87,3 +87,33 @@ like `ENVOY_LOG` except that they prepend the log message with `[C123]` or `[C123][S456` based on the connection/stream ID of the specified argument. Note that the IDs here are the Envoy IDs *NOT* the on-the-wire IDs from HTTP/2 or HTTP/3. + +#### ENVOY_TAGGED_LOG + +The following logging API allows the call site to pass a key-value object (``std::map``) with additional +context information that would be printed in the final log line. Unless JSON application logging is enabled, +the output log line will prepend the log tags in the following format: '[Tags: "key1":"value1","key2":"value2"]'. +When JSON application logging is enabled, the output JSON log will also include the key-values as additional properties +in the JSON struct. +Example: + +``` +std::map log_tags{{"key1","value1"},{"key2","value2"}}; +ENVOY_TAGGED_LOG(debug, log_tags, "failed to perform the operation"); +// output: [debug] [Tags: "key1":"value1","key2":"value2"] failed to perform the operation +``` + +#### ENVOY_TAGGED_CONN_LOG / ENVOY_TAGGED_STREAM_LOG + +These logging APIs support all the properties as described for ENVOY_TAGGED_LOG, but allow the call site to pass +additional objects to add a connection ID or stream ID (with respect to the relevant macro). +When using these macros, an additional log tag will be added with the key "ConnectionId" or "StreamId" (or both). + +Example: + +``` +std::map log_tags{{"key1","value1"},{"key2","value2"}}; +ENVOY_TAGGED_LOG(debug, log_tags, conn_, "failed to perform the operation"); +ENVOY_TAGGED_LOG(debug, log_tags, stream_, "failed to perform the operation"); +// output: [debug] [Tags: "ConnectionId":"10","StreamId":"11","key1":"value1","key2":"value2"] failed to perform the operation +``` From abaa72bbf2da9f8acd783d4b5e6aa2c2d5359592 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:56:32 +0000 Subject: [PATCH 089/151] build(deps): bump redis from `11c3e41` to `e647cfe` in /examples/redis (#32458) Bumps redis from `11c3e41` to `e647cfe`. --- updated-dependencies: - dependency-name: redis dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/redis/Dockerfile-redis | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/redis/Dockerfile-redis b/examples/redis/Dockerfile-redis index a132586d6f90..c0098b611cf2 100644 --- a/examples/redis/Dockerfile-redis +++ b/examples/redis/Dockerfile-redis @@ -1 +1 @@ -FROM redis@sha256:11c3e418c29672341be9a8e3015d96f05b88e5ad58829885d36f8342b4da13c2 +FROM redis@sha256:e647cfe134bf5e8e74e620f66346f93418acfc240b71dd85640325cb7cd01402 From 05d138c5868e852e3a3856b598eafc53dc85320e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:56:41 +0000 Subject: [PATCH 090/151] build(deps): bump otel/opentelemetry-collector from `4cead03` to `246dfe9` in /examples/opentelemetry (#32512) build(deps): bump otel/opentelemetry-collector Bumps otel/opentelemetry-collector from `4cead03` to `246dfe9`. --- updated-dependencies: - dependency-name: otel/opentelemetry-collector dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/opentelemetry/Dockerfile-opentelemetry | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/opentelemetry/Dockerfile-opentelemetry b/examples/opentelemetry/Dockerfile-opentelemetry index 63df37f24b0e..e1ae65ffa24b 100644 --- a/examples/opentelemetry/Dockerfile-opentelemetry +++ b/examples/opentelemetry/Dockerfile-opentelemetry @@ -1,7 +1,7 @@ FROM alpine:3.19@sha256:c5b1261d6d3e43071626931fc004f70149baeba2c8ec672bd4f27761f8e1ad6b as otelc_curl RUN apk --update add curl -FROM otel/opentelemetry-collector:latest@sha256:4cead03c35116d31cbf46bccb4952fbcd70d0eb8ae4f6fcfa699ebf2e8ed3f54 +FROM otel/opentelemetry-collector:latest@sha256:246dfe93f68e489a81f17b0335ca1c8e6f37bf69eb66aa9ba3375cc1743064b6 COPY --from=otelc_curl / / From 68b69628d826afe893aa1bd4aaa55b515eca8db0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:57:01 +0000 Subject: [PATCH 091/151] build(deps): bump the examples-ext-authz group in /examples/ext_authz/auth/grpc-service with 1 update (#32513) build(deps): bump the examples-ext-authz group Bumps the examples-ext-authz group in /examples/ext_authz/auth/grpc-service with 1 update: [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `google.golang.org/grpc` from 1.61.0 to 1.62.0 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.61.0...v1.62.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor dependency-group: examples-ext-authz ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/ext_authz/auth/grpc-service/go.mod | 4 +- examples/ext_authz/auth/grpc-service/go.sum | 140 ++++++++++++++++++-- 2 files changed, 132 insertions(+), 12 deletions(-) diff --git a/examples/ext_authz/auth/grpc-service/go.mod b/examples/ext_authz/auth/grpc-service/go.mod index 02aef65f5966..3e4511aeab2c 100644 --- a/examples/ext_authz/auth/grpc-service/go.mod +++ b/examples/ext_authz/auth/grpc-service/go.mod @@ -5,6 +5,6 @@ go 1.14 require ( github.com/envoyproxy/go-control-plane v0.12.0 github.com/golang/protobuf v1.5.3 - google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 - google.golang.org/grpc v1.61.0 + google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 + google.golang.org/grpc v1.62.0 ) diff --git a/examples/ext_authz/auth/grpc-service/go.sum b/examples/ext_authz/auth/grpc-service/go.sum index 52de68a07117..513e0ac97039 100644 --- a/examples/ext_authz/auth/grpc-service/go.sum +++ b/examples/ext_authz/auth/grpc-service/go.sum @@ -43,6 +43,8 @@ cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5x cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -72,6 +74,9 @@ cloud.google.com/go/aiplatform v1.51.0/go.mod h1:IRc2b8XAMTa9ZmfJV1BCCQbieWWvDnP cloud.google.com/go/aiplatform v1.51.1/go.mod h1:kY3nIMAVQOK2XDqDPHaOuD9e+FdMA6OOpfBjsvaFSOo= cloud.google.com/go/aiplatform v1.51.2/go.mod h1:hCqVYB3mY45w99TmetEoe8eCQEwZEp9WHxeZdcv9phw= cloud.google.com/go/aiplatform v1.52.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.54.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.57.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= @@ -82,6 +87,7 @@ cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N cloud.google.com/go/analytics v0.21.4/go.mod h1:zZgNCxLCy8b2rKKVfC1YkC2vTrpfZmeRCySM3aUbskA= cloud.google.com/go/analytics v0.21.5/go.mod h1:BQtOBHWTlJ96axpPPnw5CvGJ6i3Ve/qX2fTxR8qWyr8= cloud.google.com/go/analytics v0.21.6/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= +cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= @@ -149,6 +155,8 @@ cloud.google.com/go/asset v1.15.0/go.mod h1:tpKafV6mEut3+vN9ScGvCHXHj7FALFVta+ok cloud.google.com/go/asset v1.15.1/go.mod h1:yX/amTvFWRpp5rcFq6XbCxzKT8RJUam1UoboE179jU4= cloud.google.com/go/asset v1.15.2/go.mod h1:B6H5tclkXvXz7PD22qCA2TDxSVQfasa3iDlM89O2NXs= cloud.google.com/go/asset v1.15.3/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.16.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= @@ -185,6 +193,7 @@ cloud.google.com/go/batch v1.5.0/go.mod h1:KdBmDD61K0ovcxoRHGrN6GmOBWeAOyCgKD0Mu cloud.google.com/go/batch v1.5.1/go.mod h1:RpBuIYLkQu8+CWDk3dFD/t/jOCGuUpkpX+Y0n1Xccs8= cloud.google.com/go/batch v1.6.1/go.mod h1:urdpD13zPe6YOK+6iZs/8/x2VBRofvblLpx0t57vM98= cloud.google.com/go/batch v1.6.3/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= +cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= @@ -212,6 +221,7 @@ cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6Pm cloud.google.com/go/bigquery v1.55.0/go.mod h1:9Y5I3PN9kQWuid6183JFhOGOW3GcirA5LpsKCUn+2ec= cloud.google.com/go/bigquery v1.56.0/go.mod h1:KDcsploXTEY7XT3fDQzMUZlpQLHzE4itubHrnmhUrZA= cloud.google.com/go/bigquery v1.57.1/go.mod h1:iYzC0tGVWt1jqSzBHqCr3lrRn0u13E8e+AqowBsDgug= +cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= @@ -224,6 +234,7 @@ cloud.google.com/go/billing v1.17.1/go.mod h1:Z9+vZXEq+HwH7bhJkyI4OQcR6TSbeMrjlp cloud.google.com/go/billing v1.17.2/go.mod h1:u/AdV/3wr3xoRBk5xvUzYMS1IawOAPwQMuHgHMdljDg= cloud.google.com/go/billing v1.17.3/go.mod h1:z83AkoZ7mZwBGT3yTnt6rSGI1OOsHSIi6a5M3mJ8NaU= cloud.google.com/go/billing v1.17.4/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= +cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= @@ -234,6 +245,7 @@ cloud.google.com/go/binaryauthorization v1.7.0/go.mod h1:Zn+S6QqTMn6odcMU1zDZCJx cloud.google.com/go/binaryauthorization v1.7.1/go.mod h1:GTAyfRWYgcbsP3NJogpV3yeunbUIjx2T9xVeYovtURE= cloud.google.com/go/binaryauthorization v1.7.2/go.mod h1:kFK5fQtxEp97m92ziy+hbu+uKocka1qRRL8MVJIgjv0= cloud.google.com/go/binaryauthorization v1.7.3/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= +cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= @@ -250,6 +262,7 @@ cloud.google.com/go/channel v1.17.0/go.mod h1:RpbhJsGi/lXWAUM1eF4IbQGbsfVlg2o8Ii cloud.google.com/go/channel v1.17.1/go.mod h1:xqfzcOZAcP4b/hUDH0GkGg1Sd5to6di1HOJn/pi5uBQ= cloud.google.com/go/channel v1.17.2/go.mod h1:aT2LhnftnyfQceFql5I/mP8mIbiiJS4lWqgXA815zMk= cloud.google.com/go/channel v1.17.3/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= +cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= @@ -261,6 +274,7 @@ cloud.google.com/go/cloudbuild v1.14.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2 cloud.google.com/go/cloudbuild v1.14.1/go.mod h1:K7wGc/3zfvmYWOWwYTgF/d/UVJhS4pu+HAy7PL7mCsU= cloud.google.com/go/cloudbuild v1.14.2/go.mod h1:Bn6RO0mBYk8Vlrt+8NLrru7WXlQ9/RDWz2uo5KG1/sg= cloud.google.com/go/cloudbuild v1.14.3/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= +cloud.google.com/go/cloudbuild v1.15.0/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= @@ -315,6 +329,8 @@ cloud.google.com/go/contactcenterinsights v1.11.0/go.mod h1:hutBdImE4XNZ1NV4vbPJ cloud.google.com/go/contactcenterinsights v1.11.1/go.mod h1:FeNP3Kg8iteKM80lMwSk3zZZKVxr+PGnAId6soKuXwE= cloud.google.com/go/contactcenterinsights v1.11.2/go.mod h1:A9PIR5ov5cRcd28KlDbmmXE8Aay+Gccer2h4wzkYFso= cloud.google.com/go/contactcenterinsights v1.11.3/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.0/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= @@ -326,6 +342,8 @@ cloud.google.com/go/container v1.26.0/go.mod h1:YJCmRet6+6jnYYRS000T6k0D0xUXQgBS cloud.google.com/go/container v1.26.1/go.mod h1:5smONjPRUxeEpDG7bMKWfDL4sauswqEtnBK1/KKpR04= cloud.google.com/go/container v1.26.2/go.mod h1:YlO84xCt5xupVbLaMY4s3XNE79MUJ+49VmkInr6HvF4= cloud.google.com/go/container v1.27.1/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.28.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= @@ -351,6 +369,8 @@ cloud.google.com/go/datacatalog v1.18.0/go.mod h1:nCSYFHgtxh2MiEktWIz71s/X+7ds/U cloud.google.com/go/datacatalog v1.18.1/go.mod h1:TzAWaz+ON1tkNr4MOcak8EBHX7wIRX/gZKM+yTVsv+A= cloud.google.com/go/datacatalog v1.18.2/go.mod h1:SPVgWW2WEMuWHA+fHodYjmxPiMqcOiWfhc9OD5msigk= cloud.google.com/go/datacatalog v1.18.3/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= +cloud.google.com/go/datacatalog v1.19.0/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= +cloud.google.com/go/datacatalog v1.19.2/go.mod h1:2YbODwmhpLM4lOFe3PuEhHK9EyTzQJ5AXgIy7EDKTEE= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= @@ -391,6 +411,9 @@ cloud.google.com/go/dataplex v1.9.1/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MP cloud.google.com/go/dataplex v1.10.1/go.mod h1:1MzmBv8FvjYfc7vDdxhnLFNskikkB+3vl475/XdCDhs= cloud.google.com/go/dataplex v1.10.2/go.mod h1:xdC8URdTrCrZMW6keY779ZT1cTOfV8KEPNsw+LTRT1Y= cloud.google.com/go/dataplex v1.11.1/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.11.2/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.13.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= @@ -399,6 +422,7 @@ cloud.google.com/go/dataproc/v2 v2.2.0/go.mod h1:lZR7AQtwZPvmINx5J87DSOOpTfof9LV cloud.google.com/go/dataproc/v2 v2.2.1/go.mod h1:QdAJLaBjh+l4PVlVZcmrmhGccosY/omC1qwfQ61Zv/o= cloud.google.com/go/dataproc/v2 v2.2.2/go.mod h1:aocQywVmQVF4i8CL740rNI/ZRpsaaC1Wh2++BJ7HEJ4= cloud.google.com/go/dataproc/v2 v2.2.3/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= +cloud.google.com/go/dataproc/v2 v2.3.0/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= @@ -435,6 +459,9 @@ cloud.google.com/go/deploy v1.13.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCN cloud.google.com/go/deploy v1.13.1/go.mod h1:8jeadyLkH9qu9xgO3hVWw8jVr29N1mnW42gRJT8GY6g= cloud.google.com/go/deploy v1.14.1/go.mod h1:N8S0b+aIHSEeSr5ORVoC0+/mOPUysVt8ae4QkZYolAw= cloud.google.com/go/deploy v1.14.2/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.15.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.16.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.17.0/go.mod h1:XBr42U5jIr64t92gcpOXxNrqL2PStQCXHuKK5GRUuYo= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= @@ -450,6 +477,9 @@ cloud.google.com/go/dialogflow v1.44.0/go.mod h1:pDUJdi4elL0MFmt1REMvFkdsUTYSHq+ cloud.google.com/go/dialogflow v1.44.1/go.mod h1:n/h+/N2ouKOO+rbe/ZnI186xImpqvCVj2DdsWS/0EAk= cloud.google.com/go/dialogflow v1.44.2/go.mod h1:QzFYndeJhpVPElnFkUXxdlptx0wPnBWLCBT9BvtC3/c= cloud.google.com/go/dialogflow v1.44.3/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.47.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.1/go.mod h1:C1sjs2/g9cEwjCltkKeYp3FFpz8BOzNondEaAlCpt+A= cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= @@ -470,6 +500,8 @@ cloud.google.com/go/documentai v1.23.0/go.mod h1:LKs22aDHbJv7ufXuPypzRO7rG3ALLJx cloud.google.com/go/documentai v1.23.2/go.mod h1:Q/wcRT+qnuXOpjAkvOV4A+IeQl04q2/ReT7SSbytLSo= cloud.google.com/go/documentai v1.23.4/go.mod h1:4MYAaEMnADPN1LPN5xboDR5QVB6AgsaxgFdJhitlE2Y= cloud.google.com/go/documentai v1.23.5/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.6/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= @@ -510,6 +542,7 @@ cloud.google.com/go/filestore v1.7.1/go.mod h1:y10jsorq40JJnjR/lQ8AfFbbcGlw3g+Dp cloud.google.com/go/filestore v1.7.2/go.mod h1:TYOlyJs25f/omgj+vY7/tIG/E7BX369triSPzE4LdgE= cloud.google.com/go/filestore v1.7.3/go.mod h1:Qp8WaEERR3cSkxToxFPHh/b8AACkSut+4qlCjAmKTV0= cloud.google.com/go/filestore v1.7.4/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= +cloud.google.com/go/filestore v1.8.0/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/firestore v1.11.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= cloud.google.com/go/firestore v1.12.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= @@ -563,6 +596,7 @@ cloud.google.com/go/gkemulticloud v1.0.0/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZVi cloud.google.com/go/gkemulticloud v1.0.1/go.mod h1:AcrGoin6VLKT/fwZEYuqvVominLriQBCKmbjtnbMjG8= cloud.google.com/go/gkemulticloud v1.0.2/go.mod h1:+ee5VXxKb3H1l4LZAcgWB/rvI16VTNTrInWxDjAGsGo= cloud.google.com/go/gkemulticloud v1.0.3/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= +cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= @@ -647,6 +681,7 @@ cloud.google.com/go/lifesciences v0.9.4/go.mod h1:bhm64duKhMi7s9jR9WYJYvjAFJwRqN cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= +cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= @@ -671,6 +706,8 @@ cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9v cloud.google.com/go/maps v1.4.1/go.mod h1:BxSa0BnW1g2U2gNdbq5zikLlHUuHW0GFWh7sgML2kIY= cloud.google.com/go/maps v1.5.1/go.mod h1:NPMZw1LJwQZYCfz4y+EIw+SI+24A4bpdFJqdKVr0lt4= cloud.google.com/go/maps v1.6.1/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.2/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.3/go.mod h1:VGAn809ADswi1ASofL5lveOHPnE6Rk/SFTTBx1yuOLw= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= @@ -707,6 +744,7 @@ cloud.google.com/go/monitoring v1.16.0/go.mod h1:Ptp15HgAyM1fNICAojDMoNc/wUmn67m cloud.google.com/go/monitoring v1.16.1/go.mod h1:6HsxddR+3y9j+o/cMJH6q/KJ/CBTvM/38L/1m7bTRJ4= cloud.google.com/go/monitoring v1.16.2/go.mod h1:B44KGwi4ZCF8Rk/5n+FWeispDXoKSk9oss2QNlXJBgc= cloud.google.com/go/monitoring v1.16.3/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= @@ -769,6 +807,7 @@ cloud.google.com/go/orgpolicy v1.11.1/go.mod h1:8+E3jQcpZJQliP+zaFfayC2Pg5bmhuLK cloud.google.com/go/orgpolicy v1.11.2/go.mod h1:biRDpNwfyytYnmCRWZWxrKF22Nkz9eNVj9zyaBdpm1o= cloud.google.com/go/orgpolicy v1.11.3/go.mod h1:oKAtJ/gkMjum5icv2aujkP4CxROxPXsBbYGCDbPO8MM= cloud.google.com/go/orgpolicy v1.11.4/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= +cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= @@ -789,6 +828,7 @@ cloud.google.com/go/oslogin v1.11.0/go.mod h1:8GMTJs4X2nOAUVJiPGqIWVcDaF0eniEto3 cloud.google.com/go/oslogin v1.11.1/go.mod h1:OhD2icArCVNUxKqtK0mcSmKL7lgr0LVlQz+v9s1ujTg= cloud.google.com/go/oslogin v1.12.1/go.mod h1:VfwTeFJGbnakxAY236eN8fsnglLiVXndlbcNomY4iZU= cloud.google.com/go/oslogin v1.12.2/go.mod h1:CQ3V8Jvw4Qo4WRhNPF0o+HAM4DiLuE27Ul9CX9g2QdY= +cloud.google.com/go/oslogin v1.13.0/go.mod h1:xPJqLwpTZ90LSE5IL1/svko+6c5avZLluiyylMb/sRA= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= @@ -824,6 +864,7 @@ cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9 cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= cloud.google.com/go/pubsub v1.32.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= +cloud.google.com/go/pubsub v1.34.0/go.mod h1:alj4l4rBg+N3YTFDDC+/YyFTs6JAjam2QfYsddcAW4c= cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= @@ -841,6 +882,8 @@ cloud.google.com/go/recaptchaenterprise/v2 v2.8.0/go.mod h1:QuE8EdU9dEnesG8/kG3X cloud.google.com/go/recaptchaenterprise/v2 v2.8.1/go.mod h1:JZYZJOeZjgSSTGP4uz7NlQ4/d1w5hGmksVgM0lbEij0= cloud.google.com/go/recaptchaenterprise/v2 v2.8.2/go.mod h1:kpaDBOpkwD4G0GVMzG1W6Doy1tFFC97XAV3xy+Rd/pw= cloud.google.com/go/recaptchaenterprise/v2 v2.8.3/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.8.4/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= @@ -858,6 +901,7 @@ cloud.google.com/go/recommender v1.11.0/go.mod h1:kPiRQhPyTJ9kyXPCG6u/dlPLbYfFlk cloud.google.com/go/recommender v1.11.1/go.mod h1:sGwFFAyI57v2Hc5LbIj+lTwXipGu9NW015rkaEM5B18= cloud.google.com/go/recommender v1.11.2/go.mod h1:AeoJuzOvFR/emIcXdVFkspVXVTYpliRCmKNYDnyBv6Y= cloud.google.com/go/recommender v1.11.3/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= +cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= @@ -911,6 +955,7 @@ cloud.google.com/go/scheduler v1.10.1/go.mod h1:R63Ldltd47Bs4gnhQkmNDse5w8gBRrhO cloud.google.com/go/scheduler v1.10.2/go.mod h1:O3jX6HRH5eKCA3FutMw375XHZJudNIKVonSCHv7ropY= cloud.google.com/go/scheduler v1.10.3/go.mod h1:8ANskEM33+sIbpJ+R4xRfw/jzOG+ZFE8WVLy7/yGvbc= cloud.google.com/go/scheduler v1.10.4/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= +cloud.google.com/go/scheduler v1.10.5/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= @@ -940,6 +985,7 @@ cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY cloud.google.com/go/securitycenter v1.23.1/go.mod h1:w2HV3Mv/yKhbXKwOCu2i8bCuLtNP1IMHuiYQn4HJq5s= cloud.google.com/go/securitycenter v1.24.1/go.mod h1:3h9IdjjHhVMXdQnmqzVnM7b0wMn/1O/U20eWVpMpZjI= cloud.google.com/go/securitycenter v1.24.2/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= +cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= @@ -978,6 +1024,10 @@ cloud.google.com/go/spanner v1.47.0/go.mod h1:IXsJwVW2j4UKs0eYDqodab6HgGuA1bViSq cloud.google.com/go/spanner v1.49.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= cloud.google.com/go/spanner v1.50.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= cloud.google.com/go/spanner v1.51.0/go.mod h1:c5KNo5LQ1X5tJwma9rSQZsXNBDNvj4/n8BVc3LNahq0= +cloud.google.com/go/spanner v1.53.0/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.53.1/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.54.0/go.mod h1:wZvSQVBgngF0Gq86fKup6KIYmN2be7uOKjtK97X+bQU= +cloud.google.com/go/spanner v1.55.0/go.mod h1:HXEznMUVhC+PC+HDyo9YFG2Ajj5BQDkcbqB9Z2Ffxi0= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= @@ -989,6 +1039,7 @@ cloud.google.com/go/speech v1.19.0/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ cloud.google.com/go/speech v1.19.1/go.mod h1:WcuaWz/3hOlzPFOVo9DUsblMIHwxP589y6ZMtaG+iAA= cloud.google.com/go/speech v1.19.2/go.mod h1:2OYFfj+Ch5LWjsaSINuCZsre/789zlcCI3SY4oAi2oI= cloud.google.com/go/speech v1.20.1/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= +cloud.google.com/go/speech v1.21.0/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -1001,6 +1052,7 @@ cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1051,6 +1103,7 @@ cloud.google.com/go/translate v1.9.0/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNW cloud.google.com/go/translate v1.9.1/go.mod h1:TWIgDZknq2+JD4iRcojgeDtqGEp154HN/uL6hMvylS8= cloud.google.com/go/translate v1.9.2/go.mod h1:E3Tc6rUTsQkVrXW6avbUhKJSr7ZE3j7zNmqzXKHqRrY= cloud.google.com/go/translate v1.9.3/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= +cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= @@ -1148,6 +1201,7 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= +github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -1176,8 +1230,8 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -1205,8 +1259,10 @@ github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0+ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -1220,6 +1276,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -1228,6 +1288,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -1284,6 +1345,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-pkcs11 v0.2.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -1313,6 +1375,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -1345,8 +1409,10 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9K github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1375,6 +1441,8 @@ github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4 github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -1402,10 +1470,12 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -1433,6 +1503,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1447,6 +1527,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= @@ -1456,6 +1537,9 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1515,6 +1599,7 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1578,10 +1663,13 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1615,7 +1703,8 @@ golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1636,6 +1725,7 @@ golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1720,8 +1810,10 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1738,6 +1830,8 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1766,6 +1860,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1830,6 +1925,7 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1838,6 +1934,7 @@ golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= @@ -1911,6 +2008,8 @@ google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvy google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= +google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2069,8 +2168,14 @@ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqv google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -2088,9 +2193,16 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go. google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0/go.mod h1:guYXGPwC6jwxgWKW5Y405fKWOFNwlvUlUnzyp9i0uqo= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= @@ -2107,8 +2219,14 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2157,8 +2275,10 @@ google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From e7ae8bb8a2a93cbaf47bf45675b9af59e42e6598 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:57:14 +0000 Subject: [PATCH 092/151] build(deps): bump the examples-grpc-bridge group in /examples/grpc-bridge/client with 2 updates (#32515) build(deps): bump the examples-grpc-bridge group Bumps the examples-grpc-bridge group in /examples/grpc-bridge/client with 2 updates: [grpcio](https://github.com/grpc/grpc) and [grpcio-tools](https://github.com/grpc/grpc). Updates `grpcio` from 1.60.1 to 1.62.0 - [Release notes](https://github.com/grpc/grpc/releases) - [Changelog](https://github.com/grpc/grpc/blob/master/doc/grpc_release_schedule.md) - [Commits](https://github.com/grpc/grpc/compare/v1.60.1...v1.62.0) Updates `grpcio-tools` from 1.60.1 to 1.62.0 - [Release notes](https://github.com/grpc/grpc/releases) - [Changelog](https://github.com/grpc/grpc/blob/master/doc/grpc_release_schedule.md) - [Commits](https://github.com/grpc/grpc/compare/v1.60.1...v1.62.0) --- updated-dependencies: - dependency-name: grpcio dependency-type: direct:production update-type: version-update:semver-minor dependency-group: examples-grpc-bridge - dependency-name: grpcio-tools dependency-type: direct:production update-type: version-update:semver-minor dependency-group: examples-grpc-bridge ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/grpc-bridge/client/requirements.txt | 220 +++++++++---------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/examples/grpc-bridge/client/requirements.txt b/examples/grpc-bridge/client/requirements.txt index 27c967211094..f7087d393f20 100644 --- a/examples/grpc-bridge/client/requirements.txt +++ b/examples/grpc-bridge/client/requirements.txt @@ -100,119 +100,119 @@ charset-normalizer==3.3.0 \ --hash=sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e \ --hash=sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8 # via requests -grpcio==1.60.1 \ - --hash=sha256:0250a7a70b14000fa311de04b169cc7480be6c1a769b190769d347939d3232a8 \ - --hash=sha256:069fe2aeee02dfd2135d562d0663fe70fbb69d5eed6eb3389042a7e963b54de8 \ - --hash=sha256:082081e6a36b6eb5cf0fd9a897fe777dbb3802176ffd08e3ec6567edd85bc104 \ - --hash=sha256:0c5807e9152eff15f1d48f6b9ad3749196f79a4a050469d99eecb679be592acc \ - --hash=sha256:14e8f2c84c0832773fb3958240c69def72357bc11392571f87b2d7b91e0bb092 \ - --hash=sha256:2a6087f234cb570008a6041c8ffd1b7d657b397fdd6d26e83d72283dae3527b1 \ - --hash=sha256:2bb2a2911b028f01c8c64d126f6b632fcd8a9ac975aa1b3855766c94e4107180 \ - --hash=sha256:2f44c32aef186bbba254129cea1df08a20be414144ac3bdf0e84b24e3f3b2e05 \ - --hash=sha256:30e980cd6db1088c144b92fe376747328d5554bc7960ce583ec7b7d81cd47287 \ - --hash=sha256:33aed0a431f5befeffd9d346b0fa44b2c01aa4aeae5ea5b2c03d3e25e0071216 \ - --hash=sha256:33bdea30dcfd4f87b045d404388469eb48a48c33a6195a043d116ed1b9a0196c \ - --hash=sha256:39aa848794b887120b1d35b1b994e445cc028ff602ef267f87c38122c1add50d \ - --hash=sha256:4216e67ad9a4769117433814956031cb300f85edc855252a645a9a724b3b6594 \ - --hash=sha256:49c9b6a510e3ed8df5f6f4f3c34d7fbf2d2cae048ee90a45cd7415abab72912c \ - --hash=sha256:4eec8b8c1c2c9b7125508ff7c89d5701bf933c99d3910e446ed531cd16ad5d87 \ - --hash=sha256:50d56280b482875d1f9128ce596e59031a226a8b84bec88cb2bf76c289f5d0de \ - --hash=sha256:53b69e79d00f78c81eecfb38f4516080dc7f36a198b6b37b928f1c13b3c063e9 \ - --hash=sha256:55ccb7db5a665079d68b5c7c86359ebd5ebf31a19bc1a91c982fd622f1e31ff2 \ - --hash=sha256:5a1ebbae7e2214f51b1f23b57bf98eeed2cf1ba84e4d523c48c36d5b2f8829ff \ - --hash=sha256:61b7199cd2a55e62e45bfb629a35b71fc2c0cb88f686a047f25b1112d3810904 \ - --hash=sha256:660fc6b9c2a9ea3bb2a7e64ba878c98339abaf1811edca904ac85e9e662f1d73 \ - --hash=sha256:6d140bdeb26cad8b93c1455fa00573c05592793c32053d6e0016ce05ba267549 \ - --hash=sha256:6e490fa5f7f5326222cb9f0b78f207a2b218a14edf39602e083d5f617354306f \ - --hash=sha256:6ecf21d20d02d1733e9c820fb5c114c749d888704a7ec824b545c12e78734d1c \ - --hash=sha256:70c83bb530572917be20c21f3b6be92cd86b9aecb44b0c18b1d3b2cc3ae47df0 \ - --hash=sha256:72153a0d2e425f45b884540a61c6639436ddafa1829a42056aa5764b84108b8e \ - --hash=sha256:73e14acd3d4247169955fae8fb103a2b900cfad21d0c35f0dcd0fdd54cd60367 \ - --hash=sha256:76eaaba891083fcbe167aa0f03363311a9f12da975b025d30e94b93ac7a765fc \ - --hash=sha256:79ae0dc785504cb1e1788758c588c711f4e4a0195d70dff53db203c95a0bd303 \ - --hash=sha256:7d142bcd604166417929b071cd396aa13c565749a4c840d6c702727a59d835eb \ - --hash=sha256:8c9554ca8e26241dabe7951aa1fa03a1ba0856688ecd7e7bdbdd286ebc272e4c \ - --hash=sha256:8d488fbdbf04283f0d20742b64968d44825617aa6717b07c006168ed16488804 \ - --hash=sha256:91422ba785a8e7a18725b1dc40fbd88f08a5bb4c7f1b3e8739cab24b04fa8a03 \ - --hash=sha256:9a66f4d2a005bc78e61d805ed95dedfcb35efa84b7bba0403c6d60d13a3de2d6 \ - --hash=sha256:9b106bc52e7f28170e624ba61cc7dc6829566e535a6ec68528f8e1afbed1c41f \ - --hash=sha256:9b54577032d4f235452f77a83169b6527bf4b77d73aeada97d45b2aaf1bf5ce0 \ - --hash=sha256:a09506eb48fa5493c58f946c46754ef22f3ec0df64f2b5149373ff31fb67f3dd \ - --hash=sha256:a212e5dea1a4182e40cd3e4067ee46be9d10418092ce3627475e995cca95de21 \ - --hash=sha256:a731ac5cffc34dac62053e0da90f0c0b8560396a19f69d9703e88240c8f05858 \ - --hash=sha256:af5ef6cfaf0d023c00002ba25d0751e5995fa0e4c9eec6cd263c30352662cbce \ - --hash=sha256:b58b855d0071575ea9c7bc0d84a06d2edfbfccec52e9657864386381a7ce1ae9 \ - --hash=sha256:bc808924470643b82b14fe121923c30ec211d8c693e747eba8a7414bc4351a23 \ - --hash=sha256:c557e94e91a983e5b1e9c60076a8fd79fea1e7e06848eb2e48d0ccfb30f6e073 \ - --hash=sha256:c71be3f86d67d8d1311c6076a4ba3b75ba5703c0b856b4e691c9097f9b1e8bd2 \ - --hash=sha256:c8754c75f55781515a3005063d9a05878b2cfb3cb7e41d5401ad0cf19de14872 \ - --hash=sha256:cb0af13433dbbd1c806e671d81ec75bd324af6ef75171fd7815ca3074fe32bfe \ - --hash=sha256:cba6209c96828711cb7c8fcb45ecef8c8859238baf15119daa1bef0f6c84bfe7 \ - --hash=sha256:cf77f8cf2a651fbd869fbdcb4a1931464189cd210abc4cfad357f1cacc8642a6 \ - --hash=sha256:d7404cebcdb11bb5bd40bf94131faf7e9a7c10a6c60358580fe83913f360f929 \ - --hash=sha256:dd1d3a8d1d2e50ad9b59e10aa7f07c7d1be2b367f3f2d33c5fade96ed5460962 \ - --hash=sha256:e5d97c65ea7e097056f3d1ead77040ebc236feaf7f71489383d20f3b4c28412a \ - --hash=sha256:f1c3dc536b3ee124e8b24feb7533e5c70b9f2ef833e3b2e5513b2897fd46763a \ - --hash=sha256:f2212796593ad1d0235068c79836861f2201fc7137a99aa2fea7beeb3b101177 \ - --hash=sha256:fead980fbc68512dfd4e0c7b1f5754c2a8e5015a04dea454b9cada54a8423525 +grpcio==1.62.0 \ + --hash=sha256:0b9179478b09ee22f4a36b40ca87ad43376acdccc816ce7c2193a9061bf35701 \ + --hash=sha256:0d3dee701e48ee76b7d6fbbba18ba8bc142e5b231ef7d3d97065204702224e0e \ + --hash=sha256:0d7ae7fc7dbbf2d78d6323641ded767d9ec6d121aaf931ec4a5c50797b886532 \ + --hash=sha256:0e97f37a3b7c89f9125b92d22e9c8323f4e76e7993ba7049b9f4ccbe8bae958a \ + --hash=sha256:136ffd79791b1eddda8d827b607a6285474ff8a1a5735c4947b58c481e5e4271 \ + --hash=sha256:1bc8449084fe395575ed24809752e1dc4592bb70900a03ca42bf236ed5bf008f \ + --hash=sha256:1eda79574aec8ec4d00768dcb07daba60ed08ef32583b62b90bbf274b3c279f7 \ + --hash=sha256:29cb592c4ce64a023712875368bcae13938c7f03e99f080407e20ffe0a9aa33b \ + --hash=sha256:2c1488b31a521fbba50ae86423f5306668d6f3a46d124f7819c603979fc538c4 \ + --hash=sha256:2e84bfb2a734e4a234b116be208d6f0214e68dcf7804306f97962f93c22a1839 \ + --hash=sha256:2f3d9a4d0abb57e5f49ed5039d3ed375826c2635751ab89dcc25932ff683bbb6 \ + --hash=sha256:36df33080cd7897623feff57831eb83c98b84640b016ce443305977fac7566fb \ + --hash=sha256:38f69de9c28c1e7a8fd24e4af4264726637b72f27c2099eaea6e513e7142b47e \ + --hash=sha256:39cd45bd82a2e510e591ca2ddbe22352e8413378852ae814549c162cf3992a93 \ + --hash=sha256:3fa15850a6aba230eed06b236287c50d65a98f05054a0f01ccedf8e1cc89d57f \ + --hash=sha256:4cd356211579043fce9f52acc861e519316fff93980a212c8109cca8f47366b6 \ + --hash=sha256:56ca7ba0b51ed0de1646f1735154143dcbdf9ec2dbe8cc6645def299bb527ca1 \ + --hash=sha256:5e709f7c8028ce0443bddc290fb9c967c1e0e9159ef7a030e8c21cac1feabd35 \ + --hash=sha256:614c3ed234208e76991992342bab725f379cc81c7dd5035ee1de2f7e3f7a9842 \ + --hash=sha256:62aa1659d8b6aad7329ede5d5b077e3d71bf488d85795db517118c390358d5f6 \ + --hash=sha256:62ccb92f594d3d9fcd00064b149a0187c246b11e46ff1b7935191f169227f04c \ + --hash=sha256:662d3df5314ecde3184cf87ddd2c3a66095b3acbb2d57a8cada571747af03873 \ + --hash=sha256:748496af9238ac78dcd98cce65421f1adce28c3979393e3609683fcd7f3880d7 \ + --hash=sha256:77d48e5b1f8f4204889f1acf30bb57c30378e17c8d20df5acbe8029e985f735c \ + --hash=sha256:7a195531828b46ea9c4623c47e1dc45650fc7206f8a71825898dd4c9004b0928 \ + --hash=sha256:7e1f51e2a460b7394670fdb615e26d31d3260015154ea4f1501a45047abe06c9 \ + --hash=sha256:7eea57444a354ee217fda23f4b479a4cdfea35fb918ca0d8a0e73c271e52c09c \ + --hash=sha256:7f9d6c3223914abb51ac564dc9c3782d23ca445d2864321b9059d62d47144021 \ + --hash=sha256:81531632f93fece32b2762247c4c169021177e58e725494f9a746ca62c83acaa \ + --hash=sha256:81d444e5e182be4c7856cd33a610154fe9ea1726bd071d07e7ba13fafd202e38 \ + --hash=sha256:821a44bd63d0f04e33cf4ddf33c14cae176346486b0df08b41a6132b976de5fc \ + --hash=sha256:88f41f33da3840b4a9bbec68079096d4caf629e2c6ed3a72112159d570d98ebe \ + --hash=sha256:8aab8f90b2a41208c0a071ec39a6e5dbba16fd827455aaa070fec241624ccef8 \ + --hash=sha256:921148f57c2e4b076af59a815467d399b7447f6e0ee10ef6d2601eb1e9c7f402 \ + --hash=sha256:92cdb616be44c8ac23a57cce0243af0137a10aa82234f23cd46e69e115071388 \ + --hash=sha256:95370c71b8c9062f9ea033a0867c4c73d6f0ff35113ebd2618171ec1f1e903e0 \ + --hash=sha256:98d8f4eb91f1ce0735bf0b67c3b2a4fea68b52b2fd13dc4318583181f9219b4b \ + --hash=sha256:a33f2bfd8a58a02aab93f94f6c61279be0f48f99fcca20ebaee67576cd57307b \ + --hash=sha256:ab140a3542bbcea37162bdfc12ce0d47a3cda3f2d91b752a124cc9fe6776a9e2 \ + --hash=sha256:b3d3d755cfa331d6090e13aac276d4a3fb828bf935449dc16c3d554bf366136b \ + --hash=sha256:b71c65427bf0ec6a8b48c68c17356cb9fbfc96b1130d20a07cb462f4e4dcdcd5 \ + --hash=sha256:b7a6be562dd18e5d5bec146ae9537f20ae1253beb971c0164f1e8a2f5a27e829 \ + --hash=sha256:bcff647e7fe25495e7719f779cc219bbb90b9e79fbd1ce5bda6aae2567f469f2 \ + --hash=sha256:c912688acc05e4ff012c8891803659d6a8a8b5106f0f66e0aed3fb7e77898fa6 \ + --hash=sha256:ce1aafdf8d3f58cb67664f42a617af0e34555fe955450d42c19e4a6ad41c84bd \ + --hash=sha256:d6a56ba703be6b6267bf19423d888600c3f574ac7c2cc5e6220af90662a4d6b0 \ + --hash=sha256:e803e9b58d8f9b4ff0ea991611a8d51b31c68d2e24572cd1fe85e99e8cc1b4f8 \ + --hash=sha256:eef1d16ac26c5325e7d39f5452ea98d6988c700c427c52cbc7ce3201e6d93334 \ + --hash=sha256:f359d635ee9428f0294bea062bb60c478a8ddc44b0b6f8e1f42997e5dc12e2ee \ + --hash=sha256:f4c04fe33039b35b97c02d2901a164bbbb2f21fb9c4e2a45a959f0b044c3512c \ + --hash=sha256:f897b16190b46bc4d4aaf0a32a4b819d559a37a756d7c6b571e9562c360eed72 \ + --hash=sha256:fbe0c20ce9a1cff75cfb828b21f08d0a1ca527b67f2443174af6626798a754a4 \ + --hash=sha256:fc2836cb829895ee190813446dce63df67e6ed7b9bf76060262c55fcd097d270 \ + --hash=sha256:fcc98cff4084467839d0a20d16abc2a76005f3d1b38062464d088c07f500d170 # via # -r requirements.in # grpcio-tools -grpcio-tools==1.60.1 \ - --hash=sha256:075bb67895970f96aabc1761ca674bf4db193f8fcad387f08e50402023b5f953 \ - --hash=sha256:0aa34c7c21cff2177a4096b2b0d51dfbc9f8a41f929847a434e89b352c5a215d \ - --hash=sha256:0b62cb2d43a7f0eacc6a6962dfff7c2564874012e1a72ae4167e762f449e2912 \ - --hash=sha256:15f13e8f3d77b96adcb1e3615acec5b100bd836c6010c58a51465bcb9c06d128 \ - --hash=sha256:184b27333b627a7cc0972fb70d21a8bb7c02ac4a6febc16768d78ea8ff883ddd \ - --hash=sha256:18d7737f29ef5bbe3352547d0eccd080807834f00df223867dfc860bf81e9180 \ - --hash=sha256:1e96a532d38411f0543fe1903ff522f7142a9901afb0ed94de58d79caf1905be \ - --hash=sha256:214281cdafb7acfdcde848eca2de7c888a6e2b5cd25ab579712b965ea09a9cd4 \ - --hash=sha256:22ce3e3d861321d208d8bfd6161ab976623520b179712c90b2c175151463a6b1 \ - --hash=sha256:26f91161a91f1601777751230eaaafdf416fed08a15c3ba2ae391088e4a906c6 \ - --hash=sha256:284749d20fb22418f17d3d351b9eb838caf4a0393a9cb02c36e5c32fa4bbe9db \ - --hash=sha256:28ae665113affebdd109247386786e5ab4dccfcfad1b5f68e9cce2e326b57ee6 \ - --hash=sha256:2973f75e8ba5c551033a1d59cc97654f6f386deaf2559082011d245d7ed87bba \ - --hash=sha256:2a7fa55bc62d4b8ebe6fb26f8cf89df3cf3b504eb6c5f3a2f0174689d35fddb0 \ - --hash=sha256:2c19be2bba5583e30f88bb5d71b430176c396f0d6d0db3785e5845bfa3d28cd2 \ - --hash=sha256:31294b534f25f02ead204e58dcbe0e5437a95a1a6f276bb9378905595b02ff6d \ - --hash=sha256:3aeecd5b8faa2aab67e6c8b8a57e888c00ce70d39f331ede0a21312e92def1a6 \ - --hash=sha256:3fb6f4d2df0388c35c2804ba170f511238a681b679ead013bfe5e39d0ea9cf48 \ - --hash=sha256:3fcabf484720a9fa1690e2825fc940027a05a0c79a1075a730008ef634bd8ad2 \ - --hash=sha256:402efeec36d8b12b792bae8a900085416fc2f57a34b599445ace2e847b6b0d75 \ - --hash=sha256:40cd8268a675269ce59c4fa50877597ec638bb1099c52237bb726c8ac9791868 \ - --hash=sha256:46b495bae31c5d3f6ac0240eb848f0642b5410f80dff2aacdea20cdea3938c1d \ - --hash=sha256:4e66fe204da15e08e599adb3060109a42927c0868fe8933e2d341ea649eceb03 \ - --hash=sha256:5b4a939097005531edec331f22d0b82bff26e71ede009354d2f375b5d41e74f0 \ - --hash=sha256:5c7ed086fef5ff59f46d53a052b1934b73e0f7d12365d656d6af3a88057d5a3e \ - --hash=sha256:5ea6e397d87f458bb2c387a4a6e1b65df74ce5b5194a1f16850c38309012e981 \ - --hash=sha256:652b08c9fef39186ce4f97f05f5440c0ed41f117db0f7d6cb0e0d75dbc6afd3f \ - --hash=sha256:6801cfc5a85f0fb6fd12cade45942aaa1c814422328d594d12d364815fe34123 \ - --hash=sha256:8540f6480428a52614db71dd6394f52cbc0d2565b5ea1136a982f26390a42c7a \ - --hash=sha256:8c4b917aa4fcdc77990773063f0f14540aab8d4a8bf6c862b964a45d891a31d2 \ - --hash=sha256:985ac476da365267a2367ab20060f9096fbfc2e190fb02dd394f9ec05edf03ca \ - --hash=sha256:9aadc9c00baa2064baa4414cff7c269455449f14805a355226674d89c507342c \ - --hash=sha256:9bba347000f57dae8aea79c0d76ef7d72895597524d30d0170c7d1974a3a03f3 \ - --hash=sha256:a2bb8efc2cd64bd8f2779b426dd7e94e60924078ba5150cbbb60a846e62d1ed2 \ - --hash=sha256:a8cfab27ba2bd36a3e3b522aed686133531e8b919703d0247a0885dae8815317 \ - --hash=sha256:aafc94616c5f89c891d859057b194a153c451f9921053454e9d7d4cbf79047eb \ - --hash=sha256:acdba77584981fe799104aa545d9d97910bcf88c69b668b768c1f3e7d7e5afac \ - --hash=sha256:af88a2062b9c35034a80b25f289034b9c3c00c42bb88efaa465503a06fbd6a87 \ - --hash=sha256:b1041377cf32ee2338284ee26e6b9c10f9ea7728092376b19803dcb9b91d510d \ - --hash=sha256:b5ae375207af9aa82f516dcd513d2e0c83690b7788d45844daad846ed87550f8 \ - --hash=sha256:b6ef213cb0aecb2832ee82a2eac32f29f31f50b17ce020604d82205096a6bd0c \ - --hash=sha256:bba7230c60238c7a4ffa29f1aff6d78edb41f2c79cbe4443406472b1c80ccb5d \ - --hash=sha256:bd85f6c368b93ae45edf8568473053cb1cc075ef3489efb18f9832d4ecce062f \ - --hash=sha256:c1047bd831de5d9da761e9dc246988d5f07d722186938dfd5f34807398101010 \ - --hash=sha256:c20e752ff5057758845f4e5c7a298739bfba291f373ed18ea9c7c7acbe69e8ab \ - --hash=sha256:c354505e6a3d170da374f20404ea6a78135502df4f5534e5c532bdf24c4cc2a5 \ - --hash=sha256:cc8ba358d2c658c6ecbc58e779bf0fc5a673fecac015a70db27fc5b4d37b76b6 \ - --hash=sha256:cf945bd22f396c0d0c691e0990db2bfc4e77816b1edc2aea8a69c35ae721aac9 \ - --hash=sha256:d2c26ce5f774c98bd2d3d8d1703048394018b55d297ebdb41ed2ba35b9a34f68 \ - --hash=sha256:da08224ab8675c6d464b988bd8ca02cccd2bf0275bceefe8f6219bfd4a4f5e85 \ - --hash=sha256:dffa326cf901fe08a0e218d9fdf593f12276088a8caa07fcbec7d051149cf9ef \ - --hash=sha256:e529cd3d4109a6f4a3f7bdaca68946eb33734e2d7ffe861785a0586abe99ee67 \ - --hash=sha256:eba5fafd70585fbd4cb6ae45e3c5e11d8598e2426c9f289b78f682c0606e81cb \ - --hash=sha256:f95bdc6c7c50b7fc442e53537bc5b4eb8cab2a671c1da80d40b5a4ab1fd5d416 +grpcio-tools==1.62.0 \ + --hash=sha256:006ea0cc16e8bf8f307326e0556e1384f24abb402cc4e6a720aa1dfe8f268647 \ + --hash=sha256:0d9c9a4832f52c4597d6dc12d9ab3109c3bd0ee1686b8bf6d64f9eab4145e3cb \ + --hash=sha256:0e87f105f1d152934759f8975ed002d5ce057b3cdf1cc6cb63fe6008671a27b9 \ + --hash=sha256:14201950513636f515dd455a06890e3a21d115b943cf6a8f5af67ad1413cfa1f \ + --hash=sha256:17c16e9a89c0b9f4ff2b143f232c5256383453ce7b55fe981598f9517adc8252 \ + --hash=sha256:1927934dfba4658a97c2dab267e53ed239264d40fdd5b295fc317693543db85b \ + --hash=sha256:19b74e141937c885c9e56b6a7dfa190ca7d583bd48bce9171dd65bbf108b9271 \ + --hash=sha256:1faa5006fe9e7b9e65c47bc23f7cd333fdcdd4ba35d44080303848266db5ab05 \ + --hash=sha256:2e1fd7301d762bf5984b7e7fb62fce82cff864d75f0a57e15cfd07ae1bd79133 \ + --hash=sha256:2f5bd22203e64e1732e149bfdd3083716d038abca294e4e2852159b3d893f9ec \ + --hash=sha256:3730b1cd998a0cffc817602cc55e51f268fa97b3e38fa4bee578e3741474547a \ + --hash=sha256:3b526dc5566161a3a17599753838b9cfbdd4cb15b6ad419aae8a5d12053fa8ae \ + --hash=sha256:3bbe79b134dfb7c98cf60e4962e31039bef824834cc7034bdf1886a2ed1097f9 \ + --hash=sha256:3dee3be61d9032f777a9b4e2696ea3d0748a582cb99c672b5d41ca66821e8c87 \ + --hash=sha256:465c51ebaa184ee3bb619cd5bfaf562bbdde166f2822a6935461e6a741f5ac19 \ + --hash=sha256:523adf731fa4c5af0bf7ee2edb65e8c7ef4d9df9951461d6a18fe096688efd2d \ + --hash=sha256:52b216c458458f6c292e12428916e80974c5113abc505a61e7b0b9f8932a785d \ + --hash=sha256:54bb570bd963905de3bda596b35e06026552705edebbb2cb737b57aa5252b9e5 \ + --hash=sha256:563a75924109e75809b2919e68d7e6ae7872e63d20258aae7899b14f6ff9e18b \ + --hash=sha256:5a482d9625209023481e631c29a6df1392bfc49f9accfa880dabbacff642559a \ + --hash=sha256:5dacc691b18d2c294ea971720ff980a1e2d68a3f7ddcd2f0670b3204e81c4b18 \ + --hash=sha256:5eb63d9207b02a0fa30216907e1e7705cc2670f933e77236c6e0eb966ad3b4bf \ + --hash=sha256:5f8934715577c9cc0c792b8a77f7d0dd2bb60e951161b10c5f46b60856673240 \ + --hash=sha256:679cf2507090e010da73e5001665c76de2a5927b2e2110e459222b1c81cb10c2 \ + --hash=sha256:6999a4e705b03aacad46e625feb7610e47ec88dbd51220c2282b6334f90721fc \ + --hash=sha256:6b900ae319b6f9ac1be0ca572dfb41c23a7ff6fcbf36e3be6d3054e1e4c60de6 \ + --hash=sha256:711314cb4c6c8b3d51bafaee380ffa5012bd0567ed68f1b0b1fc07492b27acab \ + --hash=sha256:74196beed18383d53ff3e2412a6c1eefa3ff109e987be240368496bc3dcabc8b \ + --hash=sha256:75aca28cbeb605c59b5689a7e000fbc2bd659d2f322c58461f3912f00069f6da \ + --hash=sha256:77196c7ac8741d4a2aebb023bcc2964ac65ca44180fd791640889ab2afed3e47 \ + --hash=sha256:791aa220f8f1936e65bc079e9eb954fa0202a1f16e28b83956e59d17dface127 \ + --hash=sha256:7fca6ecfbbf0549058bb29dcc6e435d885b878d07701e77ac58e1e1f591736dc \ + --hash=sha256:84e27206bd884be83a7fdcef8be3c90eb1591341c0ba9b0d25ec9db1043ba2f2 \ + --hash=sha256:88aa62303278aec45bbb26bf679269c7890346c37140ae30e39da1070c341e11 \ + --hash=sha256:95e49839d49e79187c43cd63af5c206dc5743a01d7d3d2f039772fa743cbb30c \ + --hash=sha256:98ddf871c614cc0ed331c7159ebbbf5040be562279677d3bb97c2e6083539f72 \ + --hash=sha256:9ae5cd2f89e33a529790bf8aa59a459484edb05e4f58d4cf78836b9dfa1fab43 \ + --hash=sha256:a09db3688efd3499ce3c0b02c0bac0656abdab4cb99716f81ad879c08b92c56e \ + --hash=sha256:b46ba0b6552b4375ede65e0c89491af532635347f78d52a72f8a027529e713ed \ + --hash=sha256:b65288ebe12e38dd3650fea65d82fcce0d35df1ae4a770b525c10119ee71962f \ + --hash=sha256:bb6802d63e42734d2baf02e1343377fe18590ed6a1f5ffbdebbbe0f8331f176b \ + --hash=sha256:bf9f281f528e0220558d57e09b4518dec148dcb250d78bd9cbb27e09edabb3f9 \ + --hash=sha256:c85391e06620d6e16a56341caae5007d0c6219beba065e1e288f2523fba6a335 \ + --hash=sha256:cd1f4caeca614b04db803566473f7db0971e7a88268f95e4a529b0ace699b949 \ + --hash=sha256:d5652d3a52a2e8e1d9bdf28fbd15e21b166e31b968cd7c8c604bf31611c0bb5b \ + --hash=sha256:d5959e3df126931d28cd94dd5f0a708b7dd96019de80ab715fb922fd0c8a838d \ + --hash=sha256:dce5f04676cf94e6e2d13d7f91ac2de79097d86675bc4d404a3c24dcc0332c88 \ + --hash=sha256:e38d5800151e6804d500e329f7ddfb615c50eee0c1607593e3147a4b21037e40 \ + --hash=sha256:ec6f561c86fe13cff3be16f297cc05e1aa1274294524743a4cf91d971866fbb0 \ + --hash=sha256:ed6cf7ff4a10c46f85340f9c68982f9efb29f51ee4b66828310fcdf3c2d7ffd1 \ + --hash=sha256:f0884eaf6a2bbd7b03fea456e808909ee48dd4f7f455519d67defda791116368 \ + --hash=sha256:f3aaf3b20c0f7063856b2432335af8f76cf580f898e04085548cde28332d6833 \ + --hash=sha256:f54b5181784464bd3573ae7dbcf053da18a4b7a75fe19960791f383be3d035ca \ + --hash=sha256:f74e0053360e0eadd75193c0c379b6d7f51d074ebbff856bd41780e1a028b38d # via -r requirements.in idna==3.4 \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ From 827f03f5d562a7b8abca6aeddbb76c41fadf5005 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:57:24 +0000 Subject: [PATCH 093/151] build(deps): bump the examples-grpc-bridge group in /examples/grpc-bridge/server with 1 update (#32516) build(deps): bump the examples-grpc-bridge group Bumps the examples-grpc-bridge group in /examples/grpc-bridge/server with 1 update: [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `google.golang.org/grpc` from 1.61.1 to 1.62.0 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.61.1...v1.62.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor dependency-group: examples-grpc-bridge ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/grpc-bridge/server/go.mod | 2 +- examples/grpc-bridge/server/go.sum | 141 +++++++++++++++++++++++++++-- 2 files changed, 135 insertions(+), 8 deletions(-) diff --git a/examples/grpc-bridge/server/go.mod b/examples/grpc-bridge/server/go.mod index 606f3422bdb0..623db6c0619b 100644 --- a/examples/grpc-bridge/server/go.mod +++ b/examples/grpc-bridge/server/go.mod @@ -5,5 +5,5 @@ go 1.13 require ( github.com/golang/protobuf v1.5.3 golang.org/x/net v0.21.0 - google.golang.org/grpc v1.61.1 + google.golang.org/grpc v1.62.0 ) diff --git a/examples/grpc-bridge/server/go.sum b/examples/grpc-bridge/server/go.sum index 5dc0623cfe6d..885828afce3c 100644 --- a/examples/grpc-bridge/server/go.sum +++ b/examples/grpc-bridge/server/go.sum @@ -43,6 +43,8 @@ cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5x cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -72,6 +74,9 @@ cloud.google.com/go/aiplatform v1.51.0/go.mod h1:IRc2b8XAMTa9ZmfJV1BCCQbieWWvDnP cloud.google.com/go/aiplatform v1.51.1/go.mod h1:kY3nIMAVQOK2XDqDPHaOuD9e+FdMA6OOpfBjsvaFSOo= cloud.google.com/go/aiplatform v1.51.2/go.mod h1:hCqVYB3mY45w99TmetEoe8eCQEwZEp9WHxeZdcv9phw= cloud.google.com/go/aiplatform v1.52.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.54.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.57.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= @@ -82,6 +87,7 @@ cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N cloud.google.com/go/analytics v0.21.4/go.mod h1:zZgNCxLCy8b2rKKVfC1YkC2vTrpfZmeRCySM3aUbskA= cloud.google.com/go/analytics v0.21.5/go.mod h1:BQtOBHWTlJ96axpPPnw5CvGJ6i3Ve/qX2fTxR8qWyr8= cloud.google.com/go/analytics v0.21.6/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= +cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= @@ -149,6 +155,8 @@ cloud.google.com/go/asset v1.15.0/go.mod h1:tpKafV6mEut3+vN9ScGvCHXHj7FALFVta+ok cloud.google.com/go/asset v1.15.1/go.mod h1:yX/amTvFWRpp5rcFq6XbCxzKT8RJUam1UoboE179jU4= cloud.google.com/go/asset v1.15.2/go.mod h1:B6H5tclkXvXz7PD22qCA2TDxSVQfasa3iDlM89O2NXs= cloud.google.com/go/asset v1.15.3/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.16.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= @@ -185,6 +193,7 @@ cloud.google.com/go/batch v1.5.0/go.mod h1:KdBmDD61K0ovcxoRHGrN6GmOBWeAOyCgKD0Mu cloud.google.com/go/batch v1.5.1/go.mod h1:RpBuIYLkQu8+CWDk3dFD/t/jOCGuUpkpX+Y0n1Xccs8= cloud.google.com/go/batch v1.6.1/go.mod h1:urdpD13zPe6YOK+6iZs/8/x2VBRofvblLpx0t57vM98= cloud.google.com/go/batch v1.6.3/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= +cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= @@ -212,6 +221,7 @@ cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6Pm cloud.google.com/go/bigquery v1.55.0/go.mod h1:9Y5I3PN9kQWuid6183JFhOGOW3GcirA5LpsKCUn+2ec= cloud.google.com/go/bigquery v1.56.0/go.mod h1:KDcsploXTEY7XT3fDQzMUZlpQLHzE4itubHrnmhUrZA= cloud.google.com/go/bigquery v1.57.1/go.mod h1:iYzC0tGVWt1jqSzBHqCr3lrRn0u13E8e+AqowBsDgug= +cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= @@ -224,6 +234,7 @@ cloud.google.com/go/billing v1.17.1/go.mod h1:Z9+vZXEq+HwH7bhJkyI4OQcR6TSbeMrjlp cloud.google.com/go/billing v1.17.2/go.mod h1:u/AdV/3wr3xoRBk5xvUzYMS1IawOAPwQMuHgHMdljDg= cloud.google.com/go/billing v1.17.3/go.mod h1:z83AkoZ7mZwBGT3yTnt6rSGI1OOsHSIi6a5M3mJ8NaU= cloud.google.com/go/billing v1.17.4/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= +cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= @@ -234,6 +245,7 @@ cloud.google.com/go/binaryauthorization v1.7.0/go.mod h1:Zn+S6QqTMn6odcMU1zDZCJx cloud.google.com/go/binaryauthorization v1.7.1/go.mod h1:GTAyfRWYgcbsP3NJogpV3yeunbUIjx2T9xVeYovtURE= cloud.google.com/go/binaryauthorization v1.7.2/go.mod h1:kFK5fQtxEp97m92ziy+hbu+uKocka1qRRL8MVJIgjv0= cloud.google.com/go/binaryauthorization v1.7.3/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= +cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= @@ -250,6 +262,7 @@ cloud.google.com/go/channel v1.17.0/go.mod h1:RpbhJsGi/lXWAUM1eF4IbQGbsfVlg2o8Ii cloud.google.com/go/channel v1.17.1/go.mod h1:xqfzcOZAcP4b/hUDH0GkGg1Sd5to6di1HOJn/pi5uBQ= cloud.google.com/go/channel v1.17.2/go.mod h1:aT2LhnftnyfQceFql5I/mP8mIbiiJS4lWqgXA815zMk= cloud.google.com/go/channel v1.17.3/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= +cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= @@ -261,6 +274,7 @@ cloud.google.com/go/cloudbuild v1.14.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2 cloud.google.com/go/cloudbuild v1.14.1/go.mod h1:K7wGc/3zfvmYWOWwYTgF/d/UVJhS4pu+HAy7PL7mCsU= cloud.google.com/go/cloudbuild v1.14.2/go.mod h1:Bn6RO0mBYk8Vlrt+8NLrru7WXlQ9/RDWz2uo5KG1/sg= cloud.google.com/go/cloudbuild v1.14.3/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= +cloud.google.com/go/cloudbuild v1.15.0/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= @@ -315,6 +329,8 @@ cloud.google.com/go/contactcenterinsights v1.11.0/go.mod h1:hutBdImE4XNZ1NV4vbPJ cloud.google.com/go/contactcenterinsights v1.11.1/go.mod h1:FeNP3Kg8iteKM80lMwSk3zZZKVxr+PGnAId6soKuXwE= cloud.google.com/go/contactcenterinsights v1.11.2/go.mod h1:A9PIR5ov5cRcd28KlDbmmXE8Aay+Gccer2h4wzkYFso= cloud.google.com/go/contactcenterinsights v1.11.3/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.0/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= @@ -326,6 +342,8 @@ cloud.google.com/go/container v1.26.0/go.mod h1:YJCmRet6+6jnYYRS000T6k0D0xUXQgBS cloud.google.com/go/container v1.26.1/go.mod h1:5smONjPRUxeEpDG7bMKWfDL4sauswqEtnBK1/KKpR04= cloud.google.com/go/container v1.26.2/go.mod h1:YlO84xCt5xupVbLaMY4s3XNE79MUJ+49VmkInr6HvF4= cloud.google.com/go/container v1.27.1/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.28.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= @@ -351,6 +369,8 @@ cloud.google.com/go/datacatalog v1.18.0/go.mod h1:nCSYFHgtxh2MiEktWIz71s/X+7ds/U cloud.google.com/go/datacatalog v1.18.1/go.mod h1:TzAWaz+ON1tkNr4MOcak8EBHX7wIRX/gZKM+yTVsv+A= cloud.google.com/go/datacatalog v1.18.2/go.mod h1:SPVgWW2WEMuWHA+fHodYjmxPiMqcOiWfhc9OD5msigk= cloud.google.com/go/datacatalog v1.18.3/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= +cloud.google.com/go/datacatalog v1.19.0/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= +cloud.google.com/go/datacatalog v1.19.2/go.mod h1:2YbODwmhpLM4lOFe3PuEhHK9EyTzQJ5AXgIy7EDKTEE= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= @@ -391,6 +411,9 @@ cloud.google.com/go/dataplex v1.9.1/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MP cloud.google.com/go/dataplex v1.10.1/go.mod h1:1MzmBv8FvjYfc7vDdxhnLFNskikkB+3vl475/XdCDhs= cloud.google.com/go/dataplex v1.10.2/go.mod h1:xdC8URdTrCrZMW6keY779ZT1cTOfV8KEPNsw+LTRT1Y= cloud.google.com/go/dataplex v1.11.1/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.11.2/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.13.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= @@ -399,6 +422,7 @@ cloud.google.com/go/dataproc/v2 v2.2.0/go.mod h1:lZR7AQtwZPvmINx5J87DSOOpTfof9LV cloud.google.com/go/dataproc/v2 v2.2.1/go.mod h1:QdAJLaBjh+l4PVlVZcmrmhGccosY/omC1qwfQ61Zv/o= cloud.google.com/go/dataproc/v2 v2.2.2/go.mod h1:aocQywVmQVF4i8CL740rNI/ZRpsaaC1Wh2++BJ7HEJ4= cloud.google.com/go/dataproc/v2 v2.2.3/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= +cloud.google.com/go/dataproc/v2 v2.3.0/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= @@ -435,6 +459,9 @@ cloud.google.com/go/deploy v1.13.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCN cloud.google.com/go/deploy v1.13.1/go.mod h1:8jeadyLkH9qu9xgO3hVWw8jVr29N1mnW42gRJT8GY6g= cloud.google.com/go/deploy v1.14.1/go.mod h1:N8S0b+aIHSEeSr5ORVoC0+/mOPUysVt8ae4QkZYolAw= cloud.google.com/go/deploy v1.14.2/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.15.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.16.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.17.0/go.mod h1:XBr42U5jIr64t92gcpOXxNrqL2PStQCXHuKK5GRUuYo= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= @@ -450,6 +477,9 @@ cloud.google.com/go/dialogflow v1.44.0/go.mod h1:pDUJdi4elL0MFmt1REMvFkdsUTYSHq+ cloud.google.com/go/dialogflow v1.44.1/go.mod h1:n/h+/N2ouKOO+rbe/ZnI186xImpqvCVj2DdsWS/0EAk= cloud.google.com/go/dialogflow v1.44.2/go.mod h1:QzFYndeJhpVPElnFkUXxdlptx0wPnBWLCBT9BvtC3/c= cloud.google.com/go/dialogflow v1.44.3/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.47.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.1/go.mod h1:C1sjs2/g9cEwjCltkKeYp3FFpz8BOzNondEaAlCpt+A= cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= @@ -470,6 +500,8 @@ cloud.google.com/go/documentai v1.23.0/go.mod h1:LKs22aDHbJv7ufXuPypzRO7rG3ALLJx cloud.google.com/go/documentai v1.23.2/go.mod h1:Q/wcRT+qnuXOpjAkvOV4A+IeQl04q2/ReT7SSbytLSo= cloud.google.com/go/documentai v1.23.4/go.mod h1:4MYAaEMnADPN1LPN5xboDR5QVB6AgsaxgFdJhitlE2Y= cloud.google.com/go/documentai v1.23.5/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.6/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= @@ -510,6 +542,7 @@ cloud.google.com/go/filestore v1.7.1/go.mod h1:y10jsorq40JJnjR/lQ8AfFbbcGlw3g+Dp cloud.google.com/go/filestore v1.7.2/go.mod h1:TYOlyJs25f/omgj+vY7/tIG/E7BX369triSPzE4LdgE= cloud.google.com/go/filestore v1.7.3/go.mod h1:Qp8WaEERR3cSkxToxFPHh/b8AACkSut+4qlCjAmKTV0= cloud.google.com/go/filestore v1.7.4/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= +cloud.google.com/go/filestore v1.8.0/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/firestore v1.11.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= cloud.google.com/go/firestore v1.12.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= @@ -563,6 +596,7 @@ cloud.google.com/go/gkemulticloud v1.0.0/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZVi cloud.google.com/go/gkemulticloud v1.0.1/go.mod h1:AcrGoin6VLKT/fwZEYuqvVominLriQBCKmbjtnbMjG8= cloud.google.com/go/gkemulticloud v1.0.2/go.mod h1:+ee5VXxKb3H1l4LZAcgWB/rvI16VTNTrInWxDjAGsGo= cloud.google.com/go/gkemulticloud v1.0.3/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= +cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= @@ -647,6 +681,7 @@ cloud.google.com/go/lifesciences v0.9.4/go.mod h1:bhm64duKhMi7s9jR9WYJYvjAFJwRqN cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= +cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= @@ -671,6 +706,8 @@ cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9v cloud.google.com/go/maps v1.4.1/go.mod h1:BxSa0BnW1g2U2gNdbq5zikLlHUuHW0GFWh7sgML2kIY= cloud.google.com/go/maps v1.5.1/go.mod h1:NPMZw1LJwQZYCfz4y+EIw+SI+24A4bpdFJqdKVr0lt4= cloud.google.com/go/maps v1.6.1/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.2/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.3/go.mod h1:VGAn809ADswi1ASofL5lveOHPnE6Rk/SFTTBx1yuOLw= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= @@ -707,6 +744,7 @@ cloud.google.com/go/monitoring v1.16.0/go.mod h1:Ptp15HgAyM1fNICAojDMoNc/wUmn67m cloud.google.com/go/monitoring v1.16.1/go.mod h1:6HsxddR+3y9j+o/cMJH6q/KJ/CBTvM/38L/1m7bTRJ4= cloud.google.com/go/monitoring v1.16.2/go.mod h1:B44KGwi4ZCF8Rk/5n+FWeispDXoKSk9oss2QNlXJBgc= cloud.google.com/go/monitoring v1.16.3/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= @@ -769,6 +807,7 @@ cloud.google.com/go/orgpolicy v1.11.1/go.mod h1:8+E3jQcpZJQliP+zaFfayC2Pg5bmhuLK cloud.google.com/go/orgpolicy v1.11.2/go.mod h1:biRDpNwfyytYnmCRWZWxrKF22Nkz9eNVj9zyaBdpm1o= cloud.google.com/go/orgpolicy v1.11.3/go.mod h1:oKAtJ/gkMjum5icv2aujkP4CxROxPXsBbYGCDbPO8MM= cloud.google.com/go/orgpolicy v1.11.4/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= +cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= @@ -789,6 +828,7 @@ cloud.google.com/go/oslogin v1.11.0/go.mod h1:8GMTJs4X2nOAUVJiPGqIWVcDaF0eniEto3 cloud.google.com/go/oslogin v1.11.1/go.mod h1:OhD2icArCVNUxKqtK0mcSmKL7lgr0LVlQz+v9s1ujTg= cloud.google.com/go/oslogin v1.12.1/go.mod h1:VfwTeFJGbnakxAY236eN8fsnglLiVXndlbcNomY4iZU= cloud.google.com/go/oslogin v1.12.2/go.mod h1:CQ3V8Jvw4Qo4WRhNPF0o+HAM4DiLuE27Ul9CX9g2QdY= +cloud.google.com/go/oslogin v1.13.0/go.mod h1:xPJqLwpTZ90LSE5IL1/svko+6c5avZLluiyylMb/sRA= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= @@ -824,6 +864,7 @@ cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9 cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= cloud.google.com/go/pubsub v1.32.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= +cloud.google.com/go/pubsub v1.34.0/go.mod h1:alj4l4rBg+N3YTFDDC+/YyFTs6JAjam2QfYsddcAW4c= cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= @@ -841,6 +882,8 @@ cloud.google.com/go/recaptchaenterprise/v2 v2.8.0/go.mod h1:QuE8EdU9dEnesG8/kG3X cloud.google.com/go/recaptchaenterprise/v2 v2.8.1/go.mod h1:JZYZJOeZjgSSTGP4uz7NlQ4/d1w5hGmksVgM0lbEij0= cloud.google.com/go/recaptchaenterprise/v2 v2.8.2/go.mod h1:kpaDBOpkwD4G0GVMzG1W6Doy1tFFC97XAV3xy+Rd/pw= cloud.google.com/go/recaptchaenterprise/v2 v2.8.3/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.8.4/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= @@ -858,6 +901,7 @@ cloud.google.com/go/recommender v1.11.0/go.mod h1:kPiRQhPyTJ9kyXPCG6u/dlPLbYfFlk cloud.google.com/go/recommender v1.11.1/go.mod h1:sGwFFAyI57v2Hc5LbIj+lTwXipGu9NW015rkaEM5B18= cloud.google.com/go/recommender v1.11.2/go.mod h1:AeoJuzOvFR/emIcXdVFkspVXVTYpliRCmKNYDnyBv6Y= cloud.google.com/go/recommender v1.11.3/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= +cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= @@ -911,6 +955,7 @@ cloud.google.com/go/scheduler v1.10.1/go.mod h1:R63Ldltd47Bs4gnhQkmNDse5w8gBRrhO cloud.google.com/go/scheduler v1.10.2/go.mod h1:O3jX6HRH5eKCA3FutMw375XHZJudNIKVonSCHv7ropY= cloud.google.com/go/scheduler v1.10.3/go.mod h1:8ANskEM33+sIbpJ+R4xRfw/jzOG+ZFE8WVLy7/yGvbc= cloud.google.com/go/scheduler v1.10.4/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= +cloud.google.com/go/scheduler v1.10.5/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= @@ -940,6 +985,7 @@ cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY cloud.google.com/go/securitycenter v1.23.1/go.mod h1:w2HV3Mv/yKhbXKwOCu2i8bCuLtNP1IMHuiYQn4HJq5s= cloud.google.com/go/securitycenter v1.24.1/go.mod h1:3h9IdjjHhVMXdQnmqzVnM7b0wMn/1O/U20eWVpMpZjI= cloud.google.com/go/securitycenter v1.24.2/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= +cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= @@ -978,6 +1024,10 @@ cloud.google.com/go/spanner v1.47.0/go.mod h1:IXsJwVW2j4UKs0eYDqodab6HgGuA1bViSq cloud.google.com/go/spanner v1.49.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= cloud.google.com/go/spanner v1.50.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= cloud.google.com/go/spanner v1.51.0/go.mod h1:c5KNo5LQ1X5tJwma9rSQZsXNBDNvj4/n8BVc3LNahq0= +cloud.google.com/go/spanner v1.53.0/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.53.1/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.54.0/go.mod h1:wZvSQVBgngF0Gq86fKup6KIYmN2be7uOKjtK97X+bQU= +cloud.google.com/go/spanner v1.55.0/go.mod h1:HXEznMUVhC+PC+HDyo9YFG2Ajj5BQDkcbqB9Z2Ffxi0= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= @@ -989,6 +1039,7 @@ cloud.google.com/go/speech v1.19.0/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ cloud.google.com/go/speech v1.19.1/go.mod h1:WcuaWz/3hOlzPFOVo9DUsblMIHwxP589y6ZMtaG+iAA= cloud.google.com/go/speech v1.19.2/go.mod h1:2OYFfj+Ch5LWjsaSINuCZsre/789zlcCI3SY4oAi2oI= cloud.google.com/go/speech v1.20.1/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= +cloud.google.com/go/speech v1.21.0/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -1001,6 +1052,7 @@ cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1051,6 +1103,7 @@ cloud.google.com/go/translate v1.9.0/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNW cloud.google.com/go/translate v1.9.1/go.mod h1:TWIgDZknq2+JD4iRcojgeDtqGEp154HN/uL6hMvylS8= cloud.google.com/go/translate v1.9.2/go.mod h1:E3Tc6rUTsQkVrXW6avbUhKJSr7ZE3j7zNmqzXKHqRrY= cloud.google.com/go/translate v1.9.3/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= +cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= @@ -1148,6 +1201,7 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= +github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -1176,7 +1230,7 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -1196,6 +1250,7 @@ github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJ github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI= github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= @@ -1203,6 +1258,8 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -1216,6 +1273,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -1224,6 +1285,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -1280,6 +1342,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-pkcs11 v0.2.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -1309,6 +1372,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -1337,11 +1402,14 @@ github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1355,6 +1423,7 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -1369,6 +1438,8 @@ github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4 github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -1383,6 +1454,7 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -1395,10 +1467,12 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -1408,6 +1482,7 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -1425,9 +1500,20 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -1438,6 +1524,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= @@ -1447,6 +1534,9 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1507,6 +1597,7 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1570,9 +1661,12 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1608,7 +1702,8 @@ golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1629,6 +1724,7 @@ golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1714,6 +1810,8 @@ golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1732,6 +1830,8 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1761,6 +1861,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1825,6 +1926,7 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1833,6 +1935,7 @@ golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= @@ -1906,6 +2009,8 @@ google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvy google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= +google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2064,8 +2169,14 @@ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqv google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -2083,9 +2194,16 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go. google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0/go.mod h1:guYXGPwC6jwxgWKW5Y405fKWOFNwlvUlUnzyp9i0uqo= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= @@ -2102,8 +2220,14 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2152,8 +2276,10 @@ google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= -google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2172,8 +2298,9 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 2d149ecdf1dc566b3f57836f18e5486755bf8986 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 07:57:34 -0700 Subject: [PATCH 094/151] deps/api: Bump `prometheus_metrics_model` -> 0.6.0 (#32542) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- api/bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 15265b521878..95dff79958ff 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -92,9 +92,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Prometheus client model", project_desc = "Data model artifacts for Prometheus", project_url = "https://github.com/prometheus/client_model", - version = "0.5.0", - sha256 = "170873e0b91cab5da6634af1498b88876842ff3e01212e2dabf6b4e6512c948d", - release_date = "2023-10-03", + version = "0.6.0", + sha256 = "6f8464471e34749753e5d767b22939b98a73b2149bc551c0f017d861f8a0adeb", + release_date = "2024-02-16", strip_prefix = "client_model-{version}", urls = ["https://github.com/prometheus/client_model/archive/v{version}.tar.gz"], use_category = ["api"], From 6b4f09ecb7b4a459675fe667b26a1beb4c60356e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:57:57 +0000 Subject: [PATCH 095/151] build(deps): bump cryptography from 42.0.4 to 42.0.5 in /tools/base (#32575) Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.4 to 42.0.5. - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/42.0.4...42.0.5) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 66 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 286ac90158f7..37b99f585a1f 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -419,39 +419,39 @@ coloredlogs==15.0.1 \ crcmod==1.7 \ --hash=sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e # via gsutil -cryptography==42.0.4 \ - --hash=sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b \ - --hash=sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce \ - --hash=sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88 \ - --hash=sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7 \ - --hash=sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20 \ - --hash=sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9 \ - --hash=sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff \ - --hash=sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1 \ - --hash=sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764 \ - --hash=sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b \ - --hash=sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298 \ - --hash=sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1 \ - --hash=sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824 \ - --hash=sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257 \ - --hash=sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a \ - --hash=sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129 \ - --hash=sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb \ - --hash=sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929 \ - --hash=sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854 \ - --hash=sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52 \ - --hash=sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923 \ - --hash=sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885 \ - --hash=sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0 \ - --hash=sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd \ - --hash=sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2 \ - --hash=sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18 \ - --hash=sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b \ - --hash=sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992 \ - --hash=sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74 \ - --hash=sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660 \ - --hash=sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925 \ - --hash=sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449 +cryptography==42.0.5 \ + --hash=sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee \ + --hash=sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576 \ + --hash=sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d \ + --hash=sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30 \ + --hash=sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413 \ + --hash=sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb \ + --hash=sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da \ + --hash=sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4 \ + --hash=sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd \ + --hash=sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc \ + --hash=sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8 \ + --hash=sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1 \ + --hash=sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc \ + --hash=sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e \ + --hash=sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8 \ + --hash=sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940 \ + --hash=sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400 \ + --hash=sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7 \ + --hash=sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16 \ + --hash=sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278 \ + --hash=sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74 \ + --hash=sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec \ + --hash=sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1 \ + --hash=sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2 \ + --hash=sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c \ + --hash=sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922 \ + --hash=sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a \ + --hash=sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6 \ + --hash=sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1 \ + --hash=sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e \ + --hash=sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac \ + --hash=sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7 # via # -r requirements.in # aioquic From 90e8fadf0b01582bfd55826da8755c1d6c747f9a Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 07:58:09 -0700 Subject: [PATCH 096/151] deps: Bump `com_github_intel_qatlib` -> 24.02.0 (#32541) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 526603c8f5ad..9c7746385ca9 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -440,12 +440,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "qatlib", project_desc = "Intel QuickAssist Technology Library", project_url = "https://github.com/intel/qatlib", - version = "23.11.0", - sha256 = "f649613c243df98c2b005e58af7e0c9bb6d9638e0a12d2757d18d4930bf893cd", + version = "24.02.0", + sha256 = "ffef9a3a2bd6024b188977411944ec6267da34d40a0c6c1d42c4f59165991176", strip_prefix = "qatlib-{version}", urls = ["https://github.com/intel/qatlib/archive/refs/tags/{version}.tar.gz"], use_category = ["dataplane_ext"], - release_date = "2023-11-15", + release_date = "2024-02-20", extensions = ["envoy.tls.key_providers.qat", "envoy.compression.qatzip.compressor"], cpe = "N/A", license = "BSD-3-Clause", From 1f19bf4c5e16559d01ab01a7821f26048b1ee16c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:58:19 +0000 Subject: [PATCH 097/151] build(deps): bump setuptools from 69.1.0 to 69.1.1 in /tools/base (#32574) Bumps [setuptools](https://github.com/pypa/setuptools) from 69.1.0 to 69.1.1. - [Release notes](https://github.com/pypa/setuptools/releases) - [Changelog](https://github.com/pypa/setuptools/blob/main/NEWS.rst) - [Commits](https://github.com/pypa/setuptools/compare/v69.1.0...v69.1.1) --- updated-dependencies: - dependency-name: setuptools dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 37b99f585a1f..8d9d67bc9288 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1607,7 +1607,7 @@ zstandard==0.22.0 \ # via envoy-base-utils # The following packages are considered to be unsafe in a requirements file: -setuptools==69.1.0 \ - --hash=sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401 \ - --hash=sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6 +setuptools==69.1.1 \ + --hash=sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56 \ + --hash=sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8 # via -r requirements.in From f6ed45ac9a28a211f467530e765f9b2cdc01078b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:58:27 +0000 Subject: [PATCH 098/151] build(deps): bump orjson from 3.9.14 to 3.9.15 in /tools/base (#32573) Bumps [orjson](https://github.com/ijl/orjson) from 3.9.14 to 3.9.15. - [Release notes](https://github.com/ijl/orjson/releases) - [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md) - [Commits](https://github.com/ijl/orjson/compare/3.9.14...3.9.15) --- updated-dependencies: - dependency-name: orjson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 102 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 8d9d67bc9288..87cfe35fb798 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -921,57 +921,57 @@ oauth2client==4.1.3 \ # via # gcs-oauth2-boto-plugin # google-apitools -orjson==3.9.14 \ - --hash=sha256:0572f174f50b673b7df78680fb52cd0087a8585a6d06d295a5f790568e1064c6 \ - --hash=sha256:06fb40f8e49088ecaa02f1162581d39e2cf3fd9dbbfe411eb2284147c99bad79 \ - --hash=sha256:08e722a8d06b13b67a51f247a24938d1a94b4b3862e40e0eef3b2e98c99cd04c \ - --hash=sha256:135d518f73787ce323b1a5e21fb854fe22258d7a8ae562b81a49d6c7f826f2a3 \ - --hash=sha256:19cdea0664aec0b7f385be84986d4defd3334e9c3c799407686ee1c26f7b8251 \ - --hash=sha256:1f7b6f3ef10ae8e3558abb729873d033dbb5843507c66b1c0767e32502ba96bb \ - --hash=sha256:20837e10835c98973673406d6798e10f821e7744520633811a5a3d809762d8cc \ - --hash=sha256:236230433a9a4968ab895140514c308fdf9f607cb8bee178a04372b771123860 \ - --hash=sha256:23d1528db3c7554f9d6eeb09df23cb80dd5177ec56eeb55cc5318826928de506 \ - --hash=sha256:26280a7fcb62d8257f634c16acebc3bec626454f9ab13558bbf7883b9140760e \ - --hash=sha256:29512eb925b620e5da2fd7585814485c67cc6ba4fe739a0a700c50467a8a8065 \ - --hash=sha256:2eefc41ba42e75ed88bc396d8fe997beb20477f3e7efa000cd7a47eda452fbb2 \ - --hash=sha256:3014ccbda9be0b1b5f8ea895121df7e6524496b3908f4397ff02e923bcd8f6dd \ - --hash=sha256:449bf090b2aa4e019371d7511a6ea8a5a248139205c27d1834bb4b1e3c44d936 \ - --hash=sha256:4dc1c132259b38d12c6587d190cd09cd76e3b5273ce71fe1372437b4cbc65f6f \ - --hash=sha256:58b36f54da759602d8e2f7dad958752d453dfe2c7122767bc7f765e17dc59959 \ - --hash=sha256:5bf597530544db27a8d76aced49cfc817ee9503e0a4ebf0109cd70331e7bbe0c \ - --hash=sha256:6f39a10408478f4c05736a74da63727a1ae0e83e3533d07b19443400fe8591ca \ - --hash=sha256:6f52ac2eb49e99e7373f62e2a68428c6946cda52ce89aa8fe9f890c7278e2d3a \ - --hash=sha256:7183cc68ee2113b19b0b8714221e5e3b07b3ba10ca2bb108d78fd49cefaae101 \ - --hash=sha256:751250a31fef2bac05a2da2449aae7142075ea26139271f169af60456d8ad27a \ - --hash=sha256:75fc593cf836f631153d0e21beaeb8d26e144445c73645889335c2247fcd71a0 \ - --hash=sha256:7913079b029e1b3501854c9a78ad938ed40d61fe09bebab3c93e60ff1301b189 \ - --hash=sha256:793f6c9448ab6eb7d4974b4dde3f230345c08ca6c7995330fbceeb43a5c8aa5e \ - --hash=sha256:814f288c011efdf8f115c5ebcc1ab94b11da64b207722917e0ceb42f52ef30a3 \ - --hash=sha256:90903d2908158a2c9077a06f11e27545de610af690fb178fd3ba6b32492d4d1c \ - --hash=sha256:917311d6a64d1c327c0dfda1e41f3966a7fb72b11ca7aa2e7a68fcccc7db35d9 \ - --hash=sha256:95c03137b0cf66517c8baa65770507a756d3a89489d8ecf864ea92348e1beabe \ - --hash=sha256:978f416bbff9da8d2091e3cf011c92da68b13f2c453dcc2e8109099b2a19d234 \ - --hash=sha256:9a1af21160a38ee8be3f4fcf24ee4b99e6184cadc7f915d599f073f478a94d2c \ - --hash=sha256:a2591faa0c031cf3f57e5bce1461cfbd6160f3f66b5a72609a130924917cb07d \ - --hash=sha256:a603161318ff699784943e71f53899983b7dee571b4dd07c336437c9c5a272b0 \ - --hash=sha256:a6bc7928d161840096adc956703494b5c0193ede887346f028216cac0af87500 \ - --hash=sha256:a88cafb100af68af3b9b29b5ccd09fdf7a48c63327916c8c923a94c336d38dd3 \ - --hash=sha256:ab90c02cb264250b8a58cedcc72ed78a4a257d956c8d3c8bebe9751b818dfad8 \ - --hash=sha256:abcda41ecdc950399c05eff761c3de91485d9a70d8227cb599ad3a66afe93bcc \ - --hash=sha256:ac0c7eae7ad3a223bde690565442f8a3d620056bd01196f191af8be58a5248e1 \ - --hash=sha256:ac650d49366fa41fe702e054cb560171a8634e2865537e91f09a8d05ea5b1d37 \ - --hash=sha256:b7c11667421df2d8b18b021223505dcc3ee51be518d54e4dc49161ac88ac2b87 \ - --hash=sha256:ba3518b999f88882ade6686f1b71e207b52e23546e180499be5bbb63a2f9c6e6 \ - --hash=sha256:c19009ff37f033c70acd04b636380379499dac2cba27ae7dfc24f304deabbc81 \ - --hash=sha256:ce6f095eef0026eae76fc212f20f786011ecf482fc7df2f4c272a8ae6dd7b1ef \ - --hash=sha256:d2cf1d0557c61c75e18cf7d69fb689b77896e95553e212c0cc64cf2087944b84 \ - --hash=sha256:d450a8e0656efb5d0fcb062157b918ab02dcca73278975b4ee9ea49e2fcf5bd5 \ - --hash=sha256:df3266d54246cb56b8bb17fa908660d2a0f2e3f63fbc32451ffc1b1505051d07 \ - --hash=sha256:df76ecd17b1b3627bddfd689faaf206380a1a38cc9f6c4075bd884eaedcf46c2 \ - --hash=sha256:e2450d87dd7b4f277f4c5598faa8b49a0c197b91186c47a2c0b88e15531e4e3e \ - --hash=sha256:ea890e6dc1711aeec0a33b8520e395c2f3d59ead5b4351a788e06bf95fc7ba81 \ - --hash=sha256:f75823cc1674a840a151e999a7dfa0d86c911150dd6f951d0736ee9d383bf415 \ - --hash=sha256:fca33fdd0b38839b01912c57546d4f412ba7bfa0faf9bf7453432219aec2df07 +orjson==3.9.15 \ + --hash=sha256:001f4eb0ecd8e9ebd295722d0cbedf0748680fb9998d3993abaed2f40587257a \ + --hash=sha256:05a1f57fb601c426635fcae9ddbe90dfc1ed42245eb4c75e4960440cac667262 \ + --hash=sha256:10c57bc7b946cf2efa67ac55766e41764b66d40cbd9489041e637c1304400494 \ + --hash=sha256:12365576039b1a5a47df01aadb353b68223da413e2e7f98c02403061aad34bde \ + --hash=sha256:2973474811db7b35c30248d1129c64fd2bdf40d57d84beed2a9a379a6f57d0ab \ + --hash=sha256:2b5c0f532905e60cf22a511120e3719b85d9c25d0e1c2a8abb20c4dede3b05a5 \ + --hash=sha256:2c51378d4a8255b2e7c1e5cc430644f0939539deddfa77f6fac7b56a9784160a \ + --hash=sha256:2d99e3c4c13a7b0fb3792cc04c2829c9db07838fb6973e578b85c1745e7d0ce7 \ + --hash=sha256:2f256d03957075fcb5923410058982aea85455d035607486ccb847f095442bda \ + --hash=sha256:34cbcd216e7af5270f2ffa63a963346845eb71e174ea530867b7443892d77180 \ + --hash=sha256:4228aace81781cc9d05a3ec3a6d2673a1ad0d8725b4e915f1089803e9efd2b99 \ + --hash=sha256:4feeb41882e8aa17634b589533baafdceb387e01e117b1ec65534ec724023d04 \ + --hash=sha256:57d5d8cf9c27f7ef6bc56a5925c7fbc76b61288ab674eb352c26ac780caa5b10 \ + --hash=sha256:5bb399e1b49db120653a31463b4a7b27cf2fbfe60469546baf681d1b39f4edf2 \ + --hash=sha256:62482873e0289cf7313461009bf62ac8b2e54bc6f00c6fabcde785709231a5d7 \ + --hash=sha256:67384f588f7f8daf040114337d34a5188346e3fae6c38b6a19a2fe8c663a2f9b \ + --hash=sha256:6ae4e06be04dc00618247c4ae3f7c3e561d5bc19ab6941427f6d3722a0875ef7 \ + --hash=sha256:6f7b65bfaf69493c73423ce9db66cfe9138b2f9ef62897486417a8fcb0a92bfe \ + --hash=sha256:6fc2fe4647927070df3d93f561d7e588a38865ea0040027662e3e541d592811e \ + --hash=sha256:71c6b009d431b3839d7c14c3af86788b3cfac41e969e3e1c22f8a6ea13139404 \ + --hash=sha256:7413070a3e927e4207d00bd65f42d1b780fb0d32d7b1d951f6dc6ade318e1b5a \ + --hash=sha256:76bc6356d07c1d9f4b782813094d0caf1703b729d876ab6a676f3aaa9a47e37c \ + --hash=sha256:7f6cbd8e6e446fb7e4ed5bac4661a29e43f38aeecbf60c4b900b825a353276a1 \ + --hash=sha256:8055ec598605b0077e29652ccfe9372247474375e0e3f5775c91d9434e12d6b1 \ + --hash=sha256:809d653c155e2cc4fd39ad69c08fdff7f4016c355ae4b88905219d3579e31eb7 \ + --hash=sha256:82425dd5c7bd3adfe4e94c78e27e2fa02971750c2b7ffba648b0f5d5cc016a73 \ + --hash=sha256:87f1097acb569dde17f246faa268759a71a2cb8c96dd392cd25c668b104cad2f \ + --hash=sha256:920fa5a0c5175ab14b9c78f6f820b75804fb4984423ee4c4f1e6d748f8b22bc1 \ + --hash=sha256:92255879280ef9c3c0bcb327c5a1b8ed694c290d61a6a532458264f887f052cb \ + --hash=sha256:946c3a1ef25338e78107fba746f299f926db408d34553b4754e90a7de1d44068 \ + --hash=sha256:95cae920959d772f30ab36d3b25f83bb0f3be671e986c72ce22f8fa700dae061 \ + --hash=sha256:9cf1596680ac1f01839dba32d496136bdd5d8ffb858c280fa82bbfeb173bdd40 \ + --hash=sha256:9fe41b6f72f52d3da4db524c8653e46243c8c92df826ab5ffaece2dba9cccd58 \ + --hash=sha256:b17f0f14a9c0ba55ff6279a922d1932e24b13fc218a3e968ecdbf791b3682b25 \ + --hash=sha256:b3d336ed75d17c7b1af233a6561cf421dee41d9204aa3cfcc6c9c65cd5bb69a8 \ + --hash=sha256:b66bcc5670e8a6b78f0313bcb74774c8291f6f8aeef10fe70e910b8040f3ab75 \ + --hash=sha256:b725da33e6e58e4a5d27958568484aa766e825e93aa20c26c91168be58e08cbb \ + --hash=sha256:b72758f3ffc36ca566ba98a8e7f4f373b6c17c646ff8ad9b21ad10c29186f00d \ + --hash=sha256:bcef128f970bb63ecf9a65f7beafd9b55e3aaf0efc271a4154050fc15cdb386e \ + --hash=sha256:c8e8fe01e435005d4421f183038fc70ca85d2c1e490f51fb972db92af6e047c2 \ + --hash=sha256:d61f7ce4727a9fa7680cd6f3986b0e2c732639f46a5e0156e550e35258aa313a \ + --hash=sha256:d6768a327ea1ba44c9114dba5fdda4a214bdb70129065cd0807eb5f010bfcbb5 \ + --hash=sha256:e18668f1bd39e69b7fed19fa7cd1cd110a121ec25439328b5c89934e6d30d357 \ + --hash=sha256:e88b97ef13910e5f87bcbc4dd7979a7de9ba8702b54d3204ac587e83639c0c2b \ + --hash=sha256:ea0b183a5fe6b2b45f3b854b0d19c4e932d6f5934ae1f723b07cf9560edd4ec7 \ + --hash=sha256:ede0bde16cc6e9b96633df1631fbcd66491d1063667f260a4f2386a098393790 \ + --hash=sha256:f541587f5c558abd93cb0de491ce99a9ef8d1ae29dd6ab4dbb5a13281ae04cbd \ + --hash=sha256:fbbeb3c9b2edb5fd044b2a070f127a0ac456ffd079cb82746fc84af01ef021a4 \ + --hash=sha256:fdfa97090e2d6f73dced247a2f2d8004ac6449df6568f30e7fa1a045767c69a6 \ + --hash=sha256:ff0f9913d82e1d1fadbd976424c316fbc4d9c525c81d047bbdd16bd27dd98cfc # via # -r requirements.in # envoy-base-utils From 736b134d12befb36771f4ddb5308632870784b8b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:58:51 +0000 Subject: [PATCH 099/151] build(deps): bump the examples-load-reporting group in /examples/load-reporting-service with 1 update (#32514) build(deps): bump the examples-load-reporting group Bumps the examples-load-reporting group in /examples/load-reporting-service with 1 update: [google.golang.org/grpc](https://github.com/grpc/grpc-go). Updates `google.golang.org/grpc` from 1.61.1 to 1.62.0 - [Release notes](https://github.com/grpc/grpc-go/releases) - [Commits](https://github.com/grpc/grpc-go/compare/v1.61.1...v1.62.0) --- updated-dependencies: - dependency-name: google.golang.org/grpc dependency-type: direct:production update-type: version-update:semver-minor dependency-group: examples-load-reporting ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/load-reporting-service/go.mod | 2 +- examples/load-reporting-service/go.sum | 140 +++++++++++++++++++++++-- 2 files changed, 131 insertions(+), 11 deletions(-) diff --git a/examples/load-reporting-service/go.mod b/examples/load-reporting-service/go.mod index ba48ee0ce07a..fbfbccbb74e0 100644 --- a/examples/load-reporting-service/go.mod +++ b/examples/load-reporting-service/go.mod @@ -5,5 +5,5 @@ go 1.13 require ( github.com/envoyproxy/go-control-plane v0.12.0 github.com/golang/protobuf v1.5.3 - google.golang.org/grpc v1.61.1 + google.golang.org/grpc v1.62.0 ) diff --git a/examples/load-reporting-service/go.sum b/examples/load-reporting-service/go.sum index 6b10f8e54563..513e0ac97039 100644 --- a/examples/load-reporting-service/go.sum +++ b/examples/load-reporting-service/go.sum @@ -43,6 +43,8 @@ cloud.google.com/go v0.110.7/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5x cloud.google.com/go v0.110.8/go.mod h1:Iz8AkXJf1qmxC3Oxoep8R1T36w8B92yU29PcBhHO5fk= cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYNpM= cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= +cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -72,6 +74,9 @@ cloud.google.com/go/aiplatform v1.51.0/go.mod h1:IRc2b8XAMTa9ZmfJV1BCCQbieWWvDnP cloud.google.com/go/aiplatform v1.51.1/go.mod h1:kY3nIMAVQOK2XDqDPHaOuD9e+FdMA6OOpfBjsvaFSOo= cloud.google.com/go/aiplatform v1.51.2/go.mod h1:hCqVYB3mY45w99TmetEoe8eCQEwZEp9WHxeZdcv9phw= cloud.google.com/go/aiplatform v1.52.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.54.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.57.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= +cloud.google.com/go/aiplatform v1.58.0/go.mod h1:pwZMGvqe0JRkI1GWSZCtnAfrR4K1bv65IHILGA//VEU= cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= cloud.google.com/go/analytics v0.17.0/go.mod h1:WXFa3WSym4IZ+JiKmavYdJwGG/CvpqiqczmL59bTD9M= @@ -82,6 +87,7 @@ cloud.google.com/go/analytics v0.21.3/go.mod h1:U8dcUtmDmjrmUTnnnRnI4m6zKn/yaA5N cloud.google.com/go/analytics v0.21.4/go.mod h1:zZgNCxLCy8b2rKKVfC1YkC2vTrpfZmeRCySM3aUbskA= cloud.google.com/go/analytics v0.21.5/go.mod h1:BQtOBHWTlJ96axpPPnw5CvGJ6i3Ve/qX2fTxR8qWyr8= cloud.google.com/go/analytics v0.21.6/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= +cloud.google.com/go/analytics v0.22.0/go.mod h1:eiROFQKosh4hMaNhF85Oc9WO97Cpa7RggD40e/RBy8w= cloud.google.com/go/apigateway v1.3.0/go.mod h1:89Z8Bhpmxu6AmUxuVRg/ECRGReEdiP3vQtk4Z1J9rJk= cloud.google.com/go/apigateway v1.4.0/go.mod h1:pHVY9MKGaH9PQ3pJ4YLzoj6U5FUDeDFBllIz7WmzJoc= cloud.google.com/go/apigateway v1.5.0/go.mod h1:GpnZR3Q4rR7LVu5951qfXPJCHquZt02jf7xQx7kpqN8= @@ -149,6 +155,8 @@ cloud.google.com/go/asset v1.15.0/go.mod h1:tpKafV6mEut3+vN9ScGvCHXHj7FALFVta+ok cloud.google.com/go/asset v1.15.1/go.mod h1:yX/amTvFWRpp5rcFq6XbCxzKT8RJUam1UoboE179jU4= cloud.google.com/go/asset v1.15.2/go.mod h1:B6H5tclkXvXz7PD22qCA2TDxSVQfasa3iDlM89O2NXs= cloud.google.com/go/asset v1.15.3/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.16.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= +cloud.google.com/go/asset v1.17.0/go.mod h1:yYLfUD4wL4X589A9tYrv4rFrba0QlDeag0CMcM5ggXU= cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= @@ -185,6 +193,7 @@ cloud.google.com/go/batch v1.5.0/go.mod h1:KdBmDD61K0ovcxoRHGrN6GmOBWeAOyCgKD0Mu cloud.google.com/go/batch v1.5.1/go.mod h1:RpBuIYLkQu8+CWDk3dFD/t/jOCGuUpkpX+Y0n1Xccs8= cloud.google.com/go/batch v1.6.1/go.mod h1:urdpD13zPe6YOK+6iZs/8/x2VBRofvblLpx0t57vM98= cloud.google.com/go/batch v1.6.3/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= +cloud.google.com/go/batch v1.7.0/go.mod h1:J64gD4vsNSA2O5TtDB5AAux3nJ9iV8U3ilg3JDBYejU= cloud.google.com/go/beyondcorp v0.2.0/go.mod h1:TB7Bd+EEtcw9PCPQhCJtJGjk/7TC6ckmnSFS+xwTfm4= cloud.google.com/go/beyondcorp v0.3.0/go.mod h1:E5U5lcrcXMsCuoDNyGrpyTm/hn7ne941Jz2vmksAxW8= cloud.google.com/go/beyondcorp v0.4.0/go.mod h1:3ApA0mbhHx6YImmuubf5pyW8srKnCEPON32/5hj+RmM= @@ -212,6 +221,7 @@ cloud.google.com/go/bigquery v1.53.0/go.mod h1:3b/iXjRQGU4nKa87cXeg6/gogLjO8C6Pm cloud.google.com/go/bigquery v1.55.0/go.mod h1:9Y5I3PN9kQWuid6183JFhOGOW3GcirA5LpsKCUn+2ec= cloud.google.com/go/bigquery v1.56.0/go.mod h1:KDcsploXTEY7XT3fDQzMUZlpQLHzE4itubHrnmhUrZA= cloud.google.com/go/bigquery v1.57.1/go.mod h1:iYzC0tGVWt1jqSzBHqCr3lrRn0u13E8e+AqowBsDgug= +cloud.google.com/go/bigquery v1.58.0/go.mod h1:0eh4mWNY0KrBTjUzLjoYImapGORq9gEPT7MWjCy9lik= cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= cloud.google.com/go/billing v1.6.0/go.mod h1:WoXzguj+BeHXPbKfNWkqVtDdzORazmCjraY+vrxcyvI= @@ -224,6 +234,7 @@ cloud.google.com/go/billing v1.17.1/go.mod h1:Z9+vZXEq+HwH7bhJkyI4OQcR6TSbeMrjlp cloud.google.com/go/billing v1.17.2/go.mod h1:u/AdV/3wr3xoRBk5xvUzYMS1IawOAPwQMuHgHMdljDg= cloud.google.com/go/billing v1.17.3/go.mod h1:z83AkoZ7mZwBGT3yTnt6rSGI1OOsHSIi6a5M3mJ8NaU= cloud.google.com/go/billing v1.17.4/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= +cloud.google.com/go/billing v1.18.0/go.mod h1:5DOYQStCxquGprqfuid/7haD7th74kyMBHkjO/OvDtk= cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= cloud.google.com/go/binaryauthorization v1.3.0/go.mod h1:lRZbKgjDIIQvzYQS1p99A7/U1JqvqeZg0wiI5tp6tg0= @@ -234,6 +245,7 @@ cloud.google.com/go/binaryauthorization v1.7.0/go.mod h1:Zn+S6QqTMn6odcMU1zDZCJx cloud.google.com/go/binaryauthorization v1.7.1/go.mod h1:GTAyfRWYgcbsP3NJogpV3yeunbUIjx2T9xVeYovtURE= cloud.google.com/go/binaryauthorization v1.7.2/go.mod h1:kFK5fQtxEp97m92ziy+hbu+uKocka1qRRL8MVJIgjv0= cloud.google.com/go/binaryauthorization v1.7.3/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= +cloud.google.com/go/binaryauthorization v1.8.0/go.mod h1:VQ/nUGRKhrStlGr+8GMS8f6/vznYLkdK5vaKfdCIpvU= cloud.google.com/go/certificatemanager v1.3.0/go.mod h1:n6twGDvcUBFu9uBgt4eYvvf3sQ6My8jADcOVwHmzadg= cloud.google.com/go/certificatemanager v1.4.0/go.mod h1:vowpercVFyqs8ABSmrdV+GiFf2H/ch3KyudYQEMM590= cloud.google.com/go/certificatemanager v1.6.0/go.mod h1:3Hh64rCKjRAX8dXgRAyOcY5vQ/fE1sh8o+Mdd6KPgY8= @@ -250,6 +262,7 @@ cloud.google.com/go/channel v1.17.0/go.mod h1:RpbhJsGi/lXWAUM1eF4IbQGbsfVlg2o8Ii cloud.google.com/go/channel v1.17.1/go.mod h1:xqfzcOZAcP4b/hUDH0GkGg1Sd5to6di1HOJn/pi5uBQ= cloud.google.com/go/channel v1.17.2/go.mod h1:aT2LhnftnyfQceFql5I/mP8mIbiiJS4lWqgXA815zMk= cloud.google.com/go/channel v1.17.3/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= +cloud.google.com/go/channel v1.17.4/go.mod h1:QcEBuZLGGrUMm7kNj9IbU1ZfmJq2apotsV83hbxX7eE= cloud.google.com/go/cloudbuild v1.3.0/go.mod h1:WequR4ULxlqvMsjDEEEFnOG5ZSRSgWOywXYDb1vPE6U= cloud.google.com/go/cloudbuild v1.4.0/go.mod h1:5Qwa40LHiOXmz3386FrjrYM93rM/hdRr7b53sySrTqA= cloud.google.com/go/cloudbuild v1.6.0/go.mod h1:UIbc/w9QCbH12xX+ezUsgblrWv+Cv4Tw83GiSMHOn9M= @@ -261,6 +274,7 @@ cloud.google.com/go/cloudbuild v1.14.0/go.mod h1:lyJg7v97SUIPq4RC2sGsz/9tNczhyv2 cloud.google.com/go/cloudbuild v1.14.1/go.mod h1:K7wGc/3zfvmYWOWwYTgF/d/UVJhS4pu+HAy7PL7mCsU= cloud.google.com/go/cloudbuild v1.14.2/go.mod h1:Bn6RO0mBYk8Vlrt+8NLrru7WXlQ9/RDWz2uo5KG1/sg= cloud.google.com/go/cloudbuild v1.14.3/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= +cloud.google.com/go/cloudbuild v1.15.0/go.mod h1:eIXYWmRt3UtggLnFGx4JvXcMj4kShhVzGndL1LwleEM= cloud.google.com/go/clouddms v1.3.0/go.mod h1:oK6XsCDdW4Ib3jCCBugx+gVjevp2TMXFtgxvPSee3OM= cloud.google.com/go/clouddms v1.4.0/go.mod h1:Eh7sUGCC+aKry14O1NRljhjyrr0NFC0G2cjwX0cByRk= cloud.google.com/go/clouddms v1.5.0/go.mod h1:QSxQnhikCLUw13iAbffF2CZxAER3xDGNHjsTAkQJcQA= @@ -315,6 +329,8 @@ cloud.google.com/go/contactcenterinsights v1.11.0/go.mod h1:hutBdImE4XNZ1NV4vbPJ cloud.google.com/go/contactcenterinsights v1.11.1/go.mod h1:FeNP3Kg8iteKM80lMwSk3zZZKVxr+PGnAId6soKuXwE= cloud.google.com/go/contactcenterinsights v1.11.2/go.mod h1:A9PIR5ov5cRcd28KlDbmmXE8Aay+Gccer2h4wzkYFso= cloud.google.com/go/contactcenterinsights v1.11.3/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.0/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= +cloud.google.com/go/contactcenterinsights v1.12.1/go.mod h1:HHX5wrz5LHVAwfI2smIotQG9x8Qd6gYilaHcLLLmNis= cloud.google.com/go/container v1.6.0/go.mod h1:Xazp7GjJSeUYo688S+6J5V+n/t+G5sKBTFkKNudGRxg= cloud.google.com/go/container v1.7.0/go.mod h1:Dp5AHtmothHGX3DwwIHPgq45Y8KmNsgN3amoYfxVkLo= cloud.google.com/go/container v1.13.1/go.mod h1:6wgbMPeQRw9rSnKBCAJXnds3Pzj03C4JHamr8asWKy4= @@ -326,6 +342,8 @@ cloud.google.com/go/container v1.26.0/go.mod h1:YJCmRet6+6jnYYRS000T6k0D0xUXQgBS cloud.google.com/go/container v1.26.1/go.mod h1:5smONjPRUxeEpDG7bMKWfDL4sauswqEtnBK1/KKpR04= cloud.google.com/go/container v1.26.2/go.mod h1:YlO84xCt5xupVbLaMY4s3XNE79MUJ+49VmkInr6HvF4= cloud.google.com/go/container v1.27.1/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.28.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= +cloud.google.com/go/container v1.29.0/go.mod h1:b1A1gJeTBXVLQ6GGw9/9M4FG94BEGsqJ5+t4d/3N7O4= cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= cloud.google.com/go/containeranalysis v0.7.0/go.mod h1:9aUL+/vZ55P2CXfuZjS4UjQ9AgXoSw8Ts6lemfmxBxI= @@ -351,6 +369,8 @@ cloud.google.com/go/datacatalog v1.18.0/go.mod h1:nCSYFHgtxh2MiEktWIz71s/X+7ds/U cloud.google.com/go/datacatalog v1.18.1/go.mod h1:TzAWaz+ON1tkNr4MOcak8EBHX7wIRX/gZKM+yTVsv+A= cloud.google.com/go/datacatalog v1.18.2/go.mod h1:SPVgWW2WEMuWHA+fHodYjmxPiMqcOiWfhc9OD5msigk= cloud.google.com/go/datacatalog v1.18.3/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= +cloud.google.com/go/datacatalog v1.19.0/go.mod h1:5FR6ZIF8RZrtml0VUao22FxhdjkoG+a0866rEnObryM= +cloud.google.com/go/datacatalog v1.19.2/go.mod h1:2YbODwmhpLM4lOFe3PuEhHK9EyTzQJ5AXgIy7EDKTEE= cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= cloud.google.com/go/dataflow v0.8.0/go.mod h1:Rcf5YgTKPtQyYz8bLYhFoIV/vP39eL7fWNcSOyFfLJE= @@ -391,6 +411,9 @@ cloud.google.com/go/dataplex v1.9.1/go.mod h1:7TyrDT6BCdI8/38Uvp0/ZxBslOslP2X2MP cloud.google.com/go/dataplex v1.10.1/go.mod h1:1MzmBv8FvjYfc7vDdxhnLFNskikkB+3vl475/XdCDhs= cloud.google.com/go/dataplex v1.10.2/go.mod h1:xdC8URdTrCrZMW6keY779ZT1cTOfV8KEPNsw+LTRT1Y= cloud.google.com/go/dataplex v1.11.1/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.11.2/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.13.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= +cloud.google.com/go/dataplex v1.14.0/go.mod h1:mHJYQQ2VEJHsyoC0OdNyy988DvEbPhqFs5OOLffLX0c= cloud.google.com/go/dataproc v1.7.0/go.mod h1:CKAlMjII9H90RXaMpSxQ8EU6dQx6iAYNPcYPOkSbi8s= cloud.google.com/go/dataproc v1.8.0/go.mod h1:5OW+zNAH0pMpw14JVrPONsxMQYMBqJuzORhIBfBn9uI= cloud.google.com/go/dataproc v1.12.0/go.mod h1:zrF3aX0uV3ikkMz6z4uBbIKyhRITnxvr4i3IjKsKrw4= @@ -399,6 +422,7 @@ cloud.google.com/go/dataproc/v2 v2.2.0/go.mod h1:lZR7AQtwZPvmINx5J87DSOOpTfof9LV cloud.google.com/go/dataproc/v2 v2.2.1/go.mod h1:QdAJLaBjh+l4PVlVZcmrmhGccosY/omC1qwfQ61Zv/o= cloud.google.com/go/dataproc/v2 v2.2.2/go.mod h1:aocQywVmQVF4i8CL740rNI/ZRpsaaC1Wh2++BJ7HEJ4= cloud.google.com/go/dataproc/v2 v2.2.3/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= +cloud.google.com/go/dataproc/v2 v2.3.0/go.mod h1:G5R6GBc9r36SXv/RtZIVfB8SipI+xVn0bX5SxUzVYbY= cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= cloud.google.com/go/dataqna v0.7.0/go.mod h1:Lx9OcIIeqCrw1a6KdO3/5KMP1wAmTc0slZWwP12Qq3c= @@ -435,6 +459,9 @@ cloud.google.com/go/deploy v1.13.0/go.mod h1:tKuSUV5pXbn67KiubiUNUejqLs4f5cxxiCN cloud.google.com/go/deploy v1.13.1/go.mod h1:8jeadyLkH9qu9xgO3hVWw8jVr29N1mnW42gRJT8GY6g= cloud.google.com/go/deploy v1.14.1/go.mod h1:N8S0b+aIHSEeSr5ORVoC0+/mOPUysVt8ae4QkZYolAw= cloud.google.com/go/deploy v1.14.2/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.15.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.16.0/go.mod h1:e5XOUI5D+YGldyLNZ21wbp9S8otJbBE4i88PtO9x/2g= +cloud.google.com/go/deploy v1.17.0/go.mod h1:XBr42U5jIr64t92gcpOXxNrqL2PStQCXHuKK5GRUuYo= cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= @@ -450,6 +477,9 @@ cloud.google.com/go/dialogflow v1.44.0/go.mod h1:pDUJdi4elL0MFmt1REMvFkdsUTYSHq+ cloud.google.com/go/dialogflow v1.44.1/go.mod h1:n/h+/N2ouKOO+rbe/ZnI186xImpqvCVj2DdsWS/0EAk= cloud.google.com/go/dialogflow v1.44.2/go.mod h1:QzFYndeJhpVPElnFkUXxdlptx0wPnBWLCBT9BvtC3/c= cloud.google.com/go/dialogflow v1.44.3/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.47.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.0/go.mod h1:mHly4vU7cPXVweuB5R0zsYKPMzy240aQdAu06SqBbAQ= +cloud.google.com/go/dialogflow v1.48.1/go.mod h1:C1sjs2/g9cEwjCltkKeYp3FFpz8BOzNondEaAlCpt+A= cloud.google.com/go/dlp v1.6.0/go.mod h1:9eyB2xIhpU0sVwUixfBubDoRwP+GjeUoxxeueZmqvmM= cloud.google.com/go/dlp v1.7.0/go.mod h1:68ak9vCiMBjbasxeVD17hVPxDEck+ExiHavX8kiHG+Q= cloud.google.com/go/dlp v1.9.0/go.mod h1:qdgmqgTyReTz5/YNSSuueR8pl7hO0o9bQ39ZhtgkWp4= @@ -470,6 +500,8 @@ cloud.google.com/go/documentai v1.23.0/go.mod h1:LKs22aDHbJv7ufXuPypzRO7rG3ALLJx cloud.google.com/go/documentai v1.23.2/go.mod h1:Q/wcRT+qnuXOpjAkvOV4A+IeQl04q2/ReT7SSbytLSo= cloud.google.com/go/documentai v1.23.4/go.mod h1:4MYAaEMnADPN1LPN5xboDR5QVB6AgsaxgFdJhitlE2Y= cloud.google.com/go/documentai v1.23.5/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.6/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= +cloud.google.com/go/documentai v1.23.7/go.mod h1:ghzBsyVTiVdkfKaUCum/9bGBEyBjDO4GfooEcYKhN+g= cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= cloud.google.com/go/domains v0.8.0/go.mod h1:M9i3MMDzGFXsydri9/vW+EWz9sWb4I6WyHqdlAk0idE= @@ -510,6 +542,7 @@ cloud.google.com/go/filestore v1.7.1/go.mod h1:y10jsorq40JJnjR/lQ8AfFbbcGlw3g+Dp cloud.google.com/go/filestore v1.7.2/go.mod h1:TYOlyJs25f/omgj+vY7/tIG/E7BX369triSPzE4LdgE= cloud.google.com/go/filestore v1.7.3/go.mod h1:Qp8WaEERR3cSkxToxFPHh/b8AACkSut+4qlCjAmKTV0= cloud.google.com/go/filestore v1.7.4/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= +cloud.google.com/go/filestore v1.8.0/go.mod h1:S5JCxIbFjeBhWMTfIYH2Jx24J6BqjwpkkPl+nBA5DlI= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/firestore v1.11.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= cloud.google.com/go/firestore v1.12.0/go.mod h1:b38dKhgzlmNNGTNZZwe7ZRFEuRab1Hay3/DBsIGKKy4= @@ -563,6 +596,7 @@ cloud.google.com/go/gkemulticloud v1.0.0/go.mod h1:kbZ3HKyTsiwqKX7Yw56+wUGwwNZVi cloud.google.com/go/gkemulticloud v1.0.1/go.mod h1:AcrGoin6VLKT/fwZEYuqvVominLriQBCKmbjtnbMjG8= cloud.google.com/go/gkemulticloud v1.0.2/go.mod h1:+ee5VXxKb3H1l4LZAcgWB/rvI16VTNTrInWxDjAGsGo= cloud.google.com/go/gkemulticloud v1.0.3/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= +cloud.google.com/go/gkemulticloud v1.1.0/go.mod h1:7NpJBN94U6DY1xHIbsDqB2+TFZUfjLUKLjUX8NGLor0= cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= cloud.google.com/go/grafeas v0.3.0/go.mod h1:P7hgN24EyONOTMyeJH6DxG4zD7fwiYa5Q6GUgyFSOU8= cloud.google.com/go/gsuiteaddons v1.3.0/go.mod h1:EUNK/J1lZEZO8yPtykKxLXI6JSVN2rg9bN8SXOa0bgM= @@ -647,6 +681,7 @@ cloud.google.com/go/lifesciences v0.9.4/go.mod h1:bhm64duKhMi7s9jR9WYJYvjAFJwRqN cloud.google.com/go/logging v1.6.1/go.mod h1:5ZO0mHHbvm8gEmeEUHrmDlTDSu5imF6MUP9OfilNXBw= cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= cloud.google.com/go/logging v1.8.1/go.mod h1:TJjR+SimHwuC8MZ9cjByQulAMgni+RkXeI3wwctHJEI= +cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= cloud.google.com/go/longrunning v0.1.1/go.mod h1:UUFxuDWkv22EuY93jjmDMFT5GPQKeFVJBIF6QlTqdsE= cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo= @@ -671,6 +706,8 @@ cloud.google.com/go/maps v1.4.0/go.mod h1:6mWTUv+WhnOwAgjVsSW2QPPECmW+s3PcRyOa9v cloud.google.com/go/maps v1.4.1/go.mod h1:BxSa0BnW1g2U2gNdbq5zikLlHUuHW0GFWh7sgML2kIY= cloud.google.com/go/maps v1.5.1/go.mod h1:NPMZw1LJwQZYCfz4y+EIw+SI+24A4bpdFJqdKVr0lt4= cloud.google.com/go/maps v1.6.1/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.2/go.mod h1:4+buOHhYXFBp58Zj/K+Lc1rCmJssxxF4pJ5CJnhdz18= +cloud.google.com/go/maps v1.6.3/go.mod h1:VGAn809ADswi1ASofL5lveOHPnE6Rk/SFTTBx1yuOLw= cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= @@ -707,6 +744,7 @@ cloud.google.com/go/monitoring v1.16.0/go.mod h1:Ptp15HgAyM1fNICAojDMoNc/wUmn67m cloud.google.com/go/monitoring v1.16.1/go.mod h1:6HsxddR+3y9j+o/cMJH6q/KJ/CBTvM/38L/1m7bTRJ4= cloud.google.com/go/monitoring v1.16.2/go.mod h1:B44KGwi4ZCF8Rk/5n+FWeispDXoKSk9oss2QNlXJBgc= cloud.google.com/go/monitoring v1.16.3/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= +cloud.google.com/go/monitoring v1.17.0/go.mod h1:KwSsX5+8PnXv5NJnICZzW2R8pWTis8ypC4zmdRD63Tw= cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= cloud.google.com/go/networkconnectivity v1.6.0/go.mod h1:OJOoEXW+0LAxHh89nXd64uGG+FbQoeH8DtxCHVOMlaM= @@ -769,6 +807,7 @@ cloud.google.com/go/orgpolicy v1.11.1/go.mod h1:8+E3jQcpZJQliP+zaFfayC2Pg5bmhuLK cloud.google.com/go/orgpolicy v1.11.2/go.mod h1:biRDpNwfyytYnmCRWZWxrKF22Nkz9eNVj9zyaBdpm1o= cloud.google.com/go/orgpolicy v1.11.3/go.mod h1:oKAtJ/gkMjum5icv2aujkP4CxROxPXsBbYGCDbPO8MM= cloud.google.com/go/orgpolicy v1.11.4/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= +cloud.google.com/go/orgpolicy v1.12.0/go.mod h1:0+aNV/nrfoTQ4Mytv+Aw+stBDBjNf4d8fYRA9herfJI= cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= cloud.google.com/go/osconfig v1.9.0/go.mod h1:Yx+IeIZJ3bdWmzbQU4fxNl8xsZ4amB+dygAwFPlvnNo= @@ -789,6 +828,7 @@ cloud.google.com/go/oslogin v1.11.0/go.mod h1:8GMTJs4X2nOAUVJiPGqIWVcDaF0eniEto3 cloud.google.com/go/oslogin v1.11.1/go.mod h1:OhD2icArCVNUxKqtK0mcSmKL7lgr0LVlQz+v9s1ujTg= cloud.google.com/go/oslogin v1.12.1/go.mod h1:VfwTeFJGbnakxAY236eN8fsnglLiVXndlbcNomY4iZU= cloud.google.com/go/oslogin v1.12.2/go.mod h1:CQ3V8Jvw4Qo4WRhNPF0o+HAM4DiLuE27Ul9CX9g2QdY= +cloud.google.com/go/oslogin v1.13.0/go.mod h1:xPJqLwpTZ90LSE5IL1/svko+6c5avZLluiyylMb/sRA= cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= @@ -824,6 +864,7 @@ cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9 cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= cloud.google.com/go/pubsub v1.32.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= cloud.google.com/go/pubsub v1.33.0/go.mod h1:f+w71I33OMyxf9VpMVcZbnG5KSUkCOUHYpFd5U1GdRc= +cloud.google.com/go/pubsub v1.34.0/go.mod h1:alj4l4rBg+N3YTFDDC+/YyFTs6JAjam2QfYsddcAW4c= cloud.google.com/go/pubsublite v1.5.0/go.mod h1:xapqNQ1CuLfGi23Yda/9l4bBCKz/wC3KIJ5gKcxveZg= cloud.google.com/go/pubsublite v1.6.0/go.mod h1:1eFCS0U11xlOuMFV/0iBqw3zP12kddMeCbj/F3FSj9k= cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= @@ -841,6 +882,8 @@ cloud.google.com/go/recaptchaenterprise/v2 v2.8.0/go.mod h1:QuE8EdU9dEnesG8/kG3X cloud.google.com/go/recaptchaenterprise/v2 v2.8.1/go.mod h1:JZYZJOeZjgSSTGP4uz7NlQ4/d1w5hGmksVgM0lbEij0= cloud.google.com/go/recaptchaenterprise/v2 v2.8.2/go.mod h1:kpaDBOpkwD4G0GVMzG1W6Doy1tFFC97XAV3xy+Rd/pw= cloud.google.com/go/recaptchaenterprise/v2 v2.8.3/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.8.4/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= +cloud.google.com/go/recaptchaenterprise/v2 v2.9.0/go.mod h1:Dak54rw6lC2gBY8FBznpOCAR58wKf+R+ZSJRoeJok4w= cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= @@ -858,6 +901,7 @@ cloud.google.com/go/recommender v1.11.0/go.mod h1:kPiRQhPyTJ9kyXPCG6u/dlPLbYfFlk cloud.google.com/go/recommender v1.11.1/go.mod h1:sGwFFAyI57v2Hc5LbIj+lTwXipGu9NW015rkaEM5B18= cloud.google.com/go/recommender v1.11.2/go.mod h1:AeoJuzOvFR/emIcXdVFkspVXVTYpliRCmKNYDnyBv6Y= cloud.google.com/go/recommender v1.11.3/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= +cloud.google.com/go/recommender v1.12.0/go.mod h1:+FJosKKJSId1MBFeJ/TTyoGQZiEelQQIZMKYYD8ruK4= cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= cloud.google.com/go/redis v1.9.0/go.mod h1:HMYQuajvb2D0LvMgZmLDZW8V5aOC/WxstZHiy4g8OiA= @@ -911,6 +955,7 @@ cloud.google.com/go/scheduler v1.10.1/go.mod h1:R63Ldltd47Bs4gnhQkmNDse5w8gBRrhO cloud.google.com/go/scheduler v1.10.2/go.mod h1:O3jX6HRH5eKCA3FutMw375XHZJudNIKVonSCHv7ropY= cloud.google.com/go/scheduler v1.10.3/go.mod h1:8ANskEM33+sIbpJ+R4xRfw/jzOG+ZFE8WVLy7/yGvbc= cloud.google.com/go/scheduler v1.10.4/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= +cloud.google.com/go/scheduler v1.10.5/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= @@ -940,6 +985,7 @@ cloud.google.com/go/securitycenter v1.23.0/go.mod h1:8pwQ4n+Y9WCWM278R8W3nF65QtY cloud.google.com/go/securitycenter v1.23.1/go.mod h1:w2HV3Mv/yKhbXKwOCu2i8bCuLtNP1IMHuiYQn4HJq5s= cloud.google.com/go/securitycenter v1.24.1/go.mod h1:3h9IdjjHhVMXdQnmqzVnM7b0wMn/1O/U20eWVpMpZjI= cloud.google.com/go/securitycenter v1.24.2/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= +cloud.google.com/go/securitycenter v1.24.3/go.mod h1:l1XejOngggzqwr4Fa2Cn+iWZGf+aBLTXtB/vXjy5vXM= cloud.google.com/go/servicecontrol v1.4.0/go.mod h1:o0hUSJ1TXJAmi/7fLJAedOovnujSEvjKCAFNXPQ1RaU= cloud.google.com/go/servicecontrol v1.5.0/go.mod h1:qM0CnXHhyqKVuiZnGKrIurvVImCs8gmqWsDoqe9sU1s= cloud.google.com/go/servicecontrol v1.10.0/go.mod h1:pQvyvSRh7YzUF2efw7H87V92mxU8FnFDawMClGCNuAA= @@ -978,6 +1024,10 @@ cloud.google.com/go/spanner v1.47.0/go.mod h1:IXsJwVW2j4UKs0eYDqodab6HgGuA1bViSq cloud.google.com/go/spanner v1.49.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= cloud.google.com/go/spanner v1.50.0/go.mod h1:eGj9mQGK8+hkgSVbHNQ06pQ4oS+cyc4tXXd6Dif1KoM= cloud.google.com/go/spanner v1.51.0/go.mod h1:c5KNo5LQ1X5tJwma9rSQZsXNBDNvj4/n8BVc3LNahq0= +cloud.google.com/go/spanner v1.53.0/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.53.1/go.mod h1:liG4iCeLqm5L3fFLU5whFITqP0e0orsAW1uUSrd4rws= +cloud.google.com/go/spanner v1.54.0/go.mod h1:wZvSQVBgngF0Gq86fKup6KIYmN2be7uOKjtK97X+bQU= +cloud.google.com/go/spanner v1.55.0/go.mod h1:HXEznMUVhC+PC+HDyo9YFG2Ajj5BQDkcbqB9Z2Ffxi0= cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= cloud.google.com/go/speech v1.8.0/go.mod h1:9bYIl1/tjsAnMgKGHKmBZzXKEkGgtU+MpdDPTE9f7y0= @@ -989,6 +1039,7 @@ cloud.google.com/go/speech v1.19.0/go.mod h1:8rVNzU43tQvxDaGvqOhpDqgkJTFowBpDvCJ cloud.google.com/go/speech v1.19.1/go.mod h1:WcuaWz/3hOlzPFOVo9DUsblMIHwxP589y6ZMtaG+iAA= cloud.google.com/go/speech v1.19.2/go.mod h1:2OYFfj+Ch5LWjsaSINuCZsre/789zlcCI3SY4oAi2oI= cloud.google.com/go/speech v1.20.1/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= +cloud.google.com/go/speech v1.21.0/go.mod h1:wwolycgONvfz2EDU8rKuHRW3+wc9ILPsAWoikBEWavY= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -1001,6 +1052,7 @@ cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= +cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1051,6 +1103,7 @@ cloud.google.com/go/translate v1.9.0/go.mod h1:d1ZH5aaOA0CNhWeXeC8ujd4tdCFw8XoNW cloud.google.com/go/translate v1.9.1/go.mod h1:TWIgDZknq2+JD4iRcojgeDtqGEp154HN/uL6hMvylS8= cloud.google.com/go/translate v1.9.2/go.mod h1:E3Tc6rUTsQkVrXW6avbUhKJSr7ZE3j7zNmqzXKHqRrY= cloud.google.com/go/translate v1.9.3/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= +cloud.google.com/go/translate v1.10.0/go.mod h1:Kbq9RggWsbqZ9W5YpM94Q1Xv4dshw/gr/SHfsl5yCZ0= cloud.google.com/go/video v1.8.0/go.mod h1:sTzKFc0bUSByE8Yoh8X0mn8bMymItVGPfTuUBUyRgxk= cloud.google.com/go/video v1.9.0/go.mod h1:0RhNKFRF5v92f8dQt0yhaHrEuH95m068JYOvLZYnJSw= cloud.google.com/go/video v1.12.0/go.mod h1:MLQew95eTuaNDEGriQdcYn0dTwf9oWiA4uYebxM5kdg= @@ -1148,6 +1201,7 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= +github.com/apache/arrow/go/v12 v12.0.1/go.mod h1:weuTY7JvTG/HDPtMQxEUp7pU73vkLWMLpY67QwZ/WWw= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= @@ -1176,8 +1230,8 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -1205,8 +1259,10 @@ github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0+ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -1220,6 +1276,10 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pdf/fpdf v0.5.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhOh5M= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -1228,6 +1288,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ= +github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -1284,6 +1345,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-pkcs11 v0.2.0/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoGcuNJpa7jnd5pMGdkSaQpNDYvPlXWMcjXXThLlY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -1313,6 +1375,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -1345,8 +1409,10 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9K github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= @@ -1375,6 +1441,8 @@ github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4 github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY= github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= @@ -1402,10 +1470,12 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -1433,6 +1503,16 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= +go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= +go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= +go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= +go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1447,6 +1527,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= @@ -1456,6 +1537,9 @@ golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98y golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1515,6 +1599,7 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1578,10 +1663,13 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1615,7 +1703,8 @@ golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0= -golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= +golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM= +golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1636,6 +1725,7 @@ golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1720,8 +1810,10 @@ golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1738,6 +1830,8 @@ golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1766,6 +1860,7 @@ golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1830,6 +1925,7 @@ golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1838,6 +1934,7 @@ golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= @@ -1911,6 +2008,8 @@ google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvy google.golang.org/api v0.128.0/go.mod h1:Y611qgqaE92On/7g65MQgxYul3c0rEB894kniWLY750= google.golang.org/api v0.139.0/go.mod h1:CVagp6Eekz9CjGZ718Z+sloknzkDJE7Vc1Ckj9+viBk= google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI= +google.golang.org/api v0.150.0/go.mod h1:ccy+MJ6nrYFgE3WgRx/AMXOxOmU8Q4hSa+jjibzhxcg= +google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7Igduk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2069,8 +2168,14 @@ google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqv google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE= google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI= google.golang.org/genproto v0.0.0-20231030173426-d783a09b4405/go.mod h1:3WDQMjmJk36UQhjQ89emUzb1mdaHcPeeAh4SCBKznB4= -google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= +google.golang.org/genproto v0.0.0-20231120223509-83a465c0220f/go.mod h1:nWSwAFPb+qfNJXsoeO3Io7zf4tMSfN8EA8RlDA04GhY= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRxr25RbDzY5w+dmaqpSEvl8Gwl1x2CICf60ic= +google.golang.org/genproto v0.0.0-20231212172506-995d672761c0/go.mod h1:l/k7rMz0vFTBPy+tFSGvXEd3z+BcoG1k7EHbqm+YBsY= +google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= +google.golang.org/genproto v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:+Rvu7ElI+aLzyDQhpHMFMMltsD6m7nqpuWDd2CwJw3k= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ= +google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -2088,9 +2193,16 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231012201019-e917dd12ba7a/go. google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870= google.golang.org/genproto/googleapis/api v0.0.0-20231030173426-d783a09b4405/go.mod h1:oT32Z4o8Zv2xPQTg0pbVaPr0MPOH6f14RgXt7zfIpwg= google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= +google.golang.org/genproto/googleapis/api v0.0.0-20231120223509-83a465c0220f/go.mod h1:Uy9bTZJqmfrw2rIBxgGLnamc78euZULUBrLZ9XTITKI= +google.golang.org/genproto/googleapis/api v0.0.0-20231211222908-989df2bf70f3/go.mod h1:k2dtGpRrbsSyKcNPKKI5sstZkrNCZwpU/ns96JoHbGg= +google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go.mod h1:CAny0tYF+0/9rmDB9fahA9YLzX3+AEVl1qXbv5hhj6c= +google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= +google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20231212172506-995d672761c0/go.mod h1:guYXGPwC6jwxgWKW5Y405fKWOFNwlvUlUnzyp9i0uqo= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234015-3fc162c6f38a/go.mod h1:xURIpW9ES5+/GZhnV6beoEtxQrnkRGIfP5VQG2tCBLc= google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= @@ -2107,8 +2219,14 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:4cYg8o5yUbm77w8ZX00LhMVNl/YVBFJRYWDc0uYWMs0= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20231030173426-d783a09b4405/go.mod h1:67X1fPuzjcrkymZzZV1vvkFeTn2Rvc6lYF9MYFGCcwE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14= google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231211222908-989df2bf70f3/go.mod h1:eJVxU6o+4G1PSczBr85xmyvSNYAKvAYgkub40YGomFM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go.mod h1:FUoWkonphQm3RhTS+kOEhF8h0iDpm4tdXolVCeZ9KKA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:daQN87bsDqDoe316QbbvX60nMoJQa4r6Ds0ZuoAe5yA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -2157,8 +2275,10 @@ google.golang.org/grpc v1.57.0/go.mod h1:Sd+9RMTACXwmub0zcNY2c4arhtrbBYD1AUHI/dt google.golang.org/grpc v1.58.2/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= -google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY= -google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From ee58c0a523c34d4fe15cba8f859162bb6c01b7e9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 14:59:13 +0000 Subject: [PATCH 100/151] build(deps): bump elasticsearch from 8.12.1 to 8.12.2 in /examples/skywalking (#32537) build(deps): bump elasticsearch in /examples/skywalking Bumps elasticsearch from 8.12.1 to 8.12.2. --- updated-dependencies: - dependency-name: elasticsearch dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/skywalking/Dockerfile-elasticsearch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/skywalking/Dockerfile-elasticsearch b/examples/skywalking/Dockerfile-elasticsearch index 302ff21a4a47..ffd138dc89e8 100644 --- a/examples/skywalking/Dockerfile-elasticsearch +++ b/examples/skywalking/Dockerfile-elasticsearch @@ -1 +1 @@ -FROM elasticsearch:8.12.1@sha256:8c18a68f086b0935c56a7d6fb02e602fc0e2265a31668c6679c7d159a302b1c1 +FROM elasticsearch:8.12.2@sha256:b0dfebb1bf16b89b3cbde7f9197f13b5dd3402595a3dccb0163aa33a042a1541 From 0ff8260482487bbb7a3af1e210817cdccad3dac9 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:01:55 +0000 Subject: [PATCH 101/151] deps: Bump `com_github_aignas_rules_shellcheck` -> 0.3.3 (#32544) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 9c7746385ca9..d4ec1d4447c5 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -176,11 +176,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Shellcheck rules for bazel", project_desc = "Now you do not need to depend on the system shellcheck version in your bazel-managed (mono)repos.", project_url = "https://github.com/aignas/rules_shellcheck", - version = "0.3.2", - sha256 = "798c7ff488a949e51d7d41d853d79164ce5c5335364ba32f972b79df8dd6be62", + version = "0.3.3", + sha256 = "936ece8097b734ac7fab10f833a68f7d646b4bc760eb5cde3ab17beb85779d50", strip_prefix = "rules_shellcheck-{version}", urls = ["https://github.com/aignas/rules_shellcheck/archive/{version}.tar.gz"], - release_date = "2024-01-10", + release_date = "2024-02-15", use_category = ["build"], cpe = "N/A", license = "MIT", From e6e5e9ac971ae7690d1095aea0064509de2b8c5d Mon Sep 17 00:00:00 2001 From: norbjd Date: Mon, 26 Feb 2024 16:09:43 +0100 Subject: [PATCH 102/151] tooling: generate JSON schemas from protobuf definitions (#27640) Signed-off-by: norbjd --- api/bazel/repositories.bzl | 3 ++ api/bazel/repository_locations.bzl | 11 ++++++ bazel/dependency_imports.bzl | 2 + tools/api_proto_plugin/plugin.bzl | 24 ++++++++---- tools/protojsonschema/BUILD | 22 +++++++++++ tools/protojsonschema/generate.bzl | 22 +++++++++++ tools/protojsonschema_with_aspects/BUILD | 12 ++++++ .../protojsonschema.bzl | 39 +++++++++++++++++++ 8 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 tools/protojsonschema/BUILD create mode 100644 tools/protojsonschema/generate.bzl create mode 100644 tools/protojsonschema_with_aspects/BUILD create mode 100644 tools/protojsonschema_with_aspects/protojsonschema.bzl diff --git a/api/bazel/repositories.bzl b/api/bazel/repositories.bzl index 33aaa220fb6d..cd5936cdffd1 100644 --- a/api/bazel/repositories.bzl +++ b/api/bazel/repositories.bzl @@ -62,6 +62,9 @@ def api_dependencies(): external_http_archive( name = "com_github_chrusty_protoc_gen_jsonschema", ) + external_http_archive( + name = "rules_proto_grpc", + ) external_http_archive( name = "envoy_toolshed", diff --git a/api/bazel/repository_locations.bzl b/api/bazel/repository_locations.bzl index 95dff79958ff..4ba96d3a72f0 100644 --- a/api/bazel/repository_locations.bzl +++ b/api/bazel/repository_locations.bzl @@ -151,6 +151,17 @@ REPOSITORY_LOCATIONS_SPEC = dict( use_category = ["build"], release_date = "2023-05-30", ), + rules_proto_grpc = dict( + project_name = "rules_proto_grpc", + project_desc = "Bazel rules for building Protobuf and gRPC code and libraries from proto_library targets ", + project_url = "https://github.com/rules-proto-grpc/rules_proto_grpc", + version = "4.4.0", + sha256 = "928e4205f701b7798ce32f3d2171c1918b363e9a600390a25c876f075f1efc0a", + strip_prefix = "rules_proto_grpc-{version}", + urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/releases/download/{version}/rules_proto_grpc-{version}.tar.gz"], + use_category = ["build"], + release_date = "2023-05-03", + ), envoy_toolshed = dict( project_name = "envoy_toolshed", project_desc = "Tooling, libraries, runners and checkers for Envoy proxy's CI", diff --git a/bazel/dependency_imports.bzl b/bazel/dependency_imports.bzl index 85843dc44972..62699582017b 100644 --- a/bazel/dependency_imports.bzl +++ b/bazel/dependency_imports.bzl @@ -16,6 +16,7 @@ load("@com_github_aignas_rules_shellcheck//:deps.bzl", "shellcheck_dependencies" load("@aspect_bazel_lib//lib:repositories.bzl", "register_jq_toolchains", "register_yq_toolchains") load("@com_google_cel_cpp//bazel:deps.bzl", "parser_deps") load("@com_github_chrusty_protoc_gen_jsonschema//:deps.bzl", protoc_gen_jsonschema_go_dependencies = "go_dependencies") +load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_toolchains") # go version for rules_go GO_VERSION = "1.20" @@ -156,6 +157,7 @@ def envoy_dependency_imports(go_version = GO_VERSION, jq_version = JQ_VERSION, y ) protoc_gen_jsonschema_go_dependencies() + rules_proto_grpc_toolchains() def envoy_download_go_sdks(go_version): go_download_sdk( diff --git a/tools/api_proto_plugin/plugin.bzl b/tools/api_proto_plugin/plugin.bzl index 9c2dd92a568b..014749a7d1a5 100644 --- a/tools/api_proto_plugin/plugin.bzl +++ b/tools/api_proto_plugin/plugin.bzl @@ -23,7 +23,7 @@ def _path_ignoring_repository(f): def _input_arg(i): return "%s=%s" % (i.basename, i.path) -def api_proto_plugin_impl(target, ctx, output_group, mnemonic, output_suffixes): +def api_proto_plugin_impl(target, ctx, output_group, mnemonic, output_suffixes, output_dir = ""): # Compute output files from the current proto_library node's dependencies. transitive_outputs = depset(transitive = [dep.output_groups[output_group] for dep in ctx.rule.attr.deps]) @@ -53,18 +53,26 @@ def api_proto_plugin_impl(target, ctx, output_group, mnemonic, output_suffixes): for f in target[ProtoInfo].transitive_sources.to_list(): import_paths.append("{}={}".format(_path_ignoring_repository(f), f.path)) - # The outputs live in the ctx.label's package root. We add some additional - # path information to match with protoc's notion of path relative locations. outputs = [] - for output_suffix in output_suffixes: - outputs += [ctx.actions.declare_file(ctx.label.name + "/" + _path_ignoring_repository(f) + - output_suffix) for f in proto_sources] + output_path = "" + + if output_suffixes: + # The outputs live in the ctx.label's package root. We add some additional + # path information to match with protoc's notion of path relative locations. + outputs = [] + for output_suffix in output_suffixes: + outputs += [ctx.actions.declare_file(ctx.label.name + "/" + _path_ignoring_repository(f) + + output_suffix) for f in proto_sources] + + ctx_path = ctx.label.package + "/" + ctx.label.name + output_path = outputs[0].root.path + "/" + outputs[0].owner.workspace_root + "/" + ctx_path + elif output_dir: + outputs.append(ctx.actions.declare_directory(output_dir)) + output_path = outputs[0].path # Create the protoc command-line args. inputs = [target[ProtoInfo].transitive_sources] - ctx_path = ctx.label.package + "/" + ctx.label.name - output_path = outputs[0].root.path + "/" + outputs[0].owner.workspace_root + "/" + ctx_path args = ctx.actions.args() args.add(ctx.label.workspace_root, format = "-I./%s") args.add_all(import_paths, format_each = "-I%s") diff --git a/tools/protojsonschema/BUILD b/tools/protojsonschema/BUILD new file mode 100644 index 000000000000..6fed0c937a6c --- /dev/null +++ b/tools/protojsonschema/BUILD @@ -0,0 +1,22 @@ +load("@rules_proto_grpc//:defs.bzl", "proto_plugin") +load(":generate.bzl", "jsonschema_compile") + +licenses(["notice"]) # Apache 2 + +proto_plugin( + name = "protoc_gen_jsonschema_proto_plugin", + output_directory = True, + tool = "@com_github_chrusty_protoc_gen_jsonschema//cmd/protoc-gen-jsonschema", +) + +[ + jsonschema_compile( + # example: "@envoy_api//envoy/config/bootstrap/v3:pkg" => "envoy_config_bootstrap_v3" + name = proto.replace("@envoy_api//", "").replace("/", "_").replace(":pkg", ""), + protos = [proto], + ) + for proto in [ + "@envoy_api//envoy/config/bootstrap/v2:pkg", + "@envoy_api//envoy/config/bootstrap/v3:pkg", + ] +] diff --git a/tools/protojsonschema/generate.bzl b/tools/protojsonschema/generate.bzl new file mode 100644 index 000000000000..915945740768 --- /dev/null +++ b/tools/protojsonschema/generate.bzl @@ -0,0 +1,22 @@ +load( + "@rules_proto_grpc//:defs.bzl", + "ProtoPluginInfo", + "proto_compile_attrs", + "proto_compile_impl", +) + +# Create compile rule +jsonschema_compile = rule( + implementation = proto_compile_impl, + attrs = dict( + proto_compile_attrs, + _plugins = attr.label_list( + providers = [ProtoPluginInfo], + default = [ + Label(":protoc_gen_jsonschema_proto_plugin"), + ], + doc = "List of protoc plugins to apply", + ), + ), + toolchains = [str(Label("@rules_proto_grpc//protobuf:toolchain_type"))], +) diff --git a/tools/protojsonschema_with_aspects/BUILD b/tools/protojsonschema_with_aspects/BUILD new file mode 100644 index 000000000000..10cdeca6dd1c --- /dev/null +++ b/tools/protojsonschema_with_aspects/BUILD @@ -0,0 +1,12 @@ +load("//tools/protojsonschema_with_aspects:protojsonschema.bzl", "protojsonschema_rule") + +licenses(["notice"]) # Apache 2 + +protojsonschema_rule( + name = "api_protojsonschema", + visibility = ["//visibility:public"], + deps = [ + "@envoy_api//envoy/config/bootstrap/v2:pkg", + "@envoy_api//envoy/config/bootstrap/v3:pkg", + ], +) diff --git a/tools/protojsonschema_with_aspects/protojsonschema.bzl b/tools/protojsonschema_with_aspects/protojsonschema.bzl new file mode 100644 index 000000000000..a1a5e38742ce --- /dev/null +++ b/tools/protojsonschema_with_aspects/protojsonschema.bzl @@ -0,0 +1,39 @@ +load("@rules_proto//proto:defs.bzl", "ProtoInfo") +load("//tools/api_proto_plugin:plugin.bzl", "api_proto_plugin_aspect", "api_proto_plugin_impl") + +def _protojsonschema_impl(target, ctx): + return api_proto_plugin_impl( + target = target, + ctx = ctx, + output_group = "proto", + mnemonic = "protojsonschema", + output_suffixes = [], + output_dir = "jsonschema", + ) + +protojsonschema_aspect = api_proto_plugin_aspect( + "@com_github_chrusty_protoc_gen_jsonschema//cmd/protoc-gen-jsonschema", + _protojsonschema_impl, +) + +def _protojsonschema_rule_impl(ctx): + return [ + DefaultInfo( + files = depset( + transitive = [ + depset([ + path + for dep in ctx.attr.deps + for path in dep[OutputGroupInfo].proto.to_list() + ]), + ], + ), + ), + ] + +protojsonschema_rule = rule( + implementation = _protojsonschema_rule_impl, + attrs = { + "deps": attr.label_list(aspects = [protojsonschema_aspect]), + }, +) From cf1816ab9e3c62b3b7e4ed5f632b8966a73d5fa4 Mon Sep 17 00:00:00 2001 From: phlax Date: Mon, 26 Feb 2024 16:21:56 +0000 Subject: [PATCH 103/151] mobile/ios: Prevent use of android NDK in CI (#32582) Signed-off-by: Ryan Northey --- .github/workflows/mobile-ios_build.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 61bce4bd5026..5131d4581729 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -91,6 +91,9 @@ jobs: app: ${{ matrix.app }} args: ${{ matrix.args || '--config=mobile-remote-ci-macos-ios' }} expected: received headers with status ${{ matrix.expected-status }} + env: + ANDROID_NDK_HOME: + ANDROID_HOME: target: ${{ matrix.target }} timeout-minutes: ${{ matrix.timeout-minutes }} trusted: ${{ fromJSON(needs.load.outputs.trusted) }} @@ -134,6 +137,9 @@ jobs: expected: >- ${{ matrix.expected || format('received headers with status {0}', matrix.expected-status) }} + env: + ANDROID_NDK_HOME: + ANDROID_HOME: target: ${{ matrix.target }} timeout-minutes: 50 trusted: ${{ fromJSON(needs.load.outputs.trusted) }} From 9a47bc9eff9c3283fbfaed9144313b7bda366aac Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Mon, 26 Feb 2024 13:54:22 -0500 Subject: [PATCH 104/151] mobile: Add the `no-remote-exec` tag to the Swift stats tests (#32567) For reasons we haven't figured out yet, RBE does not work well with the Swift tests and causes frequent timeouts on CI. See https://github.com/envoyproxy/envoy/issues/32551 for details. We tagged the Swift tests with `no-remote-exec` but forgot to do so for the swift/stats tests, so we're doing that here. This test timed out recently on CI because we didn't have the `no-remote-exec` tag. Signed-off-by: Ali Beyad --- mobile/test/swift/stats/BUILD | 1 + 1 file changed, 1 insertion(+) diff --git a/mobile/test/swift/stats/BUILD b/mobile/test/swift/stats/BUILD index 2d77e215c8a3..14e1d31bd8d0 100644 --- a/mobile/test/swift/stats/BUILD +++ b/mobile/test/swift/stats/BUILD @@ -13,6 +13,7 @@ envoy_mobile_swift_test( "TagsBuilderTests.swift", ], flaky = True, # TODO(jpsim): Fix timeouts when running these tests on CI + tags = ["no-remote-exec"], # TODO(32551): Re-enable remote exec visibility = ["//visibility:public"], deps = [ "//library/objective-c:envoy_engine_objc_lib", From 4beee066581b5cae9e67484859a49366b524341d Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Mon, 26 Feb 2024 20:37:51 -0500 Subject: [PATCH 105/151] mobile: Add a comment on why the StreamCallbacks context is a raw pointer (#32504) Signed-off-by: Ali Beyad --- mobile/library/cc/stream_callbacks.cc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mobile/library/cc/stream_callbacks.cc b/mobile/library/cc/stream_callbacks.cc index eaab4f20ffc9..1de7f20a39c9 100644 --- a/mobile/library/cc/stream_callbacks.cc +++ b/mobile/library/cc/stream_callbacks.cc @@ -110,6 +110,15 @@ envoy_http_callbacks StreamCallbacks::asEnvoyHttpCallbacks() { &c_on_complete, &c_on_cancel, &c_on_send_window_available, + // Each of the function pointers in the returned `envoy_http_callbacks` struct have a + // `void* context` parameter. The value of the `context` field of this struct is passed in as + // the value of that parameter. Because this context passes through JNI, the context field + // can not be a smart pointer and must instead be a standard C-pointer. However, the + // `StreamCallbacks` object is reference counted and so will be destroyed when the final + // shared_ptr is destroyed. So in order to make sure that it lives long enough, the `context` + // field here stores a pointer to a newly created `shared_ptr` which will not go out of scope + // when this method returns. When the `StreamCallbacks` is no longer need (c_on_complete, + // c_on_cancel, c_on_error), this new `shared_ptr` will need to be deleted. new StreamCallbacksSharedPtr(shared_from_this()), }; } From ea047ed9ba4f16212fc6322a4b18a5770fcb0e05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:43:52 +0000 Subject: [PATCH 106/151] build(deps): bump distroless/base-nossl-debian12 from `49edf70` to `0e777c6` in /ci (#32576) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `49edf70` to `0e777c6`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 2de63d92e29a..07affa3502e3 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:49edf7003af1015b0841f34a04197e8b1c5f1d0c91e897c97749c78ee38b8ec2 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0e777c69ba810353b9f3f2033280bbe7d029d81fa55760f6eec817ef595aa19c AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 26424946926a80a3795f214828c03faee28565ce Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Tue, 27 Feb 2024 10:38:35 -0500 Subject: [PATCH 107/151] fuzz: throwing an exception in case of an absl Status error (#32546) Signed-off-by: Adi Suissa-Peleg --- .../filters/common/expr/evaluator_corpus/incorrect_address | 1 + test/fuzz/utility.h | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 test/extensions/filters/common/expr/evaluator_corpus/incorrect_address diff --git a/test/extensions/filters/common/expr/evaluator_corpus/incorrect_address b/test/extensions/filters/common/expr/evaluator_corpus/incorrect_address new file mode 100644 index 000000000000..480a72878d73 --- /dev/null +++ b/test/extensions/filters/common/expr/evaluator_corpus/incorrect_address @@ -0,0 +1 @@ +expression { } stream_info { address { socket_address { address: " " named_port: " " } } } \ No newline at end of file diff --git a/test/fuzz/utility.h b/test/fuzz/utility.h index 681674debc4e..a450ac92e837 100644 --- a/test/fuzz/utility.h +++ b/test/fuzz/utility.h @@ -170,7 +170,9 @@ inline std::unique_ptr fromStreamInfo(const test::fuzz::StreamIn replaceInvalidHostCharacters( stream_info.address().envoy_internal_address().server_listener_name())); } else { - address = Envoy::Network::Address::resolveProtoAddress(stream_info.address()).value(); + auto address_or_error = Envoy::Network::Address::resolveProtoAddress(stream_info.address()); + THROW_IF_STATUS_NOT_OK(address_or_error, throw); + address = address_or_error.value(); } } else { address = Network::Utility::resolveUrl("tcp://10.0.0.1:443"); From 6712ff66ab3c93904e08aae7f139d4ecdf89d225 Mon Sep 17 00:00:00 2001 From: Leonardo Sarra <8293810+leosarra@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:45:56 +0100 Subject: [PATCH 108/151] conn_manager: add overload header if request is dropped due to OM (#31907) Signed-off-by: Leonardo Sarra --- .../v3/http_connection_manager.proto | 6 +- changelogs/current.yaml | 5 ++ .../http/http_conn_man/headers.rst | 10 +++ .../operations/overload_manager.rst | 5 ++ source/common/http/conn_manager_config.h | 6 ++ source/common/http/conn_manager_impl.cc | 11 +++- source/common/http/headers.h | 1 + .../network/http_connection_manager/config.cc | 1 + .../network/http_connection_manager/config.h | 2 + source/server/admin/admin.h | 1 + .../http/conn_manager_impl_fuzz_test.cc | 1 + .../common/http/conn_manager_impl_test_base.h | 1 + test/integration/overload_integration_test.cc | 64 ++++++++++++++++++- test/mocks/http/mocks.h | 1 + 14 files changed, 110 insertions(+), 5 deletions(-) diff --git a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto index 7a92259eb43b..9e7274daa533 100644 --- a/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto +++ b/api/envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto @@ -37,7 +37,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // HTTP connection manager :ref:`configuration overview `. // [#extension: envoy.filters.network.http_connection_manager] -// [#next-free-field: 57] +// [#next-free-field: 58] message HttpConnectionManager { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager"; @@ -887,6 +887,10 @@ message HttpConnectionManager { // will be ignored if the ``x-forwarded-port`` header has been set by any trusted proxy in front of Envoy. bool append_x_forwarded_port = 51; + // Append the :ref:`config_http_conn_man_headers_x-envoy-local-overloaded` HTTP header in the scenario where + // the Overload Manager has been triggered. + bool append_local_overload = 57; + // Whether the HCM will add ProxyProtocolFilterState to the Connection lifetime filter state. Defaults to ``true``. // This should be set to ``false`` in cases where Envoy's view of the downstream address may not correspond to the // actual client address, for example, if there's another proxy in front of the Envoy. diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 93cb5a10059d..2961db159eb2 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -222,6 +222,11 @@ new_features: added an option to dynamically set a per downstream connection idle timeout period object under the key ``envoy.tcp_proxy.per_connection_idle_timeout_ms``. If this filter state value exists, it will override the idle timeout specified in the filter configuration and the default idle timeout. +- area: overload + change: | + added a :ref:`configuration option + ` to add + ``x-envoy-local-overloaded`` header when Overload Manager is triggered. deprecated: - area: listener diff --git a/docs/root/configuration/http/http_conn_man/headers.rst b/docs/root/configuration/http/http_conn_man/headers.rst index a5e95670c187..4403a74cf0c4 100644 --- a/docs/root/configuration/http/http_conn_man/headers.rst +++ b/docs/root/configuration/http/http_conn_man/headers.rst @@ -435,6 +435,16 @@ The ``x-forwarded-proto`` header will be used by Envoy over ``:scheme`` where th encryption is wanted, for example clearing default ports based on ``x-forwarded-proto``. See :ref:`why_is_envoy_using_xfp_or_scheme` for more details. +.. _config_http_conn_man_headers_x-envoy-local-overloaded: + +x-envoy-local-overloaded +------------------------ + +Envoy will set this header on the downstream response +if a request was dropped due to :ref:`overload manager` and +:ref:`configuration option ` +is set to true. + .. _config_http_conn_man_headers_x-request-id: x-request-id diff --git a/docs/root/intro/arch_overview/operations/overload_manager.rst b/docs/root/intro/arch_overview/operations/overload_manager.rst index 1639bcd3ade7..d94aadebd50e 100644 --- a/docs/root/intro/arch_overview/operations/overload_manager.rst +++ b/docs/root/intro/arch_overview/operations/overload_manager.rst @@ -53,3 +53,8 @@ Actions When a trigger changes state, the value is sent to registered actions, which can then affect how connections and requests are processed. Each action interprets the input states differently, and some may ignore the *scaling* state altogether, taking effect only when *saturated*. + +Note that, in case :ref:`append_local_overload +` has been set to true, dropping an HTTP request will cause the :ref:`x-envoy-local-overloaded +` header to be set in the local reply +sent by the connection manager. diff --git a/source/common/http/conn_manager_config.h b/source/common/http/conn_manager_config.h index a07eb825f789..6730cfb1d322 100644 --- a/source/common/http/conn_manager_config.h +++ b/source/common/http/conn_manager_config.h @@ -533,6 +533,12 @@ class ConnectionManagerConfig { */ virtual bool appendXForwardedPort() const PURE; + /** + * @return whether to append the overload header to a local reply of a request which + * has been dropped due to Overload Manager. + */ + virtual bool appendLocalOverload() const PURE; + /** * @return whether the HCM will insert ProxyProtocolFilterState into the filter state at the * Connection Lifetime. diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index f8a3bed13a6b..e486973bec90 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -1257,8 +1257,15 @@ void ConnectionManagerImpl::ActiveStream::decodeHeaders(RequestHeaderMapSharedPt // overload it is more important to avoid unnecessary allocation than to create the filters. filter_manager_.skipFilterChainCreation(); connection_manager_.stats_.named_.downstream_rq_overload_close_.inc(); - sendLocalReply(Http::Code::ServiceUnavailable, "envoy overloaded", nullptr, absl::nullopt, - StreamInfo::ResponseCodeDetails::get().Overload); + sendLocalReply( + Http::Code::ServiceUnavailable, "envoy overloaded", + [this](Http::ResponseHeaderMap& headers) { + if (connection_manager_.config_.appendLocalOverload()) { + headers.addReference(Http::Headers::get().EnvoyLocalOverloaded, + Http::Headers::get().EnvoyOverloadedValues.True); + } + }, + absl::nullopt, StreamInfo::ResponseCodeDetails::get().Overload); return; } diff --git a/source/common/http/headers.h b/source/common/http/headers.h index 24ee070ae6fb..450894af1f71 100644 --- a/source/common/http/headers.h +++ b/source/common/http/headers.h @@ -170,6 +170,7 @@ class HeaderValues { // filter. We need to figure out if we can remove this header from the set of headers that // participate in prefix overrides. const LowerCaseString EnvoyIpTags{absl::StrCat(prefix(), "-ip-tags")}; + const LowerCaseString EnvoyLocalOverloaded{absl::StrCat(prefix(), "-local-overloaded")}; const LowerCaseString EnvoyMaxRetries{absl::StrCat(prefix(), "-max-retries")}; const LowerCaseString EnvoyNotForwarded{absl::StrCat(prefix(), "-not-forwarded")}; const LowerCaseString EnvoyOriginalDstHost{absl::StrCat(prefix(), "-original-dst-host")}; diff --git a/source/extensions/filters/network/http_connection_manager/config.cc b/source/extensions/filters/network/http_connection_manager/config.cc index 9d94281cbc33..a016f5058240 100644 --- a/source/extensions/filters/network/http_connection_manager/config.cc +++ b/source/extensions/filters/network/http_connection_manager/config.cc @@ -398,6 +398,7 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig( config.proxy_status_config()) : nullptr), header_validator_factory_(createHeaderValidatorFactory(config, context)), + append_local_overload_(config.append_local_overload()), append_x_forwarded_port_(config.append_x_forwarded_port()), add_proxy_protocol_connection_state_( PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, add_proxy_protocol_connection_state, true)) { diff --git a/source/extensions/filters/network/http_connection_manager/config.h b/source/extensions/filters/network/http_connection_manager/config.h index b0a29a0f009b..b03d65e22c1c 100644 --- a/source/extensions/filters/network/http_connection_manager/config.h +++ b/source/extensions/filters/network/http_connection_manager/config.h @@ -263,6 +263,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, return nullptr; #endif } + bool appendLocalOverload() const override { return append_local_overload_; } bool appendXForwardedPort() const override { return append_x_forwarded_port_; } bool addProxyProtocolConnectionState() const override { return add_proxy_protocol_connection_state_; @@ -359,6 +360,7 @@ class HttpConnectionManagerConfig : Logger::Loggable, const uint64_t max_requests_per_connection_; const std::unique_ptr proxy_status_config_; const Http::HeaderValidatorFactoryPtr header_validator_factory_; + const bool append_local_overload_; const bool append_x_forwarded_port_; const bool add_proxy_protocol_connection_state_; }; diff --git a/source/server/admin/admin.h b/source/server/admin/admin.h index c77be2b2b92b..86c6ab7f57d3 100644 --- a/source/server/admin/admin.h +++ b/source/server/admin/admin.h @@ -235,6 +235,7 @@ class AdminImpl : public Admin, return nullptr; #endif } + bool appendLocalOverload() const override { return false; } bool appendXForwardedPort() const override { return false; } bool addProxyProtocolConnectionState() const override { return true; } diff --git a/test/common/http/conn_manager_impl_fuzz_test.cc b/test/common/http/conn_manager_impl_fuzz_test.cc index 81693fbb0cc3..cf11c952f996 100644 --- a/test/common/http/conn_manager_impl_fuzz_test.cc +++ b/test/common/http/conn_manager_impl_fuzz_test.cc @@ -239,6 +239,7 @@ class FuzzConfig : public ConnectionManagerConfig { // be changed too return nullptr; } + bool appendLocalOverload() const override { return false; } bool appendXForwardedPort() const override { return false; } bool addProxyProtocolConnectionState() const override { return true; } diff --git a/test/common/http/conn_manager_impl_test_base.h b/test/common/http/conn_manager_impl_test_base.h index 0ebaebd46bd1..35f474d385c6 100644 --- a/test/common/http/conn_manager_impl_test_base.h +++ b/test/common/http/conn_manager_impl_test_base.h @@ -172,6 +172,7 @@ class HttpConnectionManagerImplMixin : public ConnectionManagerConfig { ServerHeaderValidatorPtr makeHeaderValidator(Protocol protocol) override { return header_validator_factory_.createServerHeaderValidator(protocol, header_validator_stats_); } + bool appendLocalOverload() const override { return false; } bool appendXForwardedPort() const override { return false; } bool addProxyProtocolConnectionState() const override { return add_proxy_protocol_connection_state_; diff --git a/test/integration/overload_integration_test.cc b/test/integration/overload_integration_test.cc index d9060673cd3f..efaf470afce0 100644 --- a/test/integration/overload_integration_test.cc +++ b/test/integration/overload_integration_test.cc @@ -19,12 +19,19 @@ using testing::HasSubstr; class OverloadIntegrationTest : public BaseOverloadIntegrationTest, public HttpProtocolIntegrationTest { protected: - void - initializeOverloadManager(const envoy::config::overload::v3::OverloadAction& overload_action) { + void initializeOverloadManager(const envoy::config::overload::v3::OverloadAction& overload_action, + absl::optional appendLocalOverloadHeader = absl::nullopt) { setupOverloadManagerConfig(overload_action); config_helper_.addConfigModifier([this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { *bootstrap.mutable_overload_manager() = this->overload_manager_config_; }); + + if (appendLocalOverloadHeader.has_value() && appendLocalOverloadHeader.value()) { + config_helper_.addConfigModifier( + [=](envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager& cm) -> void { cm.set_append_local_overload(true); }); + } + initialize(); updateResource(0); } @@ -62,6 +69,8 @@ TEST_P(OverloadIntegrationTest, CloseStreamsWhenOverloaded) { EXPECT_TRUE(response->complete()); EXPECT_EQ("503", response->headers().getStatusValue()); + // Verify that no local overload header is added by default + EXPECT_EQ(true, response->headers().get(Http::Headers::get().EnvoyLocalOverloaded).empty()); EXPECT_EQ("envoy overloaded", response->body()); codec_client_->close(); @@ -71,6 +80,7 @@ TEST_P(OverloadIntegrationTest, CloseStreamsWhenOverloaded) { EXPECT_TRUE(response->complete()); EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_EQ(true, response->headers().get(Http::Headers::get().EnvoyLocalOverloaded).empty()); EXPECT_EQ("envoy overloaded", response->body()); codec_client_->close(); @@ -88,6 +98,56 @@ TEST_P(OverloadIntegrationTest, CloseStreamsWhenOverloaded) { EXPECT_EQ(0U, response->body().size()); } +TEST_P(OverloadIntegrationTest, AppendLocalOverloadHeader) { + initializeOverloadManager( + TestUtility::parseYaml(R"EOF( + name: "envoy.overload_actions.stop_accepting_requests" + triggers: + - name: "envoy.resource_monitors.testonly.fake_resource_monitor" + threshold: + value: 0.9 + )EOF"), + true); + + // Put envoy in overloaded state and check that it drops new requests and the local overload is + // correctly added. Test both header-only and header+body requests since the code paths are + // slightly different. + updateResource(0.9); + test_server_->waitForGaugeEq("overload.envoy.overload_actions.stop_accepting_requests.active", 1); + + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "sni.lyft.com"}}; + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + auto response = codec_client_->makeRequestWithBody(request_headers, 10); + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_EQ(Http::Headers::get().EnvoyOverloadedValues.True, + response->headers() + .get(Http::Headers::get().EnvoyLocalOverloaded)[0] + ->value() + .getStringView()); + EXPECT_EQ("envoy overloaded", response->body()); + codec_client_->close(); + + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + response = codec_client_->makeHeaderOnlyRequest(request_headers); + ASSERT_TRUE(response->waitForEndStream()); + + EXPECT_TRUE(response->complete()); + EXPECT_EQ("503", response->headers().getStatusValue()); + EXPECT_EQ(Http::Headers::get().EnvoyOverloadedValues.True, + response->headers() + .get(Http::Headers::get().EnvoyLocalOverloaded)[0] + ->value() + .getStringView()); + EXPECT_EQ("envoy overloaded", response->body()); + codec_client_->close(); +} + TEST_P(OverloadIntegrationTest, DisableKeepaliveWhenOverloaded) { if (downstreamProtocol() != Http::CodecType::HTTP1) { return; // only relevant for downstream HTTP1.x connections diff --git a/test/mocks/http/mocks.h b/test/mocks/http/mocks.h index 8b84f9f0e7e8..3ec3f7c5eb71 100644 --- a/test/mocks/http/mocks.h +++ b/test/mocks/http/mocks.h @@ -677,6 +677,7 @@ class MockConnectionManagerConfig : public ConnectionManagerConfig { MOCK_METHOD(uint64_t, maxRequestsPerConnection, (), (const)); MOCK_METHOD(const HttpConnectionManagerProto::ProxyStatusConfig*, proxyStatusConfig, (), (const)); MOCK_METHOD(ServerHeaderValidatorPtr, makeHeaderValidator, (Protocol protocol)); + MOCK_METHOD(bool, appendLocalOverload, (), (const)); MOCK_METHOD(bool, appendXForwardedPort, (), (const)); MOCK_METHOD(bool, addProxyProtocolConnectionState, (), (const)); From 10d45c9442a53593526b90ced2db8ff28ee38a2f Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Tue, 27 Feb 2024 11:11:35 -0500 Subject: [PATCH 109/151] wasm: moving a utility to wasm/common (#32252) This seems like a general utility but in 4 years it's still only used by wasm (at least downstream?) Risk Level: low: code move Testing: n/a Docs Changes: n/a Release Notes: n/a Signed-off-by: Alyssa Wilk --- source/common/config/datasource.cc | 26 - source/common/config/datasource.h | 60 -- source/extensions/access_loggers/wasm/BUILD | 1 + .../extensions/access_loggers/wasm/config.h | 4 +- source/extensions/bootstrap/wasm/BUILD | 1 + source/extensions/bootstrap/wasm/config.h | 3 +- source/extensions/common/wasm/BUILD | 19 + .../common/wasm/remote_async_datasource.cc | 43 ++ .../common/wasm/remote_async_datasource.h | 81 +++ source/extensions/common/wasm/wasm.cc | 7 +- source/extensions/common/wasm/wasm.h | 4 +- source/extensions/filters/http/wasm/BUILD | 1 + .../filters/http/wasm/wasm_filter.h | 3 +- source/extensions/filters/network/wasm/BUILD | 1 + .../filters/network/wasm/wasm_filter.h | 3 +- source/extensions/stat_sinks/wasm/BUILD | 1 + source/extensions/stat_sinks/wasm/config.h | 3 +- test/common/config/datasource_test.cc | 434 -------------- test/extensions/common/wasm/BUILD | 18 + .../wasm/remote_async_datasource_test.cc | 529 ++++++++++++++++++ test/extensions/common/wasm/wasm_test.cc | 6 +- test/test_common/wasm_base.h | 2 +- tools/code_format/config.yaml | 1 - 23 files changed, 715 insertions(+), 536 deletions(-) create mode 100644 source/extensions/common/wasm/remote_async_datasource.cc create mode 100644 source/extensions/common/wasm/remote_async_datasource.h create mode 100644 test/extensions/common/wasm/remote_async_datasource_test.cc diff --git a/source/common/config/datasource.cc b/source/common/config/datasource.cc index 4c3765b3b7f8..76e50f1911f2 100644 --- a/source/common/config/datasource.cc +++ b/source/common/config/datasource.cc @@ -10,11 +10,6 @@ namespace Envoy { namespace Config { namespace DataSource { -// Default Parameters of the jittered backoff strategy. -static constexpr uint32_t RetryInitialDelayMilliseconds = 1000; -static constexpr uint32_t RetryMaxDelayMilliseconds = 10 * 1000; -static constexpr uint32_t RetryCount = 1; - absl::StatusOr read(const envoy::config::core::v3::DataSource& source, bool allow_empty, Api::Api& api, uint64_t max_size) { std::string data; @@ -72,27 +67,6 @@ absl::optional getPath(const envoy::config::core::v3::DataSource& s : absl::nullopt; } -RemoteAsyncDataProvider::RemoteAsyncDataProvider( - Upstream::ClusterManager& cm, Init::Manager& manager, - const envoy::config::core::v3::RemoteDataSource& source, Event::Dispatcher& dispatcher, - Random::RandomGenerator& random, bool allow_empty, AsyncDataSourceCb&& callback) - : allow_empty_(allow_empty), callback_(std::move(callback)), - fetcher_(std::make_unique(cm, source.http_uri(), - source.sha256(), *this)), - init_target_("RemoteAsyncDataProvider", [this]() { start(); }), - retries_remaining_( - PROTOBUF_GET_WRAPPED_OR_DEFAULT(source.retry_policy(), num_retries, RetryCount)) { - - auto strategy_or_error = Utility::prepareJitteredExponentialBackOffStrategy( - source, random, RetryInitialDelayMilliseconds, RetryMaxDelayMilliseconds); - THROW_IF_STATUS_NOT_OK(strategy_or_error, throw); - backoff_strategy_ = std::move(strategy_or_error.value()); - - retry_timer_ = dispatcher.createTimer([this]() -> void { start(); }); - - manager.add(init_target_); -} - } // namespace DataSource } // namespace Config } // namespace Envoy diff --git a/source/common/config/datasource.h b/source/common/config/datasource.h index 888794623eea..4171548e8c3f 100644 --- a/source/common/config/datasource.h +++ b/source/common/config/datasource.h @@ -38,66 +38,6 @@ absl::StatusOr read(const envoy::config::core::v3::DataSource& sour */ absl::optional getPath(const envoy::config::core::v3::DataSource& source); -/** - * Callback for async data source. - */ -using AsyncDataSourceCb = std::function; - -class RemoteAsyncDataProvider : public Event::DeferredDeletable, - public Config::DataFetcher::RemoteDataFetcherCallback, - public Logger::Loggable { -public: - RemoteAsyncDataProvider(Upstream::ClusterManager& cm, Init::Manager& manager, - const envoy::config::core::v3::RemoteDataSource& source, - Event::Dispatcher& dispatcher, Random::RandomGenerator& random, - bool allow_empty, AsyncDataSourceCb&& callback); - - ~RemoteAsyncDataProvider() override { - init_target_.ready(); - if (retry_timer_) { - retry_timer_->disableTimer(); - } - } - - // Config::DataFetcher::RemoteDataFetcherCallback - void onSuccess(const std::string& data) override { - callback_(data); - init_target_.ready(); - } - - // Config::DataFetcher::RemoteDataFetcherCallback - void onFailure(Config::DataFetcher::FailureReason failure) override { - ENVOY_LOG(debug, "Failed to fetch remote data, failure reason: {}", enumToInt(failure)); - if (retries_remaining_-- == 0) { - ENVOY_LOG(warn, "Retry limit exceeded for fetching data from remote data source."); - if (allow_empty_) { - callback_(EMPTY_STRING); - } - // We need to allow server startup to continue. - init_target_.ready(); - return; - } - - const auto retry_ms = std::chrono::milliseconds(backoff_strategy_->nextBackOffMs()); - ENVOY_LOG(debug, "Remote data provider will retry in {} ms.", retry_ms.count()); - retry_timer_->enableTimer(retry_ms); - } - -private: - void start() { fetcher_->fetch(); } - - bool allow_empty_; - AsyncDataSourceCb callback_; - const Config::DataFetcher::RemoteDataFetcherPtr fetcher_; - Init::TargetImpl init_target_; - - Event::TimerPtr retry_timer_; - BackOffStrategyPtr backoff_strategy_; - uint32_t retries_remaining_; -}; - -using RemoteAsyncDataProviderPtr = std::unique_ptr; - } // namespace DataSource } // namespace Config } // namespace Envoy diff --git a/source/extensions/access_loggers/wasm/BUILD b/source/extensions/access_loggers/wasm/BUILD index 077e8f0239ec..cfb103eac2ed 100644 --- a/source/extensions/access_loggers/wasm/BUILD +++ b/source/extensions/access_loggers/wasm/BUILD @@ -32,6 +32,7 @@ envoy_cc_extension( "//envoy/registry", "//source/common/config:datasource_lib", "//source/common/protobuf", + "//source/extensions/common/wasm:remote_async_datasource_lib", "//source/extensions/common/wasm:wasm_lib", "@envoy_api//envoy/extensions/access_loggers/wasm/v3:pkg_cc_proto", ], diff --git a/source/extensions/access_loggers/wasm/config.h b/source/extensions/access_loggers/wasm/config.h index e6b69e951f1d..ef8257a9b3be 100644 --- a/source/extensions/access_loggers/wasm/config.h +++ b/source/extensions/access_loggers/wasm/config.h @@ -2,7 +2,7 @@ #include "envoy/access_log/access_log_config.h" -#include "source/common/config/datasource.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" namespace Envoy { namespace Extensions { @@ -25,7 +25,7 @@ class WasmAccessLogFactory : public AccessLog::AccessLogInstanceFactory, private: absl::flat_hash_map convertJsonFormatToMap(ProtobufWkt::Struct config); - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; + RemoteAsyncDataProviderPtr remote_data_provider_; }; } // namespace Wasm diff --git a/source/extensions/bootstrap/wasm/BUILD b/source/extensions/bootstrap/wasm/BUILD index 913e72fc5431..32e459c14401 100644 --- a/source/extensions/bootstrap/wasm/BUILD +++ b/source/extensions/bootstrap/wasm/BUILD @@ -25,6 +25,7 @@ envoy_cc_extension( "//source/common/common:empty_string", "//source/common/config:datasource_lib", "//source/common/protobuf:utility_lib", + "//source/extensions/common/wasm:remote_async_datasource_lib", "//source/extensions/common/wasm:wasm_lib", "@envoy_api//envoy/extensions/wasm/v3:pkg_cc_proto", ], diff --git a/source/extensions/bootstrap/wasm/config.h b/source/extensions/bootstrap/wasm/config.h index 6839792d71d8..fc24e085cc7c 100644 --- a/source/extensions/bootstrap/wasm/config.h +++ b/source/extensions/bootstrap/wasm/config.h @@ -8,6 +8,7 @@ #include "envoy/server/instance.h" #include "source/common/protobuf/protobuf.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" #include "source/extensions/common/wasm/wasm.h" namespace Envoy { @@ -64,7 +65,7 @@ class WasmServiceExtension : public Server::BootstrapExtension, Logger::Loggable envoy::extensions::wasm::v3::WasmService config_; Server::Configuration::ServerFactoryContext& context_; WasmServicePtr wasm_service_; - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; + RemoteAsyncDataProviderPtr remote_data_provider_; }; } // namespace Wasm diff --git a/source/extensions/common/wasm/BUILD b/source/extensions/common/wasm/BUILD index 46001d873f7e..8022b0d74413 100644 --- a/source/extensions/common/wasm/BUILD +++ b/source/extensions/common/wasm/BUILD @@ -55,6 +55,24 @@ envoy_cc_library( alwayslink = 1, ) +envoy_cc_library( + name = "remote_async_datasource_lib", + srcs = ["remote_async_datasource.cc"], + hdrs = ["remote_async_datasource.h"], + deps = [ + "//envoy/api:api_interface", + "//envoy/init:manager_interface", + "//envoy/upstream:cluster_manager_interface", + "//source/common/common:backoff_lib", + "//source/common/common:empty_string", + "//source/common/config:remote_data_fetcher_lib", + "//source/common/config:utility_lib", + "//source/common/init:target_lib", + "//source/common/protobuf:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + envoy_cc_extension( name = "wasm_lib", srcs = [ @@ -91,6 +109,7 @@ envoy_cc_extension( "//source/common/http:utility_lib", "//source/common/network/dns_resolver:dns_factory_util_lib", "//source/common/tracing:http_tracer_lib", + "//source/extensions/common/wasm:remote_async_datasource_lib", "//source/extensions/common/wasm/ext:declare_property_cc_proto", "//source/extensions/common/wasm/ext:envoy_null_vm_wasm_api", "//source/extensions/common/wasm/ext:set_envoy_filter_state_cc_proto", diff --git a/source/extensions/common/wasm/remote_async_datasource.cc b/source/extensions/common/wasm/remote_async_datasource.cc new file mode 100644 index 000000000000..4ed28651c564 --- /dev/null +++ b/source/extensions/common/wasm/remote_async_datasource.cc @@ -0,0 +1,43 @@ +#include "source/extensions/common/wasm/remote_async_datasource.h" + +#include "envoy/config/core/v3/base.pb.h" + +#include "source/common/config/utility.h" + +#include "fmt/format.h" + +namespace Envoy { + +// Default Parameters of the jittered backoff strategy. +static constexpr uint32_t RetryInitialDelayMilliseconds = 1000; +static constexpr uint32_t RetryMaxDelayMilliseconds = 10 * 1000; +static constexpr uint32_t RetryCount = 1; + +absl::optional getPath(const envoy::config::core::v3::DataSource& source) { + return source.specifier_case() == envoy::config::core::v3::DataSource::SpecifierCase::kFilename + ? absl::make_optional(source.filename()) + : absl::nullopt; +} + +RemoteAsyncDataProvider::RemoteAsyncDataProvider( + Upstream::ClusterManager& cm, Init::Manager& manager, + const envoy::config::core::v3::RemoteDataSource& source, Event::Dispatcher& dispatcher, + Random::RandomGenerator& random, bool allow_empty, AsyncDataSourceCb&& callback) + : allow_empty_(allow_empty), callback_(std::move(callback)), + fetcher_(std::make_unique(cm, source.http_uri(), + source.sha256(), *this)), + init_target_("RemoteAsyncDataProvider", [this]() { start(); }), + retries_remaining_( + PROTOBUF_GET_WRAPPED_OR_DEFAULT(source.retry_policy(), num_retries, RetryCount)) { + + auto strategy_or_error = Config::Utility::prepareJitteredExponentialBackOffStrategy( + source, random, RetryInitialDelayMilliseconds, RetryMaxDelayMilliseconds); + THROW_IF_STATUS_NOT_OK(strategy_or_error, throw); + backoff_strategy_ = std::move(strategy_or_error.value()); + + retry_timer_ = dispatcher.createTimer([this]() -> void { start(); }); + + manager.add(init_target_); +} + +} // namespace Envoy diff --git a/source/extensions/common/wasm/remote_async_datasource.h b/source/extensions/common/wasm/remote_async_datasource.h new file mode 100644 index 000000000000..f3371269685a --- /dev/null +++ b/source/extensions/common/wasm/remote_async_datasource.h @@ -0,0 +1,81 @@ +#pragma once + +#include "envoy/api/api.h" +#include "envoy/common/random_generator.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/event/deferred_deletable.h" +#include "envoy/init/manager.h" +#include "envoy/upstream/cluster_manager.h" + +#include "source/common/common/backoff_strategy.h" +#include "source/common/common/empty_string.h" +#include "source/common/common/enum_to_int.h" +#include "source/common/config/remote_data_fetcher.h" +#include "source/common/init/target_impl.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" + +#include "absl/types/optional.h" + +namespace Envoy { + +/** + * Callback for async data source. + */ +using AsyncDataSourceCb = std::function; + +class RemoteAsyncDataProvider : public Event::DeferredDeletable, + public Config::DataFetcher::RemoteDataFetcherCallback, + public Logger::Loggable { +public: + RemoteAsyncDataProvider(Upstream::ClusterManager& cm, Init::Manager& manager, + const envoy::config::core::v3::RemoteDataSource& source, + Event::Dispatcher& dispatcher, Random::RandomGenerator& random, + bool allow_empty, AsyncDataSourceCb&& callback); + + ~RemoteAsyncDataProvider() override { + init_target_.ready(); + if (retry_timer_) { + retry_timer_->disableTimer(); + } + } + + // Config::DataFetcher::RemoteDataFetcherCallback + void onSuccess(const std::string& data) override { + callback_(data); + init_target_.ready(); + } + + // Config::DataFetcher::RemoteDataFetcherCallback + void onFailure(Config::DataFetcher::FailureReason failure) override { + ENVOY_LOG(debug, "Failed to fetch remote data, failure reason: {}", enumToInt(failure)); + if (retries_remaining_-- == 0) { + ENVOY_LOG(warn, "Retry limit exceeded for fetching data from remote data source."); + if (allow_empty_) { + callback_(EMPTY_STRING); + } + // We need to allow server startup to continue. + init_target_.ready(); + return; + } + + const auto retry_ms = std::chrono::milliseconds(backoff_strategy_->nextBackOffMs()); + ENVOY_LOG(debug, "Remote data provider will retry in {} ms.", retry_ms.count()); + retry_timer_->enableTimer(retry_ms); + } + +private: + void start() { fetcher_->fetch(); } + + bool allow_empty_; + AsyncDataSourceCb callback_; + const Config::DataFetcher::RemoteDataFetcherPtr fetcher_; + Init::TargetImpl init_target_; + + Event::TimerPtr retry_timer_; + BackOffStrategyPtr backoff_strategy_; + uint32_t retries_remaining_; +}; + +using RemoteAsyncDataProviderPtr = std::unique_ptr; + +} // namespace Envoy diff --git a/source/extensions/common/wasm/wasm.cc b/source/extensions/common/wasm/wasm.cc index d2de6db4d3c8..4e9659a30255 100644 --- a/source/extensions/common/wasm/wasm.cc +++ b/source/extensions/common/wasm/wasm.cc @@ -8,6 +8,7 @@ #include "source/common/common/logger.h" #include "source/common/network/dns_resolver/dns_factory_util.h" #include "source/extensions/common/wasm/plugin.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" #include "source/extensions/common/wasm/stats_handler.h" #include "absl/strings/str_cat.h" @@ -313,8 +314,8 @@ bool createWasm(const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scop Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, Event::Dispatcher& dispatcher, Api::Api& api, Server::ServerLifecycleNotifier& lifecycle_notifier, - Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, - CreateWasmCallback&& cb, CreateContextFn create_root_context_for_testing) { + RemoteAsyncDataProviderPtr& remote_data_provider, CreateWasmCallback&& cb, + CreateContextFn create_root_context_for_testing) { auto& stats_handler = getCreateStatsHandler(); std::string source, code; auto config = plugin->wasmConfig(); @@ -453,7 +454,7 @@ bool createWasm(const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scop cb(nullptr); return false; } else { - remote_data_provider = std::make_unique( + remote_data_provider = std::make_unique( cluster_manager, init_manager, vm_config.code().remote(), dispatcher, api.randomGenerator(), true, fetch_callback); } diff --git a/source/extensions/common/wasm/wasm.h b/source/extensions/common/wasm/wasm.h index 17cf43e028b0..9e2a10c1b7b4 100644 --- a/source/extensions/common/wasm/wasm.h +++ b/source/extensions/common/wasm/wasm.h @@ -21,6 +21,7 @@ #include "source/common/version/version.h" #include "source/extensions/common/wasm/context.h" #include "source/extensions/common/wasm/plugin.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" #include "source/extensions/common/wasm/stats_handler.h" #include "source/extensions/common/wasm/wasm_vm.h" @@ -170,8 +171,7 @@ bool createWasm(const PluginSharedPtr& plugin, const Stats::ScopeSharedPtr& scop Upstream::ClusterManager& cluster_manager, Init::Manager& init_manager, Event::Dispatcher& dispatcher, Api::Api& api, Envoy::Server::ServerLifecycleNotifier& lifecycle_notifier, - Config::DataSource::RemoteAsyncDataProviderPtr& remote_data_provider, - CreateWasmCallback&& callback, + RemoteAsyncDataProviderPtr& remote_data_provider, CreateWasmCallback&& callback, CreateContextFn create_root_context_for_testing = nullptr); PluginHandleSharedPtr diff --git a/source/extensions/filters/http/wasm/BUILD b/source/extensions/filters/http/wasm/BUILD index b9eb2641e9ab..16b2a37264e1 100644 --- a/source/extensions/filters/http/wasm/BUILD +++ b/source/extensions/filters/http/wasm/BUILD @@ -19,6 +19,7 @@ envoy_cc_library( "//envoy/http:codes_interface", "//envoy/server:filter_config_interface", "//envoy/upstream:cluster_manager_interface", + "//source/extensions/common/wasm:remote_async_datasource_lib", "//source/extensions/common/wasm:wasm_lib", "@envoy_api//envoy/extensions/filters/http/wasm/v3:pkg_cc_proto", ], diff --git a/source/extensions/filters/http/wasm/wasm_filter.h b/source/extensions/filters/http/wasm/wasm_filter.h index 6dd63140e9e8..7221ec94fcb4 100644 --- a/source/extensions/filters/http/wasm/wasm_filter.h +++ b/source/extensions/filters/http/wasm/wasm_filter.h @@ -8,6 +8,7 @@ #include "envoy/upstream/cluster_manager.h" #include "source/extensions/common/wasm/plugin.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" #include "source/extensions/common/wasm/wasm.h" namespace Envoy { @@ -51,7 +52,7 @@ class FilterConfig : Logger::Loggable { private: ThreadLocal::TypedSlotPtr tls_slot_; - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; + RemoteAsyncDataProviderPtr remote_data_provider_; }; using FilterConfigSharedPtr = std::shared_ptr; diff --git a/source/extensions/filters/network/wasm/BUILD b/source/extensions/filters/network/wasm/BUILD index c3cee216c2e9..b4f3b1172342 100644 --- a/source/extensions/filters/network/wasm/BUILD +++ b/source/extensions/filters/network/wasm/BUILD @@ -18,6 +18,7 @@ envoy_cc_library( deps = [ "//envoy/server:filter_config_interface", "//envoy/upstream:cluster_manager_interface", + "//source/extensions/common/wasm:remote_async_datasource_lib", "//source/extensions/common/wasm:wasm_lib", "//source/extensions/filters/network:well_known_names", "@envoy_api//envoy/extensions/filters/network/wasm/v3:pkg_cc_proto", diff --git a/source/extensions/filters/network/wasm/wasm_filter.h b/source/extensions/filters/network/wasm/wasm_filter.h index dffd08b0c620..521c227fcf0f 100644 --- a/source/extensions/filters/network/wasm/wasm_filter.h +++ b/source/extensions/filters/network/wasm/wasm_filter.h @@ -7,6 +7,7 @@ #include "envoy/server/filter_config.h" #include "envoy/upstream/cluster_manager.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" #include "source/extensions/common/wasm/wasm.h" #include "source/extensions/filters/network/well_known_names.h" @@ -53,7 +54,7 @@ class FilterConfig : Logger::Loggable { private: ThreadLocal::TypedSlotPtr tls_slot_; - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; + RemoteAsyncDataProviderPtr remote_data_provider_; }; using FilterConfigSharedPtr = std::shared_ptr; diff --git a/source/extensions/stat_sinks/wasm/BUILD b/source/extensions/stat_sinks/wasm/BUILD index 71c6a09f50ef..781953be2a19 100644 --- a/source/extensions/stat_sinks/wasm/BUILD +++ b/source/extensions/stat_sinks/wasm/BUILD @@ -20,6 +20,7 @@ envoy_cc_extension( "//envoy/registry", "//envoy/server:factory_context_interface", "//envoy/server:instance_interface", + "//source/extensions/common/wasm:remote_async_datasource_lib", "//source/extensions/common/wasm:wasm_lib", "//source/server:configuration_lib", "@envoy_api//envoy/extensions/stat_sinks/wasm/v3:pkg_cc_proto", diff --git a/source/extensions/stat_sinks/wasm/config.h b/source/extensions/stat_sinks/wasm/config.h index 5df22470829f..f27c520f9073 100644 --- a/source/extensions/stat_sinks/wasm/config.h +++ b/source/extensions/stat_sinks/wasm/config.h @@ -6,6 +6,7 @@ #include "envoy/server/instance.h" #include "source/common/config/datasource.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" #include "source/server/configuration_impl.h" namespace Envoy { @@ -31,7 +32,7 @@ class WasmSinkFactory : Logger::Loggable, std::string name() const override; private: - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; + RemoteAsyncDataProviderPtr remote_data_provider_; }; } // namespace Wasm diff --git a/test/common/config/datasource_test.cc b/test/common/config/datasource_test.cc index b58e5e0228d3..13f96416867b 100644 --- a/test/common/config/datasource_test.cc +++ b/test/common/config/datasource_test.cc @@ -37,8 +37,6 @@ class AsyncDataSourceTest : public testing::Test { Event::TimerCb retry_timer_cb_; NiceMock request_{&cm_.thread_local_cluster_.async_client_}; - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; - using AsyncClientSendFunc = std::function; @@ -75,438 +73,6 @@ class AsyncDataSourceTest : public testing::Test { } }; -TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceNoCluster) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - xxxxxx - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - initialize(nullptr); - - std::string async_data = "non-empty"; - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, EMPTY_STRING); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - EXPECT_CALL(*retry_timer_, enableTimer(_, _)) - .WillOnce(Invoke( - [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); - init_target_handle_->initialize(init_watcher_); - - EXPECT_EQ(async_data, EMPTY_STRING); -} - -TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceReturnFailure) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - xxxxxx - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - cm_.initializeThreadLocalClusters({"cluster_1"}); - initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - callbacks.onFailure(request_, Envoy::Http::AsyncClient::FailureReason::Reset); - return nullptr; - }); - - std::string async_data = "non-empty"; - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, EMPTY_STRING); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - EXPECT_CALL(*retry_timer_, enableTimer(_, _)) - .WillOnce(Invoke( - [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); - init_target_handle_->initialize(init_watcher_); - - EXPECT_EQ(async_data, EMPTY_STRING); -} - -TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceSuccessWith503) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - xxxxxx - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - cm_.initializeThreadLocalClusters({"cluster_1"}); - initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - callbacks.onSuccess( - request_, Http::ResponseMessagePtr{new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ - new Http::TestResponseHeaderMapImpl{{":status", "503"}}})}); - return nullptr; - }); - - std::string async_data = "non-empty"; - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, EMPTY_STRING); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - EXPECT_CALL(*retry_timer_, enableTimer(_, _)) - .WillOnce(Invoke( - [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); - init_target_handle_->initialize(init_watcher_); - - EXPECT_EQ(async_data, EMPTY_STRING); -} - -TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceSuccessWithEmptyBody) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - xxxxxx - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - cm_.initializeThreadLocalClusters({"cluster_1"}); - initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - callbacks.onSuccess( - request_, Http::ResponseMessagePtr{new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ - new Http::TestResponseHeaderMapImpl{{":status", "200"}}})}); - return nullptr; - }); - - std::string async_data = "non-empty"; - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, EMPTY_STRING); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - EXPECT_CALL(*retry_timer_, enableTimer(_, _)) - .WillOnce(Invoke( - [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); - init_target_handle_->initialize(init_watcher_); - - EXPECT_EQ(async_data, EMPTY_STRING); -} - -TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceSuccessIncorrectSha256) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - xxxxxx - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - const std::string body = "hello world"; - - cm_.initializeThreadLocalClusters({"cluster_1"}); - initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - Http::ResponseMessagePtr response(new Http::ResponseMessageImpl( - Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); - response->body().add(body); - - callbacks.onSuccess(request_, std::move(response)); - return nullptr; - }); - - std::string async_data = "non-empty"; - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, EMPTY_STRING); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - EXPECT_CALL(*retry_timer_, enableTimer(_, _)) - .WillOnce(Invoke( - [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); - init_target_handle_->initialize(init_watcher_); - - EXPECT_EQ(async_data, EMPTY_STRING); -} - -TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceSuccess) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - cm_.initializeThreadLocalClusters({"cluster_1"}); - const std::string body = "hello world"; - initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - Http::ResponseMessagePtr response(new Http::ResponseMessageImpl( - Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); - response->body().add(body); - - callbacks.onSuccess(request_, std::move(response)); - return nullptr; - }); - - std::string async_data = "non-empty"; - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, body); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - init_target_handle_->initialize(init_watcher_); - - EXPECT_EQ(async_data, body); -} - -TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceDoNotAllowEmpty) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - xxxxxx - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - cm_.initializeThreadLocalClusters({"cluster_1"}); - initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - callbacks.onSuccess( - request_, Http::ResponseMessagePtr{new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ - new Http::TestResponseHeaderMapImpl{{":status", "503"}}})}); - return nullptr; - }); - - std::string async_data = "non-empty"; - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, false, - [&](const std::string& data) { async_data = data; }); - - EXPECT_CALL(init_watcher_, ready()); - EXPECT_CALL(*retry_timer_, enableTimer(_, _)) - .WillOnce(Invoke( - [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); - init_target_handle_->initialize(init_watcher_); - - EXPECT_EQ(async_data, "non-empty"); -} - -TEST_F(AsyncDataSourceTest, DatasourceReleasedBeforeFetchingData) { - const std::string body = "hello world"; - std::string async_data = "non-empty"; - - { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - cm_.initializeThreadLocalClusters({"cluster_1"}); - initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - Http::ResponseMessagePtr response(new Http::ResponseMessageImpl( - Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); - response->body().add(body); - - callbacks.onSuccess(request_, std::move(response)); - return nullptr; - }); - - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, body); - async_data = data; - }); - } - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - init_target_handle_->initialize(init_watcher_); - EXPECT_EQ(async_data, body); -} - -TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceWithRetry) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 - retry_policy: - retry_back_off: - base_interval: 1s - num_retries: 3 - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - cm_.initializeThreadLocalClusters({"cluster_1"}); - const std::string body = "hello world"; - int num_retries = 3; - - initialize( - [&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - callbacks.onSuccess( - request_, - Http::ResponseMessagePtr{new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ - new Http::TestResponseHeaderMapImpl{{":status", "503"}}})}); - return nullptr; - }, - num_retries); - - std::string async_data = "non-empty"; - remote_data_provider_ = std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string& data) { - EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); - EXPECT_EQ(data, body); - async_data = data; - }); - - EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); - EXPECT_CALL(init_watcher_, ready()); - EXPECT_CALL(*retry_timer_, enableTimer(_, _)) - .WillRepeatedly(Invoke([&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { - if (--num_retries == 0) { - EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) - .WillOnce(Invoke( - [&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, - const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { - Http::ResponseMessagePtr response( - new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ - new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); - response->body().add(body); - - callbacks.onSuccess(request_, std::move(response)); - return nullptr; - })); - } - - retry_timer_cb_(); - })); - init_target_handle_->initialize(init_watcher_); - - EXPECT_EQ(async_data, body); -} - -TEST_F(AsyncDataSourceTest, BaseIntervalGreaterThanMaxInterval) { - AsyncDataSourcePb config; - - std::string yaml = R"EOF( - remote: - http_uri: - uri: https://example.com/data - cluster: cluster_1 - timeout: 1s - sha256: - b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 - retry_policy: - retry_back_off: - base_interval: 10s - max_interval: 1s - num_retries: 3 - )EOF"; - TestUtility::loadFromYamlAndValidate(yaml, config); - EXPECT_TRUE(config.has_remote()); - - EXPECT_THROW_WITH_MESSAGE(std::make_unique( - cm_, init_manager_, config.remote(), dispatcher_, random_, true, - [&](const std::string&) {}), - EnvoyException, - "max_interval must be greater than or equal to the base_interval"); -} - TEST_F(AsyncDataSourceTest, BaseIntervalTest) { AsyncDataSourcePb config; diff --git a/test/extensions/common/wasm/BUILD b/test/extensions/common/wasm/BUILD index 14791f4599cd..62daee5c4c16 100644 --- a/test/extensions/common/wasm/BUILD +++ b/test/extensions/common/wasm/BUILD @@ -152,3 +152,21 @@ envoy_cc_test( "@proxy_wasm_cpp_host//:base_lib", ], ) + +envoy_cc_test( + name = "remote_async_datasource_test", + srcs = ["remote_async_datasource_test.cc"], + deps = [ + "//source/common/common:empty_string", + "//source/common/crypto:utility_lib", + "//source/common/http:message_lib", + "//source/common/protobuf:utility_lib", + "//source/extensions/common/wasm:remote_async_datasource_lib", + "//test/mocks/event:event_mocks", + "//test/mocks/init:init_mocks", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/upstream:cluster_manager_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/common/wasm/remote_async_datasource_test.cc b/test/extensions/common/wasm/remote_async_datasource_test.cc new file mode 100644 index 000000000000..15be73e5873f --- /dev/null +++ b/test/extensions/common/wasm/remote_async_datasource_test.cc @@ -0,0 +1,529 @@ +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/core/v3/base.pb.validate.h" + +#include "source/common/common/cleanup.h" +#include "source/common/common/empty_string.h" +#include "source/common/config/datasource.h" +#include "source/common/http/message_impl.h" +#include "source/common/protobuf/protobuf.h" +#include "source/extensions/common/wasm/remote_async_datasource.h" + +#include "test/mocks/event/mocks.h" +#include "test/mocks/init/mocks.h" +#include "test/mocks/upstream/cluster_manager.h" +#include "test/test_common/environment.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace { +using ::testing::AtLeast; +using ::testing::NiceMock; +using ::testing::Return; + +class AsyncDataSourceTest : public testing::Test { +protected: + using AsyncDataSourcePb = envoy::config::core::v3::AsyncDataSource; + + NiceMock cm_; + Init::MockManager init_manager_; + Init::ExpectableWatcherImpl init_watcher_; + Init::TargetHandlePtr init_target_handle_; + Api::ApiPtr api_{Api::createApiForTest()}; + NiceMock random_; + Event::MockDispatcher dispatcher_; + Event::MockTimer* retry_timer_; + Event::TimerCb retry_timer_cb_; + NiceMock request_{&cm_.thread_local_cluster_.async_client_}; + + RemoteAsyncDataProviderPtr remote_data_provider_; + + using AsyncClientSendFunc = std::function; + + void initialize(AsyncClientSendFunc func, int num_retries = 1) { + retry_timer_ = new Event::MockTimer(); + EXPECT_CALL(init_manager_, add(_)).WillOnce(Invoke([this](const Init::Target& target) { + init_target_handle_ = target.createHandle("test"); + })); + + EXPECT_CALL(dispatcher_, createTimer_(_)).WillOnce(Invoke([this](Event::TimerCb timer_cb) { + retry_timer_cb_ = timer_cb; + return retry_timer_; + })); + + EXPECT_CALL(*retry_timer_, disableTimer()); + if (!func) { + return; + } + + EXPECT_CALL(cm_.thread_local_cluster_, httpAsyncClient()) + .Times(AtLeast(1)) + .WillRepeatedly(ReturnRef(cm_.thread_local_cluster_.async_client_)); + + if (num_retries == 1) { + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(func)); + } else { + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .Times(num_retries) + .WillRepeatedly(Invoke(func)); + } + } +}; + +TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceNoCluster) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + xxxxxx + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + initialize(nullptr); + + std::string async_data = "non-empty"; + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, true, + [&](const std::string& data) { + EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); + EXPECT_EQ(data, EMPTY_STRING); + async_data = data; + }); + + EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); + EXPECT_CALL(init_watcher_, ready()); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)) + .WillOnce(Invoke( + [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); + init_target_handle_->initialize(init_watcher_); + + EXPECT_EQ(async_data, EMPTY_STRING); +} + +TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceReturnFailure) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + xxxxxx + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + cm_.initializeThreadLocalClusters({"cluster_1"}); + initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callbacks.onFailure(request_, Envoy::Http::AsyncClient::FailureReason::Reset); + return nullptr; + }); + + std::string async_data = "non-empty"; + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, true, + [&](const std::string& data) { + EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); + EXPECT_EQ(data, EMPTY_STRING); + async_data = data; + }); + + EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); + EXPECT_CALL(init_watcher_, ready()); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)) + .WillOnce(Invoke( + [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); + init_target_handle_->initialize(init_watcher_); + + EXPECT_EQ(async_data, EMPTY_STRING); +} + +TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceSuccessWith503) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + xxxxxx + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + cm_.initializeThreadLocalClusters({"cluster_1"}); + initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callbacks.onSuccess( + request_, Http::ResponseMessagePtr{new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", "503"}}})}); + return nullptr; + }); + + std::string async_data = "non-empty"; + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, true, + [&](const std::string& data) { + EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); + EXPECT_EQ(data, EMPTY_STRING); + async_data = data; + }); + + EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); + EXPECT_CALL(init_watcher_, ready()); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)) + .WillOnce(Invoke( + [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); + init_target_handle_->initialize(init_watcher_); + + EXPECT_EQ(async_data, EMPTY_STRING); +} + +TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceSuccessWithEmptyBody) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + xxxxxx + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + cm_.initializeThreadLocalClusters({"cluster_1"}); + initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callbacks.onSuccess( + request_, Http::ResponseMessagePtr{new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", "200"}}})}); + return nullptr; + }); + + std::string async_data = "non-empty"; + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, true, + [&](const std::string& data) { + EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); + EXPECT_EQ(data, EMPTY_STRING); + async_data = data; + }); + + EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); + EXPECT_CALL(init_watcher_, ready()); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)) + .WillOnce(Invoke( + [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); + init_target_handle_->initialize(init_watcher_); + + EXPECT_EQ(async_data, EMPTY_STRING); +} + +TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceSuccessIncorrectSha256) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + xxxxxx + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + const std::string body = "hello world"; + + cm_.initializeThreadLocalClusters({"cluster_1"}); + initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + Http::ResponseMessagePtr response(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + response->body().add(body); + + callbacks.onSuccess(request_, std::move(response)); + return nullptr; + }); + + std::string async_data = "non-empty"; + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, true, + [&](const std::string& data) { + EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); + EXPECT_EQ(data, EMPTY_STRING); + async_data = data; + }); + + EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); + EXPECT_CALL(init_watcher_, ready()); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)) + .WillOnce(Invoke( + [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); + init_target_handle_->initialize(init_watcher_); + + EXPECT_EQ(async_data, EMPTY_STRING); +} + +TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceSuccess) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + cm_.initializeThreadLocalClusters({"cluster_1"}); + const std::string body = "hello world"; + initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + Http::ResponseMessagePtr response(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + response->body().add(body); + + callbacks.onSuccess(request_, std::move(response)); + return nullptr; + }); + + std::string async_data = "non-empty"; + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, true, + [&](const std::string& data) { + EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); + EXPECT_EQ(data, body); + async_data = data; + }); + + EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); + EXPECT_CALL(init_watcher_, ready()); + init_target_handle_->initialize(init_watcher_); + + EXPECT_EQ(async_data, body); +} + +TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceDoNotAllowEmpty) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + xxxxxx + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + cm_.initializeThreadLocalClusters({"cluster_1"}); + initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callbacks.onSuccess( + request_, Http::ResponseMessagePtr{new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", "503"}}})}); + return nullptr; + }); + + std::string async_data = "non-empty"; + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, false, + [&](const std::string& data) { async_data = data; }); + + EXPECT_CALL(init_watcher_, ready()); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)) + .WillOnce(Invoke( + [&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { retry_timer_cb_(); })); + init_target_handle_->initialize(init_watcher_); + + EXPECT_EQ(async_data, "non-empty"); +} + +TEST_F(AsyncDataSourceTest, DatasourceReleasedBeforeFetchingData) { + const std::string body = "hello world"; + std::string async_data = "non-empty"; + + { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + cm_.initializeThreadLocalClusters({"cluster_1"}); + initialize([&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + Http::ResponseMessagePtr response(new Http::ResponseMessageImpl( + Http::ResponseHeaderMapPtr{new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + response->body().add(body); + + callbacks.onSuccess(request_, std::move(response)); + return nullptr; + }); + + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, true, + [&](const std::string& data) { + EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); + EXPECT_EQ(data, body); + async_data = data; + }); + } + + EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); + EXPECT_CALL(init_watcher_, ready()); + init_target_handle_->initialize(init_watcher_); + EXPECT_EQ(async_data, body); +} + +TEST_F(AsyncDataSourceTest, LoadRemoteDataSourceWithRetry) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 + retry_policy: + retry_back_off: + base_interval: 1s + num_retries: 3 + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + cm_.initializeThreadLocalClusters({"cluster_1"}); + const std::string body = "hello world"; + int num_retries = 3; + + initialize( + [&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + callbacks.onSuccess( + request_, + Http::ResponseMessagePtr{new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", "503"}}})}); + return nullptr; + }, + num_retries); + + std::string async_data = "non-empty"; + remote_data_provider_ = std::make_unique( + cm_, init_manager_, config.remote(), dispatcher_, random_, true, + [&](const std::string& data) { + EXPECT_EQ(init_manager_.state(), Init::Manager::State::Initializing); + EXPECT_EQ(data, body); + async_data = data; + }); + + EXPECT_CALL(init_manager_, state()).WillOnce(Return(Init::Manager::State::Initializing)); + EXPECT_CALL(init_watcher_, ready()); + EXPECT_CALL(*retry_timer_, enableTimer(_, _)) + .WillRepeatedly(Invoke([&](const std::chrono::milliseconds&, const ScopeTrackedObject*) { + if (--num_retries == 0) { + EXPECT_CALL(cm_.thread_local_cluster_.async_client_, send_(_, _, _)) + .WillOnce(Invoke( + [&](Http::RequestMessagePtr&, Http::AsyncClient::Callbacks& callbacks, + const Http::AsyncClient::RequestOptions&) -> Http::AsyncClient::Request* { + Http::ResponseMessagePtr response( + new Http::ResponseMessageImpl(Http::ResponseHeaderMapPtr{ + new Http::TestResponseHeaderMapImpl{{":status", "200"}}})); + response->body().add(body); + + callbacks.onSuccess(request_, std::move(response)); + return nullptr; + })); + } + + retry_timer_cb_(); + })); + init_target_handle_->initialize(init_watcher_); + + EXPECT_EQ(async_data, body); +} + +TEST_F(AsyncDataSourceTest, BaseIntervalGreaterThanMaxInterval) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 + retry_policy: + retry_back_off: + base_interval: 10s + max_interval: 1s + num_retries: 3 + )EOF"; + TestUtility::loadFromYamlAndValidate(yaml, config); + EXPECT_TRUE(config.has_remote()); + + EXPECT_THROW_WITH_MESSAGE( + std::make_unique(cm_, init_manager_, config.remote(), dispatcher_, + random_, true, [&](const std::string&) {}), + EnvoyException, "max_interval must be greater than or equal to the base_interval"); +} + +TEST_F(AsyncDataSourceTest, BaseIntervalTest) { + AsyncDataSourcePb config; + + std::string yaml = R"EOF( + remote: + http_uri: + uri: https://example.com/data + cluster: cluster_1 + timeout: 1s + sha256: + xxx + retry_policy: + retry_back_off: + base_interval: 0.0001s + num_retries: 3 + )EOF"; + EXPECT_THROW(TestUtility::loadFromYamlAndValidate(yaml, config), EnvoyException); +} + +} // namespace +} // namespace Envoy diff --git a/test/extensions/common/wasm/wasm_test.cc b/test/extensions/common/wasm/wasm_test.cc index f5e763e4851a..312d2c559774 100644 --- a/test/extensions/common/wasm/wasm_test.cc +++ b/test/extensions/common/wasm/wasm_test.cc @@ -638,7 +638,7 @@ TEST_P(WasmCommonTest, VmCache) { NiceMock init_manager; NiceMock lifecycle_notifier; Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider; + RemoteAsyncDataProviderPtr remote_data_provider; auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto vm_configuration = "vm_cache"; @@ -737,7 +737,7 @@ TEST_P(WasmCommonTest, RemoteCode) { NiceMock lifecycle_notifier; Init::ExpectableWatcherImpl init_watcher; Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider; + RemoteAsyncDataProviderPtr remote_data_provider; auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto vm_configuration = "vm_cache"; @@ -851,7 +851,7 @@ TEST_P(WasmCommonTest, RemoteCodeMultipleRetry) { NiceMock lifecycle_notifier; Init::ExpectableWatcherImpl init_watcher; Event::DispatcherPtr dispatcher(api->allocateDispatcher("wasm_test")); - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider; + RemoteAsyncDataProviderPtr remote_data_provider; auto scope = Stats::ScopeSharedPtr(stats_store.createScope("wasm.")); NiceMock local_info; auto vm_configuration = "vm_cache"; diff --git a/test/test_common/wasm_base.h b/test/test_common/wasm_base.h index 5e3394bf5559..3e2b34393c06 100644 --- a/test/test_common/wasm_base.h +++ b/test/test_common/wasm_base.h @@ -114,7 +114,7 @@ template class WasmTestBase : public Base { NiceMock lifecycle_notifier_; envoy::config::core::v3::Metadata listener_metadata_; Context* root_context_ = nullptr; // Unowned. - Config::DataSource::RemoteAsyncDataProviderPtr remote_data_provider_; + RemoteAsyncDataProviderPtr remote_data_provider_; void setRootId(std::string root_id) { root_id_ = root_id; } void setVmConfiguration(std::string vm_configuration) { vm_configuration_ = vm_configuration; } diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index bab4737b0ece..0e96847c58dd 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -133,7 +133,6 @@ paths: - source/common/tcp_proxy/tcp_proxy.cc - source/common/config/subscription_factory_impl.cc - source/common/config/xds_resource.cc - - source/common/config/datasource.cc - source/common/runtime/runtime_impl.cc - source/common/filter/config_discovery_impl.cc - source/common/json/json_internal.cc From bc0190146f08458ac5a58af5b3545b24660fa8f8 Mon Sep 17 00:00:00 2001 From: "dependency-envoy[bot]" <148525496+dependency-envoy[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:25:50 +0000 Subject: [PATCH 110/151] deps: Bump `aspect_bazel_lib` -> 2.4.2 (#32540) Signed-off-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> Co-authored-by: dependency-envoy[bot] <148525496+dependency-envoy[bot]@users.noreply.github.com> --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index d4ec1d4447c5..9aa875702a92 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -148,12 +148,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Aspect Bazel helpers", project_desc = "Base Starlark libraries and basic Bazel rules which are useful for constructing rulesets and BUILD files", project_url = "https://github.com/aspect-build/bazel-lib", - version = "2.4.1", - sha256 = "38c5bf333ae70d1bb3a18da6053084ce5f475f0ed0a8f04eed415186d5a7b04b", + version = "2.4.2", + sha256 = "f75d03783588e054899eb0729a97fb5b8973c1a26f30373fafd485c90bf207d1", strip_prefix = "bazel-lib-{version}", urls = ["https://github.com/aspect-build/bazel-lib/archive/v{version}.tar.gz"], use_category = ["build"], - release_date = "2024-02-06", + release_date = "2024-02-21", cpe = "N/A", license = "Apache-2.0", license_url = "https://github.com/aspect-build/bazel-lib/blob/v{version}/LICENSE", From 372a262894f028431cb84f3a1d361fb8fb6a2518 Mon Sep 17 00:00:00 2001 From: Ivan Prisyazhnyy Date: Tue, 27 Feb 2024 20:14:48 +0100 Subject: [PATCH 111/151] ext_proc: allow override metadata without grpc_service (#31544) * ext_proc: allow override metadata without grpc_service allows `overrides.metadata` in `ExtProcPerRoute` that appends and overrides parent metadata: - match: ... route: ... typed_per_filter_config: envoy.filters.http.ext_proc: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExtProcPerRoute overrides: metadata: - key: "x-ext-proc-override" value: "route3-override" - key: "x-router3-metadata-append" value: "route3-metadata-append" http_filters: - name: envoy.filters.http.ext_proc typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor grpc_service: envoy_grpc: cluster_name: ext-proc-proxy timeout: 60s initial_metadata: - key: "x-ext-proc-override" value: "root" - key: "x-ext-proc-original" value: "root" that allows to have: x-ext-proc-override: route3-override x-router3-metadata-append: route3-metadata-append x-ext-proc-original: root --------- Signed-off-by: Ivan Prisyazhnyy --- .../filters/http/ext_proc/v3/ext_proc.proto | 9 +- changelogs/current.yaml | 9 + source/extensions/filters/http/ext_proc/BUILD | 1 + .../filters/http/ext_proc/ext_proc.cc | 218 +++++++++++------- .../filters/http/ext_proc/ext_proc.h | 28 +-- .../filters/http/ext_proc/config_test.cc | 17 ++ .../ext_proc/ext_proc_integration_test.cc | 38 +++ .../filters/http/ext_proc/filter_test.cc | 84 +++++++ .../extensions/filters/http/ext_proc/utils.cc | 8 + test/extensions/filters/http/ext_proc/utils.h | 2 + 10 files changed, 310 insertions(+), 104 deletions(-) diff --git a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto index ebcc53d774d4..5ef01fd8a290 100644 --- a/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto +++ b/api/envoy/extensions/filters/http/ext_proc/v3/ext_proc.proto @@ -3,6 +3,7 @@ syntax = "proto3"; package envoy.extensions.filters.http.ext_proc.v3; import "envoy/config/common/mutation_rules/v3/mutation_rules.proto"; +import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/grpc_service.proto"; import "envoy/extensions/filters/http/ext_proc/v3/processing_mode.proto"; import "envoy/type/matcher/v3/string.proto"; @@ -273,7 +274,7 @@ message ExtProcPerRoute { } // Overrides that may be set on a per-route basis -// [#next-free-field: 7] +// [#next-free-field: 8] message ExtProcOverrides { // Set a different processing mode for this route than the default. ProcessingMode processing_mode = 1; @@ -301,4 +302,10 @@ message ExtProcOverrides { // config used. It is the prerogative of the control plane to ensure this // most-specific config contains the correct final overrides. MetadataOptions metadata_options = 6; + + // Additional metadata to include into streams initiated to the ext_proc gRPC + // service. This can be used for scenarios in which additional ad hoc + // authorization headers (e.g. ``x-foo-bar: baz-key``) are to be injected or + // when a route needs to partially override inherited metadata. + repeated config.core.v3.HeaderValue grpc_initial_metadata = 7; } diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2961db159eb2..9ea7f1c85310 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -149,6 +149,15 @@ removed_config_or_runtime: removed ``envoy_reloadable_features_initialize_upstream_filters`` and legacy code paths. new_features: +- area: ext_proc + change: | + Added + :ref:`grpc_initial_metadata ` + config API to allow extending inherited metadata from + :ref:`ExternalProcessor.grpc_service ` + and + :ref:`ExtProcOverrides.grpc_service ` + with the new or updated values. - area: aws_request_signing change: | Update ``aws_request_signing`` filter to support use as an upstream HTTP filter. This allows successful calculation of diff --git a/source/extensions/filters/http/ext_proc/BUILD b/source/extensions/filters/http/ext_proc/BUILD index 976de7b294b8..49ceffc502fe 100644 --- a/source/extensions/filters/http/ext_proc/BUILD +++ b/source/extensions/filters/http/ext_proc/BUILD @@ -35,6 +35,7 @@ envoy_cc_library( "//source/extensions/filters/http/common:pass_through_filter_lib", "@com_google_absl//absl/status", "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/strings:string_view", "@envoy_api//envoy/config/common/mutation_rules/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/ext_proc/v3:pkg_cc_proto", diff --git a/source/extensions/filters/http/ext_proc/ext_proc.cc b/source/extensions/filters/http/ext_proc/ext_proc.cc index 3e68fcae7568..f2fe47e90070 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.cc +++ b/source/extensions/filters/http/ext_proc/ext_proc.cc @@ -11,11 +11,13 @@ #include "source/extensions/filters/http/ext_proc/mutation_utils.h" #include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" namespace Envoy { namespace Extensions { namespace HttpFilters { namespace ExternalProcessing { +namespace { using envoy::config::common::mutation_rules::v3::HeaderMutationRules; using envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute; @@ -35,9 +37,114 @@ using Http::RequestTrailerMap; using Http::ResponseHeaderMap; using Http::ResponseTrailerMap; -static const std::string ErrorPrefix = "ext_proc_error"; -static const int DefaultImmediateStatus = 200; -static const std::string FilterName = "envoy.filters.http.ext_proc"; +constexpr absl::string_view ErrorPrefix = "ext_proc_error"; +constexpr int DefaultImmediateStatus = 200; +constexpr absl::string_view FilterName = "envoy.filters.http.ext_proc"; + +absl::optional initProcessingMode(const ExtProcPerRoute& config) { + if (!config.disabled() && config.has_overrides() && config.overrides().has_processing_mode()) { + return config.overrides().processing_mode(); + } + return absl::nullopt; +} + +absl::optional +initGrpcService(const ExtProcPerRoute& config) { + if (config.has_overrides() && config.overrides().has_grpc_service()) { + return config.overrides().grpc_service(); + } + return absl::nullopt; +} + +std::vector initNamespaces(const Protobuf::RepeatedPtrField& ns) { + std::vector namespaces; + for (const auto& single_ns : ns) { + namespaces.emplace_back(single_ns); + } + return namespaces; +} + +absl::optional> +initUntypedForwardingNamespaces(const ExtProcPerRoute& config) { + if (!config.has_overrides() || !config.overrides().has_metadata_options() || + !config.overrides().metadata_options().has_forwarding_namespaces()) { + return absl::nullopt; + } + + return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().untyped())}; +} + +absl::optional> +initTypedForwardingNamespaces(const ExtProcPerRoute& config) { + if (!config.has_overrides() || !config.overrides().has_metadata_options() || + !config.overrides().metadata_options().has_forwarding_namespaces()) { + return absl::nullopt; + } + + return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().typed())}; +} + +absl::optional> +initUntypedReceivingNamespaces(const ExtProcPerRoute& config) { + if (!config.has_overrides() || !config.overrides().has_metadata_options() || + !config.overrides().metadata_options().has_receiving_namespaces()) { + return absl::nullopt; + } + + return {initNamespaces(config.overrides().metadata_options().receiving_namespaces().untyped())}; +} + +absl::optional mergeProcessingMode(const FilterConfigPerRoute& less_specific, + const FilterConfigPerRoute& more_specific) { + if (more_specific.disabled()) { + return absl::nullopt; + } + return more_specific.processingMode().has_value() ? more_specific.processingMode() + : less_specific.processingMode(); +} + +// Replaces all entries with the same name or append one. +void mergeHeaderValues(std::vector& metadata, + const envoy::config::core::v3::HeaderValue& header) { + bool has_key = false; + for (auto& dest : metadata) { + if (dest.key() == header.key()) { + dest.CopyFrom(header); + has_key = true; + } + } + if (!has_key) { + metadata.emplace_back(header); + } +} + +std::vector +mergeGrpcInitialMetadata(const FilterConfigPerRoute& less_specific, + const FilterConfigPerRoute& more_specific) { + std::vector metadata(less_specific.grpcInitialMetadata()); + + for (const auto& header : more_specific.grpcInitialMetadata()) { + mergeHeaderValues(metadata, header); + } + + return metadata; +} + +// Replaces all entries with the same name or append one. +void mergeHeaderValuesField( + Protobuf::RepeatedPtrField<::envoy::config::core::v3::HeaderValue>& metadata, + const envoy::config::core::v3::HeaderValue& header) { + bool has_key = false; + for (auto& dest : metadata) { + if (dest.key() == header.key()) { + dest.CopyFrom(header); + has_key = true; + } + } + if (!has_key) { + metadata.Add()->CopyFrom(header); + } +} // Changes to headers are normally tested against the MutationRules supplied // with configuration. When writing an immediate response message, however, @@ -60,6 +167,19 @@ class ImmediateMutationChecker { std::unique_ptr rule_checker_; }; +const ImmediateMutationChecker& immediateResponseChecker() { + CONSTRUCT_ON_FIRST_USE(ImmediateMutationChecker); +} + +ProcessingMode allDisabledMode() { + ProcessingMode pm; + pm.set_request_header_mode(ProcessingMode::SKIP); + pm.set_response_header_mode(ProcessingMode::SKIP); + return pm; +} + +} // namespace + void ExtProcLoggingInfo::recordGrpcCall( std::chrono::microseconds latency, Grpc::Status::GrpcStatus call_status, ProcessorState::CallbackState callback_state, @@ -117,77 +237,11 @@ ExtProcLoggingInfo::grpcCalls(envoy::config::core::v3::TrafficDirection traffic_ : encoding_processor_grpc_calls_; } -std::vector -FilterConfigPerRoute::initNamespaces(const Protobuf::RepeatedPtrField& ns) { - if (ns.empty()) { - return {}; - } - - std::vector namespaces; - for (const auto& single_ns : ns) { - namespaces.emplace_back(single_ns); - } - return namespaces; -} - -absl::optional> -FilterConfigPerRoute::initUntypedForwardingNamespaces(const ExtProcPerRoute& config) { - if (!config.has_overrides() || !config.overrides().has_metadata_options() || - !config.overrides().metadata_options().has_forwarding_namespaces()) { - return absl::nullopt; - } - - return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().untyped())}; -} - -absl::optional> -FilterConfigPerRoute::initTypedForwardingNamespaces(const ExtProcPerRoute& config) { - if (!config.has_overrides() || !config.overrides().has_metadata_options() || - !config.overrides().metadata_options().has_forwarding_namespaces()) { - return absl::nullopt; - } - - return {initNamespaces(config.overrides().metadata_options().forwarding_namespaces().typed())}; -} - -absl::optional> -FilterConfigPerRoute::initUntypedReceivingNamespaces(const ExtProcPerRoute& config) { - if (!config.has_overrides() || !config.overrides().has_metadata_options() || - !config.overrides().metadata_options().has_receiving_namespaces()) { - return absl::nullopt; - } - - return {initNamespaces(config.overrides().metadata_options().receiving_namespaces().untyped())}; -} - -absl::optional -FilterConfigPerRoute::initProcessingMode(const ExtProcPerRoute& config) { - if (!config.disabled() && config.has_overrides() && config.overrides().has_processing_mode()) { - return config.overrides().processing_mode(); - } - return absl::nullopt; -} -absl::optional -FilterConfigPerRoute::initGrpcService(const ExtProcPerRoute& config) { - if (config.has_overrides() && config.overrides().has_grpc_service()) { - return config.overrides().grpc_service(); - } - return absl::nullopt; -} - -absl::optional -FilterConfigPerRoute::mergeProcessingMode(const FilterConfigPerRoute& less_specific, - const FilterConfigPerRoute& more_specific) { - if (more_specific.disabled()) { - return absl::nullopt; - } - return more_specific.processingMode().has_value() ? more_specific.processingMode() - : less_specific.processingMode(); -} - FilterConfigPerRoute::FilterConfigPerRoute(const ExtProcPerRoute& config) : disabled_(config.disabled()), processing_mode_(initProcessingMode(config)), grpc_service_(initGrpcService(config)), + grpc_initial_metadata_(config.overrides().grpc_initial_metadata().begin(), + config.overrides().grpc_initial_metadata().end()), untyped_forwarding_namespaces_(initUntypedForwardingNamespaces(config)), typed_forwarding_namespaces_(initTypedForwardingNamespaces(config)), untyped_receiving_namespaces_(initUntypedReceivingNamespaces(config)) {} @@ -198,6 +252,7 @@ FilterConfigPerRoute::FilterConfigPerRoute(const FilterConfigPerRoute& less_spec processing_mode_(mergeProcessingMode(less_specific, more_specific)), grpc_service_(more_specific.grpcService().has_value() ? more_specific.grpcService() : less_specific.grpcService()), + grpc_initial_metadata_(mergeGrpcInitialMetadata(less_specific, more_specific)), untyped_forwarding_namespaces_(more_specific.untypedForwardingMetadataNamespaces().has_value() ? more_specific.untypedForwardingMetadataNamespaces() : less_specific.untypedForwardingMetadataNamespaces()), @@ -995,10 +1050,6 @@ void Filter::onFinishProcessorCalls(Grpc::Status::GrpcStatus call_status) { encoding_state_.onFinishProcessorCall(call_status); } -static const ImmediateMutationChecker& immediateResponseChecker() { - CONSTRUCT_ON_FIRST_USE(ImmediateMutationChecker); -} - void Filter::sendImmediateResponse(const ImmediateResponse& response) { auto status_code = response.has_status() ? response.status().code() : DefaultImmediateStatus; if (!MutationUtils::isValidHttpStatus(status_code)) { @@ -1036,13 +1087,6 @@ void Filter::sendImmediateResponse(const ImmediateResponse& response) { mutate_headers, grpc_status, details); } -static ProcessingMode allDisabledMode() { - ProcessingMode pm; - pm.set_request_header_mode(ProcessingMode::SKIP); - pm.set_response_header_mode(ProcessingMode::SKIP); - return pm; -} - void Filter::mergePerRouteConfig() { if (route_config_merged_) { return; @@ -1089,6 +1133,16 @@ void Filter::mergePerRouteConfig() { grpc_service_ = *merged_config->grpcService(); config_with_hash_key_.setConfig(*merged_config->grpcService()); } + if (!merged_config->grpcInitialMetadata().empty()) { + ENVOY_LOG(trace, "Overriding grpc initial metadata from per-route configuration"); + envoy::config::core::v3::GrpcService config = config_with_hash_key_.config(); + auto ptr = config.mutable_initial_metadata(); + for (const auto& header : merged_config->grpcInitialMetadata()) { + ENVOY_LOG(trace, "Setting grpc initial metadata {} = {}", header.key(), header.value()); + mergeHeaderValuesField(*ptr, header); + } + config_with_hash_key_.setConfig(config); + } // For metadata namespaces, we only override the existing value if we have a // value from our merged config. We indicate a lack of value from the merged diff --git a/source/extensions/filters/http/ext_proc/ext_proc.h b/source/extensions/filters/http/ext_proc/ext_proc.h index 1ef14a7c2e81..689b1e322872 100644 --- a/source/extensions/filters/http/ext_proc/ext_proc.h +++ b/source/extensions/filters/http/ext_proc/ext_proc.h @@ -247,6 +247,9 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig { const absl::optional& grpcService() const { return grpc_service_; } + const std::vector& grpcInitialMetadata() const { + return grpc_initial_metadata_; + } const absl::optional>& untypedForwardingMetadataNamespaces() const { @@ -260,31 +263,11 @@ class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig { } private: - absl::optional - initProcessingMode(const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - absl::optional - initGrpcService(const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - std::vector initNamespaces(const Protobuf::RepeatedPtrField& ns); - - absl::optional> initUntypedForwardingNamespaces( - const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - absl::optional> initTypedForwardingNamespaces( - const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - absl::optional> initUntypedReceivingNamespaces( - const envoy::extensions::filters::http::ext_proc::v3::ExtProcPerRoute& config); - - absl::optional - mergeProcessingMode(const FilterConfigPerRoute& less_specific, - const FilterConfigPerRoute& more_specific); - const bool disabled_; const absl::optional processing_mode_; const absl::optional grpc_service_; + std::vector grpc_initial_metadata_; const absl::optional> untyped_forwarding_namespaces_; const absl::optional> typed_forwarding_namespaces_; @@ -321,6 +304,9 @@ class Filter : public Logger::Loggable, config->untypedReceivingMetadataNamespaces()) {} const FilterConfig& config() const { return *config_; } + const envoy::config::core::v3::GrpcService& grpc_service_config() const { + return config_with_hash_key_.config(); + } ExtProcFilterStats& stats() { return stats_; } ExtProcLoggingInfo* loggingInfo() { return logging_info_; } diff --git a/test/extensions/filters/http/ext_proc/config_test.cc b/test/extensions/filters/http/ext_proc/config_test.cc index fb982aee9b25..5c5196af6551 100644 --- a/test/extensions/filters/http/ext_proc/config_test.cc +++ b/test/extensions/filters/http/ext_proc/config_test.cc @@ -106,6 +106,23 @@ TEST(HttpExtProcConfigTest, CorrectConfigServerContext) { cb(filter_callback); } +TEST(HttpExtProcConfigTest, CorrectRouteMetadataOnlyConfig) { + std::string yaml = R"EOF( + overrides: + grpc_initial_metadata: + - key: "a" + value: "a" + )EOF"; + + ExternalProcessingFilterConfig factory; + ProtobufTypes::MessagePtr proto_config = factory.createEmptyRouteConfigProto(); + TestUtility::loadFromYaml(yaml, *proto_config); + + testing::NiceMock context; + Router::RouteSpecificFilterConfigConstSharedPtr cb = factory.createRouteSpecificFilterConfig( + *proto_config, context, context.messageValidationVisitor()); +} + } // namespace } // namespace ExternalProcessing } // namespace HttpFilters diff --git a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc index fb936314551d..02efb17e237b 100644 --- a/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc +++ b/test/extensions/filters/http/ext_proc/ext_proc_integration_test.cc @@ -41,6 +41,7 @@ using envoy::service::ext_proc::v3::ProcessingResponse; using envoy::service::ext_proc::v3::TrailersResponse; using Extensions::HttpFilters::ExternalProcessing::HasNoHeader; using Extensions::HttpFilters::ExternalProcessing::HeaderProtosEqual; +using Extensions::HttpFilters::ExternalProcessing::makeHeaderValue; using Extensions::HttpFilters::ExternalProcessing::SingleHeaderValueIs; using Http::LowerCaseString; @@ -2580,6 +2581,43 @@ TEST_P(ExtProcIntegrationTest, PerRouteGrpcService) { EXPECT_THAT(response->headers(), SingleHeaderValueIs("x-response-processed", "1")); } +// Set up per-route configuration that extends original metadata. +TEST_P(ExtProcIntegrationTest, PerRouteGrpcMetadata) { + initializeConfig(); + + // Override metadata from route config. + config_helper_.addConfigModifier([this](HttpConnectionManager& cm) { + // Set up "/foo" so that it will use a different GrpcService + auto* vh = cm.mutable_route_config()->mutable_virtual_hosts()->Mutable(0); + auto* route = vh->mutable_routes()->Mutable(0); + route->mutable_match()->set_path("/foo"); + ExtProcPerRoute per_route; + *per_route.mutable_overrides()->mutable_grpc_initial_metadata()->Add() = + makeHeaderValue("b", "c"); + *per_route.mutable_overrides()->mutable_grpc_initial_metadata()->Add() = + makeHeaderValue("c", "c"); + setPerRouteConfig(route, per_route); + }); + + HttpIntegrationTest::initialize(); + + // Request that matches route directed to ext_proc_server_0 + auto response = + sendDownstreamRequest([](Http::RequestHeaderMap& headers) { headers.setPath("/foo"); }); + + processRequestHeadersMessage(*grpc_upstreams_[0], true, absl::nullopt); + EXPECT_EQ( + "c", + processor_stream_->headers().get(Http::LowerCaseString("b"))[0]->value().getStringView()); + EXPECT_EQ( + "c", + processor_stream_->headers().get(Http::LowerCaseString("c"))[0]->value().getStringView()); + handleUpstreamRequest(); + + processResponseHeadersMessage(*grpc_upstreams_[0], false, absl::nullopt); + verifyDownstreamResponse(*response, 200); +} + // Sending new timeout API in both downstream request and upstream response // handling path with header mutation. TEST_P(ExtProcIntegrationTest, RequestAndResponseMessageNewTimeoutWithHeaderMutation) { diff --git a/test/extensions/filters/http/ext_proc/filter_test.cc b/test/extensions/filters/http/ext_proc/filter_test.cc index e319a9aef369..85c742435fe7 100644 --- a/test/extensions/filters/http/ext_proc/filter_test.cc +++ b/test/extensions/filters/http/ext_proc/filter_test.cc @@ -2998,6 +2998,33 @@ TEST(OverrideTest, GrpcServiceNonOverride) { EXPECT_THAT(*merged_route.grpcService(), ProtoEq(cfg1.overrides().grpc_service())); } +// When merging two configurations, second metadata override only extends the first's one. +TEST(OverrideTest, GrpcMetadataOverride) { + ExtProcPerRoute cfg1; + cfg1.mutable_overrides()->mutable_grpc_initial_metadata()->Add()->CopyFrom( + makeHeaderValue("a", "a")); + cfg1.mutable_overrides()->mutable_grpc_initial_metadata()->Add()->CopyFrom( + makeHeaderValue("b", "b")); + + ExtProcPerRoute cfg2; + cfg2.mutable_overrides()->mutable_grpc_initial_metadata()->Add()->CopyFrom( + makeHeaderValue("b", "c")); + cfg2.mutable_overrides()->mutable_grpc_initial_metadata()->Add()->CopyFrom( + makeHeaderValue("c", "c")); + + FilterConfigPerRoute route1(cfg1); + FilterConfigPerRoute route2(cfg2); + FilterConfigPerRoute merged_route(route1, route2); + + ASSERT_TRUE(merged_route.grpcInitialMetadata().size() == 3); + EXPECT_THAT(merged_route.grpcInitialMetadata()[0], + ProtoEq(cfg1.overrides().grpc_initial_metadata()[0])); + EXPECT_THAT(merged_route.grpcInitialMetadata()[1], + ProtoEq(cfg2.overrides().grpc_initial_metadata()[0])); + EXPECT_THAT(merged_route.grpcInitialMetadata()[2], + ProtoEq(cfg2.overrides().grpc_initial_metadata()[1])); +} + // Verify that attempts to change headers that are not allowed to be changed // are ignored and a counter is incremented. TEST_F(HttpFilterTest, IgnoreInvalidHeaderMutations) { @@ -3853,6 +3880,63 @@ TEST_F(HttpFilter2Test, LastEncodeDataCallExceedsStreamBufferLimitWouldJustRaise conn_manager_->onData(fake_input, false); } +// Test that per route metadata override does override inherited grpc_service configuration. +TEST_F(HttpFilterTest, GrpcServiceMetadataOverride) { + initialize(R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + initial_metadata: + - key: "a" + value: "a" + - key: "b" + value: "b" + )EOF"); + + // Route configuration overrides the grpc_service metadata. + ExtProcPerRoute route_proto; + *route_proto.mutable_overrides()->mutable_grpc_initial_metadata()->Add() = + makeHeaderValue("b", "c"); + *route_proto.mutable_overrides()->mutable_grpc_initial_metadata()->Add() = + makeHeaderValue("c", "c"); + FilterConfigPerRoute route_config(route_proto); + EXPECT_CALL(decoder_callbacks_, traversePerFilterConfig(_)) + .WillOnce( + testing::Invoke([&](std::function cb) { + cb(route_config); + })); + + // Build expected merged grpc_service configuration. + { + std::string expected_config = (R"EOF( + grpc_service: + envoy_grpc: + cluster_name: "ext_proc_server" + initial_metadata: + - key: "a" + value: "a" + - key: "b" + value: "c" + - key: "c" + value: "c" + )EOF"); + envoy::extensions::filters::http::ext_proc::v3::ExternalProcessor expected_proto{}; + TestUtility::loadFromYaml(expected_config, expected_proto); + final_expected_grpc_service_.emplace(expected_proto.grpc_service()); + config_with_hash_key_.setConfig(expected_proto.grpc_service()); + } + + EXPECT_EQ(FilterHeadersStatus::StopIteration, filter_->decodeHeaders(request_headers_, true)); + processRequestHeaders(false, absl::nullopt); + + const auto& meta = filter_->grpc_service_config().initial_metadata(); + EXPECT_EQ(meta[0].value(), "a"); // a = a inherited + EXPECT_EQ(meta[1].value(), "c"); // b = c overridden + EXPECT_EQ(meta[2].value(), "c"); // c = c added + + filter_->onDestroy(); +} + } // namespace } // namespace ExternalProcessing } // namespace HttpFilters diff --git a/test/extensions/filters/http/ext_proc/utils.cc b/test/extensions/filters/http/ext_proc/utils.cc index 0e322535c08a..e3723b8b728b 100644 --- a/test/extensions/filters/http/ext_proc/utils.cc +++ b/test/extensions/filters/http/ext_proc/utils.cc @@ -29,6 +29,14 @@ bool ExtProcTestUtility::headerProtosEqualIgnoreOrder( return TestUtility::headerMapEqualIgnoreOrder(expected, actual_headers); } +envoy::config::core::v3::HeaderValue makeHeaderValue(const std::string& key, + const std::string& value) { + envoy::config::core::v3::HeaderValue v; + v.set_key(key); + v.set_value(value); + return v; +} + } // namespace ExternalProcessing } // namespace HttpFilters } // namespace Extensions diff --git a/test/extensions/filters/http/ext_proc/utils.h b/test/extensions/filters/http/ext_proc/utils.h index ba88559b9c74..858e8e7cb01d 100644 --- a/test/extensions/filters/http/ext_proc/utils.h +++ b/test/extensions/filters/http/ext_proc/utils.h @@ -50,6 +50,8 @@ MATCHER_P2(SingleProtoHeaderValueIs, key, value, return false; } +envoy::config::core::v3::HeaderValue makeHeaderValue(const std::string& key, + const std::string& value); } // namespace ExternalProcessing } // namespace HttpFilters } // namespace Extensions From 8e93d16d433d3364c2b000dc9067ffc400e8f0d6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 09:07:18 +0000 Subject: [PATCH 112/151] build(deps): bump postgres from `1bd17d3` to `0e564da` in /examples/shared/postgres (#32577) build(deps): bump postgres in /examples/shared/postgres Bumps postgres from `1bd17d3` to `0e564da`. --- updated-dependencies: - dependency-name: postgres dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/postgres/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index cf9e3a129a33..c21a5ac72c53 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:1bd17d36d605b63fd62f03800a932bae292250659ffc417cf8c29836cc353b5f +FROM postgres:latest@sha256:0e564daae78c2eea56ba57c64711bb9c408a512ce73cf81f9c78623354dd6976 COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] From 9af06297f542abf2025f8c8e6de0310d427d6b91 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Wed, 28 Feb 2024 11:03:36 -0800 Subject: [PATCH 113/151] quic: Fix crash bug with QUIC upstream + X.509v1 certificates (#32584) Signed-off-by: Ryan Hamilton --- changelogs/current.yaml | 3 ++ .../common/quic/envoy_quic_proof_verifier.cc | 5 ++- .../quic/envoy_quic_proof_verifier_test.cc | 39 +++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 9ea7f1c85310..3ff9eeab0609 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -88,6 +88,9 @@ bug_fixes: - area: http change: | Fixed crash when HTTP request idle and per try timeouts occurs within backoff interval. +- area: quic + change: | + Fixed crash bug with QUIC upstream + X.509v1 certificates. - area: proxy_protocol change: | Fix crash due to uncaught exception when the operating system does not support an address type (such as IPv6) that is diff --git a/source/common/quic/envoy_quic_proof_verifier.cc b/source/common/quic/envoy_quic_proof_verifier.cc index b971b0a5ced1..93c206be91dd 100644 --- a/source/common/quic/envoy_quic_proof_verifier.cc +++ b/source/common/quic/envoy_quic_proof_verifier.cc @@ -90,7 +90,10 @@ quic::QuicAsyncStatus EnvoyQuicProofVerifier::VerifyCertChain( } std::unique_ptr cert_view = quic::CertificateView::ParseSingleCertificate(certs[0]); - ASSERT(cert_view != nullptr); + if (cert_view == nullptr) { + *error_details = "unable to parse certificate"; + return quic::QUIC_FAILURE; + } int sign_alg = deduceSignatureAlgorithmFromPublicKey(cert_view->public_key(), error_details); if (sign_alg == 0) { return quic::QUIC_FAILURE; diff --git a/test/common/quic/envoy_quic_proof_verifier_test.cc b/test/common/quic/envoy_quic_proof_verifier_test.cc index f6f7423c9fce..3953ce0ed9e5 100644 --- a/test/common/quic/envoy_quic_proof_verifier_test.cc +++ b/test/common/quic/envoy_quic_proof_verifier_test.cc @@ -399,6 +399,45 @@ TEST_F(EnvoyQuicProofVerifierTest, VerifySubjectAltNameListOverrideFailure) { EXPECT_FALSE(static_cast(*verify_details).isValid()); } +TEST_F(EnvoyQuicProofVerifierTest, VerifyX509v1Cert) { + std::string cert_v1 = R"text( +-----BEGIN CERTIFICATE----- +MIICpDCCAYwCCQClUY4hwG3eCTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlx +dWljLnRlc3QwHhcNMjQwMTMxMDUxNDI1WhcNMjUwMTMwMDUxNDI1WjAUMRIwEAYD +VQQDDAlxdWljLnRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw +PmbxO63dR1i7yCADA0Q2N6vWzkZ53lsKktSNeAoV5RM6hbH9zt5BR6blZSJ87Ybl +otMUioTb3DIF2z99k/QB8xpXevurZ09z9bMricsIM2LiS1hNyF09h1yPnRCy3BB5 +wEwbovKqDaFbCe09iefvsxGMENyDLA+uoisgxgRGfNJwggGy5WTSvpmnn1iEDmHR +4kEOxlDVHhmWIvr3nGFNuO/YEgPcDVdhu+UVCHan2RDjGX7KfkfNvt6aTLIgq+rO +CnF5/hFYRO4ypn2Lsw1me1n3H3hEm/5MuY7XdTK8tl/ezaIEHjUt5ExnKy0N2AS9 +LcMpoD4L0DFunxMdH72HAgMBAAEwDQYJKoZIhvcNAQELBQADggEBADNHjJNzozcS +APuVQCQnrdw7Drou3AyO47F2kxzheh8iqDU77MaH8aWhwmcpdg1vxhTCGkPRNKD8 +7XUpkh7kdpvfzQex12c3DDnVvgsa26aEXsbyxtV3ty+tiIRzRGAEEH4j5n0322Vd +kcd96WKVplBaYncSiSFCyomAymd+eqhBsVEXDGcf+YtEq8TwGZJ3o0RNm7AfDTu6 +vuvcjdfSQSjwxshGMLq/70K+lYoKKVw6/AaxypJ/YYIHSUtNzu85bO9yW7lG2kov +Ti7PYy9cxmZTjNqHI7Kghk3FLry/6P2bbclZxdtzJAPzZ9nw6N9xGfj2D8+3VALl +wYsML58R3P8= +-----END CERTIFICATE----- +)text"; + + // NOLINTNEXTLINE(modernize-make-shared) + transport_socket_options_.reset(new Network::TransportSocketOptionsImpl("", {"non-example.com"})); + configCertVerificationDetails(true); + const std::string ocsp_response; + const std::string cert_sct; + std::string error_details; + std::unique_ptr verify_details; + std::stringstream pem_stream(cert_v1); + std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); + EXPECT_EQ(quic::QUIC_FAILURE, + verifier_->VerifyCertChain("localhost", 54321, chain, ocsp_response, cert_sct, + &verify_context_, &error_details, &verify_details, nullptr, + nullptr)) + << error_details; + EXPECT_EQ("unable to parse certificate", error_details); + EXPECT_EQ(verify_details, nullptr); +} + TEST_F(EnvoyQuicProofVerifierTest, VerifyProof) { configCertVerificationDetails(true); EXPECT_DEATH(verifier_->VerifyProof("", 0, "", quic::QUIC_VERSION_IETF_RFC_V1, "", {}, "", "", From c7ce585074d9b2fb5fbbb9044247e847e2a65b81 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Wed, 28 Feb 2024 11:04:09 -0800 Subject: [PATCH 114/151] quic: remove duplicate code in QUIC encodeData(), encodeMetadata() and encodeTrailers() implementations (#32570) Signed-off-by: Ryan Hamilton --- source/common/quic/BUILD | 10 +- .../common/quic/envoy_quic_client_stream.cc | 97 +-------------- source/common/quic/envoy_quic_client_stream.h | 6 - .../common/quic/envoy_quic_server_stream.cc | 99 +--------------- source/common/quic/envoy_quic_server_stream.h | 9 -- source/common/quic/envoy_quic_stream.cc | 112 ++++++++++++++++++ source/common/quic/envoy_quic_stream.h | 26 +++- source/common/quic/quic_stats_gatherer.cc | 2 + 8 files changed, 151 insertions(+), 210 deletions(-) create mode 100644 source/common/quic/envoy_quic_stream.cc diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index 9f9043650ac0..6f0315c59fea 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -145,19 +145,25 @@ envoy_cc_library( envoy_cc_library( name = "envoy_quic_stream_lib", + srcs = ["envoy_quic_stream.cc"], hdrs = ["envoy_quic_stream.h"], tags = ["nofips"], deps = [ ":envoy_quic_simulated_watermark_buffer_lib", ":envoy_quic_utils_lib", ":quic_filter_manager_connection_lib", + ":quic_stats_gatherer", ":send_buffer_monitor_lib", "//envoy/event:dispatcher_interface", "//envoy/http:codec_interface", "//source/common/http:codec_helper_lib", "@com_github_google_quiche//:http2_adapter", + "@com_github_google_quiche//:quic_core_http_client_lib", + "@com_github_google_quiche//:quic_core_http_http_encoder_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - ], + ] + envoy_select_enable_http_datagrams([ + ":http_datagram_handler", + ]), ) envoy_cc_library( @@ -645,7 +651,9 @@ envoy_cc_library( tags = ["nofips"], deps = [ "//envoy/access_log:access_log_interface", + "//envoy/formatter:http_formatter_context_interface", "//envoy/http:codec_interface", + "//source/common/formatter:substitution_formatter_lib", "//source/common/http:header_map_lib", "@com_github_google_quiche//:quic_core_ack_listener_interface_lib", ], diff --git a/source/common/quic/envoy_quic_client_stream.cc b/source/common/quic/envoy_quic_client_stream.cc index b082eaec01f3..18e02bb0bdc3 100644 --- a/source/common/quic/envoy_quic_client_stream.cc +++ b/source/common/quic/envoy_quic_client_stream.cc @@ -23,6 +23,7 @@ EnvoyQuicClientStream::EnvoyQuicClientStream( const envoy::config::core::v3::Http3ProtocolOptions& http3_options) : quic::QuicSpdyClientStream(id, client_session, type), EnvoyQuicStream( + *this, // Flow control receive window should be larger than 8k so that the send buffer can fully // utilize congestion control window before it reaches the high watermark. static_cast(GetReceiveWindow().value()), *filterManagerConnection(), @@ -115,103 +116,9 @@ Http::Status EnvoyQuicClientStream::encodeHeaders(const Http::RequestHeaderMap& return Http::okStatus(); } -void EnvoyQuicClientStream::encodeData(Buffer::Instance& data, bool end_stream) { - ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, - data.length()); - const bool has_data = data.length() > 0; - if (!has_data && !end_stream) { - return; - } - if (write_side_closed()) { - IS_ENVOY_BUG("encodeData is called on write-closed stream."); - return; - } - ASSERT(!local_end_stream_); - local_end_stream_ = end_stream; - SendBufferMonitor::ScopedWatermarkBufferUpdater updater(this, this); -#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS - if (http_datagram_handler_) { - IncrementalBytesSentTracker tracker(*this, *mutableBytesMeter(), false); - if (!http_datagram_handler_->encodeCapsuleFragment(data.toString(), end_stream)) { - Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); - return; - } - } else { -#endif - Buffer::RawSliceVector raw_slices = data.getRawSlices(); - absl::InlinedVector quic_slices; - quic_slices.reserve(raw_slices.size()); - for (auto& slice : raw_slices) { - ASSERT(slice.len_ != 0); - // Move each slice into a stand-alone buffer. - // TODO(danzh): investigate the cost of allocating one buffer per slice. - // If it turns out to be expensive, add a new function to free data in the middle in buffer - // interface and re-design QuicheMemSliceImpl. - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.quiche_use_mem_slice_releasor_api")) { - quic_slices.emplace_back(quiche::QuicheMemSlice::InPlace(), data, slice.len_); - } else { - auto single_slice_buffer = std::make_unique(); - single_slice_buffer->move(data, slice.len_); - quic_slices.emplace_back( - reinterpret_cast(slice.mem_), slice.len_, - [single_slice_buffer = std::move(single_slice_buffer)](const char*) mutable { - // Free this memory explicitly when the callback is invoked. - single_slice_buffer = nullptr; - }); - } - } - quic::QuicConsumedData result{0, false}; - absl::Span span(quic_slices); - { - IncrementalBytesSentTracker tracker(*this, *mutableBytesMeter(), false); - result = WriteBodySlices(span, end_stream); - } - // QUIC stream must take all. - if (result.bytes_consumed == 0 && has_data) { - IS_ENVOY_BUG(fmt::format("Send buffer didn't take all the data. Stream is write {} with {} " - "bytes in send buffer. Current write was rejected.", - write_side_closed() ? "closed" : "open", BufferedDataBytes())); - Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); - return; - } -#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS - } -#endif - if (local_end_stream_) { - if (codec_callbacks_) { - codec_callbacks_->onCodecEncodeComplete(); - } - onLocalEndStream(); - } -} - void EnvoyQuicClientStream::encodeTrailers(const Http::RequestTrailerMap& trailers) { ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); - if (write_side_closed()) { - IS_ENVOY_BUG("encodeTrailers is called on write-closed stream."); - return; - } - ASSERT(!local_end_stream_); - local_end_stream_ = true; - ScopedWatermarkBufferUpdater updater(this, this); - - { - IncrementalBytesSentTracker tracker(*this, *mutableBytesMeter(), true); - size_t bytes_sent = WriteTrailers(envoyHeadersToHttp2HeaderBlock(trailers), nullptr); - ENVOY_BUG(bytes_sent != 0, "Failed to encode trailers"); - } - - if (codec_callbacks_) { - codec_callbacks_->onCodecEncodeComplete(); - } - onLocalEndStream(); -} - -void EnvoyQuicClientStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { - // Metadata Frame is not supported in QUICHE. - ENVOY_STREAM_LOG(debug, "METADATA is not supported in Http3.", *this); - stats_.metadata_not_supported_error_.inc(); + encodeTrailersImpl(envoyHeadersToHttp2HeaderBlock(trailers)); } void EnvoyQuicClientStream::resetStream(Http::StreamResetReason reason) { diff --git a/source/common/quic/envoy_quic_client_stream.h b/source/common/quic/envoy_quic_client_stream.h index edaba1b04ea5..98607867ea5b 100644 --- a/source/common/quic/envoy_quic_client_stream.h +++ b/source/common/quic/envoy_quic_client_stream.h @@ -25,8 +25,6 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, void setResponseDecoder(Http::ResponseDecoder& decoder) { response_decoder_ = &decoder; } // Http::StreamEncoder - void encodeData(Buffer::Instance& data, bool end_stream) override; - void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; Http::Http1StreamEncoderOptionsOptRef http1StreamEncoderOptions() override { return absl::nullopt; } @@ -89,10 +87,6 @@ class EnvoyQuicClientStream : public quic::QuicSpdyClientStream, Http::ResponseDecoder* response_decoder_{nullptr}; bool decoded_1xx_{false}; -#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS - // Setting |http_datagram_handler_| enables HTTP Datagram support. - std::unique_ptr http_datagram_handler_; -#endif // When an HTTP Upgrade is requested, this contains the protocol upgrade type, e.g. "websocket". // It will be empty, when no such request is active. diff --git a/source/common/quic/envoy_quic_server_stream.cc b/source/common/quic/envoy_quic_server_stream.cc index 662f4f123d8b..1932010257f7 100644 --- a/source/common/quic/envoy_quic_server_stream.cc +++ b/source/common/quic/envoy_quic_server_stream.cc @@ -30,6 +30,7 @@ EnvoyQuicServerStream::EnvoyQuicServerStream( headers_with_underscores_action) : quic::QuicSpdyServerStreamBase(id, session, type), EnvoyQuicStream( + *this, // Flow control receive window should be larger than 8k to fully utilize congestion // control window before it reaches the high watermark. static_cast(GetReceiveWindow().value()), *filterManagerConnection(), @@ -89,105 +90,9 @@ void EnvoyQuicServerStream::encodeHeaders(const Http::ResponseHeaderMap& headers } } -void EnvoyQuicServerStream::encodeData(Buffer::Instance& data, bool end_stream) { - ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, - data.length()); - const bool has_data = data.length() > 0; - if (!has_data && !end_stream) { - return; - } - if (write_side_closed()) { - IS_ENVOY_BUG("encodeData is called on write-closed stream."); - return; - } - ASSERT(!local_end_stream_); - local_end_stream_ = end_stream; - SendBufferMonitor::ScopedWatermarkBufferUpdater updater(this, this); -#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS - if (http_datagram_handler_) { - IncrementalBytesSentTracker tracker(*this, *mutableBytesMeter(), false); - if (!http_datagram_handler_->encodeCapsuleFragment(data.toString(), end_stream)) { - Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); - return; - } - } else { -#endif - Buffer::RawSliceVector raw_slices = data.getRawSlices(); - absl::InlinedVector quic_slices; - quic_slices.reserve(raw_slices.size()); - for (auto& slice : raw_slices) { - ASSERT(slice.len_ != 0); - // Move each slice into a stand-alone buffer. - // TODO(danzh): investigate the cost of allocating one buffer per slice. - // If it turns out to be expensive, add a new function to free data in the middle in buffer - // interface and re-design QuicheMemSliceImpl. - if (!Runtime::runtimeFeatureEnabled( - "envoy.reloadable_features.quiche_use_mem_slice_releasor_api")) { - quic_slices.emplace_back(quiche::QuicheMemSlice::InPlace(), data, slice.len_); - } else { - auto single_slice_buffer = std::make_unique(); - single_slice_buffer->move(data, slice.len_); - quic_slices.emplace_back( - reinterpret_cast(slice.mem_), slice.len_, - [single_slice_buffer = std::move(single_slice_buffer)](const char*) mutable { - // Free this memory explicitly when the callback is invoked. - single_slice_buffer = nullptr; - }); - } - } - quic::QuicConsumedData result{0, false}; - absl::Span span(quic_slices); - { - IncrementalBytesSentTracker tracker(*this, *mutableBytesMeter(), false); - result = WriteBodySlices(span, end_stream); - stats_gatherer_->addBytesSent(result.bytes_consumed, end_stream); - } - // QUIC stream must take all. - if (result.bytes_consumed == 0 && has_data) { - IS_ENVOY_BUG(fmt::format("Send buffer didn't take all the data. Stream is write {} with {} " - "bytes in send buffer. Current write was rejected.", - write_side_closed() ? "closed" : "open", BufferedDataBytes())); - Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); - return; - } -#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS - } -#endif - if (local_end_stream_) { - if (codec_callbacks_) { - codec_callbacks_->onCodecEncodeComplete(); - } - onLocalEndStream(); - } -} - void EnvoyQuicServerStream::encodeTrailers(const Http::ResponseTrailerMap& trailers) { ENVOY_STREAM_LOG(debug, "encodeTrailers: {}.", *this, trailers); - if (write_side_closed()) { - IS_ENVOY_BUG("encodeTrailers is called on write-closed stream."); - return; - } - ASSERT(!local_end_stream_); - local_end_stream_ = true; - - SendBufferMonitor::ScopedWatermarkBufferUpdater updater(this, this); - - { - IncrementalBytesSentTracker tracker(*this, *mutableBytesMeter(), true); - size_t bytes_sent = WriteTrailers(envoyHeadersToHttp2HeaderBlock(trailers), nullptr); - ENVOY_BUG(bytes_sent != 0, "Failed to encode trailers."); - stats_gatherer_->addBytesSent(bytes_sent, true); - } - if (codec_callbacks_) { - codec_callbacks_->onCodecEncodeComplete(); - } - onLocalEndStream(); -} - -void EnvoyQuicServerStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { - // Metadata Frame is not supported in QUIC. - ENVOY_STREAM_LOG(debug, "METADATA is not supported in Http3.", *this); - stats_.metadata_not_supported_error_.inc(); + encodeTrailersImpl(envoyHeadersToHttp2HeaderBlock(trailers)); } void EnvoyQuicServerStream::resetStream(Http::StreamResetReason reason) { diff --git a/source/common/quic/envoy_quic_server_stream.h b/source/common/quic/envoy_quic_server_stream.h index 146e4f8b0f04..bb9c32003b07 100644 --- a/source/common/quic/envoy_quic_server_stream.h +++ b/source/common/quic/envoy_quic_server_stream.h @@ -5,7 +5,6 @@ #ifdef ENVOY_ENABLE_HTTP_DATAGRAMS #include "source/common/quic/http_datagram_handler.h" #endif -#include "source/common/quic/quic_stats_gatherer.h" #include "quiche/common/platform/api/quiche_reference_counted.h" #include "quiche/quic/core/http/quic_spdy_server_stream_base.h" @@ -28,14 +27,11 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, request_decoder_ = &decoder; stats_gatherer_->setAccessLogHandlers(request_decoder_->accessLogHandlers()); } - QuicStatsGatherer* statsGatherer() { return stats_gatherer_.get(); } // Http::StreamEncoder void encode1xxHeaders(const Http::ResponseHeaderMap& headers) override; void encodeHeaders(const Http::ResponseHeaderMap& headers, bool end_stream) override; - void encodeData(Buffer::Instance& data, bool end_stream) override; void encodeTrailers(const Http::ResponseTrailerMap& trailers) override; - void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; Http::Http1StreamEncoderOptionsOptRef http1StreamEncoderOptions() override { return absl::nullopt; } @@ -121,11 +117,6 @@ class EnvoyQuicServerStream : public quic::QuicSpdyServerStreamBase, envoy::config::core::v3::HttpProtocolOptions::HeadersWithUnderscoresAction headers_with_underscores_action_; - quiche::QuicheReferenceCountedPointer stats_gatherer_; -#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS - // Setting |http_datagram_handler_| enables HTTP Datagram support. - std::unique_ptr http_datagram_handler_; -#endif // True if a :path header has been seen before. bool saw_path_{false}; }; diff --git a/source/common/quic/envoy_quic_stream.cc b/source/common/quic/envoy_quic_stream.cc new file mode 100644 index 000000000000..8ca1e7950466 --- /dev/null +++ b/source/common/quic/envoy_quic_stream.cc @@ -0,0 +1,112 @@ +#include "source/common/quic/envoy_quic_stream.h" + +#include "source/common/http/utility.h" + +#include "quiche/quic/core/http/http_encoder.h" +#include "quiche/quic/core/qpack/qpack_encoder.h" +#include "quiche/quic/core/qpack/qpack_instruction_encoder.h" + +namespace Envoy { +namespace Quic { + +void EnvoyQuicStream::encodeData(Buffer::Instance& data, bool end_stream) { + ENVOY_STREAM_LOG(debug, "encodeData (end_stream={}) of {} bytes.", *this, end_stream, + data.length()); + const bool has_data = data.length() > 0; + if (!has_data && !end_stream) { + return; + } + if (quic_stream_.write_side_closed()) { + IS_ENVOY_BUG("encodeData is called on write-closed stream."); + return; + } + ASSERT(!local_end_stream_); + local_end_stream_ = end_stream; + SendBufferMonitor::ScopedWatermarkBufferUpdater updater(&quic_stream_, this); +#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS + if (http_datagram_handler_) { + IncrementalBytesSentTracker tracker(quic_stream_, *mutableBytesMeter(), false); + if (!http_datagram_handler_->encodeCapsuleFragment(data.toString(), end_stream)) { + quic_stream_.Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); + return; + } + } else { +#endif + Buffer::RawSliceVector raw_slices = data.getRawSlices(); + absl::InlinedVector quic_slices; + quic_slices.reserve(raw_slices.size()); + for (auto& slice : raw_slices) { + ASSERT(slice.len_ != 0); + // Move each slice into a stand-alone buffer. + // TODO(danzh): investigate the cost of allocating one buffer per slice. + // If it turns out to be expensive, add a new function to free data in the middle in buffer + // interface and re-design QuicheMemSliceImpl. + auto single_slice_buffer = std::make_unique(); + single_slice_buffer->move(data, slice.len_); + quic_slices.emplace_back( + reinterpret_cast(slice.mem_), slice.len_, + [single_slice_buffer = std::move(single_slice_buffer)](const char*) mutable { + // Free this memory explicitly when the callback is invoked. + single_slice_buffer = nullptr; + }); + } + quic::QuicConsumedData result{0, false}; + absl::Span span(quic_slices); + { + IncrementalBytesSentTracker tracker(quic_stream_, *mutableBytesMeter(), false); + result = quic_stream_.WriteBodySlices(span, end_stream); + if (stats_gatherer_ != nullptr) { + stats_gatherer_->addBytesSent(result.bytes_consumed, end_stream); + } + } + // QUIC stream must take all. + if (result.bytes_consumed == 0 && has_data) { + IS_ENVOY_BUG(fmt::format("Send buffer didn't take all the data. Stream is write {} with {} " + "bytes in send buffer. Current write was rejected.", + quic_stream_.write_side_closed() ? "closed" : "open", + quic_stream_.BufferedDataBytes())); + quic_stream_.Reset(quic::QUIC_BAD_APPLICATION_PAYLOAD); + return; + } +#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS + } +#endif + if (local_end_stream_) { + if (codec_callbacks_) { + codec_callbacks_->onCodecEncodeComplete(); + } + onLocalEndStream(); + } +} + +void EnvoyQuicStream::encodeTrailersImpl(spdy::Http2HeaderBlock&& trailers) { + if (quic_stream_.write_side_closed()) { + IS_ENVOY_BUG("encodeTrailers is called on write-closed stream."); + return; + } + ASSERT(!local_end_stream_); + local_end_stream_ = true; + + SendBufferMonitor::ScopedWatermarkBufferUpdater updater(&quic_stream_, this); + { + IncrementalBytesSentTracker tracker(quic_stream_, *mutableBytesMeter(), true); + size_t bytes_sent = quic_stream_.WriteTrailers(std::move(trailers), nullptr); + ENVOY_BUG(bytes_sent != 0, "Failed to encode trailers."); + if (stats_gatherer_ != nullptr) { + stats_gatherer_->addBytesSent(bytes_sent, true); + } + } + if (codec_callbacks_) { + codec_callbacks_->onCodecEncodeComplete(); + } + onLocalEndStream(); +} + +void EnvoyQuicStream::encodeMetadata(const Http::MetadataMapVector& /*metadata_map_vector*/) { + // Metadata Frame is not supported in QUICHE. + ENVOY_STREAM_LOG(debug, "METADATA is not supported in Http3.", *this); + stats_.metadata_not_supported_error_.inc(); +} + +} // namespace Quic +} // namespace Envoy diff --git a/source/common/quic/envoy_quic_stream.h b/source/common/quic/envoy_quic_stream.h index 7bb5af71f25a..360788d2fdcd 100644 --- a/source/common/quic/envoy_quic_stream.h +++ b/source/common/quic/envoy_quic_stream.h @@ -10,10 +10,16 @@ #include "source/common/http/codec_helper.h" #include "source/common/quic/envoy_quic_simulated_watermark_buffer.h" #include "source/common/quic/envoy_quic_utils.h" + +#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS +#include "source/common/quic/http_datagram_handler.h" +#endif #include "source/common/quic/quic_filter_manager_connection_impl.h" +#include "source/common/quic/quic_stats_gatherer.h" #include "source/common/quic/send_buffer_monitor.h" #include "quiche/http2/adapter/header_validator.h" +#include "quiche/quic/core/http/quic_spdy_stream.h" namespace Envoy { namespace Quic { @@ -27,12 +33,13 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, public: // |buffer_limit| is the high watermark of the stream send buffer, and the low // watermark will be half of it. - EnvoyQuicStream(uint32_t buffer_limit, QuicFilterManagerConnectionImpl& filter_manager_connection, + EnvoyQuicStream(quic::QuicSpdyStream& quic_stream, uint32_t buffer_limit, + QuicFilterManagerConnectionImpl& filter_manager_connection, std::function below_low_watermark, std::function above_high_watermark, Http::Http3::CodecStats& stats, const envoy::config::core::v3::Http3ProtocolOptions& http3_options) : Http::MultiplexedStreamImplBase(filter_manager_connection.dispatcher()), stats_(stats), - http3_options_(http3_options), + http3_options_(http3_options), quic_stream_(quic_stream), send_buffer_simulation_(buffer_limit / 2, buffer_limit, std::move(below_low_watermark), std::move(above_high_watermark), ENVOY_LOGGER()), filter_manager_connection_(filter_manager_connection), @@ -44,6 +51,8 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, // Http::StreamEncoder Stream& getStream() override { return *this; } + void encodeData(Buffer::Instance& data, bool end_stream) override; + void encodeMetadata(const Http::MetadataMapVector& metadata_map_vector) override; // Http::Stream void readDisable(bool disable) override { @@ -138,6 +147,8 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, const StreamInfo::BytesMeterSharedPtr& bytesMeter() override { return bytes_meter_; } + QuicStatsGatherer* statsGatherer() { return stats_gatherer_.get(); } + protected: virtual void switchStreamBlockState() PURE; @@ -167,6 +178,14 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, StreamInfo::BytesMeterSharedPtr& mutableBytesMeter() { return bytes_meter_; } + void encodeTrailersImpl(spdy::Http2HeaderBlock&& trailers); + +#ifdef ENVOY_ENABLE_HTTP_DATAGRAMS + // Setting |http_datagram_handler_| enables HTTP Datagram support. + std::unique_ptr http_datagram_handler_; +#endif + quiche::QuicheReferenceCountedPointer stats_gatherer_; + // True once end of stream is propagated to Envoy. Envoy doesn't expect to be // notified more than once about end of stream. So once this is true, no need // to set it in the callback to Envoy stream any more. @@ -191,6 +210,9 @@ class EnvoyQuicStream : public virtual Http::StreamEncoder, bool saw_regular_headers_{false}; private: + // QUIC stream that this EnvoyQuicStream wraps. + quic::QuicSpdyStream& quic_stream_; + // Keeps track of bytes buffered in the stream send buffer in QUICHE and reacts // upon crossing high and low watermarks. // Its high watermark is also the buffer limit of stream read/write filters in diff --git a/source/common/quic/quic_stats_gatherer.cc b/source/common/quic/quic_stats_gatherer.cc index cd663aa3cb15..c1a0da083082 100644 --- a/source/common/quic/quic_stats_gatherer.cc +++ b/source/common/quic/quic_stats_gatherer.cc @@ -2,6 +2,8 @@ #include +#include "envoy/formatter/http_formatter_context.h" + namespace Envoy { namespace Quic { From a41d9ad6f8e0c8f8b92c8f7daf8b6c9d810b1bfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 20:42:37 +0000 Subject: [PATCH 115/151] build(deps): bump slack-sdk from 3.27.0 to 3.27.1 in /tools/base (#32615) Bumps [slack-sdk](https://github.com/slackapi/python-slack-sdk) from 3.27.0 to 3.27.1. - [Release notes](https://github.com/slackapi/python-slack-sdk/releases) - [Changelog](https://github.com/slackapi/python-slack-sdk/blob/main/docs-v2/changelog.html) - [Commits](https://github.com/slackapi/python-slack-sdk/compare/v3.27.0...v3.27.1) --- updated-dependencies: - dependency-name: slack-sdk dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/base/requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 87cfe35fb798..26f39ac48a40 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -1220,9 +1220,9 @@ six==1.16.0 \ # pyu2f # sphinxcontrib-httpdomain # thrift -slack-sdk==3.27.0 \ - --hash=sha256:811472ce598db855ab3c02f098fa430323ccb253cfe17ba20c7b05ab206d984d \ - --hash=sha256:a901c68cb5547d5459cdefd81343d116db56d65f6b33f4081ddf1cdd243bf07e +slack-sdk==3.27.1 \ + --hash=sha256:85d86b34d807c26c8bb33c1569ec0985876f06ae4a2692afba765b7a5490d28c \ + --hash=sha256:c108e509160cf1324c5c8b1f47ca52fb5e287021b8caf9f4ec78ad737ab7b1d9 # via -r requirements.in smmap==5.0.1 \ --hash=sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62 \ From 50ed6189e7e4f588c45e500e511c5d22eaa19380 Mon Sep 17 00:00:00 2001 From: Kuo-Chung Hsu Date: Wed, 28 Feb 2024 13:03:05 -0800 Subject: [PATCH 116/151] use createScope to guruantee longer lifetime than shadow writer (#32595) Signed-off-by: kuochunghsu --- .../network/thrift_proxy/router/router.h | 17 ++++++++++------- .../network/thrift_proxy/router/router_impl.cc | 2 +- .../thrift_proxy/router/shadow_writer_impl.cc | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/source/extensions/filters/network/thrift_proxy/router/router.h b/source/extensions/filters/network/thrift_proxy/router/router.h index 0ffff508c81f..851f04ccc06a 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router.h +++ b/source/extensions/filters/network/thrift_proxy/router/router.h @@ -129,9 +129,10 @@ class RouterStats { public: RouterStats(const std::string& stat_prefix, Stats::Scope& scope, const LocalInfo::LocalInfo& local_info) - : named_(RouterNamedStats::generateStats(stat_prefix, scope)), - stat_name_set_(scope.symbolTable().makeSet("thrift_proxy")), - symbol_table_(scope.symbolTable()), + : stats_scope_(scope.createScope("")), + named_(RouterNamedStats::generateStats(stat_prefix, *stats_scope_)), + stat_name_set_(stats_scope_->symbolTable().makeSet("thrift_proxy")), + symbol_table_(stats_scope_->symbolTable()), upstream_rq_call_(stat_name_set_->add("thrift.upstream_rq_call")), upstream_rq_oneway_(stat_name_set_->add("thrift.upstream_rq_oneway")), upstream_rq_invalid_type_(stat_name_set_->add("thrift.upstream_rq_invalid_type")), @@ -340,7 +341,7 @@ class RouterStats { Stats::Histogram::Unit::Milliseconds, value); } - const RouterNamedStats named_; + const RouterNamedStats& routerStats() const { return named_; } private: void incClusterScopeCounter(const Upstream::ClusterInfo& cluster, @@ -385,6 +386,8 @@ class RouterStats { return symbol_table_.join({zone_, local_zone_name_, upstream_zone_name, stat_name}); } + Stats::ScopeSharedPtr stats_scope_; + const RouterNamedStats named_; Stats::StatNameSetPtr stat_name_set_; Stats::SymbolTable& symbol_table_; const Stats::StatName upstream_rq_call_; @@ -506,7 +509,7 @@ class RequestOwner : public ProtocolConverter, public Logger::LoggablemaintenanceMode()) { - stats().named_.upstream_rq_maintenance_mode_.inc(); + stats().routerStats().upstream_rq_maintenance_mode_.inc(); if (metadata->messageType() == MessageType::Call) { stats().incResponseLocalException(*cluster_); } @@ -551,7 +554,7 @@ class RequestOwner : public ProtocolConverter, public Logger::LoggabletcpConnPool(Upstream::ResourcePriority::Default, lb_context); if (!conn_pool_data) { - stats().named_.no_healthy_upstream_.inc(); + stats().routerStats().no_healthy_upstream_.inc(); if (metadata->messageType() == MessageType::Call) { stats().incResponseLocalException(*cluster_); } 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 4075376459c8..445a23690b39 100644 --- a/source/extensions/filters/network/thrift_proxy/router/router_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/router/router_impl.cc @@ -278,7 +278,7 @@ FilterStatus Router::messageBegin(MessageMetadataSharedPtr metadata) { route_ = callbacks_->route(); if (!route_) { ENVOY_STREAM_LOG(debug, "no route match for method '{}'", *callbacks_, metadata->methodName()); - stats().named_.route_missing_.inc(); + stats().routerStats().route_missing_.inc(); callbacks_->sendLocalReply( AppException(AppExceptionType::UnknownMethod, fmt::format("no route for method '{}'", metadata->methodName())), diff --git a/source/extensions/filters/network/thrift_proxy/router/shadow_writer_impl.cc b/source/extensions/filters/network/thrift_proxy/router/shadow_writer_impl.cc index 7c27c675d55f..2ab6544b1a0a 100644 --- a/source/extensions/filters/network/thrift_proxy/router/shadow_writer_impl.cc +++ b/source/extensions/filters/network/thrift_proxy/router/shadow_writer_impl.cc @@ -22,7 +22,7 @@ OptRef ShadowWriterImpl::submit(const std::string& cluster_n original_transport, original_protocol); const bool created = shadow_router->createUpstreamRequest(); if (!created || !tls_.get().has_value()) { - stats_.named_.shadow_request_submit_failure_.inc(); + stats_.routerStats().shadow_request_submit_failure_.inc(); return absl::nullopt; } From 6f7645516801be3a8259aefd2faad412620f3c1c Mon Sep 17 00:00:00 2001 From: Keith Smiley Date: Wed, 28 Feb 2024 15:20:23 -0800 Subject: [PATCH 117/151] bazel: fix opentelemetry-cpp warning (#32624) Fixes https://github.com/envoyproxy/envoy/issues/32591 Signed-off-by: Keith Smiley --- bazel/io_opentelemetry_cpp.patch | 13 +++++++++++++ bazel/repositories.bzl | 6 +++++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 bazel/io_opentelemetry_cpp.patch diff --git a/bazel/io_opentelemetry_cpp.patch b/bazel/io_opentelemetry_cpp.patch new file mode 100644 index 000000000000..6eb4cfe2a9a5 --- /dev/null +++ b/bazel/io_opentelemetry_cpp.patch @@ -0,0 +1,13 @@ +# TODO: Remove once https://github.com/open-telemetry/opentelemetry-cpp/issues/2556 is merged + +--- a/api/include/opentelemetry/trace/span_context.h ++++ b/api/include/opentelemetry/trace/span_context.h +@@ -30,7 +30,7 @@ class SpanContext final + SpanContext(bool sampled_flag, bool is_remote) noexcept + : trace_id_(), + span_id_(), +- trace_flags_(trace::TraceFlags((uint8_t)sampled_flag)), ++ trace_flags_(trace::TraceFlags(static_cast(sampled_flag))), + is_remote_(is_remote), + trace_state_(TraceState::GetDefault()) + {} diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index b5ddba3eeacf..025bf5efdb87 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -777,7 +777,11 @@ def _io_opentracing_cpp(): ) def _io_opentelemetry_api_cpp(): - external_http_archive("io_opentelemetry_cpp") + external_http_archive( + name = "io_opentelemetry_cpp", + patch_args = ["-p1"], + patches = ["@envoy//bazel:io_opentelemetry_cpp.patch"], + ) native.bind( name = "opentelemetry_api", actual = "@io_opentelemetry_cpp//api:api", From eb51ea0ff70a40cd4bbafc6f03333e38c62a2ca3 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Wed, 28 Feb 2024 16:21:38 -0800 Subject: [PATCH 118/151] Fix circular dependency between matchers and stats, grpc (#32587) Signed-off-by: Greg Greenway --- .../filters/network/source/BUILD | 1 + source/common/access_log/access_log_impl.cc | 1 + source/common/common/BUILD | 1 + source/common/config/BUILD | 18 ++++++++---- source/common/config/stats_utility.cc | 17 +++++++++++ source/common/config/stats_utility.h | 28 +++++++++++++++++++ source/common/config/utility.cc | 9 ------ source/common/config/utility.h | 17 ----------- source/common/listener_manager/BUILD | 2 ++ source/common/listener_manager/lds_api.cc | 1 + source/common/router/config_impl.cc | 1 + source/common/router/vhds.cc | 1 + source/common/secret/sds_api.cc | 1 + source/common/tcp_proxy/BUILD | 1 + source/extensions/clusters/eds/eds.cc | 1 + .../common/dynamic_forward_proxy/BUILD | 1 + .../extensions/filters/http/basic_auth/BUILD | 1 + .../filters/network/dubbo_proxy/router/BUILD | 2 ++ .../filters/header_to_metadata/BUILD | 3 ++ .../filters/payload_to_metadata/BUILD | 2 ++ .../network/dns_resolver/getaddrinfo/BUILD | 1 + .../extensions/stat_sinks/common/statsd/BUILD | 2 ++ source/extensions/tracers/datadog/BUILD | 2 ++ .../tls/cert_validator/BUILD | 1 + source/server/BUILD | 2 ++ source/server/config_validation/server.cc | 3 +- source/server/server.cc | 4 ++- test/common/config/BUILD | 1 + test/common/config/utility_test.cc | 5 ++-- 29 files changed, 95 insertions(+), 35 deletions(-) create mode 100644 source/common/config/stats_utility.cc create mode 100644 source/common/config/stats_utility.h diff --git a/contrib/generic_proxy/filters/network/source/BUILD b/contrib/generic_proxy/filters/network/source/BUILD index 4df7bc7b6f28..67129461e1a5 100644 --- a/contrib/generic_proxy/filters/network/source/BUILD +++ b/contrib/generic_proxy/filters/network/source/BUILD @@ -74,6 +74,7 @@ envoy_cc_library( "//source/common/common:matchers_lib", "//source/common/config:metadata_lib", "//source/common/config:utility_lib", + "//source/common/http:header_utility_lib", "//source/common/matcher:matcher_lib", "@envoy_api//contrib/envoy/extensions/filters/network/generic_proxy/action/v3:pkg_cc_proto", "@envoy_api//contrib/envoy/extensions/filters/network/generic_proxy/v3:pkg_cc_proto", diff --git a/source/common/access_log/access_log_impl.cc b/source/common/access_log/access_log_impl.cc index fd72e00b1a74..c13213d9cbbe 100644 --- a/source/common/access_log/access_log_impl.cc +++ b/source/common/access_log/access_log_impl.cc @@ -15,6 +15,7 @@ #include "source/common/common/utility.h" #include "source/common/config/metadata.h" #include "source/common/config/utility.h" +#include "source/common/grpc/common.h" #include "source/common/http/header_map_impl.h" #include "source/common/http/header_utility.h" #include "source/common/http/headers.h" diff --git a/source/common/common/BUILD b/source/common/common/BUILD index aa0edac5aa61..6d630ec2a028 100644 --- a/source/common/common/BUILD +++ b/source/common/common/BUILD @@ -319,6 +319,7 @@ envoy_cc_library( "//envoy/common:matchers_interface", "//source/common/common:regex_lib", "//source/common/config:metadata_lib", + "//source/common/config:utility_lib", "//source/common/http:path_utility_lib", "//source/common/protobuf", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", diff --git a/source/common/config/BUILD b/source/common/config/BUILD index dffb0c2b7f3f..2965f617062a 100644 --- a/source/common/config/BUILD +++ b/source/common/config/BUILD @@ -226,15 +226,10 @@ envoy_cc_library( "//source/common/common:backoff_lib", "//source/common/common:hash_lib", "//source/common/common:hex_lib", - "//source/common/grpc:common_lib", "//source/common/protobuf", "//source/common/protobuf:utility_lib", "//source/common/runtime:runtime_features_lib", "//source/common/singleton:const_singleton", - "//source/common/stats:histogram_lib", - "//source/common/stats:stats_lib", - "//source/common/stats:stats_matcher_lib", - "//source/common/stats:tag_producer_lib", "//source/common/version:api_version_lib", "@com_github_cncf_xds//udpa/type/v1:pkg_cc_proto", "@com_github_cncf_xds//xds/type/v3:pkg_cc_proto", @@ -245,6 +240,19 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "stats_utility_lib", + srcs = ["stats_utility.cc"], + hdrs = ["stats_utility.h"], + deps = [ + "//source/common/stats:histogram_lib", + "//source/common/stats:stats_lib", + "//source/common/stats:stats_matcher_lib", + "//source/common/stats:tag_producer_lib", + "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", + ], +) + envoy_cc_library( name = "subscription_base_interface", hdrs = ["subscription_base.h"], diff --git a/source/common/config/stats_utility.cc b/source/common/config/stats_utility.cc new file mode 100644 index 000000000000..eed7f4c98b40 --- /dev/null +++ b/source/common/config/stats_utility.cc @@ -0,0 +1,17 @@ +#include "source/common/config/stats_utility.h" + +#include "source/common/stats/histogram_impl.h" +#include "source/common/stats/stats_matcher_impl.h" +#include "source/common/stats/tag_producer_impl.h" + +namespace Envoy { +namespace Config { + +Stats::TagProducerPtr +StatsUtility::createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap, + const Stats::TagVector& cli_tags) { + return std::make_unique(bootstrap.stats_config(), cli_tags); +} + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/stats_utility.h b/source/common/config/stats_utility.h new file mode 100644 index 000000000000..409bd1ddc0c8 --- /dev/null +++ b/source/common/config/stats_utility.h @@ -0,0 +1,28 @@ +#pragma once + +#include "envoy/config/bootstrap/v3/bootstrap.pb.h" +#include "envoy/stats/histogram.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" +#include "envoy/stats/stats_matcher.h" +#include "envoy/stats/tag_producer.h" + +namespace Envoy { +namespace Config { + +class StatsUtility { +public: + /** + * Create TagProducer instance. Check all tag names for conflicts to avoid + * unexpected tag name overwriting. + * @param bootstrap bootstrap proto. + * @param cli_tags tags that are provided by the cli + * @throws EnvoyException when the conflict of tag names is found. + */ + static Stats::TagProducerPtr + createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap, + const Stats::TagVector& cli_tags); +}; + +} // namespace Config +} // namespace Envoy diff --git a/source/common/config/utility.cc b/source/common/config/utility.cc index 7728019c228e..83556c148054 100644 --- a/source/common/config/utility.cc +++ b/source/common/config/utility.cc @@ -11,9 +11,6 @@ #include "source/common/common/assert.h" #include "source/common/protobuf/utility.h" -#include "source/common/stats/histogram_impl.h" -#include "source/common/stats/stats_matcher_impl.h" -#include "source/common/stats/tag_producer_impl.h" namespace Envoy { namespace Config { @@ -204,12 +201,6 @@ Utility::parseRateLimitSettings(const envoy::config::core::v3::ApiConfigSource& return rate_limit_settings; } -Stats::TagProducerPtr -Utility::createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap, - const Stats::TagVector& cli_tags) { - return std::make_unique(bootstrap.stats_config(), cli_tags); -} - absl::StatusOr Utility::factoryForGrpcApiConfigSource( Grpc::AsyncClientManager& async_client_manager, const envoy::config::core::v3::ApiConfigSource& api_config_source, Stats::Scope& scope, diff --git a/source/common/config/utility.h b/source/common/config/utility.h index 27971e06ac85..ede6515d3a07 100644 --- a/source/common/config/utility.h +++ b/source/common/config/utility.h @@ -12,11 +12,6 @@ #include "envoy/local_info/local_info.h" #include "envoy/registry/registry.h" #include "envoy/server/filter_config.h" -#include "envoy/stats/histogram.h" -#include "envoy/stats/scope.h" -#include "envoy/stats/stats_macros.h" -#include "envoy/stats/stats_matcher.h" -#include "envoy/stats/tag_producer.h" #include "envoy/upstream/cluster_manager.h" #include "source/common/common/assert.h" @@ -24,7 +19,6 @@ #include "source/common/common/hash.h" #include "source/common/common/hex.h" #include "source/common/common/utility.h" -#include "source/common/grpc/common.h" #include "source/common/protobuf/protobuf.h" #include "source/common/protobuf/utility.h" #include "source/common/runtime/runtime_features.h" @@ -392,17 +386,6 @@ class Utility { */ static std::string truncateGrpcStatusMessage(absl::string_view error_message); - /** - * Create TagProducer instance. Check all tag names for conflicts to avoid - * unexpected tag name overwriting. - * @param bootstrap bootstrap proto. - * @param cli_tags tags that are provided by the cli - * @throws EnvoyException when the conflict of tag names is found. - */ - static Stats::TagProducerPtr - createTagProducer(const envoy::config::bootstrap::v3::Bootstrap& bootstrap, - const Stats::TagVector& cli_tags); - /** * Obtain gRPC async client factory from a envoy::config::core::v3::ApiConfigSource. * @param async_client_manager gRPC async client manager. diff --git a/source/common/listener_manager/BUILD b/source/common/listener_manager/BUILD index 1b8e49427033..e567cb0ac856 100644 --- a/source/common/listener_manager/BUILD +++ b/source/common/listener_manager/BUILD @@ -137,8 +137,10 @@ envoy_cc_library( "//source/common/config:api_version_lib", "//source/common/config:subscription_base_interface", "//source/common/config:utility_lib", + "//source/common/grpc:common_lib", "//source/common/init:target_lib", "//source/common/protobuf:utility_lib", + "@com_google_absl//absl/container:node_hash_set", "@envoy_api//envoy/admin/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/config/listener/v3:pkg_cc_proto", diff --git a/source/common/listener_manager/lds_api.cc b/source/common/listener_manager/lds_api.cc index ee669d306e5d..052872846da6 100644 --- a/source/common/listener_manager/lds_api.cc +++ b/source/common/listener_manager/lds_api.cc @@ -12,6 +12,7 @@ #include "source/common/common/cleanup.h" #include "source/common/config/api_version.h" #include "source/common/config/utility.h" +#include "source/common/grpc/common.h" #include "source/common/protobuf/utility.h" #include "absl/container/node_hash_set.h" diff --git a/source/common/router/config_impl.cc b/source/common/router/config_impl.cc index ecf2c3639d90..19f1d7367f3f 100644 --- a/source/common/router/config_impl.cc +++ b/source/common/router/config_impl.cc @@ -32,6 +32,7 @@ #include "source/common/config/metadata.h" #include "source/common/config/utility.h" #include "source/common/config/well_known_names.h" +#include "source/common/grpc/common.h" #include "source/common/http/header_utility.h" #include "source/common/http/headers.h" #include "source/common/http/matching/data_impl.h" diff --git a/source/common/router/vhds.cc b/source/common/router/vhds.cc index a805597b0870..823ee0b0f86d 100644 --- a/source/common/router/vhds.cc +++ b/source/common/router/vhds.cc @@ -13,6 +13,7 @@ #include "source/common/common/fmt.h" #include "source/common/config/api_version.h" #include "source/common/config/utility.h" +#include "source/common/grpc/common.h" #include "source/common/protobuf/utility.h" #include "source/common/router/config_impl.h" diff --git a/source/common/secret/sds_api.cc b/source/common/secret/sds_api.cc index 5492fb92de28..344da3ff5833 100644 --- a/source/common/secret/sds_api.cc +++ b/source/common/secret/sds_api.cc @@ -6,6 +6,7 @@ #include "source/common/common/assert.h" #include "source/common/config/api_version.h" +#include "source/common/grpc/common.h" #include "source/common/protobuf/utility.h" namespace Envoy { diff --git a/source/common/tcp_proxy/BUILD b/source/common/tcp_proxy/BUILD index 4d1309a5a4b7..3e1f10dba933 100644 --- a/source/common/tcp_proxy/BUILD +++ b/source/common/tcp_proxy/BUILD @@ -62,6 +62,7 @@ envoy_cc_library( "//source/common/common:enum_to_int", "//source/common/common:macros", "//source/common/common:minimal_logger_lib", + "//source/common/config:well_known_names", "//source/common/formatter:substitution_format_string_lib", "//source/common/http:codec_client_lib", "//source/common/network:application_protocol_lib", diff --git a/source/extensions/clusters/eds/eds.cc b/source/extensions/clusters/eds/eds.cc index 76d0b258514f..1b60873be93f 100644 --- a/source/extensions/clusters/eds/eds.cc +++ b/source/extensions/clusters/eds/eds.cc @@ -9,6 +9,7 @@ #include "source/common/common/utility.h" #include "source/common/config/api_version.h" #include "source/common/config/decoded_resource_impl.h" +#include "source/common/grpc/common.h" namespace Envoy { namespace Upstream { diff --git a/source/extensions/common/dynamic_forward_proxy/BUILD b/source/extensions/common/dynamic_forward_proxy/BUILD index bb3535dab9ec..f8b68b72daad 100644 --- a/source/extensions/common/dynamic_forward_proxy/BUILD +++ b/source/extensions/common/dynamic_forward_proxy/BUILD @@ -18,6 +18,7 @@ envoy_cc_library( "//envoy/singleton:manager_interface", "//envoy/thread_local:thread_local_interface", "//envoy/upstream:resource_manager_interface", + "//source/common/http:header_utility_lib", "@envoy_api//envoy/extensions/common/dynamic_forward_proxy/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/filters/http/basic_auth/BUILD b/source/extensions/filters/http/basic_auth/BUILD index f610d4fee905..7f47e0226048 100644 --- a/source/extensions/filters/http/basic_auth/BUILD +++ b/source/extensions/filters/http/basic_auth/BUILD @@ -19,6 +19,7 @@ envoy_cc_library( "//source/common/common:base64_lib", "//source/common/config:utility_lib", "//source/common/http:header_map_lib", + "//source/common/http:header_utility_lib", "//source/common/protobuf:utility_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", ], diff --git a/source/extensions/filters/network/dubbo_proxy/router/BUILD b/source/extensions/filters/network/dubbo_proxy/router/BUILD index f75fc1d75d62..0901f4a09516 100644 --- a/source/extensions/filters/network/dubbo_proxy/router/BUILD +++ b/source/extensions/filters/network/dubbo_proxy/router/BUILD @@ -26,6 +26,7 @@ envoy_cc_library( "//envoy/router:router_interface", "//source/common/common:logger_lib", "//source/common/common:matchers_lib", + "//source/common/config:well_known_names", "//source/common/http:header_utility_lib", "//source/common/protobuf:utility_lib", "//source/common/router:metadatamatchcriteria_lib", @@ -63,6 +64,7 @@ envoy_cc_library( "//envoy/upstream:load_balancer_interface", "//envoy/upstream:thread_local_cluster_interface", "//source/common/common:logger_lib", + "//source/common/config:well_known_names", "//source/common/http:header_utility_lib", "//source/common/router:metadatamatchcriteria_lib", "//source/common/upstream:load_balancer_lib", diff --git a/source/extensions/filters/network/thrift_proxy/filters/header_to_metadata/BUILD b/source/extensions/filters/network/thrift_proxy/filters/header_to_metadata/BUILD index 810944a4ac04..76be28f0b1fd 100644 --- a/source/extensions/filters/network/thrift_proxy/filters/header_to_metadata/BUILD +++ b/source/extensions/filters/network/thrift_proxy/filters/header_to_metadata/BUILD @@ -27,6 +27,9 @@ envoy_cc_library( hdrs = ["header_to_metadata_filter.h"], deps = [ "//envoy/server:filter_config_interface", + "//source/common/common:base64_lib", + "//source/common/common:matchers_lib", + "//source/common/network:utility_lib", "//source/extensions/filters/network/thrift_proxy/filters:pass_through_filter_lib", "@envoy_api//envoy/extensions/filters/network/thrift_proxy/filters/header_to_metadata/v3:pkg_cc_proto", ], diff --git a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/BUILD b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/BUILD index cc6eeb8630a0..8597af0e12e7 100644 --- a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/BUILD +++ b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/BUILD @@ -27,6 +27,8 @@ envoy_cc_library( hdrs = ["payload_to_metadata_filter.h"], deps = [ "//envoy/server:filter_config_interface", + "//source/common/common:matchers_lib", + "//source/common/network:utility_lib", "//source/extensions/filters/network/thrift_proxy:auto_protocol_lib", "//source/extensions/filters/network/thrift_proxy:auto_transport_lib", "//source/extensions/filters/network/thrift_proxy:decoder_lib", diff --git a/source/extensions/network/dns_resolver/getaddrinfo/BUILD b/source/extensions/network/dns_resolver/getaddrinfo/BUILD index e2b26df7941c..05b119daa59c 100644 --- a/source/extensions/network/dns_resolver/getaddrinfo/BUILD +++ b/source/extensions/network/dns_resolver/getaddrinfo/BUILD @@ -15,6 +15,7 @@ envoy_cc_extension( deps = [ "//envoy/network:dns_resolver_interface", "//envoy/registry", + "//source/common/api:os_sys_calls_lib", "@envoy_api//envoy/extensions/network/dns_resolver/getaddrinfo/v3:pkg_cc_proto", ], ) diff --git a/source/extensions/stat_sinks/common/statsd/BUILD b/source/extensions/stat_sinks/common/statsd/BUILD index 2d74e42869e9..54fe8b9ac588 100644 --- a/source/extensions/stat_sinks/common/statsd/BUILD +++ b/source/extensions/stat_sinks/common/statsd/BUILD @@ -30,5 +30,7 @@ envoy_cc_library( "//source/common/common:utility_lib", "//source/common/config:utility_lib", "//source/common/network:address_lib", + "//source/common/network:default_socket_interface_lib", + "//source/common/network:utility_lib", ], ) diff --git a/source/extensions/tracers/datadog/BUILD b/source/extensions/tracers/datadog/BUILD index da0862389706..cd7327a67d09 100644 --- a/source/extensions/tracers/datadog/BUILD +++ b/source/extensions/tracers/datadog/BUILD @@ -43,6 +43,8 @@ envoy_cc_library( deps = [ "//source/common/config:utility_lib", "//source/common/http:async_client_utility_lib", + "//source/common/http:message_lib", + "//source/common/http:utility_lib", "//source/common/tracing:common_values_lib", "//source/common/tracing:null_span_lib", "//source/common/tracing:trace_context_lib", diff --git a/source/extensions/transport_sockets/tls/cert_validator/BUILD b/source/extensions/transport_sockets/tls/cert_validator/BUILD index c79416930672..5abc18bc2595 100644 --- a/source/extensions/transport_sockets/tls/cert_validator/BUILD +++ b/source/extensions/transport_sockets/tls/cert_validator/BUILD @@ -36,6 +36,7 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/common:base64_lib", "//source/common/common:hex_lib", + "//source/common/common:matchers_lib", "//source/common/common:minimal_logger_lib", "//source/common/common:utility_lib", "//source/common/config:utility_lib", diff --git a/source/server/BUILD b/source/server/BUILD index f0d63ba47174..7ee402750e1f 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -313,6 +313,7 @@ envoy_cc_library( "//source/common/event:scaled_range_timer_manager_lib", "//source/common/stats:symbol_table_lib", "//source/server:resource_monitor_config_lib", + "@com_google_absl//absl/container:node_hash_set", "@envoy_api//envoy/config/overload/v3:pkg_cc_proto", ], ) @@ -440,6 +441,7 @@ envoy_cc_library( "//source/common/common:mutex_tracer_lib", "//source/common/common:perf_tracing_lib", "//source/common/common:utility_lib", + "//source/common/config:stats_utility_lib", "//source/common/config:utility_lib", "//source/common/config:xds_resource_lib", "//source/common/grpc:async_client_manager_lib", diff --git a/source/server/config_validation/server.cc b/source/server/config_validation/server.cc index a8685dbd28eb..054cb3f11b24 100644 --- a/source/server/config_validation/server.cc +++ b/source/server/config_validation/server.cc @@ -5,6 +5,7 @@ #include "envoy/config/bootstrap/v3/bootstrap.pb.h" #include "source/common/common/utility.h" +#include "source/common/config/stats_utility.h" #include "source/common/config/utility.h" #include "source/common/config/well_known_names.h" #include "source/common/event/real_time_system.h" @@ -100,7 +101,7 @@ void ValidationInstance::initialize(const Options& options, Regex::EnginePtr regex_engine = createRegexEngine( bootstrap_, messageValidationContext().staticValidationVisitor(), serverFactoryContext()); - Config::Utility::createTagProducer(bootstrap_, options_.statsTags()); + Config::StatsUtility::createTagProducer(bootstrap_, options_.statsTags()); if (!bootstrap_.node().user_agent_build_version().has_version()) { *bootstrap_.mutable_node()->mutable_user_agent_build_version() = VersionInfo::buildVersion(); } diff --git a/source/server/server.cc b/source/server/server.cc index 205823a43f6c..376ebb48e829 100644 --- a/source/server/server.cc +++ b/source/server/server.cc @@ -29,6 +29,7 @@ #include "source/common/common/enum_to_int.h" #include "source/common/common/mutex_tracer_impl.h" #include "source/common/common/utility.h" +#include "source/common/config/stats_utility.h" #include "source/common/config/utility.h" #include "source/common/config/well_known_names.h" #include "source/common/config/xds_resource.h" @@ -500,7 +501,8 @@ void InstanceBase::initializeOrThrow(Network::Address::InstanceConstSharedPtr lo // Needs to happen as early as possible in the instantiation to preempt the objects that require // stats. - stats_store_.setTagProducer(Config::Utility::createTagProducer(bootstrap_, options_.statsTags())); + stats_store_.setTagProducer( + Config::StatsUtility::createTagProducer(bootstrap_, options_.statsTags())); stats_store_.setStatsMatcher(std::make_unique( bootstrap_.stats_config(), stats_store_.symbolTable())); stats_store_.setHistogramSettings( diff --git a/test/common/config/BUILD b/test/common/config/BUILD index 08fb0d1208eb..2aef53b1499b 100644 --- a/test/common/config/BUILD +++ b/test/common/config/BUILD @@ -126,6 +126,7 @@ envoy_cc_test( external_deps = ["abseil_optional"], deps = [ "//source/common/config:api_version_lib", + "//source/common/config:stats_utility_lib", "//source/common/config:utility_lib", "//source/common/config:well_known_names", "//source/common/stats:stats_lib", diff --git a/test/common/config/utility_test.cc b/test/common/config/utility_test.cc index 767c0e7db1a9..2d313ca1f981 100644 --- a/test/common/config/utility_test.cc +++ b/test/common/config/utility_test.cc @@ -8,6 +8,7 @@ #include "source/common/common/fmt.h" #include "source/common/config/api_version.h" +#include "source/common/config/stats_utility.h" #include "source/common/config/utility.h" #include "source/common/config/well_known_names.h" #include "source/common/protobuf/protobuf.h" @@ -57,7 +58,7 @@ TEST(UtilityTest, ConfigSourceInitFetchTimeout) { TEST(UtilityTest, createTagProducer) { envoy::config::bootstrap::v3::Bootstrap bootstrap; - auto producer = Utility::createTagProducer(bootstrap, {}); + auto producer = StatsUtility::createTagProducer(bootstrap, {}); ASSERT_TRUE(producer != nullptr); Stats::TagVector tags; auto extracted_name = producer->produceTags("http.config_test.rq_total", tags); @@ -67,7 +68,7 @@ TEST(UtilityTest, createTagProducer) { TEST(UtilityTest, createTagProducerWithDefaultTgs) { envoy::config::bootstrap::v3::Bootstrap bootstrap; - auto producer = Utility::createTagProducer(bootstrap, {{"foo", "bar"}}); + auto producer = StatsUtility::createTagProducer(bootstrap, {{"foo", "bar"}}); ASSERT_TRUE(producer != nullptr); Stats::TagVector tags; auto extracted_name = producer->produceTags("http.config_test.rq_total", tags); From dee4ff7519fb27da93387941a3ff0cffe0abb993 Mon Sep 17 00:00:00 2001 From: botengyao Date: Wed, 28 Feb 2024 23:42:52 -0500 Subject: [PATCH 119/151] hcm: codec creation load shed point (#32528) --------- Signed-off-by: Boteng Yao --- changelogs/current.yaml | 4 ++ .../overload_manager/overload_manager.rst | 5 ++ envoy/server/overload/load_shed_point.h | 2 + source/common/http/conn_manager_impl.cc | 11 ++++ source/common/http/conn_manager_impl.h | 1 + test/common/http/conn_manager_impl_test_2.cc | 56 ++++++++++++++++++- test/integration/overload_integration_test.cc | 35 ++++++++++++ 7 files changed, 113 insertions(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 3ff9eeab0609..2815679340e8 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -234,6 +234,10 @@ new_features: added an option to dynamically set a per downstream connection idle timeout period object under the key ``envoy.tcp_proxy.per_connection_idle_timeout_ms``. If this filter state value exists, it will override the idle timeout specified in the filter configuration and the default idle timeout. +- area: load shed point + change: | + Added load shed point ``envoy.load_shed_points.hcm_ondata_creating_codec`` that closes connections before creating codec if + Envoy is under pressure, typically memory. - area: overload change: | added a :ref:`configuration option diff --git a/docs/root/configuration/operations/overload_manager/overload_manager.rst b/docs/root/configuration/operations/overload_manager/overload_manager.rst index 7d45a1bbd758..dcc522e31d12 100644 --- a/docs/root/configuration/operations/overload_manager/overload_manager.rst +++ b/docs/root/configuration/operations/overload_manager/overload_manager.rst @@ -165,6 +165,11 @@ The following core load shed points are supported: - Envoy will send a ``GOAWAY`` while processing HTTP2 requests at the codec level which will eventually drain the HTTP/2 connection. + * - envoy.load_shed_points.hcm_ondata_creating_codec + - Envoy will close the connections before creating codec if Envoy is under + pressure, typically memory. This happens once geting data from the + connection. + .. _config_overload_manager_reducing_timeouts: Reducing timeouts diff --git a/envoy/server/overload/load_shed_point.h b/envoy/server/overload/load_shed_point.h index 50018a2906fc..076eff36b708 100644 --- a/envoy/server/overload/load_shed_point.h +++ b/envoy/server/overload/load_shed_point.h @@ -26,6 +26,8 @@ class LoadShedPointNameValues { // which will eventually drain the HTTP/2 connection. const std::string H2ServerGoAwayOnDispatch = "envoy.load_shed_points.http2_server_go_away_on_dispatch"; + + const std::string HcmCodecCreation = "envoy.load_shed_points.hcm_ondata_creating_codec"; }; using LoadShedPointName = ConstSingleton; diff --git a/source/common/http/conn_manager_impl.cc b/source/common/http/conn_manager_impl.cc index e486973bec90..4c1420575eb5 100644 --- a/source/common/http/conn_manager_impl.cc +++ b/source/common/http/conn_manager_impl.cc @@ -116,6 +116,8 @@ ConnectionManagerImpl::ConnectionManagerImpl(ConnectionManagerConfig& config, overload_state_(overload_manager.getThreadLocalOverloadState()), accept_new_http_stream_( overload_manager.getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)), + hcm_ondata_creating_codec_( + overload_manager.getLoadShedPoint(Server::LoadShedPointName::get().HcmCodecCreation)), overload_stop_accepting_requests_ref_( overload_state_.getState(Server::OverloadActionNames::get().StopAcceptingRequests)), overload_disable_keepalive_ref_( @@ -132,6 +134,9 @@ ConnectionManagerImpl::ConnectionManagerImpl(ConnectionManagerConfig& config, trace, accept_new_http_stream_ == nullptr, "LoadShedPoint envoy.load_shed_points.http_connection_manager_decode_headers is not " "found. Is it configured?"); + ENVOY_LOG_ONCE_IF(trace, hcm_ondata_creating_codec_ == nullptr, + "LoadShedPoint envoy.load_shed_points.hcm_ondata_creating_codec is not found. " + "Is it configured?"); } const ResponseHeaderMap& ConnectionManagerImpl::continueHeader() { @@ -479,6 +484,12 @@ void ConnectionManagerImpl::createCodec(Buffer::Instance& data) { Network::FilterStatus ConnectionManagerImpl::onData(Buffer::Instance& data, bool) { requests_during_dispatch_count_ = 0; if (!codec_) { + // Close connections if Envoy is under pressure, typically memory, before creating codec. + if (hcm_ondata_creating_codec_ != nullptr && hcm_ondata_creating_codec_->shouldShedLoad()) { + stats_.named_.downstream_rq_overload_close_.inc(); + handleCodecOverloadError("onData codec creation overload"); + return Network::FilterStatus::StopIteration; + } // Http3 codec should have been instantiated by now. createCodec(data); } diff --git a/source/common/http/conn_manager_impl.h b/source/common/http/conn_manager_impl.h index aac42af4b34a..11151b157bf5 100644 --- a/source/common/http/conn_manager_impl.h +++ b/source/common/http/conn_manager_impl.h @@ -642,6 +642,7 @@ class ConnectionManagerImpl : Logger::Loggable, Server::OverloadManager& overload_manager_; Server::ThreadLocalOverloadState& overload_state_; Server::LoadShedPoint* accept_new_http_stream_{nullptr}; + Server::LoadShedPoint* hcm_ondata_creating_codec_{nullptr}; // References into the overload manager thread local state map. Using these lets us avoid a // map lookup in the hot path of processing each request. const Server::OverloadActionState& overload_stop_accepting_requests_ref_; diff --git a/test/common/http/conn_manager_impl_test_2.cc b/test/common/http/conn_manager_impl_test_2.cc index 287507afa2a1..83b3971523f7 100644 --- a/test/common/http/conn_manager_impl_test_2.cc +++ b/test/common/http/conn_manager_impl_test_2.cc @@ -2489,10 +2489,64 @@ TEST_F(HttpConnectionManagerImplTest, DisableHttp2KeepAliveWhenOverloaded) { EXPECT_EQ(1, stats_.named_.downstream_cx_overload_disable_keepalive_.value()); } +TEST_F(HttpConnectionManagerImplTest, CodecCreationLoadShedPointCanCloseConnection) { + Server::MockLoadShedPoint close_connection_creating_codec_point; + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmCodecCreation)) + .WillOnce(Return(&close_connection_creating_codec_point)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)) + .WillOnce(Return(nullptr)); + + setup(false, ""); + + EXPECT_CALL(close_connection_creating_codec_point, shouldShedLoad()).WillOnce(Return(true)); + EXPECT_CALL(filter_callbacks_.connection_, close(_, _)); + + Buffer::OwnedImpl fake_input("hello"); + conn_manager_->onData(fake_input, false); + + delete codec_; + EXPECT_EQ(1U, stats_.named_.downstream_rq_overload_close_.value()); + EXPECT_TRUE(filter_callbacks_.connection().streamInfo().hasResponseFlag( + StreamInfo::CoreResponseFlag::OverloadManager)); +} + +TEST_F(HttpConnectionManagerImplTest, CodecCreationLoadShedPointBypasscheck) { + Server::MockLoadShedPoint close_connection_creating_codec_point; + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmCodecCreation)) + .WillOnce(Return(&close_connection_creating_codec_point)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)) + .WillOnce(Return(nullptr)); + + setup(false, ""); + + EXPECT_CALL(close_connection_creating_codec_point, shouldShedLoad()).WillOnce(Return(false)); + + EXPECT_CALL(*codec_, dispatch(_)).WillOnce(Invoke([&](Buffer::Instance& data) -> Http::Status { + conn_manager_->newStream(response_encoder_); + data.drain(2); + return Http::okStatus(); + })); + + Buffer::OwnedImpl fake_input("12"); + conn_manager_->onData(fake_input, false); + conn_manager_->onEvent(Network::ConnectionEvent::RemoteClose); + EXPECT_EQ(0U, stats_.named_.downstream_rq_overload_close_.value()); + EXPECT_FALSE(filter_callbacks_.connection().streamInfo().hasResponseFlag( + StreamInfo::CoreResponseFlag::OverloadManager)); +} + TEST_F(HttpConnectionManagerImplTest, DecodeHeaderLoadShedPointCanRejectNewStreams) { Server::MockLoadShedPoint accept_new_stream_point; - EXPECT_CALL(overload_manager_, getLoadShedPoint(testing::_)) + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmDecodeHeaders)) .WillOnce(Return(&accept_new_stream_point)); + EXPECT_CALL(overload_manager_, + getLoadShedPoint(Server::LoadShedPointName::get().HcmCodecCreation)) + .WillOnce(Return(nullptr)); setup(false, ""); setupFilterChain(1, 0); diff --git a/test/integration/overload_integration_test.cc b/test/integration/overload_integration_test.cc index efaf470afce0..56d6df0ffdb9 100644 --- a/test/integration/overload_integration_test.cc +++ b/test/integration/overload_integration_test.cc @@ -814,4 +814,39 @@ TEST_P(LoadShedPointIntegrationTest, Http2ServerDispatchSendsGoAwayCompletingPen "overload.envoy.load_shed_points.http2_server_go_away_on_dispatch.scale_percent", 0); } +TEST_P(LoadShedPointIntegrationTest, HttpConnectionMnagerCloseConnectionCreatingCodec) { + if (downstreamProtocol() == Http::CodecClient::Type::HTTP3) { + return; + } + autonomous_upstream_ = true; + initializeOverloadManager( + TestUtility::parseYaml(R"EOF( + name: "envoy.load_shed_points.hcm_ondata_creating_codec" + triggers: + - name: "envoy.resource_monitors.testonly.fake_resource_monitor" + threshold: + value: 0.90 + )EOF")); + + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + + updateResource(0.95); + test_server_->waitForGaugeEq( + "overload.envoy.load_shed_points.hcm_ondata_creating_codec.scale_percent", 100); + auto encoder_decoder = codec_client_->startRequest(default_request_headers_); + + test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + ASSERT_TRUE(codec_client_->waitForDisconnect()); + + updateResource(0.80); + test_server_->waitForGaugeEq( + "overload.envoy.load_shed_points.hcm_ondata_creating_codec.scale_percent", 0); + + codec_client_ = makeHttpConnection(makeClientConnection((lookupPort("http")))); + auto response = codec_client_->makeHeaderOnlyRequest(default_request_headers_); + test_server_->waitForCounterEq("http.config_test.downstream_rq_overload_close", 1); + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_EQ(response->headers().getStatusValue(), "200"); +} + } // namespace Envoy From b79d9f17bb6f9e42c6ce3fabd1570a43dceaaa69 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 29 Feb 2024 00:10:20 -0800 Subject: [PATCH 120/151] tools: fix python regex warnings for newer python (#32630) example error: ./tools/code_format/check_format.py:260: SyntaxWarning: invalid escape sequence '\s' return re.compile("^\s*namespace\s+%s\s*{" % self.namespace_check, re.MULTILINE) Signed-off-by: Greg Greenway --- tools/code_format/check_format.py | 2 +- tools/code_format/envoy_build_fixer.py | 12 ++++++------ tools/code_format/header_order.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/code_format/check_format.py b/tools/code_format/check_format.py index 368478ac2c34..bae3ce814b77 100755 --- a/tools/code_format/check_format.py +++ b/tools/code_format/check_format.py @@ -257,7 +257,7 @@ def namespace_check_excluded_paths(self): @cached_property def namespace_re(self): - return re.compile("^\s*namespace\s+%s\s*{" % self.namespace_check, re.MULTILINE) + return re.compile(r"^\s*namespace\s+%s\s*{" % self.namespace_check, re.MULTILINE) # Map a line transformation function across each line of a file, # writing the result lines as requested. diff --git a/tools/code_format/envoy_build_fixer.py b/tools/code_format/envoy_build_fixer.py index 470d25f20b6a..87fcc8909559 100755 --- a/tools/code_format/envoy_build_fixer.py +++ b/tools/code_format/envoy_build_fixer.py @@ -33,18 +33,18 @@ ENVOY_RULE_REGEX = re.compile(r'envoy[_\w]+\(') # Match a load() statement for the envoy_package macros. -PACKAGE_LOAD_BLOCK_REGEX = re.compile('("envoy_package".*?\)\n)', re.DOTALL) -EXTENSION_PACKAGE_LOAD_BLOCK_REGEX = re.compile('("envoy_extension_package".*?\)\n)', re.DOTALL) -CONTRIB_PACKAGE_LOAD_BLOCK_REGEX = re.compile('("envoy_contrib_package".*?\)\n)', re.DOTALL) -MOBILE_PACKAGE_LOAD_BLOCK_REGEX = re.compile('("envoy_mobile_package".*?\)\n)', re.DOTALL) +PACKAGE_LOAD_BLOCK_REGEX = re.compile(r'("envoy_package".*?\)\n)', re.DOTALL) +EXTENSION_PACKAGE_LOAD_BLOCK_REGEX = re.compile(r'("envoy_extension_package".*?\)\n)', re.DOTALL) +CONTRIB_PACKAGE_LOAD_BLOCK_REGEX = re.compile(r'("envoy_contrib_package".*?\)\n)', re.DOTALL) +MOBILE_PACKAGE_LOAD_BLOCK_REGEX = re.compile(r'("envoy_mobile_package".*?\)\n)', re.DOTALL) # Match Buildozer 'print' output. Example of Buildozer print output: # cc_library json_transcoder_filter_lib [json_transcoder_filter.cc] (missing) (missing) BUILDOZER_PRINT_REGEX = re.compile( - '\s*([\w_]+)\s+([\w_]+)\s+[(\[](.*?)[)\]]\s+[(\[](.*?)[)\]]\s+[(\[](.*?)[)\]]') + r'\s*([\w_]+)\s+([\w_]+)\s+[(\[](.*?)[)\]]\s+[(\[](.*?)[)\]]\s+[(\[](.*?)[)\]]') # Match API header include in Envoy source file? -API_INCLUDE_REGEX = re.compile('#include "(contrib/envoy/.*|envoy/.*)/[^/]+\.pb\.(validate\.)?h"') +API_INCLUDE_REGEX = re.compile(r'#include "(contrib/envoy/.*|envoy/.*)/[^/]+\.pb\.(validate\.)?h"') class EnvoyBuildFixerError(Exception): diff --git a/tools/code_format/header_order.py b/tools/code_format/header_order.py index bd8ec81577f2..df1c8cb77f81 100755 --- a/tools/code_format/header_order.py +++ b/tools/code_format/header_order.py @@ -65,11 +65,11 @@ def regex_filter(regex): # Filters that define the #include blocks block_filters = [ file_header_filter(), - regex_filter('<.*\.h>'), - regex_filter('<.*>'), + regex_filter(r'<.*\.h>'), + regex_filter(r'<.*>'), ] for subdir in include_dir_order: - block_filters.append(regex_filter('"' + subdir + '/.*"')) + block_filters.append(regex_filter(r'"' + subdir + r'/.*"')) blocks = [] already_included = set([]) From 09e182ec15e597780f861f2b1b33af1951cdcb59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:26:18 +0000 Subject: [PATCH 121/151] build(deps): bump distroless/base-nossl-debian12 from `0e777c6` to `28dc895` in /ci (#32613) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `0e777c6` to `28dc895`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 07affa3502e3..9a6f0abe70fd 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0e777c69ba810353b9f3f2033280bbe7d029d81fa55760f6eec817ef595aa19c AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:28dc8956c04a92ffc192d06c5da69fa747c675ee44043ba18128e747c2f539f5 AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 97ef386aeb4d12a3fd38f151c38be7decc5c37bf Mon Sep 17 00:00:00 2001 From: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:55:04 +0100 Subject: [PATCH 122/151] opentelemetrytracer: Added support to configure a Dynatrace sampler (#32598) Signed-off-by: Thomas Ebner <96168670+samohte@users.noreply.github.com> Signed-off-by: Joao Grassi <5938087+joaopgrassi@users.noreply.github.com> Signed-off-by: thomas.ebner --- .../tracers/opentelemetry/samplers/v3/BUILD | 5 +- .../samplers/v3/dynatrace_sampler.proto | 53 +++ bazel/repository_locations.bzl | 1 + changelogs/current.yaml | 3 + source/extensions/extensions_build_config.bzl | 1 + source/extensions/extensions_metadata.yaml | 7 + .../opentelemetry/samplers/dynatrace/BUILD | 46 +++ .../samplers/dynatrace/config.cc | 38 ++ .../opentelemetry/samplers/dynatrace/config.h | 42 +++ .../samplers/dynatrace/dynatrace_sampler.cc | 188 ++++++++++ .../samplers/dynatrace/dynatrace_sampler.h | 54 +++ .../samplers/dynatrace/sampler_config.cc | 28 ++ .../samplers/dynatrace/sampler_config.h | 47 +++ .../dynatrace/sampler_config_provider.cc | 21 ++ .../dynatrace/sampler_config_provider.h | 63 ++++ .../samplers/dynatrace/sampling_controller.cc | 166 +++++++++ .../samplers/dynatrace/sampling_controller.h | 134 +++++++ .../samplers/dynatrace/stream_summary.h | 204 +++++++++++ .../samplers/dynatrace/tenant_id.h | 50 +++ .../tracers/opentelemetry/tracer.cc | 2 +- .../opentelemetry/samplers/dynatrace/BUILD | 62 ++++ .../samplers/dynatrace/config_test.cc | 38 ++ .../dynatrace_sampler_integration_test.cc | 151 ++++++++ .../dynatrace/dynatrace_sampler_test.cc | 336 ++++++++++++++++++ .../dynatrace/sampler_config_provider_test.cc | 95 +++++ .../samplers/dynatrace/sampler_config_test.cc | 64 ++++ .../dynatrace/sampling_controller_test.cc | 321 +++++++++++++++++ .../samplers/dynatrace/stream_summary_test.cc | 148 ++++++++ .../samplers/dynatrace/tenant_id_test.cc | 31 ++ .../opentelemetry/samplers/sampler_test.cc | 6 + tools/spelling/spelling_dictionary.txt | 1 + 31 files changed, 2404 insertions(+), 2 deletions(-) create mode 100644 api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h create mode 100644 source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/config_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc create mode 100644 test/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id_test.cc diff --git a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD index 29ebf0741406..09a37ad16b83 100644 --- a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD +++ b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/BUILD @@ -5,5 +5,8 @@ load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") licenses(["notice"]) # Apache 2 api_proto_package( - deps = ["@com_github_cncf_xds//udpa/annotations:pkg"], + deps = [ + "//envoy/config/core/v3:pkg", + "@com_github_cncf_xds//udpa/annotations:pkg", + ], ) diff --git a/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto new file mode 100644 index 000000000000..b74e96a6a416 --- /dev/null +++ b/api/envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; + +package envoy.extensions.tracers.opentelemetry.samplers.v3; + +import "envoy/config/core/v3/http_uri.proto"; + +import "udpa/annotations/status.proto"; + +option java_package = "io.envoyproxy.envoy.extensions.tracers.opentelemetry.samplers.v3"; +option java_outer_classname = "DynatraceSamplerProto"; +option java_multiple_files = true; +option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/tracers/opentelemetry/samplers/v3;samplersv3"; +option (udpa.annotations.file_status).package_version_status = ACTIVE; + +// [#protodoc-title: Dynatrace Sampler config] +// Configuration for the Dynatrace Sampler extension. +// [#extension: envoy.tracers.opentelemetry.samplers.dynatrace] + +// [#next-free-field: 6] +message DynatraceSamplerConfig { + // The Dynatrace tenant. + // + // The value can be obtained from the Envoy deployment page in Dynatrace. + string tenant = 1; + + // The id of the Dynatrace cluster id. + // + // The value can be obtained from the Envoy deployment page in Dynatrace. + int32 cluster_id = 2; + + // The HTTP URI to fetch the sampler configuration (root spans per minute). For example: + // + // .. code-block:: yaml + // + // http_uri: + // uri: .dev.dynatracelabs.com/api/v2/otlp/v1/traces + // cluster: dynatrace + // timeout: 10s + // + config.core.v3.HttpUri http_uri = 3; + + // The access token to fetch the sampling configuration from the Dynatrace API + string token = 4; + + // Default number of root spans per minute, used when the value can't be obtained from the Dynatrace API. + // + // A default value of ``1000`` is used when: + // + // - ``root_spans_per_minute`` is unset + // - ``root_spans_per_minute`` is set to 0 + // + uint32 root_spans_per_minute = 5; +} diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 9aa875702a92..7fffdb9bd777 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -565,6 +565,7 @@ REPOSITORY_LOCATIONS_SPEC = dict( extensions = [ "envoy.tracers.opentelemetry", "envoy.tracers.opentelemetry.samplers.always_on", + "envoy.tracers.opentelemetry.samplers.dynatrace", ], release_date = "2024-02-17", cpe = "N/A", diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2815679340e8..0fc50260fb23 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -243,6 +243,9 @@ new_features: added a :ref:`configuration option ` to add ``x-envoy-local-overloaded`` header when Overload Manager is triggered. +- area: tracing + change: | + Added support to configure a Dynatrace sampler for the OpenTelemetry tracer. deprecated: - area: listener diff --git a/source/extensions/extensions_build_config.bzl b/source/extensions/extensions_build_config.bzl index a3dc5309a637..4b7ed2228f47 100644 --- a/source/extensions/extensions_build_config.bzl +++ b/source/extensions/extensions_build_config.bzl @@ -279,6 +279,7 @@ EXTENSIONS = { # "envoy.tracers.opentelemetry.samplers.always_on": "//source/extensions/tracers/opentelemetry/samplers/always_on:config", + "envoy.tracers.opentelemetry.samplers.dynatrace": "//source/extensions/tracers/opentelemetry/samplers/dynatrace:config", # # Transport sockets diff --git a/source/extensions/extensions_metadata.yaml b/source/extensions/extensions_metadata.yaml index bf75bb02917f..2822b8e21329 100644 --- a/source/extensions/extensions_metadata.yaml +++ b/source/extensions/extensions_metadata.yaml @@ -1180,6 +1180,13 @@ envoy.tracers.opentelemetry.samplers.always_on: status: wip type_urls: - envoy.extensions.tracers.opentelemetry.samplers.v3.AlwaysOnSamplerConfig +envoy.tracers.opentelemetry.samplers.dynatrace: + categories: + - envoy.tracers.opentelemetry.samplers + security_posture: unknown + status: wip + type_urls: + - envoy.extensions.tracers.opentelemetry.samplers.v3.DynatraceSamplerConfig envoy.tracers.skywalking: categories: - envoy.tracers diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD new file mode 100644 index 000000000000..629674856083 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -0,0 +1,46 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_cc_extension", + "envoy_cc_library", + "envoy_extension_package", +) + +licenses(["notice"]) # Apache 2 + +envoy_extension_package() + +envoy_cc_extension( + name = "config", + srcs = ["config.cc"], + hdrs = ["config.h"], + deps = [ + ":dynatrace_sampler_lib", + "//envoy/registry", + "//source/common/config:utility_lib", + "@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto", + ], +) + +envoy_cc_library( + name = "dynatrace_sampler_lib", + srcs = [ + "dynatrace_sampler.cc", + "sampler_config.cc", + "sampler_config_provider.cc", + "sampling_controller.cc", + ], + hdrs = [ + "dynatrace_sampler.h", + "sampler_config.h", + "sampler_config_provider.h", + "sampling_controller.h", + "stream_summary.h", + "tenant_id.h", + ], + deps = [ + "//source/common/config:datasource_lib", + "//source/extensions/tracers/opentelemetry:opentelemetry_tracer_lib", + "//source/extensions/tracers/opentelemetry/samplers:sampler_lib", + "@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto", + ], +) diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc new file mode 100644 index 000000000000..bfcfb2d382f9 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.cc @@ -0,0 +1,38 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h" + +#include + +#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.validate.h" + +#include "source/common/config/utility.h" +#include "source/common/protobuf/utility.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +SamplerSharedPtr +DynatraceSamplerFactory::createSampler(const Protobuf::Message& config, + Server::Configuration::TracerFactoryContext& context) { + auto mptr = Envoy::Config::Utility::translateAnyToFactoryConfig( + dynamic_cast(config), context.messageValidationVisitor(), *this); + + const auto& proto_config = MessageUtil::downcastAndValidate< + const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig&>( + *mptr, context.messageValidationVisitor()); + + SamplerConfigProviderPtr cf = std::make_unique(context, proto_config); + return std::make_shared(proto_config, context, std::move(cf)); +} + +/** + * Static registration for the Dynatrace sampler factory. @see RegisterFactory. + */ +REGISTER_FACTORY(DynatraceSamplerFactory, SamplerFactory); + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h new file mode 100644 index 000000000000..c317416dbfd7 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.h" +#include "envoy/registry/registry.h" + +#include "source/extensions/tracers/opentelemetry/samplers/sampler.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * Config registration for the DynatraceSampler. @see SamplerFactory. + */ +class DynatraceSamplerFactory : public SamplerFactory { +public: + /** + * @brief Creates a Dynatrace sampler + * + * @param config The sampler configuration + * @param context The tracer factory context. + * @return SamplerSharedPtr + */ + SamplerSharedPtr createSampler(const Protobuf::Message& config, + Server::Configuration::TracerFactoryContext& context) override; + + ProtobufTypes::MessagePtr createEmptyConfigProto() override { + return std::make_unique< + envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig>(); + } + std::string name() const override { return "envoy.tracers.opentelemetry.samplers.dynatrace"; } +}; + +DECLARE_FACTORY(DynatraceSamplerFactory); + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc new file mode 100644 index 000000000000..5853d86e7650 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.cc @@ -0,0 +1,188 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h" + +#include +#include +#include + +#include "source/common/common/hash.h" +#include "source/common/config/datasource.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h" +#include "source/extensions/tracers/opentelemetry/samplers/sampler.h" +#include "source/extensions/tracers/opentelemetry/span_context.h" + +#include "absl/strings/str_cat.h" +#include "opentelemetry/trace/trace_state.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace { + +constexpr std::chrono::minutes SAMPLING_UPDATE_TIMER_DURATION{1}; + +/** + * @brief Helper for creating and reading the Dynatrace tag in the tracestate http header + * This tag has at least 8 values delimited by semicolon: + * - tag[0]: version (currently version 4) + * - tag[1] - tag[4]: unused in the sampler (always 0) + * - tag[5]: ignored field. 1 if a span is ignored (not sampled), 0 otherwise + * - tag[6]: sampling exponent + * - tag[7]: path info + */ +class DynatraceTag { +public: + static DynatraceTag createInvalid() { return {false, false, 0, 0}; } + + // Creates a tag using the given values. + static DynatraceTag create(bool ignored, uint32_t sampling_exponent, uint32_t path_info) { + return {true, ignored, sampling_exponent, path_info}; + } + + // Creates a tag from a string. + static DynatraceTag create(const std::string& value) { + std::vector tracestate_components = + absl::StrSplit(value, ';', absl::AllowEmpty()); + if (tracestate_components.size() < 8) { + return createInvalid(); + } + + if (tracestate_components[0] != "fw4") { + return createInvalid(); + } + bool ignored = tracestate_components[5] == "1"; + uint32_t sampling_exponent; + uint32_t path_info; + if (absl::SimpleAtoi(tracestate_components[6], &sampling_exponent) && + absl::SimpleHexAtoi(tracestate_components[7], &path_info)) { + return {true, ignored, sampling_exponent, path_info}; + } + return createInvalid(); + } + + // Returns a Dynatrace tag as string. + std::string asString() const { + std::string ret = absl::StrCat("fw4;0;0;0;0;", ignored_ ? "1" : "0", ";", sampling_exponent_, + ";", absl::Hex(path_info_)); + return ret; + } + + // Returns true if parsing was successful. + bool isValid() const { return valid_; }; + bool isIgnored() const { return ignored_; }; + uint32_t getSamplingExponent() const { return sampling_exponent_; }; + +private: + DynatraceTag(bool valid, bool ignored, uint32_t sampling_exponent, uint32_t path_info) + : valid_(valid), ignored_(ignored), sampling_exponent_(sampling_exponent), + path_info_(path_info) {} + + bool valid_; + bool ignored_; + uint32_t sampling_exponent_; + uint32_t path_info_; +}; + +// add Dynatrace specific span attributes +void addSamplingAttributes(uint32_t sampling_exponent, + std::map& attributes) { + + const auto multiplicity = SamplingState::toMultiplicity(sampling_exponent); + // The denominator of the sampling ratio. If, for example, the Dynatrace OneAgent samples with a + // probability of 1/16, the value of supportability sampling ratio would be 16. + // Note: Ratio is also known as multiplicity. + attributes["supportability.atm_sampling_ratio"] = std::to_string(multiplicity); + + if (multiplicity > 1) { + static constexpr uint64_t two_pow_56 = 1llu << 56; // 2^56 + // The sampling probability can be interpreted as the number of spans + // that are discarded out of 2^56. The attribute is only available if the sampling.threshold is + // not 0 and therefore sampling happened. + const uint64_t sampling_threshold = two_pow_56 - two_pow_56 / multiplicity; + attributes["sampling.threshold"] = std::to_string(sampling_threshold); + } +} + +} // namespace + +DynatraceSampler::DynatraceSampler( + const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, + Server::Configuration::TracerFactoryContext& context, + SamplerConfigProviderPtr sampler_config_provider) + : dt_tracestate_key_(absl::StrCat(calculateTenantId(config.tenant()), "-", + absl::Hex(config.cluster_id()), "@dt")), + sampling_controller_(std::move(sampler_config_provider)) { + + // start a timer to periodically recalculate the sampling exponents + timer_ = context.serverFactoryContext().mainThreadDispatcher().createTimer([this]() -> void { + sampling_controller_.update(); + timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); + }); + timer_->enableTimer(SAMPLING_UPDATE_TIMER_DURATION); +} + +SamplingResult DynatraceSampler::shouldSample(const absl::optional parent_context, + const std::string& trace_id, + const std::string& /*name*/, OTelSpanKind /*kind*/, + OptRef trace_context, + const std::vector& /*links*/) { + + SamplingResult result; + std::map att; + + // trace_context->path() returns path and query. query part is removed in getSamplingKey() + const std::string sampling_key = + trace_context.has_value() + ? sampling_controller_.getSamplingKey(trace_context->path(), trace_context->method()) + : ""; + + sampling_controller_.offer(sampling_key); + + auto trace_state = opentelemetry::trace::TraceState::FromHeader( + parent_context.has_value() ? parent_context->tracestate() : ""); + + std::string trace_state_value; + bool is_root_span = true; + + if (trace_state->Get(dt_tracestate_key_, trace_state_value)) { + // we found a Dynatrace tag in the tracestate header. Respect the sampling decision in the tag. + if (DynatraceTag dynatrace_tag = DynatraceTag::create(trace_state_value); + dynatrace_tag.isValid()) { + result.decision = dynatrace_tag.isIgnored() ? Decision::Drop : Decision::RecordAndSample; + addSamplingAttributes(dynatrace_tag.getSamplingExponent(), att); + result.tracestate = parent_context->tracestate(); + is_root_span = false; + } + } + + if (is_root_span) { + // do a decision based on the calculated exponent + // we use a hash of the trace_id as random number + const auto hash = MurmurHash::murmurHash2(trace_id); + const auto sampling_state = sampling_controller_.getSamplingState(sampling_key); + const bool sample = sampling_state.shouldSample(hash); + const auto sampling_exponent = sampling_state.getExponent(); + + addSamplingAttributes(sampling_exponent, att); + + result.decision = sample ? Decision::RecordAndSample : Decision::Drop; + // create a new Dynatrace tag and add it to tracestate + DynatraceTag new_tag = + DynatraceTag::create(!sample, sampling_exponent, static_cast(hash)); + trace_state = trace_state->Set(dt_tracestate_key_, new_tag.asString()); + result.tracestate = trace_state->ToHeader(); + } + + if (!att.empty()) { + result.attributes = std::make_unique>(std::move(att)); + } + return result; +} + +std::string DynatraceSampler::getDescription() const { return "DynatraceSampler"; } + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h new file mode 100644 index 000000000000..80711ef77b3d --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.h" +#include "envoy/server/factory_context.h" + +#include "source/common/common/logger.h" +#include "source/common/config/datasource.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" +#include "source/extensions/tracers/opentelemetry/samplers/sampler.h" + +#include "absl/synchronization/mutex.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * @brief A Dynatrace specific sampler. + * + * For sampling, the requests are categorized based on the http method and the http path. + * A sampling multiplicity is calculated for every request category based on the + * number of requests. A Dynatrace specific tag is added to the http tracestate header. + */ +class DynatraceSampler : public Sampler, Logger::Loggable { +public: + DynatraceSampler( + const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config, + Server::Configuration::TracerFactoryContext& context, + SamplerConfigProviderPtr sampler_config_provider); + + /** @see Sampler#shouldSample */ + SamplingResult shouldSample(const absl::optional parent_context, + const std::string& trace_id, const std::string& name, + OTelSpanKind spankind, + OptRef trace_context, + const std::vector& links) override; + + std::string getDescription() const override; + +private: + std::string dt_tracestate_key_; // used as key in the http tracestate header + Event::TimerPtr timer_; // used to periodically calculate sampling multiplicity + SamplingController sampling_controller_; +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc new file mode 100644 index 000000000000..b3a6f2b91c45 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.cc @@ -0,0 +1,28 @@ +#include + +#include "source/common/json/json_loader.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +void SamplerConfig::parse(const std::string& json) { + const auto result = Envoy::Json::Factory::loadFromStringNoThrow(json); + if (result.ok()) { + const auto& obj = result.value(); + if (obj->hasObject("rootSpansPerMinute")) { + const auto value = obj->getInteger("rootSpansPerMinute", default_root_spans_per_minute_); + root_spans_per_minute_.store(value); + return; + } + } + // Didn't get a value, reset to default + root_spans_per_minute_.store(default_root_spans_per_minute_); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h new file mode 100644 index 000000000000..6f576b2d5afb --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * @brief Contains the configuration for the sampler. Might be extended in a future version + * + */ +class SamplerConfig { +public: + static constexpr uint32_t ROOT_SPANS_PER_MINUTE_DEFAULT = 1000; + + explicit SamplerConfig(uint32_t default_root_spans_per_minute) + : default_root_spans_per_minute_(default_root_spans_per_minute > 0 + ? default_root_spans_per_minute + : ROOT_SPANS_PER_MINUTE_DEFAULT), + root_spans_per_minute_(default_root_spans_per_minute_) {} + /** + * @brief Parses a json string containing the expected root spans per minute. + * + * @param json A string containing the configuration. + */ + void parse(const std::string& json); + + /** + * @brief Returns wanted root spans per minute + * + * @return uint32_t wanted root spans per minute + */ + uint32_t getRootSpansPerMinute() const { return root_spans_per_minute_.load(); } + +private: + const uint32_t default_root_spans_per_minute_; + std::atomic root_spans_per_minute_; +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.cc new file mode 100644 index 000000000000..465cb9e1bd5f --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.cc @@ -0,0 +1,21 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h" + +#include "source/common/common/enum_to_int.h" +#include "source/common/http/utility.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +SamplerConfigProviderImpl::SamplerConfigProviderImpl( + Server::Configuration::TracerFactoryContext& /*context*/, + const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& config) + : sampler_config_(config.root_spans_per_minute()) {} + +const SamplerConfig& SamplerConfigProviderImpl::getSamplerConfig() const { return sampler_config_; } + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h new file mode 100644 index 000000000000..6dd464d4f6c7 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.h" +#include "envoy/http/async_client.h" +#include "envoy/http/message.h" +#include "envoy/server/tracer_config.h" + +#include "source/common/http/async_client_impl.h" +#include "source/common/http/async_client_utility.h" +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" +#include "source/common/http/utility.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * @brief The Dynatrace sampling configuration provider. + * + * The configuration provider obtains sampling configuration from the Dynatrace API + * in order to dynamically tune up/down the sampling rate of spans. + */ +class SamplerConfigProvider { +public: + virtual ~SamplerConfigProvider() = default; + + /** + * @brief Get the Dynatrace Sampler configuration. + * + * @return const SamplerConfig& + */ + virtual const SamplerConfig& getSamplerConfig() const = 0; +}; + +class SamplerConfigProviderImpl : public SamplerConfigProvider, + public Logger::Loggable { +public: + SamplerConfigProviderImpl( + Server::Configuration::TracerFactoryContext& context, + const envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig& + config); + + const SamplerConfig& getSamplerConfig() const override; + + ~SamplerConfigProviderImpl() override = default; + +private: + SamplerConfig sampler_config_; +}; + +using SamplerConfigProviderPtr = std::unique_ptr; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc new file mode 100644 index 000000000000..e14ad0fa3493 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.cc @@ -0,0 +1,166 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace {} + +void SamplingController::update() { + absl::WriterMutexLock lock{&stream_summary_mutex_}; + const auto top_k = stream_summary_->getTopK(); + const auto last_period_count = stream_summary_->getN(); + + // update sampling exponents + update(top_k, last_period_count, + sampler_config_provider_->getSamplerConfig().getRootSpansPerMinute()); + // Note: getTopK() returns references to values in StreamSummary. + // Do not destroy it while top_k is used! + stream_summary_ = std::make_unique(STREAM_SUMMARY_SIZE); +} + +void SamplingController::update(const TopKListT& top_k, uint64_t last_period_count, + uint32_t total_wanted) { + + SamplingExponentsT new_sampling_exponents; + // start with sampling exponent 0, which means multiplicity == 1 (every span is sampled) + for (auto const& counter : top_k) { + new_sampling_exponents[counter.getItem()] = SamplingState(0); + } + + // use the last entry as "rest bucket", which is used for new/unknown requests + rest_bucket_key_ = (!top_k.empty()) ? top_k.back().getItem() : ""; + + calculateSamplingExponents(top_k, total_wanted, new_sampling_exponents); + last_effective_count_ = calculateEffectiveCount(top_k, new_sampling_exponents); + logSamplingInfo(top_k, new_sampling_exponents, last_period_count, total_wanted); + + absl::WriterMutexLock lock{&sampling_exponents_mutex_}; + sampling_exponents_ = std::move(new_sampling_exponents); +} + +uint64_t SamplingController::getEffectiveCount() const { + absl::ReaderMutexLock lock{&stream_summary_mutex_}; + return last_effective_count_; +} + +void SamplingController::offer(const std::string& sampling_key) { + if (!sampling_key.empty()) { + absl::WriterMutexLock lock{&stream_summary_mutex_}; + stream_summary_->offer(sampling_key); + } +} + +SamplingState SamplingController::getSamplingState(const std::string& sampling_key) const { + { // scope for lock + absl::ReaderMutexLock sax_lock{&sampling_exponents_mutex_}; + auto iter = sampling_exponents_.find(sampling_key); + if (iter != sampling_exponents_.end()) { + return iter->second; + } + + // try to use "rest bucket" + auto rest_bucket_iter = sampling_exponents_.find(rest_bucket_key_); + if (rest_bucket_iter != sampling_exponents_.end()) { + return rest_bucket_iter->second; + } + } + + // If we can't find a sampling exponent, we calculate it based on the total number of requests + // in this period. This should also handle the "warm up phase" where no top_k is available + const auto divisor = sampler_config_provider_->getSamplerConfig().getRootSpansPerMinute() / 2; + if (divisor == 0) { + return SamplingState{MAX_SAMPLING_EXPONENT}; + } + absl::ReaderMutexLock ss_lock{&stream_summary_mutex_}; + const uint32_t exp = stream_summary_->getN() / divisor; + return SamplingState{exp}; +} + +std::string SamplingController::getSamplingKey(const absl::string_view path_query, + const absl::string_view method) { + // remove query part (truncate after first '?') + const size_t query_offset = path_query.find('?'); + auto path = + path_query.substr(0, query_offset != path_query.npos ? query_offset : path_query.size()); + return absl::StrCat(method, "_", path); +} + +void SamplingController::logSamplingInfo(const TopKListT& top_k, + const SamplingExponentsT& new_sampling_exponents, + uint64_t last_period_count, uint32_t total_wanted) const { + ENVOY_LOG(debug, + "Updating sampling info. top_k.size(): {}, last_period_count: {}, total_wanted: {}", + top_k.size(), last_period_count, total_wanted); + for (auto const& counter : top_k) { + auto sampling_state = new_sampling_exponents.find(counter.getItem()); + ENVOY_LOG(debug, "- {}: value: {}, exponent: {}", counter.getItem(), counter.getValue(), + sampling_state->second.getExponent()); + } +} + +uint64_t SamplingController::calculateEffectiveCount(const TopKListT& top_k, + const SamplingExponentsT& sampling_exponents) { + uint64_t cnt = 0; + for (auto const& counter : top_k) { + auto sampling_state = sampling_exponents.find(counter.getItem()); + if (sampling_state != sampling_exponents.end()) { + auto counterVal = counter.getValue(); + auto mul = sampling_state->second.getMultiplicity(); + auto res = counterVal / mul; + cnt += res; + } + } + return cnt; +} + +void SamplingController::calculateSamplingExponents( + const TopKListT& top_k, uint32_t total_wanted, + SamplingExponentsT& new_sampling_exponents) const { + const auto top_k_size = top_k.size(); + if (top_k_size == 0 || total_wanted == 0) { + return; + } + + // number of requests which are allowed for every entry + const uint32_t allowed_per_entry = total_wanted / top_k_size; + + for (auto& counter : top_k) { + // allowed multiplicity for this entry + auto wanted_multiplicity = counter.getValue() / allowed_per_entry; + auto sampling_state = new_sampling_exponents.find(counter.getItem()); + // sampling exponent has to be a power of 2. Find the exponent to have multiplicity near to + // wanted_multiplicity + while (wanted_multiplicity > sampling_state->second.getMultiplicity() && + sampling_state->second.getExponent() < MAX_SAMPLING_EXPONENT) { + sampling_state->second.increaseExponent(); + } + if (wanted_multiplicity < sampling_state->second.getMultiplicity()) { + // we want to have multiplicity <= wanted_multiplicity, therefore exponent is decreased once. + sampling_state->second.decreaseExponent(); + } + } + + auto effective_count = calculateEffectiveCount(top_k, new_sampling_exponents); + // There might be entries where allowed_per_entry is greater than their count. + // Therefore, we would sample less than total_wanted. + // To avoid this, we decrease the exponent of other entries if possible + if (effective_count < total_wanted) { + for (int i = 0; i < 5; i++) { // max tries + for (auto reverse_it = top_k.rbegin(); reverse_it != top_k.rend(); + ++reverse_it) { // start with lowest frequency + auto rev_sampling_state = new_sampling_exponents.find(reverse_it->getItem()); + rev_sampling_state->second.decreaseExponent(); + effective_count = calculateEffectiveCount(top_k, new_sampling_exponents); + if (effective_count >= total_wanted) { // we are done + return; + } + } + } + } +} +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h new file mode 100644 index 000000000000..b5871d4d4b6e --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" + +#include "absl/synchronization/mutex.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +/** + * @brief Container for sampling exponent / multiplicity. + * based on the "Space Saving algorithm", AKA "HeavyHitter" + * See: + * https://cse.hkust.edu.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf + * + */ +class SamplingState { +public: + // Convert exponent to multiplicity + [[nodiscard]] static uint32_t toMultiplicity(uint32_t exponent) { return 1 << exponent; } + [[nodiscard]] uint32_t getExponent() const { return exponent_; } + [[nodiscard]] uint32_t getMultiplicity() const { return toMultiplicity(exponent_); } + void increaseExponent() { exponent_++; } + void decreaseExponent() { + if (exponent_ > 0) { + exponent_--; + } + } + + explicit SamplingState(uint32_t exponent) : exponent_(exponent){}; + + SamplingState() = default; + + /** + * @brief Does a sampling decision based on random number attribute and multiplicity + * + * @param random_nr Random number used for sampling decision. + * @return true if request should be sampled, false otherwise + */ + bool shouldSample(const uint64_t random_nr) const { return (random_nr % getMultiplicity() == 0); } + +private: + uint32_t exponent_{0}; +}; + +using StreamSummaryT = StreamSummary; +using TopKListT = std::list>; + +/** + * @brief Counts the requests per sampling key in the current period. Calculates the sampling + * exponents based on the request count in the latest period. + * + */ +class SamplingController : public Logger::Loggable { + +public: + explicit SamplingController(SamplerConfigProviderPtr sampler_config_provider) + : stream_summary_(std::make_unique(STREAM_SUMMARY_SIZE)), + sampler_config_provider_(std::move(sampler_config_provider)) {} + + /** + * @brief Trigger calculating the sampling exponents based on the request count since last update + * + */ + void update(); + + /** + * @brief Get the Sampling State object for a sampling key + * + * @param sampling_key Sampling Key to search for + * @return SamplingState Current Sampling State for key + */ + SamplingState getSamplingState(const std::string& sampling_key) const; + + /** + * @brief Returns the number of spans which would have been sampled in the last period using the + * current sampling states + * + * @return effective count + */ + uint64_t getEffectiveCount() const; + + /** + * @brief Counts the occurrence of sampling_key + * + * @param sampling_key Sampling Key used to categorize the request + */ + void offer(const std::string& sampling_key); + + /** + * @brief Creates the Sampling Key which is used to categorize a request + * + * @param path_query The request path. May contain the query. + * @param method The request method. + * @return The sampling key. + */ + static std::string getSamplingKey(const absl::string_view path_query, + const absl::string_view method); + + static constexpr size_t STREAM_SUMMARY_SIZE{100}; + static constexpr uint32_t MAX_SAMPLING_EXPONENT = (1 << 4) - 1; // 15 + +private: + using SamplingExponentsT = absl::flat_hash_map; + SamplingExponentsT sampling_exponents_; + mutable absl::Mutex sampling_exponents_mutex_{}; + std::string rest_bucket_key_{}; + std::unique_ptr stream_summary_; + uint64_t last_effective_count_{}; + mutable absl::Mutex stream_summary_mutex_{}; + SamplerConfigProviderPtr sampler_config_provider_; + + void logSamplingInfo(const TopKListT& top_k, const SamplingExponentsT& new_sampling_exponents, + uint64_t last_period_count, uint32_t total_wanted) const; + + static uint64_t calculateEffectiveCount(const TopKListT& top_k, + const SamplingExponentsT& sampling_exponents); + + void calculateSamplingExponents(const TopKListT& top_k, uint32_t total_wanted, + SamplingExponentsT& new_sampling_exponents) const; + + void update(const TopKListT& top_k, uint64_t last_period_count, uint32_t total_wanted); +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h new file mode 100644 index 000000000000..b6385c91313d --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h @@ -0,0 +1,204 @@ +#pragma once + +#include +#include +#include + +#include "source/common/common/assert.h" + +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/types/optional.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace detail { + +template struct Bucket; + +template using BucketIterator = typename std::list>::iterator; + +template struct Counter { + BucketIterator bucket; + absl::optional item{}; + uint64_t value{}; + uint64_t error{}; + + explicit Counter(BucketIterator bucket) : bucket(bucket) {} + Counter(Counter const&) = delete; + Counter& operator=(Counter const&) = delete; +}; + +template using CounterIterator = typename std::list>::iterator; + +template struct Bucket { + uint64_t value; + std::list> children{}; + + explicit Bucket(uint64_t value) : value(value) {} + Bucket(Bucket const&) = delete; + Bucket& operator=(Bucket const&) = delete; +}; + +} // namespace detail + +template class Counter { +private: + T const& item_; + uint64_t value_; + uint64_t error_; + +public: + Counter(detail::Counter const& c) : item_(*c.item), value_(c.value), error_(c.error) {} + + T const& getItem() const { return item_; } + uint64_t getValue() const { return value_; } + uint64_t getError() const { return error_; } +}; + +/** + * @brief Space Saving algorithm implementation also know as "HeavyHitter". + * based on the "Space Saving algorithm", AKA "HeavyHitter" + * See: + * https://cse.hkust.edu.hk/~raywong/comp5331/References/EfficientComputationOfFrequentAndTop-kElementsInDataStreams.pdf + * https://github.com/fzakaria/space-saving/tree/master + * + */ +template class StreamSummary { +private: + const size_t capacity_; + uint64_t n_{}; + absl::flat_hash_map> cache_{}; + std::list> buckets_{}; + + typename detail::CounterIterator incrementCounter(detail::CounterIterator counter_iter, + uint64_t increment) { + auto const bucket = counter_iter->bucket; + auto bucket_next = std::prev(bucket); + counter_iter->value += increment; + + detail::CounterIterator elem; + if (bucket_next != buckets_.end() && counter_iter->value == bucket_next->value) { + counter_iter->bucket = bucket_next; + bucket_next->children.splice(bucket_next->children.end(), bucket->children, counter_iter); + elem = std::prev(bucket_next->children.end()); + } else { + auto bucket_new = buckets_.emplace(bucket, counter_iter->value); + counter_iter->bucket = bucket_new; + bucket_new->children.splice(bucket_new->children.end(), bucket->children, counter_iter); + elem = std::prev(bucket_new->children.end()); + } + if (bucket->children.empty()) { + buckets_.erase(bucket); + } + return elem; + } + + absl::Status validateInternal() const { + auto cache_copy = cache_; + auto current_bucket = buckets_.begin(); + uint64_t value_sum = 0; + while (current_bucket != buckets_.end()) { + auto prev = std::prev(current_bucket); + if (prev != buckets_.end() && prev->value <= current_bucket->value) { + return absl::InternalError("buckets should be in descending order."); + } + auto current_child = current_bucket->children.begin(); + while (current_child != current_bucket->children.end()) { + if (current_child->bucket != current_bucket || + current_child->value != current_bucket->value) { + return absl::InternalError("entry does not point to a bucket with the same value."); + } + if (current_child->item) { + auto old_iter = cache_copy.find(*current_child->item); + if (old_iter != cache_copy.end()) { + cache_copy.erase(old_iter); + } + } + value_sum += current_child->value; + current_child++; + } + current_bucket++; + } + if (!cache_copy.empty() || cache_.size() > capacity_ || value_sum != n_) { + return absl::InternalError("unexpected size."); + } + return absl::OkStatus(); + } + + inline void validateDbg() { +#if !defined(NDEBUG) + ASSERT(validate().ok()); +#endif + } + +public: + explicit StreamSummary(size_t capacity) : capacity_(capacity) { + auto& new_bucket = buckets_.emplace_back(0); + for (size_t i = 0; i < capacity; ++i) { + // initialize with empty counters, optional item will not be set + new_bucket.children.emplace_back(buckets_.begin()); + } + validateDbg(); + } + + size_t getCapacity() const { return capacity_; } + + absl::Status validate() const { return validateInternal(); } + + Counter offer(T const& item, uint64_t increment = 1) { + ++n_; + auto iter = cache_.find(item); + if (iter != cache_.end()) { + iter->second = incrementCounter(iter->second, increment); + validateDbg(); + return *iter->second; + } else { + auto min_element = std::prev(buckets_.back().children.end()); + auto original_min_value = min_element->value; + if (min_element + ->item) { // element was already used (otherwise optional item would be not set) + // remove old from cache + auto old_iter = cache_.find(*min_element->item); + if (old_iter != cache_.end()) { + cache_.erase(old_iter); + } + } + min_element->item = item; + min_element = incrementCounter(min_element, increment); + cache_[item] = min_element; + if (cache_.size() <= capacity_) { + // should always be true, but keep it to be aligned to reference implementation + // originalMinValue will be 0 if element wasn't already used + min_element->error = original_min_value; + } + validateDbg(); + return *min_element; + } + } + + uint64_t getN() const { return n_; } + + typename std::list> getTopK(size_t k = SIZE_MAX) const { + std::list> r; + for (auto const& bucket : buckets_) { + for (auto const& child : bucket.children) { + if (child.item) { + r.emplace_back(child); + if (r.size() == k) { + return r; + } + } + } + } + return r; + } +}; + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h b/source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h new file mode 100644 index 000000000000..e87e55f8c8d1 --- /dev/null +++ b/source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#include "absl/strings/str_cat.h" +#include "openssl/md5.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace { + +/** + * @brief Calculates a Dynatrace specific tenant id which is used in the Dynatrace tag added to the + * tracestate header. + * + * @param file_name The tenant as received via config file + * @return the tenant id as Hex + */ +absl::Hex calculateTenantId(std::string tenant_uuid) { + if (tenant_uuid.empty()) { + return absl::Hex(0); + } + + for (char& c : tenant_uuid) { + if (c & 0x80) { + c = 0x3f; // '?' + } + } + + uint8_t digest[16]; + MD5(reinterpret_cast(tenant_uuid.data()), tenant_uuid.size(), digest); + + int32_t hash = 0; + for (int i = 0; i < 16; i++) { + const int shift_for_target_byte = (3 - (i % 4)) * 8; + // 24, 16, 8, 0 respectively + hash ^= + (static_cast(digest[i]) << shift_for_target_byte) & (0xff << shift_for_target_byte); + } + return absl::Hex(hash); +} +} // namespace +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/tracers/opentelemetry/tracer.cc b/source/extensions/tracers/opentelemetry/tracer.cc index 8c8553015510..ec28f6f69b18 100644 --- a/source/extensions/tracers/opentelemetry/tracer.cc +++ b/source/extensions/tracers/opentelemetry/tracer.cc @@ -38,7 +38,7 @@ void callSampler(SamplerSharedPtr sampler, const absl::optional spa return; } const auto sampling_result = - sampler->shouldSample(span_context, operation_name, new_span.getTraceIdAsHex(), + sampler->shouldSample(span_context, new_span.getTraceIdAsHex(), operation_name, new_span.spankind(), trace_context, {}); new_span.setSampled(sampling_result.isSampled()); diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD new file mode 100644 index 000000000000..b708876da067 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/BUILD @@ -0,0 +1,62 @@ +load( + "//bazel:envoy_build_system.bzl", + "envoy_package", +) +load( + "//test/extensions:extensions_build_system.bzl", + "envoy_extension_cc_test", +) + +licenses(["notice"]) # Apache 2 + +envoy_package() + +envoy_extension_cc_test( + name = "config_test", + srcs = ["config_test.cc"], + extension_names = ["envoy.tracers.opentelemetry.samplers.dynatrace"], + deps = [ + "//envoy/registry", + "//source/extensions/tracers/opentelemetry/samplers/dynatrace:config", + "//source/extensions/tracers/opentelemetry/samplers/dynatrace:dynatrace_sampler_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:utility_lib", + ], +) + +envoy_extension_cc_test( + name = "dynatrace_sampler_test", + srcs = [ + "dynatrace_sampler_test.cc", + "sampler_config_provider_test.cc", + "sampler_config_test.cc", + "sampling_controller_test.cc", + "stream_summary_test.cc", + "tenant_id_test.cc", + ], + extension_names = ["envoy.tracers.opentelemetry.samplers.dynatrace"], + deps = [ + "//source/extensions/tracers/opentelemetry/samplers/dynatrace:dynatrace_sampler_lib", + "//test/mocks/server:tracer_factory_context_mocks", + "//test/test_common:utility_lib", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + "@envoy_api//envoy/extensions/tracers/opentelemetry/samplers/v3:pkg_cc_proto", + ], +) + +envoy_extension_cc_test( + name = "dynatrace_sampler_integration_test", + srcs = [ + "dynatrace_sampler_integration_test.cc", + ], + extension_names = ["envoy.tracers.opentelemetry.samplers.dynatrace"], + deps = [ + "//source/exe:main_common_lib", + "//source/extensions/tracers/opentelemetry:config", + "//source/extensions/tracers/opentelemetry/resource_detectors/dynatrace:config", + "//source/extensions/tracers/opentelemetry/samplers/dynatrace:config", + "//test/integration:http_integration_lib", + "//test/test_common:utility_lib", + "@envoy_api//envoy/extensions/filters/network/http_connection_manager/v3:pkg_cc_proto", + ], +) diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/config_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/config_test.cc new file mode 100644 index 000000000000..6860df5c0ece --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/config_test.cc @@ -0,0 +1,38 @@ +#include "envoy/registry/registry.h" + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/config.h" + +#include "test/mocks/server/tracer_factory_context.h" +#include "test/test_common/utility.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +// Test creating a Dynatrace sampler via factory +TEST(DynatraceSamplerFactoryTest, Test) { + auto* factory = Registry::FactoryRegistry::getFactory( + "envoy.tracers.opentelemetry.samplers.dynatrace"); + ASSERT_NE(factory, nullptr); + EXPECT_STREQ(factory->name().c_str(), "envoy.tracers.opentelemetry.samplers.dynatrace"); + EXPECT_NE(factory->createEmptyConfigProto(), nullptr); + + envoy::config::core::v3::TypedExtensionConfig typed_config; + const std::string sampler_yaml = R"EOF( + name: envoy.tracers.opentelemetry.samplers.dynatrace + typed_config: + "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.samplers.v3.DynatraceSamplerConfig + )EOF"; + TestUtility::loadFromYaml(sampler_yaml, typed_config); + + NiceMock context; + EXPECT_NE(factory->createSampler(typed_config.typed_config(), context), nullptr); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc new file mode 100644 index 000000000000..a3887eaf5ab4 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_integration_test.cc @@ -0,0 +1,151 @@ +#include +#include + +#include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" + +#include "test/integration/http_integration.h" +#include "test/test_common/utility.h" + +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { +namespace { + +const char* TRACEPARENT_VALUE = "00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01"; +const char* TRACEPARENT_VALUE_START = "00-0af7651916cd43dd8448eb211c80319c"; + +class DynatraceSamplerIntegrationTest : public Envoy::HttpIntegrationTest, + public testing::TestWithParam { +public: + DynatraceSamplerIntegrationTest() : HttpIntegrationTest(Http::CodecType::HTTP1, GetParam()) { + const std::string yaml_string = R"EOF( + provider: + name: envoy.tracers.opentelemetry + typed_config: + "@type": type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig + grpc_service: + envoy_grpc: + cluster_name: opentelemetry_collector + timeout: 0.250s + service_name: "a_service_name" + sampler: + name: envoy.tracers.opentelemetry.samplers.dynatrace + typed_config: + "@type": type.googleapis.com/envoy.extensions.tracers.opentelemetry.samplers.v3.DynatraceSamplerConfig + tenant: "abc12345" + cluster_id: -1743916452 + )EOF"; + + auto tracing_config = + std::make_unique<::envoy::extensions::filters::network::http_connection_manager::v3:: + HttpConnectionManager_Tracing>(); + TestUtility::loadFromYaml(yaml_string, *tracing_config.get()); + config_helper_.addConfigModifier( + [&](envoy::extensions::filters::network::http_connection_manager::v3::HttpConnectionManager& + hcm) -> void { hcm.set_allocated_tracing(tracing_config.release()); }); + + initialize(); + codec_client_ = makeHttpConnection(lookupPort("http")); + } +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, DynatraceSamplerIntegrationTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +// Sends a request with traceparent and tracestate header. +TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentAndTracestate) { + // tracestate does not contain a Dynatrace tag + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, + {":authority", "host"}, {"tracestate", "key=value"}, {"traceparent", TRACEPARENT_VALUE}}; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ(response->headers().getStatusValue(), "200"); + + // traceparent should be set: traceid should be re-used, span id should be different + absl::string_view traceparent_value = upstream_request_->headers() + .get(Http::LowerCaseString("traceparent"))[0] + ->value() + .getStringView(); + EXPECT_TRUE(absl::StartsWith(traceparent_value, TRACEPARENT_VALUE_START)); + EXPECT_NE(TRACEPARENT_VALUE, traceparent_value); + // Dynatrace tracestate should be added to existing tracestate + absl::string_view tracestate_value = upstream_request_->headers() + .get(Http::LowerCaseString("tracestate"))[0] + ->value() + .getStringView(); + // use StartsWith because path-info (last element in trace state) contains a random value + EXPECT_TRUE(absl::StartsWith(tracestate_value, "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;")) + << "Received tracestate: " << tracestate_value; + EXPECT_TRUE(absl::StrContains(tracestate_value, ",key=value")) + << "Received tracestate: " << tracestate_value; +} + +// Sends a request with traceparent but no tracestate header. +TEST_P(DynatraceSamplerIntegrationTest, TestWithTraceparentOnly) { + Http::TestRequestHeaderMapImpl request_headers{{":method", "GET"}, + {":path", "/test/long/url"}, + {":scheme", "http"}, + {":authority", "host"}, + {"traceparent", TRACEPARENT_VALUE}}; + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ(response->headers().getStatusValue(), "200"); + + // traceparent should be set: traceid should be re-used, span id should be different + absl::string_view traceparent_value = upstream_request_->headers() + .get(Http::LowerCaseString("traceparent"))[0] + ->value() + .getStringView(); + EXPECT_TRUE(absl::StartsWith(traceparent_value, TRACEPARENT_VALUE_START)); + EXPECT_NE(TRACEPARENT_VALUE, traceparent_value); + // Dynatrace tag should be added to tracestate + absl::string_view tracestate_value = upstream_request_->headers() + .get(Http::LowerCaseString("tracestate"))[0] + ->value() + .getStringView(); + // use StartsWith because path-info (last element in trace state contains a random value) + EXPECT_TRUE(absl::StartsWith(tracestate_value, "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;")) + << "Received tracestate: " << tracestate_value; +} + +// Sends a request without traceparent and tracestate header. +TEST_P(DynatraceSamplerIntegrationTest, TestWithoutTraceparentAndTracestate) { + Http::TestRequestHeaderMapImpl request_headers{ + {":method", "GET"}, {":path", "/test/long/url"}, {":scheme", "http"}, {":authority", "host"}}; + + auto response = sendRequestAndWaitForResponse(request_headers, 0, default_response_headers_, 0); + + ASSERT_TRUE(response->waitForEndStream()); + EXPECT_TRUE(response->complete()); + EXPECT_EQ(response->headers().getStatusValue(), "200"); + + // traceparent will be added, trace_id and span_id will be generated, so there is nothing we can + // assert + EXPECT_EQ(upstream_request_->headers().get(::Envoy::Http::LowerCaseString("traceparent")).size(), + 1); + // Dynatrace tag should be added to tracestate + absl::string_view tracestate_value = upstream_request_->headers() + .get(Http::LowerCaseString("tracestate"))[0] + ->value() + .getStringView(); + EXPECT_TRUE(absl::StartsWith(tracestate_value, "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;")) + << "Received tracestate: " << tracestate_value; +} + +} // namespace +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc new file mode 100644 index 000000000000..44f09c7a600c --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler_test.cc @@ -0,0 +1,336 @@ +#include +#include + +#include "envoy/extensions/tracers/opentelemetry/samplers/v3/dynatrace_sampler.pb.h" + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h" +#include "source/extensions/tracers/opentelemetry/span_context.h" + +#include "test/mocks/server/tracer_factory_context.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace { + +const char* trace_id = "67a9a23155e1741b5b35368e08e6ece5"; + +const char* parent_span_id = "9d83def9a4939b7b"; + +const char* dt_tracestate_ignored = + "5b3f9fed-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;8eae;2h01;3h4af38366;4h00;5h01;" + "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; +const char* dt_tracestate_sampled = + "5b3f9fed-980df25c@dt=fw4;4;4af38366;0;0;0;0;123;8eae;2h01;3h4af38366;4h00;5h01;" + "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; +const char* dt_tracestate_ignored_different_tenant = + "6666ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;8eae;2h01;3h4af38366;4h00;5h01;" + "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; + +} // namespace + +class MockSamplerConfigProvider : public SamplerConfigProvider { +public: + MOCK_METHOD(const SamplerConfig&, getSamplerConfig, (), (const override)); +}; + +class DynatraceSamplerTest : public testing::Test { + + const std::string yaml_string_ = R"EOF( + tenant: "abc12345" + cluster_id: -1743916452 + )EOF"; + +public: + DynatraceSamplerTest() { + TestUtility::loadFromYaml(yaml_string_, proto_config_); + auto scf = std::make_unique>(); + ON_CALL(*scf, getSamplerConfig()).WillByDefault(testing::ReturnRef(sampler_config_)); + + timer_ = new NiceMock( + &tracer_factory_context_.server_factory_context_.dispatcher_); + ON_CALL(tracer_factory_context_.server_factory_context_.dispatcher_, createTimer_(_)) + .WillByDefault(Invoke([this](Event::TimerCb) { return timer_; })); + sampler_ = + std::make_unique(proto_config_, tracer_factory_context_, std::move(scf)); + } + +protected: + NiceMock tracer_factory_context_; + envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig proto_config_; + SamplerConfig sampler_config_{SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT}; + NiceMock* timer_; + std::unique_ptr sampler_; +}; + +// Verify getDescription +TEST_F(DynatraceSamplerTest, TestGetDescription) { + EXPECT_STREQ(sampler_->getDescription().c_str(), "DynatraceSampler"); +} + +// Verify sampler being invoked with an invalid/empty span context +TEST_F(DynatraceSamplerTest, TestWithoutParentContext) { + auto sampling_result = + sampler_->shouldSample(absl::nullopt, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_EQ(sampling_result.attributes->size(), 1); + EXPECT_STREQ( + sampling_result.attributes->find("supportability.atm_sampling_ratio")->second.c_str(), "1"); + EXPECT_STREQ(sampling_result.tracestate.c_str(), "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95"); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked without a Dynatrace tracestate +TEST_F(DynatraceSamplerTest, TestWithUnknownParentContext) { + SpanContext parent_context("00", trace_id, parent_span_id, true, "some_vendor=some_value"); + + auto sampling_result = + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_EQ(sampling_result.attributes->size(), 1); + EXPECT_STREQ( + sampling_result.attributes->find("supportability.atm_sampling_ratio")->second.c_str(), "1"); + // Dynatrace tracestate should be prepended + EXPECT_STREQ(sampling_result.tracestate.c_str(), + "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95,some_vendor=some_value"); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with Dynatrace trace state +TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextSampled) { + SpanContext parent_context("00", trace_id, parent_span_id, true, dt_tracestate_sampled); + + auto sampling_result = + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_EQ(sampling_result.attributes->size(), 1); + EXPECT_STREQ( + sampling_result.attributes->find("supportability.atm_sampling_ratio")->second.c_str(), "1"); + // tracestate should be forwarded + EXPECT_STREQ(sampling_result.tracestate.c_str(), dt_tracestate_sampled); + // sampling decision from parent should be respected + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with an invalid Dynatrace trace state +TEST_F(DynatraceSamplerTest, TestWithInvalidDynatraceParentContext) { + const char* invalidts = "5b3f9fed-980df25c@dt=fw4;4"; + SpanContext parent_context("00", trace_id, parent_span_id, true, invalidts); + + auto sampling_result = + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_STREQ(sampling_result.tracestate.c_str(), + "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95,5b3f9fed-980df25c@dt=fw4;4"); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with an invalid Dynatrace trace state +TEST_F(DynatraceSamplerTest, TestWithInvalidDynatraceParentContext1) { + // invalid tracestate[6] has to be an int + const char* invalidts = "5b3f9fed-980df25c@dt=fw4;4;4af38366;0;0;0;X;123"; + SpanContext parent_context("00", trace_id, parent_span_id, true, invalidts); + + auto sampling_result = + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_STREQ( + sampling_result.tracestate.c_str(), + "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95,5b3f9fed-980df25c@dt=fw4;4;4af38366;0;0;0;X;123"); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with an old Dynatrace trace state version +TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextOtherVersion) { + const char* oldts = + "5b3f9fed-980df25c@dt=fw3;4;4af38366;0;0;0;0;123;8eae;2h01;3h4af38366;4h00;5h01;" + "6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; + SpanContext parent_context("00", trace_id, parent_span_id, true, oldts); + + auto sampling_result = + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_STREQ( + sampling_result.tracestate.c_str(), + "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95,5b3f9fed-980df25c@dt=fw3;4;4af38366;0;0;0;0;123;" + "8eae;2h01;3h4af38366;4h00;5h01;6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with Dynatrace trace parent where ignored flag is set +TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextIgnored) { + SpanContext parent_context("00", trace_id, parent_span_id, true, dt_tracestate_ignored); + + auto sampling_result = + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + EXPECT_EQ(sampling_result.decision, Decision::Drop); + EXPECT_EQ(sampling_result.attributes->size(), 2); + EXPECT_STREQ( + sampling_result.attributes->find("supportability.atm_sampling_ratio")->second.c_str(), "4"); + EXPECT_STREQ(sampling_result.attributes->find("sampling.threshold")->second.c_str(), + "54043195528445952"); + // tracestate should be forwarded + EXPECT_STREQ(sampling_result.tracestate.c_str(), dt_tracestate_ignored); + // sampling decision from parent should be respected + EXPECT_FALSE(sampling_result.isRecording()); + EXPECT_FALSE(sampling_result.isSampled()); +} + +// Verify sampler being invoked with Dynatrace trace parent from a different tenant +TEST_F(DynatraceSamplerTest, TestWithDynatraceParentContextFromDifferentTenant) { + SpanContext parent_context("00", trace_id, parent_span_id, true, + dt_tracestate_ignored_different_tenant); + + auto sampling_result = + sampler_->shouldSample(parent_context, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, {}, {}); + // sampling decision on tracestate should be ignored because it is from a different tenant. + EXPECT_EQ(sampling_result.decision, Decision::RecordAndSample); + EXPECT_EQ(sampling_result.attributes->size(), 1); + EXPECT_STREQ( + sampling_result.attributes->find("supportability.atm_sampling_ratio")->second.c_str(), "1"); + // new Dynatrace tag should be prepended, already existing tag should be kept + const char* exptected = + "5b3f9fed-980df25c@dt=fw4;0;0;0;0;0;0;95,6666ad40-980df25c@dt=fw4;4;4af38366;0;0;1;2;123;" + "8eae;2h01;3h4af38366;4h00;5h01;6h67a9a23155e1741b5b35368e08e6ece5;7h9d83def9a4939b7b"; + EXPECT_STREQ(sampling_result.tracestate.c_str(), exptected); + EXPECT_TRUE(sampling_result.isRecording()); + EXPECT_TRUE(sampling_result.isSampled()); +} + +// Verify sampler being called during warm up phase (no recent top_k available) +TEST_F(DynatraceSamplerTest, TestWarmup) { + // config should allow 200 root spans per minute + sampler_config_.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); + + Tracing::TestTraceContextImpl trace_context_1{}; + trace_context_1.context_method_ = "GET"; + trace_context_1.context_path_ = "/path"; + + // timer is not invoked, because we want to test warm up phase. + // we use 200 as threshold. As long as number of requests is < (threshold/2), exponent should be 0 + uint32_t ignored = 0; + uint32_t sampled = 0; + for (int i = 0; i < 99; i++) { + auto result = sampler_->shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + result.isSampled() ? sampled++ : ignored++; + } + EXPECT_EQ(ignored, 0); + EXPECT_EQ(sampled, 99); + + // next (threshold/2) spans will get exponent 1, every second span will be sampled + for (int i = 0; i < 100; i++) { + auto result = sampler_->shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + result.isSampled() ? sampled++ : ignored++; + } + // should be 50 ignored, but the used "random" in shouldSample does not produce the same odd/even + // numbers. + EXPECT_EQ(ignored, 41); + EXPECT_EQ(sampled, 158); + + // send more requests + for (int i = 0; i < 100; i++) { + auto result = sampler_->shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + result.isSampled() ? sampled++ : ignored++; + } + // exponent should be 2, with a perfect random we would get 25 sampled and 75 ignored. + EXPECT_EQ(ignored, 113); + EXPECT_EQ(sampled, 186); + + // send more requests. + for (int i = 0; i < 700; i++) { + auto result = sampler_->shouldSample({}, std::to_string(1000 + i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + result.isSampled() ? sampled++ : ignored++; + } + // with a perfect random, the number of sampled paths would be lower than threshold (200) + // We don't care about exceeding the threshold because it is not a hard limit + EXPECT_EQ(ignored, 791); + EXPECT_EQ(sampled, 208); +} + +// Verify sampling if number of configured spans per minute is exceeded. +TEST_F(DynatraceSamplerTest, TestSampling) { + // config should allow 200 root spans per minute + sampler_config_.parse("{\n \"rootSpansPerMinute\" : 200 \n }"); + + Tracing::TestTraceContextImpl trace_context_1{}; + trace_context_1.context_method_ = "GET"; + trace_context_1.context_path_ = "/path"; + Tracing::TestTraceContextImpl trace_context_2{}; + trace_context_2.context_method_ = "POST"; + trace_context_2.context_path_ = "/path"; + Tracing::TestTraceContextImpl trace_context_3{}; + trace_context_3.context_method_ = "POST"; + trace_context_3.context_path_ = "/another_path"; + + // simulate requests + for (int i = 0; i < 180; i++) { + sampler_->shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + sampler_->shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_2, {}); + } + + sampler_->shouldSample({}, trace_id, "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, trace_context_3, + {}); + + // sampler should update sampling exponents based on number of requests in the previous period + timer_->invokeCallback(); + + // the sampler should not sample every span for 'trace_context_1' + // we call it again 10 times. This should be enough to get at least one ignored span + // 'i' is used as 'random trace_id' + bool ignored = false; + for (int i = 0; i < 10; i++) { + auto result = sampler_->shouldSample({}, std::to_string(i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_1, {}); + if (!result.isSampled()) { + ignored = true; + break; + } + } + EXPECT_TRUE(ignored); + + // trace_context_3 should always be sampled. + for (int i = 0; i < 10; i++) { + auto result = sampler_->shouldSample({}, std::to_string(i), "operation_name", + ::opentelemetry::proto::trace::v1::Span::SPAN_KIND_SERVER, + trace_context_2, {}); + EXPECT_TRUE(result.isSampled()); + } +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider_test.cc new file mode 100644 index 000000000000..d5a8c68d71b8 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider_test.cc @@ -0,0 +1,95 @@ +#include +#include +#include + +#include "envoy/config/core/v3/http_uri.pb.h" + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_provider.h" + +#include "test/mocks/common.h" +#include "test/mocks/http/mocks.h" +#include "test/mocks/server/tracer_factory_context.h" +#include "test/mocks/tracing/mocks.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +using testing::NiceMock; +using testing::Return; +using testing::ReturnRef; + +class SamplerConfigProviderTest : public testing::Test { +public: +protected: + NiceMock tracer_factory_context_; +}; + +TEST_F(SamplerConfigProviderTest, TestValueConfigured) { + const std::string yaml_string = R"EOF( + tenant: "abc12345" + cluster_id: -1743916452 + token: "tokenval" + http_uri: + cluster: "cluster_name" + uri: "https://testhost.com/otlp/v1/traces" + timeout: 0.250s + root_spans_per_minute: 3456 + + )EOF"; + + envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config); + EXPECT_EQ(config_provider.getSamplerConfig().getRootSpansPerMinute(), 3456); +} + +TEST_F(SamplerConfigProviderTest, TestNoValueConfigured) { + const std::string yaml_string = R"EOF( + tenant: "abc12345" + cluster_id: -1743916452 + token: "tokenval" + http_uri: + cluster: "cluster_name" + uri: "https://testhost.com/otlp/v1/traces" + timeout: 0.250s + + )EOF"; + + envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config); + EXPECT_EQ(config_provider.getSamplerConfig().getRootSpansPerMinute(), + SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); +} + +TEST_F(SamplerConfigProviderTest, TestValueZeroConfigured) { + const std::string yaml_string = R"EOF( + tenant: "abc12345" + cluster_id: -1743916452 + token: "tokenval" + http_uri: + cluster: "cluster_name" + uri: "https://testhost.com/otlp/v1/traces" + timeout: 0.250s + root_spans_per_minute: 0 + )EOF"; + + envoy::extensions::tracers::opentelemetry::samplers::v3::DynatraceSamplerConfig proto_config; + TestUtility::loadFromYaml(yaml_string, proto_config); + + SamplerConfigProviderImpl config_provider(tracer_factory_context_, proto_config); + EXPECT_EQ(config_provider.getSamplerConfig().getRootSpansPerMinute(), + SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc new file mode 100644 index 000000000000..8cb45a8ca3e6 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config_test.cc @@ -0,0 +1,64 @@ +#include +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampler_config.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +// Test sampler config json parsing +TEST(SamplerConfigTest, TestParsing) { + // default_root_spans_per_minute not set, ROOT_SPANS_PER_MINUTE_DEFAULT should be used + SamplerConfig config(0); + config.parse("{\n \"rootSpansPerMinute\" : 2000 \n }"); + EXPECT_EQ(config.getRootSpansPerMinute(), 2000u); + config.parse("{\n \"rootSpansPerMinute\" : 10000 \n }"); + EXPECT_EQ(config.getRootSpansPerMinute(), 10000u); + + // unexpected json, default value should be used + config.parse("{}"); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + + config.parse(""); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + + config.parse("\\"); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + + config.parse(" { "); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + + config.parse("{\n \"rootSpansPerMinute\" : 10000 "); // closing } is missing + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); +} + +// Test sampler config default root spans per minute +TEST(SamplerConfigTest, TestDefaultConfig) { + { + SamplerConfig config(0); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + config.parse(" { "); // parse invalid json, default value should still be used + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + } + { + SamplerConfig config(900); + EXPECT_EQ(config.getRootSpansPerMinute(), 900); + config.parse(" { "); + EXPECT_EQ(config.getRootSpansPerMinute(), 900); + } + { + SamplerConfig config(SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + config.parse(" { "); + EXPECT_EQ(config.getRootSpansPerMinute(), SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT); + } +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc new file mode 100644 index 000000000000..f56f2c86d4a3 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller_test.cc @@ -0,0 +1,321 @@ +#include +#include +#include +#include +#include +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/dynatrace_sampler.h" +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/sampling_controller.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace { + +// helper to offer a value multiple times to SamplingController +void offerEntry(SamplingController& sc, const std::string& value, int count) { + for (int i = 0; i < count; i++) { + sc.offer(value); + } +} + +} // namespace + +class TestSamplerConfigProvider : public SamplerConfigProvider { +public: + TestSamplerConfigProvider( + uint32_t root_spans_per_minute = SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT) + : config(root_spans_per_minute) {} + const SamplerConfig& getSamplerConfig() const override { return config; } + SamplerConfig config; +}; + +// Test with multiple different sampling keys (StreamSummary size exceeded) +TEST(SamplingControllerTest, TestStreamSummarySizeExceeded) { + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + offerEntry(sc, "1", 2000); + offerEntry(sc, "2", 1000); + offerEntry(sc, "3", 750); + offerEntry(sc, "4", 100); + offerEntry(sc, "5", 50); + // add unique sampling keys + for (int64_t i = 0; i < 2100; i++) { + sc.offer(std::to_string(i + 1000000)); + } + + sc.update(); + + EXPECT_EQ(sc.getEffectiveCount(), 1110); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 128); + EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 64); + EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 64); + EXPECT_EQ(sc.getSamplingState("4").getMultiplicity(), 8); + EXPECT_EQ(sc.getSamplingState("5").getMultiplicity(), 4); + EXPECT_EQ(sc.getSamplingState("1000000").getMultiplicity(), 2); + EXPECT_EQ(sc.getSamplingState("1000001").getMultiplicity(), 2); + EXPECT_EQ(sc.getSamplingState("1000002").getMultiplicity(), 2); +} + +// Test with 0 root span per minute +TEST(SamplingControllerTest, TestWithZeroAllowedSpan) { + // using 0 does not make sense, but let's ensure there is divide by zero + auto scf = std::make_unique(0); + SamplingController sc(std::move(scf)); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 1); + sc.update(); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 1); + offerEntry(sc, "1", 1); + sc.update(); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 1); +} + +// Test with 1 root span per minute +TEST(SamplingControllerTest, TestWithOneAllowedSpan) { + auto scf = std::make_unique(1); + SamplingController sc(std::move(scf)); + sc.update(); + EXPECT_EQ(sc.getSamplingState("1").getExponent(), SamplingController::MAX_SAMPLING_EXPONENT); + offerEntry(sc, "1", 1); + EXPECT_EQ(sc.getSamplingState("1").getExponent(), SamplingController::MAX_SAMPLING_EXPONENT); + sc.update(); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 1); +} + +// Test with StreamSummary size not exceeded +TEST(SamplingControllerTest, TestStreamSummarySizeNotExceeded) { + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + offerEntry(sc, "1", 8600); + offerEntry(sc, "2", 5000); + offerEntry(sc, "3", 4000); + offerEntry(sc, "4", 4000); + offerEntry(sc, "5", 3000); + offerEntry(sc, "6", 30); + offerEntry(sc, "7", 3); + offerEntry(sc, "8", 1); + + sc.update(); + + EXPECT_EQ(sc.getEffectiveCount(), 1074); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 64); + EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 32); + EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 32); + EXPECT_EQ(sc.getSamplingState("4").getMultiplicity(), 16); + EXPECT_EQ(sc.getSamplingState("5").getMultiplicity(), 8); + EXPECT_EQ(sc.getSamplingState("6").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("7").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("8").getMultiplicity(), 1); +} + +// Test with StreamSummary size not exceeded +TEST(SamplingControllerTest, TestStreamSummarySizeNotExceeded1) { + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + offerEntry(sc, "1", 7500); + offerEntry(sc, "2", 1000); + offerEntry(sc, "3", 1); + offerEntry(sc, "4", 1); + offerEntry(sc, "5", 1); + for (int64_t i = 0; i < 11; i++) { + sc.offer(std::to_string(i + 1000000)); + } + + sc.update(); + + EXPECT_EQ(sc.getEffectiveCount(), 1451); + EXPECT_EQ(sc.getSamplingState("1").getMultiplicity(), 8); + EXPECT_EQ(sc.getSamplingState("2").getMultiplicity(), 2); + EXPECT_EQ(sc.getSamplingState("3").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("4").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("5").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("1000000").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("1000001").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("1000002").getMultiplicity(), 1); + EXPECT_EQ(sc.getSamplingState("1000003").getMultiplicity(), 1); +} + +// Test using a sampler config having non-default root spans per minute +TEST(SamplingControllerTest, TestNonDefaultRootSpansPerMinute) { + auto scf = std::make_unique(); + scf->config.parse("{\n \"rootSpansPerMinute\" : 100 \n }"); + SamplingController sc(std::move(scf)); + + offerEntry(sc, "GET_xxxx", 300); + offerEntry(sc, "POST_asdf", 200); + offerEntry(sc, "GET_asdf", 100); + + sc.update(); + + EXPECT_EQ(sc.getSamplingState("GET_xxxx").getExponent(), 3); + EXPECT_EQ(sc.getSamplingState("GET_xxxx").getMultiplicity(), 8); + + EXPECT_EQ(sc.getSamplingState("POST_asdf").getExponent(), 2); + EXPECT_EQ(sc.getSamplingState("POST_asdf").getMultiplicity(), 4); + + EXPECT_EQ(sc.getSamplingState("GET_asdf").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_asdf").getMultiplicity(), 2); +} + +// Test warm up phase (no SamplingState available) +TEST(SamplingControllerTest, TestWarmup) { + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + // offer entries, but don't call update(); + // sampling exponents table will be empty + // exponent will be calculated based on total count. + // same exponent for both existing and non-existing keys. + + offerEntry(sc, "GET_0", 10); + EXPECT_EQ(sc.getSamplingState("GET_0").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_2").getExponent(), 0); + + offerEntry(sc, "GET_1", 540); + // threshold/2 reached, sampling exponent is set to 1 + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_2").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_3").getExponent(), 1); + + offerEntry(sc, "GET_2", 300); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_2").getExponent(), 1); + EXPECT_EQ(sc.getSamplingState("GET_123").getExponent(), 1); + + offerEntry(sc, "GET_4", 550); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 2); + EXPECT_EQ(sc.getSamplingState("GET_4").getExponent(), 2); + EXPECT_EQ(sc.getSamplingState("GET_234").getExponent(), 2); + + offerEntry(sc, "GET_5", 1000); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 4); + EXPECT_EQ(sc.getSamplingState("GET_5").getExponent(), 4); + EXPECT_EQ(sc.getSamplingState("GET_456").getExponent(), 4); + + offerEntry(sc, "GET_6", 2000); + EXPECT_EQ(sc.getSamplingState("GET_1").getExponent(), 8); + EXPECT_EQ(sc.getSamplingState("GET_6").getExponent(), 8); + EXPECT_EQ(sc.getSamplingState("GET_789").getExponent(), 8); +} + +// Test getting sampling state from an empty SamplingController +TEST(SamplingControllerTest, TestEmpty) { + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + sc.update(); + // default SamplingState is expected + EXPECT_EQ(sc.getSamplingState("GET_something").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("GET_something").getMultiplicity(), 1); +} + +// Test getting sampling state for an unknown key from a non-empty SamplingController +TEST(SamplingControllerTest, TestUnknown) { + auto scf = std::make_unique(); + SamplingController sc(std::move(scf)); + + sc.offer("key1"); + sc.update(); + + EXPECT_EQ(sc.getSamplingState("key2").getExponent(), 0); + EXPECT_EQ(sc.getSamplingState("key2").getMultiplicity(), 1); + + // Exceed capacity, + for (uint32_t i = 0; i < SamplerConfig::ROOT_SPANS_PER_MINUTE_DEFAULT * 2; i++) { + sc.offer("key1"); + } + sc.update(); + // "key1" will get exponent 1 + EXPECT_EQ(sc.getSamplingState("key1").getExponent(), 1); + // unknown "key2" will get the same exponent + EXPECT_EQ(sc.getSamplingState("key2").getExponent(), 1); +} + +// Test increasing and decreasing sampling exponent +TEST(SamplingStateTest, TestIncreaseDecrease) { + SamplingState sst{}; + EXPECT_EQ(sst.getExponent(), 0); + EXPECT_EQ(sst.getMultiplicity(), 1); + + sst.increaseExponent(); + EXPECT_EQ(sst.getExponent(), 1); + EXPECT_EQ(sst.getMultiplicity(), 2); + + sst.increaseExponent(); + EXPECT_EQ(sst.getExponent(), 2); + EXPECT_EQ(sst.getMultiplicity(), 4); + + for (int i = 0; i < 6; i++) { + sst.increaseExponent(); + } + EXPECT_EQ(sst.getExponent(), 8); + EXPECT_EQ(sst.getMultiplicity(), 256); + + sst.decreaseExponent(); + EXPECT_EQ(sst.getExponent(), 7); + EXPECT_EQ(sst.getMultiplicity(), 128); +} + +// Test SamplingState shouldSample() +TEST(SamplingStateTest, TestShouldSample) { + // default sampling state should sample every request + SamplingState sst{}; + EXPECT_TRUE(sst.shouldSample(1234)); + EXPECT_TRUE(sst.shouldSample(2345)); + EXPECT_TRUE(sst.shouldSample(3456)); + EXPECT_TRUE(sst.shouldSample(4567)); + + // exponent 1, multiplicity 2, + // we are using % for sampling decision, so even (=not odd) random numbers should be sampled + sst.increaseExponent(); + EXPECT_TRUE(sst.shouldSample(22)); + EXPECT_TRUE(sst.shouldSample(4444444)); + EXPECT_FALSE(sst.shouldSample(21)); + EXPECT_FALSE(sst.shouldSample(111111)); + + for (int i = 0; i < 9; i++) { + sst.increaseExponent(); + } + // exponent 10, multiplicity 1024, + EXPECT_TRUE(sst.shouldSample(1024)); + EXPECT_TRUE(sst.shouldSample(2048)); + EXPECT_TRUE(sst.shouldSample(4096)); + EXPECT_TRUE(sst.shouldSample(10240000000)); + EXPECT_FALSE(sst.shouldSample(1023)); + EXPECT_FALSE(sst.shouldSample(1025)); + EXPECT_FALSE(sst.shouldSample(2047)); + EXPECT_FALSE(sst.shouldSample(2049)); +} + +// Test creating sampling key used to identify a request +TEST(SamplingControllerTest, TestGetSamplingKey) { + std::string key = SamplingController::getSamplingKey("somepath", "GET"); + EXPECT_STREQ(key.c_str(), "GET_somepath"); + + key = SamplingController::getSamplingKey("somepath?withquery", "POST"); + EXPECT_STREQ(key.c_str(), "POST_somepath"); + + key = SamplingController::getSamplingKey("anotherpath", "PUT"); + EXPECT_STREQ(key.c_str(), "PUT_anotherpath"); + + key = SamplingController::getSamplingKey("", "PUT"); + EXPECT_STREQ(key.c_str(), "PUT_"); + + key = SamplingController::getSamplingKey("anotherpath", ""); + EXPECT_STREQ(key.c_str(), "_anotherpath"); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc new file mode 100644 index 000000000000..f7f80cf68b93 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary_test.cc @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/stream_summary.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +namespace { + +// helper function to compare counters +template +void compareCounter(typename std::list>::iterator counter, T item, uint64_t value, + uint64_t error, int line_num) { + SCOPED_TRACE(absl::StrCat(__FUNCTION__, " called from line ", line_num)); + EXPECT_EQ(counter->getValue(), value); + EXPECT_EQ(counter->getItem(), item); + EXPECT_EQ(counter->getError(), error); +} + +} // namespace + +// Test an empty StreamSummary +TEST(StreamSummaryTest, TestEmpty) { + StreamSummary summary(4); + EXPECT_EQ(summary.getN(), 0); + auto top_k = summary.getTopK(); + EXPECT_EQ(top_k.size(), 0); + EXPECT_EQ(top_k.begin(), top_k.end()); + EXPECT_TRUE(summary.validate().ok()); +} + +// Test adding values, capacity not exceeded +TEST(StreamSummaryTest, TestSimple) { + StreamSummary summary(4); + summary.offer('a'); + summary.offer('a'); + summary.offer('b'); + summary.offer('a'); + summary.offer('c'); + summary.offer('b'); + summary.offer('a'); + summary.offer('d'); + + EXPECT_TRUE(summary.validate().ok()); + EXPECT_EQ(summary.getN(), 8); + + auto top_k = summary.getTopK(); + EXPECT_EQ(top_k.size(), 4); + auto it = top_k.begin(); + compareCounter(it, 'a', 4, 0, __LINE__); + compareCounter(++it, 'b', 2, 0, __LINE__); + compareCounter(++it, 'c', 1, 0, __LINE__); + compareCounter(++it, 'd', 1, 0, __LINE__); +} + +// Test adding values, capacity exceeded +TEST(StreamSummaryTest, TestExceedCapacity) { + StreamSummary summary(3); + EXPECT_TRUE(summary.validate().ok()); + summary.offer('d'); + summary.offer('a'); + summary.offer('b'); + summary.offer('a'); + summary.offer('a'); + summary.offer('a'); + summary.offer('b'); + summary.offer('c'); // 'd' will be dropped + summary.offer('b'); + summary.offer('c'); + EXPECT_TRUE(summary.validate().ok()); + + { + auto top_k = summary.getTopK(); + auto it = top_k.begin(); + EXPECT_EQ(top_k.size(), 3); + compareCounter(it, 'a', 4, 0, __LINE__); + compareCounter(++it, 'b', 3, 0, __LINE__); + compareCounter(++it, 'c', 3, 1, __LINE__); + } + + // add item 'e', 'c' should be removed. value for 'c' will be added to error for 'e' + summary.offer('e'); + { + auto top_k = summary.getTopK(); + auto it = top_k.begin(); + EXPECT_EQ(top_k.size(), 3); + compareCounter(it, 'a', 4, 0, __LINE__); + compareCounter(++it, 'e', 4, 3, __LINE__); + compareCounter(++it, 'b', 3, 0, __LINE__); + } +} + +// Test inserting items in random order. topK should not depend on the insert order. +TEST(StreamSummaryTest, TestRandomInsertOrder) { + std::vector v{'a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', + 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'e', 'e', 'f'}; + for (int i = 0; i < 5; ++i) { + // insert order should not matter if all items have a different count in input stream + std::shuffle(v.begin(), v.end(), std::default_random_engine()); + StreamSummary summary(10); + for (auto const c : v) { + summary.offer(c); + } + auto top_k = summary.getTopK(); + auto it = top_k.begin(); + compareCounter(it, 'a', 6, 0, __LINE__); + compareCounter(++it, 'b', 5, 0, __LINE__); + compareCounter(++it, 'c', 4, 0, __LINE__); + compareCounter(++it, 'd', 3, 0, __LINE__); + compareCounter(++it, 'e', 2, 0, __LINE__); + compareCounter(++it, 'f', 1, 0, __LINE__); + } +} + +// Test getTopK size parameter is handled as expected +TEST(StreamSummaryTest, TestGetTopKSize) { + std::vector v{'a', 'a', 'a', 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'b', + 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'e', 'e', 'f'}; + std::shuffle(v.begin(), v.end(), std::default_random_engine()); + StreamSummary summary(20); + for (auto const c : v) { + summary.offer(c); + } + EXPECT_EQ(summary.getTopK().size(), 6); + EXPECT_EQ(summary.getTopK(1).size(), 1); + EXPECT_EQ(summary.getTopK(2).size(), 2); + EXPECT_EQ(summary.getTopK(3).size(), 3); + EXPECT_EQ(summary.getTopK(4).size(), 4); + EXPECT_EQ(summary.getTopK(5).size(), 5); + EXPECT_EQ(summary.getTopK(6).size(), 6); + EXPECT_EQ(summary.getTopK(7).size(), 6); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id_test.cc b/test/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id_test.cc new file mode 100644 index 000000000000..17f9d47d6d38 --- /dev/null +++ b/test/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id_test.cc @@ -0,0 +1,31 @@ +#include "source/extensions/tracers/opentelemetry/samplers/dynatrace/tenant_id.h" + +#include "absl/strings/str_cat.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Extensions { +namespace Tracers { +namespace OpenTelemetry { + +// Test calculation using an empty string +TEST(TenantIdTest, TestEmpty) { EXPECT_EQ(absl::StrCat(calculateTenantId("")), "0"); } + +// Test calculation using strings with unexpected characters +TEST(TenantIdTest, TestUnexpected) { + EXPECT_EQ(absl::StrCat(calculateTenantId("abc 1234")), "182ccac"); + EXPECT_EQ(absl::StrCat(calculateTenantId(" ")), "b173ef2e"); + EXPECT_EQ(absl::StrCat(calculateTenantId("€someth")), "17bd71ec"); +} + +// Test calculation using some expected strings +TEST(TenantIdTest, TestValues) { + EXPECT_EQ(absl::StrCat(calculateTenantId("jmw13303")), "4d10bede"); + EXPECT_EQ(absl::StrCat(calculateTenantId("abc12345")), "5b3f9fed"); + EXPECT_EQ(absl::StrCat(calculateTenantId("?pfel")), "7712d29d"); +} + +} // namespace OpenTelemetry +} // namespace Tracers +} // namespace Extensions +} // namespace Envoy diff --git a/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc b/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc index 687e63617050..ee5a93679d4e 100644 --- a/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc +++ b/test/extensions/tracers/opentelemetry/samplers/sampler_test.cc @@ -56,6 +56,12 @@ class SamplerFactoryTest : public testing::Test { NiceMock context; }; +// Test tracer category() +TEST_F(SamplerFactoryTest, TestGetName) { + TestSamplerFactory factory; + EXPECT_STREQ(factory.category().c_str(), "envoy.tracers.opentelemetry.samplers"); +} + // Test OTLP tracer without a sampler TEST_F(SamplerFactoryTest, TestWithoutSampler) { // using StrictMock, calls to SamplerFactory would cause a test failure diff --git a/tools/spelling/spelling_dictionary.txt b/tools/spelling/spelling_dictionary.txt index 37e1296ac3d3..7f66087cef02 100644 --- a/tools/spelling/spelling_dictionary.txt +++ b/tools/spelling/spelling_dictionary.txt @@ -1324,6 +1324,7 @@ suf superclass superroot superset +supportability svc symlink symlinked From 44d4d8291708dca34e953d77d4b4b9382571870c Mon Sep 17 00:00:00 2001 From: Zhewei Hu Date: Thu, 29 Feb 2024 08:57:19 -0800 Subject: [PATCH 123/151] [ZK filter] Add missing operation supports for multi opcode (#32456) Signed-off-by: Zhewei Hu --- .../network/zookeeper_proxy/decoder.cc | 30 ++++++-- .../network/zookeeper_proxy/filter_test.cc | 72 ++++++++++++------- 2 files changed, 70 insertions(+), 32 deletions(-) diff --git a/source/extensions/filters/network/zookeeper_proxy/decoder.cc b/source/extensions/filters/network/zookeeper_proxy/decoder.cc index ec5edd68ea0c..58303e75cd31 100644 --- a/source/extensions/filters/network/zookeeper_proxy/decoder.cc +++ b/source/extensions/filters/network/zookeeper_proxy/decoder.cc @@ -137,8 +137,11 @@ absl::StatusOr> DecoderImpl::decodeOnData(Buffer::Instan status, fmt::format("parseGetDataRequest: {}", status.message())); break; case OpCodes::Create: + ABSL_FALLTHROUGH_INTENDED; case OpCodes::Create2: + ABSL_FALLTHROUGH_INTENDED; case OpCodes::CreateContainer: + ABSL_FALLTHROUGH_INTENDED; case OpCodes::CreateTtl: status = parseCreateRequest(data, offset, len.value(), opcode); RETURN_INVALID_ARG_ERR_IF_STATUS_NOT_OK( @@ -642,22 +645,37 @@ absl::Status DecoderImpl::parseMultiRequest(Buffer::Instance& data, uint64_t& of break; } - switch (static_cast(opcode.value())) { + const auto op = static_cast(opcode.value()); + switch (op) { case OpCodes::Create: - status = parseCreateRequest(data, offset, len, OpCodes::Create); - EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, OpCodes::Create); + ABSL_FALLTHROUGH_INTENDED; + case OpCodes::Create2: + ABSL_FALLTHROUGH_INTENDED; + case OpCodes::CreateContainer: + ABSL_FALLTHROUGH_INTENDED; + case OpCodes::CreateTtl: + status = parseCreateRequest(data, offset, len, op); + EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, op); break; case OpCodes::SetData: status = parseSetRequest(data, offset, len); - EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, OpCodes::SetData); + EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, op); break; case OpCodes::Check: status = parseCheckRequest(data, offset, len); - EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, OpCodes::Check); + EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, op); break; case OpCodes::Delete: status = parseDeleteRequest(data, offset, len); - EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, OpCodes::Delete); + EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, op); + break; + case OpCodes::GetChildren: + status = parseGetChildrenRequest(data, offset, len, false); + EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, op); + break; + case OpCodes::GetData: + status = parseGetDataRequest(data, offset, len); + EMIT_DECODER_ERR_AND_RETURN_IF_STATUS_NOT_OK(status, op); break; default: callbacks_.onDecodeError(absl::nullopt); diff --git a/test/extensions/filters/network/zookeeper_proxy/filter_test.cc b/test/extensions/filters/network/zookeeper_proxy/filter_test.cc index 72f8bba27954..823f1e43a721 100644 --- a/test/extensions/filters/network/zookeeper_proxy/filter_test.cc +++ b/test/extensions/filters/network/zookeeper_proxy/filter_test.cc @@ -223,15 +223,18 @@ class ZooKeeperFilterTest : public testing::Test { return buffer; } - Buffer::OwnedImpl - encodePathWatch(const std::string& path, const bool watch, const int32_t xid = 1000, - const int32_t opcode = enumToSignedInt(OpCodes::GetData)) const { + Buffer::OwnedImpl encodePathWatch(const std::string& path, const bool watch, + const int32_t xid = 1000, + const int32_t opcode = enumToSignedInt(OpCodes::GetData), + const bool txn = false) const { Buffer::OwnedImpl buffer; - buffer.writeBEInt(13 + path.length()); - buffer.writeBEInt(xid); - // Opcode. - buffer.writeBEInt(opcode); + if (!txn) { + buffer.writeBEInt(13 + path.length()); + buffer.writeBEInt(xid); + buffer.writeBEInt(opcode); + } + // Path. addString(buffer, path); // Watch. @@ -311,7 +314,7 @@ class ZooKeeperFilterTest : public testing::Test { return buffer; } - Buffer::OwnedImpl encodeCreateRequestWithNegativeDataLen( + Buffer::OwnedImpl encodeCreateRequestWithoutZnodeData( const std::string& path, const int32_t create_flag_val, const bool txn = false, const int32_t opcode = enumToSignedInt(OpCodes::Create)) const { Buffer::OwnedImpl buffer; @@ -596,11 +599,11 @@ class ZooKeeperFilterTest : public testing::Test { store_.counter(absl::StrCat("test.zookeeper.", opname, "_decoder_error")).value()); } - void testCreateWithNegativeDataLen(CreateFlags flag, const int32_t flag_val, - const OpCodes opcode = OpCodes::Create) { + void testCreateWithoutZnodeData(CreateFlags flag, const int32_t flag_val, + const OpCodes opcode = OpCodes::Create) { initialize(); Buffer::OwnedImpl data = - encodeCreateRequestWithNegativeDataLen("/foo", flag_val, false, enumToSignedInt(opcode)); + encodeCreateRequestWithoutZnodeData("/foo", flag_val, false, enumToSignedInt(opcode)); std::string opname = "create"; switch (opcode) { @@ -1088,8 +1091,8 @@ TEST_F(ZooKeeperFilterTest, CreateRequestPersistent) { testResponse({{{"opname", "create_resp"}, {"zxid", "2000"}, {"error", "0"}}, {{"bytes", "20"}}}); } -TEST_F(ZooKeeperFilterTest, CreateRequestPersistentWithNegativeDataLen) { - testCreateWithNegativeDataLen(CreateFlags::Persistent, 0); +TEST_F(ZooKeeperFilterTest, CreateRequestPersistentWithoutZnodeData) { + testCreateWithoutZnodeData(CreateFlags::Persistent, 0); testResponse({{{"opname", "create_resp"}, {"zxid", "2000"}, {"error", "0"}}, {{"bytes", "20"}}}); } @@ -1283,33 +1286,50 @@ TEST_F(ZooKeeperFilterTest, CheckRequest) { TEST_F(ZooKeeperFilterTest, MultiRequest) { initialize(); - Buffer::OwnedImpl create1 = encodeCreateRequest("/foo", "1", 0, true); - Buffer::OwnedImpl create2 = encodeCreateRequest("/bar", "1", 0, true); - Buffer::OwnedImpl create3 = encodeCreateRequestWithNegativeDataLen("/baz", 0, true); - Buffer::OwnedImpl check1 = encodePathVersion("/foo", 100, enumToSignedInt(OpCodes::Check), true); - Buffer::OwnedImpl set1 = encodeSetRequest("/bar", "2", -1, true); + Buffer::OwnedImpl create1 = + encodeCreateRequest("/foo", "1", 0, true, 1000, enumToSignedInt(OpCodes::Create)); + Buffer::OwnedImpl create2 = + encodeCreateRequest("/bar", "1", 0, true, 1000, enumToSignedInt(OpCodes::Create2)); + Buffer::OwnedImpl create3 = + encodeCreateRequest("/baz", "1", 0, true, 1000, enumToSignedInt(OpCodes::CreateContainer)); + Buffer::OwnedImpl create4 = + encodeCreateRequestWithoutZnodeData("/qux", 0, true, enumToSignedInt(OpCodes::CreateTtl)); + Buffer::OwnedImpl check = encodePathVersion("/foo", 100, enumToSignedInt(OpCodes::Check), true); + Buffer::OwnedImpl set = encodeSetRequest("/bar", "2", -1, true); Buffer::OwnedImpl delete1 = encodeDeleteRequest("/abcd", 1, true); Buffer::OwnedImpl delete2 = encodeDeleteRequest("/efg", 2, true); + Buffer::OwnedImpl getchildren = + encodePathWatch("/foo", false, 1000, enumToSignedInt(OpCodes::GetChildren), true); + Buffer::OwnedImpl getdata = + encodePathWatch("/bar", true, 1000, enumToSignedInt(OpCodes::GetData), true); std::vector> ops; ops.push_back(std::make_pair(enumToSignedInt(OpCodes::Create), std::move(create1))); - ops.push_back(std::make_pair(enumToSignedInt(OpCodes::Create), std::move(create2))); - ops.push_back(std::make_pair(enumToSignedInt(OpCodes::Create), std::move(create3))); - ops.push_back(std::make_pair(enumToSignedInt(OpCodes::Check), std::move(check1))); - ops.push_back(std::make_pair(enumToSignedInt(OpCodes::SetData), std::move(set1))); + ops.push_back(std::make_pair(enumToSignedInt(OpCodes::Create2), std::move(create2))); + ops.push_back(std::make_pair(enumToSignedInt(OpCodes::CreateContainer), std::move(create3))); + ops.push_back(std::make_pair(enumToSignedInt(OpCodes::CreateTtl), std::move(create4))); + ops.push_back(std::make_pair(enumToSignedInt(OpCodes::Check), std::move(check))); + ops.push_back(std::make_pair(enumToSignedInt(OpCodes::SetData), std::move(set))); ops.push_back(std::make_pair(enumToSignedInt(OpCodes::Delete), std::move(delete1))); ops.push_back(std::make_pair(enumToSignedInt(OpCodes::Delete), std::move(delete2))); + ops.push_back(std::make_pair(enumToSignedInt(OpCodes::GetChildren), std::move(getchildren))); + ops.push_back(std::make_pair(enumToSignedInt(OpCodes::GetData), std::move(getdata))); Buffer::OwnedImpl data = encodeMultiRequest(ops); EXPECT_EQ(Envoy::Network::FilterStatus::Continue, filter_->onData(data, false)); EXPECT_EQ(1UL, config_->stats().multi_rq_.value()); - EXPECT_EQ(200UL, config_->stats().request_bytes_.value()); - EXPECT_EQ(200UL, config_->stats().multi_rq_bytes_.value()); - EXPECT_EQ(3UL, config_->stats().create_rq_.value()); - EXPECT_EQ(1UL, config_->stats().setdata_rq_.value()); + EXPECT_EQ(266UL, config_->stats().request_bytes_.value()); + EXPECT_EQ(266UL, config_->stats().multi_rq_bytes_.value()); + EXPECT_EQ(1UL, config_->stats().create_rq_.value()); + EXPECT_EQ(1UL, config_->stats().create2_rq_.value()); + EXPECT_EQ(1UL, config_->stats().createcontainer_rq_.value()); + EXPECT_EQ(1UL, config_->stats().createttl_rq_.value()); EXPECT_EQ(1UL, config_->stats().check_rq_.value()); + EXPECT_EQ(1UL, config_->stats().setdata_rq_.value()); EXPECT_EQ(2UL, config_->stats().delete_rq_.value()); + EXPECT_EQ(1UL, config_->stats().getchildren_rq_.value()); + EXPECT_EQ(1UL, config_->stats().getdata_rq_.value()); EXPECT_EQ(0UL, config_->stats().decoder_error_.value()); EXPECT_EQ(0UL, config_->stats().multi_decoder_error_.value()); From 88cc3025c7fa23725029e1b7cea76910c7e86a80 Mon Sep 17 00:00:00 2001 From: Kuat Date: Thu, 29 Feb 2024 09:26:10 -0800 Subject: [PATCH 124/151] oauth2: refactor SDS secret provider to remove a data race (#32625) Commit Message: Refactor SDS secret provider to use TLV for values and make it a common utility to prevent code duplication. Additional Description: Risk Level: low Testing: done Docs Changes: none Release Notes: none Fixes: #21273 Signed-off-by: Kuat Yessenov --- contrib/sxg/filters/http/source/BUILD | 1 + contrib/sxg/filters/http/source/config.cc | 3 +- .../sxg/filters/http/source/filter_config.h | 41 +++++------------- contrib/sxg/filters/http/test/filter_test.cc | 4 +- source/common/secret/BUILD | 3 ++ source/common/secret/secret_provider_impl.cc | 29 +++++++++++++ source/common/secret/secret_provider_impl.h | 25 +++++++++++ source/extensions/filters/http/oauth2/BUILD | 2 +- .../extensions/filters/http/oauth2/config.cc | 6 +-- .../extensions/filters/http/oauth2/filter.h | 42 +++++-------------- .../filters/http/oauth2/filter_test.cc | 4 +- tools/code_format/config.yaml | 1 + 12 files changed, 91 insertions(+), 70 deletions(-) diff --git a/contrib/sxg/filters/http/source/BUILD b/contrib/sxg/filters/http/source/BUILD index a6bb7cec6708..1d3b5b9231a6 100644 --- a/contrib/sxg/filters/http/source/BUILD +++ b/contrib/sxg/filters/http/source/BUILD @@ -28,6 +28,7 @@ envoy_cc_library( "//source/common/http:codes_lib", "//source/common/stats:symbol_table_lib", "//source/common/stats:utility_lib", + "//source/common/secret:secret_provider_impl_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", "@envoy_api//contrib/envoy/extensions/filters/http/sxg/v3alpha:pkg_cc_proto", # use boringssl alias to select fips vs non-fips version. diff --git a/contrib/sxg/filters/http/source/config.cc b/contrib/sxg/filters/http/source/config.cc index 68b7d2292a72..541216f8454f 100644 --- a/contrib/sxg/filters/http/source/config.cc +++ b/contrib/sxg/filters/http/source/config.cc @@ -57,7 +57,8 @@ Http::FilterFactoryCb FilterFactory::createFilterFactoryFromProtoTyped( } auto secret_reader = std::make_shared( - secret_provider_certificate, secret_provider_private_key, server_context.api()); + std::move(secret_provider_certificate), std::move(secret_provider_private_key), + server_context.threadLocal(), server_context.api()); auto config = std::make_shared(proto_config, server_context.timeSource(), secret_reader, stat_prefix, context.scope()); return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { diff --git a/contrib/sxg/filters/http/source/filter_config.h b/contrib/sxg/filters/http/source/filter_config.h index ca3cbfdb5300..a54f51794259 100644 --- a/contrib/sxg/filters/http/source/filter_config.h +++ b/contrib/sxg/filters/http/source/filter_config.h @@ -4,7 +4,7 @@ #include "envoy/stats/scope.h" #include "envoy/stats/stats_macros.h" -#include "source/common/config/datasource.h" +#include "source/common/secret/secret_provider_impl.h" #include "source/extensions/filters/http/common/pass_through_filter.h" #include "contrib/envoy/extensions/filters/http/sxg/v3alpha/sxg.pb.h" @@ -37,39 +37,18 @@ class SecretReader { class SDSSecretReader : public SecretReader { public: - SDSSecretReader(Secret::GenericSecretConfigProviderSharedPtr certificate_provider, - Secret::GenericSecretConfigProviderSharedPtr private_key_provider, Api::Api& api) - : update_callback_client_(readAndWatchSecret(certificate_, certificate_provider, api)), - update_callback_token_(readAndWatchSecret(private_key_, private_key_provider, api)) {} - + SDSSecretReader(Secret::GenericSecretConfigProviderSharedPtr&& certificate_provider, + Secret::GenericSecretConfigProviderSharedPtr&& private_key_provider, + ThreadLocal::SlotAllocator& tls, Api::Api& api) + : certificate_(std::move(certificate_provider), tls, api), + private_key_(std::move(private_key_provider), tls, api) {} // SecretReader - const std::string& certificate() const override { return certificate_; } - const std::string& privateKey() const override { return private_key_; } + const std::string& certificate() const override { return certificate_.secret(); } + const std::string& privateKey() const override { return private_key_.secret(); } private: - Envoy::Common::CallbackHandlePtr - readAndWatchSecret(std::string& value, - Secret::GenericSecretConfigProviderSharedPtr& secret_provider, Api::Api& api) { - const auto* secret = secret_provider->secret(); - if (secret != nullptr) { - value = - THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), std::string); - } - - return secret_provider->addUpdateCallback([secret_provider, &api, &value]() { - const auto* secret = secret_provider->secret(); - if (secret != nullptr) { - value = THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), - std::string); - } - }); - } - - std::string certificate_; - std::string private_key_; - - Envoy::Common::CallbackHandlePtr update_callback_client_; - Envoy::Common::CallbackHandlePtr update_callback_token_; + Secret::ThreadLocalGenericSecretProvider certificate_; + Secret::ThreadLocalGenericSecretProvider private_key_; }; class FilterConfig : public Logger::Loggable { diff --git a/contrib/sxg/filters/http/test/filter_test.cc b/contrib/sxg/filters/http/test/filter_test.cc index 933198e6f6c7..a8ba5c027e18 100644 --- a/contrib/sxg/filters/http/test/filter_test.cc +++ b/contrib/sxg/filters/http/test/filter_test.cc @@ -380,7 +380,9 @@ TEST_F(FilterTest, SdsDynamicGenericSecret) { config_source, "private_key", secret_context, init_manager); auto private_key_callback = secret_context.cluster_manager_.subscription_factory_.callbacks_; - SDSSecretReader secret_reader(certificate_secret_provider, private_key_secret_provider, *api); + NiceMock tls; + SDSSecretReader secret_reader(std::move(certificate_secret_provider), + std::move(private_key_secret_provider), tls, *api); EXPECT_TRUE(secret_reader.certificate().empty()); EXPECT_TRUE(secret_reader.privateKey().empty()); diff --git a/source/common/secret/BUILD b/source/common/secret/BUILD index b7b25aedbe5b..d6e61c39531f 100644 --- a/source/common/secret/BUILD +++ b/source/common/secret/BUILD @@ -32,6 +32,9 @@ envoy_cc_library( hdrs = ["secret_provider_impl.h"], deps = [ "//envoy/secret:secret_provider_interface", + "//envoy/thread_local:thread_local_interface", + "//envoy/thread_local:thread_local_object", + "//source/common/config:datasource_lib", "//source/common/ssl:certificate_validation_context_config_impl_lib", "//source/common/ssl:tls_certificate_config_impl_lib", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", diff --git a/source/common/secret/secret_provider_impl.cc b/source/common/secret/secret_provider_impl.cc index 6aed80d0107c..ea2ff427ec19 100644 --- a/source/common/secret/secret_provider_impl.cc +++ b/source/common/secret/secret_provider_impl.cc @@ -3,6 +3,7 @@ #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" #include "source/common/common/assert.h" +#include "source/common/config/datasource.h" #include "source/common/ssl/certificate_validation_context_config_impl.h" #include "source/common/ssl/tls_certificate_config_impl.h" @@ -36,5 +37,33 @@ GenericSecretConfigProviderImpl::GenericSecretConfigProviderImpl( std::make_unique( generic_secret)) {} +ThreadLocalGenericSecretProvider::ThreadLocalGenericSecretProvider( + GenericSecretConfigProviderSharedPtr&& provider, ThreadLocal::SlotAllocator& tls, Api::Api& api) + : provider_(provider), api_(api), + tls_(std::make_unique>(tls)), + cb_(provider_->addUpdateCallback([this] { update(); })) { + std::string value; + if (const auto* secret = provider_->secret(); secret != nullptr) { + value = + THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api_), std::string); + } + tls_->set([value = std::move(value)](Event::Dispatcher&) { + return std::make_shared(value); + }); +} + +const std::string& ThreadLocalGenericSecretProvider::secret() const { return (*tls_)->value_; } + +// This function is executed on the main during xDS update and can throw. +void ThreadLocalGenericSecretProvider::update() { + std::string value; + if (const auto* secret = provider_->secret(); secret != nullptr) { + value = + THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api_), std::string); + } + tls_->runOnAllThreads( + [value = std::move(value)](OptRef tls) { tls->value_ = value; }); +} + } // namespace Secret } // namespace Envoy diff --git a/source/common/secret/secret_provider_impl.h b/source/common/secret/secret_provider_impl.h index 566f53bf2a86..981f1c84cf41 100644 --- a/source/common/secret/secret_provider_impl.h +++ b/source/common/secret/secret_provider_impl.h @@ -6,6 +6,8 @@ #include "envoy/secret/secret_provider.h" #include "envoy/ssl/certificate_validation_context_config.h" #include "envoy/ssl/tls_certificate_config.h" +#include "envoy/thread_local/thread_local.h" +#include "envoy/thread_local/thread_local_object.h" namespace Envoy { namespace Secret { @@ -108,5 +110,28 @@ class GenericSecretConfigProviderImpl : public GenericSecretConfigProvider { Secret::GenericSecretPtr generic_secret_; }; +/** + * A utility secret provider that uses thread local values to share the updates to the secrets from + * the main to the workers. + **/ +class ThreadLocalGenericSecretProvider { +public: + ThreadLocalGenericSecretProvider(GenericSecretConfigProviderSharedPtr&& provider, + ThreadLocal::SlotAllocator& tls, Api::Api& api); + const std::string& secret() const; + +private: + struct ThreadLocalSecret : public ThreadLocal::ThreadLocalObject { + explicit ThreadLocalSecret(const std::string& value) : value_(value) {} + std::string value_; + }; + void update(); + GenericSecretConfigProviderSharedPtr provider_; + Api::Api& api_; + ThreadLocal::TypedSlotPtr tls_; + // Must be last since it has a non-trivial de-registering destructor. + Common::CallbackHandlePtr cb_; +}; + } // namespace Secret } // namespace Envoy diff --git a/source/extensions/filters/http/oauth2/BUILD b/source/extensions/filters/http/oauth2/BUILD index 936579f59266..a236dfa7583f 100644 --- a/source/extensions/filters/http/oauth2/BUILD +++ b/source/extensions/filters/http/oauth2/BUILD @@ -49,10 +49,10 @@ envoy_cc_library( "//envoy/server:filter_config_interface", "//source/common/common:assert_lib", "//source/common/common:empty_string", - "//source/common/config:datasource_lib", "//source/common/crypto:utility_lib", "//source/common/formatter:substitution_formatter_lib", "//source/common/protobuf:utility_lib", + "//source/common/secret:secret_provider_impl_lib", "//source/extensions/filters/http/common:pass_through_filter_lib", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/filters/http/oauth2/v3:pkg_cc_proto", diff --git a/source/extensions/filters/http/oauth2/config.cc b/source/extensions/filters/http/oauth2/config.cc index 98fd1f7dd6d8..1a7878b92f26 100644 --- a/source/extensions/filters/http/oauth2/config.cc +++ b/source/extensions/filters/http/oauth2/config.cc @@ -64,9 +64,9 @@ Http::FilterFactoryCb OAuth2Config::createFilterFactoryFromProtoTyped( throw EnvoyException("invalid HMAC secret configuration"); } - auto secret_reader = - std::make_shared(secret_provider_token_secret, secret_provider_hmac_secret, - context.serverFactoryContext().api()); + auto secret_reader = std::make_shared( + std::move(secret_provider_token_secret), std::move(secret_provider_hmac_secret), + context.serverFactoryContext().threadLocal(), context.serverFactoryContext().api()); auto config = std::make_shared(proto_config, cluster_manager, secret_reader, context.scope(), stats_prefix); diff --git a/source/extensions/filters/http/oauth2/filter.h b/source/extensions/filters/http/oauth2/filter.h index 715cd3230cdd..d2a594241a35 100644 --- a/source/extensions/filters/http/oauth2/filter.h +++ b/source/extensions/filters/http/oauth2/filter.h @@ -16,11 +16,11 @@ #include "source/common/common/assert.h" #include "source/common/common/matchers.h" -#include "source/common/config/datasource.h" #include "source/common/formatter/substitution_formatter.h" #include "source/common/http/header_map_impl.h" #include "source/common/http/header_utility.h" #include "source/common/http/utility.h" +#include "source/common/secret/secret_provider_impl.h" #include "source/extensions/filters/http/common/pass_through_filter.h" #include "source/extensions/filters/http/oauth2/oauth.h" #include "source/extensions/filters/http/oauth2/oauth_client.h" @@ -45,39 +45,17 @@ class SecretReader { class SDSSecretReader : public SecretReader { public: - SDSSecretReader(Secret::GenericSecretConfigProviderSharedPtr client_secret_provider, - Secret::GenericSecretConfigProviderSharedPtr token_secret_provider, Api::Api& api) - : update_callback_client_(readAndWatchSecret(client_secret_, client_secret_provider, api)), - update_callback_token_(readAndWatchSecret(token_secret_, token_secret_provider, api)) {} - - const std::string& clientSecret() const override { return client_secret_; } - - const std::string& tokenSecret() const override { return token_secret_; } + SDSSecretReader(Secret::GenericSecretConfigProviderSharedPtr&& client_secret_provider, + Secret::GenericSecretConfigProviderSharedPtr&& token_secret_provider, + ThreadLocal::SlotAllocator& tls, Api::Api& api) + : client_secret_(std::move(client_secret_provider), tls, api), + token_secret_(std::move(token_secret_provider), tls, api) {} + const std::string& clientSecret() const override { return client_secret_.secret(); } + const std::string& tokenSecret() const override { return token_secret_.secret(); } private: - Envoy::Common::CallbackHandlePtr - readAndWatchSecret(std::string& value, - Secret::GenericSecretConfigProviderSharedPtr& secret_provider, Api::Api& api) { - const auto* secret = secret_provider->secret(); - if (secret != nullptr) { - value = - THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), std::string); - } - - return secret_provider->addUpdateCallback([secret_provider, &api, &value]() { - const auto* secret = secret_provider->secret(); - if (secret != nullptr) { - value = THROW_OR_RETURN_VALUE(Config::DataSource::read(secret->secret(), true, api), - std::string); - } - }); - } - - std::string client_secret_; - std::string token_secret_; - - Envoy::Common::CallbackHandlePtr update_callback_client_; - Envoy::Common::CallbackHandlePtr update_callback_token_; + Secret::ThreadLocalGenericSecretProvider client_secret_; + Secret::ThreadLocalGenericSecretProvider token_secret_; }; /** diff --git a/test/extensions/filters/http/oauth2/filter_test.cc b/test/extensions/filters/http/oauth2/filter_test.cc index 3ac4819bc635..231f899fd611 100644 --- a/test/extensions/filters/http/oauth2/filter_test.cc +++ b/test/extensions/filters/http/oauth2/filter_test.cc @@ -247,7 +247,9 @@ TEST_F(OAuth2Test, SdsDynamicGenericSecret) { config_source, "token", secret_context, init_manager); auto token_callback = secret_context.cluster_manager_.subscription_factory_.callbacks_; - SDSSecretReader secret_reader(client_secret_provider, token_secret_provider, *api); + NiceMock tls; + SDSSecretReader secret_reader(std::move(client_secret_provider), std::move(token_secret_provider), + tls, *api); EXPECT_TRUE(secret_reader.clientSecret().empty()); EXPECT_TRUE(secret_reader.tokenSecret().empty()); diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index 0e96847c58dd..7da3e054be32 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -162,6 +162,7 @@ paths: - source/common/upstream/health_discovery_service.cc - source/common/secret/sds_api.h - source/common/secret/sds_api.cc + - source/common/secret/secret_provider_impl.cc - source/common/router/router.cc - source/common/config/config_provider_impl.h - source/common/common/logger_delegates.cc From f7352b3237d5b16cf7e078733b32ba158e66d086 Mon Sep 17 00:00:00 2001 From: Raven Black Date: Thu, 29 Feb 2024 12:37:43 -0500 Subject: [PATCH 125/151] QUIC hot restart part 6 - child instance pauses listening until parent is drained (#31130) QUIC hot restart part 6 - child instance pauses listening until parent is drained (#31130) --------- Signed-off-by: Raven Black --- envoy/network/BUILD | 6 ++ .../parent_drained_callback_registrar.h | 29 +++++ envoy/network/socket.h | 7 ++ envoy/server/hot_restart.h | 11 ++ .../listener_manager/listener_manager_impl.cc | 5 +- source/common/network/BUILD | 1 + source/common/network/listen_socket_impl.h | 19 +++- source/common/network/udp_listener_impl.cc | 40 ++++++- source/common/network/udp_listener_impl.h | 6 ++ source/server/BUILD | 1 + source/server/hot_restart_impl.cc | 4 + source/server/hot_restart_impl.h | 1 + source/server/hot_restart_nop_impl.h | 3 + source/server/hot_restarting_child.cc | 53 ++++++--- source/server/hot_restarting_child.h | 17 ++- test/common/network/BUILD | 1 + .../udp_listener_impl_batch_writer_test.cc | 1 + test/common/network/udp_listener_impl_test.cc | 102 ++++++++++++++++++ .../network/udp_listener_impl_test_base.h | 27 ++++- .../python/hotrestart_handoff_test.py | 2 +- test/mocks/network/BUILD | 6 ++ .../mock_parent_drained_callback_registrar.h | 18 ++++ test/mocks/server/hot_restart.h | 2 + test/server/hot_restart_impl_test.cc | 8 ++ test/server/hot_restarting_child_test.cc | 35 ++++++ 25 files changed, 376 insertions(+), 29 deletions(-) create mode 100644 envoy/network/parent_drained_callback_registrar.h create mode 100644 test/mocks/network/mock_parent_drained_callback_registrar.h diff --git a/envoy/network/BUILD b/envoy/network/BUILD index 3e7d51a07e90..230f7c065d58 100644 --- a/envoy/network/BUILD +++ b/envoy/network/BUILD @@ -58,6 +58,12 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "parent_drained_callback_registrar_interface", + hdrs = ["parent_drained_callback_registrar.h"], + deps = [":address_interface"], +) + envoy_cc_library( name = "udp_packet_writer_handler_interface", hdrs = ["udp_packet_writer_handler.h"], diff --git a/envoy/network/parent_drained_callback_registrar.h b/envoy/network/parent_drained_callback_registrar.h new file mode 100644 index 000000000000..d0ce7c9a191e --- /dev/null +++ b/envoy/network/parent_drained_callback_registrar.h @@ -0,0 +1,29 @@ +#pragma once + +#include "envoy/network/address.h" + +#include "absl/functional/any_invocable.h" + +namespace Envoy { +namespace Network { + +/** + * An interface through which a UDP listen socket, especially a QUIC socket, can + * postpone reading during hot restart until the parent instance is drained. + */ +class ParentDrainedCallbackRegistrar { +public: + /** + * @param address is the address of the listener. + * @param callback the function to call when the listener matching address is + * drained on the parent instance. + */ + virtual void registerParentDrainedCallback(const Address::InstanceConstSharedPtr& address, + absl::AnyInvocable callback) PURE; + +protected: + virtual ~ParentDrainedCallbackRegistrar() = default; +}; + +} // namespace Network +} // namespace Envoy diff --git a/envoy/network/socket.h b/envoy/network/socket.h index b83d0f213047..6091cf9fe922 100644 --- a/envoy/network/socket.h +++ b/envoy/network/socket.h @@ -542,6 +542,13 @@ class Socket { * @return the socket options stored earlier with addOption() and addOptions() calls, if any. */ virtual const OptionsSharedPtr& options() const PURE; + + /** + * @return a ParentDrainedCallbackRegistrar for UDP listen sockets during hot restart. + */ + virtual OptRef parentDrainedCallbackRegistrar() const { + return absl::nullopt; + } }; using SocketPtr = std::unique_ptr; diff --git a/envoy/server/hot_restart.h b/envoy/server/hot_restart.h index a1ce1663cde4..8e201dd65e08 100644 --- a/envoy/server/hot_restart.h +++ b/envoy/server/hot_restart.h @@ -62,6 +62,17 @@ class HotRestart { virtual void registerUdpForwardingListener(Network::Address::InstanceConstSharedPtr address, std::shared_ptr listener_config) PURE; + + /** + * @return An interface on which registerParentDrainedCallback can be called during + * creation of a listener, or nullopt if there is no parent instance. + * + * If this is set, any UDP listener should start paused and only begin listening + * when the parent instance is drained; this allows draining QUIC listeners to + * catch their own packets and forward unrecognized packets to the child instance. + */ + virtual OptRef parentDrainedCallbackRegistrar() PURE; + /** * Initialize the parent logic of our restarter. Meant to be called after initialization of a * new child has begun. The hot restart implementation needs to be created early to deal with diff --git a/source/common/listener_manager/listener_manager_impl.cc b/source/common/listener_manager/listener_manager_impl.cc index 853087d0b1bf..930378a94b94 100644 --- a/source/common/listener_manager/listener_manager_impl.cc +++ b/source/common/listener_manager/listener_manager_impl.cc @@ -321,7 +321,10 @@ Network::SocketSharedPtr ProdListenerComponentFactory::createListenSocket( if (socket_type == Network::Socket::Type::Stream) { return std::make_shared(std::move(io_handle), address, options); } else { - return std::make_shared(std::move(io_handle), address, options); + auto socket = std::make_shared( + std::move(io_handle), address, options, + server_.hotRestart().parentDrainedCallbackRegistrar()); + return socket; } } } diff --git a/source/common/network/BUILD b/source/common/network/BUILD index c563ca21428b..53ca953779a4 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -344,6 +344,7 @@ envoy_cc_library( "//envoy/event:file_event_interface", "//envoy/network:exception_interface", "//envoy/network:listener_interface", + "//envoy/network:parent_drained_callback_registrar_interface", "//envoy/runtime:runtime_interface", "//envoy/stats:stats_interface", "//envoy/stats:stats_macros", diff --git a/source/common/network/listen_socket_impl.h b/source/common/network/listen_socket_impl.h index 1dd9a6f668d7..e17e3178637d 100644 --- a/source/common/network/listen_socket_impl.h +++ b/source/common/network/listen_socket_impl.h @@ -68,12 +68,19 @@ template class NetworkListenSocket : public ListenSocketImpl { } } - NetworkListenSocket(IoHandlePtr&& io_handle, const Address::InstanceConstSharedPtr& address, - const Network::Socket::OptionsSharedPtr& options) - : ListenSocketImpl(std::move(io_handle), address) { + NetworkListenSocket( + IoHandlePtr&& io_handle, const Address::InstanceConstSharedPtr& address, + const Network::Socket::OptionsSharedPtr& options, + OptRef parent_drained_callback_registrar = absl::nullopt) + : ListenSocketImpl(std::move(io_handle), address), + parent_drained_callback_registrar_(parent_drained_callback_registrar) { setListenSocketOptions(options); } + OptRef parentDrainedCallbackRegistrar() const override { + return parent_drained_callback_registrar_; + } + Socket::Type socketType() const override { return T::type; } SocketPtr duplicate() override { @@ -110,6 +117,12 @@ template class NetworkListenSocket : public ListenSocketImpl { } protected: + // Usually a socket when initialized starts listening for ready-to-read or ready-to-write events; + // for a QUIC socket during hot restart this is undesirable as the parent instance needs to + // receive all packets; in that case this interface is set, and listening won't begin until the + // callback is called. + OptRef parent_drained_callback_registrar_; + void setPrebindSocketOptions() { // On Windows, SO_REUSEADDR does not restrict subsequent bind calls when there is a listener as // on Linux and later BSD socket stacks. diff --git a/source/common/network/udp_listener_impl.cc b/source/common/network/udp_listener_impl.cc index 62c5b273db96..a184eaae7036 100644 --- a/source/common/network/udp_listener_impl.cc +++ b/source/common/network/udp_listener_impl.cc @@ -9,6 +9,7 @@ #include "envoy/common/platform.h" #include "envoy/config/core/v3/base.pb.h" #include "envoy/network/exception.h" +#include "envoy/network/parent_drained_callback_registrar.h" #include "source/common/api/os_sys_calls_impl.h" #include "source/common/common/assert.h" @@ -34,9 +35,34 @@ UdpListenerImpl::UdpListenerImpl(Event::Dispatcher& dispatcher, SocketSharedPtr : BaseListenerImpl(dispatcher, std::move(socket)), cb_(cb), time_source_(time_source), // Default prefer_gro to false for downstream server traffic. config_(config, false) { + parent_drained_callback_registrar_ = socket_->parentDrainedCallbackRegistrar(); socket_->ioHandle().initializeFileEvent( dispatcher, [this](uint32_t events) -> void { onSocketEvent(events); }, - Event::PlatformDefaultTriggerType, Event::FileReadyType::Read | Event::FileReadyType::Write); + Event::PlatformDefaultTriggerType, paused() ? 0 : events_when_unpaused_); + if (paused()) { + parent_drained_callback_registrar_->registerParentDrainedCallback( + socket_->connectionInfoProvider().localAddress(), + [this, &dispatcher, alive = std::weak_ptr(destruction_checker_)]() { + dispatcher.post([this, alive = std::move(alive)]() { + auto still_alive = alive.lock(); + if (still_alive != nullptr) { + unpause(); + } + }); + }); + } +} + +void UdpListenerImpl::unpause() { + // Remove the paused state so enable will actually start listening to events. + parent_drained_callback_registrar_ = absl::nullopt; + if (events_when_unpaused_ != 0) { + // Start listening to events. + enable(); + // There may have already been events while this instance was ignoring them, + // so try reading immediately. + activateRead(); + } } UdpListenerImpl::~UdpListenerImpl() { socket_->ioHandle().resetFileEvents(); } @@ -44,10 +70,18 @@ UdpListenerImpl::~UdpListenerImpl() { socket_->ioHandle().resetFileEvents(); } void UdpListenerImpl::disable() { disableEvent(); } void UdpListenerImpl::enable() { - socket_->ioHandle().enableFileEvents(Event::FileReadyType::Read | Event::FileReadyType::Write); + events_when_unpaused_ = Event::FileReadyType::Read | Event::FileReadyType::Write; + if (!paused()) { + socket_->ioHandle().enableFileEvents(events_when_unpaused_); + } } -void UdpListenerImpl::disableEvent() { socket_->ioHandle().enableFileEvents(0); } +void UdpListenerImpl::disableEvent() { + events_when_unpaused_ = 0; + if (!paused()) { + socket_->ioHandle().enableFileEvents(0); + } +} void UdpListenerImpl::onSocketEvent(short flags) { ASSERT((flags & (Event::FileReadyType::Read | Event::FileReadyType::Write))); diff --git a/source/common/network/udp_listener_impl.h b/source/common/network/udp_listener_impl.h index 723c3c74de75..244f93f3923b 100644 --- a/source/common/network/udp_listener_impl.h +++ b/source/common/network/udp_listener_impl.h @@ -26,6 +26,8 @@ class UdpListenerImpl : public BaseListenerImpl, TimeSource& time_source, const envoy::config::core::v3::UdpSocketConfig& config); ~UdpListenerImpl() override; uint32_t packetsDropped() { return packets_dropped_; } + bool paused() const { return parent_drained_callback_registrar_ != absl::nullopt; } + void unpause(); // Network::Listener void disable() override; @@ -63,6 +65,10 @@ class UdpListenerImpl : public BaseListenerImpl, TimeSource& time_source_; const ResolvedUdpSocketConfig config_; + OptRef parent_drained_callback_registrar_; + // Taking a weak_ptr to this lets us detect if the listener has been destroyed. + std::shared_ptr destruction_checker_ = std::make_shared(true); + uint32_t events_when_unpaused_ = Event::FileReadyType::Read | Event::FileReadyType::Write; }; class UdpListenerWorkerRouterImpl : public UdpListenerWorkerRouter { diff --git a/source/server/BUILD b/source/server/BUILD index 7ee402750e1f..f656303df03e 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -187,6 +187,7 @@ envoy_cc_library( hdrs = envoy_select_hot_restart(["hot_restarting_child.h"]), deps = [ ":hot_restarting_base", + "//envoy/network:parent_drained_callback_registrar_interface", "//source/common/stats:stat_merger_lib", ], ) diff --git a/source/server/hot_restart_impl.cc b/source/server/hot_restart_impl.cc index 417bb4a5f2f3..6e2377c9c6f0 100644 --- a/source/server/hot_restart_impl.cc +++ b/source/server/hot_restart_impl.cc @@ -124,6 +124,10 @@ void HotRestartImpl::registerUdpForwardingListener( as_child_.registerUdpForwardingListener(address, listener_config); } +OptRef HotRestartImpl::parentDrainedCallbackRegistrar() { + return as_child_; +} + void HotRestartImpl::initialize(Event::Dispatcher& dispatcher, Server::Instance& server) { as_parent_.initialize(dispatcher, server); as_child_.initialize(dispatcher); diff --git a/source/server/hot_restart_impl.h b/source/server/hot_restart_impl.h index ace7d41321d9..9a22b6f3ec13 100644 --- a/source/server/hot_restart_impl.h +++ b/source/server/hot_restart_impl.h @@ -106,6 +106,7 @@ class HotRestartImpl : public HotRestart { void registerUdpForwardingListener( Network::Address::InstanceConstSharedPtr address, std::shared_ptr listener_config) override; + OptRef parentDrainedCallbackRegistrar() override; void initialize(Event::Dispatcher& dispatcher, Server::Instance& server) override; absl::optional sendParentAdminShutdownRequest() override; void sendParentTerminateRequest() override; diff --git a/source/server/hot_restart_nop_impl.h b/source/server/hot_restart_nop_impl.h index 99f006083937..031cf1e4613b 100644 --- a/source/server/hot_restart_nop_impl.h +++ b/source/server/hot_restart_nop_impl.h @@ -20,6 +20,9 @@ class HotRestartNopImpl : public Server::HotRestart { int duplicateParentListenSocket(const std::string&, uint32_t) override { return -1; } void registerUdpForwardingListener(Network::Address::InstanceConstSharedPtr, std::shared_ptr) override {} + OptRef parentDrainedCallbackRegistrar() override { + return absl::nullopt; + } void initialize(Event::Dispatcher&, Server::Instance&) override {} absl::optional sendParentAdminShutdownRequest() override { return absl::nullopt; diff --git a/source/server/hot_restarting_child.cc b/source/server/hot_restarting_child.cc index 0d842a2755eb..0bb33e650db9 100644 --- a/source/server/hot_restarting_child.cc +++ b/source/server/hot_restarting_child.cc @@ -46,9 +46,12 @@ HotRestartingChild::UdpForwardingContext::getListenerForDestination( return it->second; } +// If restart_epoch is 0 there is no parent, so it's effectively already +// drained and terminated. HotRestartingChild::HotRestartingChild(int base_id, int restart_epoch, const std::string& socket_path, mode_t socket_mode) - : HotRestartingBase(base_id), restart_epoch_(restart_epoch) { + : HotRestartingBase(base_id), restart_epoch_(restart_epoch), + parent_terminated_(restart_epoch == 0), parent_drained_(restart_epoch == 0) { main_rpc_stream_.initDomainSocketAddress(&parent_address_); std::string socket_path_udp = socket_path + "_udp"; udp_forwarding_rpc_stream_.initDomainSocketAddress(&parent_address_udp_forwarding_); @@ -102,7 +105,7 @@ void HotRestartingChild::onForwardedUdpPacket(uint32_t worker_index, Network::Ud int HotRestartingChild::duplicateParentListenSocket(const std::string& address, uint32_t worker_index) { - if (restart_epoch_ == 0 || parent_terminated_) { + if (parent_terminated_) { return -1; } @@ -121,7 +124,7 @@ int HotRestartingChild::duplicateParentListenSocket(const std::string& address, } std::unique_ptr HotRestartingChild::getParentStats() { - if (restart_epoch_ == 0 || parent_terminated_) { + if (parent_terminated_) { return nullptr; } @@ -138,7 +141,7 @@ std::unique_ptr HotRestartingChild::getParentStats() { } void HotRestartingChild::drainParentListeners() { - if (restart_epoch_ == 0 || parent_terminated_) { + if (parent_terminated_) { return; } // No reply expected. @@ -154,9 +157,27 @@ void HotRestartingChild::registerUdpForwardingListener( udp_forwarding_context_.registerListener(address, listener_config); } +void HotRestartingChild::registerParentDrainedCallback( + const Network::Address::InstanceConstSharedPtr& address, absl::AnyInvocable callback) { + if (parent_drained_) { + callback(); + } else { + on_drained_actions_.emplace(address->asString(), std::move(callback)); + } +} + +void HotRestartingChild::allDrainsImplicitlyComplete() { + for (auto& drain_action : on_drained_actions_) { + // Call the callback. + std::move(drain_action.second)(); + } + on_drained_actions_.clear(); + parent_drained_ = true; +} + absl::optional HotRestartingChild::sendParentAdminShutdownRequest() { - if (restart_epoch_ == 0 || parent_terminated_) { + if (parent_terminated_) { return absl::nullopt; } @@ -176,9 +197,11 @@ HotRestartingChild::sendParentAdminShutdownRequest() { } void HotRestartingChild::sendParentTerminateRequest() { - if (restart_epoch_ == 0 || parent_terminated_) { + if (parent_terminated_) { return; } + allDrainsImplicitlyComplete(); + HotRestartMessage wrapped_request; wrapped_request.mutable_request()->mutable_terminate(); main_rpc_stream_.sendHotRestartMessage(parent_address_, wrapped_request); @@ -186,15 +209,17 @@ void HotRestartingChild::sendParentTerminateRequest() { // Note that the 'generation' counter needs to retain the contribution from // the parent. - stat_merger_->retainParentGaugeValue(hot_restart_generation_stat_name_); + if (stat_merger_ != nullptr) { + stat_merger_->retainParentGaugeValue(hot_restart_generation_stat_name_); - // Now it is safe to forget our stat transferral state. - // - // This destruction is actually important far beyond memory efficiency. The - // scope-based temporary counter logic relies on the StatMerger getting - // destroyed once hot restart's stat merging is all done. (See stat_merger.h - // for details). - stat_merger_.reset(); + // Now it is safe to forget our stat transferral state. + // + // This destruction is actually important far beyond memory efficiency. The + // scope-based temporary counter logic relies on the StatMerger getting + // destroyed once hot restart's stat merging is all done. (See stat_merger.h + // for details). + stat_merger_.reset(); + } } void HotRestartingChild::mergeParentStats(Stats::Store& stats_store, diff --git a/source/server/hot_restarting_child.h b/source/server/hot_restarting_child.h index 7f61dfc59f55..7f485511b3d4 100644 --- a/source/server/hot_restarting_child.h +++ b/source/server/hot_restarting_child.h @@ -1,5 +1,6 @@ #pragma once +#include "envoy/network/parent_drained_callback_registrar.h" #include "envoy/server/instance.h" #include "source/common/stats/stat_merger.h" @@ -11,7 +12,8 @@ namespace Server { /** * The child half of hot restarting. Issues requests and commands to the parent. */ -class HotRestartingChild : public HotRestartingBase { +class HotRestartingChild : public HotRestartingBase, + public Network::ParentDrainedCallbackRegistrar { public: // A structure to record the set of registered UDP listeners keyed on their addresses, // to support QUIC packet forwarding. @@ -42,7 +44,7 @@ class HotRestartingChild : public HotRestartingBase { HotRestartingChild(int base_id, int restart_epoch, const std::string& socket_path, mode_t socket_mode); - ~HotRestartingChild() = default; + ~HotRestartingChild() override = default; void initialize(Event::Dispatcher& dispatcher); void shutdown(); @@ -50,6 +52,9 @@ class HotRestartingChild : public HotRestartingBase { int duplicateParentListenSocket(const std::string& address, uint32_t worker_index); void registerUdpForwardingListener(Network::Address::InstanceConstSharedPtr address, std::shared_ptr listener_config); + // From Network::ParentDrainedCallbackRegistrar. + void registerParentDrainedCallback(const Network::Address::InstanceConstSharedPtr& addr, + absl::AnyInvocable action) override; std::unique_ptr getParentStats(); void drainParentListeners(); absl::optional sendParentAdminShutdownRequest(); @@ -60,15 +65,21 @@ class HotRestartingChild : public HotRestartingBase { protected: void onSocketEventUdpForwarding(); void onForwardedUdpPacket(uint32_t worker_index, Network::UdpRecvData&& data); + // When call to terminate parent is sent, or parent is already terminated, + void allDrainsImplicitlyComplete(); private: friend class HotRestartUdpForwardingTestHelper; const int restart_epoch_; - bool parent_terminated_{}; + bool parent_terminated_; + bool parent_drained_; sockaddr_un parent_address_; sockaddr_un parent_address_udp_forwarding_; std::unique_ptr stat_merger_{}; Stats::StatName hot_restart_generation_stat_name_; + // There are multiple listener instances per address that must all be reactivated + // when the parent is drained, so a multimap is used to contain them. + std::unordered_multimap> on_drained_actions_; Event::FileEventPtr socket_event_udp_forwarding_; UdpForwardingContext udp_forwarding_context_; }; diff --git a/test/common/network/BUILD b/test/common/network/BUILD index ea59472db9b6..9e4c1c2e8205 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -249,6 +249,7 @@ envoy_cc_test( "//source/common/network:utility_lib", "//source/common/stats:stats_lib", "//test/common/network:listener_impl_test_base_lib", + "//test/mocks/network:mock_parent_drained_callback_registrar", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", diff --git a/test/common/network/udp_listener_impl_batch_writer_test.cc b/test/common/network/udp_listener_impl_batch_writer_test.cc index 39b69e86c02a..35156dbd6a83 100644 --- a/test/common/network/udp_listener_impl_batch_writer_test.cc +++ b/test/common/network/udp_listener_impl_batch_writer_test.cc @@ -61,6 +61,7 @@ size_t getPacketLength(const msghdr* msg) { class UdpListenerImplBatchWriterTest : public UdpListenerImplTestBase { public: void SetUp() override { + UdpListenerImplTestBase::setup(); // Set listening socket options and set UdpGsoBatchWriter server_socket_->addOptions(SocketOptionFactory::buildIpPacketInfoOptions()); server_socket_->addOptions(SocketOptionFactory::buildRxQueueOverFlowOptions()); diff --git a/test/common/network/udp_listener_impl_test.cc b/test/common/network/udp_listener_impl_test.cc index 18810ca7467a..6df91c150355 100644 --- a/test/common/network/udp_listener_impl_test.cc +++ b/test/common/network/udp_listener_impl_test.cc @@ -16,6 +16,7 @@ #include "test/common/network/udp_listener_impl_test_base.h" #include "test/mocks/api/mocks.h" +#include "test/mocks/network/mock_parent_drained_callback_registrar.h" #include "test/mocks/network/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" @@ -52,6 +53,7 @@ class OverrideOsSysCallsImpl : public Api::OsSysCallsImpl { class UdpListenerImplTest : public UdpListenerImplTestBase { public: void setup(bool prefer_gro = false) { + UdpListenerImplTestBase::setup(); ON_CALL(override_syscall_, supportsUdpGro()).WillByDefault(Return(false)); // Return the real version by default. ON_CALL(override_syscall_, supportsMmsg()) @@ -385,6 +387,106 @@ TEST_P(UdpListenerImplTest, UdpListenerEnableDisable) { dispatcher_->run(Event::Dispatcher::RunType::Block); } +class HotRestartedUdpListenerImplTest : public UdpListenerImplTest { +public: + void SetUp() override { +#ifdef WIN32 + GTEST_SKIP() << "Hot restart is not supported on Windows."; +#endif + } + void setup() { + io_handle_ = &useHotRestartSocket(registrar_); + // File event should be created listening to no events (i.e. disabled). + EXPECT_CALL(*io_handle_, createFileEvent_(_, _, _, 0)); + // Parent drained callback should be registered when the listener is created. + // We capture the callback so we can simulate "drain complete". + EXPECT_CALL(registrar_, registerParentDrainedCallback(_, _)) + .WillOnce( + [this](const Address::InstanceConstSharedPtr&, absl::AnyInvocable callback) { + parent_drained_callback_ = std::move(callback); + }); + UdpListenerImplTest::setup(); + testing::Mock::VerifyAndClearExpectations(®istrar_); + } + +protected: + MockParentDrainedCallbackRegistrar registrar_; + MockIoHandle* io_handle_; + absl::AnyInvocable parent_drained_callback_; +}; + +INSTANTIATE_TEST_SUITE_P(IpVersions, HotRestartedUdpListenerImplTest, + testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), + TestUtility::ipTestParamsToString); + +/** + * During hot restart, while the parent instance is draining, a quic udp + * listener (created with a parent_drained_callback_registrar) should not + * be reading packets, regardless of enable/disable calls. + * It should begin reading packets after drain completes. + */ +TEST_P(HotRestartedUdpListenerImplTest, EnableAndDisableDuringParentDrainShouldDoNothing) { + setup(); + // Enabling and disabling listener should *not* trigger any + // event actions on the io_handle, because of listener being paused + // while draining. + EXPECT_CALL(*io_handle_, enableFileEvents(_)).Times(0); + listener_->disable(); + listener_->enable(); + testing::Mock::VerifyAndClearExpectations(io_handle_); + // Ending parent drain should cause io_handle to go into reading mode. + EXPECT_CALL(*io_handle_, + enableFileEvents(Event::FileReadyType::Read | Event::FileReadyType::Write)); + EXPECT_CALL(*io_handle_, activateFileEvents(Event::FileReadyType::Read)); + std::move(parent_drained_callback_)(); + dispatcher_->run(Event::Dispatcher::RunType::Block); + testing::Mock::VerifyAndClearExpectations(io_handle_); + // Enabling and disabling once unpaused should update io_handle. + EXPECT_CALL(*io_handle_, enableFileEvents(0)); + listener_->disable(); + testing::Mock::VerifyAndClearExpectations(io_handle_); + EXPECT_CALL(*io_handle_, + enableFileEvents(Event::FileReadyType::Read | Event::FileReadyType::Write)); + listener_->enable(); + testing::Mock::VerifyAndClearExpectations(io_handle_); +} + +/** + * Mostly the same as EnableAndDisableDuringParentDrainShouldDoNothing, but in disabled state when + * drain ends. + */ +TEST_P(HotRestartedUdpListenerImplTest, EndingParentDrainedWhileDisabledShouldNotStartReading) { + setup(); + // Enabling and disabling listener should *not* trigger any + // event actions on the io_handle, because of listener being paused + // while draining. + EXPECT_CALL(*io_handle_, enableFileEvents(_)).Times(0); + listener_->enable(); + listener_->disable(); + testing::Mock::VerifyAndClearExpectations(io_handle_); + // Ending drain should not trigger any event changes because the last state + // of the listener was disabled. + std::move(parent_drained_callback_)(); + dispatcher_->run(Event::Dispatcher::RunType::Block); + testing::Mock::VerifyAndClearExpectations(io_handle_); + // Enabling after unpaused should set io_handle to reading/writing. + EXPECT_CALL(*io_handle_, + enableFileEvents(Event::FileReadyType::Read | Event::FileReadyType::Write)); + listener_->enable(); + testing::Mock::VerifyAndClearExpectations(io_handle_); +} + +TEST_P(HotRestartedUdpListenerImplTest, + ParentDrainedCallbackAfterListenerDestroyedShouldDoNothing) { + setup(); + EXPECT_CALL(*io_handle_, enableFileEvents(_)).Times(0); + listener_ = nullptr; + // Signaling end-of-drain after the listener was destroyed should do nothing. + std::move(parent_drained_callback_)(); + dispatcher_->run(Event::Dispatcher::RunType::Block); + // At this point io_handle should be an invalid reference. +} + /** * Tests UDP listener's error callback. */ diff --git a/test/common/network/udp_listener_impl_test_base.h b/test/common/network/udp_listener_impl_test_base.h index 112f89d68dc3..9b7636e13ae8 100644 --- a/test/common/network/udp_listener_impl_test_base.h +++ b/test/common/network/udp_listener_impl_test_base.h @@ -31,13 +31,24 @@ namespace Envoy { namespace Network { class UdpListenerImplTestBase : public ListenerImplTestBase { -public: - UdpListenerImplTestBase() - : server_socket_(createServerSocket(true)), send_to_addr_(getServerLoopbackAddress()) { +protected: + MockIoHandle& + useHotRestartSocket(OptRef parent_drained_callback_registrar) { + auto io_handle = std::make_unique>(); + MockIoHandle& ret = *io_handle; + server_socket_ = createServerSocketFromExistingHandle(std::move(io_handle), + parent_drained_callback_registrar); + return ret; + } + + void setup() { + if (server_socket_ == nullptr) { + server_socket_ = createServerSocket(true); + } + send_to_addr_ = Address::InstanceConstSharedPtr(getServerLoopbackAddress()); time_system_.advanceTimeWait(std::chrono::milliseconds(100)); } -protected: Address::Instance* getServerLoopbackAddress() { if (version_ == Address::IpVersion::v4) { return new Address::Ipv4Instance( @@ -60,6 +71,14 @@ class UdpListenerImplTestBase : public ListenerImplTestBase { bind); } + SocketSharedPtr createServerSocketFromExistingHandle( + IoHandlePtr&& io_handle, + OptRef parent_drained_callback_registrar) { + return std::make_shared( + std::move(io_handle), Network::Test::getCanonicalLoopbackAddress(version_), + SocketOptionFactory::buildIpFreebindOptions(), parent_drained_callback_registrar); + } + Address::InstanceConstSharedPtr getNonDefaultSourceAddress() { // Use a self address that is unlikely to be picked by source address discovery // algorithm if not specified in recvmsg/recvmmsg. Port is not taken into diff --git a/test/integration/python/hotrestart_handoff_test.py b/test/integration/python/hotrestart_handoff_test.py index 7d975bd7dda7..dcbbb5a8ba1e 100644 --- a/test/integration/python/hotrestart_handoff_test.py +++ b/test/integration/python/hotrestart_handoff_test.py @@ -304,7 +304,7 @@ async def _wait_for_envoy_epoch(i: int): pass await asyncio.sleep(0.2) # Envoy instance with expected restart_epoch should have started up - assert expected_substring in response, f"server_info={response}" + assert expected_substring in response, f"expected_substring={expected_substring}, server_info={response}" class IntegrationTest(unittest.IsolatedAsyncioTestCase): diff --git a/test/mocks/network/BUILD b/test/mocks/network/BUILD index 82b5ef8c79f1..73c9854193a3 100644 --- a/test/mocks/network/BUILD +++ b/test/mocks/network/BUILD @@ -42,6 +42,12 @@ envoy_cc_mock( ], ) +envoy_cc_mock( + name = "mock_parent_drained_callback_registrar", + hdrs = ["mock_parent_drained_callback_registrar.h"], + deps = ["//envoy/network:parent_drained_callback_registrar_interface"], +) + envoy_cc_mock( name = "network_mocks", srcs = ["mocks.cc"], diff --git a/test/mocks/network/mock_parent_drained_callback_registrar.h b/test/mocks/network/mock_parent_drained_callback_registrar.h new file mode 100644 index 000000000000..ae82b52d31f6 --- /dev/null +++ b/test/mocks/network/mock_parent_drained_callback_registrar.h @@ -0,0 +1,18 @@ +#pragma once + +#include "envoy/network/parent_drained_callback_registrar.h" + +#include "gmock/gmock.h" + +namespace Envoy { +namespace Network { + +class MockParentDrainedCallbackRegistrar : public ParentDrainedCallbackRegistrar { +public: + MOCK_METHOD(void, registerParentDrainedCallback, + (const Address::InstanceConstSharedPtr& address, + absl::AnyInvocable callback)); +}; + +} // namespace Network +} // namespace Envoy diff --git a/test/mocks/server/hot_restart.h b/test/mocks/server/hot_restart.h index c83142692c06..99bfa3ccbb0c 100644 --- a/test/mocks/server/hot_restart.h +++ b/test/mocks/server/hot_restart.h @@ -20,6 +20,8 @@ class MockHotRestart : public HotRestart { MOCK_METHOD(void, registerUdpForwardingListener, (Network::Address::InstanceConstSharedPtr address, std::shared_ptr listener_config)); + MOCK_METHOD(OptRef, parentDrainedCallbackRegistrar, ()); + MOCK_METHOD(void, whenDrainComplete, (absl::string_view addr, absl::AnyInvocable action)); MOCK_METHOD(void, initialize, (Event::Dispatcher & dispatcher, Server::Instance& server)); MOCK_METHOD(absl::optional, sendParentAdminShutdownRequest, ()); MOCK_METHOD(void, sendParentTerminateRequest, ()); diff --git a/test/server/hot_restart_impl_test.cc b/test/server/hot_restart_impl_test.cc index 81baf88181b5..28e1427564e1 100644 --- a/test/server/hot_restart_impl_test.cc +++ b/test/server/hot_restart_impl_test.cc @@ -87,6 +87,14 @@ class HotRestartImplTest : public testing::Test { std::unique_ptr hot_restart_; }; +TEST_F(HotRestartImplTest, ParentDrainedCallbackRegistrarIsSetAndCanBeCalled) { + setup(); + OptRef registrar = + hot_restart_->parentDrainedCallbackRegistrar(); + ASSERT_TRUE(registrar.has_value()); + registrar->registerParentDrainedCallback(test_addresses_.ipv4_test_addr_, []() {}); +} + TEST_F(HotRestartImplTest, VersionString) { // Tests that the version-string will be consistent and HOT_RESTART_VERSION, // between multiple instantiations. diff --git a/test/server/hot_restarting_child_test.cc b/test/server/hot_restarting_child_test.cc index f2455034f054..d16653d6454a 100644 --- a/test/server/hot_restarting_child_test.cc +++ b/test/server/hot_restarting_child_test.cc @@ -67,6 +67,11 @@ class FakeHotRestartingParent : public HotRestartingBase { }); udp_forwarding_rpc_stream_.sendHotRestartMessage(child_address_udp_forwarding_, message); } + void expectParentTerminateMessages() { + EXPECT_CALL(os_sys_calls_, sendmsg(_, _, _)).WillOnce([](int, const msghdr* msg, int) { + return Api::SysCallSizeResult{static_cast(msg->msg_iov[0].iov_len), 0}; + }); + } Api::MockOsSysCalls& os_sys_calls_; Event::FileReadyCb udp_file_ready_callback_; sockaddr_un child_address_udp_forwarding_; @@ -100,6 +105,36 @@ class HotRestartingChildTest : public testing::Test { std::unique_ptr hot_restarting_child_; }; +TEST_F(HotRestartingChildTest, ParentDrainedCallbacksAreCalled) { + auto test_listener_addr = Network::Utility::resolveUrl("udp://127.0.0.1:1234"); + auto test_listener_addr2 = Network::Utility::resolveUrl("udp://127.0.0.1:1235"); + testing::MockFunction callback1; + testing::MockFunction callback2; + hot_restarting_child_->registerParentDrainedCallback(test_listener_addr, + callback1.AsStdFunction()); + hot_restarting_child_->registerParentDrainedCallback(test_listener_addr2, + callback2.AsStdFunction()); + EXPECT_CALL(callback1, Call()); + EXPECT_CALL(callback2, Call()); + fake_parent_->expectParentTerminateMessages(); + hot_restarting_child_->sendParentTerminateRequest(); +} + +TEST_F(HotRestartingChildTest, ParentDrainedCallbacksAreCalledImmediatelyWhenAlreadyDrained) { + auto test_listener_addr = Network::Utility::resolveUrl("udp://127.0.0.1:1234"); + auto test_listener_addr2 = Network::Utility::resolveUrl("udp://127.0.0.1:1235"); + testing::MockFunction callback1; + testing::MockFunction callback2; + fake_parent_->expectParentTerminateMessages(); + hot_restarting_child_->sendParentTerminateRequest(); + EXPECT_CALL(callback1, Call()); + EXPECT_CALL(callback2, Call()); + hot_restarting_child_->registerParentDrainedCallback(test_listener_addr, + callback1.AsStdFunction()); + hot_restarting_child_->registerParentDrainedCallback(test_listener_addr2, + callback2.AsStdFunction()); +} + TEST_F(HotRestartingChildTest, LogsErrorOnReplyMessageInUdpStream) { envoy::HotRestartMessage msg; msg.mutable_reply(); From 572860489074d1a47b05de307dec0f275f3c1410 Mon Sep 17 00:00:00 2001 From: Greg Greenway Date: Thu, 29 Feb 2024 09:40:39 -0800 Subject: [PATCH 126/151] network: fix tsan test flake (#32631) Fixes #32527 Signed-off-by: Greg Greenway --- source/common/network/address_impl.cc | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/source/common/network/address_impl.cc b/source/common/network/address_impl.cc index cb6b233cdc7a..40dfb793261c 100644 --- a/source/common/network/address_impl.cc +++ b/source/common/network/address_impl.cc @@ -213,18 +213,18 @@ std::string Ipv4Instance::sockaddrToString(const sockaddr_in& addr) { } namespace { -bool force_ipv4_unsupported_for_test = false; +std::atomic force_ipv4_unsupported_for_test = false; } Cleanup Ipv4Instance::forceProtocolUnsupportedForTest(bool new_val) { - bool old_val = force_ipv4_unsupported_for_test; - force_ipv4_unsupported_for_test = new_val; - return {[old_val]() { force_ipv4_unsupported_for_test = old_val; }}; + bool old_val = force_ipv4_unsupported_for_test.load(); + force_ipv4_unsupported_for_test.store(new_val); + return {[old_val]() { force_ipv4_unsupported_for_test.store(old_val); }}; } absl::Status Ipv4Instance::validateProtocolSupported() { static const bool supported = SocketInterfaceSingleton::get().ipFamilySupported(AF_INET); - if (supported && !force_ipv4_unsupported_for_test) { + if (supported && !force_ipv4_unsupported_for_test.load(std::memory_order_relaxed)) { return absl::OkStatus(); } return absl::FailedPreconditionError("IPv4 addresses are not supported on this machine"); @@ -335,18 +335,18 @@ Ipv6Instance::Ipv6Instance(absl::Status& status, const sockaddr_in6& address, bo } namespace { -bool force_ipv6_unsupported_for_test = false; +std::atomic force_ipv6_unsupported_for_test = false; } Cleanup Ipv6Instance::forceProtocolUnsupportedForTest(bool new_val) { - bool old_val = force_ipv6_unsupported_for_test; - force_ipv6_unsupported_for_test = new_val; - return {[old_val]() { force_ipv6_unsupported_for_test = old_val; }}; + bool old_val = force_ipv6_unsupported_for_test.load(); + force_ipv6_unsupported_for_test.store(new_val); + return {[old_val]() { force_ipv6_unsupported_for_test.store(old_val); }}; } absl::Status Ipv6Instance::validateProtocolSupported() { static const bool supported = SocketInterfaceSingleton::get().ipFamilySupported(AF_INET6); - if (supported && !force_ipv6_unsupported_for_test) { + if (supported && !force_ipv6_unsupported_for_test.load(std::memory_order_relaxed)) { return absl::OkStatus(); } return absl::FailedPreconditionError("IPv6 addresses are not supported on this machine"); From 300c26c062ca1f14d864763d4487a25707f5483c Mon Sep 17 00:00:00 2001 From: danzh Date: Thu, 29 Feb 2024 23:11:19 -0500 Subject: [PATCH 127/151] Update QUICHE from 9e8759380 to 02047e04d (#32642) https://github.com/google/quiche/compare/9e8759380..02047e04d ``` $ git log 9e8759380..02047e04d --date=short --no-merges --format="%ad %al %s" 2024-02-27 rch Deprecate --gfe2_restart_flag_quic_allow_control_frames_while_procesing. 2024-02-27 quiche-dev Remove unused locals and trivial assignments from StructuredHeaderParser 2024-02-26 awillia Fix issues with Hpack EXPECT_DEATH test on Android + ChromeOS 2024-02-26 wub Skip a QUIC_BUG in QuicConnection::SendAllPendingAcks when connection is closed. 2024-02-26 quiche-dev Add [[maybe_unused]] 2024-02-26 diannahu Fix a potential Http2WriteQueue overflow scenario. 2024-02-23 danzh Change ProcessAdditionalTransportParameters() to return a bool to indicate whether the handshake should continue or immediately fail. 2024-02-23 quiche-dev Add GFE_CODE_COUNT for ALPS use old codepoint 2024-02-21 martinduke Update MoQT chat client to use InteractiveCli when there is no output file. 2024-02-20 vasilvv Add logic to handle ANNOUNCE messages. 2024-02-20 vasilvv Add InteractiveCli tool class, for use in MoQT chat client. 2024-02-20 martinduke Improve UI by streaming MoQT chat messages to a file. 2024-02-20 vasilvv Always buffer control messages in MoQT. 2024-02-20 wub In QUIC client, use normalized server address to determine whether a packet is from unknown server. 2024-02-18 vasilvv Refactor the framer and some of the publishing pathway for MOQT. 2024-02-17 martinduke Object handling in MoQT chat client. 2024-02-16 wub In GFE simple clients, change the socket fd configurators to run before bind(), because some socket options(e.g. Enable AnyIp and DualStack) must be set before it. 2024-02-16 birenroy Removes the `trailers_require_end_data` option from oghttp2. 2024-02-16 quiche-dev Add find and clear methods to structured-header Dictionary type ``` Signed-off-by: Dan Zhang Co-authored-by: Dan Zhang --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 7fffdb9bd777..6cc43c9ca296 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1209,12 +1209,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "9e875938052443c7f51b25012093be59e20598ac", - sha256 = "ae1d45c62b1a0e43384697c1becd8f8d3bf6624ba0dd05a0334b319aafecb1ef", + version = "02047e04d076d36226e92847a94acf33544f10d6", + sha256 = "0fc35f9794ae3ff698ff1f248129e3123e0cf699ff7dfa6563708316db00ce19", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2024-02-15", + release_date = "2024-02-28", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", From 136f7fd53f0546d8abf6f42a8293ff91fd573eef Mon Sep 17 00:00:00 2001 From: "Adi (Suissa) Peleg" Date: Fri, 1 Mar 2024 05:19:55 -0500 Subject: [PATCH 128/151] xDS: enable envoy.restart_features.use_eds_cache_for_ads by default (#32604) Caching of EDS responses was added in #28273 and we have been using it internally. This PR flips the runtime guard to be true by default. This solves the issue of not receiving EDS assignments after receiving a cluster update (in a CDS response). Risk Level: medium - may impact memory, but should not impact data-plane/config-plane behavior Testing: updated in previous PRs Docs Changes: updated in previous PRs Release Notes: Added Platform Specific Features: N/A Signed-off-by: Adi Suissa-Peleg --- changelogs/current.yaml | 9 +++++++++ source/common/runtime/runtime_features.cc | 3 +-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0fc50260fb23..8239ae092e43 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -11,6 +11,15 @@ behavior_changes: The runtime flag ``envoy.reloadable_features.enable_include_histograms`` is now enabled by default. This causes the ``includeHistogram()`` method on ``Stats::SinkPredicates`` to filter histograms to be flushed to stat sinks. +- area: eds + change: | + Enabling caching caching of EDS assignments when used with ADS by default (introduced in Envoy v1.28). + Prior to this change, Envoy required that EDS assignments were sent after an EDS cluster was updated. + If no EDS assignment was received for the cluster, it ended up with an empty assignment. + Following this change, after a cluster update, Envoy waits for an EDS assignment until + :ref:`initial_fetch_timeout ` times out, and will then apply + the cached assignment and finish updating the warmed cluster. This change temporarily disabled by setting + the runtime flag ``envoy.restart_features.use_eds_cache_for_ads`` to ``false``. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 8ea3e31f731b..68c68f32e75b 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -102,6 +102,7 @@ RUNTIME_GUARD(envoy_reloadable_features_validate_grpc_header_before_log_grpc_sta RUNTIME_GUARD(envoy_reloadable_features_validate_upstream_headers); RUNTIME_GUARD(envoy_restart_features_send_goaway_for_premature_rst_streams); RUNTIME_GUARD(envoy_restart_features_udp_read_normalize_addresses); +RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); // Begin false flags. Most of them should come with a TODO to flip true. @@ -133,8 +134,6 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_quiche_use_mem_slice_releasor_api) // remove the feature flag and remove code path that relies on old technique to fetch credentials // via libcurl and remove the bazel steps to pull and test the curl dependency. FALSE_RUNTIME_GUARD(envoy_reloadable_features_use_http_client_to_fetch_aws_credentials); -// TODO(adisuissa): enable by default once this is tested in prod. -FALSE_RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); // TODO(#10646) change to true when UHV is sufficiently tested // For more information about Universal Header Validation, please see // https://github.com/envoyproxy/envoy/issues/10646 From d9048ad4a75ce00c13b9f06ebad45a938427b3ec Mon Sep 17 00:00:00 2001 From: botengyao Date: Fri, 1 Mar 2024 08:21:31 -0500 Subject: [PATCH 129/151] docs: fix typo (#32649) Signed-off-by: Boteng Yao --- changelogs/current.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 8239ae092e43..ffaec77acce2 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -124,7 +124,7 @@ bug_fixes: Fixed JWT extractor, which concatenated headers with a comma, resultig in invalid tokens. - area: router change: | - Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happends. This is + Fix a timing issue when upstream requests are empty when decoding data and send local reply when that happens. This is controlled by ``envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request``. - area: deps change: | From 0ceff607112da7b4172f4273e67b496a0277d21c Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 Mar 2024 14:56:33 +0000 Subject: [PATCH 130/151] ci: Set default timeout (and add for windows) (#32635) Signed-off-by: Ryan Northey --- .github/workflows/_run.yml | 1 + .github/workflows/envoy-windows.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 14199b0fcd73..6977b51c76d9 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -116,6 +116,7 @@ on: type: string timeout-minutes: type: number + default: 60 trusted: type: boolean required: true diff --git a/.github/workflows/envoy-windows.yml b/.github/workflows/envoy-windows.yml index 17be3ff309e8..fbd01b992a95 100644 --- a/.github/workflows/envoy-windows.yml +++ b/.github/workflows/envoy-windows.yml @@ -65,6 +65,7 @@ jobs: steps-post: target: ${{ matrix.target }} temp-dir: 'C:\Users\runner\AppData\Local\Temp\bazel-shared' + timeout-minutes: 120 trusted: ${{ fromJSON(needs.load.outputs.trusted) }} upload-name: windows.release upload-path: 'C:\Users\runner\AppData\Local\Temp\envoy' From 28ed0f4f3fba491bbf0abc2bcfa69430b501f2ad Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 Mar 2024 15:24:44 +0000 Subject: [PATCH 131/151] =?UTF-8?q?Revert=20"QUIC=20hot=20restart=20part?= =?UTF-8?q?=206=20-=20child=20instance=20pauses=20listening=20unt=E2=80=A6?= =?UTF-8?q?=20(#32658)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert "QUIC hot restart part 6 - child instance pauses listening until parent is drained (#31130)" This reverts commit f7352b3237d5b16cf7e078733b32ba158e66d086. Signed-off-by: Ryan Northey --- envoy/network/BUILD | 6 -- .../parent_drained_callback_registrar.h | 29 ----- envoy/network/socket.h | 7 -- envoy/server/hot_restart.h | 11 -- .../listener_manager/listener_manager_impl.cc | 5 +- source/common/network/BUILD | 1 - source/common/network/listen_socket_impl.h | 19 +--- source/common/network/udp_listener_impl.cc | 40 +------ source/common/network/udp_listener_impl.h | 6 -- source/server/BUILD | 1 - source/server/hot_restart_impl.cc | 4 - source/server/hot_restart_impl.h | 1 - source/server/hot_restart_nop_impl.h | 3 - source/server/hot_restarting_child.cc | 53 +++------ source/server/hot_restarting_child.h | 17 +-- test/common/network/BUILD | 1 - .../udp_listener_impl_batch_writer_test.cc | 1 - test/common/network/udp_listener_impl_test.cc | 102 ------------------ .../network/udp_listener_impl_test_base.h | 27 +---- .../python/hotrestart_handoff_test.py | 2 +- test/mocks/network/BUILD | 6 -- .../mock_parent_drained_callback_registrar.h | 18 ---- test/mocks/server/hot_restart.h | 2 - test/server/hot_restart_impl_test.cc | 8 -- test/server/hot_restarting_child_test.cc | 35 ------ 25 files changed, 29 insertions(+), 376 deletions(-) delete mode 100644 envoy/network/parent_drained_callback_registrar.h delete mode 100644 test/mocks/network/mock_parent_drained_callback_registrar.h diff --git a/envoy/network/BUILD b/envoy/network/BUILD index 230f7c065d58..3e7d51a07e90 100644 --- a/envoy/network/BUILD +++ b/envoy/network/BUILD @@ -58,12 +58,6 @@ envoy_cc_library( ], ) -envoy_cc_library( - name = "parent_drained_callback_registrar_interface", - hdrs = ["parent_drained_callback_registrar.h"], - deps = [":address_interface"], -) - envoy_cc_library( name = "udp_packet_writer_handler_interface", hdrs = ["udp_packet_writer_handler.h"], diff --git a/envoy/network/parent_drained_callback_registrar.h b/envoy/network/parent_drained_callback_registrar.h deleted file mode 100644 index d0ce7c9a191e..000000000000 --- a/envoy/network/parent_drained_callback_registrar.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include "envoy/network/address.h" - -#include "absl/functional/any_invocable.h" - -namespace Envoy { -namespace Network { - -/** - * An interface through which a UDP listen socket, especially a QUIC socket, can - * postpone reading during hot restart until the parent instance is drained. - */ -class ParentDrainedCallbackRegistrar { -public: - /** - * @param address is the address of the listener. - * @param callback the function to call when the listener matching address is - * drained on the parent instance. - */ - virtual void registerParentDrainedCallback(const Address::InstanceConstSharedPtr& address, - absl::AnyInvocable callback) PURE; - -protected: - virtual ~ParentDrainedCallbackRegistrar() = default; -}; - -} // namespace Network -} // namespace Envoy diff --git a/envoy/network/socket.h b/envoy/network/socket.h index 6091cf9fe922..b83d0f213047 100644 --- a/envoy/network/socket.h +++ b/envoy/network/socket.h @@ -542,13 +542,6 @@ class Socket { * @return the socket options stored earlier with addOption() and addOptions() calls, if any. */ virtual const OptionsSharedPtr& options() const PURE; - - /** - * @return a ParentDrainedCallbackRegistrar for UDP listen sockets during hot restart. - */ - virtual OptRef parentDrainedCallbackRegistrar() const { - return absl::nullopt; - } }; using SocketPtr = std::unique_ptr; diff --git a/envoy/server/hot_restart.h b/envoy/server/hot_restart.h index 8e201dd65e08..a1ce1663cde4 100644 --- a/envoy/server/hot_restart.h +++ b/envoy/server/hot_restart.h @@ -62,17 +62,6 @@ class HotRestart { virtual void registerUdpForwardingListener(Network::Address::InstanceConstSharedPtr address, std::shared_ptr listener_config) PURE; - - /** - * @return An interface on which registerParentDrainedCallback can be called during - * creation of a listener, or nullopt if there is no parent instance. - * - * If this is set, any UDP listener should start paused and only begin listening - * when the parent instance is drained; this allows draining QUIC listeners to - * catch their own packets and forward unrecognized packets to the child instance. - */ - virtual OptRef parentDrainedCallbackRegistrar() PURE; - /** * Initialize the parent logic of our restarter. Meant to be called after initialization of a * new child has begun. The hot restart implementation needs to be created early to deal with diff --git a/source/common/listener_manager/listener_manager_impl.cc b/source/common/listener_manager/listener_manager_impl.cc index 930378a94b94..853087d0b1bf 100644 --- a/source/common/listener_manager/listener_manager_impl.cc +++ b/source/common/listener_manager/listener_manager_impl.cc @@ -321,10 +321,7 @@ Network::SocketSharedPtr ProdListenerComponentFactory::createListenSocket( if (socket_type == Network::Socket::Type::Stream) { return std::make_shared(std::move(io_handle), address, options); } else { - auto socket = std::make_shared( - std::move(io_handle), address, options, - server_.hotRestart().parentDrainedCallbackRegistrar()); - return socket; + return std::make_shared(std::move(io_handle), address, options); } } } diff --git a/source/common/network/BUILD b/source/common/network/BUILD index 53ca953779a4..c563ca21428b 100644 --- a/source/common/network/BUILD +++ b/source/common/network/BUILD @@ -344,7 +344,6 @@ envoy_cc_library( "//envoy/event:file_event_interface", "//envoy/network:exception_interface", "//envoy/network:listener_interface", - "//envoy/network:parent_drained_callback_registrar_interface", "//envoy/runtime:runtime_interface", "//envoy/stats:stats_interface", "//envoy/stats:stats_macros", diff --git a/source/common/network/listen_socket_impl.h b/source/common/network/listen_socket_impl.h index e17e3178637d..1dd9a6f668d7 100644 --- a/source/common/network/listen_socket_impl.h +++ b/source/common/network/listen_socket_impl.h @@ -68,19 +68,12 @@ template class NetworkListenSocket : public ListenSocketImpl { } } - NetworkListenSocket( - IoHandlePtr&& io_handle, const Address::InstanceConstSharedPtr& address, - const Network::Socket::OptionsSharedPtr& options, - OptRef parent_drained_callback_registrar = absl::nullopt) - : ListenSocketImpl(std::move(io_handle), address), - parent_drained_callback_registrar_(parent_drained_callback_registrar) { + NetworkListenSocket(IoHandlePtr&& io_handle, const Address::InstanceConstSharedPtr& address, + const Network::Socket::OptionsSharedPtr& options) + : ListenSocketImpl(std::move(io_handle), address) { setListenSocketOptions(options); } - OptRef parentDrainedCallbackRegistrar() const override { - return parent_drained_callback_registrar_; - } - Socket::Type socketType() const override { return T::type; } SocketPtr duplicate() override { @@ -117,12 +110,6 @@ template class NetworkListenSocket : public ListenSocketImpl { } protected: - // Usually a socket when initialized starts listening for ready-to-read or ready-to-write events; - // for a QUIC socket during hot restart this is undesirable as the parent instance needs to - // receive all packets; in that case this interface is set, and listening won't begin until the - // callback is called. - OptRef parent_drained_callback_registrar_; - void setPrebindSocketOptions() { // On Windows, SO_REUSEADDR does not restrict subsequent bind calls when there is a listener as // on Linux and later BSD socket stacks. diff --git a/source/common/network/udp_listener_impl.cc b/source/common/network/udp_listener_impl.cc index a184eaae7036..62c5b273db96 100644 --- a/source/common/network/udp_listener_impl.cc +++ b/source/common/network/udp_listener_impl.cc @@ -9,7 +9,6 @@ #include "envoy/common/platform.h" #include "envoy/config/core/v3/base.pb.h" #include "envoy/network/exception.h" -#include "envoy/network/parent_drained_callback_registrar.h" #include "source/common/api/os_sys_calls_impl.h" #include "source/common/common/assert.h" @@ -35,34 +34,9 @@ UdpListenerImpl::UdpListenerImpl(Event::Dispatcher& dispatcher, SocketSharedPtr : BaseListenerImpl(dispatcher, std::move(socket)), cb_(cb), time_source_(time_source), // Default prefer_gro to false for downstream server traffic. config_(config, false) { - parent_drained_callback_registrar_ = socket_->parentDrainedCallbackRegistrar(); socket_->ioHandle().initializeFileEvent( dispatcher, [this](uint32_t events) -> void { onSocketEvent(events); }, - Event::PlatformDefaultTriggerType, paused() ? 0 : events_when_unpaused_); - if (paused()) { - parent_drained_callback_registrar_->registerParentDrainedCallback( - socket_->connectionInfoProvider().localAddress(), - [this, &dispatcher, alive = std::weak_ptr(destruction_checker_)]() { - dispatcher.post([this, alive = std::move(alive)]() { - auto still_alive = alive.lock(); - if (still_alive != nullptr) { - unpause(); - } - }); - }); - } -} - -void UdpListenerImpl::unpause() { - // Remove the paused state so enable will actually start listening to events. - parent_drained_callback_registrar_ = absl::nullopt; - if (events_when_unpaused_ != 0) { - // Start listening to events. - enable(); - // There may have already been events while this instance was ignoring them, - // so try reading immediately. - activateRead(); - } + Event::PlatformDefaultTriggerType, Event::FileReadyType::Read | Event::FileReadyType::Write); } UdpListenerImpl::~UdpListenerImpl() { socket_->ioHandle().resetFileEvents(); } @@ -70,18 +44,10 @@ UdpListenerImpl::~UdpListenerImpl() { socket_->ioHandle().resetFileEvents(); } void UdpListenerImpl::disable() { disableEvent(); } void UdpListenerImpl::enable() { - events_when_unpaused_ = Event::FileReadyType::Read | Event::FileReadyType::Write; - if (!paused()) { - socket_->ioHandle().enableFileEvents(events_when_unpaused_); - } + socket_->ioHandle().enableFileEvents(Event::FileReadyType::Read | Event::FileReadyType::Write); } -void UdpListenerImpl::disableEvent() { - events_when_unpaused_ = 0; - if (!paused()) { - socket_->ioHandle().enableFileEvents(0); - } -} +void UdpListenerImpl::disableEvent() { socket_->ioHandle().enableFileEvents(0); } void UdpListenerImpl::onSocketEvent(short flags) { ASSERT((flags & (Event::FileReadyType::Read | Event::FileReadyType::Write))); diff --git a/source/common/network/udp_listener_impl.h b/source/common/network/udp_listener_impl.h index 244f93f3923b..723c3c74de75 100644 --- a/source/common/network/udp_listener_impl.h +++ b/source/common/network/udp_listener_impl.h @@ -26,8 +26,6 @@ class UdpListenerImpl : public BaseListenerImpl, TimeSource& time_source, const envoy::config::core::v3::UdpSocketConfig& config); ~UdpListenerImpl() override; uint32_t packetsDropped() { return packets_dropped_; } - bool paused() const { return parent_drained_callback_registrar_ != absl::nullopt; } - void unpause(); // Network::Listener void disable() override; @@ -65,10 +63,6 @@ class UdpListenerImpl : public BaseListenerImpl, TimeSource& time_source_; const ResolvedUdpSocketConfig config_; - OptRef parent_drained_callback_registrar_; - // Taking a weak_ptr to this lets us detect if the listener has been destroyed. - std::shared_ptr destruction_checker_ = std::make_shared(true); - uint32_t events_when_unpaused_ = Event::FileReadyType::Read | Event::FileReadyType::Write; }; class UdpListenerWorkerRouterImpl : public UdpListenerWorkerRouter { diff --git a/source/server/BUILD b/source/server/BUILD index f656303df03e..7ee402750e1f 100644 --- a/source/server/BUILD +++ b/source/server/BUILD @@ -187,7 +187,6 @@ envoy_cc_library( hdrs = envoy_select_hot_restart(["hot_restarting_child.h"]), deps = [ ":hot_restarting_base", - "//envoy/network:parent_drained_callback_registrar_interface", "//source/common/stats:stat_merger_lib", ], ) diff --git a/source/server/hot_restart_impl.cc b/source/server/hot_restart_impl.cc index 6e2377c9c6f0..417bb4a5f2f3 100644 --- a/source/server/hot_restart_impl.cc +++ b/source/server/hot_restart_impl.cc @@ -124,10 +124,6 @@ void HotRestartImpl::registerUdpForwardingListener( as_child_.registerUdpForwardingListener(address, listener_config); } -OptRef HotRestartImpl::parentDrainedCallbackRegistrar() { - return as_child_; -} - void HotRestartImpl::initialize(Event::Dispatcher& dispatcher, Server::Instance& server) { as_parent_.initialize(dispatcher, server); as_child_.initialize(dispatcher); diff --git a/source/server/hot_restart_impl.h b/source/server/hot_restart_impl.h index 9a22b6f3ec13..ace7d41321d9 100644 --- a/source/server/hot_restart_impl.h +++ b/source/server/hot_restart_impl.h @@ -106,7 +106,6 @@ class HotRestartImpl : public HotRestart { void registerUdpForwardingListener( Network::Address::InstanceConstSharedPtr address, std::shared_ptr listener_config) override; - OptRef parentDrainedCallbackRegistrar() override; void initialize(Event::Dispatcher& dispatcher, Server::Instance& server) override; absl::optional sendParentAdminShutdownRequest() override; void sendParentTerminateRequest() override; diff --git a/source/server/hot_restart_nop_impl.h b/source/server/hot_restart_nop_impl.h index 031cf1e4613b..99f006083937 100644 --- a/source/server/hot_restart_nop_impl.h +++ b/source/server/hot_restart_nop_impl.h @@ -20,9 +20,6 @@ class HotRestartNopImpl : public Server::HotRestart { int duplicateParentListenSocket(const std::string&, uint32_t) override { return -1; } void registerUdpForwardingListener(Network::Address::InstanceConstSharedPtr, std::shared_ptr) override {} - OptRef parentDrainedCallbackRegistrar() override { - return absl::nullopt; - } void initialize(Event::Dispatcher&, Server::Instance&) override {} absl::optional sendParentAdminShutdownRequest() override { return absl::nullopt; diff --git a/source/server/hot_restarting_child.cc b/source/server/hot_restarting_child.cc index 0bb33e650db9..0d842a2755eb 100644 --- a/source/server/hot_restarting_child.cc +++ b/source/server/hot_restarting_child.cc @@ -46,12 +46,9 @@ HotRestartingChild::UdpForwardingContext::getListenerForDestination( return it->second; } -// If restart_epoch is 0 there is no parent, so it's effectively already -// drained and terminated. HotRestartingChild::HotRestartingChild(int base_id, int restart_epoch, const std::string& socket_path, mode_t socket_mode) - : HotRestartingBase(base_id), restart_epoch_(restart_epoch), - parent_terminated_(restart_epoch == 0), parent_drained_(restart_epoch == 0) { + : HotRestartingBase(base_id), restart_epoch_(restart_epoch) { main_rpc_stream_.initDomainSocketAddress(&parent_address_); std::string socket_path_udp = socket_path + "_udp"; udp_forwarding_rpc_stream_.initDomainSocketAddress(&parent_address_udp_forwarding_); @@ -105,7 +102,7 @@ void HotRestartingChild::onForwardedUdpPacket(uint32_t worker_index, Network::Ud int HotRestartingChild::duplicateParentListenSocket(const std::string& address, uint32_t worker_index) { - if (parent_terminated_) { + if (restart_epoch_ == 0 || parent_terminated_) { return -1; } @@ -124,7 +121,7 @@ int HotRestartingChild::duplicateParentListenSocket(const std::string& address, } std::unique_ptr HotRestartingChild::getParentStats() { - if (parent_terminated_) { + if (restart_epoch_ == 0 || parent_terminated_) { return nullptr; } @@ -141,7 +138,7 @@ std::unique_ptr HotRestartingChild::getParentStats() { } void HotRestartingChild::drainParentListeners() { - if (parent_terminated_) { + if (restart_epoch_ == 0 || parent_terminated_) { return; } // No reply expected. @@ -157,27 +154,9 @@ void HotRestartingChild::registerUdpForwardingListener( udp_forwarding_context_.registerListener(address, listener_config); } -void HotRestartingChild::registerParentDrainedCallback( - const Network::Address::InstanceConstSharedPtr& address, absl::AnyInvocable callback) { - if (parent_drained_) { - callback(); - } else { - on_drained_actions_.emplace(address->asString(), std::move(callback)); - } -} - -void HotRestartingChild::allDrainsImplicitlyComplete() { - for (auto& drain_action : on_drained_actions_) { - // Call the callback. - std::move(drain_action.second)(); - } - on_drained_actions_.clear(); - parent_drained_ = true; -} - absl::optional HotRestartingChild::sendParentAdminShutdownRequest() { - if (parent_terminated_) { + if (restart_epoch_ == 0 || parent_terminated_) { return absl::nullopt; } @@ -197,11 +176,9 @@ HotRestartingChild::sendParentAdminShutdownRequest() { } void HotRestartingChild::sendParentTerminateRequest() { - if (parent_terminated_) { + if (restart_epoch_ == 0 || parent_terminated_) { return; } - allDrainsImplicitlyComplete(); - HotRestartMessage wrapped_request; wrapped_request.mutable_request()->mutable_terminate(); main_rpc_stream_.sendHotRestartMessage(parent_address_, wrapped_request); @@ -209,17 +186,15 @@ void HotRestartingChild::sendParentTerminateRequest() { // Note that the 'generation' counter needs to retain the contribution from // the parent. - if (stat_merger_ != nullptr) { - stat_merger_->retainParentGaugeValue(hot_restart_generation_stat_name_); + stat_merger_->retainParentGaugeValue(hot_restart_generation_stat_name_); - // Now it is safe to forget our stat transferral state. - // - // This destruction is actually important far beyond memory efficiency. The - // scope-based temporary counter logic relies on the StatMerger getting - // destroyed once hot restart's stat merging is all done. (See stat_merger.h - // for details). - stat_merger_.reset(); - } + // Now it is safe to forget our stat transferral state. + // + // This destruction is actually important far beyond memory efficiency. The + // scope-based temporary counter logic relies on the StatMerger getting + // destroyed once hot restart's stat merging is all done. (See stat_merger.h + // for details). + stat_merger_.reset(); } void HotRestartingChild::mergeParentStats(Stats::Store& stats_store, diff --git a/source/server/hot_restarting_child.h b/source/server/hot_restarting_child.h index 7f485511b3d4..7f61dfc59f55 100644 --- a/source/server/hot_restarting_child.h +++ b/source/server/hot_restarting_child.h @@ -1,6 +1,5 @@ #pragma once -#include "envoy/network/parent_drained_callback_registrar.h" #include "envoy/server/instance.h" #include "source/common/stats/stat_merger.h" @@ -12,8 +11,7 @@ namespace Server { /** * The child half of hot restarting. Issues requests and commands to the parent. */ -class HotRestartingChild : public HotRestartingBase, - public Network::ParentDrainedCallbackRegistrar { +class HotRestartingChild : public HotRestartingBase { public: // A structure to record the set of registered UDP listeners keyed on their addresses, // to support QUIC packet forwarding. @@ -44,7 +42,7 @@ class HotRestartingChild : public HotRestartingBase, HotRestartingChild(int base_id, int restart_epoch, const std::string& socket_path, mode_t socket_mode); - ~HotRestartingChild() override = default; + ~HotRestartingChild() = default; void initialize(Event::Dispatcher& dispatcher); void shutdown(); @@ -52,9 +50,6 @@ class HotRestartingChild : public HotRestartingBase, int duplicateParentListenSocket(const std::string& address, uint32_t worker_index); void registerUdpForwardingListener(Network::Address::InstanceConstSharedPtr address, std::shared_ptr listener_config); - // From Network::ParentDrainedCallbackRegistrar. - void registerParentDrainedCallback(const Network::Address::InstanceConstSharedPtr& addr, - absl::AnyInvocable action) override; std::unique_ptr getParentStats(); void drainParentListeners(); absl::optional sendParentAdminShutdownRequest(); @@ -65,21 +60,15 @@ class HotRestartingChild : public HotRestartingBase, protected: void onSocketEventUdpForwarding(); void onForwardedUdpPacket(uint32_t worker_index, Network::UdpRecvData&& data); - // When call to terminate parent is sent, or parent is already terminated, - void allDrainsImplicitlyComplete(); private: friend class HotRestartUdpForwardingTestHelper; const int restart_epoch_; - bool parent_terminated_; - bool parent_drained_; + bool parent_terminated_{}; sockaddr_un parent_address_; sockaddr_un parent_address_udp_forwarding_; std::unique_ptr stat_merger_{}; Stats::StatName hot_restart_generation_stat_name_; - // There are multiple listener instances per address that must all be reactivated - // when the parent is drained, so a multimap is used to contain them. - std::unordered_multimap> on_drained_actions_; Event::FileEventPtr socket_event_udp_forwarding_; UdpForwardingContext udp_forwarding_context_; }; diff --git a/test/common/network/BUILD b/test/common/network/BUILD index 9e4c1c2e8205..ea59472db9b6 100644 --- a/test/common/network/BUILD +++ b/test/common/network/BUILD @@ -249,7 +249,6 @@ envoy_cc_test( "//source/common/network:utility_lib", "//source/common/stats:stats_lib", "//test/common/network:listener_impl_test_base_lib", - "//test/mocks/network:mock_parent_drained_callback_registrar", "//test/mocks/network:network_mocks", "//test/mocks/server:server_mocks", "//test/test_common:environment_lib", diff --git a/test/common/network/udp_listener_impl_batch_writer_test.cc b/test/common/network/udp_listener_impl_batch_writer_test.cc index 35156dbd6a83..39b69e86c02a 100644 --- a/test/common/network/udp_listener_impl_batch_writer_test.cc +++ b/test/common/network/udp_listener_impl_batch_writer_test.cc @@ -61,7 +61,6 @@ size_t getPacketLength(const msghdr* msg) { class UdpListenerImplBatchWriterTest : public UdpListenerImplTestBase { public: void SetUp() override { - UdpListenerImplTestBase::setup(); // Set listening socket options and set UdpGsoBatchWriter server_socket_->addOptions(SocketOptionFactory::buildIpPacketInfoOptions()); server_socket_->addOptions(SocketOptionFactory::buildRxQueueOverFlowOptions()); diff --git a/test/common/network/udp_listener_impl_test.cc b/test/common/network/udp_listener_impl_test.cc index 6df91c150355..18810ca7467a 100644 --- a/test/common/network/udp_listener_impl_test.cc +++ b/test/common/network/udp_listener_impl_test.cc @@ -16,7 +16,6 @@ #include "test/common/network/udp_listener_impl_test_base.h" #include "test/mocks/api/mocks.h" -#include "test/mocks/network/mock_parent_drained_callback_registrar.h" #include "test/mocks/network/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" @@ -53,7 +52,6 @@ class OverrideOsSysCallsImpl : public Api::OsSysCallsImpl { class UdpListenerImplTest : public UdpListenerImplTestBase { public: void setup(bool prefer_gro = false) { - UdpListenerImplTestBase::setup(); ON_CALL(override_syscall_, supportsUdpGro()).WillByDefault(Return(false)); // Return the real version by default. ON_CALL(override_syscall_, supportsMmsg()) @@ -387,106 +385,6 @@ TEST_P(UdpListenerImplTest, UdpListenerEnableDisable) { dispatcher_->run(Event::Dispatcher::RunType::Block); } -class HotRestartedUdpListenerImplTest : public UdpListenerImplTest { -public: - void SetUp() override { -#ifdef WIN32 - GTEST_SKIP() << "Hot restart is not supported on Windows."; -#endif - } - void setup() { - io_handle_ = &useHotRestartSocket(registrar_); - // File event should be created listening to no events (i.e. disabled). - EXPECT_CALL(*io_handle_, createFileEvent_(_, _, _, 0)); - // Parent drained callback should be registered when the listener is created. - // We capture the callback so we can simulate "drain complete". - EXPECT_CALL(registrar_, registerParentDrainedCallback(_, _)) - .WillOnce( - [this](const Address::InstanceConstSharedPtr&, absl::AnyInvocable callback) { - parent_drained_callback_ = std::move(callback); - }); - UdpListenerImplTest::setup(); - testing::Mock::VerifyAndClearExpectations(®istrar_); - } - -protected: - MockParentDrainedCallbackRegistrar registrar_; - MockIoHandle* io_handle_; - absl::AnyInvocable parent_drained_callback_; -}; - -INSTANTIATE_TEST_SUITE_P(IpVersions, HotRestartedUdpListenerImplTest, - testing::ValuesIn(TestEnvironment::getIpVersionsForTest()), - TestUtility::ipTestParamsToString); - -/** - * During hot restart, while the parent instance is draining, a quic udp - * listener (created with a parent_drained_callback_registrar) should not - * be reading packets, regardless of enable/disable calls. - * It should begin reading packets after drain completes. - */ -TEST_P(HotRestartedUdpListenerImplTest, EnableAndDisableDuringParentDrainShouldDoNothing) { - setup(); - // Enabling and disabling listener should *not* trigger any - // event actions on the io_handle, because of listener being paused - // while draining. - EXPECT_CALL(*io_handle_, enableFileEvents(_)).Times(0); - listener_->disable(); - listener_->enable(); - testing::Mock::VerifyAndClearExpectations(io_handle_); - // Ending parent drain should cause io_handle to go into reading mode. - EXPECT_CALL(*io_handle_, - enableFileEvents(Event::FileReadyType::Read | Event::FileReadyType::Write)); - EXPECT_CALL(*io_handle_, activateFileEvents(Event::FileReadyType::Read)); - std::move(parent_drained_callback_)(); - dispatcher_->run(Event::Dispatcher::RunType::Block); - testing::Mock::VerifyAndClearExpectations(io_handle_); - // Enabling and disabling once unpaused should update io_handle. - EXPECT_CALL(*io_handle_, enableFileEvents(0)); - listener_->disable(); - testing::Mock::VerifyAndClearExpectations(io_handle_); - EXPECT_CALL(*io_handle_, - enableFileEvents(Event::FileReadyType::Read | Event::FileReadyType::Write)); - listener_->enable(); - testing::Mock::VerifyAndClearExpectations(io_handle_); -} - -/** - * Mostly the same as EnableAndDisableDuringParentDrainShouldDoNothing, but in disabled state when - * drain ends. - */ -TEST_P(HotRestartedUdpListenerImplTest, EndingParentDrainedWhileDisabledShouldNotStartReading) { - setup(); - // Enabling and disabling listener should *not* trigger any - // event actions on the io_handle, because of listener being paused - // while draining. - EXPECT_CALL(*io_handle_, enableFileEvents(_)).Times(0); - listener_->enable(); - listener_->disable(); - testing::Mock::VerifyAndClearExpectations(io_handle_); - // Ending drain should not trigger any event changes because the last state - // of the listener was disabled. - std::move(parent_drained_callback_)(); - dispatcher_->run(Event::Dispatcher::RunType::Block); - testing::Mock::VerifyAndClearExpectations(io_handle_); - // Enabling after unpaused should set io_handle to reading/writing. - EXPECT_CALL(*io_handle_, - enableFileEvents(Event::FileReadyType::Read | Event::FileReadyType::Write)); - listener_->enable(); - testing::Mock::VerifyAndClearExpectations(io_handle_); -} - -TEST_P(HotRestartedUdpListenerImplTest, - ParentDrainedCallbackAfterListenerDestroyedShouldDoNothing) { - setup(); - EXPECT_CALL(*io_handle_, enableFileEvents(_)).Times(0); - listener_ = nullptr; - // Signaling end-of-drain after the listener was destroyed should do nothing. - std::move(parent_drained_callback_)(); - dispatcher_->run(Event::Dispatcher::RunType::Block); - // At this point io_handle should be an invalid reference. -} - /** * Tests UDP listener's error callback. */ diff --git a/test/common/network/udp_listener_impl_test_base.h b/test/common/network/udp_listener_impl_test_base.h index 9b7636e13ae8..112f89d68dc3 100644 --- a/test/common/network/udp_listener_impl_test_base.h +++ b/test/common/network/udp_listener_impl_test_base.h @@ -31,24 +31,13 @@ namespace Envoy { namespace Network { class UdpListenerImplTestBase : public ListenerImplTestBase { -protected: - MockIoHandle& - useHotRestartSocket(OptRef parent_drained_callback_registrar) { - auto io_handle = std::make_unique>(); - MockIoHandle& ret = *io_handle; - server_socket_ = createServerSocketFromExistingHandle(std::move(io_handle), - parent_drained_callback_registrar); - return ret; - } - - void setup() { - if (server_socket_ == nullptr) { - server_socket_ = createServerSocket(true); - } - send_to_addr_ = Address::InstanceConstSharedPtr(getServerLoopbackAddress()); +public: + UdpListenerImplTestBase() + : server_socket_(createServerSocket(true)), send_to_addr_(getServerLoopbackAddress()) { time_system_.advanceTimeWait(std::chrono::milliseconds(100)); } +protected: Address::Instance* getServerLoopbackAddress() { if (version_ == Address::IpVersion::v4) { return new Address::Ipv4Instance( @@ -71,14 +60,6 @@ class UdpListenerImplTestBase : public ListenerImplTestBase { bind); } - SocketSharedPtr createServerSocketFromExistingHandle( - IoHandlePtr&& io_handle, - OptRef parent_drained_callback_registrar) { - return std::make_shared( - std::move(io_handle), Network::Test::getCanonicalLoopbackAddress(version_), - SocketOptionFactory::buildIpFreebindOptions(), parent_drained_callback_registrar); - } - Address::InstanceConstSharedPtr getNonDefaultSourceAddress() { // Use a self address that is unlikely to be picked by source address discovery // algorithm if not specified in recvmsg/recvmmsg. Port is not taken into diff --git a/test/integration/python/hotrestart_handoff_test.py b/test/integration/python/hotrestart_handoff_test.py index dcbbb5a8ba1e..7d975bd7dda7 100644 --- a/test/integration/python/hotrestart_handoff_test.py +++ b/test/integration/python/hotrestart_handoff_test.py @@ -304,7 +304,7 @@ async def _wait_for_envoy_epoch(i: int): pass await asyncio.sleep(0.2) # Envoy instance with expected restart_epoch should have started up - assert expected_substring in response, f"expected_substring={expected_substring}, server_info={response}" + assert expected_substring in response, f"server_info={response}" class IntegrationTest(unittest.IsolatedAsyncioTestCase): diff --git a/test/mocks/network/BUILD b/test/mocks/network/BUILD index 73c9854193a3..82b5ef8c79f1 100644 --- a/test/mocks/network/BUILD +++ b/test/mocks/network/BUILD @@ -42,12 +42,6 @@ envoy_cc_mock( ], ) -envoy_cc_mock( - name = "mock_parent_drained_callback_registrar", - hdrs = ["mock_parent_drained_callback_registrar.h"], - deps = ["//envoy/network:parent_drained_callback_registrar_interface"], -) - envoy_cc_mock( name = "network_mocks", srcs = ["mocks.cc"], diff --git a/test/mocks/network/mock_parent_drained_callback_registrar.h b/test/mocks/network/mock_parent_drained_callback_registrar.h deleted file mode 100644 index ae82b52d31f6..000000000000 --- a/test/mocks/network/mock_parent_drained_callback_registrar.h +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "envoy/network/parent_drained_callback_registrar.h" - -#include "gmock/gmock.h" - -namespace Envoy { -namespace Network { - -class MockParentDrainedCallbackRegistrar : public ParentDrainedCallbackRegistrar { -public: - MOCK_METHOD(void, registerParentDrainedCallback, - (const Address::InstanceConstSharedPtr& address, - absl::AnyInvocable callback)); -}; - -} // namespace Network -} // namespace Envoy diff --git a/test/mocks/server/hot_restart.h b/test/mocks/server/hot_restart.h index 99bfa3ccbb0c..c83142692c06 100644 --- a/test/mocks/server/hot_restart.h +++ b/test/mocks/server/hot_restart.h @@ -20,8 +20,6 @@ class MockHotRestart : public HotRestart { MOCK_METHOD(void, registerUdpForwardingListener, (Network::Address::InstanceConstSharedPtr address, std::shared_ptr listener_config)); - MOCK_METHOD(OptRef, parentDrainedCallbackRegistrar, ()); - MOCK_METHOD(void, whenDrainComplete, (absl::string_view addr, absl::AnyInvocable action)); MOCK_METHOD(void, initialize, (Event::Dispatcher & dispatcher, Server::Instance& server)); MOCK_METHOD(absl::optional, sendParentAdminShutdownRequest, ()); MOCK_METHOD(void, sendParentTerminateRequest, ()); diff --git a/test/server/hot_restart_impl_test.cc b/test/server/hot_restart_impl_test.cc index 28e1427564e1..81baf88181b5 100644 --- a/test/server/hot_restart_impl_test.cc +++ b/test/server/hot_restart_impl_test.cc @@ -87,14 +87,6 @@ class HotRestartImplTest : public testing::Test { std::unique_ptr hot_restart_; }; -TEST_F(HotRestartImplTest, ParentDrainedCallbackRegistrarIsSetAndCanBeCalled) { - setup(); - OptRef registrar = - hot_restart_->parentDrainedCallbackRegistrar(); - ASSERT_TRUE(registrar.has_value()); - registrar->registerParentDrainedCallback(test_addresses_.ipv4_test_addr_, []() {}); -} - TEST_F(HotRestartImplTest, VersionString) { // Tests that the version-string will be consistent and HOT_RESTART_VERSION, // between multiple instantiations. diff --git a/test/server/hot_restarting_child_test.cc b/test/server/hot_restarting_child_test.cc index d16653d6454a..f2455034f054 100644 --- a/test/server/hot_restarting_child_test.cc +++ b/test/server/hot_restarting_child_test.cc @@ -67,11 +67,6 @@ class FakeHotRestartingParent : public HotRestartingBase { }); udp_forwarding_rpc_stream_.sendHotRestartMessage(child_address_udp_forwarding_, message); } - void expectParentTerminateMessages() { - EXPECT_CALL(os_sys_calls_, sendmsg(_, _, _)).WillOnce([](int, const msghdr* msg, int) { - return Api::SysCallSizeResult{static_cast(msg->msg_iov[0].iov_len), 0}; - }); - } Api::MockOsSysCalls& os_sys_calls_; Event::FileReadyCb udp_file_ready_callback_; sockaddr_un child_address_udp_forwarding_; @@ -105,36 +100,6 @@ class HotRestartingChildTest : public testing::Test { std::unique_ptr hot_restarting_child_; }; -TEST_F(HotRestartingChildTest, ParentDrainedCallbacksAreCalled) { - auto test_listener_addr = Network::Utility::resolveUrl("udp://127.0.0.1:1234"); - auto test_listener_addr2 = Network::Utility::resolveUrl("udp://127.0.0.1:1235"); - testing::MockFunction callback1; - testing::MockFunction callback2; - hot_restarting_child_->registerParentDrainedCallback(test_listener_addr, - callback1.AsStdFunction()); - hot_restarting_child_->registerParentDrainedCallback(test_listener_addr2, - callback2.AsStdFunction()); - EXPECT_CALL(callback1, Call()); - EXPECT_CALL(callback2, Call()); - fake_parent_->expectParentTerminateMessages(); - hot_restarting_child_->sendParentTerminateRequest(); -} - -TEST_F(HotRestartingChildTest, ParentDrainedCallbacksAreCalledImmediatelyWhenAlreadyDrained) { - auto test_listener_addr = Network::Utility::resolveUrl("udp://127.0.0.1:1234"); - auto test_listener_addr2 = Network::Utility::resolveUrl("udp://127.0.0.1:1235"); - testing::MockFunction callback1; - testing::MockFunction callback2; - fake_parent_->expectParentTerminateMessages(); - hot_restarting_child_->sendParentTerminateRequest(); - EXPECT_CALL(callback1, Call()); - EXPECT_CALL(callback2, Call()); - hot_restarting_child_->registerParentDrainedCallback(test_listener_addr, - callback1.AsStdFunction()); - hot_restarting_child_->registerParentDrainedCallback(test_listener_addr2, - callback2.AsStdFunction()); -} - TEST_F(HotRestartingChildTest, LogsErrorOnReplyMessageInUdpStream) { envoy::HotRestartMessage msg; msg.mutable_reply(); From 5364f4d7f4d853a8309c20384dd745f215e2f265 Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 Mar 2024 16:05:46 +0000 Subject: [PATCH 132/151] ci: Add scheduled garbage collection (and fix retest) (#32639) Signed-off-by: Ryan Northey --- .github/workflows/_cache.yml | 10 +++--- .github/workflows/_finish.yml | 8 ++--- .github/workflows/_load.yml | 10 +++--- .github/workflows/_load_env.yml | 8 ++--- .github/workflows/_request.yml | 10 +++--- .github/workflows/_run.yml | 16 ++++----- .github/workflows/_stage_publish.yml | 8 ++--- .github/workflows/_stage_verify.yml | 8 ++--- .github/workflows/_start.yml | 10 +++--- .github/workflows/codeql-daily.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/command.yml | 6 ++-- .github/workflows/envoy-dependency.yml | 18 +++++----- .github/workflows/envoy-release.yml | 22 ++++++------ .github/workflows/envoy-sync.yml | 4 +-- .github/workflows/garbage.yml | 40 ++++++++++++++++++++++ .github/workflows/mobile-android_build.yml | 12 +++---- .github/workflows/mobile-ios_build.yml | 4 +-- 18 files changed, 119 insertions(+), 79 deletions(-) create mode 100644 .github/workflows/garbage.yml diff --git a/.github/workflows/_cache.yml b/.github/workflows/_cache.yml index 199480da01f2..04d03a408030 100644 --- a/.github/workflows/_cache.yml +++ b/.github/workflows/_cache.yml @@ -29,7 +29,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 +# - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.2.26 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -39,20 +39,20 @@ jobs: docker: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.2.26 id: docker name: Prime Docker cache (${{ inputs.image-tag }}) with: image-tag: ${{ inputs.image-tag }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 id: data name: Cache data with: @@ -60,7 +60,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }} - - uses: envoyproxy/toolshed/gh-actions/json/table@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/json/table@actions-v0.2.26 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index c09f05bc130c..9989c8466a26 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/gh-actions/github/checks@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.26 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index c0eefc1f0363..0d25a5371674 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -91,7 +91,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/gh-actions/github/env/load@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/env/load@actions-v0.2.26 name: Load env id: data with: @@ -102,13 +102,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/gh-actions/github/checks@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.26 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -116,7 +116,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 name: Print request summary with: input: | @@ -136,7 +136,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index 97733df48af1..9cb5529de060 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -63,18 +63,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/gh-actions/envoy/ci/env@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/envoy/ci/env@actions-v0.2.26 id: env with: branch-name: ${{ inputs.branch-name }} @@ -86,7 +86,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/gh-actions/github/env/summary@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/env/summary@actions-v0.2.26 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index cd21dccc20ee..a5197bd04aab 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -40,14 +40,14 @@ jobs: env: ${{ steps.data.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 id: checkout name: Checkout Envoy repository with: @@ -60,7 +60,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/gh-actions/envoy/ci/request@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/envoy/ci/request@actions-v0.2.26 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -71,7 +71,7 @@ jobs: vars: ${{ toJSON(vars) }} - name: Request summary id: summary - uses: envoyproxy/toolshed/gh-actions/github/env/summary@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/env/summary@actions-v0.2.26 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -87,7 +87,7 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - name: Environment data - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 id: data with: input: | diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 6977b51c76d9..7ed521465894 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -94,7 +94,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/gh-actions/envoy/run/summary@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/run/summary@actions-v0.2.26 with: context: %{{ inputs.context }} steps-pre: @@ -156,7 +156,7 @@ jobs: name: ${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 id: started name: Create timestamp with: @@ -164,7 +164,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 name: Context id: context with: @@ -185,11 +185,11 @@ jobs: | . * {$config, $check} - if: ${{ inputs.cache-build-image }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.2.26 with: image_tag: ${{ inputs.cache-build-image }} - - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -200,7 +200,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 id: checkout name: Checkout Envoy repository with: @@ -217,7 +217,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -225,7 +225,7 @@ jobs: config: ${{ inputs.checkout-extra }} ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - - uses: envoyproxy/toolshed/gh-actions/github/run@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.26 name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: args: ${{ inputs.args != '--' && inputs.args || inputs.target }} diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index c8925a75c5a4..f24d3b565ac5 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -63,7 +63,7 @@ jobs: export ENVOY_PUBLISH_DRY_RUN=${{ (fromJSON(inputs.request).request.version.dev || ! inputs.trusted) && 1 || '' }} steps-pre: | - id: url - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 with: options: -Rr input: >- @@ -80,7 +80,7 @@ jobs: end | . as $bucket | "https://storage.googleapis.com/\($bucket)/\($sha)/\($path)" - - uses: envoyproxy/toolshed/gh-actions/fetch@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.2.26 with: url: %{{ steps.url.outputs.value }} path: %{{ runner.temp }}/release.signed @@ -98,12 +98,12 @@ jobs: needs: - publish steps: - - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/gh-actions/dispatch@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.2.26 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index d9bd340881cc..3b4b714c0d2c 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -50,7 +50,7 @@ jobs: rbe: false steps-pre: | - id: url - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 with: options: -Rr input: >- @@ -66,15 +66,15 @@ jobs: end | . as $bucket | "https://storage.googleapis.com/\($bucket)/\($sha)" - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.26 with: url: %{{ steps.url.outputs.value }}/docker/envoy.tar variant: dev - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.26 with: url: %{{ steps.url.outputs.value }}/docker/envoy-contrib.tar variant: contrib-dev - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.26 with: url: %{{ steps.url.outputs.value }}/docker/envoy-google-vrp.tar variant: google-vrp-dev diff --git a/.github/workflows/_start.yml b/.github/workflows/_start.yml index 458dac614033..cd431e830832 100644 --- a/.github/workflows/_start.yml +++ b/.github/workflows/_start.yml @@ -54,7 +54,7 @@ jobs: start: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/jq@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 id: check-config name: Prepare check data with: @@ -77,13 +77,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/gh-actions/github/checks@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.26 name: Start checks id: checks with: @@ -94,7 +94,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/gh-actions/json/table@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/json/table@actions-v0.2.26 name: Summary with: collapse-open: true @@ -118,7 +118,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/gh-actions/github/env/save@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/env/save@actions-v0.2.26 name: Save env id: data with: diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 2f8fe09ad17c..603f1ffa6167 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Free disk space - uses: envoyproxy/toolshed/gh-actions/diskspace@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.2.26 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 856f6aafdf48..81fb5c33418b 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -61,7 +61,7 @@ jobs: - name: Free disk space if: ${{ env.BUILD_TARGETS != '' }} - uses: envoyproxy/toolshed/gh-actions/diskspace@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.2.26 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index 3c25b7c7bb6d..d8bda837b82e 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/gh-actions/github/command@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/command@actions-v0.2.26 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/gh-actions/retest@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.2.26 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index e78ace0acd15..d620f6acabbc 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -50,16 +50,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/gh-actions/bson@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/bson@actions-v0.2.26 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -94,13 +94,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/gh-actions/upload/diff@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.2.26 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/gh-actions/github/pr@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.26 with: base: main body: | @@ -131,11 +131,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 id: checkout name: Checkout Envoy repository with: @@ -177,7 +177,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/gh-actions/docker/shas@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.2.26 with: images: | sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} @@ -206,7 +206,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.26 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 1466c5d57d31..78ffc5ec6a40 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -55,14 +55,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -83,10 +83,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/gh-actions/email/validate@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.2.26 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/gh-actions/github/run@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.26 name: Create release with: source: | @@ -111,7 +111,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.26 with: base: ${{ github.ref_name }} commit: false @@ -136,18 +136,18 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} - - uses: envoyproxy/toolshed/gh-actions/github/run@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.26 name: Sync version histories with: command: >- @@ -157,7 +157,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.26 with: append-commit-message: true base: ${{ github.ref_name }} @@ -187,13 +187,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index f9d2dc8cdce0..8f552a6c9fa1 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -31,12 +31,12 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/gh-actions/appauth@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/gh-actions/dispatch@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.2.26 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/garbage.yml b/.github/workflows/garbage.yml new file mode 100644 index 000000000000..e74393a67fce --- /dev/null +++ b/.github/workflows/garbage.yml @@ -0,0 +1,40 @@ +name: Garbage collection + +permissions: + contents: read + +on: + schedule: + - cron: '0 0 * * *' + +concurrency: + group: ${{ github.head_ref || github.run_id }}-${{ github.workflow }} + cancel-in-progress: true + + +jobs: + azp-agents: + runs-on: ubuntu-22.04 + if: github.repository == 'envoyproxy/envoy' + strategy: + matrix: + include: + - target: envoy-arm-large + pool-id: 21 + - target: envoy-arm-small + pool-id: 24 + - target: envoy-x64-large + pool-id: 20 + - target: envoy-x64-small + pool-id: 23 + - target: salvo-control + pool-id: 22 + - target: x64-nano + pool-id: 17 + steps: + - name: Remove dead AZP agents (${{ matrix.target }}) + uses: envoyproxy/toolshed/gh-actions/azp/agent-cleanup@actions-v0.2.26 + with: + azp-org: cncf + azp-token: ${{ secrets.AZP_TOKEN }} + pool-id: ${{ matrix.pool-id }} diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index 70f271a11962..7fdbbed182d6 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -75,9 +75,9 @@ jobs: target: kotlin-hello-world runs-on: envoy-x64-small steps-pre: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/pre@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/pre@actions-v0.2.26 steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.26 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -104,7 +104,7 @@ jobs: target: ${{ matrix.target }} runs-on: envoy-x64-small steps-pre: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/pre@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/pre@actions-v0.2.26 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ fromJSON(needs.load.outputs.trusted) }} @@ -115,7 +115,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.26 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -134,7 +134,7 @@ jobs: --config=mobile-remote-release-clang-android //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.26 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -149,7 +149,7 @@ jobs: --config=mobile-remote-release-clang-android //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.26 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 5131d4581729..6b03f451f719 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -86,7 +86,7 @@ jobs: source ./ci/mac_ci_setup.sh ./bazelw shutdown steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@actions-v0.2.26 with: app: ${{ matrix.app }} args: ${{ matrix.args || '--config=mobile-remote-ci-macos-ios' }} @@ -130,7 +130,7 @@ jobs: source: | source ./ci/mac_ci_setup.sh steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@680d414be3f56cbb161dfdebebece85d81c3f686 # actions-v0.2.24 + - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@actions-v0.2.26 with: app: ${{ matrix.app }} args: ${{ matrix.args || '--config=mobile-remote-ci-macos-ios' }} From e27a8e57768c07dc2cfd0739bd3b299938fb196d Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 1 Mar 2024 11:46:57 -0500 Subject: [PATCH 133/151] owners: adding Fredy as an Enovy Mobile maintainer (#32638) Signed-off-by: Alyssa Wilk --- OWNERS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OWNERS.md b/OWNERS.md index 772b07e8bbfd..12711f3880c0 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -59,6 +59,8 @@ The following Envoy maintainers have final say over any changes only affecting / * Ali Beyad ([abeyad](https://github.com/abeyad)) (abeyad@google.com) * xDS, C++ integration tests. +* Fredy Wijaya ([fredyw](https://github.com/fredyw)) (fredyw@google.com) + * Android, Java, Kotlin, JNI. # Senior extension maintainers From 4c21513c7b8f3bae7e08f08d169b9be3910ee5ab Mon Sep 17 00:00:00 2001 From: ohadvano <49730675+ohadvano@users.noreply.github.com> Date: Fri, 1 Mar 2024 18:49:28 +0200 Subject: [PATCH 134/151] TcpAsyncClient: enhance reconnect robustness (#32578) Signed-off-by: ohadvano --- envoy/tcp/async_tcp_client.h | 7 +++- source/common/tcp/async_tcp_client_impl.cc | 28 ++++++------- source/common/tcp/async_tcp_client_impl.h | 6 +-- test/common/tcp/async_tcp_client_impl_test.cc | 39 ++++++++++++++++++- .../filters/test_network_async_tcp_filter.cc | 13 +++++++ .../tcp_async_client_integration_test.cc | 32 +++++++++++++++ 6 files changed, 105 insertions(+), 20 deletions(-) diff --git a/envoy/tcp/async_tcp_client.h b/envoy/tcp/async_tcp_client.h index df80fd0c3866..bd367a55db1b 100644 --- a/envoy/tcp/async_tcp_client.h +++ b/envoy/tcp/async_tcp_client.h @@ -40,8 +40,11 @@ class AsyncTcpClient { /** * Connect to a remote host. Errors or connection events are reported via the - * event callback registered via setAsyncTcpClientCallbacks(). We need to set the - * callbacks again to call connect() after the connection is disconnected. + * event callback registered via setAsyncTcpClientCallbacks(). If the callbacks + * needs to be changed before reconnecting, it is required to set the callbacks + * again, before calling to connect() to attempting to reconnect. + * @returns true if a new client has created and the connection is in progress. + * @returns false if an underlying client exists and is connected or connecting. */ virtual bool connect() PURE; diff --git a/source/common/tcp/async_tcp_client_impl.cc b/source/common/tcp/async_tcp_client_impl.cc index 6bcfa9a21500..4d6482cb5dc6 100644 --- a/source/common/tcp/async_tcp_client_impl.cc +++ b/source/common/tcp/async_tcp_client_impl.cc @@ -22,20 +22,20 @@ AsyncTcpClientImpl::AsyncTcpClientImpl(Event::Dispatcher& dispatcher, : dispatcher_(dispatcher), thread_local_cluster_(thread_local_cluster), cluster_info_(thread_local_cluster_.info()), context_(context), connect_timer_(dispatcher.createTimer([this]() { onConnectTimeout(); })), - enable_half_close_(enable_half_close) { - cluster_info_->trafficStats()->upstream_cx_active_.inc(); - cluster_info_->trafficStats()->upstream_cx_total_.inc(); -} - -AsyncTcpClientImpl::~AsyncTcpClientImpl() { - cluster_info_->trafficStats()->upstream_cx_active_.dec(); -} + enable_half_close_(enable_half_close) {} bool AsyncTcpClientImpl::connect() { + if (connection_) { + return false; + } + connection_ = std::move(thread_local_cluster_.tcpConn(context_).connection_); if (!connection_) { return false; } + + cluster_info_->trafficStats()->upstream_cx_total_.inc(); + cluster_info_->trafficStats()->upstream_cx_active_.inc(); connection_->enableHalfClose(enable_half_close_); connection_->addConnectionCallbacks(*this); connection_->addReadFilter(std::make_shared(*this)); @@ -109,17 +109,20 @@ void AsyncTcpClientImpl::reportConnectionDestroy(Network::ConnectionEvent event) void AsyncTcpClientImpl::onEvent(Network::ConnectionEvent event) { if (event == Network::ConnectionEvent::RemoteClose || event == Network::ConnectionEvent::LocalClose) { - if (disconnected_) { + cluster_info_->trafficStats()->upstream_cx_active_.dec(); + if (!connected_) { cluster_info_->trafficStats()->upstream_cx_connect_fail_.inc(); } - if (!disconnected_ && conn_length_ms_ != nullptr) { + if (connected_ && conn_length_ms_ != nullptr) { conn_length_ms_->complete(); conn_length_ms_.reset(); } + disableConnectTimeout(); reportConnectionDestroy(event); - disconnected_ = true; + + connected_ = false; if (connection_) { detected_close_ = connection_->detectedCloseType(); } @@ -127,10 +130,9 @@ void AsyncTcpClientImpl::onEvent(Network::ConnectionEvent event) { dispatcher_.deferredDelete(std::move(connection_)); if (callbacks_) { callbacks_->onEvent(event); - callbacks_ = nullptr; } } else { - disconnected_ = false; + connected_ = true; conn_connect_ms_->complete(); conn_connect_ms_.reset(); disableConnectTimeout(); diff --git a/source/common/tcp/async_tcp_client_impl.h b/source/common/tcp/async_tcp_client_impl.h index 6250fb574d6d..ef965ca68cc5 100644 --- a/source/common/tcp/async_tcp_client_impl.h +++ b/source/common/tcp/async_tcp_client_impl.h @@ -29,8 +29,6 @@ class AsyncTcpClientImpl : public AsyncTcpClient, Upstream::ThreadLocalCluster& thread_local_cluster, Upstream::LoadBalancerContext* context, bool enable_half_close); - ~AsyncTcpClientImpl() override; - void close(Network::ConnectionCloseType type) override; Network::DetectedCloseType detectedCloseType() const override { return detected_close_; } @@ -56,7 +54,7 @@ class AsyncTcpClientImpl : public AsyncTcpClient, /** * @return if the client connects to a peer host. */ - bool connected() override { return !disconnected_; } + bool connected() override { return connected_; } Event::Dispatcher& dispatcher() override { return dispatcher_; } @@ -108,7 +106,7 @@ class AsyncTcpClientImpl : public AsyncTcpClient, Event::TimerPtr connect_timer_; AsyncTcpClientCallbacks* callbacks_{}; Network::DetectedCloseType detected_close_{Network::DetectedCloseType::Normal}; - bool disconnected_{true}; + bool connected_{false}; bool enable_half_close_{false}; }; diff --git a/test/common/tcp/async_tcp_client_impl_test.cc b/test/common/tcp/async_tcp_client_impl_test.cc index a08a1e7576df..97e5d64c5129 100644 --- a/test/common/tcp/async_tcp_client_impl_test.cc +++ b/test/common/tcp/async_tcp_client_impl_test.cc @@ -245,10 +245,47 @@ TEST_F(AsyncTcpClientImplTest, TestActiveCx) { expectCreateConnection(); EXPECT_EQ(1UL, cluster_manager_.thread_local_cluster_.cluster_.info_->traffic_stats_ ->upstream_cx_active_.value()); - client_.reset(); + EXPECT_CALL(callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + connection_->raiseEvent(Network::ConnectionEvent::LocalClose); + EXPECT_EQ(0UL, cluster_manager_.thread_local_cluster_.cluster_.info_->traffic_stats_ + ->upstream_cx_active_.value()); +} + +TEST_F(AsyncTcpClientImplTest, TestActiveCxWhileNotConnected) { + setUpClient(); + expectCreateConnection(false); + EXPECT_EQ(1UL, cluster_manager_.thread_local_cluster_.cluster_.info_->traffic_stats_ + ->upstream_cx_active_.value()); + EXPECT_CALL(callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + connection_->raiseEvent(Network::ConnectionEvent::LocalClose); EXPECT_EQ(0UL, cluster_manager_.thread_local_cluster_.cluster_.info_->traffic_stats_ ->upstream_cx_active_.value()); } +TEST_F(AsyncTcpClientImplTest, ReconnectWhileClientConnected) { + setUpClient(); + expectCreateConnection(); + EXPECT_FALSE(client_->connect()); +} + +TEST_F(AsyncTcpClientImplTest, ReconnectWhileClientConnecting) { + setUpClient(); + expectCreateConnection(false); + EXPECT_FALSE(client_->connect()); +} + +TEST_F(AsyncTcpClientImplTest, ReconnectAfterClientDisconnected) { + setUpClient(); + expectCreateConnection(); + + EXPECT_CALL(callbacks_, onEvent(Network::ConnectionEvent::LocalClose)); + connection_->raiseEvent(Network::ConnectionEvent::LocalClose); + connect_timer_ = new NiceMock(&dispatcher_); + expectCreateConnection(); + + EXPECT_EQ(2UL, cluster_manager_.thread_local_cluster_.cluster_.info_->traffic_stats_ + ->upstream_cx_total_.value()); +} + } // namespace Tcp } // namespace Envoy diff --git a/test/integration/filters/test_network_async_tcp_filter.cc b/test/integration/filters/test_network_async_tcp_filter.cc index 17f7d6fe4e75..6116b2d01dc1 100644 --- a/test/integration/filters/test_network_async_tcp_filter.cc +++ b/test/integration/filters/test_network_async_tcp_filter.cc @@ -50,6 +50,11 @@ class TestNetworkAsyncTcpFilter : public Network::ReadFilter { } Network::FilterStatus onData(Buffer::Instance& data, bool end_stream) override { + if (require_reconnect_ && !client_->connect()) { + ENVOY_LOG_MISC(debug, "Unable to reconnect to cluster"); + return Network::FilterStatus::StopIteration; + } + stats_.on_data_.inc(); ENVOY_LOG_MISC(debug, "Downstream onData: {}, length: {} sending to upstream", data.toString(), data.length()); @@ -76,6 +81,8 @@ class TestNetworkAsyncTcpFilter : public Network::ReadFilter { read_callbacks_->connection().addConnectionCallbacks(*downstream_callbacks_); } + bool require_reconnect_{false}; + private: struct DownstreamCallbacks : public Envoy::Network::ConnectionCallbacks { explicit DownstreamCallbacks(TestNetworkAsyncTcpFilter& parent) : parent_(parent) {} @@ -121,6 +128,12 @@ class TestNetworkAsyncTcpFilter : public Network::ReadFilter { void onEvent(Network::ConnectionEvent event) override { ENVOY_LOG_MISC(debug, "tcp client test filter upstream callback onEvent: {}", static_cast(event)); + + if (event == Network::ConnectionEvent::RemoteClose || + event == Network::ConnectionEvent::LocalClose) { + parent_.require_reconnect_ = true; + } + if (event != Network::ConnectionEvent::RemoteClose) { return; } diff --git a/test/integration/tcp_async_client_integration_test.cc b/test/integration/tcp_async_client_integration_test.cc index cd31af091dd9..89c4e29c1771 100644 --- a/test/integration/tcp_async_client_integration_test.cc +++ b/test/integration/tcp_async_client_integration_test.cc @@ -111,6 +111,38 @@ TEST_P(TcpAsyncClientIntegrationTest, MultipleResponseFrames) { tcp_client->close(); } +TEST_P(TcpAsyncClientIntegrationTest, Reconnect) { + if (GetParam() == Network::Address::IpVersion::v6) { + return; + } + + enableHalfClose(true); + initialize(); + + IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("listener_0")); + ASSERT_TRUE(tcp_client->write("hello1", false)); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 1); + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 1); + FakeRawConnectionPtr fake_upstream_connection; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection)); + ASSERT_TRUE(fake_upstream_connection->waitForData( + [&](const std::string& data) -> bool { return data == "hello1"; })); + ASSERT_TRUE(fake_upstream_connection->close()); + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); + + // We use the same tcp_client to ensure that a new upstream connection is created. + ASSERT_TRUE(tcp_client->write("hello2", false)); + test_server_->waitForCounterEq("cluster.cluster_0.upstream_cx_total", 2); + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 1); + FakeRawConnectionPtr fake_upstream_connection2; + ASSERT_TRUE(fake_upstreams_[0]->waitForRawConnection(fake_upstream_connection2)); + ASSERT_TRUE(fake_upstream_connection2->waitForData( + [&](const std::string& data) -> bool { return data == "hello2"; })); + + tcp_client->close(); + test_server_->waitForGaugeEq("cluster.cluster_0.upstream_cx_active", 0); +} + #if ENVOY_PLATFORM_ENABLE_SEND_RST // Test if RST close can be detected from downstream and upstream is closed by RST. TEST_P(TcpAsyncClientIntegrationTest, TestClientCloseRST) { From 44d580cac5084f48cae8de0d2798593fe088fbcc Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Fri, 1 Mar 2024 11:53:43 -0500 Subject: [PATCH 135/151] sockets: flipping graceful client socket creation failure (#32602) Allowing graceful client socket creation failure by default in Envoy Risk Level: medium Testing: yes Docs Changes: n/a Release Notes: inline [Optional Runtime guard:] yes. Signed-off-by: Alyssa Wilk --- changelogs/current.yaml | 4 ++++ source/common/network/listen_socket_impl.cc | 2 +- source/common/network/listen_socket_impl.h | 2 +- source/common/runtime/runtime_features.cc | 3 +-- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index ffaec77acce2..895c82e23d50 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -23,6 +23,10 @@ behavior_changes: minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* +- area: sockets + change: | + Failure to create an upstream socket should now result in clean connection failure rather than failing a release assert. This behavior + can be temporarily reverted by setting runtime feature ``envoy.restart_features_.allow_client_socket_creation_failure`` to false. - area: adaptive concurrency filter stats change: | Multiply the gradient value stat by 1000 to make it more granular (values will range between 500 and 2000). diff --git a/source/common/network/listen_socket_impl.cc b/source/common/network/listen_socket_impl.cc index 596d2c0846f6..121473fdd492 100644 --- a/source/common/network/listen_socket_impl.cc +++ b/source/common/network/listen_socket_impl.cc @@ -46,7 +46,7 @@ void ListenSocketImpl::setupSocket(const Network::Socket::OptionsSharedPtr& opti UdsListenSocket::UdsListenSocket(const Address::InstanceConstSharedPtr& address) : ListenSocketImpl(ioHandleForAddr(Socket::Type::Stream, address, {}), address) { - RELEASE_ASSERT(io_handle_->isOpen(), ""); + RELEASE_ASSERT(io_handle_ && io_handle_->isOpen(), ""); bind(connection_info_provider_->localAddress()); } diff --git a/source/common/network/listen_socket_impl.h b/source/common/network/listen_socket_impl.h index 1dd9a6f668d7..1a365c65d1a3 100644 --- a/source/common/network/listen_socket_impl.h +++ b/source/common/network/listen_socket_impl.h @@ -53,7 +53,7 @@ template class NetworkListenSocket : public ListenSocketImpl { address) { // Prebind is applied if the socket is bind to port. if (bind_to_port) { - RELEASE_ASSERT(io_handle_->isOpen(), ""); + RELEASE_ASSERT(io_handle_ && io_handle_->isOpen(), ""); setPrebindSocketOptions(); setupSocket(options); } else { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 68c68f32e75b..23c9979c91d9 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -100,14 +100,13 @@ RUNTIME_GUARD(envoy_reloadable_features_use_http3_header_normalisation); RUNTIME_GUARD(envoy_reloadable_features_validate_connect); RUNTIME_GUARD(envoy_reloadable_features_validate_grpc_header_before_log_grpc_status); RUNTIME_GUARD(envoy_reloadable_features_validate_upstream_headers); +RUNTIME_GUARD(envoy_restart_features_allow_client_socket_creation_failure); RUNTIME_GUARD(envoy_restart_features_send_goaway_for_premature_rst_streams); RUNTIME_GUARD(envoy_restart_features_udp_read_normalize_addresses); RUNTIME_GUARD(envoy_restart_features_use_eds_cache_for_ads); // Begin false flags. Most of them should come with a TODO to flip true. -// TODO(alyssawilk) flip true after server side is handled. -FALSE_RUNTIME_GUARD(envoy_restart_features_allow_client_socket_creation_failure); // Execution context is optional and must be enabled explicitly. // See https://github.com/envoyproxy/envoy/issues/32012. FALSE_RUNTIME_GUARD(envoy_restart_features_enable_execution_context); From 0d3bdfe471fa78d9b16bae67550e1424f596613e Mon Sep 17 00:00:00 2001 From: Raven Black Date: Fri, 1 Mar 2024 14:11:19 -0500 Subject: [PATCH 136/151] Change udpa renaming workaround to not compile the same archive twice (#32647) * Change udpa renaming workaround to not compile the same archive twice --------- Signed-off-by: Raven Black --- api/bazel/repositories.bzl | 7 ------- bazel/repositories.bzl | 2 ++ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/api/bazel/repositories.bzl b/api/bazel/repositories.bzl index cd5936cdffd1..acd03fe0adf6 100644 --- a/api/bazel/repositories.bzl +++ b/api/bazel/repositories.bzl @@ -29,13 +29,6 @@ def api_dependencies(): name = "com_github_cncf_xds", ) - # Needed until @com_github_grpc_grpc renames @com_github_cncf_udpa - # to @com_github_cncf_xds as well. - external_http_archive( - name = "com_github_cncf_udpa", - location_name = "com_github_cncf_xds", - ) - external_http_archive( name = "prometheus_metrics_model", build_file_content = PROMETHEUSMETRICS_BUILD_CONTENT, diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 025bf5efdb87..8a03e4599054 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -1173,6 +1173,8 @@ def _com_github_grpc_grpc(): name = "com_github_grpc_grpc", patch_args = ["-p1"], patches = ["@envoy//bazel:grpc.patch"], + # Needed until grpc updates its naming (v1.62.0) + repo_mapping = {"@com_github_cncf_udpa": "@com_github_cncf_xds"}, ) external_http_archive("build_bazel_rules_apple") From 6231a648424d6ae5209d5346960e825a216f95fa Mon Sep 17 00:00:00 2001 From: phlax Date: Fri, 1 Mar 2024 19:17:04 +0000 Subject: [PATCH 137/151] ci/logging: Add failure detection (#32662) Signed-off-by: Ryan Northey --- .github/workflows/_cache.yml | 10 +++++----- .github/workflows/_finish.yml | 8 ++++---- .github/workflows/_load.yml | 10 +++++----- .github/workflows/_load_env.yml | 8 ++++---- .github/workflows/_request.yml | 10 +++++----- .github/workflows/_run.yml | 19 +++++++++++-------- .github/workflows/_stage_publish.yml | 8 ++++---- .github/workflows/_stage_verify.yml | 8 ++++---- .github/workflows/_start.yml | 10 +++++----- .github/workflows/codeql-daily.yml | 2 +- .github/workflows/codeql-push.yml | 2 +- .github/workflows/command.yml | 6 +++--- .github/workflows/envoy-dependency.yml | 18 +++++++++--------- .github/workflows/envoy-release.yml | 22 +++++++++++----------- .github/workflows/envoy-sync.yml | 4 ++-- .github/workflows/garbage.yml | 2 +- .github/workflows/mobile-android_build.yml | 12 ++++++------ .github/workflows/mobile-ios_build.yml | 4 ++-- 18 files changed, 83 insertions(+), 80 deletions(-) diff --git a/.github/workflows/_cache.yml b/.github/workflows/_cache.yml index 04d03a408030..a7283626bf3e 100644 --- a/.github/workflows/_cache.yml +++ b/.github/workflows/_cache.yml @@ -29,7 +29,7 @@ on: # For a job that does, you can restore with something like: # # steps: -# - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.2.26 +# - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.2.27 # with: # key: "${{ needs.env.outputs.build-image }}" # @@ -39,20 +39,20 @@ jobs: docker: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 id: appauth name: Appauth (mutex lock) with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/docker/cache/prime@actions-v0.2.27 id: docker name: Prime Docker cache (${{ inputs.image-tag }}) with: image-tag: ${{ inputs.image-tag }} lock-token: ${{ steps.appauth.outputs.token }} lock-repository: ${{ inputs.lock-repository }} - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 id: data name: Cache data with: @@ -60,7 +60,7 @@ jobs: input: | cached: ${{ steps.docker.outputs.cached }} key: ${{ inputs.image-tag }} - - uses: envoyproxy/toolshed/gh-actions/json/table@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/json/table@actions-v0.2.27 name: Summary with: json: ${{ steps.data.outputs.value }} diff --git a/.github/workflows/_finish.yml b/.github/workflows/_finish.yml index 9989c8466a26..bdf6441467bb 100644 --- a/.github/workflows/_finish.yml +++ b/.github/workflows/_finish.yml @@ -36,7 +36,7 @@ jobs: actions: read contents: read steps: - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 name: Incoming data id: needs with: @@ -87,7 +87,7 @@ jobs: summary: "Check has finished", text: $text}}}} - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 name: Print summary with: input: ${{ toJSON(steps.needs.outputs.value).summary-title }} @@ -95,13 +95,13 @@ jobs: "## \(.)" options: -Rr output-path: GITHUB_STEP_SUMMARY - - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.27 name: Update check with: action: update diff --git a/.github/workflows/_load.yml b/.github/workflows/_load.yml index 0d25a5371674..26912abbdfc6 100644 --- a/.github/workflows/_load.yml +++ b/.github/workflows/_load.yml @@ -91,7 +91,7 @@ jobs: # Handle any failure in triggering job # Remove any `checks` we dont care about # Prepare a check request - - uses: envoyproxy/toolshed/gh-actions/github/env/load@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/env/load@actions-v0.2.27 name: Load env id: data with: @@ -102,13 +102,13 @@ jobs: GH_TOKEN: ${{ github.token }} # Update the check - - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.27 name: Update check if: ${{ fromJSON(steps.data.outputs.data).data.check.action == 'RUN' }} with: @@ -116,7 +116,7 @@ jobs: checks: ${{ toJSON(fromJSON(steps.data.outputs.data).checks) }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 name: Print request summary with: input: | @@ -136,7 +136,7 @@ jobs: | $summary.summary as $summary | "${{ inputs.template-request-summary }}" - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 id: request-output name: Load request with: diff --git a/.github/workflows/_load_env.yml b/.github/workflows/_load_env.yml index 9cb5529de060..a4fbad786893 100644 --- a/.github/workflows/_load_env.yml +++ b/.github/workflows/_load_env.yml @@ -63,18 +63,18 @@ jobs: request: ${{ steps.env.outputs.data }} trusted: true steps: - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 id: checkout name: Checkout Envoy repository - name: Generate environment variables - uses: envoyproxy/toolshed/gh-actions/envoy/ci/env@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/envoy/ci/env@actions-v0.2.27 id: env with: branch-name: ${{ inputs.branch-name }} @@ -86,7 +86,7 @@ jobs: - name: Request summary id: summary - uses: envoyproxy/toolshed/gh-actions/github/env/summary@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/env/summary@actions-v0.2.27 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} diff --git a/.github/workflows/_request.yml b/.github/workflows/_request.yml index a5197bd04aab..4ae97b2865eb 100644 --- a/.github/workflows/_request.yml +++ b/.github/workflows/_request.yml @@ -40,14 +40,14 @@ jobs: env: ${{ steps.data.outputs.value }} config: ${{ steps.config.outputs.config }} steps: - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 id: started name: Create timestamp with: options: -r filter: | now - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 id: checkout name: Checkout Envoy repository with: @@ -60,7 +60,7 @@ jobs: # *ALL* variables collected should be treated as untrusted and should be sanitized before # use - name: Generate environment variables from commit - uses: envoyproxy/toolshed/gh-actions/envoy/ci/request@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/envoy/ci/request@actions-v0.2.27 id: env with: branch-name: ${{ steps.checkout.outputs.branch-name }} @@ -71,7 +71,7 @@ jobs: vars: ${{ toJSON(vars) }} - name: Request summary id: summary - uses: envoyproxy/toolshed/gh-actions/github/env/summary@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/env/summary@actions-v0.2.27 with: actor: ${{ toJSON(fromJSON(steps.env.outputs.data).request.actor) }} base-sha: ${{ fromJSON(steps.env.outputs.data).request.base-sha }} @@ -87,7 +87,7 @@ jobs: target-branch: ${{ fromJSON(steps.env.outputs.data).request.target-branch }} - name: Environment data - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 id: data with: input: | diff --git a/.github/workflows/_run.yml b/.github/workflows/_run.yml index 7ed521465894..5b1ad16e9f18 100644 --- a/.github/workflows/_run.yml +++ b/.github/workflows/_run.yml @@ -52,6 +52,8 @@ on: ERROR error: Error: + fail-match: + type: string notice-match: type: string default: | @@ -94,7 +96,7 @@ on: summary-post: type: string default: | - - uses: envoyproxy/toolshed/gh-actions/envoy/run/summary@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/run/summary@actions-v0.2.27 with: context: %{{ inputs.context }} steps-pre: @@ -156,7 +158,7 @@ jobs: name: ${{ inputs.command }} ${{ inputs.target }} timeout-minutes: ${{ inputs.timeout-minutes }} steps: - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 id: started name: Create timestamp with: @@ -164,7 +166,7 @@ jobs: filter: | now # This controls which input vars are exposed to the run action (and related steps) - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 name: Context id: context with: @@ -185,11 +187,11 @@ jobs: | . * {$config, $check} - if: ${{ inputs.cache-build-image }} name: Restore Docker cache ${{ inputs.cache-build-image && format('({0})', inputs.cache-build-image) || '' }} - uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/docker/cache/restore@actions-v0.2.27 with: image_tag: ${{ inputs.cache-build-image }} - - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 id: appauth name: Appauth if: ${{ inputs.trusted }} @@ -200,7 +202,7 @@ jobs: # - the workaround is to allow the token to be passed through. token: ${{ github.token }} token-ok: true - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 id: checkout name: Checkout Envoy repository with: @@ -217,7 +219,7 @@ jobs: token: ${{ inputs.trusted && steps.appauth.outputs.token || github.token }} # This is currently only use by mobile-docs and can be removed once they are updated to the newer website - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 id: checkout-extra name: Checkout extra repository (for publishing) if: ${{ inputs.checkout-extra }} @@ -225,7 +227,7 @@ jobs: config: ${{ inputs.checkout-extra }} ssh-key: ${{ inputs.trusted && inputs.ssh-key-extra || '' }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.27 name: Run CI ${{ inputs.command }} ${{ inputs.target }} with: args: ${{ inputs.args != '--' && inputs.args || inputs.target }} @@ -239,6 +241,7 @@ jobs: downloads: ${{ inputs.downloads }} entrypoint: ${{ inputs.entrypoint }} error-match: ${{ inputs.error-match }} + fail-match: ${{ inputs.fail-match }} notice-match: ${{ inputs.notice-match }} output-path: ${{ inputs.output-path }} report-pre: ${{ inputs.report-pre }} diff --git a/.github/workflows/_stage_publish.yml b/.github/workflows/_stage_publish.yml index f24d3b565ac5..94e7766df102 100644 --- a/.github/workflows/_stage_publish.yml +++ b/.github/workflows/_stage_publish.yml @@ -63,7 +63,7 @@ jobs: export ENVOY_PUBLISH_DRY_RUN=${{ (fromJSON(inputs.request).request.version.dev || ! inputs.trusted) && 1 || '' }} steps-pre: | - id: url - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 with: options: -Rr input: >- @@ -80,7 +80,7 @@ jobs: end | . as $bucket | "https://storage.googleapis.com/\($bucket)/\($sha)/\($path)" - - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/fetch@actions-v0.2.27 with: url: %{{ steps.url.outputs.value }} path: %{{ runner.temp }}/release.signed @@ -98,12 +98,12 @@ jobs: needs: - publish steps: - - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.2.27 with: ref: main repository: ${{ fromJSON(inputs.request).request.version.dev && 'envoyproxy/envoy-website' || 'envoyproxy/archive' }} diff --git a/.github/workflows/_stage_verify.yml b/.github/workflows/_stage_verify.yml index 3b4b714c0d2c..33d32850dfab 100644 --- a/.github/workflows/_stage_verify.yml +++ b/.github/workflows/_stage_verify.yml @@ -50,7 +50,7 @@ jobs: rbe: false steps-pre: | - id: url - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 with: options: -Rr input: >- @@ -66,15 +66,15 @@ jobs: end | . as $bucket | "https://storage.googleapis.com/\($bucket)/\($sha)" - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.27 with: url: %{{ steps.url.outputs.value }}/docker/envoy.tar variant: dev - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.27 with: url: %{{ steps.url.outputs.value }}/docker/envoy-contrib.tar variant: contrib-dev - - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/docker/fetch@actions-v0.2.27 with: url: %{{ steps.url.outputs.value }}/docker/envoy-google-vrp.tar variant: google-vrp-dev diff --git a/.github/workflows/_start.yml b/.github/workflows/_start.yml index cd431e830832..7ed6c1776e9f 100644 --- a/.github/workflows/_start.yml +++ b/.github/workflows/_start.yml @@ -54,7 +54,7 @@ jobs: start: runs-on: ubuntu-22.04 steps: - - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/jq@actions-v0.2.27 id: check-config name: Prepare check data with: @@ -77,13 +77,13 @@ jobs: | .skipped.output.summary = "${{ inputs.skipped-summary }}" | .skipped.output.text = "" - - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 name: Appauth id: appauth with: app_id: ${{ secrets.app-id }} key: ${{ secrets.app-key }} - - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/checks@actions-v0.2.27 name: Start checks id: checks with: @@ -94,7 +94,7 @@ jobs: ${{ fromJSON(inputs.env).summary.summary }} token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/gh-actions/json/table@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/json/table@actions-v0.2.27 name: Summary with: collapse-open: true @@ -118,7 +118,7 @@ jobs: output-path: GITHUB_STEP_SUMMARY title: Checks started/skipped - - uses: envoyproxy/toolshed/gh-actions/github/env/save@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/env/save@actions-v0.2.27 name: Save env id: data with: diff --git a/.github/workflows/codeql-daily.yml b/.github/workflows/codeql-daily.yml index 603f1ffa6167..0217ac0c4462 100644 --- a/.github/workflows/codeql-daily.yml +++ b/.github/workflows/codeql-daily.yml @@ -30,7 +30,7 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Free disk space - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.2.27 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/codeql-push.yml b/.github/workflows/codeql-push.yml index 81fb5c33418b..91cfb8b2df1f 100644 --- a/.github/workflows/codeql-push.yml +++ b/.github/workflows/codeql-push.yml @@ -61,7 +61,7 @@ jobs: - name: Free disk space if: ${{ env.BUILD_TARGETS != '' }} - uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/diskspace@actions-v0.2.27 with: to_remove: | /usr/local/lib/android diff --git a/.github/workflows/command.yml b/.github/workflows/command.yml index d8bda837b82e..0d31d698ae5f 100644 --- a/.github/workflows/command.yml +++ b/.github/workflows/command.yml @@ -28,7 +28,7 @@ jobs: && github.actor != 'dependabot[bot]' }} steps: - - uses: envoyproxy/toolshed/gh-actions/github/command@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/command@actions-v0.2.27 name: Parse command from comment id: command with: @@ -37,14 +37,14 @@ jobs: ^/(retest) # /retest - - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 if: ${{ steps.command.outputs.command == 'retest' }} id: appauth-retest name: Appauth (retest) with: key: ${{ secrets.ENVOY_CI_APP_KEY }} app_id: ${{ secrets.ENVOY_CI_APP_ID }} - - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/retest@actions-v0.2.27 if: ${{ steps.command.outputs.command == 'retest' }} name: Retest with: diff --git a/.github/workflows/envoy-dependency.yml b/.github/workflows/envoy-dependency.yml index d620f6acabbc..1aae9d60d58d 100644 --- a/.github/workflows/envoy-dependency.yml +++ b/.github/workflows/envoy-dependency.yml @@ -50,16 +50,16 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 with: token: ${{ steps.appauth.outputs.token }} - - uses: envoyproxy/toolshed/gh-actions/bson@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/bson@actions-v0.2.27 id: update name: Update dependency (${{ inputs.dependency }}) with: @@ -94,13 +94,13 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/upload/diff@actions-v0.2.27 name: Upload diff with: name: ${{ inputs.dependency }}-${{ steps.update.outputs.output }} - name: Create a PR if: ${{ inputs.pr }} - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.27 with: base: main body: | @@ -131,11 +131,11 @@ jobs: steps: - id: appauth name: Appauth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 with: app_id: ${{ secrets.ENVOY_CI_DEP_APP_ID }} key: ${{ secrets.ENVOY_CI_DEP_APP_KEY }} - - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 id: checkout name: Checkout Envoy repository with: @@ -177,7 +177,7 @@ jobs: - name: Check Docker SHAs id: build-images - uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/docker/shas@actions-v0.2.27 with: images: | sha: envoyproxy/envoy-build-ubuntu:${{ steps.build-tools.outputs.tag }} @@ -206,7 +206,7 @@ jobs: name: Update SHAs working-directory: envoy - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.27 with: base: main body: Created by Envoy dependency bot diff --git a/.github/workflows/envoy-release.yml b/.github/workflows/envoy-release.yml index 78ffc5ec6a40..bc5be1af287d 100644 --- a/.github/workflows/envoy-release.yml +++ b/.github/workflows/envoy-release.yml @@ -55,14 +55,14 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} @@ -83,10 +83,10 @@ jobs: name: Check changelog summary - if: ${{ inputs.author }} name: Validate signoff email - uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/email/validate@actions-v0.2.27 with: email: ${{ inputs.author }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.27 name: Create release with: source: | @@ -111,7 +111,7 @@ jobs: name: Release version id: release - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.27 with: base: ${{ github.ref_name }} commit: false @@ -136,18 +136,18 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - id: checkout name: Checkout Envoy repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} - - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/github/run@actions-v0.2.27 name: Sync version histories with: command: >- @@ -157,7 +157,7 @@ jobs: -- --signoff="${{ env.COMMITTER_NAME }} <${{ env.COMMITTER_EMAIL }}>" - name: Create a PR - uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/pr@actions-v0.2.27 with: append-commit-message: true base: ${{ github.ref_name }} @@ -187,13 +187,13 @@ jobs: steps: - id: appauth name: App auth - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 with: app_id: ${{ secrets.ENVOY_CI_PUBLISH_APP_ID }} key: ${{ secrets.ENVOY_CI_PUBLISH_APP_KEY }} - name: Checkout repository - uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/github/checkout@actions-v0.2.27 with: committer-name: ${{ env.COMMITTER_NAME }} committer-email: ${{ env.COMMITTER_EMAIL }} diff --git a/.github/workflows/envoy-sync.yml b/.github/workflows/envoy-sync.yml index 8f552a6c9fa1..bfa6fbb385fd 100644 --- a/.github/workflows/envoy-sync.yml +++ b/.github/workflows/envoy-sync.yml @@ -31,12 +31,12 @@ jobs: - data-plane-api - mobile-website steps: - - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/appauth@actions-v0.2.27 id: appauth with: app_id: ${{ secrets.ENVOY_CI_SYNC_APP_ID }} key: ${{ secrets.ENVOY_CI_SYNC_APP_KEY }} - - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/dispatch@actions-v0.2.27 with: repository: "envoyproxy/${{ matrix.downstream }}" ref: main diff --git a/.github/workflows/garbage.yml b/.github/workflows/garbage.yml index e74393a67fce..19110b583254 100644 --- a/.github/workflows/garbage.yml +++ b/.github/workflows/garbage.yml @@ -33,7 +33,7 @@ jobs: pool-id: 17 steps: - name: Remove dead AZP agents (${{ matrix.target }}) - uses: envoyproxy/toolshed/gh-actions/azp/agent-cleanup@actions-v0.2.26 + uses: envoyproxy/toolshed/gh-actions/azp/agent-cleanup@actions-v0.2.27 with: azp-org: cncf azp-token: ${{ secrets.AZP_TOKEN }} diff --git a/.github/workflows/mobile-android_build.yml b/.github/workflows/mobile-android_build.yml index 7fdbbed182d6..24c26d702ec9 100644 --- a/.github/workflows/mobile-android_build.yml +++ b/.github/workflows/mobile-android_build.yml @@ -75,9 +75,9 @@ jobs: target: kotlin-hello-world runs-on: envoy-x64-small steps-pre: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/pre@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/pre@actions-v0.2.27 steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.27 with: apk: bazel-bin/examples/kotlin/hello_world/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoykotlin/.MainActivity @@ -104,7 +104,7 @@ jobs: target: ${{ matrix.target }} runs-on: envoy-x64-small steps-pre: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/pre@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/pre@actions-v0.2.27 steps-post: ${{ matrix.steps-post }} timeout-minutes: 50 trusted: ${{ fromJSON(needs.load.outputs.trusted) }} @@ -115,7 +115,7 @@ jobs: include: - name: java-hello-world steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.27 with: apk: bazel-bin/examples/java/hello_world/hello_envoy.apk app: io.envoyproxy.envoymobile.helloenvoy/.MainActivity @@ -134,7 +134,7 @@ jobs: --config=mobile-remote-release-clang-android //test/kotlin/apps/baseline:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.27 with: apk: bazel-bin/test/kotlin/apps/baseline/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoybaselinetest/.MainActivity @@ -149,7 +149,7 @@ jobs: --config=mobile-remote-release-clang-android //test/kotlin/apps/experimental:hello_envoy_kt steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/android/post@actions-v0.2.27 with: apk: bazel-bin/test/kotlin/apps/experimental/hello_envoy_kt.apk app: io.envoyproxy.envoymobile.helloenvoyexperimentaltest/.MainActivity diff --git a/.github/workflows/mobile-ios_build.yml b/.github/workflows/mobile-ios_build.yml index 6b03f451f719..f64d6a42f5d3 100644 --- a/.github/workflows/mobile-ios_build.yml +++ b/.github/workflows/mobile-ios_build.yml @@ -86,7 +86,7 @@ jobs: source ./ci/mac_ci_setup.sh ./bazelw shutdown steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@actions-v0.2.27 with: app: ${{ matrix.app }} args: ${{ matrix.args || '--config=mobile-remote-ci-macos-ios' }} @@ -130,7 +130,7 @@ jobs: source: | source ./ci/mac_ci_setup.sh steps-post: | - - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@actions-v0.2.26 + - uses: envoyproxy/toolshed/gh-actions/envoy/ios/post@actions-v0.2.27 with: app: ${{ matrix.app }} args: ${{ matrix.args || '--config=mobile-remote-ci-macos-ios' }} From 83126f4a14c5dd34ea088871b9b0b371b26fb1f2 Mon Sep 17 00:00:00 2001 From: Ryan Hamilton Date: Sat, 2 Mar 2024 07:05:23 -0800 Subject: [PATCH 138/151] Update QUICHE from 02047e04d to 3373df94b (#32650) https://github.com/google/quiche/compare/02047e04d..3373df94b ``` $ git log 02047e04d..3373df94b --date=short --no-merges --format="%ad %al %s" 2024-02-29 awillia Add work-around for unused private fields in quic/masque 2024-02-29 rch Add a new MetadataVisitor class to QuicSpdyStream. If registered, an instance of this new class will receive decoded HTTP/3 METADATA frames. If not, registered, HTTP/3 METADATA frames will be delivered as unknown HTTP/3 frames, which is current behavior. 2024-02-29 wangsteve No public description 2024-02-29 birenroy Refactors error handling in PassthroughHeadersHandler. ``` Signed-off-by: Ryan Hamilton --- bazel/repository_locations.bzl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index 6cc43c9ca296..6e6043e8452b 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -1209,12 +1209,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "QUICHE", project_desc = "QUICHE (QUIC, HTTP/2, Etc) is Google‘s implementation of QUIC and related protocols", project_url = "https://github.com/google/quiche", - version = "02047e04d076d36226e92847a94acf33544f10d6", - sha256 = "0fc35f9794ae3ff698ff1f248129e3123e0cf699ff7dfa6563708316db00ce19", + version = "3373df94b3713d4e3ef69ee54ba6e7b6aaaebcc0", + sha256 = "fab452b368990d31a241f18a96fad613377752ff114b7584d0e7eb8cfe5464fe", urls = ["https://github.com/google/quiche/archive/{version}.tar.gz"], strip_prefix = "quiche-{version}", use_category = ["controlplane", "dataplane_core"], - release_date = "2024-02-28", + release_date = "2024-02-29", cpe = "N/A", license = "BSD-3-Clause", license_url = "https://github.com/google/quiche/blob/{version}/LICENSE", From d8d6f7ea6c1a1a67f0d482b30c68452180827db6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:24:01 +0000 Subject: [PATCH 139/151] build(deps): bump distroless/base-nossl-debian12 from `28dc895` to `0e777c6` in /ci (#32652) build(deps): bump distroless/base-nossl-debian12 in /ci Bumps distroless/base-nossl-debian12 from `28dc895` to `0e777c6`. --- updated-dependencies: - dependency-name: distroless/base-nossl-debian12 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- ci/Dockerfile-envoy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 9a6f0abe70fd..07affa3502e3 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -58,7 +58,7 @@ COPY --chown=0:0 --chmod=755 \ # STAGE: envoy-distroless -FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:28dc8956c04a92ffc192d06c5da69fa747c675ee44043ba18128e747c2f539f5 AS envoy-distroless +FROM gcr.io/distroless/base-nossl-debian12:nonroot@sha256:0e777c69ba810353b9f3f2033280bbe7d029d81fa55760f6eec817ef595aa19c AS envoy-distroless EXPOSE 10000 ENTRYPOINT ["/usr/local/bin/envoy"] CMD ["-c", "/etc/envoy/envoy.yaml"] From 15c86543b0258127ea856020e443272f0b2ae6b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:24:13 +0000 Subject: [PATCH 140/151] build(deps): bump the examples-ext-authz group in /examples/ext_authz with 1 update (#32654) build(deps): bump the examples-ext-authz group Bumps the examples-ext-authz group in /examples/ext_authz with 1 update: openpolicyagent/opa. Updates `openpolicyagent/opa` from 0.61.0-istio to 0.62.0-istio --- updated-dependencies: - dependency-name: openpolicyagent/opa dependency-type: direct:production update-type: version-update:semver-minor dependency-group: examples-ext-authz ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/ext_authz/Dockerfile-opa | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/ext_authz/Dockerfile-opa b/examples/ext_authz/Dockerfile-opa index 5afaffa73be2..85fdf75504ca 100644 --- a/examples/ext_authz/Dockerfile-opa +++ b/examples/ext_authz/Dockerfile-opa @@ -1 +1 @@ -FROM openpolicyagent/opa:0.61.0-istio@sha256:5ee86eb43bbe8a80e24d4d218a7fd568e5e5c1a782f20aa03a5643cad307034f +FROM openpolicyagent/opa:0.62.0-istio@sha256:94244de629099cea0a92d2680c982f16756f1e1e9de465db7af46bbc25516f7f From 00c441ad9dd3241ec92049bb49a8cbf7de40ca26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:40:31 +0000 Subject: [PATCH 141/151] build(deps): bump postgres from `0e564da` to `f58300a` in /examples/shared/postgres (#32632) build(deps): bump postgres in /examples/shared/postgres Bumps postgres from `0e564da` to `f58300a`. --- updated-dependencies: - dependency-name: postgres dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/shared/postgres/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index c21a5ac72c53..eb29c21895f4 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:0e564daae78c2eea56ba57c64711bb9c408a512ce73cf81f9c78623354dd6976 +FROM postgres:latest@sha256:f58300ac8d393b2e3b09d36ea12d7d24ee9440440e421472a300e929ddb63460 COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] From 5ea7376f9f0e0d3328dd70ed25bc26143d2a5416 Mon Sep 17 00:00:00 2001 From: sschepens Date: Mon, 4 Mar 2024 10:43:12 -0300 Subject: [PATCH 142/151] metrics_service: populate histogram summary sample sum (#32666) * populate histogram summary sample sum Signed-off-by: Sebastian Schepens --- .../stat_sinks/metrics_service/grpc_metrics_service_impl.cc | 1 + .../metrics_service/grpc_metrics_service_impl_test.cc | 2 ++ 2 files changed, 3 insertions(+) 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 2b74663df397..f1f593a83aa6 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 @@ -135,6 +135,7 @@ void MetricsFlusher::flushSummary(io::prometheus::client::MetricFamily& metrics_ quantile->set_value(hist_stats.computedQuantiles()[i]); } summary->set_sample_count(hist_stats.sampleCount()); + summary->set_sample_sum(hist_stats.sampleSum()); } io::prometheus::client::Metric* 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 6e7dc7425ce0..59ceb061fbd4 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 @@ -344,6 +344,7 @@ TEST_F(MetricsServiceSinkTest, HistogramEmitModeBoth) { const auto& metric1 = (*metrics)[0].metric(0); EXPECT_TRUE(metric1.has_summary()); + EXPECT_TRUE(metric1.summary().has_sample_sum()); const auto& metric2 = (*metrics)[1].metric(0); EXPECT_TRUE(metric2.has_histogram()); })); @@ -364,6 +365,7 @@ TEST_F(MetricsServiceSinkTest, HistogramEmitModeSummary) { const auto& metric1 = (*metrics)[0].metric(0); EXPECT_TRUE(metric1.has_summary()); + EXPECT_TRUE(metric1.summary().has_sample_sum()); })); sink.flush(snapshot_); } From ae11b936838aa7c397a4836fc4b2f91ab05f7d41 Mon Sep 17 00:00:00 2001 From: Kuo-Chung Hsu Date: Mon, 4 Mar 2024 06:27:58 -0800 Subject: [PATCH 143/151] Fix null node for list of struct in payload_to_metadaata filter (#32309) Signed-off-by: kuochunghsu --- .../payload_to_metadata_filter.cc | 34 +++++----- .../payload_to_metadata_filter.h | 5 +- .../payload_to_metadata_filter_test.cc | 67 ++++++++++++++++++- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.cc b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.cc index da4af15771af..bb22658d10a1 100644 --- a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.cc +++ b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.cc @@ -102,6 +102,7 @@ bool Rule::matches(const ThriftProxy::MessageMetadata& metadata) const { } FilterStatus TrieMatchHandler::messageEnd() { + ASSERT(steps_ == 0); ENVOY_LOG(trace, "TrieMatchHandler messageEnd"); parent_.handleOnMissing(); complete_ = true; @@ -110,13 +111,12 @@ FilterStatus TrieMatchHandler::messageEnd() { FilterStatus TrieMatchHandler::structBegin(absl::string_view) { ENVOY_LOG(trace, "TrieMatchHandler structBegin id: {}, steps: {}", - last_field_id_.has_value() ? std::to_string(last_field_id_.value()) - : "top_level_struct", - steps_); + field_ids_.empty() ? "top_level_struct" : std::to_string(field_ids_.back()), steps_); + ASSERT(steps_ >= 0); assertNode(); - if (last_field_id_.has_value()) { - if (steps_ == 0 && node_->children_.find(last_field_id_.value()) != node_->children_.end()) { - node_ = node_->children_[last_field_id_.value()]; + if (!field_ids_.empty()) { + if (steps_ == 0 && node_->children_.find(field_ids_.back()) != node_->children_.end()) { + node_ = node_->children_[field_ids_.back()]; ENVOY_LOG(trace, "name: {}", node_->name_); } else { steps_++; @@ -136,37 +136,41 @@ FilterStatus TrieMatchHandler::structEnd() { // last decoder event node_ = nullptr; } + ASSERT(steps_ >= 0); return FilterStatus::Continue; } FilterStatus TrieMatchHandler::fieldBegin(absl::string_view, FieldType&, int16_t& field_id) { - last_field_id_ = field_id; + ENVOY_LOG(trace, "TrieMatchHandler fieldBegin id: {}", field_id); + field_ids_.push_back(field_id); return FilterStatus::Continue; } FilterStatus TrieMatchHandler::fieldEnd() { - last_field_id_.reset(); + ENVOY_LOG(trace, "TrieMatchHandler fieldEnd"); + field_ids_.pop_back(); return FilterStatus::Continue; } FilterStatus TrieMatchHandler::stringValue(absl::string_view value) { assertLastFieldId(); - ENVOY_LOG(trace, "TrieMatchHandler stringValue id:{} value:{}", last_field_id_.value(), value); + ENVOY_LOG(trace, "TrieMatchHandler stringValue id:{} value:{}", field_ids_.back(), value); return handleString(static_cast(value)); } template FilterStatus TrieMatchHandler::numberValue(NumberType value) { assertLastFieldId(); - ENVOY_LOG(trace, "TrieMatchHandler numberValue id:{} value:{}", last_field_id_.value(), value); + ENVOY_LOG(trace, "TrieMatchHandler numberValue id:{} value:{}", field_ids_.back(), value); return handleString(std::to_string(value)); } FilterStatus TrieMatchHandler::handleString(std::string value) { + ASSERT(steps_ >= 0); assertNode(); assertLastFieldId(); - if (steps_ == 0 && node_->children_.find(last_field_id_.value()) != node_->children_.end() && - !node_->children_[last_field_id_.value()]->rule_ids_.empty()) { - auto on_present_node = node_->children_[last_field_id_.value()]; + if (steps_ == 0 && node_->children_.find(field_ids_.back()) != node_->children_.end() && + !node_->children_[field_ids_.back()]->rule_ids_.empty()) { + auto on_present_node = node_->children_[field_ids_.back()]; ENVOY_LOG(trace, "name: {}", on_present_node->name_); parent_.handleOnPresent(std::move(value), on_present_node->rule_ids_); } @@ -180,8 +184,8 @@ void TrieMatchHandler::assertNode() { } void TrieMatchHandler::assertLastFieldId() { - if (!last_field_id_.has_value()) { - throw EnvoyException("payload to metadata filter: invalid trie state, last_field_id is null"); + if (field_ids_.empty()) { + throw EnvoyException("payload to metadata filter: invalid trie state, field_ids_ is null"); } } diff --git a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.h b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.h index 5ca1dc8135c9..ffd39ab87d74 100644 --- a/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.h +++ b/source/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter.h @@ -135,6 +135,7 @@ class TrieMatchHandler : public DecoderCallbacks, } FilterStatus handleContainerEnd() { + ASSERT(steps_ > 0, "unmatched container end"); steps_--; return FilterStatus::Continue; } @@ -142,8 +143,8 @@ class TrieMatchHandler : public DecoderCallbacks, MetadataHandler& parent_; TrieSharedPtr node_; bool complete_{false}; - absl::optional last_field_id_; - uint16_t steps_{0}; + std::vector field_ids_; + int16_t steps_{0}; }; const uint32_t MAX_PAYLOAD_VALUE_LEN = 8 * 1024; diff --git a/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter_test.cc b/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter_test.cc index bf5e387a9877..b1adb0cc9a49 100644 --- a/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter_test.cc +++ b/test/extensions/filters/network/thrift_proxy/filters/payload_to_metadata/payload_to_metadata_filter_test.cc @@ -192,7 +192,7 @@ class PayloadToMetadataTest : public testing::Test, decoder->onData(buffer, underflow); } - void writeMessageMultiStruct() { + void writeMessageMultiStruct(bool list_of_struct = false) { Buffer::OwnedImpl buffer; auto metadata_ptr = std::make_shared(); @@ -229,6 +229,25 @@ class PayloadToMetadataTest : public testing::Test, proto->writeString(msg, "qux"); proto->writeFieldEnd(msg); + if (list_of_struct) { + proto->writeFieldBegin(msg, "list_of_struct", FieldType::List, 2); + const int list_size = 4; + proto->writeListBegin(msg, FieldType::Struct, 4); + + // In the list of struct, each struct has a single field "check". + for (int i = 0; i < list_size; i++) { + proto->writeStructBegin(msg, "data"); + proto->writeFieldBegin(msg, "check", FieldType::String, 1); + proto->writeString(msg, "check" + std::to_string(i)); + proto->writeFieldEnd(msg); + proto->writeFieldBegin(msg, "", FieldType::Stop, 0); // data stop field + proto->writeStructEnd(msg); + } + + proto->writeListEnd(msg); + proto->writeFieldEnd(msg); + } + proto->writeFieldBegin(msg, "", FieldType::Stop, 0); // request stop field proto->writeStructEnd(msg); proto->writeFieldEnd(msg); @@ -1316,6 +1335,52 @@ TEST_F(PayloadToMetadataTest, MultipleRulesOnMultiStruct) { filter_->onDestroy(); } +TEST_F(PayloadToMetadataTest, MultipleRulesOnListOfStruct) { + const std::string request_config_yaml = R"EOF( +request_rules: + - method_name: unmatched_foo + field_selector: + name: request + id: 2 + child: + name: baz + id: 1 + on_present: + metadata_namespace: envoy.lb + key: baz + - method_name: unmatched_foo2 + field_selector: + name: request + id: 2 + child: + name: baz + id: 1 + on_present: + metadata_namespace: envoy.lb + key: baz + - method_name: foo + field_selector: + name: request + id: 2 + child: + name: baz + id: 1 + on_present: + metadata_namespace: envoy.lb + key: baz +)EOF"; + + const std::map expected = {{"baz", "qux"}}; + + initializeFilter(request_config_yaml); + EXPECT_CALL(req_info_, setDynamicMetadata("envoy.lb", MapEq(expected))); + EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(req_info_)); + + // Add list of struct + writeMessageMultiStruct(true); + filter_->onDestroy(); +} + TEST_F(PayloadToMetadataTest, MalformPayloadWontCrash) { const std::string request_config_yaml = R"EOF( request_rules: From a2673f749bb7a3f68f357e006e49b2143cc98b8f Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Mon, 4 Mar 2024 09:36:45 -0500 Subject: [PATCH 144/151] Docs: Add diagram for histogram stat sink. (#32665) Signed-off-by: Kevin Baichoo --- source/docs/histogram.png | Bin 0 -> 218126 bytes source/docs/stats.md | 4 ++++ 2 files changed, 4 insertions(+) create mode 100644 source/docs/histogram.png diff --git a/source/docs/histogram.png b/source/docs/histogram.png new file mode 100644 index 0000000000000000000000000000000000000000..bd642210da75435131b7f0a67f382d8be0f41f4e GIT binary patch literal 218126 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�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 Date: Mon, 4 Mar 2024 09:31:11 -0800 Subject: [PATCH 145/151] c++20 by default (#32585) Commit Message: Update all builds except mobile to use C++20 language reference. Additional Description: Risk Level: medium Testing: done Docs Changes: none Release Notes: none --- .bazelrc | 6 +- bazel/cel-cpp.patch | 15 ++ bazel/proxy_wasm_cpp_sdk.patch | 40 ++++ bazel/repositories.bzl | 8 +- bazel/v8.patch | 27 ++- .../http/test/language_integration_test.cc | 3 +- .../network/test/mysql_integration_test.cc | 12 +- .../network/test/postgres_integration_test.cc | 4 +- mobile/.bazelrc | 2 + .../platform_bridge_filter_test.cc | 2 +- .../grpc/grpc_access_log_utils.cc | 7 + .../extensions/common/dubbo/hessian2_utils.h | 8 + .../network/dubbo_proxy/hessian_utils.h | 9 + .../filters/udp/dns_filter/dns_filter.cc | 2 +- .../grpc/health_checker_impl.cc | 7 + .../http/health_checker_impl.cc | 1 - .../http/health_checker_impl.h | 7 + .../health_checkers/thrift/thrift.h | 7 + .../stat_sinks/common/statsd/statsd.cc | 2 +- .../quic/envoy_quic_proof_verifier_test.cc | 2 +- test/common/tcp_proxy/tcp_proxy_test.cc | 2 +- test/common/tcp_proxy/tcp_proxy_test_base.h | 2 +- .../bootstrap/wasm/test_data/stats_cpp.cc | 6 +- .../dns_resolver/cares/dns_impl_test.cc | 4 +- .../starttls/starttls_integration_test.cc | 2 +- .../integration/quic_http_integration_test.cc | 192 +++++++++--------- test/mocks/thread_local/mocks.h | 3 +- tools/gen_compilation_database.py | 4 +- 28 files changed, 247 insertions(+), 139 deletions(-) create mode 100644 bazel/proxy_wasm_cpp_sdk.patch diff --git a/.bazelrc b/.bazelrc index edec083a41d1..03d5a5f7d48e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -64,7 +64,7 @@ common --experimental_allow_tags_propagation build:linux --copt=-fdebug-types-section build:linux --copt=-fPIC build:linux --copt=-Wno-deprecated-declarations -build:linux --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 +build:linux --cxxopt=-std=c++20 --host_cxxopt=-std=c++20 build:linux --cxxopt=-fsized-deallocation --host_cxxopt=-fsized-deallocation build:linux --conlyopt=-fexceptions build:linux --fission=dbg,opt @@ -133,7 +133,7 @@ build:clang-asan --linkopt --rtlib=compiler-rt build:clang-asan --linkopt --unwindlib=libgcc # macOS -build:macos --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 +build:macos --cxxopt=-std=c++20 --host_cxxopt=-std=c++20 build:macos --action_env=PATH=/opt/homebrew/bin:/opt/local/bin:/usr/local/bin:/usr/bin:/bin build:macos --host_action_env=PATH=/opt/homebrew/bin:/opt/local/bin:/usr/local/bin:/usr/bin:/bin build:macos --define tcmalloc=disabled @@ -194,7 +194,7 @@ build:libc++ --define force_libcpp=enabled build:libc++20 --config=libc++ # gRPC has a lot of deprecated-enum-enum-conversion warning. Remove once it is addressed -build:libc++20 --cxxopt=-std=c++20 --copt=-Wno-error=deprecated-enum-enum-conversion +build:libc++20 --copt=-Wno-error=deprecated-enum-enum-conversion # Optimize build for binary size reduction. build:sizeopt -c opt --copt -Os diff --git a/bazel/cel-cpp.patch b/bazel/cel-cpp.patch index f0d7d3005be7..34dd41033e56 100644 --- a/bazel/cel-cpp.patch +++ b/bazel/cel-cpp.patch @@ -1,3 +1,18 @@ +diff --git a/base/memory.h b/base/memory.h +index 3552e19..0fbe618 100644 +--- a/base/memory.h ++++ b/base/memory.h +@@ -166,8 +166,8 @@ std::enable_if_t, Handle> HandleFactory::Make( + #if defined(__cpp_lib_is_pointer_interconvertible) && \ + __cpp_lib_is_pointer_interconvertible >= 201907L + // Only available in C++20. +- static_assert(std::is_pointer_interconvertible_base_of_v, +- "F must be pointer interconvertible to Data"); ++// static_assert(std::is_pointer_interconvertible_base_of_v, ++// "F must be pointer interconvertible to Data"); + #endif + if (memory_manager.memory_management() == MemoryManagement::kPooling) { + void* addr; diff --git a/eval/internal/interop.cc b/eval/internal/interop.cc index 3acde6c..20f8ea3 100644 --- a/eval/internal/interop.cc diff --git a/bazel/proxy_wasm_cpp_sdk.patch b/bazel/proxy_wasm_cpp_sdk.patch new file mode 100644 index 000000000000..6dc24d20b2ca --- /dev/null +++ b/bazel/proxy_wasm_cpp_sdk.patch @@ -0,0 +1,40 @@ +diff --git a/proxy_wasm_api.h b/proxy_wasm_api.h +index 166b49c..b44637c 100644 +--- a/proxy_wasm_api.h ++++ b/proxy_wasm_api.h +@@ -1207,8 +1207,9 @@ struct SimpleHistogram { + template struct Counter : public MetricBase { + static Counter *New(std::string_view name, MetricTagDescriptor... fieldnames); + +- Counter(std::string_view name, MetricTagDescriptor... descriptors) +- : Counter(std::string(name), std::vector({toMetricTag(descriptors)...})) { ++ template ++ Counter(std::string_view name, MetricTagDescriptor... descriptors) ++ : Counter(std::string(name), std::vector({toMetricTag(descriptors)...})) { + } + + SimpleCounter resolve(Tags... f) { +@@ -1256,8 +1257,9 @@ inline Counter *Counter::New(std::string_view name, + template struct Gauge : public MetricBase { + static Gauge *New(std::string_view name, MetricTagDescriptor... fieldnames); + +- Gauge(std::string_view name, MetricTagDescriptor... descriptors) +- : Gauge(std::string(name), std::vector({toMetricTag(descriptors)...})) {} ++ template ++ Gauge(std::string_view name, MetricTagDescriptor... descriptors) ++ : Gauge(std::string(name), std::vector({toMetricTag(descriptors)...})) {} + + SimpleGauge resolve(Tags... f) { + std::vector fields{toString(f)...}; +@@ -1302,8 +1304,9 @@ inline Gauge *Gauge::New(std::string_view name, + template struct Histogram : public MetricBase { + static Histogram *New(std::string_view name, MetricTagDescriptor... fieldnames); + +- Histogram(std::string_view name, MetricTagDescriptor... descriptors) +- : Histogram(std::string(name), ++ template ++ Histogram(std::string_view name, MetricTagDescriptor... descriptors) ++ : Histogram(std::string(name), + std::vector({toMetricTag(descriptors)...})) {} + + SimpleHistogram resolve(Tags... f) { diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 8a03e4599054..bec5940badf3 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -1280,7 +1280,13 @@ def _upb(): ) def _proxy_wasm_cpp_sdk(): - external_http_archive(name = "proxy_wasm_cpp_sdk") + external_http_archive( + name = "proxy_wasm_cpp_sdk", + patch_args = ["-p1"], + patches = [ + "@envoy//bazel:proxy_wasm_cpp_sdk.patch", + ], + ) def _proxy_wasm_cpp_host(): external_http_archive( diff --git a/bazel/v8.patch b/bazel/v8.patch index 9805eb749589..2d7101db917e 100644 --- a/bazel/v8.patch +++ b/bazel/v8.patch @@ -1,11 +1,11 @@ # 1. Use already imported python dependencies # 2. Disable pointer compression (limits the maximum number of WasmVMs). -# 3. Add support for --define=no_debug_info=1. +# 3. Add support for --define=no_debug_info=1. # 4. Allow compiling v8 on macOS 10.15 to 13.0. TODO(dio): Will remove this patch when https://bugs.chromium.org/p/v8/issues/detail?id=13428 is fixed. # 5. Don't expose Wasm C API (only Wasm C++ API). diff --git a/BUILD.bazel b/BUILD.bazel -index 4e89f90e7e..ced403d5aa 100644 +index 4e89f90..0df4f67 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -4,7 +4,7 @@ @@ -17,10 +17,6 @@ index 4e89f90e7e..ced403d5aa 100644 load( "@v8//:bazel/defs.bzl", "v8_binary", -diff --git a/BUILD.bazel b/BUILD.bazel -index 4e89f90e7e..3fcb38b3f3 100644 ---- a/BUILD.bazel -+++ b/BUILD.bazel @@ -157,7 +157,7 @@ v8_int( # If no explicit value for v8_enable_pointer_compression, we set it to 'none'. v8_string( @@ -31,7 +27,7 @@ index 4e89f90e7e..3fcb38b3f3 100644 # Default setting for v8_enable_pointer_compression. diff --git a/bazel/defs.bzl b/bazel/defs.bzl -index e957c0fad3..a6de50e6ab 100644 +index e957c0f..0327669 100644 --- a/bazel/defs.bzl +++ b/bazel/defs.bzl @@ -116,6 +116,7 @@ def _default_args(): @@ -42,15 +38,16 @@ index e957c0fad3..a6de50e6ab 100644 "-std=c++17", ], "@v8//bazel/config:is_gcc": [ -@@ -131,6 +132,7 @@ def _default_args(): +@@ -131,6 +132,8 @@ def _default_args(): "-Wno-redundant-move", "-Wno-return-type", "-Wno-stringop-overflow", + "-Wno-nonnull", ++ "-Wno-pessimizing-move", # Use GNU dialect, because GCC doesn't allow using # ##__VA_ARGS__ when in standards-conforming mode. "-std=gnu++17", -@@ -151,6 +153,23 @@ def _default_args(): +@@ -151,6 +154,23 @@ def _default_args(): "-fno-integrated-as", ], "//conditions:default": [], @@ -75,10 +72,10 @@ index e957c0fad3..a6de50e6ab 100644 includes = ["include"], linkopts = select({ diff --git a/src/wasm/c-api.cc b/src/wasm/c-api.cc -index ce3f569fd5..dc8a4c4f6a 100644 +index 4473e20..65a6ec7 100644 --- a/src/wasm/c-api.cc +++ b/src/wasm/c-api.cc -@@ -2238,6 +2238,8 @@ auto Instance::exports() const -> ownvec { +@@ -2247,6 +2247,8 @@ auto Instance::exports() const -> ownvec { } // namespace wasm @@ -87,22 +84,22 @@ index ce3f569fd5..dc8a4c4f6a 100644 // BEGIN FILE wasm-c.cc extern "C" { -@@ -3257,3 +3259,5 @@ wasm_instance_t* wasm_frame_instance(const wasm_frame_t* frame) { +@@ -3274,3 +3276,5 @@ wasm_instance_t* wasm_frame_instance(const wasm_frame_t* frame) { #undef WASM_DEFINE_SHARABLE_REF } // extern "C" + +#endif diff --git a/third_party/inspector_protocol/code_generator.py b/third_party/inspector_protocol/code_generator.py -index c3768b8..d4a1dda 100644 +index c3768b8..d4a1dda 100755 --- a/third_party/inspector_protocol/code_generator.py +++ b/third_party/inspector_protocol/code_generator.py @@ -16,6 +16,8 @@ try: except ImportError: import simplejson as json - + +sys.path += [os.path.dirname(__file__)] + import pdl - + try: diff --git a/contrib/language/filters/http/test/language_integration_test.cc b/contrib/language/filters/http/test/language_integration_test.cc index 0870392dd41d..6875c0f23d11 100644 --- a/contrib/language/filters/http/test/language_integration_test.cc +++ b/contrib/language/filters/http/test/language_integration_test.cc @@ -21,7 +21,8 @@ name: envoy.filters.http.language default_language: {} supported_languages: {} )EOF"; - config_helper_.prependFilter(fmt::format(yaml, default_language, supported_languages)); + config_helper_.prependFilter( + fmt::format(fmt::runtime(yaml), default_language, supported_languages)); } }; diff --git a/contrib/mysql_proxy/filters/network/test/mysql_integration_test.cc b/contrib/mysql_proxy/filters/network/test/mysql_integration_test.cc index 0b28e75cf952..282dc83bdb0e 100644 --- a/contrib/mysql_proxy/filters/network/test/mysql_integration_test.cc +++ b/contrib/mysql_proxy/filters/network/test/mysql_integration_test.cc @@ -24,12 +24,12 @@ class MySQLIntegrationTest : public testing::TestWithParam(upstream_ssl_config), // upstream SSL transport socket diff --git a/mobile/.bazelrc b/mobile/.bazelrc index 85c2bb3e2b27..0fc992ca2e46 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -193,6 +193,8 @@ build:mobile-remote-ci-linux --config=mobile-remote-ci-common build:mobile-remote-ci-linux-clang --action_env=CC=/opt/llvm/bin/clang build:mobile-remote-ci-linux-clang --action_env=CXX=/opt/llvm/bin/clang++ build:mobile-remote-ci-linux-clang --config=mobile-remote-ci-linux +# Temporary revert to C++17 for mobile NDK builds. +build:mobile-remote-ci-linux-clang --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 ############################################################################# # mobile-remote-ci-linux-asan: These options are Linux-only using Clang and AddressSanitizer diff --git a/mobile/test/common/extensions/filters/http/platform_bridge/platform_bridge_filter_test.cc b/mobile/test/common/extensions/filters/http/platform_bridge/platform_bridge_filter_test.cc index 63b348c931ce..0fc1c915f3a4 100644 --- a/mobile/test/common/extensions/filters/http/platform_bridge/platform_bridge_filter_test.cc +++ b/mobile/test/common/extensions/filters/http/platform_bridge/platform_bridge_filter_test.cc @@ -69,7 +69,7 @@ class PlatformBridgeFilterTest : public testing::Test { )EOF"; std::string expected_state = fmt::format( - expected_state_template, name, error_response, request.iteration_state, + fmt::runtime(expected_state_template), name, error_response, request.iteration_state, request.on_headers_called, request.headers_forwarded, request.on_data_called, request.data_forwarded, request.on_trailers_called, request.trailers_forwarded, request.on_resume_called, request.pending_headers, request.buffer, request.pending_trailers, 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 e353949f0fea..8eb330766c37 100644 --- a/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc +++ b/source/extensions/access_loggers/grpc/grpc_access_log_utils.cc @@ -271,7 +271,14 @@ void Utility::extractCommonAccessLogProperties( } if (stream_info.upstreamInfo().has_value()) { +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdangling-reference" +#endif const auto& upstream_info = stream_info.upstreamInfo().value().get(); +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif if (upstream_info.upstreamHost() != nullptr) { Network::Utility::addressToProtobufAddress( *upstream_info.upstreamHost()->address(), diff --git a/source/extensions/common/dubbo/hessian2_utils.h b/source/extensions/common/dubbo/hessian2_utils.h index a51a224bc836..30a0f54335d6 100644 --- a/source/extensions/common/dubbo/hessian2_utils.h +++ b/source/extensions/common/dubbo/hessian2_utils.h @@ -5,7 +5,15 @@ #include "envoy/buffer/buffer.h" #include "absl/strings/string_view.h" + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdangling-reference" +#endif #include "hessian2/basic_codec/object_codec.hpp" +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif #include "hessian2/codec.hpp" #include "hessian2/object.hpp" #include "hessian2/reader.hpp" diff --git a/source/extensions/filters/network/dubbo_proxy/hessian_utils.h b/source/extensions/filters/network/dubbo_proxy/hessian_utils.h index 8385eb37c0a4..da30248da3b2 100644 --- a/source/extensions/filters/network/dubbo_proxy/hessian_utils.h +++ b/source/extensions/filters/network/dubbo_proxy/hessian_utils.h @@ -5,7 +5,16 @@ #include "envoy/buffer/buffer.h" #include "absl/strings/string_view.h" + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdangling-reference" +#endif #include "hessian2/basic_codec/object_codec.hpp" +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + #include "hessian2/codec.hpp" #include "hessian2/object.hpp" #include "hessian2/reader.hpp" diff --git a/source/extensions/filters/udp/dns_filter/dns_filter.cc b/source/extensions/filters/udp/dns_filter/dns_filter.cc index 211f9d95a13c..1da684d586d7 100644 --- a/source/extensions/filters/udp/dns_filter/dns_filter.cc +++ b/source/extensions/filters/udp/dns_filter/dns_filter.cc @@ -250,7 +250,7 @@ DnsFilter::DnsFilter(Network::UdpReadFilterCallbacks& callbacks, for (const auto& ip : iplist) { incrementExternalQueryTypeAnswerCount(query->type_); const std::chrono::seconds ttl = getDomainTTL(query->name_); - message_parser_.storeDnsAnswerRecord(context, *query, ttl, std::move(ip)); + message_parser_.storeDnsAnswerRecord(context, *query, ttl, ip); } sendDnsResponse(std::move(context)); }; diff --git a/source/extensions/health_checkers/grpc/health_checker_impl.cc b/source/extensions/health_checkers/grpc/health_checker_impl.cc index 3d6906995553..ee9b4a047ae8 100644 --- a/source/extensions/health_checkers/grpc/health_checker_impl.cc +++ b/source/extensions/health_checkers/grpc/health_checker_impl.cc @@ -195,8 +195,15 @@ void GrpcHealthCheckerImpl::GrpcActiveHealthCheckSession::onInterval() { request_encoder_ = &client_->newStream(*this); request_encoder_->getStream().addCallbacks(*this); +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdangling-reference" +#endif const std::string& authority = getHostname(host_, parent_.authority_value_, parent_.cluster_.info()); +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif auto headers_message = Grpc::Common::prepareHeaders(authority, parent_.service_method_.service()->full_name(), parent_.service_method_.name(), absl::nullopt); diff --git a/source/extensions/health_checkers/http/health_checker_impl.cc b/source/extensions/health_checkers/http/health_checker_impl.cc index 49b07984222e..608dd8d7556e 100644 --- a/source/extensions/health_checkers/http/health_checker_impl.cc +++ b/source/extensions/health_checkers/http/health_checker_impl.cc @@ -183,7 +183,6 @@ HttpHealthCheckerImpl::HttpActiveHealthCheckSession::HttpActiveHealthCheckSessio response_body_(std::make_unique()), hostname_( HealthCheckerFactory::getHostname(host, parent_.host_value_, parent_.cluster_.info())), - local_connection_info_provider_(std::make_shared( Network::Utility::getCanonicalIpv4LoopbackAddress(), Network::Utility::getCanonicalIpv4LoopbackAddress())), diff --git a/source/extensions/health_checkers/http/health_checker_impl.h b/source/extensions/health_checkers/http/health_checker_impl.h index f5e5c2c5a1d7..4403b11f559f 100644 --- a/source/extensions/health_checkers/http/health_checker_impl.h +++ b/source/extensions/health_checkers/http/health_checker_impl.h @@ -141,7 +141,14 @@ class HttpHealthCheckerImpl : public HealthCheckerImplBase { Http::CodecClientPtr client_; Http::ResponseHeaderMapPtr response_headers_; Buffer::InstancePtr response_body_; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdangling-reference" +#endif const std::string& hostname_; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif Network::ConnectionInfoProviderSharedPtr local_connection_info_provider_; // Keep small members (bools and enums) at the end of class, to reduce alignment overhead. const Http::Protocol protocol_; diff --git a/source/extensions/health_checkers/thrift/thrift.h b/source/extensions/health_checkers/thrift/thrift.h index 9c75e9df56d7..329a01e9d08d 100644 --- a/source/extensions/health_checkers/thrift/thrift.h +++ b/source/extensions/health_checkers/thrift/thrift.h @@ -57,7 +57,14 @@ class ThriftHealthChecker : public Upstream::HealthCheckerImplBase { private: ThriftHealthChecker& parent_; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdangling-reference" +#endif const std::string& hostname_; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif ClientPtr client_; bool expect_close_{}; }; diff --git a/source/extensions/stat_sinks/common/statsd/statsd.cc b/source/extensions/stat_sinks/common/statsd/statsd.cc index 6236fd67a866..25872589ca74 100644 --- a/source/extensions/stat_sinks/common/statsd/statsd.cc +++ b/source/extensions/stat_sinks/common/statsd/statsd.cc @@ -198,7 +198,7 @@ TcpStatsdSink::TcpStatsdSink(const LocalInfo::LocalInfo& local_info, const auto cluster_or_error = Config::Utility::checkCluster("tcp statsd", cluster_name, cluster_manager); THROW_IF_STATUS_NOT_OK(cluster_or_error, throw); - const auto cluster = std::move(cluster_or_error.value()); + const auto cluster = cluster_or_error.value(); cluster_info_ = cluster->get().info(); tls_->set([this](Event::Dispatcher& dispatcher) -> ThreadLocal::ThreadLocalObjectSharedPtr { return std::make_shared(*this, dispatcher); diff --git a/test/common/quic/envoy_quic_proof_verifier_test.cc b/test/common/quic/envoy_quic_proof_verifier_test.cc index 3953ce0ed9e5..c7eef7189ac1 100644 --- a/test/common/quic/envoy_quic_proof_verifier_test.cc +++ b/test/common/quic/envoy_quic_proof_verifier_test.cc @@ -35,7 +35,7 @@ class EnvoyQuicProofVerifierTest : public testing::Test { public: EnvoyQuicProofVerifierTest() : root_ca_cert_(cert_chain_.substr(cert_chain_.rfind("-----BEGIN CERTIFICATE-----"))), - leaf_cert_([=]() { + leaf_cert_([this]() { std::stringstream pem_stream(cert_chain_); std::vector chain = quic::CertificateView::LoadPemFromStream(&pem_stream); return chain[0]; diff --git a/test/common/tcp_proxy/tcp_proxy_test.cc b/test/common/tcp_proxy/tcp_proxy_test.cc index 32376a18b31d..41ad163dba41 100644 --- a/test/common/tcp_proxy/tcp_proxy_test.cc +++ b/test/common/tcp_proxy/tcp_proxy_test.cc @@ -103,7 +103,7 @@ class TcpProxyTest : public TcpProxyTestBase { .RetiresOnSaturation(); EXPECT_CALL(conn_pool_, newConnection(_)) .WillOnce(Invoke( - [=](Tcp::ConnectionPool::Callbacks& cb) -> Tcp::ConnectionPool::Cancellable* { + [=, this](Tcp::ConnectionPool::Callbacks& cb) -> Tcp::ConnectionPool::Cancellable* { conn_pool_callbacks_.push_back(&cb); return onNewConnection(conn_pool_handles_.at(i).get()); })) diff --git a/test/common/tcp_proxy/tcp_proxy_test_base.h b/test/common/tcp_proxy/tcp_proxy_test_base.h index fa621fccf6b2..9816df3871d9 100644 --- a/test/common/tcp_proxy/tcp_proxy_test_base.h +++ b/test/common/tcp_proxy/tcp_proxy_test_base.h @@ -121,7 +121,7 @@ class TcpProxyTestBase : public testing::Test { void raiseEventUpstreamConnected(uint32_t conn_index) { EXPECT_CALL(filter_callbacks_.connection_, readDisable(false)); EXPECT_CALL(*upstream_connection_data_.at(conn_index), addUpstreamCallbacks(_)) - .WillOnce(Invoke([=](Tcp::ConnectionPool::UpstreamCallbacks& cb) -> void { + .WillOnce(Invoke([=, this](Tcp::ConnectionPool::UpstreamCallbacks& cb) -> void { upstream_callbacks_ = &cb; // Simulate TCP conn pool upstream callbacks. This is safe because the TCP proxy never diff --git a/test/extensions/bootstrap/wasm/test_data/stats_cpp.cc b/test/extensions/bootstrap/wasm/test_data/stats_cpp.cc index 51b21a4c28bc..885c91c3e7aa 100644 --- a/test/extensions/bootstrap/wasm/test_data/stats_cpp.cc +++ b/test/extensions/bootstrap/wasm/test_data/stats_cpp.cc @@ -96,16 +96,16 @@ WASM_EXPORT(void, proxy_on_log, (uint32_t /* context_zero */)) { auto simple_h = complete_h->resolve("test_tag", true); logError(std::string("h_id = ") + complete_h->nameFromIdSlow(simple_h.metric_id)); - Counter stack_c("test_counter", "string_tag", "int_tag", "bool_tag"); + Counter stack_c("test_counter", MetricTagDescriptor("string_tag"), MetricTagDescriptor("int_tag"), MetricTagDescriptor("bool_tag")); stack_c.increment(1, "test_tag_stack", 7, true); logError(std::string("stack_c = ") + std::to_string(stack_c.get("test_tag_stack", 7, true))); - Gauge stack_g("test_gauge", "string_tag1", "string_tag2"); + Gauge stack_g("test_gauge", MetricTagDescriptor("string_tag1"), MetricTagDescriptor("string_tag2")); stack_g.record(2, "stack_test_tag1", "test_tag2"); logError(std::string("stack_g = ") + std::to_string(stack_g.get("stack_test_tag1", "test_tag2"))); std::string_view int_tag = "int_tag"; - Histogram stack_h("test_histogram", int_tag, "string_tag", "bool_tag"); + Histogram stack_h("test_histogram", MetricTagDescriptor(int_tag), MetricTagDescriptor("string_tag"), MetricTagDescriptor("bool_tag")); std::string_view stack_test_tag = "stack_test_tag"; stack_h.record(3, 7, stack_test_tag, true); } diff --git a/test/extensions/network/dns_resolver/cares/dns_impl_test.cc b/test/extensions/network/dns_resolver/cares/dns_impl_test.cc index eb77fdff8e41..d4ce05473be7 100644 --- a/test/extensions/network/dns_resolver/cares/dns_impl_test.cc +++ b/test/extensions/network/dns_resolver/cares/dns_impl_test.cc @@ -794,7 +794,7 @@ class DnsImplTest : public testing::TestWithParam { const absl::optional expected_ttl) { return resolver_->resolve( address, lookup_family, - [=](DnsResolver::ResolutionStatus status, std::list&& results) -> void { + [=, this](DnsResolver::ResolutionStatus status, std::list&& results) -> void { EXPECT_EQ(expected_status, status); std::list address_as_string_list = getAddressAsStringList(results); @@ -827,7 +827,7 @@ class DnsImplTest : public testing::TestWithParam { const DnsLookupFamily lookup_family) { return resolver_->resolve( address, lookup_family, - [=](DnsResolver::ResolutionStatus status, std::list&& results) -> void { + [=, this](DnsResolver::ResolutionStatus status, std::list&& results) -> void { EXPECT_EQ(DnsResolver::ResolutionStatus::Success, status); std::list address_as_string_list = getAddressAsStringList(results); EXPECT_EQ(0, address_as_string_list.size()); diff --git a/test/extensions/transport_sockets/starttls/starttls_integration_test.cc b/test/extensions/transport_sockets/starttls/starttls_integration_test.cc index 719a0c34fcd7..cb2564e38286 100644 --- a/test/extensions/transport_sockets/starttls/starttls_integration_test.cc +++ b/test/extensions/transport_sockets/starttls/starttls_integration_test.cc @@ -71,7 +71,7 @@ Network::FilterStatus StartTlsSwitchFilter::onCommand(Buffer::Instance& buf, boo if (message == "switch") { buf.drain(buf.length()); buf.add("usetls"); - read_callbacks_->connection().addBytesSentCallback([=](uint64_t bytes) -> bool { + read_callbacks_->connection().addBytesSentCallback([=, this](uint64_t bytes) -> bool { // Wait until 6 bytes long "usetls" has been sent. if (bytes >= 6) { read_callbacks_->connection().startSecureTransport(); diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index a4460d7f6add..17f3242cfb06 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -1804,38 +1804,39 @@ TEST_P(QuicInplaceLdsIntegrationTest, StatelessResetOldConnection) { TEST_P(QuicHttpIntegrationTest, UsesPreferredAddress) { autonomous_upstream_ = true; - config_helper_.addConfigModifier([=](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { - auto* listen_address = bootstrap.mutable_static_resources() - ->mutable_listeners(0) - ->mutable_address() - ->mutable_socket_address(); - // Change listening address to Any. - listen_address->set_address(version_ == Network::Address::IpVersion::v4 ? "0.0.0.0" : "::"); - auto* preferred_address_config = bootstrap.mutable_static_resources() - ->mutable_listeners(0) - ->mutable_udp_listener_config() - ->mutable_quic_options() - ->mutable_server_preferred_address_config(); - // Configure a loopback interface as the server's preferred address. - preferred_address_config->set_name("quic.server_preferred_address.fixed"); - envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig - server_preferred_address; - server_preferred_address.set_ipv4_address("127.0.0.2"); - server_preferred_address.set_ipv6_address("::2"); - preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); - - // Configure a test listener filter which is incompatible with any server preferred addresses - // but with any matcher, which effectively disables the filter. - auto* listener_filter = - bootstrap.mutable_static_resources()->mutable_listeners(0)->add_listener_filters(); - listener_filter->set_name("dumb_filter"); - auto configuration = test::integration::filters::TestQuicListenerFilterConfig(); - configuration.set_added_value("foo"); - configuration.set_allow_server_migration(false); - configuration.set_allow_client_migration(false); - listener_filter->mutable_typed_config()->PackFrom(configuration); - listener_filter->mutable_filter_disabled()->set_any_match(true); - }); + config_helper_.addConfigModifier( + [=, this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listen_address = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_address() + ->mutable_socket_address(); + // Change listening address to Any. + listen_address->set_address(version_ == Network::Address::IpVersion::v4 ? "0.0.0.0" : "::"); + auto* preferred_address_config = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_udp_listener_config() + ->mutable_quic_options() + ->mutable_server_preferred_address_config(); + // Configure a loopback interface as the server's preferred address. + preferred_address_config->set_name("quic.server_preferred_address.fixed"); + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig + server_preferred_address; + server_preferred_address.set_ipv4_address("127.0.0.2"); + server_preferred_address.set_ipv6_address("::2"); + preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); + + // Configure a test listener filter which is incompatible with any server preferred + // addresses but with any matcher, which effectively disables the filter. + auto* listener_filter = + bootstrap.mutable_static_resources()->mutable_listeners(0)->add_listener_filters(); + listener_filter->set_name("dumb_filter"); + auto configuration = test::integration::filters::TestQuicListenerFilterConfig(); + configuration.set_added_value("foo"); + configuration.set_allow_server_migration(false); + configuration.set_allow_client_migration(false); + listener_filter->mutable_typed_config()->PackFrom(configuration); + listener_filter->mutable_filter_disabled()->set_any_match(true); + }); initialize(); codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); @@ -1872,38 +1873,39 @@ TEST_P(QuicHttpIntegrationTest, PreferredAddressRuntimeFlag) { autonomous_upstream_ = true; config_helper_.addRuntimeOverride( "envoy.reloadable_features.quic_send_server_preferred_address_to_all_clients", "false"); - config_helper_.addConfigModifier([=](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { - auto* listen_address = bootstrap.mutable_static_resources() - ->mutable_listeners(0) - ->mutable_address() - ->mutable_socket_address(); - // Change listening address to Any. - listen_address->set_address(version_ == Network::Address::IpVersion::v4 ? "0.0.0.0" : "::"); - auto* preferred_address_config = bootstrap.mutable_static_resources() - ->mutable_listeners(0) - ->mutable_udp_listener_config() - ->mutable_quic_options() - ->mutable_server_preferred_address_config(); - // Configure a loopback interface as the server's preferred address. - preferred_address_config->set_name("quic.server_preferred_address.fixed"); - envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig - server_preferred_address; - server_preferred_address.set_ipv4_address("127.0.0.2"); - server_preferred_address.set_ipv6_address("::2"); - preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); - - // Configure a test listener filter which is incompatible with any server preferred addresses - // but with any matcher, which effectively disables the filter. - auto* listener_filter = - bootstrap.mutable_static_resources()->mutable_listeners(0)->add_listener_filters(); - listener_filter->set_name("dumb_filter"); - auto configuration = test::integration::filters::TestQuicListenerFilterConfig(); - configuration.set_added_value("foo"); - configuration.set_allow_server_migration(false); - configuration.set_allow_client_migration(false); - listener_filter->mutable_typed_config()->PackFrom(configuration); - listener_filter->mutable_filter_disabled()->set_any_match(true); - }); + config_helper_.addConfigModifier( + [=, this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listen_address = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_address() + ->mutable_socket_address(); + // Change listening address to Any. + listen_address->set_address(version_ == Network::Address::IpVersion::v4 ? "0.0.0.0" : "::"); + auto* preferred_address_config = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_udp_listener_config() + ->mutable_quic_options() + ->mutable_server_preferred_address_config(); + // Configure a loopback interface as the server's preferred address. + preferred_address_config->set_name("quic.server_preferred_address.fixed"); + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig + server_preferred_address; + server_preferred_address.set_ipv4_address("127.0.0.2"); + server_preferred_address.set_ipv6_address("::2"); + preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); + + // Configure a test listener filter which is incompatible with any server preferred + // addresses but with any matcher, which effectively disables the filter. + auto* listener_filter = + bootstrap.mutable_static_resources()->mutable_listeners(0)->add_listener_filters(); + listener_filter->set_name("dumb_filter"); + auto configuration = test::integration::filters::TestQuicListenerFilterConfig(); + configuration.set_added_value("foo"); + configuration.set_allow_server_migration(false); + configuration.set_allow_client_migration(false); + listener_filter->mutable_typed_config()->PackFrom(configuration); + listener_filter->mutable_filter_disabled()->set_any_match(true); + }); initialize(); codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); @@ -1992,36 +1994,38 @@ TEST_P(QuicHttpIntegrationTest, PreferredAddressDroppedByIncompatibleListenerFil autonomous_upstream_ = true; useAccessLog(fmt::format("%RESPONSE_CODE% %FILTER_STATE({})%", TestQuicListenerFilter::TestStringFilterState::key())); - config_helper_.addConfigModifier([=](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { - auto* listen_address = bootstrap.mutable_static_resources() - ->mutable_listeners(0) - ->mutable_address() - ->mutable_socket_address(); - // Change listening address to Any. - listen_address->set_address(version_ == Network::Address::IpVersion::v4 ? "0.0.0.0" : "::"); - auto* preferred_address_config = bootstrap.mutable_static_resources() - ->mutable_listeners(0) - ->mutable_udp_listener_config() - ->mutable_quic_options() - ->mutable_server_preferred_address_config(); - // Configure a loopback interface as the server's preferred address. - preferred_address_config->set_name("quic.server_preferred_address.fixed"); - envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig - server_preferred_address; - server_preferred_address.set_ipv4_address("127.0.0.2"); - server_preferred_address.set_ipv6_address("::2"); - preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); - - // Configure a test listener filter which is incompatible with any server preferred addresses. - auto* listener_filter = - bootstrap.mutable_static_resources()->mutable_listeners(0)->add_listener_filters(); - listener_filter->set_name("dumb_filter"); - auto configuration = test::integration::filters::TestQuicListenerFilterConfig(); - configuration.set_added_value("foo"); - configuration.set_allow_server_migration(false); - configuration.set_allow_client_migration(false); - listener_filter->mutable_typed_config()->PackFrom(configuration); - }); + config_helper_.addConfigModifier( + [=, this](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void { + auto* listen_address = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_address() + ->mutable_socket_address(); + // Change listening address to Any. + listen_address->set_address(version_ == Network::Address::IpVersion::v4 ? "0.0.0.0" : "::"); + auto* preferred_address_config = bootstrap.mutable_static_resources() + ->mutable_listeners(0) + ->mutable_udp_listener_config() + ->mutable_quic_options() + ->mutable_server_preferred_address_config(); + // Configure a loopback interface as the server's preferred address. + preferred_address_config->set_name("quic.server_preferred_address.fixed"); + envoy::extensions::quic::server_preferred_address::v3::FixedServerPreferredAddressConfig + server_preferred_address; + server_preferred_address.set_ipv4_address("127.0.0.2"); + server_preferred_address.set_ipv6_address("::2"); + preferred_address_config->mutable_typed_config()->PackFrom(server_preferred_address); + + // Configure a test listener filter which is incompatible with any server preferred + // addresses. + auto* listener_filter = + bootstrap.mutable_static_resources()->mutable_listeners(0)->add_listener_filters(); + listener_filter->set_name("dumb_filter"); + auto configuration = test::integration::filters::TestQuicListenerFilterConfig(); + configuration.set_added_value("foo"); + configuration.set_allow_server_migration(false); + configuration.set_allow_client_migration(false); + listener_filter->mutable_typed_config()->PackFrom(configuration); + }); initialize(); codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http"))); diff --git a/test/mocks/thread_local/mocks.h b/test/mocks/thread_local/mocks.h index 36ccd578023b..09dff2377706 100644 --- a/test/mocks/thread_local/mocks.h +++ b/test/mocks/thread_local/mocks.h @@ -74,8 +74,7 @@ class MockInstance : public Instance { } void runOnAllThreads(const UpdateCb& cb, const std::function& main_callback) override { EXPECT_TRUE(was_set_); - parent_.runOnAllThreads([cb, this]() { cb(parent_.data_[index_]); }, - std::move(main_callback)); + parent_.runOnAllThreads([cb, this]() { cb(parent_.data_[index_]); }, main_callback); } bool isShutdown() const override { return parent_.shutdown_; } diff --git a/tools/gen_compilation_database.py b/tools/gen_compilation_database.py index 11932552f6f5..5e7fc75d3e13 100755 --- a/tools/gen_compilation_database.py +++ b/tools/gen_compilation_database.py @@ -100,8 +100,8 @@ def modify_compile_command(target, args): # depend on Envoy targets. if not target["file"].startswith("external/") or target["file"].startswith( "external/envoy"): - # *.h file is treated as C header by default while our headers files are all C++17. - options = "-x c++ -std=c++17 -fexceptions " + options + # *.h file is treated as C header by default while our headers files are all C++20. + options = "-x c++ -std=c++20 -fexceptions " + options target["command"] = " ".join([cc, options]) return target From 8e5b5273a254451ddaa21f42922674fc2377118c Mon Sep 17 00:00:00 2001 From: Ali Beyad Date: Mon, 4 Mar 2024 12:58:31 -0500 Subject: [PATCH 146/151] mobile: Reenable the FilterIntegrationTest.AltSvcCachedH2Slow test (#32675) After some upstream changes, the test no longer seems to flake. Tested with: bazelisk test --runs_per_test=500 --test_output=streamed --cache_test_results=no --test_arg="--gtest_filter=*FilterIntegrationTest*AltSvcCachedH2Slow*" test/extensions/filters/http/alternate_protocols_cache:filter_integration_test Fixes #32151 Signed-off-by: Ali Beyad --- .../http/alternate_protocols_cache/filter_integration_test.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/extensions/filters/http/alternate_protocols_cache/filter_integration_test.cc b/test/extensions/filters/http/alternate_protocols_cache/filter_integration_test.cc index 6994b20314ab..2f38ff058105 100644 --- a/test/extensions/filters/http/alternate_protocols_cache/filter_integration_test.cc +++ b/test/extensions/filters/http/alternate_protocols_cache/filter_integration_test.cc @@ -261,8 +261,7 @@ TEST_P(FilterIntegrationTest, AltSvcCachedH3Slow) { 100); } -// TODO(32151): Figure out why it's flaky and re-enable. -TEST_P(FilterIntegrationTest, DISABLED_AltSvcCachedH2Slow) { +TEST_P(FilterIntegrationTest, AltSvcCachedH2Slow) { #ifdef WIN32 // TODO: sort out what race only happens on windows and GCC. GTEST_SKIP() << "Skipping on Windows"; From 016254db66e70dd99327cef768d58f5e9e424ca5 Mon Sep 17 00:00:00 2001 From: Kevin Baichoo Date: Mon, 4 Mar 2024 13:09:59 -0500 Subject: [PATCH 147/151] Logging: ENVOY_BUG include filter name. (#32663) Signed-off-by: Kevin Baichoo --- source/common/http/filter_manager.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/common/http/filter_manager.cc b/source/common/http/filter_manager.cc index 15c854b14daf..187bb7b58561 100644 --- a/source/common/http/filter_manager.cc +++ b/source/common/http/filter_manager.cc @@ -604,7 +604,9 @@ void FilterManager::decodeHeaders(ActiveStreamDecoderFilter* filter, RequestHead const auto continue_iteration = (*entry)->commonHandleAfterHeadersCallback(status, end_stream); ENVOY_BUG(!continue_iteration || !state_.local_complete_, - "Filter did not return StopAll or StopIteration after sending a local reply."); + fmt::format( + "filter={} did not return StopAll or StopIteration after sending a local reply.", + (*entry)->filter_context_.config_name)); // If this filter ended the stream, decodeComplete() should be called for it. if ((*entry)->end_stream_) { From c96d0c3a838fbbdafefcaedce1695fb364931905 Mon Sep 17 00:00:00 2001 From: Tianyu <72890320+tyxia@users.noreply.github.com> Date: Mon, 4 Mar 2024 13:18:44 -0500 Subject: [PATCH 148/151] rlqs: add logging around token bucket (#32612) Signed-off-by: tyxia --- .../http/rate_limit_quota/client_impl.cc | 19 +++++++++++++------ .../filters/http/rate_limit_quota/filter.cc | 6 ++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.cc b/source/extensions/filters/http/rate_limit_quota/client_impl.cc index 62154c371de0..6dbfecacb55b 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.cc +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.cc @@ -76,11 +76,13 @@ void RateLimitClientImpl::onReceiveMessage(RateLimitQuotaResponsePtr&& response) // Get the hash id value from BucketId in the response. const size_t bucket_id = MessageUtil::hash(action.bucket_id()); + ENVOY_LOG(trace, + "Received a response for bucket id proto :\n {}, and generated " + "the associated hashed bucket id: {}", + action.bucket_id().DebugString(), bucket_id); if (quota_buckets_.find(bucket_id) == quota_buckets_.end()) { // The response should be matched to the report we sent. - ENVOY_LOG(error, - "Received a response, but but it is not matched any quota " - "cache entry: ", + ENVOY_LOG(error, "The received response is not matched to any quota cache entry: ", response->ShortDebugString()); } else { quota_buckets_[bucket_id]->bucket_action = action; @@ -98,9 +100,14 @@ void RateLimitClientImpl::onReceiveMessage(RateLimitQuotaResponsePtr&& response) double fill_rate_per_sec = static_cast(rate_limit_strategy.token_bucket().tokens_per_fill().value()) / fill_interval_sec; - - quota_buckets_[bucket_id]->token_bucket_limiter = std::make_unique( - rate_limit_strategy.token_bucket().max_tokens(), time_source_, fill_rate_per_sec); + uint32_t max_tokens = rate_limit_strategy.token_bucket().max_tokens(); + ENVOY_LOG( + trace, + "Created the token bucket limiter for hashed bucket id: {}, with max_tokens: {}; " + "fill_interval_sec: {}; fill_rate_per_sec: {}.", + bucket_id, max_tokens, fill_interval_sec, fill_rate_per_sec); + quota_buckets_[bucket_id]->token_bucket_limiter = + std::make_unique(max_tokens, time_source_, fill_rate_per_sec); } } } diff --git a/source/extensions/filters/http/rate_limit_quota/filter.cc b/source/extensions/filters/http/rate_limit_quota/filter.cc index 48dc20ee61e1..ec83101c8ea2 100644 --- a/source/extensions/filters/http/rate_limit_quota/filter.cc +++ b/source/extensions/filters/http/rate_limit_quota/filter.cc @@ -38,6 +38,8 @@ Http::FilterHeadersStatus RateLimitQuotaFilter::decodeHeaders(Http::RequestHeade BucketId bucket_id_proto = ret.value(); const size_t bucket_id = MessageUtil::hash(bucket_id_proto); + ENVOY_LOG(trace, "Generated the associated hashed bucket id: {} for bucket id proto:\n {}", + bucket_id, bucket_id_proto.DebugString()); if (quota_buckets_.find(bucket_id) == quota_buckets_.end()) { // For first matched request, create a new bucket in the cache and sent the report to RLQS // server immediately. @@ -190,9 +192,13 @@ Http::FilterHeadersStatus RateLimitQuotaFilter::processCachedBucket(size_t bucke if (limiter->consume(1, /*allow_partial=*/false)) { // Request is allowed. quota_buckets_[bucket_id]->quota_usage.num_requests_allowed += 1; + ENVOY_LOG(trace, "Request with hashed bucket_id {} is allowed by token bucket limiter.", + bucket_id); } else { // Request is throttled. quota_buckets_[bucket_id]->quota_usage.num_requests_denied += 1; + ENVOY_LOG(trace, "Request with hashed bucket_id {} is throttled by token bucket limiter", + bucket_id); // TODO(tyxia) Build the customized response based on `DenyResponseSettings` if it is // configured. callbacks_->sendLocalReply(Envoy::Http::Code::TooManyRequests, "", nullptr, absl::nullopt, From b1dced6b8cd0b2928ed3479d499132bd7cca784b Mon Sep 17 00:00:00 2001 From: botengyao Date: Mon, 4 Mar 2024 14:01:55 -0500 Subject: [PATCH 149/151] proxy status: add more mapping to proxystatus (#32606) --------- Signed-off-by: Boteng Yao --- changelogs/current.yaml | 4 ++ source/common/runtime/runtime_features.cc | 1 + source/common/stream_info/utility.cc | 47 +++++++++++++++++----- test/common/http/conn_manager_impl_test.cc | 37 +++++++++++++++++ test/common/stream_info/utility_test.cc | 45 +++++++++++++++++++++ 5 files changed, 125 insertions(+), 9 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 895c82e23d50..5e304dca7c61 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -60,6 +60,10 @@ minor_behavior_changes: change: | Enable obsolete line folding in BalsaParser (for behavior parity with http-parser, the previously used HTTP/1 parser). +- area: proxy status + change: | + Add more conversion in the proxy status utility. It can be disabled by the runtime guard + ``envoy.reloadable_features.proxy_status_mapping_more_core_response_flags``. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 23c9979c91d9..528b1e993ffa 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -75,6 +75,7 @@ RUNTIME_GUARD(envoy_reloadable_features_oauth_make_token_cookie_httponly); RUNTIME_GUARD(envoy_reloadable_features_oauth_use_standard_max_age_value); RUNTIME_GUARD(envoy_reloadable_features_oauth_use_url_encoding); RUNTIME_GUARD(envoy_reloadable_features_original_dst_rely_on_idle_timeout); +RUNTIME_GUARD(envoy_reloadable_features_proxy_status_mapping_more_core_response_flags); RUNTIME_GUARD(envoy_reloadable_features_proxy_status_upstream_request_timeout); RUNTIME_GUARD(envoy_reloadable_features_quic_fix_filter_manager_uaf); // Ignore the automated "remove this flag" issue: we should keep this for 1 year. Confirm with diff --git a/source/common/stream_info/utility.cc b/source/common/stream_info/utility.cc index 4045a45981b6..6ef029ef1cfe 100644 --- a/source/common/stream_info/utility.cc +++ b/source/common/stream_info/utility.cc @@ -440,15 +440,44 @@ ProxyStatusUtils::fromStreamInfo(const StreamInfo& stream_info) { return ProxyStatusError::ConnectionTimeout; } return ProxyStatusError::HttpResponseTimeout; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::LocalReset)) { - return ProxyStatusError::ConnectionTimeout; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamRemoteReset)) { - return ProxyStatusError::ConnectionTerminated; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionFailure)) { - return ProxyStatusError::ConnectionRefused; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionTermination)) { - return ProxyStatusError::ConnectionTerminated; - } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamOverflow)) { + } + + if (Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.proxy_status_mapping_more_core_response_flags")) { + if (stream_info.hasResponseFlag(CoreResponseFlag::DurationTimeout)) { + return ProxyStatusError::ConnectionTimeout; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::LocalReset)) { + return ProxyStatusError::ConnectionTimeout; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamRemoteReset)) { + return ProxyStatusError::ConnectionTerminated; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionFailure)) { + return ProxyStatusError::ConnectionRefused; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UnauthorizedExternalService)) { + return ProxyStatusError::ConnectionRefused; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionTermination)) { + return ProxyStatusError::ConnectionTerminated; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::OverloadManager)) { + return ProxyStatusError::ConnectionLimitReached; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::DropOverLoad)) { + return ProxyStatusError::ConnectionLimitReached; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::FaultInjected)) { + return ProxyStatusError::HttpRequestError; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::DownstreamConnectionTermination)) { + return ProxyStatusError::ConnectionTerminated; + } + } else { + if (stream_info.hasResponseFlag(CoreResponseFlag::LocalReset)) { + return ProxyStatusError::ConnectionTimeout; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamRemoteReset)) { + return ProxyStatusError::ConnectionTerminated; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionFailure)) { + return ProxyStatusError::ConnectionRefused; + } else if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamConnectionTermination)) { + return ProxyStatusError::ConnectionTerminated; + } + } + + if (stream_info.hasResponseFlag(CoreResponseFlag::UpstreamOverflow)) { return ProxyStatusError::ConnectionLimitReached; } else if (stream_info.hasResponseFlag(CoreResponseFlag::NoRouteFound)) { return ProxyStatusError::DestinationNotFound; diff --git a/test/common/http/conn_manager_impl_test.cc b/test/common/http/conn_manager_impl_test.cc index 7f9f1f897d52..bd4b458d40f1 100644 --- a/test/common/http/conn_manager_impl_test.cc +++ b/test/common/http/conn_manager_impl_test.cc @@ -4430,6 +4430,43 @@ TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetailsAndResponseCode) { EXPECT_EQ(altered_headers->getStatusValue(), "504"); } +TEST_F(ProxyStatusTest, PopulateUnauthorizedProxyStatus) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_mapping_more_core_response_flags", "true"}}); + proxy_status_config_ = std::make_unique(); + proxy_status_config_->set_remove_details(false); + + initialize(); + + const ResponseHeaderMap* altered_headers = sendRequestWith( + 403, StreamInfo::CoreResponseFlag::UnauthorizedExternalService, /*details=*/"bar"); + + ASSERT_TRUE(altered_headers); + ASSERT_TRUE(altered_headers->ProxyStatus()); + EXPECT_EQ(altered_headers->getProxyStatusValue(), + "custom_server_name; error=connection_refused; details=\"bar; UAEX\""); + EXPECT_EQ(altered_headers->getStatusValue(), "403"); +} + +TEST_F(ProxyStatusTest, NoPopulateUnauthorizedProxyStatus) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_mapping_more_core_response_flags", "false"}}); + proxy_status_config_ = std::make_unique(); + proxy_status_config_->set_remove_details(false); + + initialize(); + + const ResponseHeaderMap* altered_headers = sendRequestWith( + 403, StreamInfo::CoreResponseFlag::UnauthorizedExternalService, /*details=*/"bar"); + + ASSERT_TRUE(altered_headers); + ASSERT_FALSE(altered_headers->ProxyStatus()); + EXPECT_EQ(altered_headers->getProxyStatusValue(), ""); + EXPECT_EQ(altered_headers->getStatusValue(), "403"); +} + TEST_F(ProxyStatusTest, PopulateProxyStatusWithDetails) { TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues( diff --git a/test/common/stream_info/utility_test.cc b/test/common/stream_info/utility_test.cc index 21e6f69c6877..60ec543ab3b5 100644 --- a/test/common/stream_info/utility_test.cc +++ b/test/common/stream_info/utility_test.cc @@ -368,16 +368,61 @@ TEST(ProxyStatusFromStreamInfo, TestAll) { TestScopedRuntime scoped_runtime; scoped_runtime.mergeValues( {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_mapping_more_core_response_flags", "false"}}); + for (const auto& [response_flag, proxy_status_error] : + std::vector>{ + {CoreResponseFlag::FailedLocalHealthCheck, ProxyStatusError::DestinationUnavailable}, + {CoreResponseFlag::NoHealthyUpstream, ProxyStatusError::DestinationUnavailable}, + {CoreResponseFlag::UpstreamRequestTimeout, ProxyStatusError::HttpResponseTimeout}, + {CoreResponseFlag::LocalReset, ProxyStatusError::ConnectionTimeout}, + {CoreResponseFlag::UpstreamRemoteReset, ProxyStatusError::ConnectionTerminated}, + {CoreResponseFlag::UpstreamConnectionFailure, ProxyStatusError::ConnectionRefused}, + {CoreResponseFlag::UpstreamConnectionTermination, + ProxyStatusError::ConnectionTerminated}, + {CoreResponseFlag::UpstreamOverflow, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::NoRouteFound, ProxyStatusError::DestinationNotFound}, + {CoreResponseFlag::RateLimited, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::RateLimitServiceError, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::UpstreamRetryLimitExceeded, ProxyStatusError::DestinationUnavailable}, + {CoreResponseFlag::StreamIdleTimeout, ProxyStatusError::HttpResponseTimeout}, + {CoreResponseFlag::InvalidEnvoyRequestHeaders, ProxyStatusError::HttpRequestError}, + {CoreResponseFlag::DownstreamProtocolError, ProxyStatusError::HttpRequestError}, + {CoreResponseFlag::UpstreamMaxStreamDurationReached, + ProxyStatusError::HttpResponseTimeout}, + {CoreResponseFlag::NoFilterConfigFound, ProxyStatusError::ProxyConfigurationError}, + {CoreResponseFlag::UpstreamProtocolError, ProxyStatusError::HttpProtocolError}, + {CoreResponseFlag::NoClusterFound, ProxyStatusError::DestinationUnavailable}, + {CoreResponseFlag::DnsResolutionFailed, ProxyStatusError::DnsError}}) { + NiceMock stream_info; + ON_CALL(stream_info, hasResponseFlag(response_flag)).WillByDefault(Return(true)); + EXPECT_THAT(ProxyStatusUtils::fromStreamInfo(stream_info), proxy_status_error); + } +} + +TEST(ProxyStatusFromStreamInfo, TestNewAll) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_upstream_request_timeout", "true"}}); + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.proxy_status_mapping_more_core_response_flags", "true"}}); for (const auto& [response_flag, proxy_status_error] : std::vector>{ {CoreResponseFlag::FailedLocalHealthCheck, ProxyStatusError::DestinationUnavailable}, {CoreResponseFlag::NoHealthyUpstream, ProxyStatusError::DestinationUnavailable}, {CoreResponseFlag::UpstreamRequestTimeout, ProxyStatusError::HttpResponseTimeout}, + {CoreResponseFlag::DurationTimeout, ProxyStatusError::ConnectionTimeout}, {CoreResponseFlag::LocalReset, ProxyStatusError::ConnectionTimeout}, {CoreResponseFlag::UpstreamRemoteReset, ProxyStatusError::ConnectionTerminated}, {CoreResponseFlag::UpstreamConnectionFailure, ProxyStatusError::ConnectionRefused}, + {CoreResponseFlag::UnauthorizedExternalService, ProxyStatusError::ConnectionRefused}, {CoreResponseFlag::UpstreamConnectionTermination, ProxyStatusError::ConnectionTerminated}, + {CoreResponseFlag::DownstreamConnectionTermination, + ProxyStatusError::ConnectionTerminated}, + {CoreResponseFlag::OverloadManager, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::DropOverLoad, ProxyStatusError::ConnectionLimitReached}, + {CoreResponseFlag::FaultInjected, ProxyStatusError::HttpRequestError}, {CoreResponseFlag::UpstreamOverflow, ProxyStatusError::ConnectionLimitReached}, {CoreResponseFlag::NoRouteFound, ProxyStatusError::DestinationNotFound}, {CoreResponseFlag::RateLimited, ProxyStatusError::ConnectionLimitReached}, From 3e597f5af0e4604e0bd755e420b3ed4209e1a49c Mon Sep 17 00:00:00 2001 From: Tianyu <72890320+tyxia@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:40:13 -0500 Subject: [PATCH 150/151] rlqs: reset quota usage (#32569) --------- Signed-off-by: tyxia --- .../http/rate_limit_quota/client_impl.cc | 5 ++++ .../http/rate_limit_quota/integration_test.cc | 28 +++++++++++++------ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/source/extensions/filters/http/rate_limit_quota/client_impl.cc b/source/extensions/filters/http/rate_limit_quota/client_impl.cc index 6dbfecacb55b..33069f17e4cd 100644 --- a/source/extensions/filters/http/rate_limit_quota/client_impl.cc +++ b/source/extensions/filters/http/rate_limit_quota/client_impl.cc @@ -34,6 +34,7 @@ RateLimitQuotaUsageReports RateLimitClientImpl::buildReport(absl::optionalmutable_bucket_id() = bucket->bucket_id; usage->set_num_requests_allowed(bucket->quota_usage.num_requests_allowed); usage->set_num_requests_denied(bucket->quota_usage.num_requests_denied); + auto now = std::chrono::duration_cast( time_source_.monotonicTime().time_since_epoch()); // For the newly created bucket (i.e., `bucket_id` input is not null), its time @@ -48,6 +49,10 @@ RateLimitQuotaUsageReports RateLimitClientImpl::buildReport(absl::optionalquota_usage.last_report = now; + // Reset the number of request allowed/denied. The RLQS server expects the client to report + // those two usage numbers only for last report period. + bucket->quota_usage.num_requests_allowed = 0; + bucket->quota_usage.num_requests_denied = 0; } // Set the domain name. diff --git a/test/extensions/filters/http/rate_limit_quota/integration_test.cc b/test/extensions/filters/http/rate_limit_quota/integration_test.cc index 7317a1fdfa0b..1a81812c975d 100644 --- a/test/extensions/filters/http/rate_limit_quota/integration_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/integration_test.cc @@ -372,6 +372,16 @@ TEST_P(RateLimitQuotaIntegrationTest, BasicFlowPeriodicalReport) { // reports should be built in filter.cc envoy::service::rate_limit_quota::v3::RateLimitQuotaUsageReports reports; ASSERT_TRUE(rlqs_stream_->waitForGrpcMessage(*dispatcher_, reports)); + + // Verify the usage report content. + ASSERT_THAT(reports.bucket_quota_usages_size(), 1); + const auto& usage = reports.bucket_quota_usages(0); + // We only send single downstream client request and it is allowed. + EXPECT_EQ(usage.num_requests_allowed(), 1); + EXPECT_EQ(usage.num_requests_denied(), 0); + // It is first report so the time_elapsed is 0. + EXPECT_EQ(Protobuf::util::TimeUtil::DurationToSeconds(usage.time_elapsed()), 0); + rlqs_stream_->startGrpcStream(); // Build the response. @@ -408,14 +418,16 @@ TEST_P(RateLimitQuotaIntegrationTest, BasicFlowPeriodicalReport) { ASSERT_TRUE(rlqs_stream_->waitForGrpcMessage(*dispatcher_, reports)); // Verify the usage report content. - for (const auto& usage : reports.bucket_quota_usages()) { - // We only send single downstream client request and it is allowed. - EXPECT_EQ(usage.num_requests_allowed(), 1); - EXPECT_EQ(usage.num_requests_denied(), 0); - // time_elapsed equals to periodical reporting interval. - EXPECT_EQ(Protobuf::util::TimeUtil::DurationToSeconds(usage.time_elapsed()), - report_interval_sec); - } + ASSERT_THAT(reports.bucket_quota_usages_size(), 1); + const auto& usage = reports.bucket_quota_usages(0); + // Report only represents the usage since last report. + // In the periodical report case here, the number of request allowed and denied is 0 since no + // new requests comes in. + EXPECT_EQ(usage.num_requests_allowed(), 0); + EXPECT_EQ(usage.num_requests_denied(), 0); + // time_elapsed equals to periodical reporting interval. + EXPECT_EQ(Protobuf::util::TimeUtil::DurationToSeconds(usage.time_elapsed()), + report_interval_sec); // Build the rlqs server response. envoy::service::rate_limit_quota::v3::RateLimitQuotaResponse rlqs_response2; From e28e0d67fc9f9f677b644f940320c5c01bf057c9 Mon Sep 17 00:00:00 2001 From: alyssawilk Date: Mon, 4 Mar 2024 15:49:21 -0500 Subject: [PATCH 151/151] http3: fixing an upstream threading issue and bumping http3 upstream code back to alpha (#32640) Envoy as a community is not in the habit of moving "GA" code to alpha. It was believed at the time that HTTP/3 upstream code had been deployed successfully in production but based on the severity of the bug fixed here it appears that was not the case. Changing security status to reflect the current perceived code stability. Risk Level: low Testing: e2e test Docs Changes: inline Release Notes: inline Signed-off-by: Alyssa Wilk --- changelogs/current.yaml | 4 ++ docs/root/intro/arch_overview/http/http3.rst | 3 +- .../quic_client_transport_socket_factory.cc | 16 +++-- .../quic_client_transport_socket_factory.h | 18 +++-- test/common/http/conn_pool_grid_test.cc | 18 +++-- test/common/http/http3/conn_pool_test.cc | 2 + .../client_connection_factory_impl_test.cc | 2 + .../quic_transport_socket_factory_test.cc | 4 ++ test/integration/base_integration_test.cc | 1 + test/integration/base_integration_test.h | 1 + test/integration/http_integration.cc | 2 +- .../integration/quic_http_integration_test.cc | 71 +++++++++++++++++-- test/integration/utility.cc | 5 +- test/integration/utility.h | 8 +-- 14 files changed, 124 insertions(+), 31 deletions(-) diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 5e304dca7c61..33a8bd26ac12 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -67,6 +67,10 @@ minor_behavior_changes: bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* +- area: http3_upstream + change: | + Fixing a bug with HTTP/3 upstream using a non-threadsafe cache cross-thread. Bumping HTTP/3 support down + to alpha as the severity of this bug indicates it is both not in use and not GA quality code. - area: tracers change: | use unary RPC calls for OpenTelemetry trace exports, rather than client-side streaming connections. diff --git a/docs/root/intro/arch_overview/http/http3.rst b/docs/root/intro/arch_overview/http/http3.rst index e87cd4dc6b7b..5f574d913ca9 100644 --- a/docs/root/intro/arch_overview/http/http3.rst +++ b/docs/root/intro/arch_overview/http/http3.rst @@ -8,8 +8,7 @@ HTTP/3 overview While HTTP/3 **downstream support is deemed ready for production use**, improvements are ongoing, tracked in the `area-quic `_ tag. - HTTP/3 **upstream support is fine for locally controlled networks**, but is alpha for - general internet use - key features are implemented but have not been tested at scale. + HTTP/3 upstream support is alpha - key features are implemented but have not been tested at scale. .. _arch_overview_http3_downstream: diff --git a/source/common/quic/quic_client_transport_socket_factory.cc b/source/common/quic/quic_client_transport_socket_factory.cc index 7075c9867db6..3b90ee46143e 100644 --- a/source/common/quic/quic_client_transport_socket_factory.cc +++ b/source/common/quic/quic_client_transport_socket_factory.cc @@ -33,7 +33,10 @@ QuicClientTransportSocketFactory::QuicClientTransportSocketFactory( Server::Configuration::TransportSocketFactoryContext& factory_context) : QuicTransportSocketFactoryBase(factory_context.statsScope(), "client"), fallback_factory_(std::make_unique( - std::move(config), factory_context.sslContextManager(), factory_context.statsScope())) {} + std::move(config), factory_context.sslContextManager(), factory_context.statsScope())), + tls_slot_(factory_context.serverFactoryContext().threadLocal()) { + tls_slot_.set([](Event::Dispatcher&) { return std::make_shared(); }); +} void QuicClientTransportSocketFactory::initialize() { if (!fallback_factory_->clientContextConfig()->alpnProtocols().empty()) { @@ -55,15 +58,18 @@ std::shared_ptr QuicClientTransportSocketFactory:: return nullptr; } - if (client_context_ != context) { + ASSERT(tls_slot_.currentThreadRegistered()); + ThreadLocalQuicConfig& tls_config = *tls_slot_; + + if (tls_config.client_context_ != context) { // If the context has been updated, update the crypto config. - client_context_ = context; - crypto_config_ = std::make_shared( + tls_config.client_context_ = context; + tls_config.crypto_config_ = std::make_shared( std::make_unique(std::move(context)), std::make_unique()); } // Return the latest crypto config. - return crypto_config_; + return tls_config.crypto_config_; } REGISTER_FACTORY(QuicClientTransportSocketConfigFactory, diff --git a/source/common/quic/quic_client_transport_socket_factory.h b/source/common/quic/quic_client_transport_socket_factory.h index a9fa4c8b9c43..c6783bd78580 100644 --- a/source/common/quic/quic_client_transport_socket_factory.h +++ b/source/common/quic/quic_client_transport_socket_factory.h @@ -45,14 +45,22 @@ class QuicClientTransportSocketFactory : public Network::CommonUpstreamTransport // fallback_factory_ will update the context. void onSecretUpdated() override {} + // The cache in the QuicCryptoClientConfig is not thread-safe, so crypto_config_ needs to + // be a thread local object. client_context lets the thread local object determine if the crypto + // config needs to be updated. + struct ThreadLocalQuicConfig : public ThreadLocal::ThreadLocalObject { + // Latch the latest client context, to determine if it has updated since last + // checked. + Envoy::Ssl::ClientContextSharedPtr client_context_; + // If client_context_ changes, client config will be updated as well. + std::shared_ptr crypto_config_; + }; + private: // The QUIC client transport socket can create TLS sockets for fallback to TCP. std::unique_ptr fallback_factory_; - // Latch the latest client context, to determine if it has updated since last - // checked. - Envoy::Ssl::ClientContextSharedPtr client_context_; - // If client_context_ changes, client config will be updated as well. - std::shared_ptr crypto_config_; + // The storage for thread local quic config. + ThreadLocal::TypedSlot tls_slot_; }; class QuicClientTransportSocketConfigFactory diff --git a/test/common/http/conn_pool_grid_test.cc b/test/common/http/conn_pool_grid_test.cc index 807fe5d32edc..d9b1fff1fdf6 100644 --- a/test/common/http/conn_pool_grid_test.cc +++ b/test/common/http/conn_pool_grid_test.cc @@ -13,6 +13,7 @@ #include "test/mocks/http/stream_decoder.h" #include "test/mocks/http/stream_encoder.h" #include "test/mocks/network/connection.h" +#include "test/mocks/server/transport_socket_factory_context.h" #include "test/mocks/ssl/mocks.h" #include "test/mocks/upstream/cluster_info.h" #include "test/test_common/simulated_time_system.h" @@ -131,7 +132,10 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin options_({Http::Protocol::Http11, Http::Protocol::Http2, Http::Protocol::Http3}), alternate_protocols_(std::make_shared( dispatcher_, std::vector(), nullptr, 10)), - quic_stat_names_(store_.symbolTable()) {} + quic_stat_names_(store_.symbolTable()) { + ON_CALL(factory_context_.server_context_, threadLocal()) + .WillByDefault(ReturnRef(thread_local_)); + } void initialize() { quic_connection_persistent_info_ = @@ -194,6 +198,9 @@ class ConnectivityGridTest : public Event::TestUsingSimulatedTime, public testin StreamInfo::MockStreamInfo info_; NiceMock encoder_; + + NiceMock factory_context_; + testing::NiceMock thread_local_; }; // Test the first pool successfully connecting. @@ -945,7 +952,6 @@ TEST_F(ConnectivityGridTest, Http3FailedH2SuceedsInline) { } // namespace Http } // namespace Envoy -#include "test/mocks/server/transport_socket_factory_context.h" #include "source/common/quic/quic_client_transport_socket_factory.h" namespace Envoy { namespace Http { @@ -957,9 +963,8 @@ TEST_F(ConnectivityGridTest, RealGrid) { dispatcher_.allow_null_callback_ = true; // Set the cluster up to have a quic transport socket. Envoy::Ssl::ClientContextConfigPtr config(new NiceMock()); - NiceMock factory_context; auto factory = - std::make_unique(std::move(config), factory_context); + std::make_unique(std::move(config), factory_context_); factory->initialize(); auto& matcher = static_cast(*cluster_->transport_socket_matcher_); @@ -999,12 +1004,11 @@ TEST_F(ConnectivityGridTest, ConnectionCloseDuringAysnConnect) { dispatcher_.allow_null_callback_ = true; // Set the cluster up to have a quic transport socket. Envoy::Ssl::ClientContextConfigPtr config(new NiceMock()); - NiceMock factory_context; Ssl::ClientContextSharedPtr ssl_context(new Ssl::MockClientContext()); - EXPECT_CALL(factory_context.context_manager_, createSslClientContext(_, _)) + EXPECT_CALL(factory_context_.context_manager_, createSslClientContext(_, _)) .WillOnce(Return(ssl_context)); auto factory = - std::make_unique(std::move(config), factory_context); + std::make_unique(std::move(config), factory_context_); factory->initialize(); auto& matcher = static_cast(*cluster_->transport_socket_matcher_); diff --git a/test/common/http/http3/conn_pool_test.cc b/test/common/http/http3/conn_pool_test.cc index 75f82c9336e5..9ccccb2f37ec 100644 --- a/test/common/http/http3/conn_pool_test.cc +++ b/test/common/http/http3/conn_pool_test.cc @@ -39,6 +39,7 @@ class MockPoolConnectResultCallback : public PoolConnectResultCallback { class Http3ConnPoolImplTest : public Event::TestUsingSimulatedTime, public testing::Test { public: Http3ConnPoolImplTest() { + ON_CALL(context_.server_context_, threadLocal()).WillByDefault(ReturnRef(thread_local_)); EXPECT_CALL(context_.context_manager_, createSslClientContext(_, _)) .WillRepeatedly(Return(ssl_context_)); factory_.emplace(std::unique_ptr( @@ -86,6 +87,7 @@ class Http3ConnPoolImplTest : public Event::TestUsingSimulatedTime, public testi std::unique_ptr pool_; MockPoolConnectResultCallback connect_result_callback_; std::shared_ptr socket_option_{new Network::MockSocketOption()}; + testing::NiceMock thread_local_; }; class MockQuicClientTransportSocketFactory : public Quic::QuicClientTransportSocketFactory { diff --git a/test/common/quic/client_connection_factory_impl_test.cc b/test/common/quic/client_connection_factory_impl_test.cc index 6b8f77ae0691..50e5468a2215 100644 --- a/test/common/quic/client_connection_factory_impl_test.cc +++ b/test/common/quic/client_connection_factory_impl_test.cc @@ -27,6 +27,7 @@ class QuicNetworkConnectionTest : public Event::TestUsingSimulatedTime, public testing::TestWithParam { protected: void initialize() { + ON_CALL(context_.server_context_, threadLocal()).WillByDefault(ReturnRef(thread_local_)); EXPECT_CALL(*cluster_, perConnectionBufferLimitBytes()).WillOnce(Return(45)); EXPECT_CALL(*cluster_, connectTimeout).WillOnce(Return(std::chrono::seconds(10))); auto* protocol_options = cluster_->http3_options_.mutable_quic_protocol_options(); @@ -88,6 +89,7 @@ class QuicNetworkConnectionTest : public Event::TestUsingSimulatedTime, QuicStatNames quic_stat_names_{store_.symbolTable()}; quic::DeterministicConnectionIdGenerator connection_id_generator_{ quic::kQuicDefaultConnectionIdLength}; + testing::NiceMock thread_local_; }; TEST_P(QuicNetworkConnectionTest, BufferLimits) { diff --git a/test/common/quic/quic_transport_socket_factory_test.cc b/test/common/quic/quic_transport_socket_factory_test.cc index e2cc6998af3c..154cfb59afea 100644 --- a/test/common/quic/quic_transport_socket_factory_test.cc +++ b/test/common/quic/quic_transport_socket_factory_test.cc @@ -19,6 +19,7 @@ class QuicServerTransportSocketFactoryConfigTest : public Event::TestUsingSimula QuicServerTransportSocketFactoryConfigTest() : server_api_(Api::createApiForTest(server_stats_store_, simTime())) { ON_CALL(context_.server_context_, api()).WillByDefault(ReturnRef(*server_api_)); + ON_CALL(context_.server_context_, threadLocal()).WillByDefault(ReturnRef(thread_local_)); } void verifyQuicServerTransportSocketFactory(std::string yaml, bool expect_early_data) { @@ -35,6 +36,7 @@ class QuicServerTransportSocketFactoryConfigTest : public Event::TestUsingSimula Stats::TestUtil::TestStore server_stats_store_; Api::ApiPtr server_api_; NiceMock context_; + testing::NiceMock thread_local_; }; TEST_F(QuicServerTransportSocketFactoryConfigTest, EarlyDataEnabledByDefault) { @@ -113,6 +115,7 @@ TEST_F(QuicServerTransportSocketFactoryConfigTest, ClientAuthUnsupported) { class QuicClientTransportSocketFactoryTest : public testing::Test { public: QuicClientTransportSocketFactoryTest() { + ON_CALL(context_.server_context_, threadLocal()).WillByDefault(ReturnRef(thread_local_)); EXPECT_CALL(context_.context_manager_, createSslClientContext(_, _)).WillOnce(Return(nullptr)); EXPECT_CALL(*context_config_, setSecretUpdateCallback(_)) .WillOnce(testing::SaveArg<0>(&update_callback_)); @@ -125,6 +128,7 @@ class QuicClientTransportSocketFactoryTest : public testing::Test { NiceMock* context_config_{ new NiceMock}; std::function update_callback_; + testing::NiceMock thread_local_; }; TEST_F(QuicClientTransportSocketFactoryTest, SupportedAlpns) { diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index bfae7ee7bbe9..23b216c0d2a6 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -74,6 +74,7 @@ BaseIntegrationTest::BaseIntegrationTest(const InstanceConstSharedPtrFn& upstrea })); ON_CALL(factory_context_.server_context_, api()).WillByDefault(ReturnRef(*api_)); ON_CALL(factory_context_, statsScope()).WillByDefault(ReturnRef(*stats_store_.rootScope())); + ON_CALL(factory_context_.server_context_, threadLocal()).WillByDefault(ReturnRef(thread_local_)); #ifndef ENVOY_ADMIN_FUNCTIONALITY config_helper_.addConfigModifier( diff --git a/test/integration/base_integration_test.h b/test/integration/base_integration_test.h index c6cf68fdcb11..33f1a755567d 100644 --- a/test/integration/base_integration_test.h +++ b/test/integration/base_integration_test.h @@ -523,6 +523,7 @@ class BaseIntegrationTest : protected Logger::Loggable { Network::DownstreamTransportSocketFactoryPtr createUpstreamTlsContext(const FakeUpstreamConfig& upstream_config); + testing::NiceMock thread_local_; testing::NiceMock factory_context_; Extensions::TransportSockets::Tls::ContextManagerImpl context_manager_{timeSystem()}; diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 32cd019ca2b5..e639caa7eeca 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -368,7 +368,7 @@ void HttpIntegrationTest::initialize() { // Needs to be instantiated before base class calls initialize() which starts a QUIC listener // according to the config. quic_transport_socket_factory_ = IntegrationUtil::createQuicUpstreamTransportSocketFactory( - *api_, stats_store_, context_manager_, san_to_match_); + *api_, stats_store_, context_manager_, thread_local_, san_to_match_); // Needed to config QUIC transport socket factory, and needs to be added before base class calls // initialize(). diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index 17f3242cfb06..80c5c117e4bb 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -300,6 +300,8 @@ class QuicHttpIntegrationTestBase : public HttpIntegrationTest { ON_CALL(context.server_context_, api()).WillByDefault(testing::ReturnRef(*api_)); ON_CALL(context, statsScope()).WillByDefault(testing::ReturnRef(stats_scope_)); ON_CALL(context, sslContextManager()).WillByDefault(testing::ReturnRef(context_manager_)); + ON_CALL(context.server_context_, threadLocal()) + .WillByDefault(testing::ReturnRef(thread_local_)); envoy::extensions::transport_sockets::quic::v3::QuicUpstreamTransport quic_transport_socket_config; auto* tls_context = quic_transport_socket_config.mutable_upstream_tls_context(); @@ -330,6 +332,60 @@ class QuicHttpIntegrationTestBase : public HttpIntegrationTest { } } + void testMultipleUpstreamQuicConnections() { + // As with testMultipleQuicConnections this function may be flaky when run with + // --runs_per_test=N where N > 1 but without --jobs=1. + setConcurrency(8); + + // Avoid having to figure out which requests land on which connections by + // having one request per upstream connection. + setUpstreamProtocol(Http::CodecType::HTTP3); + envoy::config::listener::v3::QuicProtocolOptions options; + options.mutable_quic_protocol_options()->mutable_max_concurrent_streams()->set_value(1); + mergeOptions(options); + + initialize(); + std::vector codec_clients; + std::vector responses; + std::vector upstream_requests; + std::vector upstream_connections; + + // Create |concurrency| clients + size_t num_requests = concurrency_ * 2; + for (size_t i = 1; i <= concurrency_; ++i) { + // See testMultipleQuicConnections for why this should result in connection spread. + designated_connection_ids_.push_back(quic::test::TestConnectionId(i << 32)); + codec_clients.push_back(makeHttpConnection(lookupPort("http"))); + } + + // Create |num_requests| requests and wait for them to be received upstream + for (size_t i = 0; i < num_requests; ++i) { + responses.push_back( + codec_clients[i % concurrency_]->makeHeaderOnlyRequest(default_request_headers_)); + waitForNextUpstreamRequest(); + ASSERT(upstream_request_ != nullptr); + upstream_connections.push_back(std::move(fake_upstream_connection_)); + upstream_requests.push_back(std::move(upstream_request_)); + } + + // Send |num_requests| responses as fast as possible to regression test + // against a prior credentials insert race. + for (size_t i = 0; i < num_requests; ++i) { + upstream_requests[i]->encodeHeaders(default_response_headers_, true); + } + + // Wait for |num_requests| responses to complete. + for (size_t i = 0; i < num_requests; ++i) { + ASSERT_TRUE(responses[i]->waitForEndStream()); + EXPECT_TRUE(responses[i]->complete()); + } + + // Close |concurrency| clients + for (size_t i = 0; i < concurrency_; ++i) { + codec_clients[i]->close(); + } + } + void testMultipleQuicConnections() { // Enabling SO_REUSEPORT with 8 workers. Unfortunately this setting makes the test rarely flaky // if it is configured to run with --runs_per_test=N where N > 1 but without --jobs=1. @@ -373,11 +429,8 @@ class QuicHttpIntegrationTestBase : public HttpIntegrationTest { for (size_t i = 0; i < concurrency_; ++i) { fake_upstream_connection_ = nullptr; upstream_request_ = nullptr; - auto encoder_decoder = - codec_clients[i]->startRequest(Http::TestRequestHeaderMapImpl{{":method", "GET"}, - {":path", "/test/long/url"}, - {":scheme", "http"}, - {":authority", "host"}}); + auto encoder_decoder = codec_clients[i]->startRequest(default_request_headers_); + auto& request_encoder = encoder_decoder.first; auto response = std::move(encoder_decoder.second); codec_clients[i]->sendData(request_encoder, 1000, true); @@ -693,7 +746,13 @@ TEST_P(QuicHttpIntegrationTest, EarlyDataDisabled) { codec_client_->close(); } -// Ensure multiple quic connections work, regardless of platform BPF support +// Not only test multiple quic connections, but disconnect and reconnect to +// trigger resumption. +TEST_P(QuicHttpIntegrationTest, MultipleUpstreamQuicConnections) { + setUpstreamProtocol(Http::CodecType::HTTP3); + testMultipleUpstreamQuicConnections(); +} + TEST_P(QuicHttpIntegrationTest, MultipleQuicConnectionsDefaultMode) { testMultipleQuicConnections(); } diff --git a/test/integration/utility.cc b/test/integration/utility.cc index 1f7e3b75f73d..3d06c513d7fa 100644 --- a/test/integration/utility.cc +++ b/test/integration/utility.cc @@ -142,11 +142,13 @@ class TestConnectionCallbacks : public Network::ConnectionCallbacks { Network::UpstreamTransportSocketFactoryPtr IntegrationUtil::createQuicUpstreamTransportSocketFactory(Api::Api& api, Stats::Store& store, Ssl::ContextManager& context_manager, + ThreadLocal::Instance& threadlocal, const std::string& san_to_match) { NiceMock context; ON_CALL(context.server_context_, api()).WillByDefault(testing::ReturnRef(api)); ON_CALL(context, statsScope()).WillByDefault(testing::ReturnRef(*store.rootScope())); ON_CALL(context, sslContextManager()).WillByDefault(testing::ReturnRef(context_manager)); + ON_CALL(context.server_context_, threadLocal()).WillByDefault(testing::ReturnRef(threadlocal)); envoy::extensions::transport_sockets::quic::v3::QuicUpstreamTransport quic_transport_socket_config; auto* tls_context = quic_transport_socket_config.mutable_upstream_tls_context(); @@ -236,9 +238,10 @@ IntegrationUtil::makeSingleRequest(const Network::Address::InstanceConstSharedPt } #ifdef ENVOY_ENABLE_QUIC + testing::NiceMock threadlocal; Extensions::TransportSockets::Tls::ContextManagerImpl manager(time_system); Network::UpstreamTransportSocketFactoryPtr transport_socket_factory = - createQuicUpstreamTransportSocketFactory(api, mock_stats_store, manager, + createQuicUpstreamTransportSocketFactory(api, mock_stats_store, manager, threadlocal, "spiffe://lyft.com/backend-team"); auto& quic_transport_socket_factory = dynamic_cast(*transport_socket_factory); diff --git a/test/integration/utility.h b/test/integration/utility.h index 4bb330053a0e..64e4fbe63ab0 100644 --- a/test/integration/utility.h +++ b/test/integration/utility.h @@ -11,6 +11,7 @@ #include "envoy/http/header_map.h" #include "envoy/network/filter.h" #include "envoy/server/factory_context.h" +#include "envoy/thread_local/thread_local.h" #include "source/common/common/assert.h" #include "source/common/common/dump_state_utils.h" @@ -205,10 +206,9 @@ class IntegrationUtil { * Create transport socket factory for Quic upstream transport socket. * @return TransportSocketFactoryPtr the client transport socket factory. */ - static Network::UpstreamTransportSocketFactoryPtr - createQuicUpstreamTransportSocketFactory(Api::Api& api, Stats::Store& store, - Ssl::ContextManager& context_manager, - const std::string& san_to_match); + static Network::UpstreamTransportSocketFactoryPtr createQuicUpstreamTransportSocketFactory( + Api::Api& api, Stats::Store& store, Ssl::ContextManager& context_manager, + ThreadLocal::Instance& threadlocal, const std::string& san_to_match); static Http::HeaderValidatorFactoryPtr makeHeaderValidationFactory( const ::envoy::extensions::http::header_validators::envoy_default::v3::HeaderValidatorConfig&