diff --git a/.github/workflows/pr_notifier.yml b/.github/workflows/pr_notifier.yml index 941c3f7fb1fa..0cbb4bd1cb17 100644 --- a/.github/workflows/pr_notifier.yml +++ b/.github/workflows/pr_notifier.yml @@ -29,7 +29,7 @@ jobs: if [[ "${{ github.event_name }}" == 'pull_request' ]]; then ARGS+=(--dry_run) fi - bazel run //tools/repo:notify -- "${ARGS[@]}" + bazel run --config=ci //tools/repo:notify -- "${ARGS[@]}" env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/BUILD b/BUILD index e7f90fcd9ea7..903253c8601c 100644 --- a/BUILD +++ b/BUILD @@ -16,6 +16,7 @@ exports_files([ "CODEOWNERS", "OWNERS.md", ".github/config.yml", + "reviewers.yaml", ]) alias( diff --git a/api/envoy/admin/v3/server_info.proto b/api/envoy/admin/v3/server_info.proto index 58bbedf2ec29..4269d02c38e7 100644 --- a/api/envoy/admin/v3/server_info.proto +++ b/api/envoy/admin/v3/server_info.proto @@ -59,7 +59,7 @@ message ServerInfo { config.core.v3.Node node = 7; } -// [#next-free-field: 40] +// [#next-free-field: 41] message CommandLineOptions { option (udpa.annotations.versioning).previous_message_type = "envoy.admin.v2alpha.CommandLineOptions"; @@ -101,6 +101,9 @@ message CommandLineOptions { // See :option:`--skip-hot-restart-on-no-parent` for details. bool skip_hot_restart_on_no_parent = 39; + // See :option:`--skip-hot-restart-parent-stats` for details. + bool skip_hot_restart_parent_stats = 40; + // See :option:`--base-id-path` for details. string base_id_path = 32; diff --git a/api/envoy/config/core/v3/health_check.proto b/api/envoy/config/core/v3/health_check.proto index 41cde41afa3f..821f042bbe6b 100644 --- a/api/envoy/config/core/v3/health_check.proto +++ b/api/envoy/config/core/v3/health_check.proto @@ -5,6 +5,7 @@ package envoy.config.core.v3; import "envoy/config/core/v3/base.proto"; import "envoy/config/core/v3/event_service_config.proto"; import "envoy/config/core/v3/extension.proto"; +import "envoy/config/core/v3/proxy_protocol.proto"; import "envoy/type/matcher/v3/string.proto"; import "envoy/type/v3/http.proto"; import "envoy/type/v3/range.proto"; @@ -177,6 +178,13 @@ message HealthCheck { // payload block must be found, and in the order specified, but not // necessarily contiguous. repeated Payload receive = 2; + + // When setting this value, it tries to attempt health check request with ProxyProtocol. + // When ``send`` is presented, they are sent after preceding ProxyProtocol header. + // Only ProxyProtocol header is sent when ``send`` is not presented. + // It allows to use both ProxyProtocol V1 and V2. In V1, it presents L3/L4. In V2, it includes + // LOCAL command and doesn't include L3/L4. + ProxyProtocolConfig proxy_protocol_config = 3; } message RedisHealthCheck { diff --git a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto index ad27fbba0eed..f4750864c91b 100644 --- a/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto +++ b/api/envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto @@ -28,7 +28,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; // External Authorization :ref:`configuration overview `. // [#extension: envoy.filters.http.ext_authz] -// [#next-free-field: 25] +// [#next-free-field: 26] message ExtAuthz { option (udpa.annotations.versioning).previous_message_type = "envoy.config.filter.http.ext_authz.v2.ExtAuthz"; @@ -219,8 +219,17 @@ message ExtAuthz { // ` setting), // consequently the value of *Content-Length* of the authorization request reflects the size of // its payload size. + // + // .. note:: + // + // 3. This can be overridden by the field ``disallowed_headers`` below. That is, if a header + // matches for both ``allowed_headers`` and ``disallowed_headers``, the header will NOT be sent. type.matcher.v3.ListStringMatcher allowed_headers = 17; + // If set, specifically disallow any header in this list to be forwarded to the external + // authentication server. This overrides the above ``allowed_headers`` if a header matches both. + type.matcher.v3.ListStringMatcher disallowed_headers = 25; + // Specifies if the TLS session level details like SNI are sent to the external service. // // When this field is true, Envoy will include the SNI name used for TLSClientHello, if available, in the diff --git a/bazel/external/googleurl.patch b/bazel/external/googleurl.patch index 4a0d3fe02a88..692746c19231 100644 --- a/bazel/external/googleurl.patch +++ b/bazel/external/googleurl.patch @@ -29,15 +29,37 @@ diff --git a/base/containers/checked_iterators.h b/base/containers/checked_itera index dc8d2ba..9306697 100644 --- a/base/containers/checked_iterators.h +++ b/base/containers/checked_iterators.h -@@ -237,9 +237,11 @@ using CheckedContiguousConstIterator = CheckedContiguousIterator; +@@ -237,9 +237,32 @@ using CheckedContiguousConstIterator = CheckedContiguousIterator; // [3] https://wg21.link/pointer.traits.optmem - namespace std { +-namespace std { +#ifdef SUPPORTS_CPP_17_CONTIGUOUS_ITERATOR ++#if defined(_LIBCPP_VERSION) ++ ++// TODO(crbug.com/1284275): Remove when C++20 is on by default, as the use ++// of `iterator_concept` above should suffice. ++_LIBCPP_BEGIN_NAMESPACE_STD ++ ++// TODO(crbug.com/1449299): https://reviews.llvm.org/D150801 renamed this from ++// `__is_cpp17_contiguous_iterator` to `__libcpp_is_contiguous_iterator`. Clean ++// up the old spelling after libc++ rolls. ++template ++struct __is_cpp17_contiguous_iterator; template struct __is_cpp17_contiguous_iterator<::gurl_base::CheckedContiguousIterator> : true_type {}; ++template ++struct __libcpp_is_contiguous_iterator; ++template ++struct __libcpp_is_contiguous_iterator<::gurl_base::CheckedContiguousIterator> ++ : true_type {}; ++ ++_LIBCPP_END_NAMESPACE_STD ++ +#endif ++#endif ++ ++namespace std { template struct pointer_traits<::gurl_base::CheckedContiguousIterator> { diff --git a/bazel/foreign_cc/dlb.patch b/bazel/foreign_cc/dlb.patch new file mode 100644 index 000000000000..c8db5b597417 --- /dev/null +++ b/bazel/foreign_cc/dlb.patch @@ -0,0 +1,20 @@ +--- + Makefile | 2 - + 1 files changed, 0(+), 2 deletions(-) + +diff --git a/dlb/libdlb/Makefile b/dlb/libdlb/Makefile +index 0ff7c00..d2429ea 100644 +--- a/dlb/libdlb/Makefile ++++ b/dlb/libdlb/Makefile +@@ -54,8 +54,6 @@ lib: $(LIB) $(SLIB) + + $(BUILD_DIR): + @mkdir $@ +- @echo copying dlb2_user.h, please make sure the driver was built. +- @cp $(DLB2_USER_HEADER_DIR)/dlb2_user.h . + + $(BUILD_DIR)/%.o: %.c | $(BUILD_DIR) + @echo Compiling $@ +-- +2.25.1 + diff --git a/bazel/repositories.bzl b/bazel/repositories.bzl index 0e8cacef2215..8f259f7d59ab 100644 --- a/bazel/repositories.bzl +++ b/bazel/repositories.bzl @@ -1372,12 +1372,13 @@ def _intel_dlb(): build_file_content = """ filegroup( name = "libdlb", - srcs = glob([ - "dlb/libdlb/**", - ]), + srcs = glob(["dlb/libdlb/*"]), visibility = ["@envoy//contrib/dlb/source:__pkg__"], ) """, + patch_args = ["-p1"], + patches = ["@envoy//bazel/foreign_cc:dlb.patch"], + patch_cmds = ["cp dlb/driver/dlb2/uapi/linux/dlb2_user.h dlb/libdlb/"], ) def _rules_fuzzing(): diff --git a/bazel/repository_locations.bzl b/bazel/repository_locations.bzl index baa795058369..a7ba39c0857b 100644 --- a/bazel/repository_locations.bzl +++ b/bazel/repository_locations.bzl @@ -84,11 +84,11 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_desc = "Bazel rules for fuzz tests", project_url = "https://github.com/bazelbuild/rules_fuzzing", # Patch contains workaround for https://github.com/bazelbuild/rules_python/issues/1221 - version = "0.4.1", - sha256 = "f6f3f42c48576acd5653bf07637deee2ae4ebb77ccdb0dacc67c184508bedc8c", + version = "0.5.2", + sha256 = "3ec0eee05b243552cc4a784b30323d088bf73cb2177ddda02c827e68981933f1", strip_prefix = "rules_fuzzing-{version}", urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v{version}.tar.gz"], - release_date = "2023-10-19", + release_date = "2024-05-14", use_category = ["test_only"], implied_untracked_deps = [ # This is a repository rule generated to define an OSS-Fuzz fuzzing @@ -1038,9 +1038,9 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Python rules for Bazel", project_desc = "Bazel rules for the Python language", project_url = "https://github.com/bazelbuild/rules_python", - version = "0.31.0", - sha256 = "c68bdc4fbec25de5b5493b8819cfc877c4ea299c0dcb15c244c5a00208cde311", - release_date = "2024-02-13", + version = "0.32.2", + sha256 = "4912ced70dc1a2a8e4b86cec233b192ca053e82bc72d877b98e126156e8f228d", + release_date = "2024-05-14", strip_prefix = "rules_python-{version}", urls = ["https://github.com/bazelbuild/rules_python/archive/{version}.tar.gz"], use_category = ["build"], @@ -1484,12 +1484,12 @@ REPOSITORY_LOCATIONS_SPEC = dict( project_name = "Intel Dlb", project_desc = "Dlb", project_url = "https://networkbuilders.intel.com/solutionslibrary/queue-management-and-load-balancing-on-intel-architecture", - version = "8.0.0", - sha256 = "075533229bb2bd2f945ec8089a707205f3f8e8d87a8030e9208603d997236171", - urls = ["https://downloadmirror.intel.com/763709/dlb_linux_src_release8.0.0.txz"], + version = "8.8.0", + sha256 = "564534254ef32bfed56e0a464c53fca0907e446b30929c253210e2c3d6de58b9", + urls = ["https://downloadmirror.intel.com/819078/dlb_linux_src_release_8.8.0.txz"], use_category = ["dataplane_ext"], extensions = ["envoy.network.connection_balance.dlb"], - release_date = "2022-12-15", + release_date = "2023-12-15", cpe = "N/A", ), libpfm = dict( diff --git a/bazel/rules_fuzzing.patch b/bazel/rules_fuzzing.patch index 3c1efa1b77e5..a93bcd02d158 100644 --- a/bazel/rules_fuzzing.patch +++ b/bazel/rules_fuzzing.patch @@ -1,8 +1,8 @@ diff --git a/fuzzing/private/oss_fuzz/package.bzl b/fuzzing/private/oss_fuzz/package.bzl -index e5e9dc4..a3bb1b8 100644 +index 4f4e636..a1add46 100644 --- a/fuzzing/private/oss_fuzz/package.bzl +++ b/fuzzing/private/oss_fuzz/package.bzl -@@ -71,7 +71,7 @@ def _oss_fuzz_package_impl(ctx): +@@ -79,7 +79,7 @@ def _oss_fuzz_package_impl(ctx): if [[ -n "{options_path}" ]]; then ln -s "$(pwd)/{options_path}" "$STAGING_DIR/{base_name}.options" fi @@ -12,18 +12,17 @@ index e5e9dc4..a3bb1b8 100644 base_name = ctx.attr.base_name, binary_path = binary_info.binary_file.path, diff --git a/fuzzing/tools/validate_dict.py b/fuzzing/tools/validate_dict.py -index d561e68..24e3adc 100644 +index 52cbcb8..dac313a 100644 --- a/fuzzing/tools/validate_dict.py +++ b/fuzzing/tools/validate_dict.py -@@ -19,6 +19,11 @@ Validates and merges a set of fuzzing dictionary files into a single output. +@@ -22,6 +22,10 @@ from absl import flags + from fuzzing.tools.dict_validation import validate_line + from sys import stderr - from absl import app - from absl import flags -+ +import os +import sys +sys.path += [os.path.dirname(__file__)] + - from dict_validation import validate_line - from sys import stderr + FLAGS = flags.FLAGS + flags.DEFINE_list("dict_list", [], diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 0d6985e8d957..dcd787a0581f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -22,6 +22,11 @@ minor_behavior_changes: - area: tracers change: | Set status code for OpenTelemetry tracers (previously unset). +- area: config + change: | + Stricter validation of a ``google.protobuf.Duration`` field in a config, rejecting invalid values (where the number + of years is over 292). This can be temporarily reverted by setting runtime guard + ``envoy.reloadable_features.strict_duration_validation`` to ``false``. - area: xds change: | Updated xDS-TP path naming to better comply with RFC-3986. Encoded resource paths can now include an a colon ``:``, @@ -41,6 +46,12 @@ minor_behavior_changes: as successful queries with empty results, instead of as DNS failures. This change brings the getaddrinfo behavior in-line with the c-ares resolver behavior. This behavior can be reverted by setting the runtime guard ``envoy.reloadable_features.dns_nodata_noname_is_success`` to false. +- area: access_log + change: | + The upstream connection address, rather than the upstream host address, will be used for the ``%UPSTREAM_REMOTE_ADDRESS%``, + ``%UPSTREAM_REMOTE_PORT%`` and ``%UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%`` access log format specifiers. + This behavior can be reverted by setting the runtime guard + ``envoy.reloadable_features.upstream_remote_address_use_connection`` to false. bug_fixes: # *Changes expected to improve the state of the world and are unlikely to have negative effects* @@ -76,6 +87,10 @@ bug_fixes: change: | Handle ``append_action`` from :ref:`external authorization service ` that was ignored. +- area: http + change: | + Fix BalsaParser resetting state too early, guarded by default-true + ``envoy.reloadable_features.http1_balsa_delay_reset``. removed_config_or_runtime: # *Normally occurs at the end of the* :ref:`deprecation period ` @@ -102,10 +117,19 @@ removed_config_or_runtime: Removed ``envoy.reloadable_features.copy_response_code_to_downstream_stream_info`` runtime flag and legacy code paths. new_features: +- area: hot_restart + change: | + Added new command-line flag :option:`--skip-hot-restart-parent-stats`. - area: matching change: | Added :ref:`Filter State Input ` for matching http input based on filter state objects. +- area: ext_authz + change: | + Added :ref:`disallowed_headers ` + to specify headers that should never be sent to the external authentication service. Overrides + :ref:`allowed_headers ` + if a header matches both. - area: quic change: | Added support for QUIC server preferred address when there is a DNAT between the client and Envoy. See @@ -124,6 +148,14 @@ new_features: - area: redis change: | Added support for `inline commands `_. +- area: access_log + change: | + added support for :ref:`%UPSTREAM_HOST_NAME% ` for the upstream host + identifier. +- area: healthcheck + change: | + Added support to healthcheck with ProxyProtocol in TCP Healthcheck by setting + :ref:`health_check_config `. deprecated: - area: tracing diff --git a/ci/Dockerfile-envoy b/ci/Dockerfile-envoy index 4e4f2624fa15..3041b5a3bb59 100644 --- a/ci/Dockerfile-envoy +++ b/ci/Dockerfile-envoy @@ -1,5 +1,5 @@ ARG BUILD_OS=ubuntu -ARG BUILD_TAG=22.04@sha256:1b8d8ff4777f36f19bfe73ee4df61e3a0b789caeff29caa019539ec7c9a57f95 +ARG BUILD_TAG=22.04@sha256:a6d2b38300ce017add71440577d5b0a90460d0e57fd7aec21dd0d1b0761bbfb2 ARG ENVOY_VRP_BASE_IMAGE=envoy-base diff --git a/contrib/dlb/source/BUILD b/contrib/dlb/source/BUILD index c7086757a0c3..46c28a3dad18 100644 --- a/contrib/dlb/source/BUILD +++ b/contrib/dlb/source/BUILD @@ -16,7 +16,6 @@ envoy_contrib_package() make( name = "dlb", env = {"DLB_DISABLE_DOMAIN_SERVER": "TRUE"}, - includes = [], lib_source = "@intel_dlb//:libdlb", out_static_libs = ["libdlb.a"], postfix_script = "mv libdlb.a $INSTALLDIR/lib && rm -rf $INSTALLDIR/include && mkdir -p $INSTALLDIR/include && cp -L *.h $INSTALLDIR/include", diff --git a/contrib/postgres_proxy/filters/network/test/postgres_integration_test.cc b/contrib/postgres_proxy/filters/network/test/postgres_integration_test.cc index f027cfec1074..af321ddb210b 100644 --- a/contrib/postgres_proxy/filters/network/test/postgres_integration_test.cc +++ b/contrib/postgres_proxy/filters/network/test/postgres_integration_test.cc @@ -1,6 +1,7 @@ #include "source/common/network/connection_impl.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_config_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "source/extensions/filters/network/common/factory_base.h" #include "test/integration/fake_upstream.h" diff --git a/docs/root/configuration/observability/access_log/usage.rst b/docs/root/configuration/observability/access_log/usage.rst index ce9defc7c64d..7472c046e2da 100644 --- a/docs/root/configuration/observability/access_log/usage.rst +++ b/docs/root/configuration/observability/access_log/usage.rst @@ -563,8 +563,13 @@ UDP .. _config_access_log_format_upstream_host: %UPSTREAM_HOST% - Upstream host URL (e.g., tcp://ip:port for TCP connections). Identical to the :ref:`UPSTREAM_REMOTE_ADDRESS - ` value. + Main address of upstream host (e.g., ip:port for TCP connections). + +.. _config_access_log_format_upstream_host_name: + +%UPSTREAM_HOST_NAME% + Upstream host name (e.g., DNS name). If no DNS name is available, the main address of the upstream host + (e.g., ip:port for TCP connections) will be used. %UPSTREAM_CLUSTER% Upstream cluster to which the upstream host belongs to. :ref:`alt_stat_name @@ -586,7 +591,8 @@ UDP %UPSTREAM_REMOTE_ADDRESS% Remote address of the upstream connection. If the address is an IP address it includes both - address and port. Identical to the :ref:`UPSTREAM_HOST ` value. + address and port. Identical to the :ref:`UPSTREAM_HOST ` value if the upstream + host only has one address and connection is established successfully. %UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT% Remote address of the upstream connection, without any port component. diff --git a/docs/root/operations/cli.rst b/docs/root/operations/cli.rst index 51308aac4548..cd33505380dc 100644 --- a/docs/root/operations/cli.rst +++ b/docs/root/operations/cli.rst @@ -77,6 +77,15 @@ following are the command line options that Envoy supports. an unexpected parent termination after interprocess communication is established will still cause the child instance to terminate due to failing communication. +.. option:: --skip-hot-restart-parent-stats + + *(optional)* In conjunction with :option:`--restart-epoch`, this flag allows for hot restart + to proceed without duplicating stats from the parent instance. Transferring stats can be an + expensive operation; skipping it can prevent overloading the main thread with this work, or + potentially dramatically increased memory load. + + Has no effect if hot restarting is not in use. + .. option:: --base-id-path *(optional)* Writes the base ID to the given path. While this option is compatible with diff --git a/envoy/config/dynamic_extension_config_provider.h b/envoy/config/dynamic_extension_config_provider.h index a557ad5fc300..875aae268ccc 100644 --- a/envoy/config/dynamic_extension_config_provider.h +++ b/envoy/config/dynamic_extension_config_provider.h @@ -35,13 +35,14 @@ class DynamicExtensionConfigProviderBase { /** * Removes the current configuration from the provider. * @param cb the continuation callback for a completed configuration application on all threads. + * @return status indicating if the config was successfully removed. */ - virtual void onConfigRemoved(ConfigAppliedCb applied_on_all_threads) PURE; + virtual absl::Status onConfigRemoved(ConfigAppliedCb applied_on_all_threads) PURE; /** * Applies the default configuration if one is set, otherwise does nothing. */ - virtual void applyDefaultConfiguration() PURE; + virtual absl::Status applyDefaultConfiguration() PURE; /** * Return Network::ListenerFilterMatcherSharedPtr& the listener filter matcher. */ diff --git a/envoy/http/async_client.h b/envoy/http/async_client.h index 7c4a19666431..f16769c0451b 100644 --- a/envoy/http/async_client.h +++ b/envoy/http/async_client.h @@ -251,6 +251,10 @@ class AsyncClient { send_xff = v; return *this; } + StreamOptions& setSendInternal(bool v) { + send_internal = v; + return *this; + } StreamOptions& setHashPolicy( const Protobuf::RepeatedPtrField& v) { hash_policy = v; @@ -320,7 +324,7 @@ class AsyncClient { // For gmock test bool operator==(const StreamOptions& src) const { return timeout == src.timeout && buffer_body_for_retry == src.buffer_body_for_retry && - send_xff == src.send_xff; + send_xff == src.send_xff && send_internal == src.send_internal; } // The timeout supplies the stream timeout, measured since when the frame with @@ -336,6 +340,9 @@ class AsyncClient { // If true, x-forwarded-for header will be added. bool send_xff{true}; + // If true, x-envoy-internal header will be added. + bool send_internal{true}; + // Provides the hash policy for hashing load balancing strategies. Protobuf::RepeatedPtrField hash_policy; @@ -380,6 +387,10 @@ class AsyncClient { StreamOptions::setSendXff(v); return *this; } + RequestOptions& setSendInternal(bool v) { + StreamOptions::setSendInternal(v); + return *this; + } RequestOptions& setHashPolicy( const Protobuf::RepeatedPtrField& v) { StreamOptions::setHashPolicy(v); diff --git a/envoy/server/options.h b/envoy/server/options.h index 0d9ce70e2249..f8e28f91dc22 100644 --- a/envoy/server/options.h +++ b/envoy/server/options.h @@ -90,6 +90,12 @@ class Options { */ virtual bool skipHotRestartOnNoParent() const PURE; + /** + * @return bool don't get stats from the parent. If there are a lot of stats, getting them + * from the parent instance can be slow and require a lot of memory. + */ + virtual bool skipHotRestartParentStats() const PURE; + /** * @return const std::string& the dynamic base id output file. */ diff --git a/examples/ext_authz/auth/grpc-service/go.mod b/examples/ext_authz/auth/grpc-service/go.mod index b422061f389a..33e2474db3fd 100644 --- a/examples/ext_authz/auth/grpc-service/go.mod +++ b/examples/ext_authz/auth/grpc-service/go.mod @@ -5,6 +5,7 @@ go 1.14 require ( github.com/envoyproxy/go-control-plane v0.12.0 github.com/golang/protobuf v1.5.4 - google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de - google.golang.org/grpc v1.63.2 + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 + google.golang.org/grpc v1.64.0 ) diff --git a/examples/ext_authz/auth/grpc-service/go.sum b/examples/ext_authz/auth/grpc-service/go.sum index c2b245a1a5e1..72ccd6569732 100644 --- a/examples/ext_authz/auth/grpc-service/go.sum +++ b/examples/ext_authz/auth/grpc-service/go.sum @@ -45,6 +45,7 @@ cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYN 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 v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= 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= @@ -344,6 +345,7 @@ cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4h cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= @@ -1161,6 +1163,7 @@ cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjp 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/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= 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= @@ -1366,8 +1369,9 @@ github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWH 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 h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= 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= @@ -1550,6 +1554,8 @@ github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38 github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -1672,19 +1678,30 @@ 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/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 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 v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= 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/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= 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/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= 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/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= 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= @@ -1713,6 +1730,7 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq 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/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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= @@ -1846,8 +1864,9 @@ 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/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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= @@ -1885,6 +1904,7 @@ golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74Ow 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/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= 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= @@ -1996,8 +2016,9 @@ 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/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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= @@ -2017,6 +2038,7 @@ 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/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 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= @@ -2201,6 +2223,9 @@ google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7I google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= +google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= 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= @@ -2365,6 +2390,7 @@ google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRx 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/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= @@ -2393,16 +2419,22 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go. 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-20240122161410-6c6643bf1457/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= 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/bytestream v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:ZSvZ8l+AWJwXw91DoTjWjaVLpWU6o0eZ4YLYpH8aLeQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240205150955-31a09d347014/go.mod h1:EhZbXt+eY4Yr3YVaEGLdNZF5viWowOJZ8KTPqjYMKzg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:om8Bj876Z0v9ei+RD1LnEWig7vpHQ371PUqsgjmLQEA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= 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= @@ -2426,11 +2458,17 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go. 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-20240122161410-6c6643bf1457/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228201840-1f18d85a4ec2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 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= @@ -2483,8 +2521,10 @@ google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= 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= diff --git a/examples/grpc-bridge/server/go.mod b/examples/grpc-bridge/server/go.mod index 3e50c0b1581b..784d578fb1f6 100644 --- a/examples/grpc-bridge/server/go.mod +++ b/examples/grpc-bridge/server/go.mod @@ -5,5 +5,6 @@ go 1.13 require ( github.com/golang/protobuf v1.5.4 golang.org/x/net v0.25.0 - google.golang.org/grpc v1.63.2 + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/grpc v1.64.0 ) diff --git a/examples/grpc-bridge/server/go.sum b/examples/grpc-bridge/server/go.sum index f0c4ad167824..75d93af29f2c 100644 --- a/examples/grpc-bridge/server/go.sum +++ b/examples/grpc-bridge/server/go.sum @@ -45,6 +45,7 @@ cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYN 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 v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= 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= @@ -344,6 +345,7 @@ cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4h cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= @@ -1161,6 +1163,7 @@ cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjp 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/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= 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= @@ -1367,6 +1370,7 @@ github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWH 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/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= 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= @@ -1547,6 +1551,8 @@ github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38 github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -1669,19 +1675,30 @@ 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/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 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 v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= 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/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= 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/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= 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/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= 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= @@ -1710,6 +1727,7 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq 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/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 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= @@ -1845,6 +1863,7 @@ 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/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1884,6 +1903,7 @@ golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74Ow 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/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= 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= @@ -1996,6 +2016,7 @@ 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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -2017,6 +2038,7 @@ 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/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 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= @@ -2203,6 +2225,9 @@ google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7I google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= +google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= 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= @@ -2367,6 +2392,7 @@ google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRx 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/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= @@ -2395,16 +2421,22 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go. 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-20240122161410-6c6643bf1457/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= 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/bytestream v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:ZSvZ8l+AWJwXw91DoTjWjaVLpWU6o0eZ4YLYpH8aLeQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240205150955-31a09d347014/go.mod h1:EhZbXt+eY4Yr3YVaEGLdNZF5viWowOJZ8KTPqjYMKzg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:om8Bj876Z0v9ei+RD1LnEWig7vpHQ371PUqsgjmLQEA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= 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= @@ -2428,11 +2460,17 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go. 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-20240122161410-6c6643bf1457/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228201840-1f18d85a4ec2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 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= @@ -2485,8 +2523,10 @@ google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= 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= diff --git a/examples/load-reporting-service/go.mod b/examples/load-reporting-service/go.mod index 30f97e7e1561..626195b70e79 100644 --- a/examples/load-reporting-service/go.mod +++ b/examples/load-reporting-service/go.mod @@ -5,5 +5,6 @@ go 1.13 require ( github.com/envoyproxy/go-control-plane v0.12.0 github.com/golang/protobuf v1.5.4 - google.golang.org/grpc v1.63.2 + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + google.golang.org/grpc v1.64.0 ) diff --git a/examples/load-reporting-service/go.sum b/examples/load-reporting-service/go.sum index c2b245a1a5e1..72ccd6569732 100644 --- a/examples/load-reporting-service/go.sum +++ b/examples/load-reporting-service/go.sum @@ -45,6 +45,7 @@ cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYN 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 v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= 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= @@ -344,6 +345,7 @@ cloud.google.com/go/compute v1.23.2/go.mod h1:JJ0atRC0J/oWYiiVBmsSsrRnh92DhZPG4h cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI= cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZEXYonfTBHHFPO/4UU= cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= @@ -1161,6 +1163,7 @@ cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjp 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/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= 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= @@ -1366,8 +1369,9 @@ github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWH 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 h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc= +github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM= 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= @@ -1550,6 +1554,8 @@ github.com/googleapis/gax-go/v2 v2.8.0/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38 github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= +github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= @@ -1672,19 +1678,30 @@ 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/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= 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 v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= +go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= 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/metric v1.22.0/go.mod h1:evJGjVpZv0mQ5QBRJoBF64yMuOf4xCWdXjK8pzFvliY= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= 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/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= 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/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo= +go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= 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= @@ -1713,6 +1730,7 @@ golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq 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/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= 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= @@ -1846,8 +1864,9 @@ 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/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= 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= @@ -1885,6 +1904,7 @@ golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74Ow 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/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= 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= @@ -1996,8 +2016,9 @@ 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/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.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= @@ -2017,6 +2038,7 @@ 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/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= 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= @@ -2201,6 +2223,9 @@ google.golang.org/api v0.155.0/go.mod h1:GI5qK5f40kCpHfPn6+YzGAByIKWv8ujFnmoWm7I google.golang.org/api v0.157.0/go.mod h1:+z4v4ufbZ1WEpld6yMGHyggs+PmAHiaLNj5ytP3N01g= google.golang.org/api v0.160.0/go.mod h1:0mu0TpK33qnydLvWqbImq2b1eQ5FHRSDCBzAxX9ZHyw= google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYlAHs0= +google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= +google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= 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= @@ -2365,6 +2390,7 @@ google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3/go.mod h1:5RBcpGRx 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/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= @@ -2393,16 +2419,22 @@ google.golang.org/genproto/googleapis/api v0.0.0-20231212172506-995d672761c0/go. 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-20240122161410-6c6643bf1457/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014/go.mod h1:rbHMSEDyoYX62nRVLOCc4Qt1HbsdytAYoVwgjiOhF3I= google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:PVreiBMirk8ypES6aw9d4p6iiBNSIfZEBqr3UGoAi2E= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= 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/bytestream v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:ZSvZ8l+AWJwXw91DoTjWjaVLpWU6o0eZ4YLYpH8aLeQ= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:SCz6T5xjNXM4QFPRwxHcfChp7V+9DcXR3ay2TkHR8Tg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240205150955-31a09d347014/go.mod h1:EhZbXt+eY4Yr3YVaEGLdNZF5viWowOJZ8KTPqjYMKzg= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:om8Bj876Z0v9ei+RD1LnEWig7vpHQ371PUqsgjmLQEA= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:vh/N7795ftP0AkN1w8XKqN4w1OdUKXW5Eummda+ofv8= 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= @@ -2426,11 +2458,17 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0/go. 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-20240122161410-6c6643bf1457/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s= google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:YUWgXUFRPfoYK1IHMuxH5K6nPEXSCzIMljnQ59lLRCk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228201840-1f18d85a4ec2/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240304161311-37d4d3c04a78/go.mod h1:UCOku4NytXMJuLQE5VuqA5lX3PcHCBo8pxNyvkf4xBs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= 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= @@ -2483,8 +2521,10 @@ google.golang.org/grpc v1.60.0/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY= +google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= 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= diff --git a/examples/local_ratelimit/Dockerfile-nginx b/examples/local_ratelimit/Dockerfile-nginx index a25d87b260e9..143002abfe1c 100644 --- a/examples/local_ratelimit/Dockerfile-nginx +++ b/examples/local_ratelimit/Dockerfile-nginx @@ -1 +1 @@ -FROM nginx@sha256:32e76d4f34f80e479964a0fbd4c5b4f6967b5322c8d004e9cf0cb81c93510766 +FROM nginx@sha256:a484819eb60211f5299034ac80f6a681b06f89e65866ce91f356ed7c72af059c diff --git a/examples/redis/Dockerfile-redis b/examples/redis/Dockerfile-redis index e3653dd330cb..6ff32b6b64b9 100644 --- a/examples/redis/Dockerfile-redis +++ b/examples/redis/Dockerfile-redis @@ -1 +1 @@ -FROM redis@sha256:f14f42fc7e824b93c0e2fe3cdf42f68197ee0311c3d2e0235be37480b2e208e6 +FROM redis@sha256:bf2eef6365155332a8a9f86255818c8cef43f1ebb70ed0335712d596662c1510 diff --git a/examples/shared/golang/Dockerfile b/examples/shared/golang/Dockerfile index 2304caacaba6..d60f85e4464d 100644 --- a/examples/shared/golang/Dockerfile +++ b/examples/shared/golang/Dockerfile @@ -1,9 +1,9 @@ -FROM debian:bookworm-slim@sha256:155280b00ee0133250f7159b567a07d7cd03b1645714c3a7458b2287b0ca83cb as os-base +FROM debian:bookworm-slim@sha256:804194b909ef23fb995d9412c9378fb3505fe2427b70f3cc425339e48a828fca as os-base 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.3-bookworm@sha256:6d71b7c3f884e7b9552bffa852d938315ecca843dcc75a86ee7000567da0923d as golang-base +FROM golang:1.22.3-bookworm@sha256:5b4854acee80f9bacd1e6f1fe5adb2341eef70542566c2c0667ed478424e7115 as golang-base FROM golang-base as golang-control-plane-builder diff --git a/examples/shared/node/Dockerfile b/examples/shared/node/Dockerfile index d564b2b5b049..7611a1546560 100644 --- a/examples/shared/node/Dockerfile +++ b/examples/shared/node/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.1-bookworm-slim@sha256:74e7601f5070008f3cb77cd9af9e430646589f40b633ed95c010448da310b9dd as node-base +FROM node:22.1-bookworm-slim@sha256:d9911e842c571d37bf81095e842277b2bb51759df4a2901e85c53afa0378b75c as node-base FROM node-base as node-http-auth diff --git a/examples/shared/postgres/Dockerfile b/examples/shared/postgres/Dockerfile index 489e67fb857e..062281ca418e 100644 --- a/examples/shared/postgres/Dockerfile +++ b/examples/shared/postgres/Dockerfile @@ -1,3 +1,3 @@ -FROM postgres:latest@sha256:ba727f758a75cdd503c6b63db66a5fbc22ded0a228952e9d88e601621ad4de64 +FROM postgres:latest@sha256:075d8c46b50d0d72c3a519766a3a91ca184253309d2e03e944e3fe6299814262 COPY docker-healthcheck.sh /usr/local/bin/ HEALTHCHECK CMD ["docker-healthcheck.sh"] diff --git a/examples/shared/websocket/Dockerfile b/examples/shared/websocket/Dockerfile index 6461293d228a..4de8945cf483 100644 --- a/examples/shared/websocket/Dockerfile +++ b/examples/shared/websocket/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:bookworm-slim@sha256:155280b00ee0133250f7159b567a07d7cd03b1645714c3a7458b2287b0ca83cb as websocket-base +FROM debian:bookworm-slim@sha256:804194b909ef23fb995d9412c9378fb3505fe2427b70f3cc425339e48a828fca as websocket-base ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ diff --git a/examples/single-page-app/ui/package.json b/examples/single-page-app/ui/package.json index c063f790e239..dfc796717b03 100644 --- a/examples/single-page-app/ui/package.json +++ b/examples/single-page-app/ui/package.json @@ -14,7 +14,7 @@ "@chakra-ui/react": "^2.8.2", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "framer-motion": "^11.1.9", + "framer-motion": "^11.2.0", "mdi-react": "^9.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/single-page-app/ui/yarn.lock b/examples/single-page-app/ui/yarn.lock index 14d3bde186b0..ab12e2207788 100644 --- a/examples/single-page-app/ui/yarn.lock +++ b/examples/single-page-app/ui/yarn.lock @@ -2652,10 +2652,10 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -framer-motion@^11.1.9: - version "11.1.9" - resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.1.9.tgz#1ef021fc35615eb83d6baa903a47ba872be99187" - integrity sha512-flECDIPV4QDNcOrDafVFiIazp8X01HFpzc01eDKJsdNH/wrATcYydJSH9JbPWMS8UD5lZlw+J1sK8LG2kICgqw== +framer-motion@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.2.0.tgz#8eacf5e6acbe83a7777fa6179cc5fc8b47a3b853" + integrity sha512-LRfLVPEwtO9IXJCAsWvtj3XZxrdZDcTxNNkZEq30aQ8p7/wimfUkDy67TDWdtzPiyKDkqOHDhaQC6XVrQ4Fh7A== dependencies: tslib "^2.4.0" diff --git a/mobile/.bazelrc b/mobile/.bazelrc index 0ce21abe7a5f..f0b73f13596d 100644 --- a/mobile/.bazelrc +++ b/mobile/.bazelrc @@ -253,7 +253,6 @@ build:mobile-remote-ci-cc --config=mobile-remote-ci # Temporary revert to C++17 for mobile NDK builds. build:mobile-remote-ci-cc --cxxopt=-std=c++17 --host_cxxopt=-std=c++17 build:mobile-remote-ci-cc --define=envoy_full_protos=disabled -build:mobile-remote-ci-cc --build_tests_only test:mobile-remote-ci-cc --action_env=LD_LIBRARY_PATH build:mobile-remote-ci-cc-no-exceptions --config=mobile-remote-ci-cc @@ -273,7 +272,6 @@ build:mobile-remote-ci-macos-swift --@envoy//bazel:http3=False build:mobile-remote-ci-core --config=mobile-remote-ci build:mobile-remote-ci-core --define=envoy_full_protos=disabled -build:mobile-remote-ci-core --build_tests_only test:mobile-remote-ci-core --action_env=LD_LIBRARY_PATH test:mobile-remote-ci-core --test_env=ENVOY_IP_TEST_VERSIONS=v4only diff --git a/mobile/.clang-format b/mobile/.clang-format index 83c65db769f2..245c8af44adc 100644 --- a/mobile/.clang-format +++ b/mobile/.clang-format @@ -4,7 +4,7 @@ AccessModifierOffset: -2 ColumnLimit: 100 DerivePointerAlignment: false PointerAlignment: Left -SortIncludes: false +SortIncludes: Never ... --- @@ -14,7 +14,7 @@ ColumnLimit: 100 DerivePointerAlignment: false IndentWidth: 2 PointerAlignment: Left -SortIncludes: false +SortIncludes: Never ... --- @@ -27,12 +27,12 @@ ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true PointerAlignment: Right -SortIncludes: false +SortIncludes: Never ... --- Language: Proto ColumnLimit: 100 SpacesInContainerLiterals: false -AllowShortFunctionsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None ... diff --git a/mobile/bazel/BUILD b/mobile/bazel/BUILD index dbfa2d7871d4..f6a41ae7f016 100644 --- a/mobile/bazel/BUILD +++ b/mobile/bazel/BUILD @@ -32,11 +32,10 @@ alias( actual = "@bazel_tools//tools/zip:zipper", ) -# Don't depend on these directly, use //library/jni:jni_import_lib. alias( name = "jni", actual = "@bazel_tools//tools/jdk:jni", - visibility = ["//library/jni/import:__pkg__"], + visibility = ["//library/jni:__pkg__"], ) # autoformat requires that there be no @bazel_tools references outside of //bazel, diff --git a/mobile/docs/conf.py b/mobile/docs/conf.py index 9dbeba35fc13..8ffe8cfcd4bb 100644 --- a/mobile/docs/conf.py +++ b/mobile/docs/conf.py @@ -14,7 +14,6 @@ from datetime import datetime import os -import sys from sphinx.directives.code import CodeBlock import sphinx_rtd_theme diff --git a/mobile/envoy_build_config/BUILD b/mobile/envoy_build_config/BUILD index 08f786040f6e..2b4fe508b43f 100644 --- a/mobile/envoy_build_config/BUILD +++ b/mobile/envoy_build_config/BUILD @@ -43,7 +43,7 @@ envoy_cc_library( "@envoy//source/extensions/request_id/uuid:config", "@envoy//source/extensions/transport_sockets/http_11_proxy:upstream_config", "@envoy//source/extensions/transport_sockets/raw_buffer:config", - "@envoy//source/extensions/transport_sockets/tls:config", + "@envoy//source/extensions/transport_sockets/tls:upstream_config", "@envoy//source/extensions/upstreams/http/generic:config", "@envoy_mobile//library/common/extensions/cert_validator/platform_bridge:config", "@envoy_mobile//library/common/extensions/filters/http/local_error:config", diff --git a/mobile/envoy_build_config/extension_registry.cc b/mobile/envoy_build_config/extension_registry.cc index 47acf91734da..5a3da36cb196 100644 --- a/mobile/envoy_build_config/extension_registry.cc +++ b/mobile/envoy_build_config/extension_registry.cc @@ -28,7 +28,7 @@ #include "source/extensions/request_id/uuid/config.h" #include "source/extensions/transport_sockets/http_11_proxy/config.h" #include "source/extensions/transport_sockets/raw_buffer/config.h" -#include "source/extensions/transport_sockets/tls/config.h" +#include "source/extensions/transport_sockets/tls/upstream_config.h" #include "source/extensions/upstreams/http/generic/config.h" #ifdef ENVOY_MOBILE_ENABLE_LISTENER diff --git a/mobile/envoy_build_config/extensions_build_config.bzl b/mobile/envoy_build_config/extensions_build_config.bzl index a1135c44c941..00d3a08d0440 100644 --- a/mobile/envoy_build_config/extensions_build_config.bzl +++ b/mobile/envoy_build_config/extensions_build_config.bzl @@ -24,7 +24,7 @@ EXTENSIONS = { "envoy.retry.options.network_configuration": "@envoy_mobile//library/common/extensions/retry/options/network_configuration:config", "envoy.transport_sockets.http_11_proxy": "//source/extensions/transport_sockets/http_11_proxy:upstream_config", "envoy.transport_sockets.raw_buffer": "//source/extensions/transport_sockets/raw_buffer:config", - "envoy.transport_sockets.tls": "//source/extensions/transport_sockets/tls:config", + "envoy.transport_sockets.tls": "//source/extensions/transport_sockets/tls:upstream_config", "envoy.http.stateful_header_formatters.preserve_case": "//source/extensions/http/header_formatters/preserve_case:config", "envoy_mobile.cert_validator.platform_bridge_cert_validator": "@envoy_mobile//library/common/extensions/cert_validator/platform_bridge:config", "envoy.listener_manager_impl.api": "@envoy_mobile//library/common/extensions/listener_managers/api_listener_manager:api_listener_manager_lib", diff --git a/mobile/library/jni/BUILD b/mobile/library/jni/BUILD index 4fd82817d934..74d416c6638a 100644 --- a/mobile/library/jni/BUILD +++ b/mobile/library/jni/BUILD @@ -5,26 +5,6 @@ licenses(["notice"]) # Apache 2 envoy_mobile_package() -# Support for JNI, using NDK on Android and Java otherwise, since the JNI APIs -# differ a bit between the two. -envoy_cc_library( - name = "jni_support_lib", - srcs = select({ - "@envoy//bazel:android": ["ndk_jni_support.cc"], - "//conditions:default": ["java_jni_support.cc"], - }), - hdrs = ["jni_support.h"], - linkopts = select({ - "@envoy//bazel:android": ["-llog"], - "//conditions:default": [], - }), - repository = "@envoy", - deps = [ - "//library/jni/import:jni_import_lib", - ], -) - -# Various JNI helper libraries. envoy_cc_library( name = "jni_utility_lib", srcs = [ @@ -36,12 +16,9 @@ envoy_cc_library( repository = "@envoy", deps = [ ":jni_helper_lib", - ":jni_support_lib", "//library/common/types:c_types_lib", "//library/common/types:managed_types_lib", "//library/common/types:matcher_data_lib", - "//library/jni/import:jni_import_lib", - "//library/jni/types:jni_env_lib", "//library/jni/types:jni_exception_lib", "@envoy//source/common/buffer:buffer_lib", "@envoy//source/common/common:assert_lib", @@ -59,8 +36,12 @@ envoy_cc_library( "jni_helper.h", ], repository = "@envoy", - deps = [ - "//library/jni/import:jni_import_lib", + deps = select({ + "@envoy//bazel:linux": ["//bazel:jni"], + "@envoy//bazel:apple": ["//bazel:jni"], + # On Android, we should use the JNI provided by the system. + "//conditions:default": [], + }) + [ "@com_google_absl//absl/container:flat_hash_map", "@envoy//source/common/common:assert_lib", ], @@ -85,7 +66,6 @@ envoy_cc_library( "//library/common/extensions/key_value/platform:config", "//library/common/types:managed_types_lib", "//library/jni/types:jni_exception_lib", - "//library/jni/types:jni_javavm_lib", "@envoy//source/common/protobuf", ], # We need this to ensure that we link this into the .so even though there are no code references. @@ -103,13 +83,12 @@ envoy_cc_library( ], repository = "@envoy", deps = [ + ":jni_helper_lib", ":jni_utility_lib", "//library/common/bridge:utility_lib", "//library/common/extensions/cert_validator/platform_bridge:c_types_lib", "//library/common/types:c_types_lib", - "//library/jni/import:jni_import_lib", "//library/jni/types:jni_exception_lib", - "//library/jni/types:jni_javavm_lib", "@envoy//bazel:boringssl", ], ) @@ -123,11 +102,10 @@ envoy_cc_library( repository = "@envoy", deps = [ ":android_network_utility_lib", + ":jni_helper_lib", ":jni_impl_lib", - ":jni_support_lib", ":jni_utility_lib", "//library/common:internal_engine_lib", - "//library/jni/import:jni_import_lib", ], # We need this to ensure that we link this into the .so even though there are no code references. alwayslink = True, @@ -145,7 +123,6 @@ envoy_cc_library( ":android_jni_impl_lib", ":android_network_utility_lib", ":jni_impl_lib", - ":jni_support_lib", ":jni_utility_lib", ], # We need this to ensure that we link this into the .so even though there are no code references. @@ -202,10 +179,9 @@ envoy_cc_library( "@envoy//source/common/common:assert_lib", ] + select({ "@envoy//bazel:android": [ - ":jni_support_lib", + ":jni_helper_lib", ":jni_utility_lib", "//library/common/bridge:utility_lib", - "//library/jni/import:jni_import_lib", ], "//conditions:default": [], }), diff --git a/mobile/library/jni/android_jni_impl.cc b/mobile/library/jni/android_jni_impl.cc index 27dce22aac73..306db2aed68e 100644 --- a/mobile/library/jni/android_jni_impl.cc +++ b/mobile/library/jni/android_jni_impl.cc @@ -1,4 +1,3 @@ -#include "library/jni/import/jni_import.h" #include "library/jni/jni_utility.h" // NOLINT(namespace-envoy) diff --git a/mobile/library/jni/android_jni_utility.cc b/mobile/library/jni/android_jni_utility.cc index 2ca842267eac..1872088741d0 100644 --- a/mobile/library/jni/android_jni_utility.cc +++ b/mobile/library/jni/android_jni_utility.cc @@ -4,9 +4,8 @@ #if defined(__ANDROID_API__) #include "library/common/bridge/utility.h" -#include "library/jni/import/jni_import.h" -#include "library/jni/jni_support.h" #include "library/jni/jni_utility.h" +#include "library/jni/jni_helper.h" #endif namespace Envoy { @@ -14,16 +13,16 @@ namespace JNI { bool isCleartextPermitted(absl::string_view hostname) { #if defined(__ANDROID_API__) - envoy_data host = Envoy::Bridge::Utility::copyToBridgeData(hostname); - JniHelper jni_helper(getEnv()); - LocalRefUniquePtr java_host = envoyDataToJavaString(jni_helper, host); - LocalRefUniquePtr jcls_AndroidNetworkLibrary = + JniHelper jni_helper(JniHelper::getThreadLocalEnv()); + LocalRefUniquePtr java_host = cppStringToJavaString(jni_helper, std::string(hostname)); + LocalRefUniquePtr java_android_network_library_class = findClass("io.envoyproxy.envoymobile.utilities.AndroidNetworkLibrary"); - jmethodID jmid_isCleartextTrafficPermitted = jni_helper.getStaticMethodId( - jcls_AndroidNetworkLibrary.get(), "isCleartextTrafficPermitted", "(Ljava/lang/String;)Z"); + jmethodID java_is_cleartext_traffic_permitted_method_id = + jni_helper.getStaticMethodId(java_android_network_library_class.get(), + "isCleartextTrafficPermitted", "(Ljava/lang/String;)Z"); jboolean result = jni_helper.callStaticBooleanMethod( - jcls_AndroidNetworkLibrary.get(), jmid_isCleartextTrafficPermitted, java_host.get()); - release_envoy_data(host); + java_android_network_library_class.get(), java_is_cleartext_traffic_permitted_method_id, + java_host.get()); return result == JNI_TRUE; #else UNREFERENCED_PARAMETER(hostname); @@ -33,12 +32,13 @@ bool isCleartextPermitted(absl::string_view hostname) { void tagSocket(int ifd, int uid, int tag) { #if defined(__ANDROID_API__) - JniHelper jni_helper(getEnv()); - LocalRefUniquePtr jcls_AndroidNetworkLibrary = + JniHelper jni_helper(JniHelper::getThreadLocalEnv()); + LocalRefUniquePtr java_android_network_library_class = findClass("io.envoyproxy.envoymobile.utilities.AndroidNetworkLibrary"); - jmethodID jmid_tagSocket = - jni_helper.getStaticMethodId(jcls_AndroidNetworkLibrary.get(), "tagSocket", "(III)V"); - jni_helper.callStaticVoidMethod(jcls_AndroidNetworkLibrary.get(), jmid_tagSocket, ifd, uid, tag); + jmethodID java_tag_socket_method_id = + jni_helper.getStaticMethodId(java_android_network_library_class.get(), "tagSocket", "(III)V"); + jni_helper.callStaticVoidMethod(java_android_network_library_class.get(), + java_tag_socket_method_id, ifd, uid, tag); #else UNREFERENCED_PARAMETER(ifd); UNREFERENCED_PARAMETER(uid); diff --git a/mobile/library/jni/android_network_utility.cc b/mobile/library/jni/android_network_utility.cc index d9a6408ff66a..5732fa1535f0 100644 --- a/mobile/library/jni/android_network_utility.cc +++ b/mobile/library/jni/android_network_utility.cc @@ -1,10 +1,8 @@ #include "library/jni/android_network_utility.h" #include "library/common/bridge//utility.h" -#include "library/jni/jni_support.h" #include "library/jni/jni_utility.h" #include "library/jni/types/exception.h" -#include "library/jni/types/java_virtual_machine.h" #include "openssl/ssl.h" namespace Envoy { @@ -87,7 +85,7 @@ static void jvmVerifyX509CertChain(const std::vector& cert_chain, std::string auth_type, absl::string_view hostname, CertVerifyStatus* status, bool* is_issued_by_known_root, std::vector* verified_chain) { - JniHelper jni_helper(getEnv()); + JniHelper jni_helper(JniHelper::getThreadLocalEnv()); LocalRefUniquePtr result = callJvmVerifyX509CertChain(jni_helper, cert_chain, auth_type, hostname); if (Exception::checkAndClear()) { @@ -166,7 +164,7 @@ envoy_cert_validation_result verifyX509CertChain(const std::vector& } } -void jvmDetachThread() { JavaVirtualMachine::detachCurrentThread(); } +void jvmDetachThread() { JniHelper::detachCurrentThread(); } } // namespace JNI } // namespace Envoy diff --git a/mobile/library/jni/android_network_utility.h b/mobile/library/jni/android_network_utility.h index ae19a3cfa929..b86ff77081fe 100644 --- a/mobile/library/jni/android_network_utility.h +++ b/mobile/library/jni/android_network_utility.h @@ -5,7 +5,6 @@ #include "absl/strings/string_view.h" #include "library/common/extensions/cert_validator/platform_bridge/c_types.h" -#include "library/jni/import/jni_import.h" #include "library/jni/jni_helper.h" namespace Envoy { diff --git a/mobile/library/jni/import/BUILD b/mobile/library/jni/import/BUILD deleted file mode 100644 index f9225bd8d6d7..000000000000 --- a/mobile/library/jni/import/BUILD +++ /dev/null @@ -1,18 +0,0 @@ -load("@envoy//bazel:envoy_build_system.bzl", "envoy_cc_library", "envoy_mobile_package") - -licenses(["notice"]) # Apache 2 - -envoy_mobile_package() - -envoy_cc_library( - name = "jni_import_lib", - srcs = [ - "jni_import.h", - ], - repository = "@envoy", - deps = select({ - "@envoy//bazel:linux": ["//bazel:jni"], - "@envoy//bazel:apple": ["//bazel:jni"], - "//conditions:default": [], - }), -) diff --git a/mobile/library/jni/import/jni_import.h b/mobile/library/jni/import/jni_import.h deleted file mode 100644 index 73e0d8812341..000000000000 --- a/mobile/library/jni/import/jni_import.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include - -// NOLINT(namespace-envoy) - -// This validates that the jni.h header that we include is *not* the jni.h provided by the JVM. This -// helps ensure that the build is using a consistent jni.h header. -#if defined(__ANDROID_API__) && defined(_JAVASOFT_JNI_H_) -#error "JVM jni.h imported during android build" -#endif diff --git a/mobile/library/jni/java_jni_support.cc b/mobile/library/jni/java_jni_support.cc deleted file mode 100644 index 346e2cc1afbd..000000000000 --- a/mobile/library/jni/java_jni_support.cc +++ /dev/null @@ -1,7 +0,0 @@ -#include "library/jni/jni_support.h" - -// NOLINT(namespace-envoy) - -jint attach_jvm(JavaVM* vm, JNIEnv** p_env, void* thr_args) { - return vm->AttachCurrentThread(reinterpret_cast(p_env), thr_args); -} diff --git a/mobile/library/jni/jni_helper.cc b/mobile/library/jni/jni_helper.cc index fea40c4faa95..41dc4dbed982 100644 --- a/mobile/library/jni/jni_helper.cc +++ b/mobile/library/jni/jni_helper.cc @@ -4,6 +4,45 @@ namespace Envoy { namespace JNI { +namespace { + +constexpr jint JNI_VERSION = JNI_VERSION_1_6; +constexpr const char* THREAD_NAME = "EnvoyMain"; +std::atomic java_vm_cache_; +thread_local JNIEnv* jni_env_cache_ = nullptr; + +} // namespace + +jint JniHelper::getVersion() { return JNI_VERSION; } + +void JniHelper::initialize(JavaVM* java_vm) { + java_vm_cache_.store(java_vm, std::memory_order_release); +} + +JavaVM* JniHelper::getJavaVm() { return java_vm_cache_.load(std::memory_order_acquire); } + +void JniHelper::detachCurrentThread() { + ASSERT(getJavaVm()->DetachCurrentThread() == JNI_OK, "Unable to detach current thread."); +} + +JNIEnv* JniHelper::getThreadLocalEnv() { + if (jni_env_cache_ != nullptr) { + return jni_env_cache_; + } + JavaVM* java_vm = getJavaVm(); + ASSERT(java_vm != nullptr, "Unable to get JavaVM."); + jint result = java_vm->GetEnv(reinterpret_cast(&jni_env_cache_), getVersion()); + if (result == JNI_EDETACHED) { + JavaVMAttachArgs args = {getVersion(), const_cast(THREAD_NAME), nullptr}; +#if defined(__ANDROID__) + result = java_vm->AttachCurrentThread(&jni_env_cache_, &args); +#else + result = java_vm->AttachCurrentThread(reinterpret_cast(&jni_env_cache_), &args); +#endif + } + ASSERT(result == JNI_OK, "Unable to get JNIEnv."); + return jni_env_cache_; +} JNIEnv* JniHelper::getEnv() { return env_; } @@ -53,7 +92,7 @@ void JniHelper::throwNew(const char* java_class_name, const char* message) { LocalRefUniquePtr java_class = findClass(java_class_name); if (java_class != nullptr) { jint error = env_->ThrowNew(java_class.get(), message); - RELEASE_ASSERT(error == JNI_OK, fmt::format("Failed calling ThrowNew.")); + ASSERT(error == JNI_OK, fmt::format("Failed calling ThrowNew.")); } } diff --git a/mobile/library/jni/jni_helper.h b/mobile/library/jni/jni_helper.h index 8a23c1fa7506..3dd936e1571c 100644 --- a/mobile/library/jni/jni_helper.h +++ b/mobile/library/jni/jni_helper.h @@ -1,8 +1,8 @@ #pragma once -#include +#include -#include "library/jni/import/jni_import.h" +#include namespace Envoy { namespace JNI { @@ -138,6 +138,30 @@ class JniHelper { public: explicit JniHelper(JNIEnv* env) : env_(env) {} + /** Gets the JNI version supported. */ + static jint getVersion(); + + /** Initializes the `JavaVM`. This is typically set in `JNI_OnLoad`. */ + static void initialize(JavaVM* java_vm); + + /** Gets the `JavaVM`. The `initialize(JavaVM*) must be called first. */ + static JavaVM* getJavaVm(); + + /** Detaches the current thread from the `JavaVM`. */ + static void detachCurrentThread(); + + /** + * Gets the thread-local `JNIEnv`. This is useful for getting the `JNIEnv` between threads. + * If the thread-local `JNIEnv` does not exist, this function will attach the current thread to + * the JavaVM. Care must be taken to ensure `detachCurrentThread()` is called before the thread + * exists to avoid a resource leak. + * + * See https://developer.android.com/training/articles/perf-jni#threads + * + * The `initialize(JavaVM*)` must be called first or else `JNIEnv` will return a `nullptr`. + */ + static JNIEnv* getThreadLocalEnv(); + /** Gets the underlying `JNIEnv`. */ JNIEnv* getEnv(); diff --git a/mobile/library/jni/jni_impl.cc b/mobile/library/jni/jni_impl.cc index f0bfac0693e6..2ef56cff14fc 100644 --- a/mobile/library/jni/jni_impl.cc +++ b/mobile/library/jni/jni_impl.cc @@ -12,22 +12,17 @@ #include "library/common/internal_engine.h" #include "library/common/types/managed_envoy_headers.h" #include "library/jni/android_network_utility.h" -#include "library/jni/import/jni_import.h" +#include "library/jni/jni_helper.h" #include "library/jni/jni_utility.h" #include "library/jni/types/exception.h" -#include "library/jni/types/java_virtual_machine.h" using Envoy::Platform::EngineBuilder; // NOLINT(namespace-envoy) -JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { - const auto result = Envoy::JNI::JavaVirtualMachine::initialize(vm); - if (result != JNI_OK) { - return result; - } - - return Envoy::JNI::JavaVirtualMachine::getJNIVersion(); +extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { + Envoy::JNI::JniHelper::initialize(vm); + return Envoy::JNI::JniHelper::getVersion(); } extern "C" JNIEXPORT void JNICALL @@ -45,7 +40,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr if (on_engine_running != nullptr) { jobject on_engine_running_global_ref = env->NewGlobalRef(on_engine_running); callbacks->on_engine_running_ = [on_engine_running_global_ref] { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr java_on_engine_running_class = jni_helper.getObjectClass(on_engine_running_global_ref); jmethodID java_on_engine_running_method_id = jni_helper.getMethodId( @@ -59,7 +54,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr // needs to be detached is the engine thread. // This function is called from the context of the engine's // thread due to it being posted to the engine's event dispatcher. - Envoy::JNI::JavaVirtualMachine::detachCurrentThread(); + Envoy::JNI::JniHelper::detachCurrentThread(); }; } //================================================================================================ @@ -70,7 +65,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr const jobject envoy_logger_global_ref = env->NewGlobalRef(envoy_logger); logger->on_log_ = [envoy_logger_global_ref](Envoy::Logger::Logger::Levels level, const std::string& message) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr java_message = Envoy::JNI::cppStringToJavaString(jni_helper, message); jint java_level = static_cast(level); @@ -82,7 +77,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr java_message.get()); }; logger->on_exit_ = [envoy_logger_global_ref] { - Envoy::JNI::getEnv()->DeleteGlobalRef(envoy_logger_global_ref); + Envoy::JNI::JniHelper::getThreadLocalEnv()->DeleteGlobalRef(envoy_logger_global_ref); }; } //================================================================================================ @@ -94,7 +89,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr const jobject event_tracker_global_ref = env->NewGlobalRef(envoy_event_tracker); event_tracker->on_track_ = [event_tracker_global_ref]( const absl::flat_hash_map& events) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr java_events = Envoy::JNI::cppMapToJavaMap(jni_helper, events); Envoy::JNI::LocalRefUniquePtr java_envoy_event_tracker_class = @@ -104,7 +99,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibr jni_helper.callVoidMethod(event_tracker_global_ref, java_track_method_id, java_events.get()); }; event_tracker->on_exit_ = [event_tracker_global_ref] { - Envoy::JNI::getEnv()->DeleteGlobalRef(event_tracker_global_ref); + Envoy::JNI::JniHelper::getThreadLocalEnv()->DeleteGlobalRef(event_tracker_global_ref); }; } @@ -163,7 +158,7 @@ Java_io_envoyproxy_envoymobile_engine_JniLibrary_dumpStats(JNIEnv* env, static void passHeaders(const char* method, const Envoy::Types::ManagedEnvoyHeaders& headers, jobject j_context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr jcls_JvmCallbackContext = jni_helper.getObjectClass(j_context); jmethodID jmid_passHeader = @@ -200,7 +195,7 @@ static void passHeaders(const char* method, const Envoy::Types::ManagedEnvoyHead 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) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(context); passHeaders("passHeader", headers, j_context); @@ -257,7 +252,7 @@ static void jvm_on_response_headers(envoy_headers headers, bool end_stream, static envoy_filter_headers_status 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()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); const auto headers = Envoy::Types::ManagedEnvoyHeaders(input_headers); Envoy::JNI::LocalRefUniquePtr result = jvm_on_headers( "onRequestHeaders", headers, end_stream, stream_intel, const_cast(context)); @@ -271,7 +266,7 @@ jvm_http_filter_on_request_headers(envoy_headers input_headers, bool end_stream, Envoy::JNI::LocalRefUniquePtr j_headers = jni_helper.getObjectArrayElement(result.get(), 1); - int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); + int unboxed_status = Envoy::JNI::javaIntegerToCppInt(jni_helper, status.get()); envoy_headers native_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeaders(jni_helper, j_headers.get()); @@ -282,7 +277,7 @@ jvm_http_filter_on_request_headers(envoy_headers input_headers, bool end_stream, static envoy_filter_headers_status 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()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); const auto headers = Envoy::Types::ManagedEnvoyHeaders(input_headers); Envoy::JNI::LocalRefUniquePtr result = jvm_on_headers( "onResponseHeaders", headers, end_stream, stream_intel, const_cast(context)); @@ -296,7 +291,7 @@ jvm_http_filter_on_response_headers(envoy_headers input_headers, bool end_stream Envoy::JNI::LocalRefUniquePtr j_headers = jni_helper.getObjectArrayElement(result.get(), 1); - int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); + int unboxed_status = Envoy::JNI::javaIntegerToCppInt(jni_helper, status.get()); envoy_headers native_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeaders(jni_helper, j_headers.get()); @@ -308,7 +303,7 @@ static Envoy::JNI::LocalRefUniquePtr jvm_on_data(const char* metho bool end_stream, envoy_stream_intel stream_intel, void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(context); Envoy::JNI::LocalRefUniquePtr jcls_JvmCallbackContext = @@ -337,7 +332,7 @@ static void jvm_on_response_data(envoy_data data, bool end_stream, envoy_stream_ 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()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr result = jvm_on_data("onRequestData", data, end_stream, stream_intel, const_cast(context)); @@ -351,7 +346,7 @@ static envoy_filter_data_status jvm_http_filter_on_request_data(envoy_data data, Envoy::JNI::LocalRefUniquePtr j_data = jni_helper.getObjectArrayElement(result.get(), 1); - int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); + int unboxed_status = Envoy::JNI::javaIntegerToCppInt(jni_helper, status.get()); envoy_data native_data = Envoy::JNI::javaByteBufferToEnvoyData(jni_helper, j_data.get()); envoy_headers* pending_headers = nullptr; @@ -371,7 +366,7 @@ static envoy_filter_data_status jvm_http_filter_on_request_data(envoy_data data, static envoy_filter_data_status jvm_http_filter_on_response_data(envoy_data data, bool end_stream, envoy_stream_intel stream_intel, const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr result = jvm_on_data("onResponseData", data, end_stream, stream_intel, const_cast(context)); @@ -385,7 +380,7 @@ static envoy_filter_data_status jvm_http_filter_on_response_data(envoy_data data Envoy::JNI::LocalRefUniquePtr j_data = jni_helper.getObjectArrayElement(result.get(), 1); - int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); + int unboxed_status = Envoy::JNI::javaIntegerToCppInt(jni_helper, status.get()); envoy_data native_data = Envoy::JNI::javaByteBufferToEnvoyData(jni_helper, j_data.get()); envoy_headers* pending_headers = nullptr; @@ -406,7 +401,7 @@ static Envoy::JNI::LocalRefUniquePtr jvm_on_trailers(const char* m envoy_headers trailers, envoy_stream_intel stream_intel, void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(context); passHeaders("passHeader", trailers, j_context); @@ -433,7 +428,7 @@ static void jvm_on_response_trailers(envoy_headers trailers, envoy_stream_intel 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()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr result = jvm_on_trailers("onRequestTrailers", trailers, stream_intel, const_cast(context)); @@ -448,7 +443,7 @@ jvm_http_filter_on_request_trailers(envoy_headers trailers, envoy_stream_intel s Envoy::JNI::LocalRefUniquePtr j_trailers = jni_helper.getObjectArrayElement(result.get(), 1); - int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); + int unboxed_status = Envoy::JNI::javaIntegerToCppInt(jni_helper, status.get()); envoy_headers native_trailers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeaders(jni_helper, j_trailers.get()); @@ -475,7 +470,7 @@ jvm_http_filter_on_request_trailers(envoy_headers trailers, envoy_stream_intel s 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()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr result = jvm_on_trailers("onResponseTrailers", trailers, stream_intel, const_cast(context)); @@ -490,7 +485,7 @@ jvm_http_filter_on_response_trailers(envoy_headers trailers, envoy_stream_intel Envoy::JNI::LocalRefUniquePtr j_trailers = jni_helper.getObjectArrayElement(result.get(), 1); - int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); + int unboxed_status = Envoy::JNI::javaIntegerToCppInt(jni_helper, status.get()); envoy_headers native_trailers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeaders(jni_helper, j_trailers.get()); @@ -517,7 +512,7 @@ jvm_http_filter_on_response_trailers(envoy_headers trailers, envoy_stream_intel static void jvm_http_filter_set_request_callbacks(envoy_http_filter_callbacks callbacks, const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(const_cast(context)); Envoy::JNI::LocalRefUniquePtr jcls_JvmCallbackContext = jni_helper.getObjectClass(j_context); @@ -535,7 +530,7 @@ static void jvm_http_filter_set_request_callbacks(envoy_http_filter_callbacks ca static void jvm_http_filter_set_response_callbacks(envoy_http_filter_callbacks callbacks, const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(const_cast(context)); Envoy::JNI::LocalRefUniquePtr jcls_JvmCallbackContext = jni_helper.getObjectClass(j_context); @@ -554,7 +549,7 @@ static envoy_filter_resume_status jvm_http_filter_on_resume(const char* method, envoy_headers* headers, envoy_data* data, envoy_headers* trailers, bool end_stream, envoy_stream_intel stream_intel, const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(const_cast(context)); jlong headers_length = -1; if (headers) { @@ -591,7 +586,7 @@ jvm_http_filter_on_resume(const char* method, envoy_headers* headers, envoy_data Envoy::JNI::LocalRefUniquePtr j_trailers = jni_helper.getObjectArrayElement(result.get(), 3); - int unboxed_status = Envoy::JNI::javaIntegerTotInt(jni_helper, status.get()); + int unboxed_status = Envoy::JNI::javaIntegerToCppInt(jni_helper, status.get()); envoy_headers* pending_headers = Envoy::JNI::javaArrayOfObjectArrayToEnvoyHeadersPtr(jni_helper, j_headers.get()); envoy_data* pending_data = Envoy::JNI::javaByteBufferToEnvoyDataPtr(jni_helper, j_data.get()); @@ -622,7 +617,7 @@ jvm_http_filter_on_resume_response(envoy_headers* headers, envoy_data* data, static void call_jvm_on_complete(envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(context); Envoy::JNI::LocalRefUniquePtr jcls_JvmObserverContext = @@ -640,7 +635,7 @@ static void call_jvm_on_complete(envoy_stream_intel stream_intel, static void call_jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(context); Envoy::JNI::LocalRefUniquePtr jcls_JvmObserverContext = @@ -670,7 +665,7 @@ static void jvm_on_error(envoy_error error, envoy_stream_intel stream_intel, static void call_jvm_on_cancel(envoy_stream_intel stream_intel, envoy_final_stream_intel final_stream_intel, void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(context); Envoy::JNI::LocalRefUniquePtr jcls_JvmObserverContext = @@ -712,7 +707,7 @@ static void jvm_http_filter_on_cancel(envoy_stream_intel stream_intel, } static void jvm_on_send_window_available(envoy_stream_intel stream_intel, void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(context); Envoy::JNI::LocalRefUniquePtr jcls_JvmObserverContext = @@ -729,7 +724,7 @@ static void jvm_on_send_window_available(envoy_stream_intel stream_intel, void* // JvmKeyValueStoreContext static envoy_data jvm_kv_store_read(envoy_data key, const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(const_cast(context)); @@ -747,7 +742,7 @@ static envoy_data jvm_kv_store_read(envoy_data key, const void* context) { } static void jvm_kv_store_remove(envoy_data key, const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(const_cast(context)); @@ -761,7 +756,7 @@ static void jvm_kv_store_remove(envoy_data key, const void* context) { } static void jvm_kv_store_save(envoy_data key, envoy_data value, const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(const_cast(context)); @@ -779,7 +774,7 @@ static void jvm_kv_store_save(envoy_data key, envoy_data value, const void* cont // JvmFilterFactoryContext static const void* jvm_http_filter_init(const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); envoy_http_filter* c_filter = static_cast(const_cast(context)); jobject j_context = static_cast(const_cast(c_filter->static_context)); @@ -800,7 +795,7 @@ static const void* jvm_http_filter_init(const void* context) { // EnvoyStringAccessor static envoy_data jvm_get_string(const void* context) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); jobject j_context = static_cast(const_cast(context)); Envoy::JNI::LocalRefUniquePtr jcls_JvmStringAccessorContext = jni_helper.getObjectClass(j_context); @@ -1334,7 +1329,7 @@ extern "C" JNIEXPORT jint JNICALL Java_io_envoyproxy_envoymobile_engine_JniLibra } static void jvm_add_test_root_certificate(const uint8_t* cert, size_t len) { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr jcls_AndroidNetworkLibrary = Envoy::JNI::findClass("io.envoyproxy.envoymobile.utilities.AndroidNetworkLibrary"); jmethodID jmid_addTestRootCertificate = jni_helper.getStaticMethodId( @@ -1347,7 +1342,7 @@ static void jvm_add_test_root_certificate(const uint8_t* cert, size_t len) { } static void jvm_clear_test_root_certificate() { - Envoy::JNI::JniHelper jni_helper(Envoy::JNI::getEnv()); + Envoy::JNI::JniHelper jni_helper(Envoy::JNI::JniHelper::getThreadLocalEnv()); Envoy::JNI::LocalRefUniquePtr jcls_AndroidNetworkLibrary = Envoy::JNI::findClass("io.envoyproxy.envoymobile.utilities.AndroidNetworkLibrary"); jmethodID jmid_clearTestRootCertificates = jni_helper.getStaticMethodId( diff --git a/mobile/library/jni/jni_support.h b/mobile/library/jni/jni_support.h deleted file mode 100644 index a66c87648710..000000000000 --- a/mobile/library/jni/jni_support.h +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once - -#include "library/jni/import/jni_import.h" - -// NOLINT(namespace-envoy) - -extern "C" jint attach_jvm(JavaVM* vm, JNIEnv** p_env, void* thr_args); diff --git a/mobile/library/jni/jni_utility.cc b/mobile/library/jni/jni_utility.cc index d10af5772469..b3556ff3810e 100644 --- a/mobile/library/jni/jni_utility.cc +++ b/mobile/library/jni/jni_utility.cc @@ -7,8 +7,6 @@ #include "source/common/common/assert.h" #include "library/common/types/matcher_data.h" -#include "library/jni/jni_support.h" -#include "library/jni/types/env.h" namespace Envoy { namespace JNI { @@ -24,7 +22,7 @@ jobject getClassLoader() { } LocalRefUniquePtr findClass(const char* class_name) { - JniHelper jni_helper(getEnv()); + JniHelper jni_helper(JniHelper::getThreadLocalEnv()); LocalRefUniquePtr class_loader = jni_helper.findClass("java/lang/ClassLoader"); jmethodID find_class_method = jni_helper.getMethodId(class_loader.get(), "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;"); @@ -34,10 +32,8 @@ LocalRefUniquePtr findClass(const char* class_name) { return clazz; } -JNIEnv* getEnv() { return Envoy::JNI::Env::get(); } - void jniDeleteGlobalRef(void* context) { - JNIEnv* env = getEnv(); + JNIEnv* env = JniHelper::getThreadLocalEnv(); jobject ref = static_cast(context); env->DeleteGlobalRef(ref); } @@ -46,7 +42,7 @@ void jniDeleteConstGlobalRef(const void* context) { jniDeleteGlobalRef(const_cast(context)); } -int javaIntegerTotInt(JniHelper& jni_helper, jobject boxed_integer) { +int javaIntegerToCppInt(JniHelper& jni_helper, jobject boxed_integer) { LocalRefUniquePtr jcls_Integer = jni_helper.findClass("java/lang/Integer"); jmethodID jmid_intValue = jni_helper.getMethodId(jcls_Integer.get(), "intValue", "()I"); return jni_helper.callIntMethod(boxed_integer, jmid_intValue); @@ -65,12 +61,6 @@ envoy_data javaByteArrayToEnvoyData(JniHelper& jni_helper, jbyteArray j_data, si return {data_length, native_bytes, free, native_bytes}; } -LocalRefUniquePtr envoyDataToJavaString(JniHelper& jni_helper, envoy_data data) { - // Ensure we get a null-terminated string, the data coming in via envoy_data might not be. - std::string str(reinterpret_cast(data.bytes), data.length); - return jni_helper.newStringUtf(str.c_str()); -} - LocalRefUniquePtr envoyDataToJavaByteArray(JniHelper& jni_helper, envoy_data data) { LocalRefUniquePtr j_data = jni_helper.newByteArray(data.length); PrimitiveArrayCriticalUniquePtr critical_data = @@ -125,22 +115,6 @@ envoyFinalStreamIntelToJavaLongArray(JniHelper& jni_helper, return j_array; } -LocalRefUniquePtr envoyMapToJavaMap(JniHelper& jni_helper, envoy_map map) { - LocalRefUniquePtr jcls_hashMap = jni_helper.findClass("java/util/HashMap"); - jmethodID jmid_hashMapInit = jni_helper.getMethodId(jcls_hashMap.get(), "", "(I)V"); - LocalRefUniquePtr j_hashMap = - jni_helper.newObject(jcls_hashMap.get(), jmid_hashMapInit, map.length); - jmethodID jmid_hashMapPut = jni_helper.getMethodId( - jcls_hashMap.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); - for (envoy_map_size_t i = 0; i < map.length; i++) { - LocalRefUniquePtr key = envoyDataToJavaString(jni_helper, map.entries[i].key); - LocalRefUniquePtr value = envoyDataToJavaString(jni_helper, map.entries[i].value); - LocalRefUniquePtr ignored = - jni_helper.callObjectMethod(j_hashMap.get(), jmid_hashMapPut, key.get(), value.get()); - } - return j_hashMap; -} - envoy_data javaByteBufferToEnvoyData(JniHelper& jni_helper, jobject j_data) { // Returns -1 if the buffer is not a direct buffer. jlong data_length = jni_helper.getDirectBufferCapacity(j_data); @@ -547,7 +521,7 @@ Buffer::InstancePtr javaDirectByteBufferToCppBufferInstance(JniHelper& jni_helpe java_byte_buffer_address, static_cast(length), [java_byte_buffer_global_ref](const void*, size_t, const Buffer::BufferFragmentImpl* this_fragment) { - getEnv()->DeleteGlobalRef(java_byte_buffer_global_ref); + JniHelper::getThreadLocalEnv()->DeleteGlobalRef(java_byte_buffer_global_ref); delete this_fragment; }); Buffer::InstancePtr cpp_buffer_instance = std::make_unique(); diff --git a/mobile/library/jni/jni_utility.h b/mobile/library/jni/jni_utility.h index 8677f47b2a56..93614bf55e4c 100644 --- a/mobile/library/jni/jni_utility.h +++ b/mobile/library/jni/jni_utility.h @@ -11,15 +11,11 @@ #include "absl/container/flat_hash_map.h" #include "library/common/types/c_types.h" #include "library/common/types/managed_envoy_headers.h" -#include "library/jni/import/jni_import.h" #include "library/jni/jni_helper.h" namespace Envoy { namespace JNI { -// TODO(Augustyniak): Replace the usages of this global method with Envoy::JNI::Env::get() -JNIEnv* getEnv(); - void setClassLoader(jobject class_loader); /** @@ -49,7 +45,7 @@ void jniDeleteGlobalRef(void* context); void jniDeleteConstGlobalRef(const void* context); /** Converts `java.lang.Integer` to C++ `int`. */ -int javaIntegerTotInt(JniHelper& jni_helper, jobject boxed_integer); +int javaIntegerToCppInt(JniHelper& jni_helper, jobject boxed_integer); /** Converts from Java byte array to `envoy_data`. */ envoy_data javaByteArrayToEnvoyData(JniHelper& jni_helper, jbyteArray j_data); @@ -72,12 +68,6 @@ LocalRefUniquePtr envoyFinalStreamIntelToJavaLongArray(JniHelper& jni_helper, envoy_final_stream_intel final_stream_intel); -/** Converts from Java `Map` to `envoy_map`. */ -LocalRefUniquePtr envoyMapToJavaMap(JniHelper& jni_helper, envoy_map map); - -/** Converts from `envoy_data` to Java `String`. */ -LocalRefUniquePtr envoyDataToJavaString(JniHelper& jni_helper, envoy_data data); - /** Converts from Java `ByteBuffer` to `envoy_data`. */ envoy_data javaByteBufferToEnvoyData(JniHelper& jni_helper, jobject j_data); diff --git a/mobile/library/jni/ndk_jni_support.cc b/mobile/library/jni/ndk_jni_support.cc deleted file mode 100644 index 98403edbbfef..000000000000 --- a/mobile/library/jni/ndk_jni_support.cc +++ /dev/null @@ -1,7 +0,0 @@ -#include "library/jni/jni_support.h" - -// NOLINT(namespace-envoy) - -jint attach_jvm(JavaVM* vm, JNIEnv** p_env, void* thr_args) { - return vm->AttachCurrentThread(p_env, thr_args); -} diff --git a/mobile/library/jni/types/BUILD b/mobile/library/jni/types/BUILD index b8fbd4f7ebac..f98656bc7a09 100644 --- a/mobile/library/jni/types/BUILD +++ b/mobile/library/jni/types/BUILD @@ -14,8 +14,8 @@ envoy_cc_library( ], repository = "@envoy", deps = [ - "//library/jni/import:jni_import_lib", - "//library/jni/types:jni_string_lib", + ":jni_string_lib", + "//library/jni:jni_helper_lib", "@envoy//source/common/common:assert_lib", ], # We need this to ensure that we link this into the .so even though there are no code references. @@ -29,45 +29,7 @@ envoy_cc_library( ], repository = "@envoy", deps = [ - "//library/jni/import:jni_import_lib", - "//library/jni/types:jni_env_lib", - ], - # We need this to ensure that we link this into the .so even though there are no code references. - alwayslink = True, -) - -envoy_cc_library( - name = "jni_env_lib", - srcs = [ - "env.cc", - ], - hdrs = [ - "env.h", - ], - repository = "@envoy", - deps = [ - "//library/jni:jni_support_lib", - "//library/jni/import:jni_import_lib", - "//library/jni/types:jni_javavm_lib", - "@envoy//source/common/common:assert_lib", - ], - # We need this to ensure that we link this into the .so even though there are no code references. - alwayslink = True, -) - -envoy_cc_library( - name = "jni_javavm_lib", - srcs = [ - "java_virtual_machine.cc", - ], - hdrs = [ - "java_virtual_machine.h", - ], - repository = "@envoy", - deps = [ - "//library/jni:jni_support_lib", - "//library/jni/import:jni_import_lib", - "@envoy//source/common/common:assert_lib", + "//library/jni:jni_helper_lib", ], # We need this to ensure that we link this into the .so even though there are no code references. alwayslink = True, diff --git a/mobile/library/jni/types/env.cc b/mobile/library/jni/types/env.cc deleted file mode 100644 index d0591ce5a4b0..000000000000 --- a/mobile/library/jni/types/env.cc +++ /dev/null @@ -1,32 +0,0 @@ -#include "library/jni/types/env.h" - -#include "source/common/common/assert.h" - -#include "library/jni/jni_support.h" -#include "library/jni/types/java_virtual_machine.h" - -namespace Envoy { -namespace JNI { - -JNIEnv* Env::get() { - if (local_env_) { - return local_env_; - } - - jint result = JavaVirtualMachine::getJavaVM()->GetEnv(reinterpret_cast(&local_env_), - JavaVirtualMachine::getJNIVersion()); - if (result == JNI_EDETACHED) { - // Note: the only thread that should need to be attached is Envoy's engine std::thread. - static const char* thread_name = "EnvoyMain"; - JavaVMAttachArgs args = {JavaVirtualMachine::getJNIVersion(), const_cast(thread_name), - nullptr}; - result = attach_jvm(JavaVirtualMachine::getJavaVM(), &local_env_, &args); - } - RELEASE_ASSERT(result == JNI_OK, "Unable to get a JVM env for the current thread"); - return local_env_; -} - -thread_local JNIEnv* Env::local_env_ = nullptr; - -} // namespace JNI -} // namespace Envoy diff --git a/mobile/library/jni/types/env.h b/mobile/library/jni/types/env.h deleted file mode 100644 index a6f2621b1928..000000000000 --- a/mobile/library/jni/types/env.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "library/jni/import/jni_import.h" - -namespace Envoy { -namespace JNI { - -/** - * JNI Environment variable wrapper. - */ -class Env { -public: - static JNIEnv* get(); - -private: - static thread_local JNIEnv* local_env_; -}; - -} // namespace JNI -} // namespace Envoy diff --git a/mobile/library/jni/types/exception.cc b/mobile/library/jni/types/exception.cc index 5e1ecb9fc2f9..f06eed9ca0f4 100644 --- a/mobile/library/jni/types/exception.cc +++ b/mobile/library/jni/types/exception.cc @@ -3,14 +3,13 @@ #include "source/common/common/assert.h" #include "fmt/format.h" -#include "library/jni/types/env.h" #include "library/jni/types/string.h" namespace Envoy { namespace JNI { bool Exception::checkAndClear(const std::string& details) { - auto env = Env::get(); + auto env = JniHelper::getThreadLocalEnv(); if (env->ExceptionCheck() == JNI_TRUE) { jthrowable throwable = env->ExceptionOccurred(); env->ExceptionClear(); diff --git a/mobile/library/jni/types/exception.h b/mobile/library/jni/types/exception.h index f6c4c7e8d5d7..16b183725790 100644 --- a/mobile/library/jni/types/exception.h +++ b/mobile/library/jni/types/exception.h @@ -2,7 +2,7 @@ #include -#include "library/jni/import/jni_import.h" +#include "library/jni/jni_helper.h" namespace Envoy { namespace JNI { diff --git a/mobile/library/jni/types/java_virtual_machine.cc b/mobile/library/jni/types/java_virtual_machine.cc deleted file mode 100644 index b9db12d6b9d7..000000000000 --- a/mobile/library/jni/types/java_virtual_machine.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include "library/jni/types/java_virtual_machine.h" - -#include "source/common/common/assert.h" - -#include "library/jni/jni_support.h" - -namespace Envoy { -namespace JNI { - -jint JavaVirtualMachine::initialize(JavaVM* jvm) { - JNIEnv* env = nullptr; - if (jvm->GetEnv(reinterpret_cast(&env), getJNIVersion()) != JNI_OK) { - return -1; - } - - ASSERT(jvm_ == nullptr, "JavaVM has already been set"); - ASSERT(jvm != nullptr, "Passed JavaVM is invalid"); - jvm_ = jvm; - - return JNI_OK; -} - -JavaVM* JavaVirtualMachine::getJavaVM() { return jvm_; } - -jint JavaVirtualMachine::getJNIVersion() { return JNI_VERSION_1_6; } - -void JavaVirtualMachine::detachCurrentThread() { - const auto result = jvm_->DetachCurrentThread(); - ASSERT(result == JNI_OK, "Failed to detach current thread"); -} - -JavaVM* JavaVirtualMachine::jvm_ = nullptr; - -} // namespace JNI -} // namespace Envoy diff --git a/mobile/library/jni/types/java_virtual_machine.h b/mobile/library/jni/types/java_virtual_machine.h deleted file mode 100644 index fb36be6285ef..000000000000 --- a/mobile/library/jni/types/java_virtual_machine.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "library/jni/import/jni_import.h" - -namespace Envoy { -namespace JNI { - -/** - * @brief A convenience wrapper for JNI JavaVM type. - * - */ -class JavaVirtualMachine { -public: - /** - * @brief Initializes virtual machine. It should be called only once. - * - * @param jvm A pointer to running JavaVM instance. - * @return jint JNI_OK if the operation was successful, error code information otherwise. - */ - static jint initialize(JavaVM* jvm); - static JavaVM* getJavaVM(); - static jint getJNIVersion(); - static void detachCurrentThread(); - -private: - static JavaVM* jvm_; -}; - -} // namespace JNI -} // namespace Envoy diff --git a/mobile/library/jni/types/string.h b/mobile/library/jni/types/string.h index f444ce65dfae..1ef98a61c07f 100644 --- a/mobile/library/jni/types/string.h +++ b/mobile/library/jni/types/string.h @@ -1,8 +1,6 @@ #pragma once -#include "absl/strings/string_view.h" -#include "library/jni/import/jni_import.h" -#include "library/jni/types/env.h" +#include "library/jni/jni_helper.h" namespace Envoy { namespace JNI { @@ -20,7 +18,7 @@ class String { * the string in modified UTF-8 encoding. */ explicit String(jstring jni_string) - : env_(Env::get()), jni_string_(jni_string), + : env_(JniHelper::getThreadLocalEnv()), jni_string_(jni_string), string_(env_->GetStringUTFChars(jni_string, nullptr)) {} ~String() { diff --git a/mobile/test/cc/integration/BUILD b/mobile/test/cc/integration/BUILD index 8c7e2b16c4da..9ddbb45a0d50 100644 --- a/mobile/test/cc/integration/BUILD +++ b/mobile/test/cc/integration/BUILD @@ -46,6 +46,48 @@ envoy_cc_test( ], ) +envoy_cc_test( + name = "receive_headers_test", + srcs = ["receive_headers_test.cc"], + repository = "@envoy", + deps = [ + "//library/cc:engine_builder_lib", + "//library/common:engine_types_lib", + "//library/common/http:header_utility_lib", + "//test/common/integration:engine_with_test_server", + "//test/common/integration:test_server_lib", + "@envoy_build_config//:test_extensions", + ], +) + +envoy_cc_test( + name = "receive_data_test", + srcs = ["receive_data_test.cc"], + repository = "@envoy", + deps = [ + "//library/cc:engine_builder_lib", + "//library/common:engine_types_lib", + "//library/common/http:header_utility_lib", + "//test/common/integration:engine_with_test_server", + "//test/common/integration:test_server_lib", + "@envoy_build_config//:test_extensions", + ], +) + +envoy_cc_test( + name = "receive_trailers_test", + srcs = ["receive_trailers_test.cc"], + repository = "@envoy", + deps = [ + "//library/cc:engine_builder_lib", + "//library/common:engine_types_lib", + "//library/common/http:header_utility_lib", + "//test/common/integration:engine_with_test_server", + "//test/common/integration:test_server_lib", + "@envoy_build_config//:test_extensions", + ], +) + envoy_cc_test( name = "lifetimes_test", srcs = ["lifetimes_test.cc"], diff --git a/mobile/test/cc/integration/receive_data_test.cc b/mobile/test/cc/integration/receive_data_test.cc new file mode 100644 index 000000000000..e5d9499e94d6 --- /dev/null +++ b/mobile/test/cc/integration/receive_data_test.cc @@ -0,0 +1,67 @@ +#include "test/common/integration/engine_with_test_server.h" +#include "test/common/integration/test_server.h" + +#include "absl/synchronization/notification.h" +#include "gtest/gtest.h" +#include "library/cc/engine_builder.h" +#include "library/common/engine_types.h" +#include "library/common/http/header_utility.h" + +namespace Envoy { + +TEST(ReceiveDataTest, Success) { + absl::Notification engine_running; + Platform::EngineBuilder engine_builder; + engine_builder.enforceTrustChainVerification(false) + .setLogLevel(Logger::Logger::debug) + .setOnEngineRunning([&]() { engine_running.Notify(); }); + EngineWithTestServer engine_with_test_server(engine_builder, TestServerType::HTTP2_WITH_TLS, + /* headers= */ {}, /* body= */ "hello world", + /* trailers= */ {}); + engine_running.WaitForNotification(); + + bool on_data_was_called = false; + std::string actual_status_code; + bool actual_end_stream; + std::string actual_response_body; + absl::Notification stream_complete; + EnvoyStreamCallbacks stream_callbacks; + stream_callbacks.on_headers_ = [&](const Http::ResponseHeaderMap& headers, bool end_stream, + envoy_stream_intel) { + actual_status_code = headers.getStatusValue(); + actual_end_stream = end_stream; + }; + stream_callbacks.on_data_ = [&](const Buffer::Instance& buffer, uint64_t length, bool end_stream, + envoy_stream_intel) { + on_data_was_called = true; + actual_end_stream = end_stream; + actual_response_body += absl::string_view(buffer.toString().data(), length); + }; + stream_callbacks.on_complete_ = [&](envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + stream_callbacks.on_error_ = [&](EnvoyError, envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + stream_callbacks.on_cancel_ = [&](envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + auto stream_prototype = engine_with_test_server.engine()->streamClient()->newStreamPrototype(); + Platform::StreamSharedPtr stream = stream_prototype->start(std::move(stream_callbacks)); + + auto headers = Http::Utility::createRequestHeaderMapPtr(); + headers->addCopy(Http::LowerCaseString(":method"), "GET"); + headers->addCopy(Http::LowerCaseString(":scheme"), "https"); + headers->addCopy(Http::LowerCaseString(":authority"), + engine_with_test_server.testServer().getAddress()); + headers->addCopy(Http::LowerCaseString(":path"), "/"); + stream->sendHeaders(std::move(headers), true); + stream_complete.WaitForNotification(); + + EXPECT_TRUE(on_data_was_called); + EXPECT_EQ(actual_status_code, "200"); + EXPECT_TRUE(actual_end_stream); + EXPECT_EQ(actual_response_body, "hello world"); +} + +} // namespace Envoy diff --git a/mobile/test/cc/integration/receive_headers_test.cc b/mobile/test/cc/integration/receive_headers_test.cc new file mode 100644 index 000000000000..6c635da9f956 --- /dev/null +++ b/mobile/test/cc/integration/receive_headers_test.cc @@ -0,0 +1,64 @@ +#include "test/common/integration/engine_with_test_server.h" +#include "test/common/integration/test_server.h" + +#include "absl/synchronization/notification.h" +#include "gtest/gtest.h" +#include "library/cc/engine_builder.h" +#include "library/common/engine_types.h" +#include "library/common/http/header_utility.h" + +namespace Envoy { + +TEST(ReceiveHeadersTest, Success) { + absl::Notification engine_running; + Platform::EngineBuilder engine_builder; + engine_builder.enforceTrustChainVerification(false) + .setLogLevel(Logger::Logger::debug) + .setOnEngineRunning([&]() { engine_running.Notify(); }); + EngineWithTestServer engine_with_test_server(engine_builder, TestServerType::HTTP2_WITH_TLS, + {{"foo", "bar"}}); + engine_running.WaitForNotification(); + + bool on_headers_was_called = false; + std::string actual_status_code; + bool actual_end_stream; + absl::Notification stream_complete; + EnvoyStreamCallbacks stream_callbacks; + stream_callbacks.on_headers_ = [&](const Http::ResponseHeaderMap& headers, bool end_stream, + envoy_stream_intel) { + on_headers_was_called = true; + actual_status_code = headers.getStatusValue(); + EXPECT_EQ("bar", headers.get(Http::LowerCaseString("foo"))[0]->value().getStringView()); + actual_end_stream = end_stream; + }; + stream_callbacks.on_data_ = [&](const Buffer::Instance&, uint64_t /* length */, bool end_stream, + envoy_stream_intel) { actual_end_stream = end_stream; }; + stream_callbacks.on_complete_ = [&](envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + stream_callbacks.on_error_ = [&](EnvoyError, envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + stream_callbacks.on_cancel_ = [&](envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + auto stream_prototype = engine_with_test_server.engine()->streamClient()->newStreamPrototype(); + Platform::StreamSharedPtr stream = stream_prototype->start(std::move(stream_callbacks)); + + auto headers = Http::Utility::createRequestHeaderMapPtr(); + headers->addCopy(Http::LowerCaseString(":method"), "GET"); + headers->addCopy(Http::LowerCaseString(":scheme"), "https"); + headers->addCopy(Http::LowerCaseString(":authority"), + engine_with_test_server.testServer().getAddress()); + headers->addCopy(Http::LowerCaseString(":path"), "/"); + stream->sendHeaders(std::move(headers), true); + stream->sendData(std::make_unique("request body")); + stream->close(std::make_unique("request body")); + stream_complete.WaitForNotification(); + + EXPECT_TRUE(on_headers_was_called); + EXPECT_EQ(actual_status_code, "200"); + EXPECT_TRUE(actual_end_stream); +} + +} // namespace Envoy diff --git a/mobile/test/cc/integration/receive_trailers_test.cc b/mobile/test/cc/integration/receive_trailers_test.cc new file mode 100644 index 000000000000..1519cc53d748 --- /dev/null +++ b/mobile/test/cc/integration/receive_trailers_test.cc @@ -0,0 +1,62 @@ +#include "test/common/integration/engine_with_test_server.h" +#include "test/common/integration/test_server.h" + +#include "absl/synchronization/notification.h" +#include "gtest/gtest.h" +#include "library/cc/engine_builder.h" +#include "library/common/engine_types.h" +#include "library/common/http/header_utility.h" + +namespace Envoy { + +TEST(ReceiveTrailersTest, Success) { + absl::Notification engine_running; + Platform::EngineBuilder engine_builder; + engine_builder.enforceTrustChainVerification(false) + .setLogLevel(Logger::Logger::debug) + .setOnEngineRunning([&]() { engine_running.Notify(); }); + EngineWithTestServer engine_with_test_server(engine_builder, TestServerType::HTTP2_WITH_TLS, + /* headers= */ {}, + /* body= */ "", /* trailers= */ {{"foo", "bar"}}); + engine_running.WaitForNotification(); + + bool on_trailers_was_called = false; + std::string actual_status_code; + absl::Notification stream_complete; + EnvoyStreamCallbacks stream_callbacks; + stream_callbacks.on_headers_ = [&](const Http::ResponseHeaderMap& headers, bool /* end_stream */, + envoy_stream_intel) { + actual_status_code = headers.getStatusValue(); + }; + stream_callbacks.on_trailers_ = [&](const Http::ResponseTrailerMap& trailers, + envoy_stream_intel) { + on_trailers_was_called = true; + EXPECT_EQ("bar", trailers.get(Http::LowerCaseString("foo"))[0]->value().getStringView()); + }; + stream_callbacks.on_complete_ = [&](envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + stream_callbacks.on_error_ = [&](EnvoyError, envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + stream_callbacks.on_cancel_ = [&](envoy_stream_intel, envoy_final_stream_intel) { + stream_complete.Notify(); + }; + auto stream_prototype = engine_with_test_server.engine()->streamClient()->newStreamPrototype(); + Platform::StreamSharedPtr stream = stream_prototype->start(std::move(stream_callbacks)); + + auto headers = Http::Utility::createRequestHeaderMapPtr(); + headers->addCopy(Http::LowerCaseString(":method"), "GET"); + headers->addCopy(Http::LowerCaseString(":scheme"), "https"); + headers->addCopy(Http::LowerCaseString(":authority"), + engine_with_test_server.testServer().getAddress()); + headers->addCopy(Http::LowerCaseString(":path"), "/"); + stream->sendHeaders(std::move(headers), true); + + stream_complete.WaitForNotification(); + + EXPECT_TRUE(on_trailers_was_called); + EXPECT_EQ(actual_status_code, "200"); +} + +} // namespace Envoy diff --git a/mobile/test/cc/integration/send_trailers_test.cc b/mobile/test/cc/integration/send_trailers_test.cc index 07a3d3a5ccec..263c817abd46 100644 --- a/mobile/test/cc/integration/send_trailers_test.cc +++ b/mobile/test/cc/integration/send_trailers_test.cc @@ -28,11 +28,11 @@ TEST(SendTrailersTest, Success) { .addNativeFilter("envoy.filters.http.assertion", std::string(ASSERTION_FILTER_TEXT_PROTO)) #endif .setOnEngineRunning([&]() { engine_running.Notify(); }); - EngineWithTestServer engine_with_test_server(engine_builder, TestServerType::HTTP2_WITH_TLS); + EngineWithTestServer engine_with_test_server(engine_builder, TestServerType::HTTP2_WITH_TLS, + /* headers= */ {}, /* body= */ "body", + /* trailers= */ {{"trailer-key", "trailer-value"}}); engine_running.WaitForNotification(); - engine_with_test_server.testServer().setResponse({}, "body", {{"trailer-key", "trailer-value"}}); - std::string actual_status_code; std::string actual_trailer_value; absl::Notification stream_complete; diff --git a/mobile/test/common/integration/client_integration_test.cc b/mobile/test/common/integration/client_integration_test.cc index d9ee13837f08..55d80ca880d0 100644 --- a/mobile/test/common/integration/client_integration_test.cc +++ b/mobile/test/common/integration/client_integration_test.cc @@ -250,6 +250,7 @@ TEST_P(ClientIntegrationTest, Basic) { } } +#if not defined(__APPLE__) TEST_P(ClientIntegrationTest, BasicWithCares) { builder_.setUseCares(true); initialize(); @@ -258,6 +259,7 @@ TEST_P(ClientIntegrationTest, BasicWithCares) { ASSERT_EQ(cc_.on_complete_received_byte_count_, 67); } } +#endif TEST_P(ClientIntegrationTest, LargeResponse) { initialize(); @@ -603,6 +605,42 @@ TEST_P(ClientIntegrationTest, InvalidDomainFakeResolver) { ASSERT_EQ(cc_.on_error_calls_, 0); ASSERT_EQ(cc_.on_headers_calls_, 1); ASSERT_EQ(cc_.status_, "200"); + + // Kick off a second request. There should be no resolution. + stream_ = createNewStream(createDefaultStreamCallbacks()); + stream_->sendHeaders(std::make_unique(default_request_headers_), + true); + terminal_callback_.waitReady(); + EXPECT_EQ(1, getCounterValue("dns_cache.base_dns_cache.dns_query_attempt")); +} + +TEST_P(ClientIntegrationTest, InvalidDomainReresolveWithNoAddresses) { + builder_.setRuntimeGuard("reresolve_null_addresses", true); + Network::OverrideAddrInfoDnsResolverFactory factory; + Registry::InjectFactory inject_factory(factory); + Registry::InjectFactory::forceAllowDuplicates(); + + initialize(); + default_request_headers_.setHost( + absl::StrCat("www.doesnotexist.com:", fake_upstreams_[0]->localAddress()->ip()->port())); + stream_ = stream_prototype_->start(createDefaultStreamCallbacks(), explicit_flow_control_); + stream_->sendHeaders(std::make_unique(default_request_headers_), + true); + // Unblock resolve, but resolve to the bad domain. + ASSERT_TRUE(waitForCounterGe("dns_cache.base_dns_cache.dns_query_attempt", 1)); + Network::TestResolver::unblockResolve(); + terminal_callback_.waitReady(); + + // The stream should fail. + ASSERT_EQ(cc_.on_error_calls_, 1); + + // A new stream will kick off a new resolution because the address was null. + stream_ = createNewStream(createDefaultStreamCallbacks()); + stream_->sendHeaders(std::make_unique(default_request_headers_), + true); + Network::TestResolver::unblockResolve(); + terminal_callback_.waitReady(); + EXPECT_EQ(2, getCounterValue("dns_cache.base_dns_cache.dns_query_attempt")); } TEST_P(ClientIntegrationTest, ReresolveAndDrain) { diff --git a/mobile/test/common/integration/engine_with_test_server.cc b/mobile/test/common/integration/engine_with_test_server.cc index eedab7d0d57a..cdcb7d55f23d 100644 --- a/mobile/test/common/integration/engine_with_test_server.cc +++ b/mobile/test/common/integration/engine_with_test_server.cc @@ -2,9 +2,12 @@ namespace Envoy { -EngineWithTestServer::EngineWithTestServer(Platform::EngineBuilder& engine_builder, - TestServerType type) { +EngineWithTestServer::EngineWithTestServer( + Platform::EngineBuilder& engine_builder, TestServerType type, + const absl::flat_hash_map& headers, absl::string_view body, + const absl::flat_hash_map& trailers) { test_server_.start(type); + test_server_.setResponse(headers, body, trailers); engine_ = engine_builder.build(); } diff --git a/mobile/test/common/integration/engine_with_test_server.h b/mobile/test/common/integration/engine_with_test_server.h index f4f4fa73ea43..e3e066080fdd 100644 --- a/mobile/test/common/integration/engine_with_test_server.h +++ b/mobile/test/common/integration/engine_with_test_server.h @@ -14,7 +14,10 @@ namespace Envoy { */ class EngineWithTestServer { public: - EngineWithTestServer(Platform::EngineBuilder& engine_builder, TestServerType type); + EngineWithTestServer(Platform::EngineBuilder& engine_builder, TestServerType type, + const absl::flat_hash_map& headers = {}, + absl::string_view body = "", + const absl::flat_hash_map& trailers = {}); ~EngineWithTestServer(); /** Returns the reference of `Engine` created. */ diff --git a/mobile/test/common/integration/test_server.cc b/mobile/test/common/integration/test_server.cc index 1f1f4a33e4aa..47c7c142f741 100644 --- a/mobile/test/common/integration/test_server.cc +++ b/mobile/test/common/integration/test_server.cc @@ -458,17 +458,17 @@ void TestServer::shutdown() { test_server_.reset(); } -std::string TestServer::getAddress() { +std::string TestServer::getAddress() const { ASSERT(upstream_); return upstream_->localAddress()->asString(); } -std::string TestServer::getIpAddress() { +std::string TestServer::getIpAddress() const { ASSERT(upstream_); return upstream_->localAddress()->ip()->addressAsString(); } -int TestServer::getPort() { +int TestServer::getPort() const { ASSERT(upstream_ || test_server_); if (upstream_) { return upstream_->localAddress()->ip()->port(); diff --git a/mobile/test/common/integration/test_server.h b/mobile/test/common/integration/test_server.h index c342e1361b66..68f6bbd59484 100644 --- a/mobile/test/common/integration/test_server.h +++ b/mobile/test/common/integration/test_server.h @@ -39,13 +39,13 @@ class TestServer : public ListenerHooks { void shutdown(); /** Gets the server address. The server address is a combination of IP address and port number. */ - std::string getAddress(); + std::string getAddress() const; /** Gets the server IP address. */ - std::string getIpAddress(); + std::string getIpAddress() const; /** Returns the server port number. */ - int getPort(); + int getPort() const; /** * Sets headers and data for the test server to return on all future requests. diff --git a/mobile/test/common/integration/xds_test_server.cc b/mobile/test/common/integration/xds_test_server.cc index 7cd7eed24829..39c0a1f7e114 100644 --- a/mobile/test/common/integration/xds_test_server.cc +++ b/mobile/test/common/integration/xds_test_server.cc @@ -6,7 +6,7 @@ #include "source/common/event/libevent.h" #include "source/common/tls/context_config_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "source/extensions/config_subscription/grpc/grpc_collection_subscription_factory.h" #include "source/extensions/config_subscription/grpc/grpc_mux_impl.h" #include "source/extensions/config_subscription/grpc/grpc_subscription_factory.h" diff --git a/mobile/test/jni/BUILD b/mobile/test/jni/BUILD index 0c63c8dfb0c7..31b628a3230e 100644 --- a/mobile/test/jni/BUILD +++ b/mobile/test/jni/BUILD @@ -214,7 +214,6 @@ cc_library( deps = [ "//library/common/http:header_utility_lib", "//library/jni:jni_utility_lib", - "//library/jni/types:jni_javavm_lib", "@envoy//test/test_common:utility_lib", ], alwayslink = True, diff --git a/mobile/test/jni/jni_helper_test.cc b/mobile/test/jni/jni_helper_test.cc index 4bbc27b78808..d93882babb76 100644 --- a/mobile/test/jni/jni_helper_test.cc +++ b/mobile/test/jni/jni_helper_test.cc @@ -7,6 +7,11 @@ // This file contains JNI implementation used by // `test/java/io/envoyproxy/envoymobile/jni/JniHelperTest.java` unit tests. +extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + Envoy::JNI::JniHelper::initialize(vm); + return Envoy::JNI::JniHelper::getVersion(); +} + extern "C" JNIEXPORT void JNICALL Java_io_envoyproxy_envoymobile_jni_JniHelperTest_getFieldId( JNIEnv* env, jclass, jclass clazz, jstring name, jstring signature) { Envoy::JNI::JniHelper jni_helper(env); diff --git a/mobile/test/jni/jni_utility_test.cc b/mobile/test/jni/jni_utility_test.cc index fd43b95febe6..f3c66951956f 100644 --- a/mobile/test/jni/jni_utility_test.cc +++ b/mobile/test/jni/jni_utility_test.cc @@ -5,19 +5,15 @@ #include "absl/container/flat_hash_map.h" #include "library/common/http/header_utility.h" #include "library/jni/jni_utility.h" -#include "library/jni/types/java_virtual_machine.h" // NOLINT(namespace-envoy) // This file contains JNI implementation used by // `test/java/io/envoyproxy/envoymobile/jni/JniUtilityTest.java` unit tests. -extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /*reserved*/) { - const auto result = Envoy::JNI::JavaVirtualMachine::initialize(vm); - if (result != JNI_OK) { - return result; - } - return Envoy::JNI::JavaVirtualMachine::getJNIVersion(); +extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* /* reserved */) { + Envoy::JNI::JniHelper::initialize(vm); + return Envoy::JNI::JniHelper::getVersion(); } extern "C" JNIEXPORT jbyteArray JNICALL diff --git a/mobile/test/jni/test_jni_impl.cc b/mobile/test/jni/test_jni_impl.cc index 05062c4a664c..bcff248b7b39 100644 --- a/mobile/test/jni/test_jni_impl.cc +++ b/mobile/test/jni/test_jni_impl.cc @@ -2,8 +2,6 @@ #include "test/test_common/utility.h" -#include "library/jni/jni_support.h" - // NOLINT(namespace-envoy) extern "C" JNIEXPORT jstring JNICALL diff --git a/mobile/test/performance/files_em_does_not_use b/mobile/test/performance/files_em_does_not_use index e4c823572497..88dc8da40412 100644 --- a/mobile/test/performance/files_em_does_not_use +++ b/mobile/test/performance/files_em_does_not_use @@ -10,3 +10,5 @@ source/server/options_impl.cc source/extensions/access_loggers/common/file_access_log_impl.h source/common/router/scoped_rds.h source/extensions/load_balancing_policies/subset/subset_lb.h +source/extensions/transport_sockets/tls/downstream_config.h +source/common/tls/server_ssl_socket.h diff --git a/reviewers.yaml b/reviewers.yaml new file mode 100644 index 000000000000..0b3ec1dc2959 --- /dev/null +++ b/reviewers.yaml @@ -0,0 +1,87 @@ +adisuissa: + maintainer: true + opsgenie: Adi + slack: UT17EMMTP +alyssawilk: + maintainer: true + opsgenie: Alyssa + slack: U78RP48V9 +botengyao: + first-pass: true + slack: U037YUAK147 +daixiang0: + first-pass: true + slack: U020CJG6UU8 +ggreenway: + maintainer: true + opsgenie: Greg + slack: U78MBV869 +htuch: + maintainer: true + opsgenie: Harvey + slack: U78E7055Z +jmarantz: + maintainer: true + opsgenie: Joshua + slack: U80HPLBPG +KBaichoo: + maintainer: true + opsgenie: Kevin + slack: U016ZPU8KBK +keith: + maintainer: true + opsgenie: Keith + slack: UGS5P90CF +kyessenov: + opsgenie: kuat + slack: U7KTRAA8M + maintainer: true +lizan: + maintainer: true + opsgenie: Lizan + slack: U79E51EQ6 +markdroth: + api: true + slack: UMN8K55A6 +mattklein123: + maintainer: true + opsgenie: Matt + slack: U5CALEVSL +nezdolik: + maintainer: true + opsgenie: Kateryna + slack: UDYUWRL13 +phlax: + maintainer: true + opsgenie: phlax + slack: U017PLM0GNQ +ravenblackx: + maintainer: true + opsgenie: Raven + slack: U02MJHFEX35 +RyanTheOptimist: + maintainer: true + opsgenie: Ryan + slack: U01SW3JC8GP +silverstar194: + first-pass: true + slack: U03LNPC8JN9 +soulxu: + maintainer: true + opsgenie: Hejie + slack: U01GNQ3B8AY +tyxia: + first-pass: true + slack: U023U1ZN9SP +wbpcode: + maintainer: true + opsgenie: Baiping + slack: U017KF5C0Q6 +yanavlasov: + maintainer: true + opsgenie: Yan + slack: UJHLR5KFS +zuercher: + maintainer: true + opsgenie: Stephan + slack: U78J72Q82 diff --git a/source/common/common/logger.h b/source/common/common/logger.h index 9ecc436731f1..c50f5e32a017 100644 --- a/source/common/common/logger.h +++ b/source/common/common/logger.h @@ -96,7 +96,8 @@ const static bool should_log = true; FUNCTION(udp) \ FUNCTION(wasm) \ FUNCTION(websocket) \ - FUNCTION(golang) + FUNCTION(golang) \ + FUNCTION(stats_sinks) // clang-format off enum class Id { diff --git a/source/common/filter/config_discovery_impl.cc b/source/common/filter/config_discovery_impl.cc index 477faa7ffd70..d3cb3e26b70d 100644 --- a/source/common/filter/config_discovery_impl.cc +++ b/source/common/filter/config_discovery_impl.cc @@ -155,7 +155,7 @@ absl::Status FilterConfigSubscription::onConfigUpdate( Common::applyToAllWithCleanup( filter_config_providers_, [](DynamicFilterConfigProviderImplBase* provider, std::shared_ptr cleanup) { - provider->onConfigRemoved([cleanup] {}); + THROW_IF_NOT_OK(provider->onConfigRemoved([cleanup] {})); }, [me = shared_from_this()]() { me->updateComplete(); }); } else if (!added_resources.empty()) { @@ -244,7 +244,7 @@ void FilterConfigProviderManagerImplBase::applyLastOrDefaultConfig( // Apply the default config if none has been applied. if (!last_config_valid) { - provider.applyDefaultConfiguration(); + THROW_IF_NOT_OK(provider.applyDefaultConfiguration()); } } diff --git a/source/common/filter/config_discovery_impl.h b/source/common/filter/config_discovery_impl.h index c3dea0506d72..652b6f54b8c5 100644 --- a/source/common/filter/config_discovery_impl.h +++ b/source/common/filter/config_discovery_impl.h @@ -110,26 +110,28 @@ class DynamicFilterConfigProviderImpl : public DynamicFilterConfigProviderImplBa // Config::DynamicExtensionConfigProviderBase absl::Status onConfigUpdate(const Protobuf::Message& message, const std::string&, Config::ConfigAppliedCb applied_on_all_threads) override { - const FactoryCb config = instantiateFilterFactory(message); - update(config, applied_on_all_threads); + const absl::StatusOr config_or_error = instantiateFilterFactory(message); + RETURN_IF_STATUS_NOT_OK(config_or_error); + update(config_or_error.value(), applied_on_all_threads); return absl::OkStatus(); } - void onConfigRemoved(Config::ConfigAppliedCb applied_on_all_threads) override { - const absl::optional default_config = - default_configuration_ - ? absl::make_optional(instantiateFilterFactory(*default_configuration_)) - : absl::nullopt; - update(default_config, applied_on_all_threads); + absl::Status onConfigRemoved(Config::ConfigAppliedCb applied_on_all_threads) override { + absl::optional cb; + if (default_configuration_) { + auto cb_or_error = instantiateFilterFactory(*default_configuration_); + RETURN_IF_STATUS_NOT_OK(cb_or_error); + cb = cb_or_error.value(); + } + update(cb, applied_on_all_threads); + return absl::OkStatus(); } - void applyDefaultConfiguration() override { + absl::Status applyDefaultConfiguration() override { if (default_configuration_) { - auto status = onConfigUpdate(*default_configuration_, "", nullptr); - if (!status.ok()) { - throwEnvoyExceptionOrPanic(std::string(status.message())); - } + return onConfigUpdate(*default_configuration_, "", nullptr); } + return absl::OkStatus(); } const Network::ListenerFilterMatcherSharedPtr& getListenerFilterMatcher() override { return listener_filter_matcher_; @@ -140,7 +142,8 @@ class DynamicFilterConfigProviderImpl : public DynamicFilterConfigProviderImplBa const Network::ListenerFilterMatcherSharedPtr listener_filter_matcher_; private: - virtual FactoryCb instantiateFilterFactory(const Protobuf::Message& message) const PURE; + virtual absl::StatusOr + instantiateFilterFactory(const Protobuf::Message& message) const PURE; void update(absl::optional config, Config::ConfigAppliedCb applied_on_all_threads) { // This call must not capture 'this' as it is invoked on all workers asynchronously. @@ -215,17 +218,14 @@ class HttpDynamicFilterConfigProviderImpl } private: - NamedHttpFilterFactoryCb + absl::StatusOr instantiateFilterFactory(const Protobuf::Message& message) const override { auto* factory = Registry::FactoryRegistry::getFactoryByType( message.GetTypeName()); absl::StatusOr error_or_factory = factory->createFilterFactoryFromProto(message, getStatPrefix(), factory_context_); - if (!error_or_factory.status().ok()) { - throwEnvoyExceptionOrPanic(std::string(error_or_factory.status().message())); - } - - return {factory->name(), error_or_factory.value()}; + RETURN_IF_STATUS_NOT_OK(error_or_factory); + return NamedHttpFilterFactoryCb{factory->name(), error_or_factory.value()}; } Server::Configuration::ServerFactoryContext& server_context_; @@ -250,12 +250,14 @@ class NetworkDynamicFilterConfigProviderImplBase server_context_(server_context), factory_context_(factory_context) {} private: - Network::FilterFactoryCb + absl::StatusOr instantiateFilterFactory(const Protobuf::Message& message) const override { auto* factory = Registry::FactoryRegistry::getFactoryByType( message.GetTypeName()); - return THROW_OR_RETURN_VALUE(factory->createFilterFactoryFromProto(message, factory_context_), - Network::FilterFactoryCb); + absl::StatusOr cb_or_error = + factory->createFilterFactoryFromProto(message, factory_context_); + RETURN_IF_STATUS_NOT_OK(cb_or_error); + return cb_or_error.value(); } protected: @@ -328,7 +330,7 @@ class TcpListenerDynamicFilterConfigProviderImpl using ListenerDynamicFilterConfigProviderImpl::ListenerDynamicFilterConfigProviderImpl; private: - Network::ListenerFilterFactoryCb + absl::StatusOr instantiateFilterFactory(const Protobuf::Message& message) const override { auto* factory = Registry::FactoryRegistry:: @@ -344,7 +346,7 @@ class UdpListenerDynamicFilterConfigProviderImpl using ListenerDynamicFilterConfigProviderImpl::ListenerDynamicFilterConfigProviderImpl; private: - Network::UdpListenerFilterFactoryCb + absl::StatusOr instantiateFilterFactory(const Protobuf::Message& message) const override { auto* factory = Registry::FactoryRegistry:: @@ -359,7 +361,7 @@ class QuicListenerDynamicFilterConfigProviderImpl using ListenerDynamicFilterConfigProviderImpl::ListenerDynamicFilterConfigProviderImpl; private: - Network::QuicListenerFilterFactoryCb + absl::StatusOr instantiateFilterFactory(const Protobuf::Message& message) const override { auto* factory = Registry::FactoryRegistry:: @@ -581,9 +583,10 @@ class FilterConfigProviderManagerImpl : public FilterConfigProviderManagerImplBa ProtobufTypes::MessagePtr default_config; if (config_source.has_default_config()) { - default_config = + default_config = THROW_OR_RETURN_VALUE( getDefaultConfig(config_source.default_config(), filter_config_name, server_context, - last_filter_in_filter_chain, filter_chain_type, require_type_urls); + last_filter_in_filter_chain, filter_chain_type, require_type_urls), + ProtobufTypes::MessagePtr); } std::unique_ptr> provider = @@ -626,14 +629,14 @@ class FilterConfigProviderManagerImpl : public FilterConfigProviderManagerImplBa return false; } - ProtobufTypes::MessagePtr + absl::StatusOr getDefaultConfig(const ProtobufWkt::Any& proto_config, const std::string& filter_config_name, Server::Configuration::ServerFactoryContext& server_context, bool last_filter_in_filter_chain, const std::string& filter_chain_type, const absl::flat_hash_set& require_type_urls) const { auto* default_factory = Config::Utility::getFactoryByType(proto_config); - THROW_IF_NOT_OK(validateProtoConfigDefaultFactory(default_factory == nullptr, - filter_config_name, proto_config.type_url())); + RETURN_IF_NOT_OK(validateProtoConfigDefaultFactory( + default_factory == nullptr, filter_config_name, proto_config.type_url())); validateProtoConfigTypeUrl(Config::Utility::getFactoryType(proto_config), require_type_urls); ProtobufTypes::MessagePtr message = Config::Utility::translateAnyToFactoryConfig( proto_config, server_context.messageValidationVisitor(), *default_factory); diff --git a/source/common/formatter/stream_info_formatter.cc b/source/common/formatter/stream_info_formatter.cc index 1aa75193225f..82cdd2fb0a37 100644 --- a/source/common/formatter/stream_info_formatter.cc +++ b/source/common/formatter/stream_info_formatter.cc @@ -21,6 +21,29 @@ const std::regex& getSystemTimeFormatNewlinePattern() { CONSTRUCT_ON_FIRST_USE(std::regex, "%[-_0^#]*[1-9]*(E|O)?n"); } +Network::Address::InstanceConstSharedPtr +getUpstreamRemoteAddress(const StreamInfo::StreamInfo& stream_info) { + auto opt_ref = stream_info.upstreamInfo(); + if (!opt_ref.has_value()) { + return nullptr; + } + + // TODO(wbpcode): remove this after the flag is removed. + const bool use_upstream_remote_address = Runtime::runtimeFeatureEnabled( + "envoy.reloadable_features.upstream_remote_address_use_connection"); + if (!use_upstream_remote_address) { + if (auto host = opt_ref->upstreamHost(); host != nullptr) { + return host->address(); + } + return nullptr; + } + + if (auto addr = opt_ref->upstreamRemoteAddress(); addr != nullptr) { + return addr; + } + return nullptr; +} + } // namespace MetadataFormatter::MetadataFormatter(const std::string& filter_namespace, @@ -649,7 +672,7 @@ class StreamInfoUInt64FormatterProvider : public StreamInfoFormatterProvider { FieldExtractor field_extractor_; }; -// StreamInfo Envoy::Network::Address::InstanceConstSharedPtr field extractor. +// StreamInfo Network::Address::InstanceConstSharedPtr field extractor. class StreamInfoAddressFormatterProvider : public StreamInfoFormatterProvider { public: using FieldExtractor = @@ -1036,16 +1059,42 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide return StreamInfo::ResponseFlagUtils::toString(stream_info); }); }}}, + {"UPSTREAM_HOST_NAME", + {CommandSyntaxChecker::COMMAND_ONLY, + [](const std::string&, absl::optional) { + return std::make_unique( + [](const StreamInfo::StreamInfo& stream_info) -> absl::optional { + const auto opt_ref = stream_info.upstreamInfo(); + if (!opt_ref.has_value()) { + return absl::nullopt; + } + const auto host = opt_ref->upstreamHost(); + if (host == nullptr) { + return absl::nullopt; + } + std::string host_name = host->hostname(); + if (host_name.empty()) { + // If no hostname is available, the main address is used. + return host->address()->asString(); + } + return absl::make_optional(std::move(host_name)); + }); + }}}, {"UPSTREAM_HOST", {CommandSyntaxChecker::COMMAND_ONLY, [](const std::string&, absl::optional) { return StreamInfoAddressFormatterProvider::withPort( [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo() && stream_info.upstreamInfo()->upstreamHost()) { - return stream_info.upstreamInfo()->upstreamHost()->address(); + -> Network::Address::InstanceConstSharedPtr { + const auto opt_ref = stream_info.upstreamInfo(); + if (!opt_ref.has_value()) { + return nullptr; } - return nullptr; + const auto host = opt_ref->upstreamHost(); + if (host == nullptr) { + return nullptr; + } + return host->address(); }); }}}, {"UPSTREAM_CONNECTION_ID", @@ -1083,7 +1132,7 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide [](const std::string&, absl::optional) { return StreamInfoAddressFormatterProvider::withPort( [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { + -> Network::Address::InstanceConstSharedPtr { if (stream_info.upstreamInfo().has_value()) { return stream_info.upstreamInfo().value().get().upstreamLocalAddress(); } @@ -1095,7 +1144,7 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide [](const std::string&, absl::optional) { return StreamInfoAddressFormatterProvider::withoutPort( [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { + -> Network::Address::InstanceConstSharedPtr { if (stream_info.upstreamInfo().has_value()) { return stream_info.upstreamInfo().value().get().upstreamLocalAddress(); } @@ -1107,7 +1156,7 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide [](const std::string&, absl::optional) { return StreamInfoAddressFormatterProvider::justPort( [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { + -> Network::Address::InstanceConstSharedPtr { if (stream_info.upstreamInfo().has_value()) { return stream_info.upstreamInfo().value().get().upstreamLocalAddress(); } @@ -1119,11 +1168,8 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide [](const std::string&, absl::optional) { return StreamInfoAddressFormatterProvider::withPort( [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo() && stream_info.upstreamInfo()->upstreamHost()) { - return stream_info.upstreamInfo()->upstreamHost()->address(); - } - return nullptr; + -> Network::Address::InstanceConstSharedPtr { + return getUpstreamRemoteAddress(stream_info); }); }}}, {"UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT", @@ -1131,11 +1177,8 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide [](const std::string&, absl::optional) { return StreamInfoAddressFormatterProvider::withoutPort( [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo() && stream_info.upstreamInfo()->upstreamHost()) { - return stream_info.upstreamInfo()->upstreamHost()->address(); - } - return nullptr; + -> Network::Address::InstanceConstSharedPtr { + return getUpstreamRemoteAddress(stream_info); }); }}}, {"UPSTREAM_REMOTE_PORT", @@ -1143,11 +1186,8 @@ const StreamInfoFormatterProviderLookupTable& getKnownStreamInfoFormatterProvide [](const std::string&, absl::optional) { return StreamInfoAddressFormatterProvider::justPort( [](const StreamInfo::StreamInfo& stream_info) - -> std::shared_ptr { - if (stream_info.upstreamInfo() && stream_info.upstreamInfo()->upstreamHost()) { - return stream_info.upstreamInfo()->upstreamHost()->address(); - } - return nullptr; + -> Network::Address::InstanceConstSharedPtr { + return getUpstreamRemoteAddress(stream_info); }); }}}, {"UPSTREAM_REQUEST_ATTEMPT_COUNT", diff --git a/source/common/http/async_client_impl.cc b/source/common/http/async_client_impl.cc index 9d283cd4118b..da55e917ea98 100644 --- a/source/common/http/async_client_impl.cc +++ b/source/common/http/async_client_impl.cc @@ -111,8 +111,8 @@ AsyncStreamImpl::AsyncStreamImpl(AsyncClientImpl& parent, AsyncClient::StreamCal parent_.cluster_->name(), retry_policy_ != nullptr ? *retry_policy_ : *options.parsed_retry_policy, parent_.factory_context_.regexEngine(), options.timeout, options.hash_policy)), - account_(options.account_), buffer_limit_(options.buffer_limit_), - send_xff_(options.send_xff) { + account_(options.account_), buffer_limit_(options.buffer_limit_), send_xff_(options.send_xff), + send_internal_(options.send_internal) { stream_info_.dynamicMetadata().MergeFrom(options.metadata); stream_info_.setIsShadow(options.is_shadow); stream_info_.setUpstreamClusterInfo(parent_.cluster_); @@ -173,7 +173,10 @@ void AsyncStreamImpl::sendHeaders(RequestHeaderMap& headers, bool end_stream) { } is_grpc_request_ = Grpc::Common::isGrpcRequestHeaders(headers); - headers.setReferenceEnvoyInternalRequest(Headers::get().EnvoyInternalRequestValues.True); + if (send_internal_) { + headers.setReferenceEnvoyInternalRequest(Headers::get().EnvoyInternalRequestValues.True); + } + if (send_xff_) { Utility::appendXff(headers, *parent_.config_->local_info_.address()); } diff --git a/source/common/http/async_client_impl.h b/source/common/http/async_client_impl.h index 711a2c3aa058..b8ec394d088e 100644 --- a/source/common/http/async_client_impl.h +++ b/source/common/http/async_client_impl.h @@ -291,6 +291,7 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream, bool is_grpc_request_{}; bool is_head_request_{false}; bool send_xff_{true}; + bool send_internal_{true}; friend class AsyncClientImpl; friend class AsyncClientImplUnitTest; diff --git a/source/common/protobuf/utility.cc b/source/common/protobuf/utility.cc index 77eed286b79d..e9b5003b9061 100644 --- a/source/common/protobuf/utility.cc +++ b/source/common/protobuf/utility.cc @@ -27,6 +27,89 @@ using namespace std::chrono_literals; namespace Envoy { namespace { +// Validates that the max value of nanoseconds and seconds doesn't cause an +// overflow in the protobuf time-util computations. +// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation" +// is removed this function should be renamed to validateDurationNoThrow. +absl::Status validateDurationUnifiedNoThrow(const ProtobufWkt::Duration& duration) { + // Apply a strict max boundary to the `seconds` value to avoid overflow when + // both seconds and nanoseconds are at their highest values. + // Note that protobuf internally converts to the input's seconds and + // nanoseconds to nanoseconds (with a max nanoseconds value of 999999999). + // The kMaxSecondsValue = 9223372035, which is about 292 years. + constexpr int64_t kMaxSecondsValue = + (std::numeric_limits::max() - 999999999) / (1000 * 1000 * 1000); + + if (duration.seconds() < 0 || duration.nanos() < 0) { + return absl::OutOfRangeError( + fmt::format("Expected positive duration: {}", duration.DebugString())); + } + if (!Protobuf::util::TimeUtil::IsDurationValid(duration)) { + return absl::OutOfRangeError( + fmt::format("Duration out-of-range according to Protobuf: {}", duration.DebugString())); + } + if (duration.nanos() > 999999999 || duration.seconds() > kMaxSecondsValue) { + return absl::OutOfRangeError(fmt::format("Duration out-of-range: {}", duration.DebugString())); + } + return absl::OkStatus(); +} + +// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation" +// is removed this function should be removed. +absl::Status validateDurationNoThrow(const ProtobufWkt::Duration& duration, + int64_t max_seconds_value) { + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_duration_validation")) { + return validateDurationUnifiedNoThrow(duration); + } + if (duration.seconds() < 0 || duration.nanos() < 0) { + return absl::OutOfRangeError( + fmt::format("Expected positive duration: {}", duration.DebugString())); + } + if (duration.nanos() > 999999999 || duration.seconds() > max_seconds_value) { + return absl::OutOfRangeError(fmt::format("Duration out-of-range: {}", duration.DebugString())); + } + return absl::OkStatus(); +} + +// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation" +// is removed this function should call validateDurationUnifiedNoThrow instead +// of validateDurationNoThrow. +void validateDuration(const ProtobufWkt::Duration& duration, int64_t max_seconds_value) { + const auto result = validateDurationNoThrow(duration, max_seconds_value); + if (!result.ok()) { + throwEnvoyExceptionOrPanic(std::string(result.message())); + } +} + +// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation" +// is removed this function should be removed. +void validateDuration(const ProtobufWkt::Duration& duration) { + validateDuration(duration, Protobuf::util::TimeUtil::kDurationMaxSeconds); +} + +// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation" +// is removed this function should be removed. +void validateDurationAsMilliseconds(const ProtobufWkt::Duration& duration) { + // Apply stricter max boundary to the `seconds` value to avoid overflow. + // Note that protobuf internally converts to nanoseconds. + // The kMaxInt64Nanoseconds = 9223372036, which is about 300 years. + constexpr int64_t kMaxInt64Nanoseconds = + std::numeric_limits::max() / (1000 * 1000 * 1000); + validateDuration(duration, kMaxInt64Nanoseconds); +} + +// TODO(adisuissa): Once "envoy.reloadable_features.strict_duration_validation" +// is removed this function should be removed. +absl::Status validateDurationAsMillisecondsNoThrow(const ProtobufWkt::Duration& duration) { + constexpr int64_t kMaxInt64Nanoseconds = + std::numeric_limits::max() / (1000 * 1000 * 1000); + return validateDurationNoThrow(duration, kMaxInt64Nanoseconds); +} + +} // namespace + +namespace { + absl::string_view filenameFromPath(absl::string_view full_path) { size_t index = full_path.rfind('/'); if (index == std::string::npos || index == full_path.size()) { @@ -307,6 +390,43 @@ void MessageUtil::checkForUnexpectedFields(const Protobuf::Message& message, namespace { +// A proto visitor that validates the correctness of google.protobuf.Duration messages +// as defined by Envoy's duration constraints. +class DurationFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { +public: + void onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&) override {} + + void onMessage(const Protobuf::Message& message, absl::Span, + bool) override { + const Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); + if (reflectable_message->GetDescriptor()->full_name() == "google.protobuf.Duration") { + ProtobufWkt::Duration duration_message; +#if defined(ENVOY_ENABLE_FULL_PROTOS) + duration_message.CheckTypeAndMergeFrom(message); +#else + duration_message.MergeFromCord(message.SerializeAsCord()); +#endif + // Validate the value of the duration. + absl::Status status = validateDurationUnifiedNoThrow(duration_message); + if (!status.ok()) { + throwEnvoyExceptionOrPanic(fmt::format("Invalid duration: {}", status.message())); + } + } + } +}; + +} // namespace + +void MessageUtil::validateDurationFields(const Protobuf::Message& message, bool recurse_into_any) { + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_duration_validation")) { + DurationFieldProtoVisitor duration_field_visitor; + THROW_IF_NOT_OK( + ProtobufMessage::traverseMessage(duration_field_visitor, message, recurse_into_any)); + } +} + +namespace { + class PgvCheckVisitor : public ProtobufMessage::ConstProtoVisitor { public: void onMessage(const Protobuf::Message& message, absl::Span, @@ -732,48 +852,6 @@ ProtobufWkt::Value ValueUtil::listValue(const std::vector& v return val; } -namespace { - -absl::Status validateDurationNoThrow(const ProtobufWkt::Duration& duration, - int64_t max_seconds_value) { - if (duration.seconds() < 0 || duration.nanos() < 0) { - return absl::OutOfRangeError( - fmt::format("Expected positive duration: {}", duration.DebugString())); - } - if (duration.nanos() > 999999999 || duration.seconds() > max_seconds_value) { - return absl::OutOfRangeError(fmt::format("Duration out-of-range: {}", duration.DebugString())); - } - return absl::OkStatus(); -} - -void validateDuration(const ProtobufWkt::Duration& duration, int64_t max_seconds_value) { - const auto result = validateDurationNoThrow(duration, max_seconds_value); - if (!result.ok()) { - throwEnvoyExceptionOrPanic(std::string(result.message())); - } -} - -void validateDuration(const ProtobufWkt::Duration& duration) { - validateDuration(duration, Protobuf::util::TimeUtil::kDurationMaxSeconds); -} - -void validateDurationAsMilliseconds(const ProtobufWkt::Duration& duration) { - // Apply stricter max boundary to the `seconds` value to avoid overflow. - // Note that protobuf internally converts to nanoseconds. - // The kMaxInt64Nanoseconds = 9223372036, which is about 300 years. - constexpr int64_t kMaxInt64Nanoseconds = - std::numeric_limits::max() / (1000 * 1000 * 1000); - validateDuration(duration, kMaxInt64Nanoseconds); -} - -absl::Status validateDurationAsMillisecondsNoThrow(const ProtobufWkt::Duration& duration) { - constexpr int64_t kMaxInt64Nanoseconds = - std::numeric_limits::max() / (1000 * 1000 * 1000); - return validateDurationNoThrow(duration, kMaxInt64Nanoseconds); -} - -} // namespace - uint64_t DurationUtil::durationToMilliseconds(const ProtobufWkt::Duration& duration) { validateDurationAsMilliseconds(duration); return Protobuf::util::TimeUtil::DurationToMilliseconds(duration); diff --git a/source/common/protobuf/utility.h b/source/common/protobuf/utility.h index 7e613772fe4a..546fbcd22783 100644 --- a/source/common/protobuf/utility.h +++ b/source/common/protobuf/utility.h @@ -287,6 +287,15 @@ class MessageUtil { ProtobufMessage::ValidationVisitor& validation_visitor, bool recurse_into_any = false); + /** + * Validates that duration fields in the config are valid. + * @param message message to validate. + * @param recurse_into_any whether to recurse into Any messages during unexpected checking. + * @throw EnvoyException if a duration field is invalid. + */ + static void validateDurationFields(const Protobuf::Message& message, + bool recurse_into_any = false); + /** * Perform a PGV check on the entire message tree, recursing into Any messages as needed. */ @@ -306,11 +315,19 @@ class MessageUtil { static void validate(const MessageType& message, ProtobufMessage::ValidationVisitor& validation_visitor, bool recurse_into_any = false) { + // TODO(adisuissa): There are multiple recursive traversals done by the + // calls in this function. This can be refactored into a single recursive + // traversal that invokes the various validators. + // Log warnings or throw errors if deprecated fields or unknown fields are in use. if (!validation_visitor.skipValidation()) { checkForUnexpectedFields(message, validation_visitor, recurse_into_any); } + // Throw an exception if the config has an invalid Duration field. This is needed + // because Envoy validates the duration in a strict way that is not supported by PGV. + validateDurationFields(message, recurse_into_any); + // TODO(mattklein123): This will recurse the message twice, once above and once for PGV. When // we move to always recursing, satisfying the TODO below, we should merge into a single // recursion for performance reasons. diff --git a/source/common/quic/BUILD b/source/common/quic/BUILD index 7aafa7be99f8..e3e9d2dc5ab4 100644 --- a/source/common/quic/BUILD +++ b/source/common/quic/BUILD @@ -181,7 +181,7 @@ envoy_cc_library( "//envoy/http:persistent_quic_info_interface", "//envoy/registry", "//source/common/runtime:runtime_lib", - "//source/common/tls:ssl_socket_lib", + "//source/common/tls:client_ssl_socket_lib", "//source/extensions/quic/crypto_stream:envoy_quic_crypto_client_stream_lib", "@com_github_google_quiche//:quic_core_http_spdy_session_lib", ], @@ -462,8 +462,8 @@ envoy_cc_library( "//envoy/ssl:context_config_interface", "//source/common/common:assert_lib", "//source/common/network:transport_socket_options_lib", + "//source/common/tls:client_ssl_socket_lib", "//source/common/tls:context_config_lib", - "//source/common/tls:ssl_socket_lib", "@com_github_google_quiche//:quic_core_crypto_crypto_handshake_lib", "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", ], @@ -488,7 +488,7 @@ envoy_cc_library( "//source/common/common:assert_lib", "//source/common/network:transport_socket_options_lib", "//source/common/tls:context_config_lib", - "//source/common/tls:ssl_socket_lib", + "//source/common/tls:server_ssl_socket_lib", "@com_github_google_quiche//:quic_core_crypto_crypto_handshake_lib", "@envoy_api//envoy/extensions/transport_sockets/quic/v3:pkg_cc_proto", ], diff --git a/source/common/quic/client_connection_factory_impl.h b/source/common/quic/client_connection_factory_impl.h index b247c61bc54a..70170d0034c8 100644 --- a/source/common/quic/client_connection_factory_impl.h +++ b/source/common/quic/client_connection_factory_impl.h @@ -9,7 +9,7 @@ #include "source/common/quic/envoy_quic_client_session.h" #include "source/common/quic/envoy_quic_connection_helper.h" #include "source/common/quic/envoy_quic_utils.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/extensions/quic/crypto_stream/envoy_quic_crypto_client_stream.h" #include "quiche/quic/core/quic_utils.h" diff --git a/source/common/quic/envoy_quic_proof_verifier.cc b/source/common/quic/envoy_quic_proof_verifier.cc index 78bc355589ff..0ae8f324eb8b 100644 --- a/source/common/quic/envoy_quic_proof_verifier.cc +++ b/source/common/quic/envoy_quic_proof_verifier.cc @@ -7,6 +7,7 @@ #include "source/common/quic/envoy_quic_utils.h" #include "source/common/runtime/runtime_features.h" +#include "source/common/tls/client_context_impl.h" #include "source/common/tls/utility.h" #include "quiche/quic/core/crypto/certificate_view.h" diff --git a/source/common/quic/quic_client_transport_socket_factory.h b/source/common/quic/quic_client_transport_socket_factory.h index c6783bd78580..ee17332dcdbe 100644 --- a/source/common/quic/quic_client_transport_socket_factory.h +++ b/source/common/quic/quic_client_transport_socket_factory.h @@ -1,6 +1,7 @@ #pragma once #include "source/common/quic/quic_transport_socket_factory.h" +#include "source/common/tls/client_ssl_socket.h" namespace Envoy { namespace Quic { diff --git a/source/common/quic/quic_server_transport_socket_factory.cc b/source/common/quic/quic_server_transport_socket_factory.cc index 0202ca3fe605..781ce62cd2d2 100644 --- a/source/common/quic/quic_server_transport_socket_factory.cc +++ b/source/common/quic/quic_server_transport_socket_factory.cc @@ -7,6 +7,7 @@ #include "source/common/quic/envoy_quic_utils.h" #include "source/common/runtime/runtime_features.h" #include "source/common/tls/context_config_impl.h" +#include "source/common/tls/server_context_impl.h" namespace Envoy { namespace Quic { diff --git a/source/common/quic/quic_server_transport_socket_factory.h b/source/common/quic/quic_server_transport_socket_factory.h index eaf93fac49ae..32628d8837e6 100644 --- a/source/common/quic/quic_server_transport_socket_factory.h +++ b/source/common/quic/quic_server_transport_socket_factory.h @@ -8,7 +8,7 @@ #include "source/common/common/assert.h" #include "source/common/network/transport_socket_options_impl.h" #include "source/common/quic/quic_transport_socket_factory.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" namespace Envoy { namespace Quic { diff --git a/source/common/runtime/runtime_features.cc b/source/common/runtime/runtime_features.cc index 0db64624c70f..922ff4e4dc1c 100644 --- a/source/common/runtime/runtime_features.cc +++ b/source/common/runtime/runtime_features.cc @@ -51,6 +51,7 @@ RUNTIME_GUARD(envoy_reloadable_features_ext_authz_http_send_original_xff); RUNTIME_GUARD(envoy_reloadable_features_grpc_http1_reverse_bridge_change_http_status); RUNTIME_GUARD(envoy_reloadable_features_grpc_http1_reverse_bridge_handle_empty_response); RUNTIME_GUARD(envoy_reloadable_features_hmac_base64_encoding_only); +RUNTIME_GUARD(envoy_reloadable_features_http1_balsa_delay_reset); RUNTIME_GUARD(envoy_reloadable_features_http1_connection_close_header_in_redirect); // Ignore the automated "remove this flag" issue: we should keep this for 1 year. RUNTIME_GUARD(envoy_reloadable_features_http1_use_balsa_parser); @@ -88,6 +89,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_strict_duration_validation); 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); @@ -96,6 +98,7 @@ RUNTIME_GUARD(envoy_reloadable_features_token_passed_entirely); RUNTIME_GUARD(envoy_reloadable_features_udp_socket_apply_aggregated_read_limit); RUNTIME_GUARD(envoy_reloadable_features_uhv_allow_malformed_url_encoding); RUNTIME_GUARD(envoy_reloadable_features_upstream_allow_connect_with_2xx); +RUNTIME_GUARD(envoy_reloadable_features_upstream_remote_address_use_connection); RUNTIME_GUARD(envoy_reloadable_features_upstream_wait_for_response_headers_before_disabling_read); RUNTIME_GUARD(envoy_reloadable_features_use_http3_header_normalisation); RUNTIME_GUARD(envoy_reloadable_features_validate_connect); @@ -150,6 +153,8 @@ FALSE_RUNTIME_GUARD(envoy_reloadable_features_quic_defer_logging_to_ack_listener FALSE_RUNTIME_GUARD(envoy_reloadable_features_use_config_in_happy_eyeballs); // TODO(#33474) removed it once GRO packet dropping is fixed. FALSE_RUNTIME_GUARD(envoy_reloadable_features_prefer_quic_client_udp_gro); +// TODO(alyssar) evaluate and either make this a config knob or remove. +FALSE_RUNTIME_GUARD(envoy_reloadable_features_reresolve_null_addresses); // A flag to set the maximum TLS version for google_grpc client to TLS1.2, when needed for // compliance restrictions. diff --git a/source/common/stream_info/stream_info_impl.h b/source/common/stream_info/stream_info_impl.h index 5fbc6b7f7e14..8ea7df0e0b2f 100644 --- a/source/common/stream_info/stream_info_impl.h +++ b/source/common/stream_info/stream_info_impl.h @@ -437,26 +437,26 @@ struct StreamInfoImpl : public StreamInfo { SystemTime start_time_; MonotonicTime start_time_monotonic_; absl::optional final_time_; - absl::optional protocol_; private: absl::optional response_code_; - -public: absl::optional response_code_details_; absl::optional connection_termination_details_; + +public: absl::InlinedVector response_flags_{}; bool health_check_request_{}; Router::RouteConstSharedPtr route_; envoy::config::core::v3::Metadata metadata_{}; FilterStateSharedPtr filter_state_; + +private: absl::optional attempt_count_; // TODO(agrawroh): Check if the owner of this storage outlives the StreamInfo. We should only copy // the string if it could outlive the StreamInfo. absl::optional virtual_cluster_name_; -private: static Network::ConnectionInfoProviderSharedPtr emptyDownstreamAddressProvider() { MUTABLE_CONSTRUCT_ON_FIRST_USE( Network::ConnectionInfoProviderSharedPtr, diff --git a/source/common/tls/BUILD b/source/common/tls/BUILD index d78ea0bf7dd8..9950e6756bdd 100644 --- a/source/common/tls/BUILD +++ b/source/common/tls/BUILD @@ -60,7 +60,7 @@ envoy_cc_library( ) envoy_cc_library( - name = "ssl_socket_lib", + name = "ssl_socket_base", srcs = ["ssl_socket.cc"], hdrs = ["ssl_socket.h"], external_deps = [ @@ -95,6 +95,46 @@ envoy_cc_library( ], ) +envoy_cc_library( + name = "client_ssl_socket_lib", + srcs = ["client_ssl_socket.cc"], + hdrs = ["client_ssl_socket.h"], + external_deps = [ + "abseil_hash", + "abseil_node_hash_map", + "abseil_optional", + "abseil_synchronization", + "ssl", + ], + deps = [ + ":ssl_socket_base", + ], +) + +envoy_cc_library( + name = "server_ssl_socket_lib", + srcs = ["server_ssl_socket.cc"], + hdrs = ["server_ssl_socket.h"], + external_deps = [ + "abseil_hash", + "abseil_node_hash_map", + "abseil_optional", + "abseil_synchronization", + "ssl", + ], + deps = [ + ":ssl_socket_base", + ], +) + +envoy_cc_library( + name = "ssl_socket_lib", + deps = [ + ":client_ssl_socket_lib", + ":server_ssl_socket_lib", + ], +) + envoy_cc_library( name = "context_config_lib", srcs = ["context_config_impl.cc"], @@ -126,12 +166,16 @@ envoy_cc_library( envoy_cc_library( name = "context_lib", srcs = [ + "client_context_impl.cc", "context_impl.cc", "context_manager_impl.cc", + "server_context_impl.cc", ], hdrs = [ + "client_context_impl.h", "context_impl.h", "context_manager_impl.h", + "server_context_impl.h", ], external_deps = [ "abseil_node_hash_set", diff --git a/source/common/tls/client_context_impl.cc b/source/common/tls/client_context_impl.cc new file mode 100644 index 000000000000..91710b2a1adf --- /dev/null +++ b/source/common/tls/client_context_impl.cc @@ -0,0 +1,176 @@ +#include "source/common/tls/client_context_impl.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "envoy/admin/v3/certs.pb.h" +#include "envoy/common/exception.h" +#include "envoy/common/platform.h" +#include "envoy/ssl/ssl_socket_extended_info.h" +#include "envoy/stats/scope.h" +#include "envoy/type/matcher/v3/string.pb.h" + +#include "source/common/common/assert.h" +#include "source/common/common/base64.h" +#include "source/common/common/fmt.h" +#include "source/common/common/hex.h" +#include "source/common/common/utility.h" +#include "source/common/network/address_impl.h" +#include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_features.h" +#include "source/common/stats/utility.h" +#include "source/common/tls/cert_validator/factory.h" +#include "source/common/tls/stats.h" +#include "source/common/tls/utility.h" + +#include "absl/container/node_hash_set.h" +#include "absl/strings/match.h" +#include "absl/strings/str_join.h" +#include "cert_validator/cert_validator.h" +#include "openssl/evp.h" +#include "openssl/hmac.h" +#include "openssl/pkcs12.h" +#include "openssl/rand.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +ClientContextImpl::ClientContextImpl(Stats::Scope& scope, + const Envoy::Ssl::ClientContextConfig& config, + Server::Configuration::CommonFactoryContext& factory_context) + : ContextImpl(scope, config, factory_context, nullptr /* additional_init */), + server_name_indication_(config.serverNameIndication()), + allow_renegotiation_(config.allowRenegotiation()), + enforce_rsa_key_usage_(config.enforceRsaKeyUsage()), + max_session_keys_(config.maxSessionKeys()) { + // This should be guaranteed during configuration ingestion for client contexts. + ASSERT(tls_contexts_.size() == 1); + if (!parsed_alpn_protocols_.empty()) { + for (auto& ctx : tls_contexts_) { + const int rc = SSL_CTX_set_alpn_protos(ctx.ssl_ctx_.get(), parsed_alpn_protocols_.data(), + parsed_alpn_protocols_.size()); + RELEASE_ASSERT(rc == 0, Utility::getLastCryptoError().value_or("")); + } + } + + if (max_session_keys_ > 0) { + SSL_CTX_set_session_cache_mode(tls_contexts_[0].ssl_ctx_.get(), SSL_SESS_CACHE_CLIENT); + SSL_CTX_sess_set_new_cb( + tls_contexts_[0].ssl_ctx_.get(), [](SSL* ssl, SSL_SESSION* session) -> int { + ContextImpl* context_impl = + static_cast(SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl))); + ClientContextImpl* client_context_impl = dynamic_cast(context_impl); + RELEASE_ASSERT(client_context_impl != nullptr, ""); // for Coverity + return client_context_impl->newSessionKey(session); + }); + } +} + +absl::StatusOr> +ClientContextImpl::newSsl(const Network::TransportSocketOptionsConstSharedPtr& options) { + absl::StatusOr> ssl_con_or_status(ContextImpl::newSsl(options)); + if (!ssl_con_or_status.ok()) { + return ssl_con_or_status; + } + + bssl::UniquePtr ssl_con = std::move(ssl_con_or_status.value()); + + const std::string server_name_indication = options && options->serverNameOverride().has_value() + ? options->serverNameOverride().value() + : server_name_indication_; + if (!server_name_indication.empty()) { + const int rc = SSL_set_tlsext_host_name(ssl_con.get(), server_name_indication.c_str()); + if (rc != 1) { + return absl::InvalidArgumentError( + absl::StrCat("Failed to create upstream TLS due to failure setting SNI: ", + Utility::getLastCryptoError().value_or("unknown"))); + } + } + + if (options && !options->verifySubjectAltNameListOverride().empty()) { + SSL_set_verify(ssl_con.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); + } + + // We determine what ALPN using the following precedence: + // 1. Option-provided ALPN override. + // 2. ALPN statically configured in the upstream TLS context. + // 3. Option-provided ALPN fallback. + + // At this point in the code the ALPN has already been set (if present) to the value specified in + // the TLS context. We've stored this value in parsed_alpn_protocols_ so we can check that to see + // if it's already been set. + bool has_alpn_defined = !parsed_alpn_protocols_.empty(); + if (options) { + // ALPN override takes precedence over TLS context specified, so blindly overwrite it. + has_alpn_defined |= parseAndSetAlpn(options->applicationProtocolListOverride(), *ssl_con); + } + + if (options && !has_alpn_defined && !options->applicationProtocolFallback().empty()) { + // If ALPN hasn't already been set (either through TLS context or override), use the fallback. + parseAndSetAlpn(options->applicationProtocolFallback(), *ssl_con); + } + + if (allow_renegotiation_) { + SSL_set_renegotiate_mode(ssl_con.get(), ssl_renegotiate_freely); + } + + SSL_set_enforce_rsa_key_usage(ssl_con.get(), enforce_rsa_key_usage_); + + if (max_session_keys_ > 0) { + if (session_keys_single_use_) { + // Stored single-use session keys, use write/write locks. + absl::WriterMutexLock l(&session_keys_mu_); + if (!session_keys_.empty()) { + // Use the most recently stored session key, since it has the highest + // probability of still being recognized/accepted by the server. + SSL_SESSION* session = session_keys_.front().get(); + SSL_set_session(ssl_con.get(), session); + // Remove single-use session key (TLS 1.3) after first use. + if (SSL_SESSION_should_be_single_use(session)) { + session_keys_.pop_front(); + } + } + } else { + // Never stored single-use session keys, use read/write locks. + absl::ReaderMutexLock l(&session_keys_mu_); + if (!session_keys_.empty()) { + // Use the most recently stored session key, since it has the highest + // probability of still being recognized/accepted by the server. + SSL_SESSION* session = session_keys_.front().get(); + SSL_set_session(ssl_con.get(), session); + } + } + } + + return ssl_con; +} + +int ClientContextImpl::newSessionKey(SSL_SESSION* session) { + // In case we ever store single-use session key (TLS 1.3), + // we need to switch to using write/write locks. + if (SSL_SESSION_should_be_single_use(session)) { + session_keys_single_use_ = true; + } + absl::WriterMutexLock l(&session_keys_mu_); + // Evict oldest entries. + while (session_keys_.size() >= max_session_keys_) { + session_keys_.pop_back(); + } + // Add new session key at the front of the queue, so that it's used first. + session_keys_.push_front(bssl::UniquePtr(session)); + return 1; // Tell BoringSSL that we took ownership of the session. +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/common/tls/client_context_impl.h b/source/common/tls/client_context_impl.h new file mode 100644 index 000000000000..372fed093a4e --- /dev/null +++ b/source/common/tls/client_context_impl.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "envoy/network/transport_socket.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +#include "source/common/common/matchers.h" +#include "source/common/stats/symbol_table.h" +#include "source/common/tls/cert_validator/cert_validator.h" +#include "source/common/tls/context_impl.h" +#include "source/common/tls/context_manager_impl.h" +#include "source/common/tls/ocsp/ocsp.h" +#include "source/common/tls/stats.h" + +#include "absl/synchronization/mutex.h" +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +#ifdef ENVOY_ENABLE_QUIC +#include "quiche/quic/core/crypto/proof_source.h" +#endif + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class ClientContextImpl : public ContextImpl, public Envoy::Ssl::ClientContext { +public: + ClientContextImpl(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, + Server::Configuration::CommonFactoryContext& factory_context); + + absl::StatusOr> + newSsl(const Network::TransportSocketOptionsConstSharedPtr& options) override; + +private: + int newSessionKey(SSL_SESSION* session); + + const std::string server_name_indication_; + const bool allow_renegotiation_; + const bool enforce_rsa_key_usage_; + const size_t max_session_keys_; + absl::Mutex session_keys_mu_; + std::deque> session_keys_ ABSL_GUARDED_BY(session_keys_mu_); + bool session_keys_single_use_{false}; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/common/tls/client_ssl_socket.cc b/source/common/tls/client_ssl_socket.cc new file mode 100644 index 000000000000..3246b354e369 --- /dev/null +++ b/source/common/tls/client_ssl_socket.cc @@ -0,0 +1,89 @@ +#include "source/common/tls/client_ssl_socket.h" + +#include "envoy/stats/scope.h" + +#include "source/common/common/assert.h" +#include "source/common/common/empty_string.h" +#include "source/common/common/hex.h" +#include "source/common/http/headers.h" +#include "source/common/runtime/runtime_features.h" +#include "source/common/tls/io_handle_bio.h" +#include "source/common/tls/ssl_handshaker.h" +#include "source/common/tls/utility.h" + +#include "absl/strings/str_replace.h" +#include "openssl/err.h" +#include "openssl/x509v3.h" + +using Envoy::Network::PostIoAction; + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +namespace { +SslSocketFactoryStats generateStats(Stats::Scope& store) { + return {ALL_SSL_SOCKET_FACTORY_STATS(POOL_COUNTER_PREFIX(store, "client_ssl_socket_factory."))}; +} +} // namespace + +ClientSslSocketFactory::ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPtr config, + Envoy::Ssl::ContextManager& manager, + Stats::Scope& stats_scope) + : manager_(manager), stats_scope_(stats_scope), stats_(generateStats(stats_scope)), + config_(std::move(config)), + ssl_ctx_(manager_.createSslClientContext(stats_scope_, *config_)) { + config_->setSecretUpdateCallback([this]() { onAddOrUpdateSecret(); }); +} + +ClientSslSocketFactory::~ClientSslSocketFactory() { manager_.removeContext(ssl_ctx_); } + +Network::TransportSocketPtr ClientSslSocketFactory::createTransportSocket( + Network::TransportSocketOptionsConstSharedPtr transport_socket_options, + Upstream::HostDescriptionConstSharedPtr) const { + // onAddOrUpdateSecret() could be invoked in the middle of checking the existence of ssl_ctx and + // creating SslSocket using ssl_ctx. Capture ssl_ctx_ into a local variable so that we check and + // use the same ssl_ctx to create SslSocket. + Envoy::Ssl::ClientContextSharedPtr ssl_ctx; + { + absl::ReaderMutexLock l(&ssl_ctx_mu_); + ssl_ctx = ssl_ctx_; + } + if (ssl_ctx) { + auto status_or_socket = + SslSocket::create(std::move(ssl_ctx), InitialState::Client, transport_socket_options, + config_->createHandshaker()); + if (status_or_socket.ok()) { + return std::move(status_or_socket.value()); + } + return std::make_unique(status_or_socket.status().message()); + } else { + ENVOY_LOG(debug, "Create NotReadySslSocket"); + stats_.upstream_context_secrets_not_ready_.inc(); + return std::make_unique(); + } +} + +bool ClientSslSocketFactory::implementsSecureTransport() const { return true; } + +void ClientSslSocketFactory::onAddOrUpdateSecret() { + ENVOY_LOG(debug, "Secret is updated."); + auto ctx = manager_.createSslClientContext(stats_scope_, *config_); + { + absl::WriterMutexLock l(&ssl_ctx_mu_); + std::swap(ctx, ssl_ctx_); + } + manager_.removeContext(ctx); + stats_.ssl_context_update_by_sds_.inc(); +} + +Envoy::Ssl::ClientContextSharedPtr ClientSslSocketFactory::sslCtx() { + absl::ReaderMutexLock l(&ssl_ctx_mu_); + return ssl_ctx_; +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/common/tls/client_ssl_socket.h b/source/common/tls/client_ssl_socket.h new file mode 100644 index 000000000000..e39e4b4a5cc4 --- /dev/null +++ b/source/common/tls/client_ssl_socket.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +#include "envoy/network/connection.h" +#include "envoy/network/transport_socket.h" +#include "envoy/secret/secret_callbacks.h" +#include "envoy/ssl/handshaker.h" +#include "envoy/ssl/private_key/private_key_callbacks.h" +#include "envoy/ssl/ssl_socket_extended_info.h" +#include "envoy/ssl/ssl_socket_state.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +#include "source/common/common/logger.h" +#include "source/common/network/transport_socket_options_impl.h" +#include "source/common/tls/context_impl.h" +#include "source/common/tls/ssl_handshaker.h" +#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/utility.h" + +#include "absl/container/node_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/optional.h" +#include "openssl/ssl.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class ClientSslSocketFactory : public Network::CommonUpstreamTransportSocketFactory, + public Secret::SecretCallbacks, + Logger::Loggable { +public: + ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPtr config, + Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope); + + ~ClientSslSocketFactory() override; + + Network::TransportSocketPtr + createTransportSocket(Network::TransportSocketOptionsConstSharedPtr options, + Upstream::HostDescriptionConstSharedPtr) const override; + bool implementsSecureTransport() const override; + absl::string_view defaultServerNameIndication() const override { + return clientContextConfig()->serverNameIndication(); + } + bool supportsAlpn() const override { return true; } + + // Secret::SecretCallbacks + void onAddOrUpdateSecret() override; + + OptRef clientContextConfig() const override { return {*config_}; } + + Envoy::Ssl::ClientContextSharedPtr sslCtx() override; + +private: + Envoy::Ssl::ContextManager& manager_; + Stats::Scope& stats_scope_; + SslSocketFactoryStats stats_; + Envoy::Ssl::ClientContextConfigPtr config_; + mutable absl::Mutex ssl_ctx_mu_; + Envoy::Ssl::ClientContextSharedPtr ssl_ctx_ ABSL_GUARDED_BY(ssl_ctx_mu_); +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/common/tls/context_impl.cc b/source/common/tls/context_impl.cc index 8aec9232a9c6..47b98c3f06e6 100644 --- a/source/common/tls/context_impl.cc +++ b/source/common/tls/context_impl.cc @@ -42,20 +42,6 @@ namespace Envoy { namespace { -bool cbsContainsU16(CBS& cbs, uint16_t n) { - while (CBS_len(&cbs) > 0) { - uint16_t v; - if (!CBS_get_u16(&cbs, &v)) { - return false; - } - if (v == n) { - return true; - } - } - - return false; -} - void logSslErrorChain() { while (uint64_t err = ERR_get_error()) { ENVOY_LOG_MISC(debug, "SSL error: {}:{}:{}:{}", err, @@ -424,20 +410,6 @@ int ContextImpl::sslSocketIndex() { }()); } -int ServerContextImpl::alpnSelectCallback(const unsigned char** out, unsigned char* outlen, - const unsigned char* in, unsigned int inlen) { - // Currently this uses the standard selection algorithm in priority order. - const uint8_t* alpn_data = parsed_alpn_protocols_.data(); - size_t alpn_data_size = parsed_alpn_protocols_.size(); - - if (SSL_select_next_proto(const_cast(out), outlen, alpn_data, alpn_data_size, in, - inlen) != OPENSSL_NPN_NEGOTIATED) { - return SSL_TLSEXT_ERR_NOACK; - } else { - return SSL_TLSEXT_ERR_OK; - } -} - std::vector ContextImpl::parseAlpnProtocols(const std::string& alpn_protocols) { if (alpn_protocols.empty()) { return {}; @@ -660,37 +632,6 @@ std::vector ContextImpl::getCertChainInformat return cert_details; } -ClientContextImpl::ClientContextImpl(Stats::Scope& scope, - const Envoy::Ssl::ClientContextConfig& config, - Server::Configuration::CommonFactoryContext& factory_context) - : ContextImpl(scope, config, factory_context, nullptr /* additional_init */), - server_name_indication_(config.serverNameIndication()), - allow_renegotiation_(config.allowRenegotiation()), - enforce_rsa_key_usage_(config.enforceRsaKeyUsage()), - max_session_keys_(config.maxSessionKeys()) { - // This should be guaranteed during configuration ingestion for client contexts. - ASSERT(tls_contexts_.size() == 1); - if (!parsed_alpn_protocols_.empty()) { - for (auto& ctx : tls_contexts_) { - const int rc = SSL_CTX_set_alpn_protos(ctx.ssl_ctx_.get(), parsed_alpn_protocols_.data(), - parsed_alpn_protocols_.size()); - RELEASE_ASSERT(rc == 0, Utility::getLastCryptoError().value_or("")); - } - } - - if (max_session_keys_ > 0) { - SSL_CTX_set_session_cache_mode(tls_contexts_[0].ssl_ctx_.get(), SSL_SESS_CACHE_CLIENT); - SSL_CTX_sess_set_new_cb( - tls_contexts_[0].ssl_ctx_.get(), [](SSL* ssl, SSL_SESSION* session) -> int { - ContextImpl* context_impl = - static_cast(SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl))); - ClientContextImpl* client_context_impl = dynamic_cast(context_impl); - RELEASE_ASSERT(client_context_impl != nullptr, ""); // for Coverity - return client_context_impl->newSessionKey(session); - }); - } -} - bool ContextImpl::parseAndSetAlpn(const std::vector& alpn, SSL& ssl) { std::vector parsed_alpn = parseAlpnProtocols(absl::StrJoin(alpn, ",")); if (!parsed_alpn.empty()) { @@ -703,702 +644,6 @@ bool ContextImpl::parseAndSetAlpn(const std::vector& alpn, SSL& ssl return false; } -absl::StatusOr> -ClientContextImpl::newSsl(const Network::TransportSocketOptionsConstSharedPtr& options) { - absl::StatusOr> ssl_con_or_status(ContextImpl::newSsl(options)); - if (!ssl_con_or_status.ok()) { - return ssl_con_or_status; - } - - bssl::UniquePtr ssl_con = std::move(ssl_con_or_status.value()); - - const std::string server_name_indication = options && options->serverNameOverride().has_value() - ? options->serverNameOverride().value() - : server_name_indication_; - if (!server_name_indication.empty()) { - const int rc = SSL_set_tlsext_host_name(ssl_con.get(), server_name_indication.c_str()); - if (rc != 1) { - return absl::InvalidArgumentError( - absl::StrCat("Failed to create upstream TLS due to failure setting SNI: ", - Utility::getLastCryptoError().value_or("unknown"))); - } - } - - if (options && !options->verifySubjectAltNameListOverride().empty()) { - SSL_set_verify(ssl_con.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); - } - - // We determine what ALPN using the following precedence: - // 1. Option-provided ALPN override. - // 2. ALPN statically configured in the upstream TLS context. - // 3. Option-provided ALPN fallback. - - // At this point in the code the ALPN has already been set (if present) to the value specified in - // the TLS context. We've stored this value in parsed_alpn_protocols_ so we can check that to see - // if it's already been set. - bool has_alpn_defined = !parsed_alpn_protocols_.empty(); - if (options) { - // ALPN override takes precedence over TLS context specified, so blindly overwrite it. - has_alpn_defined |= parseAndSetAlpn(options->applicationProtocolListOverride(), *ssl_con); - } - - if (options && !has_alpn_defined && !options->applicationProtocolFallback().empty()) { - // If ALPN hasn't already been set (either through TLS context or override), use the fallback. - parseAndSetAlpn(options->applicationProtocolFallback(), *ssl_con); - } - - if (allow_renegotiation_) { - SSL_set_renegotiate_mode(ssl_con.get(), ssl_renegotiate_freely); - } - - SSL_set_enforce_rsa_key_usage(ssl_con.get(), enforce_rsa_key_usage_); - - if (max_session_keys_ > 0) { - if (session_keys_single_use_) { - // Stored single-use session keys, use write/write locks. - absl::WriterMutexLock l(&session_keys_mu_); - if (!session_keys_.empty()) { - // Use the most recently stored session key, since it has the highest - // probability of still being recognized/accepted by the server. - SSL_SESSION* session = session_keys_.front().get(); - SSL_set_session(ssl_con.get(), session); - // Remove single-use session key (TLS 1.3) after first use. - if (SSL_SESSION_should_be_single_use(session)) { - session_keys_.pop_front(); - } - } - } else { - // Never stored single-use session keys, use read/write locks. - absl::ReaderMutexLock l(&session_keys_mu_); - if (!session_keys_.empty()) { - // Use the most recently stored session key, since it has the highest - // probability of still being recognized/accepted by the server. - SSL_SESSION* session = session_keys_.front().get(); - SSL_set_session(ssl_con.get(), session); - } - } - } - - return ssl_con; -} - -int ClientContextImpl::newSessionKey(SSL_SESSION* session) { - // In case we ever store single-use session key (TLS 1.3), - // we need to switch to using write/write locks. - if (SSL_SESSION_should_be_single_use(session)) { - session_keys_single_use_ = true; - } - absl::WriterMutexLock l(&session_keys_mu_); - // Evict oldest entries. - while (session_keys_.size() >= max_session_keys_) { - session_keys_.pop_back(); - } - // Add new session key at the front of the queue, so that it's used first. - session_keys_.push_front(bssl::UniquePtr(session)); - return 1; // Tell BoringSSL that we took ownership of the session. -} - -ServerContextImpl::ServerContextImpl(Stats::Scope& scope, - const Envoy::Ssl::ServerContextConfig& config, - const std::vector& server_names, - Server::Configuration::CommonFactoryContext& factory_context, - Ssl::ContextAdditionalInitFunc additional_init) - : ContextImpl(scope, config, factory_context, additional_init), - session_ticket_keys_(config.sessionTicketKeys()), - ocsp_staple_policy_(config.ocspStaplePolicy()), - full_scan_certs_on_sni_mismatch_(config.fullScanCertsOnSNIMismatch()) { - if (config.tlsCertificates().empty() && !config.capabilities().provides_certificates) { - throwEnvoyExceptionOrPanic("Server TlsCertificates must have a certificate specified"); - } - - for (auto& ctx : tls_contexts_) { - if (ctx.cert_chain_ == nullptr) { - continue; - } - bssl::UniquePtr public_key(X509_get_pubkey(ctx.cert_chain_.get())); - const int pkey_id = EVP_PKEY_id(public_key.get()); - // Load DNS SAN entries and Subject Common Name as server name patterns after certificate - // chain loaded, and populate ServerNamesMap which will be used to match SNI. - has_rsa_ |= (pkey_id == EVP_PKEY_RSA); - populateServerNamesMap(ctx, pkey_id); - } - - // Compute the session context ID hash. We use all the certificate identities, - // since we should have a common ID for session resumption no matter what cert - // is used. We do this early because it can throw an EnvoyException. - const SessionContextID session_id = generateHashForSessionContextId(server_names); - - // First, configure the base context for ClientHello interception. - // TODO(htuch): replace with SSL_IDENTITY when we have this as a means to do multi-cert in - // BoringSSL. - if (!config.capabilities().provides_certificates) { - SSL_CTX_set_select_certificate_cb( - tls_contexts_[0].ssl_ctx_.get(), - [](const SSL_CLIENT_HELLO* client_hello) -> ssl_select_cert_result_t { - return static_cast( - SSL_CTX_get_app_data(SSL_get_SSL_CTX(client_hello->ssl))) - ->selectTlsContext(client_hello); - }); - } - - const auto tls_certificates = config.tlsCertificates(); - - for (uint32_t i = 0; i < tls_certificates.size(); ++i) { - auto& ctx = tls_contexts_[i]; - if (!config.capabilities().verifies_peer_certificates) { - THROW_IF_NOT_OK(cert_validator_->addClientValidationContext( - ctx.ssl_ctx_.get(), config.requireClientCertificate())); - } - - if (!parsed_alpn_protocols_.empty() && !config.capabilities().handles_alpn_selection) { - SSL_CTX_set_alpn_select_cb( - ctx.ssl_ctx_.get(), - [](SSL*, const unsigned char** out, unsigned char* outlen, const unsigned char* in, - unsigned int inlen, void* arg) -> int { - return static_cast(arg)->alpnSelectCallback(out, outlen, in, inlen); - }, - this); - } - - // If the handshaker handles session tickets natively, don't call - // `SSL_CTX_set_tlsext_ticket_key_cb`. - if (config.disableStatelessSessionResumption()) { - SSL_CTX_set_options(ctx.ssl_ctx_.get(), SSL_OP_NO_TICKET); - } else if (!session_ticket_keys_.empty() && !config.capabilities().handles_session_resumption) { - SSL_CTX_set_tlsext_ticket_key_cb( - ctx.ssl_ctx_.get(), - [](SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, - int encrypt) -> int { - ContextImpl* context_impl = - static_cast(SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl))); - ServerContextImpl* server_context_impl = dynamic_cast(context_impl); - RELEASE_ASSERT(server_context_impl != nullptr, ""); // for Coverity - return server_context_impl->sessionTicketProcess(ssl, key_name, iv, ctx, hmac_ctx, - encrypt); - }); - } - - if (config.disableStatefulSessionResumption()) { - SSL_CTX_set_session_cache_mode(ctx.ssl_ctx_.get(), SSL_SESS_CACHE_OFF); - } - - if (config.sessionTimeout() && !config.capabilities().handles_session_resumption) { - auto timeout = config.sessionTimeout().value().count(); - SSL_CTX_set_timeout(ctx.ssl_ctx_.get(), uint32_t(timeout)); - } - - int rc = - SSL_CTX_set_session_id_context(ctx.ssl_ctx_.get(), session_id.data(), session_id.size()); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - - auto& ocsp_resp_bytes = tls_certificates[i].get().ocspStaple(); - if (ocsp_resp_bytes.empty()) { - if (ctx.is_must_staple_) { - throwEnvoyExceptionOrPanic("OCSP response is required for must-staple certificate"); - } - if (ocsp_staple_policy_ == Ssl::ServerContextConfig::OcspStaplePolicy::MustStaple) { - throwEnvoyExceptionOrPanic("Required OCSP response is missing from TLS context"); - } - } else { - auto response_or_error = - Ocsp::OcspResponseWrapper::create(ocsp_resp_bytes, factory_context_.timeSource()); - THROW_IF_STATUS_NOT_OK(response_or_error, throw); - if (!response_or_error.value()->matchesCertificate(*ctx.cert_chain_)) { - throwEnvoyExceptionOrPanic("OCSP response does not match its TLS certificate"); - } - ctx.ocsp_response_ = std::move(response_or_error.value()); - } - } -} - -void ServerContextImpl::populateServerNamesMap(Ssl::TlsContext& ctx, int pkey_id) { - if (ctx.cert_chain_ == nullptr) { - return; - } - - auto populate = [&](const std::string& sn) { - std::string sn_pattern = sn; - if (absl::StartsWith(sn, "*.")) { - sn_pattern = sn.substr(1); - } - PkeyTypesMap pkey_types_map; - // Multiple certs with different key type are allowed for one server name pattern. - auto sn_match = server_names_map_.try_emplace(sn_pattern, pkey_types_map).first; - auto pt_match = sn_match->second.find(pkey_id); - if (pt_match != sn_match->second.end()) { - // When there are duplicate names, prefer the earlier one. - // - // If all of the SANs in a certificate are unused due to duplicates, it could be useful - // to issue a warning, but that would require additional tracking that hasn't been - // implemented. - return; - } - sn_match->second.emplace(std::pair>(pkey_id, ctx)); - }; - - bssl::UniquePtr san_names(static_cast( - X509_get_ext_d2i(ctx.cert_chain_.get(), NID_subject_alt_name, nullptr, nullptr))); - if (san_names != nullptr) { - auto dns_sans = Utility::getSubjectAltNames(*ctx.cert_chain_, GEN_DNS); - // https://www.rfc-editor.org/rfc/rfc6066#section-3 - // Currently, the only server names supported are DNS hostnames, so we - // only save dns san entries to match SNI. - for (const auto& san : dns_sans) { - populate(san); - } - } else { - // https://www.rfc-editor.org/rfc/rfc6125#section-6.4.4 - // As noted, a client MUST NOT seek a match for a reference identifier - // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, - // URI-ID, or any application-specific identifier types supported by the - // client. - X509_NAME* cert_subject = X509_get_subject_name(ctx.cert_chain_.get()); - const int cn_index = X509_NAME_get_index_by_NID(cert_subject, NID_commonName, -1); - if (cn_index >= 0) { - X509_NAME_ENTRY* cn_entry = X509_NAME_get_entry(cert_subject, cn_index); - if (cn_entry) { - ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); - if (ASN1_STRING_length(cn_asn1) > 0) { - std::string subject_cn(reinterpret_cast(ASN1_STRING_data(cn_asn1)), - ASN1_STRING_length(cn_asn1)); - populate(subject_cn); - } - } - } - } -} - -ServerContextImpl::SessionContextID -ServerContextImpl::generateHashForSessionContextId(const std::vector& server_names) { - uint8_t hash_buffer[EVP_MAX_MD_SIZE]; - unsigned hash_length = 0; - - bssl::ScopedEVP_MD_CTX md; - - int rc = EVP_DigestInit(md.get(), EVP_sha256()); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - - // Hash the CommonName/SANs of all the server certificates. This makes sure that sessions can only - // be resumed to certificate(s) for the same name(s), but allows resuming to unique certs in the - // case that different Envoy instances each have their own certs. All certificates in a - // ServerContextImpl context are hashed together, since they all constitute a match on a filter - // chain for resumption purposes. - if (!capabilities_.provides_certificates) { - for (const auto& ctx : tls_contexts_) { - X509* cert = SSL_CTX_get0_certificate(ctx.ssl_ctx_.get()); - RELEASE_ASSERT(cert != nullptr, "TLS context should have an active certificate"); - X509_NAME* cert_subject = X509_get_subject_name(cert); - RELEASE_ASSERT(cert_subject != nullptr, "TLS certificate should have a subject"); - - const int cn_index = X509_NAME_get_index_by_NID(cert_subject, NID_commonName, -1); - if (cn_index >= 0) { - X509_NAME_ENTRY* cn_entry = X509_NAME_get_entry(cert_subject, cn_index); - RELEASE_ASSERT(cn_entry != nullptr, "certificate subject CN should be present"); - - ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); - if (ASN1_STRING_length(cn_asn1) <= 0) { - throwEnvoyExceptionOrPanic("Invalid TLS context has an empty subject CN"); - } - - rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(cn_asn1), ASN1_STRING_length(cn_asn1)); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - } - - unsigned san_count = 0; - bssl::UniquePtr san_names(static_cast( - X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); - - if (san_names != nullptr) { - for (const GENERAL_NAME* san : san_names.get()) { - switch (san->type) { - case GEN_IPADD: - rc = EVP_DigestUpdate(md.get(), san->d.iPAddress->data, san->d.iPAddress->length); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - ++san_count; - break; - case GEN_DNS: - rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(san->d.dNSName), - ASN1_STRING_length(san->d.dNSName)); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - ++san_count; - break; - case GEN_URI: - rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(san->d.uniformResourceIdentifier), - ASN1_STRING_length(san->d.uniformResourceIdentifier)); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - ++san_count; - break; - } - } - } - - // It's possible that the certificate doesn't have a subject, but - // does have SANs. Make sure that we have one or the other. - if (cn_index < 0 && san_count == 0) { - throwEnvoyExceptionOrPanic("Invalid TLS context has neither subject CN nor SAN names"); - } - - rc = X509_NAME_digest(X509_get_issuer_name(cert), EVP_sha256(), hash_buffer, &hash_length); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - RELEASE_ASSERT(hash_length == SHA256_DIGEST_LENGTH, - fmt::format("invalid SHA256 hash length {}", hash_length)); - - rc = EVP_DigestUpdate(md.get(), hash_buffer, hash_length); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - } - } - - cert_validator_->updateDigestForSessionId(md, hash_buffer, hash_length); - - // Hash configured SNIs for this context, so that sessions cannot be resumed across different - // filter chains, even when using the same server certificate. - for (const auto& name : server_names) { - rc = EVP_DigestUpdate(md.get(), name.data(), name.size()); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - } - - SessionContextID session_id; - - // Ensure that the output size of the hash we are using is no greater than - // TLS session ID length that we want to generate. - static_assert(session_id.size() == SHA256_DIGEST_LENGTH, "hash size mismatch"); - static_assert(session_id.size() == SSL_MAX_SSL_SESSION_ID_LENGTH, "TLS session ID size mismatch"); - - rc = EVP_DigestFinal(md.get(), session_id.data(), &hash_length); - RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); - RELEASE_ASSERT(hash_length == session_id.size(), - "SHA256 hash length must match TLS Session ID size"); - - return session_id; -} - -int ServerContextImpl::sessionTicketProcess(SSL*, uint8_t* key_name, uint8_t* iv, - EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, int encrypt) { - const EVP_MD* hmac = EVP_sha256(); - const EVP_CIPHER* cipher = EVP_aes_256_cbc(); - - if (encrypt == 1) { - // Encrypt - RELEASE_ASSERT(!session_ticket_keys_.empty(), ""); - // TODO(ggreenway): validate in SDS that session_ticket_keys_ cannot be empty, - // or if we allow it to be emptied, reconfigure the context so this callback - // isn't set. - - const Envoy::Ssl::ServerContextConfig::SessionTicketKey& key = session_ticket_keys_.front(); - - static_assert(std::tuple_size::value == SSL_TICKET_KEY_NAME_LEN, - "Expected key.name length"); - std::copy_n(key.name_.begin(), SSL_TICKET_KEY_NAME_LEN, key_name); - - const int rc = RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)); - ASSERT(rc); - - // This RELEASE_ASSERT is logically a static_assert, but we can't actually get - // EVP_CIPHER_key_length(cipher) at compile-time - RELEASE_ASSERT(key.aes_key_.size() == EVP_CIPHER_key_length(cipher), ""); - if (!EVP_EncryptInit_ex(ctx, cipher, nullptr, key.aes_key_.data(), iv)) { - return -1; - } - - if (!HMAC_Init_ex(hmac_ctx, key.hmac_key_.data(), key.hmac_key_.size(), hmac, nullptr)) { - return -1; - } - - return 1; // success - } else { - // Decrypt - bool is_enc_key = true; // first element is the encryption key - for (const Envoy::Ssl::ServerContextConfig::SessionTicketKey& key : session_ticket_keys_) { - static_assert(std::tuple_size::value == SSL_TICKET_KEY_NAME_LEN, - "Expected key.name length"); - if (std::equal(key.name_.begin(), key.name_.end(), key_name)) { - if (!HMAC_Init_ex(hmac_ctx, key.hmac_key_.data(), key.hmac_key_.size(), hmac, nullptr)) { - return -1; - } - - RELEASE_ASSERT(key.aes_key_.size() == EVP_CIPHER_key_length(cipher), ""); - if (!EVP_DecryptInit_ex(ctx, cipher, nullptr, key.aes_key_.data(), iv)) { - return -1; - } - - // If our current encryption was not the decryption key, renew - return is_enc_key ? 1 // success; do not renew - : 2; // success: renew key - } - is_enc_key = false; - } - - return 0; // decryption failed - } -} - -bool ServerContextImpl::isClientEcdsaCapable(const SSL_CLIENT_HELLO* ssl_client_hello) { - CBS client_hello; - CBS_init(&client_hello, ssl_client_hello->client_hello, ssl_client_hello->client_hello_len); - - // This is the TLSv1.3 case (TLSv1.2 on the wire and the supported_versions extensions present). - // We just need to look at signature algorithms. - const uint16_t client_version = ssl_client_hello->version; - if (client_version == TLS1_2_VERSION && tls_max_version_ == TLS1_3_VERSION) { - // If the supported_versions extension is found then we assume that the client is competent - // enough that just checking the signature_algorithms is sufficient. - const uint8_t* supported_versions_data; - size_t supported_versions_len; - if (SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_supported_versions, - &supported_versions_data, &supported_versions_len)) { - const uint8_t* signature_algorithms_data; - size_t signature_algorithms_len; - if (SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_signature_algorithms, - &signature_algorithms_data, - &signature_algorithms_len)) { - CBS signature_algorithms_ext, signature_algorithms; - CBS_init(&signature_algorithms_ext, signature_algorithms_data, signature_algorithms_len); - if (!CBS_get_u16_length_prefixed(&signature_algorithms_ext, &signature_algorithms) || - CBS_len(&signature_algorithms_ext) != 0) { - return false; - } - if (cbsContainsU16(signature_algorithms, SSL_SIGN_ECDSA_SECP256R1_SHA256)) { - return true; - } - } - - return false; - } - } - - // Otherwise we are < TLSv1.3 and need to look at both the curves in the supported_groups for - // ECDSA and also for a compatible cipher suite. https://tools.ietf.org/html/rfc4492#section-5.1.1 - const uint8_t* curvelist_data; - size_t curvelist_len; - if (!SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_supported_groups, - &curvelist_data, &curvelist_len)) { - return false; - } - - CBS curvelist; - CBS_init(&curvelist, curvelist_data, curvelist_len); - - // We only support P256 ECDSA curves today. - if (!cbsContainsU16(curvelist, SSL_CURVE_SECP256R1)) { - return false; - } - - // The client must have offered an ECDSA ciphersuite that we like. - CBS cipher_suites; - CBS_init(&cipher_suites, ssl_client_hello->cipher_suites, ssl_client_hello->cipher_suites_len); - - while (CBS_len(&cipher_suites) > 0) { - uint16_t cipher_id; - if (!CBS_get_u16(&cipher_suites, &cipher_id)) { - return false; - } - // All tls_context_ share the same set of enabled ciphers, so we can just look at the base - // context. - if (tls_contexts_[0].isCipherEnabled(cipher_id, client_version)) { - return true; - } - } - - return false; -} - -bool ServerContextImpl::isClientOcspCapable(const SSL_CLIENT_HELLO* ssl_client_hello) { - const uint8_t* status_request_data; - size_t status_request_len; - if (SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_status_request, - &status_request_data, &status_request_len)) { - return true; - } - - return false; -} - -OcspStapleAction ServerContextImpl::ocspStapleAction(const Ssl::TlsContext& ctx, - bool client_ocsp_capable) { - if (!client_ocsp_capable) { - return OcspStapleAction::ClientNotCapable; - } - - auto& response = ctx.ocsp_response_; - - auto policy = ocsp_staple_policy_; - if (ctx.is_must_staple_) { - // The certificate has the must-staple extension, so upgrade the policy to match. - policy = Ssl::ServerContextConfig::OcspStaplePolicy::MustStaple; - } - - const bool valid_response = response && !response->isExpired(); - - switch (policy) { - case Ssl::ServerContextConfig::OcspStaplePolicy::LenientStapling: - if (!valid_response) { - return OcspStapleAction::NoStaple; - } - return OcspStapleAction::Staple; - - case Ssl::ServerContextConfig::OcspStaplePolicy::StrictStapling: - if (valid_response) { - return OcspStapleAction::Staple; - } - if (response) { - // Expired response. - return OcspStapleAction::Fail; - } - return OcspStapleAction::NoStaple; - - case Ssl::ServerContextConfig::OcspStaplePolicy::MustStaple: - if (!valid_response) { - return OcspStapleAction::Fail; - } - return OcspStapleAction::Staple; - } - PANIC_DUE_TO_CORRUPT_ENUM; -} - -std::pair -ServerContextImpl::findTlsContext(absl::string_view sni, bool client_ecdsa_capable, - bool client_ocsp_capable, bool* cert_matched_sni) { - bool unused = false; - if (cert_matched_sni == nullptr) { - // Avoid need for nullptr checks when this is set. - cert_matched_sni = &unused; - } - - // selected_ctx represents the final selected certificate, it should meet all requirements or pick - // a candidate. - const Ssl::TlsContext* selected_ctx = nullptr; - const Ssl::TlsContext* candidate_ctx = nullptr; - OcspStapleAction ocsp_staple_action; - - auto selected = [&](const Ssl::TlsContext& ctx) -> bool { - auto action = ocspStapleAction(ctx, client_ocsp_capable); - if (action == OcspStapleAction::Fail) { - // The selected ctx must adhere to OCSP policy - return false; - } - - if (client_ecdsa_capable == ctx.is_ecdsa_) { - selected_ctx = &ctx; - ocsp_staple_action = action; - return true; - } - - if (client_ecdsa_capable && !ctx.is_ecdsa_ && candidate_ctx == nullptr) { - // ECDSA cert is preferred if client is ECDSA capable, so RSA cert is marked as a candidate, - // searching will continue until exhausting all certs or find a exact match. - candidate_ctx = &ctx; - ocsp_staple_action = action; - return false; - } - - return false; - }; - - auto select_from_map = [this, &selected](absl::string_view server_name) -> void { - auto it = server_names_map_.find(server_name); - if (it == server_names_map_.end()) { - return; - } - const auto& pkey_types_map = it->second; - for (const auto& entry : pkey_types_map) { - if (selected(entry.second.get())) { - break; - } - } - }; - - auto tail_select = [&](bool go_to_next_phase) { - if (selected_ctx == nullptr) { - selected_ctx = candidate_ctx; - } - - if (selected_ctx == nullptr && !go_to_next_phase) { - selected_ctx = &tls_contexts_[0]; - ocsp_staple_action = ocspStapleAction(*selected_ctx, client_ocsp_capable); - } - }; - - // Select cert based on SNI if SNI is provided by client. - if (!sni.empty()) { - // Match on exact server name, i.e. "www.example.com" for "www.example.com". - select_from_map(sni); - tail_select(true); - - if (selected_ctx == nullptr) { - // Match on wildcard domain, i.e. ".example.com" for "www.example.com". - // https://datatracker.ietf.org/doc/html/rfc6125#section-6.4 - size_t pos = sni.find('.', 1); - if (pos < sni.size() - 1 && pos != std::string::npos) { - absl::string_view wildcard = sni.substr(pos); - select_from_map(wildcard); - } - } - *cert_matched_sni = (selected_ctx != nullptr || candidate_ctx != nullptr); - tail_select(full_scan_certs_on_sni_mismatch_); - } - // Full scan certs if SNI is not provided by client; - // Full scan certs if client provides SNI but no cert matches to it, - // it requires full_scan_certs_on_sni_mismatch is enabled. - if (selected_ctx == nullptr) { - candidate_ctx = nullptr; - // Skip loop when there is no cert compatible to key type - if (client_ecdsa_capable || (!client_ecdsa_capable && has_rsa_)) { - for (const auto& ctx : tls_contexts_) { - if (selected(ctx)) { - break; - } - } - } - tail_select(false); - } - - ASSERT(selected_ctx != nullptr); - return {*selected_ctx, ocsp_staple_action}; -} - -enum ssl_select_cert_result_t -ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { - absl::string_view sni = absl::NullSafeStringView( - SSL_get_servername(ssl_client_hello->ssl, TLSEXT_NAMETYPE_host_name)); - const bool client_ecdsa_capable = isClientEcdsaCapable(ssl_client_hello); - const bool client_ocsp_capable = isClientOcspCapable(ssl_client_hello); - - auto [selected_ctx, ocsp_staple_action] = - findTlsContext(sni, client_ecdsa_capable, client_ocsp_capable, nullptr); - - // Apply the selected context. This must be done before OCSP stapling below - // since applying the context can remove the previously-set OCSP response. - // This will only return NULL if memory allocation fails. - RELEASE_ASSERT(SSL_set_SSL_CTX(ssl_client_hello->ssl, selected_ctx.ssl_ctx_.get()) != nullptr, - ""); - - if (client_ocsp_capable) { - stats_.ocsp_staple_requests_.inc(); - } - - switch (ocsp_staple_action) { - case OcspStapleAction::Staple: { - // We avoid setting the OCSP response if the client didn't request it, but doing so is safe. - RELEASE_ASSERT(selected_ctx.ocsp_response_, - "OCSP response must be present under OcspStapleAction::Staple"); - auto& resp_bytes = selected_ctx.ocsp_response_->rawBytes(); - int rc = SSL_set_ocsp_response(ssl_client_hello->ssl, resp_bytes.data(), resp_bytes.size()); - RELEASE_ASSERT(rc != 0, ""); - stats_.ocsp_staple_responses_.inc(); - } break; - case OcspStapleAction::NoStaple: - stats_.ocsp_staple_omitted_.inc(); - break; - case OcspStapleAction::Fail: - stats_.ocsp_staple_failed_.inc(); - return ssl_select_cert_error; - case OcspStapleAction::ClientNotCapable: - break; - } - - return ssl_select_cert_success; -} - ValidationResults ContextImpl::customVerifyCertChainForQuic( STACK_OF(X509)& cert_chain, Ssl::ValidateResultCallbackPtr callback, bool is_server, const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, diff --git a/source/common/tls/context_impl.h b/source/common/tls/context_impl.h index b445df66e3c3..07f41f10f1ba 100644 --- a/source/common/tls/context_impl.h +++ b/source/common/tls/context_impl.h @@ -172,77 +172,6 @@ class ContextImpl : public virtual Envoy::Ssl::Context, using ContextImplSharedPtr = std::shared_ptr; -class ClientContextImpl : public ContextImpl, public Envoy::Ssl::ClientContext { -public: - ClientContextImpl(Stats::Scope& scope, const Envoy::Ssl::ClientContextConfig& config, - Server::Configuration::CommonFactoryContext& factory_context); - - absl::StatusOr> - newSsl(const Network::TransportSocketOptionsConstSharedPtr& options) override; - -private: - int newSessionKey(SSL_SESSION* session); - - const std::string server_name_indication_; - const bool allow_renegotiation_; - const bool enforce_rsa_key_usage_; - const size_t max_session_keys_; - absl::Mutex session_keys_mu_; - std::deque> session_keys_ ABSL_GUARDED_BY(session_keys_mu_); - bool session_keys_single_use_{false}; -}; - -enum class OcspStapleAction { Staple, NoStaple, Fail, ClientNotCapable }; - -class ServerContextImpl : public ContextImpl, public Envoy::Ssl::ServerContext { -public: - ServerContextImpl(Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config, - const std::vector& server_names, - Server::Configuration::CommonFactoryContext& factory_context, - Ssl::ContextAdditionalInitFunc additional_init); - - // Select the TLS certificate context in SSL_CTX_set_select_certificate_cb() callback with - // ClientHello details. This is made public for use by custom TLS extensions who want to - // manually create and use this as a client hello callback. - enum ssl_select_cert_result_t selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello); - - // Finds the best matching context. The returned context will have the same lifetime as - // this ``ServerContextImpl``. - std::pair findTlsContext(absl::string_view sni, - bool client_ecdsa_capable, - bool client_ocsp_capable, - bool* cert_matched_sni); - -private: - // Currently, at most one certificate of a given key type may be specified for each exact - // server name or wildcard domain name. - using PkeyTypesMap = absl::flat_hash_map>; - // Both exact server names and wildcard domains are part of the same map, in which wildcard - // domains are prefixed with "." (i.e. ".example.com" for "*.example.com") to differentiate - // between exact and wildcard entries. - using ServerNamesMap = absl::flat_hash_map; - - void populateServerNamesMap(Ssl::TlsContext& ctx, const int pkey_id); - - using SessionContextID = std::array; - - int alpnSelectCallback(const unsigned char** out, unsigned char* outlen, const unsigned char* in, - unsigned int inlen); - int sessionTicketProcess(SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, - HMAC_CTX* hmac_ctx, int encrypt); - bool isClientEcdsaCapable(const SSL_CLIENT_HELLO* ssl_client_hello); - bool isClientOcspCapable(const SSL_CLIENT_HELLO* ssl_client_hello); - OcspStapleAction ocspStapleAction(const Ssl::TlsContext& ctx, bool client_ocsp_capable); - - SessionContextID generateHashForSessionContextId(const std::vector& server_names); - - const std::vector session_ticket_keys_; - const Ssl::ServerContextConfig::OcspStaplePolicy ocsp_staple_policy_; - ServerNamesMap server_names_map_; - bool has_rsa_{false}; - bool full_scan_certs_on_sni_mismatch_; -}; - } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/source/common/tls/context_manager_impl.cc b/source/common/tls/context_manager_impl.cc index 1c0e8d027aeb..45d65aa85108 100644 --- a/source/common/tls/context_manager_impl.cc +++ b/source/common/tls/context_manager_impl.cc @@ -8,7 +8,9 @@ #include "envoy/stats/scope.h" #include "source/common/common/assert.h" +#include "source/common/tls/client_context_impl.h" #include "source/common/tls/context_impl.h" +#include "source/common/tls/server_context_impl.h" namespace Envoy { namespace Extensions { diff --git a/source/common/tls/ocsp/asn1_utility.cc b/source/common/tls/ocsp/asn1_utility.cc index 5e1c606978af..df21b7d1b835 100644 --- a/source/common/tls/ocsp/asn1_utility.cc +++ b/source/common/tls/ocsp/asn1_utility.cc @@ -27,34 +27,34 @@ absl::string_view Asn1Utility::cbsToString(CBS& cbs) { return {str_head, CBS_len(&cbs)}; } -ParsingResult> Asn1Utility::getOptional(CBS& cbs, unsigned tag) { +absl::StatusOr> Asn1Utility::getOptional(CBS& cbs, unsigned tag) { int is_present; CBS data; if (!CBS_get_optional_asn1(&cbs, &data, &is_present, tag)) { - return "Failed to parse ASN.1 element tag"; + return absl::InvalidArgumentError("Failed to parse ASN.1 element tag"); } return is_present ? absl::optional(data) : absl::nullopt; } -ParsingResult Asn1Utility::parseOid(CBS& cbs) { +absl::StatusOr Asn1Utility::parseOid(CBS& cbs) { CBS oid; if (!CBS_get_asn1(&cbs, &oid, CBS_ASN1_OBJECT)) { - return absl::string_view("Input is not a well-formed ASN.1 OBJECT"); + return absl::InvalidArgumentError("Input is not a well-formed ASN.1 OBJECT"); } CSmartPtr oid_text(CBS_asn1_oid_to_text(&oid)); if (oid_text == nullptr) { - return absl::string_view("Failed to parse oid"); + return absl::InvalidArgumentError("Failed to parse oid"); } std::string oid_text_str(oid_text.get()); return oid_text_str; } -ParsingResult Asn1Utility::parseGeneralizedTime(CBS& cbs) { +absl::StatusOr Asn1Utility::parseGeneralizedTime(CBS& cbs) { CBS elem; if (!CBS_get_asn1(&cbs, &elem, CBS_ASN1_GENERALIZEDTIME)) { - return "Input is not a well-formed ASN.1 GENERALIZEDTIME"; + return absl::InvalidArgumentError("Input is not a well-formed ASN.1 GENERALIZEDTIME"); } auto time_str = cbsToString(elem); @@ -64,24 +64,24 @@ ParsingResult Asn1Utility::parseGeneralizedTime(CBS& cbs) { // `GENERALIZEDTIME` spec, are not supported. // Reference: https://tools.ietf.org/html/rfc5280#section-4.1.2.5.2 if (time_str.length() > 0 && absl::ascii_toupper(time_str.at(time_str.length() - 1)) != 'Z') { - return "GENERALIZEDTIME must be in UTC"; + return absl::InvalidArgumentError("GENERALIZEDTIME must be in UTC"); } absl::Time time; auto utc_time_str = time_str.substr(0, time_str.length() - 1); std::string parse_error; if (!absl::ParseTime(GENERALIZED_TIME_FORMAT, utc_time_str, &time, &parse_error)) { - return "Error parsing string of GENERALIZEDTIME format"; + return absl::InvalidArgumentError("Error parsing string of GENERALIZEDTIME format"); } return absl::ToChronoTime(time); } // Performs the following conversions to go from bytestring to hex integer // `CBS` -> `ASN1_INTEGER` -> `BIGNUM` -> String. -ParsingResult Asn1Utility::parseInteger(CBS& cbs) { +absl::StatusOr Asn1Utility::parseInteger(CBS& cbs) { CBS num; if (!CBS_get_asn1(&cbs, &num, CBS_ASN1_INTEGER)) { - return absl::string_view("Input is not a well-formed ASN.1 INTEGER"); + return absl::InvalidArgumentError("Input is not a well-formed ASN.1 INTEGER"); } auto head = CBS_data(&num); @@ -100,29 +100,29 @@ ParsingResult Asn1Utility::parseInteger(CBS& cbs) { } } - return absl::string_view("Failed to parse ASN.1 INTEGER"); + return absl::InvalidArgumentError("Failed to parse ASN.1 INTEGER"); } -ParsingResult> Asn1Utility::parseOctetString(CBS& cbs) { +absl::StatusOr> Asn1Utility::parseOctetString(CBS& cbs) { CBS value; if (!CBS_get_asn1(&cbs, &value, CBS_ASN1_OCTETSTRING)) { - return "Input is not a well-formed ASN.1 OCTETSTRING"; + return absl::InvalidArgumentError("Input is not a well-formed ASN.1 OCTETSTRING"); } auto data = reinterpret_cast(CBS_data(&value)); return std::vector{data, data + CBS_len(&value)}; } -ParsingResult Asn1Utility::skipOptional(CBS& cbs, unsigned tag) { +absl::StatusOr Asn1Utility::skipOptional(CBS& cbs, unsigned tag) { if (!CBS_get_optional_asn1(&cbs, nullptr, nullptr, tag)) { - return "Failed to parse ASN.1 element tag"; + return absl::InvalidArgumentError("Failed to parse ASN.1 element tag"); } return absl::monostate(); } -ParsingResult Asn1Utility::skip(CBS& cbs, unsigned tag) { +absl::StatusOr Asn1Utility::skip(CBS& cbs, unsigned tag) { if (!CBS_get_asn1(&cbs, nullptr, tag)) { - return "Failed to parse ASN.1 element"; + return absl::InvalidArgumentError("Failed to parse ASN.1 element"); } return absl::monostate(); diff --git a/source/common/tls/ocsp/asn1_utility.h b/source/common/tls/ocsp/asn1_utility.h index 061b0fc5f7a9..83e85796c49a 100644 --- a/source/common/tls/ocsp/asn1_utility.h +++ b/source/common/tls/ocsp/asn1_utility.h @@ -9,6 +9,7 @@ #include "source/common/common/assert.h" +#include "absl/status/statusor.h" #include "absl/types/optional.h" #include "absl/types/variant.h" #include "openssl/bn.h" @@ -23,17 +24,11 @@ namespace Ocsp { constexpr absl::string_view GENERALIZED_TIME_FORMAT = "%E4Y%m%d%H%M%S"; -/** - * The result of parsing an `ASN.1` structure or an `absl::string_view` error - * description. - */ -template using ParsingResult = absl::variant; - /** * Construct a `T` from the data contained in the CBS&. Functions * of this type must advance the input CBS& over the element. */ -template using Asn1ParsingFunc = std::function(CBS&)>; +template using Asn1ParsingFunc = std::function(CBS&)>; /** * Utility functions for parsing DER-encoded `ASN.1` objects. @@ -71,12 +66,12 @@ class Asn1Utility { * * @param cbs a CBS& that refers to an `ASN.1` SEQUENCE OF object * @param parse_element an `Asn1ParsingFunc` used to parse each element - * @returns ParsingResult> containing the parsed elements of the sequence + * @returns absl::StatusOr> containing the parsed elements of the sequence * or an error string if `cbs` does not point to a well-formed * SEQUENCE OF object. */ template - static ParsingResult> parseSequenceOf(CBS& cbs, Asn1ParsingFunc parse_element); + static absl::StatusOr> parseSequenceOf(CBS& cbs, Asn1ParsingFunc parse_element); /** * Checks if an explicitly tagged optional element of `tag` is present and @@ -85,12 +80,12 @@ class Asn1Utility { * * @param cbs a CBS& that refers to the current document position * @param parse_data an `Asn1ParsingFunc` used to parse the data if present - * @return ParsingResult> with a `T` if `cbs` is of the specified tag, + * @return absl::StatusOr> with a `T` if `cbs` is of the specified tag, * nullopt if the element has a different tag, or an error string if parsing fails. */ template - static ParsingResult> parseOptional(CBS& cbs, Asn1ParsingFunc parse_data, - unsigned tag); + static absl::StatusOr> parseOptional(CBS& cbs, Asn1ParsingFunc parse_data, + unsigned tag); /** * Returns whether or not an element explicitly tagged with `tag` is present @@ -101,25 +96,25 @@ class Asn1Utility { * @param cbs a CBS& that refers to the current document position * @param an unsigned explicit tag indicating an optional value * - * @returns ParsingResult whether `cbs` points to an element tagged with `tag` or + * @returns absl::StatusOr whether `cbs` points to an element tagged with `tag` or * an error string if parsing fails. */ - static ParsingResult> getOptional(CBS& cbs, unsigned tag); + static absl::StatusOr> getOptional(CBS& cbs, unsigned tag); /** * @param cbs a CBS& that refers to an `ASN.1` OBJECT IDENTIFIER element - * @returns ParsingResult the `OID` encoded in `cbs` or an error + * @returns absl::StatusOr the `OID` encoded in `cbs` or an error * string if `cbs` does not point to a well-formed OBJECT IDENTIFIER */ - static ParsingResult parseOid(CBS& cbs); + static absl::StatusOr parseOid(CBS& cbs); /** * @param cbs a CBS& that refers to an `ASN.1` `GENERALIZEDTIME` element - * @returns ParsingResult the UTC timestamp encoded in `cbs` + * @returns absl::StatusOr the UTC timestamp encoded in `cbs` * or an error string if `cbs` does not point to a well-formed * `GENERALIZEDTIME` */ - static ParsingResult parseGeneralizedTime(CBS& cbs); + static absl::StatusOr parseGeneralizedTime(CBS& cbs); /** * Parses an `ASN.1` INTEGER type into its hex string representation. @@ -128,17 +123,17 @@ class Asn1Utility { * use `CBS_get_asn1_*` functions for the given integer type instead. * * @param cbs a CBS& that refers to an `ASN.1` INTEGER element - * @returns ParsingResult a hex representation of the integer + * @returns absl::StatusOr a hex representation of the integer * or an error string if `cbs` does not point to a well-formed INTEGER */ - static ParsingResult parseInteger(CBS& cbs); + static absl::StatusOr parseInteger(CBS& cbs); /** * @param cbs a CBS& that refers to an `ASN.1` `OCTETSTRING` element - * @returns ParsingResult> the octets in `cbs` or + * @returns absl::StatusOr> the octets in `cbs` or * an error string if `cbs` does not point to a well-formed `OCTETSTRING` */ - static ParsingResult> parseOctetString(CBS& cbs); + static absl::StatusOr> parseOctetString(CBS& cbs); /** * Advance `cbs` over an `ASN.1` value of the class `tag` if that @@ -146,41 +141,40 @@ class Asn1Utility { * * @param cbs a CBS& that refers to the current document position * @param tag the tag of the value to skip - * @returns `ParsingResult` a unit type denoting success + * @returns `absl::StatusOr` a unit type denoting success * or an error string if parsing fails. */ - static ParsingResult skipOptional(CBS& cbs, unsigned tag); + static absl::StatusOr skipOptional(CBS& cbs, unsigned tag); /** * Advance `cbs` over an `ASN.1` value of the class `tag`. * * @param cbs a CBS& that refers to the current document position * @param tag the tag of the value to skip - * @returns `ParsingResult` a unit type denoting success + * @returns `absl::StatusOr` a unit type denoting success * or an error string if parsing fails. */ - static ParsingResult skip(CBS& cbs, unsigned tag); + static absl::StatusOr skip(CBS& cbs, unsigned tag); }; template -ParsingResult> Asn1Utility::parseSequenceOf(CBS& cbs, - Asn1ParsingFunc parse_element) { +absl::StatusOr> Asn1Utility::parseSequenceOf(CBS& cbs, + Asn1ParsingFunc parse_element) { CBS seq_elem; std::vector vec; // Initialize seq_elem to first element in sequence. if (!CBS_get_asn1(&cbs, &seq_elem, CBS_ASN1_SEQUENCE)) { - return "Expected sequence of ASN.1 elements."; + return absl::InvalidArgumentError("Expected sequence of ASN.1 elements."); } while (CBS_data(&seq_elem) < CBS_data(&cbs)) { // parse_element MUST advance seq_elem auto elem_res = parse_element(seq_elem); - if (absl::holds_alternative(elem_res)) { - vec.push_back(absl::get<0>(elem_res)); - } else { - return absl::get<1>(elem_res); + if (!elem_res.status().ok()) { + return elem_res.status(); } + vec.push_back(elem_res.value()); } RELEASE_ASSERT(CBS_data(&cbs) == CBS_data(&seq_elem), @@ -190,21 +184,17 @@ ParsingResult> Asn1Utility::parseSequenceOf(CBS& cbs, } template -ParsingResult> Asn1Utility::parseOptional(CBS& cbs, Asn1ParsingFunc parse_data, - unsigned tag) { +absl::StatusOr> +Asn1Utility::parseOptional(CBS& cbs, Asn1ParsingFunc parse_data, unsigned tag) { auto maybe_data_res = getOptional(cbs, tag); - if (absl::holds_alternative(maybe_data_res)) { - return absl::get(maybe_data_res); + if (!maybe_data_res.status().ok()) { + return maybe_data_res.status(); } - auto maybe_data = absl::get>(maybe_data_res); + auto maybe_data = maybe_data_res.value(); if (maybe_data) { - auto res = parse_data(maybe_data.value()); - if (absl::holds_alternative(res)) { - return absl::get<0>(res); - } - return absl::get<1>(res); + return parse_data(maybe_data.value()); } return absl::nullopt; diff --git a/source/common/tls/ocsp/ocsp.cc b/source/common/tls/ocsp/ocsp.cc index 8a1418203a77..bee9453a5bd2 100644 --- a/source/common/tls/ocsp/ocsp.cc +++ b/source/common/tls/ocsp/ocsp.cc @@ -14,14 +14,6 @@ namespace CertUtility = Envoy::Extensions::TransportSockets::Tls::Utility; namespace { -template T unwrap(ParsingResult res) { - if (absl::holds_alternative(res)) { - return absl::get<0>(res); - } - - throwEnvoyExceptionOrPanic(std::string(absl::get<1>(res))); -} - int attemptParseTagForErrorMessage(CBS& cbs) { unsigned tag = 0; if (!CBS_get_any_asn1_element(&cbs, nullptr, &tag, nullptr)) { @@ -52,8 +44,12 @@ absl::Status skipResponderId(CBS& cbs) { // KeyHash ::= OCTET STRING -- SHA-1 hash of responder's public key // (excluding the tag and length fields) - if (unwrap(Asn1Utility::getOptional(cbs, CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1)) || - unwrap(Asn1Utility::getOptional(cbs, CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 2))) { + auto opt1 = Asn1Utility::getOptional(cbs, CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1); + RETURN_IF_STATUS_NOT_OK(opt1); + auto opt2 = Asn1Utility::getOptional(cbs, CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 2); + RETURN_IF_STATUS_NOT_OK(opt2); + + if (opt1.value() || opt2.value()) { return absl::OkStatus(); } @@ -67,10 +63,14 @@ absl::Status skipCertStatus(CBS& cbs) { // revoked [1] IMPLICIT RevokedInfo, // unknown [2] IMPLICIT UnknownInfo // } - if (!(unwrap(Asn1Utility::getOptional(cbs, CBS_ASN1_CONTEXT_SPECIFIC | 0)) || - unwrap( - Asn1Utility::getOptional(cbs, CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1)) || - unwrap(Asn1Utility::getOptional(cbs, CBS_ASN1_CONTEXT_SPECIFIC | 2)))) { + auto opt1 = Asn1Utility::getOptional(cbs, CBS_ASN1_CONTEXT_SPECIFIC | 0); + RETURN_IF_STATUS_NOT_OK(opt1); + auto opt2 = Asn1Utility::getOptional(cbs, CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 1); + RETURN_IF_STATUS_NOT_OK(opt2); + auto opt3 = Asn1Utility::getOptional(cbs, CBS_ASN1_CONTEXT_SPECIFIC | 2); + RETURN_IF_STATUS_NOT_OK(opt3); + + if (!(opt1.value() || opt2.value() || opt3.value())) { return absl::InvalidArgumentError( absl::StrCat("Unknown OcspCertStatus tag: ", attemptParseTagForErrorMessage(cbs))); } @@ -181,8 +181,9 @@ absl::StatusOr> Asn1OcspUtility::parseOcspResponse auto status_or_error = Asn1OcspUtility::parseResponseStatus(elem); RETURN_IF_STATUS_NOT_OK(status_or_error); - auto maybe_bytes = - unwrap(Asn1Utility::getOptional(elem, CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0)); + auto opt = Asn1Utility::getOptional(elem, CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0); + RETURN_IF_STATUS_NOT_OK(opt); + auto maybe_bytes = opt.value(); ResponsePtr resp = nullptr; if (maybe_bytes) { auto resp_or_error = Asn1OcspUtility::parseResponseBytes(maybe_bytes.value()); @@ -240,7 +241,9 @@ absl::StatusOr Asn1OcspUtility::parseResponseBytes(CBS& cbs) { return absl::InvalidArgumentError("OCSP ResponseBytes is not a well-formed SEQUENCE"); } - auto oid_str = unwrap(Asn1Utility::parseOid(elem)); + auto parse_or_error = Asn1Utility::parseOid(elem); + RETURN_IF_STATUS_NOT_OK(parse_or_error); + auto oid_str = parse_or_error.value(); if (!CBS_get_asn1(&elem, &response, CBS_ASN1_OCTETSTRING)) { return absl::InvalidArgumentError("Expected ASN.1 OCTETSTRING for response"); } @@ -289,10 +292,14 @@ absl::StatusOr Asn1OcspUtility::parseResponseData(CBS& cbs) { } // only support v1, the value of v1 is 0x00 - auto version_cbs = - unwrap(Asn1Utility::getOptional(elem, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0)); + auto version_or_error = + Asn1Utility::getOptional(elem, CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0); + RETURN_IF_STATUS_NOT_OK(version_or_error); + auto version_cbs = version_or_error.value(); if (version_cbs.has_value()) { - auto version = unwrap(Asn1Utility::parseInteger(*version_cbs)); + auto version_or_error = Asn1Utility::parseInteger(*version_cbs); + RETURN_IF_STATUS_NOT_OK(version_or_error); + auto version = version_or_error.value(); if (version != "00") { return absl::InvalidArgumentError( fmt::format("OCSP ResponseData version 0x{} is not supported", version)); @@ -301,14 +308,13 @@ absl::StatusOr Asn1OcspUtility::parseResponseData(CBS& cbs) { auto status = skipResponderId(elem); RETURN_IF_NOT_OK(status); - unwrap(Asn1Utility::skip(elem, CBS_ASN1_GENERALIZEDTIME)); - auto responses = unwrap(Asn1Utility::parseSequenceOf( - elem, [](CBS& cbs) -> ParsingResult { - return {THROW_OR_RETURN_VALUE(parseSingleResponse(cbs), SingleResponse)}; - })); + RETURN_IF_STATUS_NOT_OK(Asn1Utility::skip(elem, CBS_ASN1_GENERALIZEDTIME)); + auto responses_or_error = Asn1Utility::parseSequenceOf( + elem, [](CBS& cbs) -> absl::StatusOr { return {parseSingleResponse(cbs)}; }); + RETURN_IF_STATUS_NOT_OK(responses_or_error); // Extensions currently ignored. - return {std::move(responses)}; + return {std::move(responses_or_error.value())}; } absl::StatusOr Asn1OcspUtility::parseSingleResponse(CBS& cbs) { @@ -327,13 +333,16 @@ absl::StatusOr Asn1OcspUtility::parseSingleResponse(CBS& cbs) { auto id_or_error = Asn1OcspUtility::parseCertId(elem); RETURN_IF_STATUS_NOT_OK(id_or_error); RETURN_IF_NOT_OK(skipCertStatus(elem)); - auto this_update = unwrap(Asn1Utility::parseGeneralizedTime(elem)); - auto next_update = unwrap(Asn1Utility::parseOptional( + auto this_update_or_error = Asn1Utility::parseGeneralizedTime(elem); + RETURN_IF_STATUS_NOT_OK(this_update_or_error); + auto next_update_or_error = Asn1Utility::parseOptional( elem, Asn1Utility::parseGeneralizedTime, - CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0)); + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0); + RETURN_IF_STATUS_NOT_OK(next_update_or_error); // Extensions currently ignored. - return SingleResponse{id_or_error.value(), this_update, next_update}; + return SingleResponse{id_or_error.value(), this_update_or_error.value(), + next_update_or_error.value()}; } absl::StatusOr Asn1OcspUtility::parseCertId(CBS& cbs) { @@ -348,12 +357,13 @@ absl::StatusOr Asn1OcspUtility::parseCertId(CBS& cbs) { return absl::InvalidArgumentError("OCSP CertID is not a well-formed ASN.1 SEQUENCE"); } - unwrap(Asn1Utility::skip(elem, CBS_ASN1_SEQUENCE)); - unwrap(Asn1Utility::skip(elem, CBS_ASN1_OCTETSTRING)); - unwrap(Asn1Utility::skip(elem, CBS_ASN1_OCTETSTRING)); - auto serial_number = unwrap(Asn1Utility::parseInteger(elem)); + RETURN_IF_STATUS_NOT_OK(Asn1Utility::skip(elem, CBS_ASN1_SEQUENCE)); + RETURN_IF_STATUS_NOT_OK(Asn1Utility::skip(elem, CBS_ASN1_OCTETSTRING)); + RETURN_IF_STATUS_NOT_OK(Asn1Utility::skip(elem, CBS_ASN1_OCTETSTRING)); + auto serial_number_or_error = Asn1Utility::parseInteger(elem); + RETURN_IF_STATUS_NOT_OK(serial_number_or_error); - return {serial_number}; + return {serial_number_or_error.value()}; } } // namespace Ocsp diff --git a/source/common/tls/server_context_impl.cc b/source/common/tls/server_context_impl.cc new file mode 100644 index 000000000000..e73d0c4ef486 --- /dev/null +++ b/source/common/tls/server_context_impl.cc @@ -0,0 +1,682 @@ +#include "source/common/tls/server_context_impl.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "envoy/admin/v3/certs.pb.h" +#include "envoy/common/exception.h" +#include "envoy/common/platform.h" +#include "envoy/ssl/ssl_socket_extended_info.h" +#include "envoy/stats/scope.h" +#include "envoy/type/matcher/v3/string.pb.h" + +#include "source/common/common/assert.h" +#include "source/common/common/base64.h" +#include "source/common/common/fmt.h" +#include "source/common/common/hex.h" +#include "source/common/common/utility.h" +#include "source/common/network/address_impl.h" +#include "source/common/protobuf/utility.h" +#include "source/common/runtime/runtime_features.h" +#include "source/common/stats/utility.h" +#include "source/common/tls/cert_validator/factory.h" +#include "source/common/tls/stats.h" +#include "source/common/tls/utility.h" + +#include "absl/container/node_hash_set.h" +#include "absl/strings/match.h" +#include "absl/strings/str_join.h" +#include "cert_validator/cert_validator.h" +#include "openssl/evp.h" +#include "openssl/hmac.h" +#include "openssl/pkcs12.h" +#include "openssl/rand.h" + +namespace Envoy { +namespace { + +bool cbsContainsU16(CBS& cbs, uint16_t n) { + while (CBS_len(&cbs) > 0) { + uint16_t v; + if (!CBS_get_u16(&cbs, &v)) { + return false; + } + if (v == n) { + return true; + } + } + + return false; +} + +} // namespace + +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +int ServerContextImpl::alpnSelectCallback(const unsigned char** out, unsigned char* outlen, + const unsigned char* in, unsigned int inlen) { + // Currently this uses the standard selection algorithm in priority order. + const uint8_t* alpn_data = parsed_alpn_protocols_.data(); + size_t alpn_data_size = parsed_alpn_protocols_.size(); + + if (SSL_select_next_proto(const_cast(out), outlen, alpn_data, alpn_data_size, in, + inlen) != OPENSSL_NPN_NEGOTIATED) { + return SSL_TLSEXT_ERR_NOACK; + } else { + return SSL_TLSEXT_ERR_OK; + } +} + +ServerContextImpl::ServerContextImpl(Stats::Scope& scope, + const Envoy::Ssl::ServerContextConfig& config, + const std::vector& server_names, + Server::Configuration::CommonFactoryContext& factory_context, + Ssl::ContextAdditionalInitFunc additional_init) + : ContextImpl(scope, config, factory_context, additional_init), + session_ticket_keys_(config.sessionTicketKeys()), + ocsp_staple_policy_(config.ocspStaplePolicy()), + full_scan_certs_on_sni_mismatch_(config.fullScanCertsOnSNIMismatch()) { + if (config.tlsCertificates().empty() && !config.capabilities().provides_certificates) { + throwEnvoyExceptionOrPanic("Server TlsCertificates must have a certificate specified"); + } + + for (auto& ctx : tls_contexts_) { + if (ctx.cert_chain_ == nullptr) { + continue; + } + bssl::UniquePtr public_key(X509_get_pubkey(ctx.cert_chain_.get())); + const int pkey_id = EVP_PKEY_id(public_key.get()); + // Load DNS SAN entries and Subject Common Name as server name patterns after certificate + // chain loaded, and populate ServerNamesMap which will be used to match SNI. + has_rsa_ |= (pkey_id == EVP_PKEY_RSA); + populateServerNamesMap(ctx, pkey_id); + } + + // Compute the session context ID hash. We use all the certificate identities, + // since we should have a common ID for session resumption no matter what cert + // is used. We do this early because it can throw an EnvoyException. + const SessionContextID session_id = generateHashForSessionContextId(server_names); + + // First, configure the base context for ClientHello interception. + // TODO(htuch): replace with SSL_IDENTITY when we have this as a means to do multi-cert in + // BoringSSL. + if (!config.capabilities().provides_certificates) { + SSL_CTX_set_select_certificate_cb( + tls_contexts_[0].ssl_ctx_.get(), + [](const SSL_CLIENT_HELLO* client_hello) -> ssl_select_cert_result_t { + return static_cast( + SSL_CTX_get_app_data(SSL_get_SSL_CTX(client_hello->ssl))) + ->selectTlsContext(client_hello); + }); + } + + const auto tls_certificates = config.tlsCertificates(); + + for (uint32_t i = 0; i < tls_certificates.size(); ++i) { + auto& ctx = tls_contexts_[i]; + if (!config.capabilities().verifies_peer_certificates) { + THROW_IF_NOT_OK(cert_validator_->addClientValidationContext( + ctx.ssl_ctx_.get(), config.requireClientCertificate())); + } + + if (!parsed_alpn_protocols_.empty() && !config.capabilities().handles_alpn_selection) { + SSL_CTX_set_alpn_select_cb( + ctx.ssl_ctx_.get(), + [](SSL*, const unsigned char** out, unsigned char* outlen, const unsigned char* in, + unsigned int inlen, void* arg) -> int { + return static_cast(arg)->alpnSelectCallback(out, outlen, in, inlen); + }, + this); + } + + // If the handshaker handles session tickets natively, don't call + // `SSL_CTX_set_tlsext_ticket_key_cb`. + if (config.disableStatelessSessionResumption()) { + SSL_CTX_set_options(ctx.ssl_ctx_.get(), SSL_OP_NO_TICKET); + } else if (!session_ticket_keys_.empty() && !config.capabilities().handles_session_resumption) { + SSL_CTX_set_tlsext_ticket_key_cb( + ctx.ssl_ctx_.get(), + [](SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, + int encrypt) -> int { + ContextImpl* context_impl = + static_cast(SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl))); + ServerContextImpl* server_context_impl = dynamic_cast(context_impl); + RELEASE_ASSERT(server_context_impl != nullptr, ""); // for Coverity + return server_context_impl->sessionTicketProcess(ssl, key_name, iv, ctx, hmac_ctx, + encrypt); + }); + } + + if (config.disableStatefulSessionResumption()) { + SSL_CTX_set_session_cache_mode(ctx.ssl_ctx_.get(), SSL_SESS_CACHE_OFF); + } + + if (config.sessionTimeout() && !config.capabilities().handles_session_resumption) { + auto timeout = config.sessionTimeout().value().count(); + SSL_CTX_set_timeout(ctx.ssl_ctx_.get(), uint32_t(timeout)); + } + + int rc = + SSL_CTX_set_session_id_context(ctx.ssl_ctx_.get(), session_id.data(), session_id.size()); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + + auto& ocsp_resp_bytes = tls_certificates[i].get().ocspStaple(); + if (ocsp_resp_bytes.empty()) { + if (ctx.is_must_staple_) { + throwEnvoyExceptionOrPanic("OCSP response is required for must-staple certificate"); + } + if (ocsp_staple_policy_ == Ssl::ServerContextConfig::OcspStaplePolicy::MustStaple) { + throwEnvoyExceptionOrPanic("Required OCSP response is missing from TLS context"); + } + } else { + auto response_or_error = + Ocsp::OcspResponseWrapper::create(ocsp_resp_bytes, factory_context_.timeSource()); + THROW_IF_STATUS_NOT_OK(response_or_error, throw); + if (!response_or_error.value()->matchesCertificate(*ctx.cert_chain_)) { + throwEnvoyExceptionOrPanic("OCSP response does not match its TLS certificate"); + } + ctx.ocsp_response_ = std::move(response_or_error.value()); + } + } +} + +void ServerContextImpl::populateServerNamesMap(Ssl::TlsContext& ctx, int pkey_id) { + if (ctx.cert_chain_ == nullptr) { + return; + } + + auto populate = [&](const std::string& sn) { + std::string sn_pattern = sn; + if (absl::StartsWith(sn, "*.")) { + sn_pattern = sn.substr(1); + } + PkeyTypesMap pkey_types_map; + // Multiple certs with different key type are allowed for one server name pattern. + auto sn_match = server_names_map_.try_emplace(sn_pattern, pkey_types_map).first; + auto pt_match = sn_match->second.find(pkey_id); + if (pt_match != sn_match->second.end()) { + // When there are duplicate names, prefer the earlier one. + // + // If all of the SANs in a certificate are unused due to duplicates, it could be useful + // to issue a warning, but that would require additional tracking that hasn't been + // implemented. + return; + } + sn_match->second.emplace(std::pair>(pkey_id, ctx)); + }; + + bssl::UniquePtr san_names(static_cast( + X509_get_ext_d2i(ctx.cert_chain_.get(), NID_subject_alt_name, nullptr, nullptr))); + if (san_names != nullptr) { + auto dns_sans = Utility::getSubjectAltNames(*ctx.cert_chain_, GEN_DNS); + // https://www.rfc-editor.org/rfc/rfc6066#section-3 + // Currently, the only server names supported are DNS hostnames, so we + // only save dns san entries to match SNI. + for (const auto& san : dns_sans) { + populate(san); + } + } else { + // https://www.rfc-editor.org/rfc/rfc6125#section-6.4.4 + // As noted, a client MUST NOT seek a match for a reference identifier + // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, + // URI-ID, or any application-specific identifier types supported by the + // client. + X509_NAME* cert_subject = X509_get_subject_name(ctx.cert_chain_.get()); + const int cn_index = X509_NAME_get_index_by_NID(cert_subject, NID_commonName, -1); + if (cn_index >= 0) { + X509_NAME_ENTRY* cn_entry = X509_NAME_get_entry(cert_subject, cn_index); + if (cn_entry) { + ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); + if (ASN1_STRING_length(cn_asn1) > 0) { + std::string subject_cn(reinterpret_cast(ASN1_STRING_data(cn_asn1)), + ASN1_STRING_length(cn_asn1)); + populate(subject_cn); + } + } + } + } +} + +ServerContextImpl::SessionContextID +ServerContextImpl::generateHashForSessionContextId(const std::vector& server_names) { + uint8_t hash_buffer[EVP_MAX_MD_SIZE]; + unsigned hash_length = 0; + + bssl::ScopedEVP_MD_CTX md; + + int rc = EVP_DigestInit(md.get(), EVP_sha256()); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + + // Hash the CommonName/SANs of all the server certificates. This makes sure that sessions can only + // be resumed to certificate(s) for the same name(s), but allows resuming to unique certs in the + // case that different Envoy instances each have their own certs. All certificates in a + // ServerContextImpl context are hashed together, since they all constitute a match on a filter + // chain for resumption purposes. + if (!capabilities_.provides_certificates) { + for (const auto& ctx : tls_contexts_) { + X509* cert = SSL_CTX_get0_certificate(ctx.ssl_ctx_.get()); + RELEASE_ASSERT(cert != nullptr, "TLS context should have an active certificate"); + X509_NAME* cert_subject = X509_get_subject_name(cert); + RELEASE_ASSERT(cert_subject != nullptr, "TLS certificate should have a subject"); + + const int cn_index = X509_NAME_get_index_by_NID(cert_subject, NID_commonName, -1); + if (cn_index >= 0) { + X509_NAME_ENTRY* cn_entry = X509_NAME_get_entry(cert_subject, cn_index); + RELEASE_ASSERT(cn_entry != nullptr, "certificate subject CN should be present"); + + ASN1_STRING* cn_asn1 = X509_NAME_ENTRY_get_data(cn_entry); + if (ASN1_STRING_length(cn_asn1) <= 0) { + throwEnvoyExceptionOrPanic("Invalid TLS context has an empty subject CN"); + } + + rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(cn_asn1), ASN1_STRING_length(cn_asn1)); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } + + unsigned san_count = 0; + bssl::UniquePtr san_names(static_cast( + X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr))); + + if (san_names != nullptr) { + for (const GENERAL_NAME* san : san_names.get()) { + switch (san->type) { + case GEN_IPADD: + rc = EVP_DigestUpdate(md.get(), san->d.iPAddress->data, san->d.iPAddress->length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + ++san_count; + break; + case GEN_DNS: + rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(san->d.dNSName), + ASN1_STRING_length(san->d.dNSName)); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + ++san_count; + break; + case GEN_URI: + rc = EVP_DigestUpdate(md.get(), ASN1_STRING_data(san->d.uniformResourceIdentifier), + ASN1_STRING_length(san->d.uniformResourceIdentifier)); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + ++san_count; + break; + } + } + } + + // It's possible that the certificate doesn't have a subject, but + // does have SANs. Make sure that we have one or the other. + if (cn_index < 0 && san_count == 0) { + throwEnvoyExceptionOrPanic("Invalid TLS context has neither subject CN nor SAN names"); + } + + rc = X509_NAME_digest(X509_get_issuer_name(cert), EVP_sha256(), hash_buffer, &hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + RELEASE_ASSERT(hash_length == SHA256_DIGEST_LENGTH, + fmt::format("invalid SHA256 hash length {}", hash_length)); + + rc = EVP_DigestUpdate(md.get(), hash_buffer, hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } + } + + cert_validator_->updateDigestForSessionId(md, hash_buffer, hash_length); + + // Hash configured SNIs for this context, so that sessions cannot be resumed across different + // filter chains, even when using the same server certificate. + for (const auto& name : server_names) { + rc = EVP_DigestUpdate(md.get(), name.data(), name.size()); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + } + + SessionContextID session_id; + + // Ensure that the output size of the hash we are using is no greater than + // TLS session ID length that we want to generate. + static_assert(session_id.size() == SHA256_DIGEST_LENGTH, "hash size mismatch"); + static_assert(session_id.size() == SSL_MAX_SSL_SESSION_ID_LENGTH, "TLS session ID size mismatch"); + + rc = EVP_DigestFinal(md.get(), session_id.data(), &hash_length); + RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or("")); + RELEASE_ASSERT(hash_length == session_id.size(), + "SHA256 hash length must match TLS Session ID size"); + + return session_id; +} + +int ServerContextImpl::sessionTicketProcess(SSL*, uint8_t* key_name, uint8_t* iv, + EVP_CIPHER_CTX* ctx, HMAC_CTX* hmac_ctx, int encrypt) { + const EVP_MD* hmac = EVP_sha256(); + const EVP_CIPHER* cipher = EVP_aes_256_cbc(); + + if (encrypt == 1) { + // Encrypt + RELEASE_ASSERT(!session_ticket_keys_.empty(), ""); + // TODO(ggreenway): validate in SDS that session_ticket_keys_ cannot be empty, + // or if we allow it to be emptied, reconfigure the context so this callback + // isn't set. + + const Envoy::Ssl::ServerContextConfig::SessionTicketKey& key = session_ticket_keys_.front(); + + static_assert(std::tuple_size::value == SSL_TICKET_KEY_NAME_LEN, + "Expected key.name length"); + std::copy_n(key.name_.begin(), SSL_TICKET_KEY_NAME_LEN, key_name); + + const int rc = RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)); + ASSERT(rc); + + // This RELEASE_ASSERT is logically a static_assert, but we can't actually get + // EVP_CIPHER_key_length(cipher) at compile-time + RELEASE_ASSERT(key.aes_key_.size() == EVP_CIPHER_key_length(cipher), ""); + if (!EVP_EncryptInit_ex(ctx, cipher, nullptr, key.aes_key_.data(), iv)) { + return -1; + } + + if (!HMAC_Init_ex(hmac_ctx, key.hmac_key_.data(), key.hmac_key_.size(), hmac, nullptr)) { + return -1; + } + + return 1; // success + } else { + // Decrypt + bool is_enc_key = true; // first element is the encryption key + for (const Envoy::Ssl::ServerContextConfig::SessionTicketKey& key : session_ticket_keys_) { + static_assert(std::tuple_size::value == SSL_TICKET_KEY_NAME_LEN, + "Expected key.name length"); + if (std::equal(key.name_.begin(), key.name_.end(), key_name)) { + if (!HMAC_Init_ex(hmac_ctx, key.hmac_key_.data(), key.hmac_key_.size(), hmac, nullptr)) { + return -1; + } + + RELEASE_ASSERT(key.aes_key_.size() == EVP_CIPHER_key_length(cipher), ""); + if (!EVP_DecryptInit_ex(ctx, cipher, nullptr, key.aes_key_.data(), iv)) { + return -1; + } + + // If our current encryption was not the decryption key, renew + return is_enc_key ? 1 // success; do not renew + : 2; // success: renew key + } + is_enc_key = false; + } + + return 0; // decryption failed + } +} + +bool ServerContextImpl::isClientEcdsaCapable(const SSL_CLIENT_HELLO* ssl_client_hello) { + CBS client_hello; + CBS_init(&client_hello, ssl_client_hello->client_hello, ssl_client_hello->client_hello_len); + + // This is the TLSv1.3 case (TLSv1.2 on the wire and the supported_versions extensions present). + // We just need to look at signature algorithms. + const uint16_t client_version = ssl_client_hello->version; + if (client_version == TLS1_2_VERSION && tls_max_version_ == TLS1_3_VERSION) { + // If the supported_versions extension is found then we assume that the client is competent + // enough that just checking the signature_algorithms is sufficient. + const uint8_t* supported_versions_data; + size_t supported_versions_len; + if (SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_supported_versions, + &supported_versions_data, &supported_versions_len)) { + const uint8_t* signature_algorithms_data; + size_t signature_algorithms_len; + if (SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_signature_algorithms, + &signature_algorithms_data, + &signature_algorithms_len)) { + CBS signature_algorithms_ext, signature_algorithms; + CBS_init(&signature_algorithms_ext, signature_algorithms_data, signature_algorithms_len); + if (!CBS_get_u16_length_prefixed(&signature_algorithms_ext, &signature_algorithms) || + CBS_len(&signature_algorithms_ext) != 0) { + return false; + } + if (cbsContainsU16(signature_algorithms, SSL_SIGN_ECDSA_SECP256R1_SHA256)) { + return true; + } + } + + return false; + } + } + + // Otherwise we are < TLSv1.3 and need to look at both the curves in the supported_groups for + // ECDSA and also for a compatible cipher suite. https://tools.ietf.org/html/rfc4492#section-5.1.1 + const uint8_t* curvelist_data; + size_t curvelist_len; + if (!SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_supported_groups, + &curvelist_data, &curvelist_len)) { + return false; + } + + CBS curvelist; + CBS_init(&curvelist, curvelist_data, curvelist_len); + + // We only support P256 ECDSA curves today. + if (!cbsContainsU16(curvelist, SSL_CURVE_SECP256R1)) { + return false; + } + + // The client must have offered an ECDSA ciphersuite that we like. + CBS cipher_suites; + CBS_init(&cipher_suites, ssl_client_hello->cipher_suites, ssl_client_hello->cipher_suites_len); + + while (CBS_len(&cipher_suites) > 0) { + uint16_t cipher_id; + if (!CBS_get_u16(&cipher_suites, &cipher_id)) { + return false; + } + // All tls_context_ share the same set of enabled ciphers, so we can just look at the base + // context. + if (tls_contexts_[0].isCipherEnabled(cipher_id, client_version)) { + return true; + } + } + + return false; +} + +bool ServerContextImpl::isClientOcspCapable(const SSL_CLIENT_HELLO* ssl_client_hello) { + const uint8_t* status_request_data; + size_t status_request_len; + if (SSL_early_callback_ctx_extension_get(ssl_client_hello, TLSEXT_TYPE_status_request, + &status_request_data, &status_request_len)) { + return true; + } + + return false; +} + +OcspStapleAction ServerContextImpl::ocspStapleAction(const Ssl::TlsContext& ctx, + bool client_ocsp_capable) { + if (!client_ocsp_capable) { + return OcspStapleAction::ClientNotCapable; + } + + auto& response = ctx.ocsp_response_; + + auto policy = ocsp_staple_policy_; + if (ctx.is_must_staple_) { + // The certificate has the must-staple extension, so upgrade the policy to match. + policy = Ssl::ServerContextConfig::OcspStaplePolicy::MustStaple; + } + + const bool valid_response = response && !response->isExpired(); + + switch (policy) { + case Ssl::ServerContextConfig::OcspStaplePolicy::LenientStapling: + if (!valid_response) { + return OcspStapleAction::NoStaple; + } + return OcspStapleAction::Staple; + + case Ssl::ServerContextConfig::OcspStaplePolicy::StrictStapling: + if (valid_response) { + return OcspStapleAction::Staple; + } + if (response) { + // Expired response. + return OcspStapleAction::Fail; + } + return OcspStapleAction::NoStaple; + + case Ssl::ServerContextConfig::OcspStaplePolicy::MustStaple: + if (!valid_response) { + return OcspStapleAction::Fail; + } + return OcspStapleAction::Staple; + } + PANIC_DUE_TO_CORRUPT_ENUM; +} + +std::pair +ServerContextImpl::findTlsContext(absl::string_view sni, bool client_ecdsa_capable, + bool client_ocsp_capable, bool* cert_matched_sni) { + bool unused = false; + if (cert_matched_sni == nullptr) { + // Avoid need for nullptr checks when this is set. + cert_matched_sni = &unused; + } + + // selected_ctx represents the final selected certificate, it should meet all requirements or pick + // a candidate. + const Ssl::TlsContext* selected_ctx = nullptr; + const Ssl::TlsContext* candidate_ctx = nullptr; + OcspStapleAction ocsp_staple_action; + + auto selected = [&](const Ssl::TlsContext& ctx) -> bool { + auto action = ocspStapleAction(ctx, client_ocsp_capable); + if (action == OcspStapleAction::Fail) { + // The selected ctx must adhere to OCSP policy + return false; + } + + if (client_ecdsa_capable == ctx.is_ecdsa_) { + selected_ctx = &ctx; + ocsp_staple_action = action; + return true; + } + + if (client_ecdsa_capable && !ctx.is_ecdsa_ && candidate_ctx == nullptr) { + // ECDSA cert is preferred if client is ECDSA capable, so RSA cert is marked as a candidate, + // searching will continue until exhausting all certs or find a exact match. + candidate_ctx = &ctx; + ocsp_staple_action = action; + return false; + } + + return false; + }; + + auto select_from_map = [this, &selected](absl::string_view server_name) -> void { + auto it = server_names_map_.find(server_name); + if (it == server_names_map_.end()) { + return; + } + const auto& pkey_types_map = it->second; + for (const auto& entry : pkey_types_map) { + if (selected(entry.second.get())) { + break; + } + } + }; + + auto tail_select = [&](bool go_to_next_phase) { + if (selected_ctx == nullptr) { + selected_ctx = candidate_ctx; + } + + if (selected_ctx == nullptr && !go_to_next_phase) { + selected_ctx = &tls_contexts_[0]; + ocsp_staple_action = ocspStapleAction(*selected_ctx, client_ocsp_capable); + } + }; + + // Select cert based on SNI if SNI is provided by client. + if (!sni.empty()) { + // Match on exact server name, i.e. "www.example.com" for "www.example.com". + select_from_map(sni); + tail_select(true); + + if (selected_ctx == nullptr) { + // Match on wildcard domain, i.e. ".example.com" for "www.example.com". + // https://datatracker.ietf.org/doc/html/rfc6125#section-6.4 + size_t pos = sni.find('.', 1); + if (pos < sni.size() - 1 && pos != std::string::npos) { + absl::string_view wildcard = sni.substr(pos); + select_from_map(wildcard); + } + } + *cert_matched_sni = (selected_ctx != nullptr || candidate_ctx != nullptr); + tail_select(full_scan_certs_on_sni_mismatch_); + } + // Full scan certs if SNI is not provided by client; + // Full scan certs if client provides SNI but no cert matches to it, + // it requires full_scan_certs_on_sni_mismatch is enabled. + if (selected_ctx == nullptr) { + candidate_ctx = nullptr; + // Skip loop when there is no cert compatible to key type + if (client_ecdsa_capable || (!client_ecdsa_capable && has_rsa_)) { + for (const auto& ctx : tls_contexts_) { + if (selected(ctx)) { + break; + } + } + } + tail_select(false); + } + + ASSERT(selected_ctx != nullptr); + return {*selected_ctx, ocsp_staple_action}; +} + +enum ssl_select_cert_result_t +ServerContextImpl::selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello) { + absl::string_view sni = absl::NullSafeStringView( + SSL_get_servername(ssl_client_hello->ssl, TLSEXT_NAMETYPE_host_name)); + const bool client_ecdsa_capable = isClientEcdsaCapable(ssl_client_hello); + const bool client_ocsp_capable = isClientOcspCapable(ssl_client_hello); + + auto [selected_ctx, ocsp_staple_action] = + findTlsContext(sni, client_ecdsa_capable, client_ocsp_capable, nullptr); + + // Apply the selected context. This must be done before OCSP stapling below + // since applying the context can remove the previously-set OCSP response. + // This will only return NULL if memory allocation fails. + RELEASE_ASSERT(SSL_set_SSL_CTX(ssl_client_hello->ssl, selected_ctx.ssl_ctx_.get()) != nullptr, + ""); + + if (client_ocsp_capable) { + stats_.ocsp_staple_requests_.inc(); + } + + switch (ocsp_staple_action) { + case OcspStapleAction::Staple: { + // We avoid setting the OCSP response if the client didn't request it, but doing so is safe. + RELEASE_ASSERT(selected_ctx.ocsp_response_, + "OCSP response must be present under OcspStapleAction::Staple"); + auto& resp_bytes = selected_ctx.ocsp_response_->rawBytes(); + int rc = SSL_set_ocsp_response(ssl_client_hello->ssl, resp_bytes.data(), resp_bytes.size()); + RELEASE_ASSERT(rc != 0, ""); + stats_.ocsp_staple_responses_.inc(); + } break; + case OcspStapleAction::NoStaple: + stats_.ocsp_staple_omitted_.inc(); + break; + case OcspStapleAction::Fail: + stats_.ocsp_staple_failed_.inc(); + return ssl_select_cert_error; + case OcspStapleAction::ClientNotCapable: + break; + } + + return ssl_select_cert_success; +} +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/common/tls/server_context_impl.h b/source/common/tls/server_context_impl.h new file mode 100644 index 000000000000..55ae310e15e3 --- /dev/null +++ b/source/common/tls/server_context_impl.h @@ -0,0 +1,95 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "envoy/network/transport_socket.h" +#include "envoy/ssl/context.h" +#include "envoy/ssl/context_config.h" +#include "envoy/ssl/private_key/private_key.h" +#include "envoy/ssl/ssl_socket_extended_info.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +#include "source/common/common/matchers.h" +#include "source/common/stats/symbol_table.h" +#include "source/common/tls/cert_validator/cert_validator.h" +#include "source/common/tls/context_impl.h" +#include "source/common/tls/context_manager_impl.h" +#include "source/common/tls/ocsp/ocsp.h" +#include "source/common/tls/stats.h" + +#include "absl/synchronization/mutex.h" +#include "openssl/ssl.h" +#include "openssl/x509v3.h" + +#ifdef ENVOY_ENABLE_QUIC +#include "quiche/quic/core/crypto/proof_source.h" +#endif + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +enum class OcspStapleAction { Staple, NoStaple, Fail, ClientNotCapable }; + +class ServerContextImpl : public ContextImpl, public Envoy::Ssl::ServerContext { +public: + ServerContextImpl(Stats::Scope& scope, const Envoy::Ssl::ServerContextConfig& config, + const std::vector& server_names, + Server::Configuration::CommonFactoryContext& factory_context, + Ssl::ContextAdditionalInitFunc additional_init); + + // Select the TLS certificate context in SSL_CTX_set_select_certificate_cb() callback with + // ClientHello details. This is made public for use by custom TLS extensions who want to + // manually create and use this as a client hello callback. + enum ssl_select_cert_result_t selectTlsContext(const SSL_CLIENT_HELLO* ssl_client_hello); + + // Finds the best matching context. The returned context will have the same lifetime as + // this ``ServerContextImpl``. + std::pair findTlsContext(absl::string_view sni, + bool client_ecdsa_capable, + bool client_ocsp_capable, + bool* cert_matched_sni); + +private: + // Currently, at most one certificate of a given key type may be specified for each exact + // server name or wildcard domain name. + using PkeyTypesMap = absl::flat_hash_map>; + // Both exact server names and wildcard domains are part of the same map, in which wildcard + // domains are prefixed with "." (i.e. ".example.com" for "*.example.com") to differentiate + // between exact and wildcard entries. + using ServerNamesMap = absl::flat_hash_map; + + void populateServerNamesMap(Ssl::TlsContext& ctx, const int pkey_id); + + using SessionContextID = std::array; + + int alpnSelectCallback(const unsigned char** out, unsigned char* outlen, const unsigned char* in, + unsigned int inlen); + int sessionTicketProcess(SSL* ssl, uint8_t* key_name, uint8_t* iv, EVP_CIPHER_CTX* ctx, + HMAC_CTX* hmac_ctx, int encrypt); + bool isClientEcdsaCapable(const SSL_CLIENT_HELLO* ssl_client_hello); + bool isClientOcspCapable(const SSL_CLIENT_HELLO* ssl_client_hello); + OcspStapleAction ocspStapleAction(const Ssl::TlsContext& ctx, bool client_ocsp_capable); + + SessionContextID generateHashForSessionContextId(const std::vector& server_names); + + const std::vector session_ticket_keys_; + const Ssl::ServerContextConfig::OcspStaplePolicy ocsp_staple_policy_; + ServerNamesMap server_names_map_; + bool has_rsa_{false}; + bool full_scan_certs_on_sni_mismatch_; +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/common/tls/server_ssl_socket.cc b/source/common/tls/server_ssl_socket.cc new file mode 100644 index 000000000000..e3018db8d00f --- /dev/null +++ b/source/common/tls/server_ssl_socket.cc @@ -0,0 +1,83 @@ +#include "source/common/tls/server_ssl_socket.h" + +#include "envoy/stats/scope.h" + +#include "source/common/common/assert.h" +#include "source/common/common/empty_string.h" +#include "source/common/common/hex.h" +#include "source/common/http/headers.h" +#include "source/common/runtime/runtime_features.h" +#include "source/common/tls/io_handle_bio.h" +#include "source/common/tls/ssl_handshaker.h" +#include "source/common/tls/utility.h" + +#include "absl/strings/str_replace.h" +#include "openssl/err.h" +#include "openssl/x509v3.h" + +using Envoy::Network::PostIoAction; + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +namespace { +SslSocketFactoryStats generateStats(Stats::Scope& store) { + return {ALL_SSL_SOCKET_FACTORY_STATS(POOL_COUNTER_PREFIX(store, "server_ssl_socket_factory."))}; +} +} // namespace + +ServerSslSocketFactory::ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPtr config, + Envoy::Ssl::ContextManager& manager, + Stats::Scope& stats_scope, + const std::vector& server_names) + : manager_(manager), stats_scope_(stats_scope), stats_(generateStats(stats_scope)), + config_(std::move(config)), server_names_(server_names), + ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_, nullptr)) { + config_->setSecretUpdateCallback([this]() { onAddOrUpdateSecret(); }); +} + +ServerSslSocketFactory::~ServerSslSocketFactory() { manager_.removeContext(ssl_ctx_); } + +Network::TransportSocketPtr ServerSslSocketFactory::createDownstreamTransportSocket() const { + // onAddOrUpdateSecret() could be invoked in the middle of checking the existence of ssl_ctx and + // creating SslSocket using ssl_ctx. Capture ssl_ctx_ into a local variable so that we check and + // use the same ssl_ctx to create SslSocket. + Envoy::Ssl::ServerContextSharedPtr ssl_ctx; + { + absl::ReaderMutexLock l(&ssl_ctx_mu_); + ssl_ctx = ssl_ctx_; + } + if (ssl_ctx) { + auto status_or_socket = SslSocket::create(std::move(ssl_ctx), InitialState::Server, nullptr, + config_->createHandshaker()); + if (status_or_socket.ok()) { + return std::move(status_or_socket.value()); + } + return std::make_unique(status_or_socket.status().message()); + } else { + ENVOY_LOG(debug, "Create NotReadySslSocket"); + stats_.downstream_context_secrets_not_ready_.inc(); + return std::make_unique(); + } +} + +bool ServerSslSocketFactory::implementsSecureTransport() const { return true; } + +void ServerSslSocketFactory::onAddOrUpdateSecret() { + ENVOY_LOG(debug, "Secret is updated."); + auto ctx = manager_.createSslServerContext(stats_scope_, *config_, server_names_, nullptr); + { + absl::WriterMutexLock l(&ssl_ctx_mu_); + std::swap(ctx, ssl_ctx_); + } + manager_.removeContext(ctx); + + stats_.ssl_context_update_by_sds_.inc(); +} + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/common/tls/server_ssl_socket.h b/source/common/tls/server_ssl_socket.h new file mode 100644 index 000000000000..40138c107c56 --- /dev/null +++ b/source/common/tls/server_ssl_socket.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +#include "envoy/network/connection.h" +#include "envoy/network/transport_socket.h" +#include "envoy/secret/secret_callbacks.h" +#include "envoy/ssl/handshaker.h" +#include "envoy/ssl/private_key/private_key_callbacks.h" +#include "envoy/ssl/ssl_socket_extended_info.h" +#include "envoy/ssl/ssl_socket_state.h" +#include "envoy/stats/scope.h" +#include "envoy/stats/stats_macros.h" + +#include "source/common/common/logger.h" +#include "source/common/network/transport_socket_options_impl.h" +#include "source/common/tls/context_impl.h" +#include "source/common/tls/ssl_handshaker.h" +#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/utility.h" + +#include "absl/container/node_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/optional.h" +#include "openssl/ssl.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class ServerSslSocketFactory : public Network::DownstreamTransportSocketFactory, + public Secret::SecretCallbacks, + Logger::Loggable { +public: + ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPtr config, + Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope, + const std::vector& server_names); + + ~ServerSslSocketFactory() override; + + Network::TransportSocketPtr createDownstreamTransportSocket() const override; + bool implementsSecureTransport() const override; + + // Secret::SecretCallbacks + void onAddOrUpdateSecret() override; + +private: + Ssl::ContextManager& manager_; + Stats::Scope& stats_scope_; + SslSocketFactoryStats stats_; + Envoy::Ssl::ServerContextConfigPtr config_; + const std::vector server_names_; + mutable absl::Mutex ssl_ctx_mu_; + Envoy::Ssl::ServerContextSharedPtr ssl_ctx_ ABSL_GUARDED_BY(ssl_ctx_mu_); +}; + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/common/tls/ssl_socket.cc b/source/common/tls/ssl_socket.cc index 20c38d2a6203..2fdfcc147d6d 100644 --- a/source/common/tls/ssl_socket.cc +++ b/source/common/tls/ssl_socket.cc @@ -26,43 +26,10 @@ namespace { constexpr absl::string_view NotReadyReason{"TLS error: Secret is not supplied by SDS"}; -class InvalidSslSocket : public Network::TransportSocket { -public: - // Network::TransportSocket - void setTransportSocketCallbacks(Network::TransportSocketCallbacks&) override {} - std::string protocol() const override { return EMPTY_STRING; } - bool canFlushClose() override { return true; } - void closeSocket(Network::ConnectionEvent) override {} - Network::IoResult doRead(Buffer::Instance&) override { return {PostIoAction::Close, 0, false}; } - Network::IoResult doWrite(Buffer::Instance&, bool) override { - return {PostIoAction::Close, 0, false}; - } - void onConnected() override {} - Ssl::ConnectionInfoConstSharedPtr ssl() const override { return nullptr; } - bool startSecureTransport() override { return false; } - void configureInitialCongestionWindow(uint64_t, std::chrono::microseconds) override {} -}; - -// This SslSocket will be used when SSL secret is not fetched from SDS server. -class NotReadySslSocket : public InvalidSslSocket { -public: - // Network::TransportSocket - absl::string_view failureReason() const override { return NotReadyReason; } -}; - -class ErrorSslSocket : public InvalidSslSocket { -public: - ErrorSslSocket(absl::string_view error) : error_(error) {} - - // Network::TransportSocket - absl::string_view failureReason() const override { return error_; } - -private: - std::string error_; -}; - } // namespace +absl::string_view NotReadySslSocket::failureReason() const { return NotReadyReason; } + absl::StatusOr> SslSocket::create(Envoy::Ssl::ContextSharedPtr ctx, InitialState state, const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, @@ -410,117 +377,6 @@ void SslSocket::onAsynchronousCertValidationComplete() { } } -namespace { -SslSocketFactoryStats generateStats(const std::string& prefix, Stats::Scope& store) { - return { - ALL_SSL_SOCKET_FACTORY_STATS(POOL_COUNTER_PREFIX(store, prefix + "_ssl_socket_factory."))}; -} -} // namespace - -ClientSslSocketFactory::ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPtr config, - Envoy::Ssl::ContextManager& manager, - Stats::Scope& stats_scope) - : manager_(manager), stats_scope_(stats_scope), stats_(generateStats("client", stats_scope)), - config_(std::move(config)), - ssl_ctx_(manager_.createSslClientContext(stats_scope_, *config_)) { - config_->setSecretUpdateCallback([this]() { onAddOrUpdateSecret(); }); -} - -ClientSslSocketFactory::~ClientSslSocketFactory() { manager_.removeContext(ssl_ctx_); } - -Network::TransportSocketPtr ClientSslSocketFactory::createTransportSocket( - Network::TransportSocketOptionsConstSharedPtr transport_socket_options, - Upstream::HostDescriptionConstSharedPtr) const { - // onAddOrUpdateSecret() could be invoked in the middle of checking the existence of ssl_ctx and - // creating SslSocket using ssl_ctx. Capture ssl_ctx_ into a local variable so that we check and - // use the same ssl_ctx to create SslSocket. - Envoy::Ssl::ClientContextSharedPtr ssl_ctx; - { - absl::ReaderMutexLock l(&ssl_ctx_mu_); - ssl_ctx = ssl_ctx_; - } - if (ssl_ctx) { - auto status_or_socket = - SslSocket::create(std::move(ssl_ctx), InitialState::Client, transport_socket_options, - config_->createHandshaker()); - if (status_or_socket.ok()) { - return std::move(status_or_socket.value()); - } - return std::make_unique(status_or_socket.status().message()); - } else { - ENVOY_LOG(debug, "Create NotReadySslSocket"); - stats_.upstream_context_secrets_not_ready_.inc(); - return std::make_unique(); - } -} - -bool ClientSslSocketFactory::implementsSecureTransport() const { return true; } - -void ClientSslSocketFactory::onAddOrUpdateSecret() { - ENVOY_LOG(debug, "Secret is updated."); - auto ctx = manager_.createSslClientContext(stats_scope_, *config_); - { - absl::WriterMutexLock l(&ssl_ctx_mu_); - std::swap(ctx, ssl_ctx_); - } - manager_.removeContext(ctx); - stats_.ssl_context_update_by_sds_.inc(); -} - -ServerSslSocketFactory::ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPtr config, - Envoy::Ssl::ContextManager& manager, - Stats::Scope& stats_scope, - const std::vector& server_names) - : manager_(manager), stats_scope_(stats_scope), stats_(generateStats("server", stats_scope)), - config_(std::move(config)), server_names_(server_names), - ssl_ctx_(manager_.createSslServerContext(stats_scope_, *config_, server_names_, nullptr)) { - config_->setSecretUpdateCallback([this]() { onAddOrUpdateSecret(); }); -} - -ServerSslSocketFactory::~ServerSslSocketFactory() { manager_.removeContext(ssl_ctx_); } - -Envoy::Ssl::ClientContextSharedPtr ClientSslSocketFactory::sslCtx() { - absl::ReaderMutexLock l(&ssl_ctx_mu_); - return ssl_ctx_; -} - -Network::TransportSocketPtr ServerSslSocketFactory::createDownstreamTransportSocket() const { - // onAddOrUpdateSecret() could be invoked in the middle of checking the existence of ssl_ctx and - // creating SslSocket using ssl_ctx. Capture ssl_ctx_ into a local variable so that we check and - // use the same ssl_ctx to create SslSocket. - Envoy::Ssl::ServerContextSharedPtr ssl_ctx; - { - absl::ReaderMutexLock l(&ssl_ctx_mu_); - ssl_ctx = ssl_ctx_; - } - if (ssl_ctx) { - auto status_or_socket = SslSocket::create(std::move(ssl_ctx), InitialState::Server, nullptr, - config_->createHandshaker()); - if (status_or_socket.ok()) { - return std::move(status_or_socket.value()); - } - return std::make_unique(status_or_socket.status().message()); - } else { - ENVOY_LOG(debug, "Create NotReadySslSocket"); - stats_.downstream_context_secrets_not_ready_.inc(); - return std::make_unique(); - } -} - -bool ServerSslSocketFactory::implementsSecureTransport() const { return true; } - -void ServerSslSocketFactory::onAddOrUpdateSecret() { - ENVOY_LOG(debug, "Secret is updated."); - auto ctx = manager_.createSslServerContext(stats_scope_, *config_, server_names_, nullptr); - { - absl::WriterMutexLock l(&ssl_ctx_mu_); - std::swap(ctx, ssl_ctx_); - } - manager_.removeContext(ctx); - - stats_.ssl_context_update_by_sds_.inc(); -} - } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/source/common/tls/ssl_socket.h b/source/common/tls/ssl_socket.h index a412b1e7ecbc..d5254a556a47 100644 --- a/source/common/tls/ssl_socket.h +++ b/source/common/tls/ssl_socket.h @@ -105,64 +105,41 @@ class SslSocket : public Network::TransportSocket, SslHandshakerImplSharedPtr info_; }; -class ClientSslSocketFactory : public Network::CommonUpstreamTransportSocketFactory, - public Secret::SecretCallbacks, - Logger::Loggable { +class InvalidSslSocket : public Network::TransportSocket { public: - ClientSslSocketFactory(Envoy::Ssl::ClientContextConfigPtr config, - Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope); - - ~ClientSslSocketFactory() override; - - Network::TransportSocketPtr - createTransportSocket(Network::TransportSocketOptionsConstSharedPtr options, - Upstream::HostDescriptionConstSharedPtr) const override; - bool implementsSecureTransport() const override; - absl::string_view defaultServerNameIndication() const override { - return clientContextConfig()->serverNameIndication(); + // Network::TransportSocket + void setTransportSocketCallbacks(Network::TransportSocketCallbacks&) override {} + std::string protocol() const override { return EMPTY_STRING; } + bool canFlushClose() override { return true; } + void closeSocket(Network::ConnectionEvent) override {} + Network::IoResult doRead(Buffer::Instance&) override { + return {Network::PostIoAction::Close, 0, false}; } - bool supportsAlpn() const override { return true; } - - // Secret::SecretCallbacks - void onAddOrUpdateSecret() override; - - OptRef clientContextConfig() const override { return {*config_}; } - - Envoy::Ssl::ClientContextSharedPtr sslCtx() override; - -private: - Envoy::Ssl::ContextManager& manager_; - Stats::Scope& stats_scope_; - SslSocketFactoryStats stats_; - Envoy::Ssl::ClientContextConfigPtr config_; - mutable absl::Mutex ssl_ctx_mu_; - Envoy::Ssl::ClientContextSharedPtr ssl_ctx_ ABSL_GUARDED_BY(ssl_ctx_mu_); + Network::IoResult doWrite(Buffer::Instance&, bool) override { + return {Network::PostIoAction::Close, 0, false}; + } + void onConnected() override {} + Ssl::ConnectionInfoConstSharedPtr ssl() const override { return nullptr; } + bool startSecureTransport() override { return false; } + void configureInitialCongestionWindow(uint64_t, std::chrono::microseconds) override {} }; -class ServerSslSocketFactory : public Network::DownstreamTransportSocketFactory, - public Secret::SecretCallbacks, - Logger::Loggable { +// This SslSocket will be used when SSL secret is not fetched from SDS server. +class NotReadySslSocket : public InvalidSslSocket { public: - ServerSslSocketFactory(Envoy::Ssl::ServerContextConfigPtr config, - Envoy::Ssl::ContextManager& manager, Stats::Scope& stats_scope, - const std::vector& server_names); - - ~ServerSslSocketFactory() override; + // Network::TransportSocket + absl::string_view failureReason() const override; +}; - Network::TransportSocketPtr createDownstreamTransportSocket() const override; - bool implementsSecureTransport() const override; +class ErrorSslSocket : public InvalidSslSocket { +public: + ErrorSslSocket(absl::string_view error) : error_(error) {} - // Secret::SecretCallbacks - void onAddOrUpdateSecret() override; + // Network::TransportSocket + absl::string_view failureReason() const override { return error_; } private: - Ssl::ContextManager& manager_; - Stats::Scope& stats_scope_; - SslSocketFactoryStats stats_; - Envoy::Ssl::ServerContextConfigPtr config_; - const std::vector server_names_; - mutable absl::Mutex ssl_ctx_mu_; - Envoy::Ssl::ServerContextSharedPtr ssl_ctx_ ABSL_GUARDED_BY(ssl_ctx_mu_); + std::string error_; }; } // namespace Tls diff --git a/source/exe/BUILD b/source/exe/BUILD index 76dfa429a524..a0a42b9d0f7d 100644 --- a/source/exe/BUILD +++ b/source/exe/BUILD @@ -158,6 +158,7 @@ envoy_cc_library( ":main_common_with_all_extensions_lib", # These are compiled as extensions so Envoy Mobile doesn't have to link them in. # Envoy requires them. + "//source/extensions/transport_sockets/tls:config", "//source/common/listener_manager:listener_manager_lib", "//source/extensions/listener_managers/validation_listener_manager:validation_listener_manager_lib", "//source/common/version:version_linkstamp", diff --git a/source/exe/stripped_main_base.cc b/source/exe/stripped_main_base.cc index 121e1d6484d9..3a84503c12c0 100644 --- a/source/exe/stripped_main_base.cc +++ b/source/exe/stripped_main_base.cc @@ -125,9 +125,9 @@ void StrippedMainBase::configureHotRestarter(Random::RandomGenerator& random_gen base_id = static_cast(random_generator.random()) & 0x0FFFFFFF; TRY_ASSERT_MAIN_THREAD { - restarter = std::make_unique(base_id, 0, options_.socketPath(), - options_.socketMode(), - options_.skipHotRestartOnNoParent()); + restarter = std::make_unique( + base_id, 0, options_.socketPath(), options_.socketMode(), + options_.skipHotRestartOnNoParent(), options_.skipHotRestartParentStats()); } END_TRY CATCH(Server::HotRestartDomainSocketInUseException & ex, { @@ -144,7 +144,7 @@ void StrippedMainBase::configureHotRestarter(Random::RandomGenerator& random_gen } else { restarter_ = std::make_unique( base_id, options_.restartEpoch(), options_.socketPath(), options_.socketMode(), - options_.skipHotRestartOnNoParent()); + options_.skipHotRestartOnNoParent(), options_.skipHotRestartParentStats()); } // Write the base-id to the requested path whether we selected it diff --git a/source/extensions/all_extensions.bzl b/source/extensions/all_extensions.bzl index f239a04b9537..475cf1b855ea 100644 --- a/source/extensions/all_extensions.bzl +++ b/source/extensions/all_extensions.bzl @@ -6,7 +6,6 @@ load("@envoy_build_config//:extensions_build_config.bzl", "EXTENSIONS") _required_extensions = { "envoy.http.original_ip_detection.xff": "//source/extensions/http/original_ip_detection/xff:config", "envoy.request_id.uuid": "//source/extensions/request_id/uuid:config", - "envoy.transport_sockets.tls": "//source/extensions/transport_sockets/tls:config", # To provide default round robin load balancer. "envoy.load_balancing_policies.round_robin": "//source/extensions/load_balancing_policies/round_robin:config", } diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc index 12a2446ecf2b..e50dd7c6d2a8 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.cc @@ -65,7 +65,7 @@ DnsCacheImpl::DnsCacheImpl( ? DnsHostInfo::normalizeHostForDfp(hostname.address(), hostname.port_value()) : hostname.address(); ENVOY_LOG(debug, "DNS pre-resolve starting for host {}", host); - startCacheLoad(host, hostname.port_value(), false); + startCacheLoad(host, hostname.port_value(), false, false); } } @@ -106,33 +106,44 @@ DnsCacheImpl::loadDnsCacheEntry(absl::string_view raw_host, uint16_t default_por is_proxy_lookup ? "proxy mode " : ""); ThreadLocalHostInfo& tls_host_info = *tls_slot_; - auto [is_overflow, host_info] = [&]() { + bool is_overflow = false; + absl::optional host_info = absl::nullopt; + bool ignore_cached_entries = false; + + { absl::ReaderMutexLock read_lock{&primary_hosts_lock_}; + is_overflow = primary_hosts_.size() >= max_hosts_; auto tls_host = primary_hosts_.find(host); - return std::make_tuple( - primary_hosts_.size() >= max_hosts_, - (tls_host != primary_hosts_.end() && tls_host->second->host_info_->firstResolveComplete()) - ? absl::optional(tls_host->second->host_info_) - : absl::nullopt); - }(); + if (tls_host != primary_hosts_.end() && tls_host->second->host_info_->firstResolveComplete()) { + host_info = tls_host->second->host_info_; + } + }; if (host_info) { ENVOY_LOG(debug, "cache hit for host '{}'", host); - return {LoadDnsCacheEntryStatus::InCache, nullptr, host_info}; - } else if (is_overflow) { + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.reresolve_null_addresses") && + !is_proxy_lookup && *host_info && (*host_info)->address() == nullptr) { + ENVOY_LOG(debug, "ignoring null address cache hit for miss for host '{}'", host); + ignore_cached_entries = true; + // fall through to handle below + } else { + return {LoadDnsCacheEntryStatus::InCache, nullptr, host_info}; + } + } + if (is_overflow) { ENVOY_LOG(debug, "DNS cache overflow for host '{}'", host); stats_.host_overflow_.inc(); return {LoadDnsCacheEntryStatus::Overflow, nullptr, absl::nullopt}; - } else { - ENVOY_LOG(debug, "cache miss for host '{}', posting to main thread", host); - main_thread_dispatcher_.post([this, host = std::string(host), default_port, is_proxy_lookup]() { - startCacheLoad(host, default_port, is_proxy_lookup); - }); - return {LoadDnsCacheEntryStatus::Loading, - std::make_unique(tls_host_info.pending_resolutions_, host, - callbacks), - absl::nullopt}; } + ENVOY_LOG(debug, "cache miss for host '{}', posting to main thread", host); + main_thread_dispatcher_.post( + [this, host = std::string(host), default_port, is_proxy_lookup, ignore_cached_entries]() { + startCacheLoad(host, default_port, is_proxy_lookup, ignore_cached_entries); + }); + return {LoadDnsCacheEntryStatus::Loading, + std::make_unique(tls_host_info.pending_resolutions_, host, + callbacks), + absl::nullopt}; } Upstream::ResourceAutoIncDecPtr DnsCacheImpl::canCreateDnsRequest() { @@ -176,7 +187,7 @@ DnsCacheImpl::addUpdateCallbacks(UpdateCallbacks& callbacks) { } void DnsCacheImpl::startCacheLoad(const std::string& host, uint16_t default_port, - bool is_proxy_lookup) { + bool is_proxy_lookup, bool ignore_cached_entries) { ASSERT(main_thread_dispatcher_.isThreadSafe()); // It's possible for multiple requests to race trying to start a resolution. If a host is @@ -192,8 +203,15 @@ void DnsCacheImpl::startCacheLoad(const std::string& host, uint16_t default_port }(); if (primary_host) { - ENVOY_LOG(debug, "main thread resolve for host '{}' skipped. Entry present", host); - return; + if (!ignore_cached_entries) { + // This may mean a resolve is in-progress, or a resolve completed before we + // switched contexts to startCacheLoad. Either way the caller will be informed. + ENVOY_LOG(debug, "main thread resolve for host '{}' skipped. Entry present", host); + return; + } + // The host was in cache but we want to force a refresh. Remove the host + // entirely to ensure initial resolve logic works as expected. + removeHost(host, *primary_host); } primary_host = createHost(host, default_port); @@ -220,7 +238,7 @@ DnsCacheImpl::PrimaryHostInfo* DnsCacheImpl::createHost(const std::string& host, std::make_unique( *this, std::string(host_attributes.host_), host_attributes.port_.value_or(default_port), - host_attributes.is_ip_address_, [this, host]() { onReResolve(host); }, + host_attributes.is_ip_address_, [this, host]() { onReResolveAlarm(host); }, [this, host]() { onResolveTimeout(host); })) .first->second.get(); } @@ -246,12 +264,8 @@ void DnsCacheImpl::onResolveTimeout(const std::string& host) { finishResolve(host, Network::DnsResolver::ResolutionStatus::Failure, {}); } -void DnsCacheImpl::onReResolve(const std::string& host) { +void DnsCacheImpl::onReResolveAlarm(const std::string& host) { ASSERT(main_thread_dispatcher_.isThreadSafe()); - // If we need to erase the host, hold onto the PrimaryHostInfo object that owns this callback. - // This is defined at function scope so that it is only erased on function exit to avoid - // use-after-free issues - PrimaryHostInfoPtr host_to_erase; auto& primary_host = getPrimaryHost(host); const std::chrono::steady_clock::duration now_duration = @@ -261,26 +275,35 @@ void DnsCacheImpl::onReResolve(const std::string& host) { last_used_time.count(), host_ttl_.count()); if ((now_duration - last_used_time) > host_ttl_) { ENVOY_LOG(debug, "host='{}' TTL expired, removing", host); - // If the host has no address then that means that the DnsCacheImpl has never - // runAddUpdateCallbacks for this host, and thus the callback targets are not aware of it. - // Therefore, runRemoveCallbacks should only be ran if the host's address != nullptr. - if (primary_host.host_info_->address()) { - runRemoveCallbacks(host); - } - { - removeCacheEntry(host); - absl::WriterMutexLock writer_lock{&primary_hosts_lock_}; - auto host_it = primary_hosts_.find(host); - ASSERT(host_it != primary_hosts_.end()); - host_to_erase = std::move(host_it->second); - primary_hosts_.erase(host_it); - } - notifyThreads(host, primary_host.host_info_); + removeHost(host, primary_host); } else { startResolve(host, primary_host); } } +void DnsCacheImpl::removeHost(const std::string& host, const PrimaryHostInfo& primary_host) { + // If we need to erase the host, hold onto the PrimaryHostInfo object that owns this callback. + // This is defined at function scope so that it is only erased on function exit to avoid + // use-after-free issues + PrimaryHostInfoPtr host_to_erase; + + // If the host has no address then that means that the DnsCacheImpl has never + // runAddUpdateCallbacks for this host, and thus the callback targets are not aware of it. + // Therefore, runRemoveCallbacks should only be ran if the host's address != nullptr. + if (primary_host.host_info_->address()) { + runRemoveCallbacks(host); + } + { + removeCacheEntry(host); + absl::WriterMutexLock writer_lock{&primary_hosts_lock_}; + auto host_it = primary_hosts_.find(host); + ASSERT(host_it != primary_hosts_.end()); + host_to_erase = std::move(host_it->second); + primary_hosts_.erase(host_it); + } + notifyThreads(host, primary_host.host_info_); +} + void DnsCacheImpl::forceRefreshHosts() { ENVOY_LOG(debug, "beginning DNS cache force refresh"); // Tell the underlying resolver to reset itself since we likely just went through a network diff --git a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h index c7bd9656897b..38d94d5bc398 100644 --- a/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h +++ b/source/extensions/common/dynamic_forward_proxy/dns_cache_impl.h @@ -204,7 +204,8 @@ class DnsCacheImpl : public DnsCache, Logger::Loggablematches(key)) { + if (disallowed_headers_matcher != nullptr && disallowed_headers_matcher->matches(key)) { + return Envoy::Http::HeaderMap::Iterate::Continue; + } + + if (allowed_headers_matcher != nullptr && !allowed_headers_matcher->matches(key)) { return Envoy::Http::HeaderMap::Iterate::Continue; } @@ -208,10 +213,12 @@ void CheckRequestUtils::setAttrContextRequest( envoy::service::auth::v3::AttributeContext::Request& req, const uint64_t stream_id, const StreamInfo::StreamInfo& stream_info, const Buffer::Instance* decoding_buffer, const Envoy::Http::RequestHeaderMap& headers, uint64_t max_request_bytes, bool pack_as_bytes, - bool encode_raw_headers, const MatcherSharedPtr& request_header_matchers) { + bool encode_raw_headers, const MatcherSharedPtr& allowed_headers_matcher, + const MatcherSharedPtr& disallowed_headers_matcher) { setRequestTime(req, stream_info); setHttpRequest(*req.mutable_http(), stream_id, stream_info, decoding_buffer, headers, - max_request_bytes, pack_as_bytes, encode_raw_headers, request_header_matchers); + max_request_bytes, pack_as_bytes, encode_raw_headers, allowed_headers_matcher, + disallowed_headers_matcher); } void CheckRequestUtils::setTLSSession( @@ -232,7 +239,8 @@ void CheckRequestUtils::createHttpCheck( envoy::service::auth::v3::CheckRequest& request, uint64_t max_request_bytes, bool pack_as_bytes, bool encode_raw_headers, bool include_peer_certificate, bool include_tls_session, const Protobuf::Map& destination_labels, - const MatcherSharedPtr& request_header_matchers) { + const MatcherSharedPtr& allowed_headers_matcher, + const MatcherSharedPtr& disallowed_headers_matcher) { auto attrs = request.mutable_attributes(); const std::string service = getHeaderStr(headers.EnvoyDownstreamServiceCluster()); @@ -246,7 +254,7 @@ void CheckRequestUtils::createHttpCheck( include_peer_certificate); setAttrContextRequest(*attrs->mutable_request(), cb->streamId(), cb->streamInfo(), cb->decodingBuffer(), headers, max_request_bytes, pack_as_bytes, - encode_raw_headers, request_header_matchers); + encode_raw_headers, allowed_headers_matcher, disallowed_headers_matcher); if (include_tls_session && cb->connection()->ssl() != nullptr) { setTLSSession(*attrs->mutable_tls_session(), cb->connection()->ssl()); diff --git a/source/extensions/filters/common/ext_authz/check_request_utils.h b/source/extensions/filters/common/ext_authz/check_request_utils.h index 46e4814f20d9..73563363cce5 100644 --- a/source/extensions/filters/common/ext_authz/check_request_utils.h +++ b/source/extensions/filters/common/ext_authz/check_request_utils.h @@ -100,7 +100,8 @@ class CheckRequestUtils { bool encode_raw_headers, bool include_peer_certificate, bool include_tls_session, const Protobuf::Map& destination_labels, - const MatcherSharedPtr& request_header_matchers); + const MatcherSharedPtr& allowed_headers_matcher, + const MatcherSharedPtr& disallowed_headers_matcher); /** * createTcpCheck is used to extract the attributes from the network layer and fill them up @@ -133,12 +134,14 @@ class CheckRequestUtils { const Envoy::Http::RequestHeaderMap& headers, uint64_t max_request_bytes, bool pack_as_bytes, bool encode_raw_headers, - const MatcherSharedPtr& request_header_matchers); + const MatcherSharedPtr& allowed_headers_matcher, + const MatcherSharedPtr& disallowed_headers_matcher); static void setAttrContextRequest( envoy::service::auth::v3::AttributeContext::Request& req, const uint64_t stream_id, const StreamInfo::StreamInfo& stream_info, const Buffer::Instance* decoding_buffer, const Envoy::Http::RequestHeaderMap& headers, uint64_t max_request_bytes, bool pack_as_bytes, - bool encode_raw_headers, const MatcherSharedPtr& request_header_matchers); + bool encode_raw_headers, const MatcherSharedPtr& allowed_headers_matcher, + const MatcherSharedPtr& disallowed_headers_matcher); static void setTLSSession(envoy::service::auth::v3::AttributeContext::TLSSession& session, const Ssl::ConnectionInfoConstSharedPtr ssl_info); static std::string getHeaderStr(const Envoy::Http::HeaderEntry* entry); diff --git a/source/extensions/filters/http/ext_authz/ext_authz.cc b/source/extensions/filters/http/ext_authz/ext_authz.cc index 14fefc5bc34d..824682477bc3 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.cc +++ b/source/extensions/filters/http/ext_authz/ext_authz.cc @@ -106,7 +106,8 @@ void Filter::initiateCall(const Http::RequestHeaderMap& headers) { decoder_callbacks_, headers, std::move(context_extensions), std::move(metadata_context), std::move(route_metadata_context), check_request_, max_request_bytes_, config_->packAsBytes(), config_->headersAsBytes(), config_->includePeerCertificate(), config_->includeTLSSession(), - config_->destinationLabels(), config_->requestHeaderMatchers()); + config_->destinationLabels(), config_->allowedHeadersMatcher(), + config_->disallowedHeadersMatcher()); ENVOY_STREAM_LOG(trace, "ext_authz filter calling authorization server", *decoder_callbacks_); // Store start time of ext_authz filter call diff --git a/source/extensions/filters/http/ext_authz/ext_authz.h b/source/extensions/filters/http/ext_authz/ext_authz.h index 94c5ec410588..ad2b14f92408 100644 --- a/source/extensions/filters/http/ext_authz/ext_authz.h +++ b/source/extensions/filters/http/ext_authz/ext_authz.h @@ -129,17 +129,21 @@ class FilterConfig { // HTTP authz servers only and defaults to blocking all but a few headers (i.e. Authorization, // Method, Path and Host). if (config.has_grpc_service() && config.has_allowed_headers()) { - request_header_matchers_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( + allowed_headers_matcher_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( config.allowed_headers(), false, factory_context); } else if (config.has_http_service()) { if (config.http_service().authorization_request().has_allowed_headers()) { - request_header_matchers_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( + allowed_headers_matcher_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( config.http_service().authorization_request().allowed_headers(), true, factory_context); } else { - request_header_matchers_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( + allowed_headers_matcher_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( config.allowed_headers(), true, factory_context); } } + if (config.has_disallowed_headers()) { + disallowed_headers_matcher_ = Filters::Common::ExtAuthz::CheckRequestUtils::toRequestMatchers( + config.disallowed_headers(), false, factory_context); + } } bool allowPartialMessage() const { return allow_partial_message_; } @@ -205,8 +209,12 @@ class FilterConfig { bool chargeClusterResponseStats() const { return charge_cluster_response_stats_; } - const Filters::Common::ExtAuthz::MatcherSharedPtr& requestHeaderMatchers() const { - return request_header_matchers_; + const Filters::Common::ExtAuthz::MatcherSharedPtr& allowedHeadersMatcher() const { + return allowed_headers_matcher_; + } + + const Filters::Common::ExtAuthz::MatcherSharedPtr& disallowedHeadersMatcher() const { + return disallowed_headers_matcher_; } private: @@ -267,7 +275,8 @@ class FilterConfig { // The stats for the filter. ExtAuthzFilterStats stats_; - Filters::Common::ExtAuthz::MatcherSharedPtr request_header_matchers_; + Filters::Common::ExtAuthz::MatcherSharedPtr allowed_headers_matcher_; + Filters::Common::ExtAuthz::MatcherSharedPtr disallowed_headers_matcher_; public: // TODO(nezdolik): deprecate cluster scope stats counters in favor of filter scope stats diff --git a/source/extensions/health_checkers/tcp/health_checker_impl.cc b/source/extensions/health_checkers/tcp/health_checker_impl.cc index 0c1b540157f8..585df883a571 100644 --- a/source/extensions/health_checkers/tcp/health_checker_impl.cc +++ b/source/extensions/health_checkers/tcp/health_checker_impl.cc @@ -25,6 +25,7 @@ #include "source/common/router/router.h" #include "source/common/runtime/runtime_features.h" #include "source/common/upstream/host_utility.h" +#include "source/extensions/common/proxy_protocol/proxy_protocol_header.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" @@ -56,7 +57,11 @@ TcpHealthCheckerImpl::TcpHealthCheckerImpl(const Cluster& cluster, auto bytes_or_error = PayloadMatcher::loadProtoBytes(send_repeated); THROW_IF_STATUS_NOT_OK(bytes_or_error, throw); return bytes_or_error.value(); - }()) { + }()), + proxy_protocol_config_(config.tcp_health_check().has_proxy_protocol_config() + ? std::make_unique( + config.tcp_health_check().proxy_protocol_config()) + : nullptr) { auto bytes_or_error = PayloadMatcher::loadProtoBytes(config.tcp_health_check().receive()); THROW_IF_STATUS_NOT_OK(bytes_or_error, throw); receive_bytes_ = bytes_or_error.value(); @@ -141,12 +146,28 @@ void TcpHealthCheckerImpl::TcpActiveHealthCheckSession::onInterval() { client_->noDelay(true); } + Buffer::OwnedImpl data; + bool should_write_data = false; + + if (parent_.proxy_protocol_config_ != nullptr) { + if (parent_.proxy_protocol_config_->version() == + envoy::config::core::v3::ProxyProtocolConfig::V1) { + auto src_addr = client_->connectionInfoProvider().localAddress()->ip(); + auto dst_addr = client_->connectionInfoProvider().remoteAddress()->ip(); + Extensions::Common::ProxyProtocol::generateV1Header(*src_addr, *dst_addr, data); + } else if (parent_.proxy_protocol_config_->version() == + envoy::config::core::v3::ProxyProtocolConfig::V2) { + Extensions::Common::ProxyProtocol::generateV2LocalHeader(data); + } + should_write_data = true; + } if (!parent_.send_bytes_.empty()) { - Buffer::OwnedImpl data; for (const std::vector& segment : parent_.send_bytes_) { data.add(segment.data(), segment.size()); } - + should_write_data = true; + } + if (should_write_data) { client_->write(data, false); } } diff --git a/source/extensions/health_checkers/tcp/health_checker_impl.h b/source/extensions/health_checkers/tcp/health_checker_impl.h index 1787332d6aac..ec3b14ef37c8 100644 --- a/source/extensions/health_checkers/tcp/health_checker_impl.h +++ b/source/extensions/health_checkers/tcp/health_checker_impl.h @@ -104,6 +104,7 @@ class TcpHealthCheckerImpl : public HealthCheckerImplBase { const PayloadMatcher::MatchSegments send_bytes_; PayloadMatcher::MatchSegments receive_bytes_; + const std::unique_ptr proxy_protocol_config_; }; } // namespace Upstream diff --git a/source/extensions/network/dns_resolver/cares/dns_impl.cc b/source/extensions/network/dns_resolver/cares/dns_impl.cc index 0c0e29accfdf..1478fd3a080d 100644 --- a/source/extensions/network/dns_resolver/cares/dns_impl.cc +++ b/source/extensions/network/dns_resolver/cares/dns_impl.cc @@ -64,7 +64,7 @@ absl::optional DnsResolverImpl::maybeBuildResolversCsv( for (const auto& resolver : resolvers) { // This should be an IP address (i.e. not a pipe). if (resolver->ip() == nullptr) { - throw EnvoyException( + throwEnvoyExceptionOrPanic( fmt::format("DNS resolver '{}' is not an IP address", resolver->asString())); } // Note that the ip()->port() may be zero if the port is not fully specified by the @@ -309,18 +309,16 @@ void DnsResolverImpl::PendingResolution::finishResolve() { callback_(pending_response_.status_, std::move(pending_response_.address_list_)); } END_TRY - catch (const EnvoyException& e) { - ENVOY_LOG(critical, "EnvoyException in c-ares callback: {}", e.what()); - dispatcher_.post([s = std::string(e.what())] { throw EnvoyException(s); }); - } - catch (const std::exception& e) { - ENVOY_LOG(critical, "std::exception in c-ares callback: {}", e.what()); - dispatcher_.post([s = std::string(e.what())] { throw EnvoyException(s); }); - } - catch (...) { - ENVOY_LOG(critical, "Unknown exception in c-ares callback"); - dispatcher_.post([] { throw EnvoyException("unknown"); }); - } + MULTI_CATCH( + const EnvoyException& e, + { + ENVOY_LOG(critical, "EnvoyException in c-ares callback: {}", e.what()); + dispatcher_.post([s = std::string(e.what())] { throwEnvoyExceptionOrPanic(s); }); + }, + { + ENVOY_LOG(critical, "Unknown exception in c-ares callback"); + dispatcher_.post([] { throwEnvoyExceptionOrPanic("unknown"); }); + }); } else { ENVOY_LOG_EVENT(debug, "cares_dns_callback_cancelled", "dns resolution callback for {} not issued. Cancelled with reason={}", diff --git a/source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.cc b/source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.cc index fbeca474a809..8c65137c6443 100644 --- a/source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.cc +++ b/source/extensions/network/dns_resolver/getaddrinfo/getaddrinfo.cc @@ -164,11 +164,12 @@ void GetAddrInfoDnsResolver::resolveThreadRoutine() { // the DNS query is retried. // NOTE: this is also how the c-ares resolver treats NONAME and NODATA: // https://github.com/envoyproxy/envoy/blob/099d85925b32ce8bf06e241ee433375a0a3d751b/source/extensions/network/dns_resolver/cares/dns_impl.h#L109-L111. - ENVOY_LOG(debug, "getaddrinfo no results rc={}", gai_strerror(rc.return_value_)); + ENVOY_LOG(debug, "getaddrinfo for host={} has no results rc={}", next_query->dns_name_, + gai_strerror(rc.return_value_)); response = std::make_pair(ResolutionStatus::Success, std::list()); } else { - ENVOY_LOG(debug, "getaddrinfo failed with rc={} errno={}", gai_strerror(rc.return_value_), - errorDetails(rc.errno_)); + ENVOY_LOG(debug, "getaddrinfo failed for host={} with rc={} errno={}", + next_query->dns_name_, gai_strerror(rc.return_value_), errorDetails(rc.errno_)); response = std::make_pair(ResolutionStatus::Failure, std::list()); } } 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 f1f593a83aa6..76c4117fe993 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 @@ -33,14 +33,18 @@ void GrpcMetricsStreamerImpl::send(MetricsPtr&& metrics) { message.mutable_envoy_metrics()->MergeFrom(*metrics); if (stream_ == nullptr) { + ENVOY_LOG(debug, "Establishing new gRPC metrics service stream"); stream_ = client_->start(service_method_, *this, Http::AsyncClient::StreamOptions()); // For perf reasons, the identifier is only sent on establishing the stream. auto* identifier = message.mutable_identifier(); *identifier->mutable_node() = local_info_.node(); } - if (stream_ != nullptr) { - stream_->sendMessage(message, false); + if (stream_ == nullptr) { + ENVOY_LOG(error, + "unable to establish metrics service stream. Will retry in the next flush cycle"); + return; } + stream_->sendMessage(message, false); } MetricsPtr MetricsFlusher::flush(Stats::MetricSnapshot& snapshot) const { diff --git a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h index 73a819c98f9d..f18f731cd67c 100644 --- a/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h +++ b/source/extensions/stat_sinks/metrics_service/grpc_metrics_service_impl.h @@ -14,6 +14,7 @@ #include "envoy/upstream/cluster_manager.h" #include "source/common/buffer/buffer_impl.h" +#include "source/common/grpc/status.h" #include "source/common/grpc/typed_async_client.h" namespace Envoy { @@ -63,7 +64,8 @@ using GrpcMetricsStreamerSharedPtr = class GrpcMetricsStreamerImpl : public Singleton::Instance, public GrpcMetricsStreamer { + envoy::service::metrics::v3::StreamMetricsResponse>, + public Logger::Loggable { public: GrpcMetricsStreamerImpl(Grpc::RawAsyncClientSharedPtr raw_async_client, const LocalInfo::LocalInfo& local_info); @@ -72,7 +74,11 @@ class GrpcMetricsStreamerImpl void send(MetricsPtr&& metrics) override; // Grpc::AsyncStreamCallbacks - void onRemoteClose(Grpc::Status::GrpcStatus, const std::string&) override { stream_ = nullptr; } + void onRemoteClose(Grpc::Status::GrpcStatus status, const std::string& message) override { + ENVOY_LOG(debug, "metric service stream closed with status: {} message: {}", + Grpc::Utility::grpcStatusToString(status), message); + stream_ = nullptr; + } private: const LocalInfo::LocalInfo& local_info_; diff --git a/source/extensions/transport_sockets/tls/BUILD b/source/extensions/transport_sockets/tls/BUILD index 3ce74ef46a9a..074c6a805cad 100644 --- a/source/extensions/transport_sockets/tls/BUILD +++ b/source/extensions/transport_sockets/tls/BUILD @@ -1,6 +1,7 @@ load( "//bazel:envoy_build_system.bzl", "envoy_cc_extension", + "envoy_cc_library", "envoy_extension_package", ) @@ -10,17 +11,52 @@ licenses(["notice"]) # Apache 2 envoy_extension_package() -envoy_cc_extension( - name = "config", - srcs = ["config.cc"], +envoy_cc_library( + name = "base_config", hdrs = ["config.h"], - # TLS is core functionality. - visibility = ["//visibility:public"], deps = [ "//envoy/network:transport_socket_interface", "//envoy/registry", "//envoy/server:transport_socket_config_interface", - "//source/common/tls:ssl_socket_lib", + "//source/common/tls:ssl_socket_base", + ], +) + +envoy_cc_library( + name = "downstream_config", + srcs = ["downstream_config.cc"], + hdrs = ["downstream_config.h"], + deps = [ + ":base_config", + "//envoy/network:transport_socket_interface", + "//envoy/registry", + "//envoy/server:transport_socket_config_interface", + "//source/common/tls:server_ssl_socket_lib", "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", ], + alwayslink = True, +) + +envoy_cc_library( + name = "upstream_config", + srcs = ["upstream_config.cc"], + hdrs = ["upstream_config.h"], + deps = [ + ":base_config", + "//envoy/network:transport_socket_interface", + "//envoy/registry", + "//envoy/server:transport_socket_config_interface", + "//source/common/tls:client_ssl_socket_lib", + "@envoy_api//envoy/extensions/transport_sockets/tls/v3:pkg_cc_proto", + ], + alwayslink = True, +) + +envoy_cc_extension( + name = "config", + visibility = ["//visibility:public"], + deps = [ + ":downstream_config", + ":upstream_config", + ], ) diff --git a/source/extensions/transport_sockets/tls/config.h b/source/extensions/transport_sockets/tls/config.h index 3e39adc38bfb..e332ea8cf1cf 100644 --- a/source/extensions/transport_sockets/tls/config.h +++ b/source/extensions/transport_sockets/tls/config.h @@ -18,30 +18,6 @@ class SslSocketConfigFactory : public virtual Server::Configuration::TransportSo std::string name() const override { return "envoy.transport_sockets.tls"; } }; -class UpstreamSslSocketFactory : public Server::Configuration::UpstreamTransportSocketConfigFactory, - public SslSocketConfigFactory { -public: - Network::UpstreamTransportSocketFactoryPtr createTransportSocketFactory( - const Protobuf::Message& config, - Server::Configuration::TransportSocketFactoryContext& context) override; - ProtobufTypes::MessagePtr createEmptyConfigProto() override; -}; - -DECLARE_FACTORY(UpstreamSslSocketFactory); - -class DownstreamSslSocketFactory - : public Server::Configuration::DownstreamTransportSocketConfigFactory, - public SslSocketConfigFactory { -public: - Network::DownstreamTransportSocketFactoryPtr - createTransportSocketFactory(const Protobuf::Message& config, - Server::Configuration::TransportSocketFactoryContext& context, - const std::vector& server_names) override; - ProtobufTypes::MessagePtr createEmptyConfigProto() override; -}; - -DECLARE_FACTORY(DownstreamSslSocketFactory); - } // namespace Tls } // namespace TransportSockets } // namespace Extensions diff --git a/source/extensions/transport_sockets/tls/config.cc b/source/extensions/transport_sockets/tls/downstream_config.cc similarity index 57% rename from source/extensions/transport_sockets/tls/config.cc rename to source/extensions/transport_sockets/tls/downstream_config.cc index d89789c6e2ad..7a3ea092db71 100644 --- a/source/extensions/transport_sockets/tls/config.cc +++ b/source/extensions/transport_sockets/tls/downstream_config.cc @@ -1,36 +1,17 @@ -#include "source/extensions/transport_sockets/tls/config.h" +#include "source/extensions/transport_sockets/tls/downstream_config.h" #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" #include "envoy/extensions/transport_sockets/tls/v3/tls.pb.validate.h" #include "source/common/protobuf/utility.h" #include "source/common/tls/context_config_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" namespace Envoy { namespace Extensions { namespace TransportSockets { namespace Tls { -Network::UpstreamTransportSocketFactoryPtr UpstreamSslSocketFactory::createTransportSocketFactory( - const Protobuf::Message& message, - Server::Configuration::TransportSocketFactoryContext& context) { - auto client_config = std::make_unique( - MessageUtil::downcastAndValidate< - const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext&>( - message, context.messageValidationVisitor()), - context); - return std::make_unique( - std::move(client_config), context.sslContextManager(), context.statsScope()); -} - -ProtobufTypes::MessagePtr UpstreamSslSocketFactory::createEmptyConfigProto() { - return std::make_unique(); -} - -LEGACY_REGISTER_FACTORY(UpstreamSslSocketFactory, - Server::Configuration::UpstreamTransportSocketConfigFactory, "tls"); - Network::DownstreamTransportSocketFactoryPtr DownstreamSslSocketFactory::createTransportSocketFactory( const Protobuf::Message& message, Server::Configuration::TransportSocketFactoryContext& context, diff --git a/source/extensions/transport_sockets/tls/downstream_config.h b/source/extensions/transport_sockets/tls/downstream_config.h new file mode 100644 index 000000000000..8a9d5db5c433 --- /dev/null +++ b/source/extensions/transport_sockets/tls/downstream_config.h @@ -0,0 +1,29 @@ +#pragma once + +#include "envoy/registry/registry.h" +#include "envoy/server/transport_socket_config.h" + +#include "source/extensions/transport_sockets/tls/config.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class DownstreamSslSocketFactory + : public Server::Configuration::DownstreamTransportSocketConfigFactory, + public SslSocketConfigFactory { +public: + Network::DownstreamTransportSocketFactoryPtr + createTransportSocketFactory(const Protobuf::Message& config, + Server::Configuration::TransportSocketFactoryContext& context, + const std::vector& server_names) override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; +}; + +DECLARE_FACTORY(DownstreamSslSocketFactory); + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/upstream_config.cc b/source/extensions/transport_sockets/tls/upstream_config.cc new file mode 100644 index 000000000000..c9bff7d78727 --- /dev/null +++ b/source/extensions/transport_sockets/tls/upstream_config.cc @@ -0,0 +1,37 @@ +#include "source/extensions/transport_sockets/tls/upstream_config.h" + +#include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" +#include "envoy/extensions/transport_sockets/tls/v3/tls.pb.validate.h" + +#include "source/common/protobuf/utility.h" +#include "source/common/tls/client_ssl_socket.h" +#include "source/common/tls/context_config_impl.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +Network::UpstreamTransportSocketFactoryPtr UpstreamSslSocketFactory::createTransportSocketFactory( + const Protobuf::Message& message, + Server::Configuration::TransportSocketFactoryContext& context) { + auto client_config = std::make_unique( + MessageUtil::downcastAndValidate< + const envoy::extensions::transport_sockets::tls::v3::UpstreamTlsContext&>( + message, context.messageValidationVisitor()), + context); + return std::make_unique( + std::move(client_config), context.sslContextManager(), context.statsScope()); +} + +ProtobufTypes::MessagePtr UpstreamSslSocketFactory::createEmptyConfigProto() { + return std::make_unique(); +} + +LEGACY_REGISTER_FACTORY(UpstreamSslSocketFactory, + Server::Configuration::UpstreamTransportSocketConfigFactory, "tls"); + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/transport_sockets/tls/upstream_config.h b/source/extensions/transport_sockets/tls/upstream_config.h new file mode 100644 index 000000000000..a053faf35b62 --- /dev/null +++ b/source/extensions/transport_sockets/tls/upstream_config.h @@ -0,0 +1,27 @@ +#pragma once + +#include "envoy/registry/registry.h" +#include "envoy/server/transport_socket_config.h" + +#include "source/extensions/transport_sockets/tls/config.h" + +namespace Envoy { +namespace Extensions { +namespace TransportSockets { +namespace Tls { + +class UpstreamSslSocketFactory : public Server::Configuration::UpstreamTransportSocketConfigFactory, + public SslSocketConfigFactory { +public: + Network::UpstreamTransportSocketFactoryPtr createTransportSocketFactory( + const Protobuf::Message& config, + Server::Configuration::TransportSocketFactoryContext& context) override; + ProtobufTypes::MessagePtr createEmptyConfigProto() override; +}; + +DECLARE_FACTORY(UpstreamSslSocketFactory); + +} // namespace Tls +} // namespace TransportSockets +} // namespace Extensions +} // namespace Envoy diff --git a/source/extensions/upstreams/http/config.cc b/source/extensions/upstreams/http/config.cc index 66b31e48c632..ce9330bd4661 100644 --- a/source/extensions/upstreams/http/config.cc +++ b/source/extensions/upstreams/http/config.cc @@ -82,19 +82,19 @@ bool useHttp3(const envoy::extensions::upstreams::http::v3::HttpProtocolOptions& return false; } -absl::optional +absl::StatusOr> getAlternateProtocolsCacheOptions( const envoy::extensions::upstreams::http::v3::HttpProtocolOptions& options, Server::Configuration::ServerFactoryContext& server_context) { if (options.has_auto_config() && options.auto_config().has_http3_protocol_options()) { if (!options.auto_config().has_alternate_protocols_cache_options()) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("alternate protocols cache must be configured when HTTP/3 " "is enabled with auto_config")); } auto cache_options = options.auto_config().alternate_protocols_cache_options(); if (cache_options.has_key_value_store_config() && server_context.options().concurrency() != 1) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("options has key value store but Envoy has concurrency = {} : {}", server_context.options().concurrency(), cache_options.DebugString())); } @@ -104,7 +104,7 @@ getAlternateProtocolsCacheOptions( return absl::nullopt; } -Envoy::Http::HeaderValidatorFactoryPtr createHeaderValidatorFactory( +absl::StatusOr createHeaderValidatorFactory( [[maybe_unused]] const envoy::extensions::upstreams::http::v3::HttpProtocolOptions& options, [[maybe_unused]] Server::Configuration::ServerFactoryContext& server_context) { @@ -134,26 +134,26 @@ Envoy::Http::HeaderValidatorFactoryPtr createHeaderValidatorFactory( auto* factory = Envoy::Config::Utility::getFactory( header_validator_config); if (!factory) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("Header validator extension not found: '{}'", header_validator_config.name())); } header_validator_factory = factory->createFromProto(header_validator_config.typed_config(), server_context); if (!header_validator_factory) { - throwEnvoyExceptionOrPanic(fmt::format("Header validator extension could not be created: '{}'", - header_validator_config.name())); + return absl::InvalidArgumentError(fmt::format( + "Header validator extension could not be created: '{}'", header_validator_config.name())); } #else if (options.has_header_validation_config()) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( fmt::format("This Envoy binary does not support header validator extensions: '{}'", options.header_validation_config().name())); } if (Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.enable_universal_header_validator")) { - throwEnvoyExceptionOrPanic( + return absl::InvalidArgumentError( "Header validator can not be enabled since this Envoy binary does not support it."); } #endif @@ -190,8 +190,13 @@ ProtocolOptionsConfigImpl::createProtocolOptionsConfig( Server::Configuration::ServerFactoryContext& server_context) { auto options_or_error = Http2::Utility::initializeAndValidateOptions(getHttp2Options(options)); RETURN_IF_STATUS_NOT_OK(options_or_error); - return std::shared_ptr( - new ProtocolOptionsConfigImpl(options, options_or_error.value(), server_context)); + auto cache_options_or_error = getAlternateProtocolsCacheOptions(options, server_context); + RETURN_IF_STATUS_NOT_OK(cache_options_or_error); + auto validator_factory_or_error = createHeaderValidatorFactory(options, server_context); + RETURN_IF_STATUS_NOT_OK(validator_factory_or_error); + return std::shared_ptr(new ProtocolOptionsConfigImpl( + options, options_or_error.value(), std::move(validator_factory_or_error.value()), + cache_options_or_error.value(), server_context)); } absl::StatusOr> @@ -212,6 +217,8 @@ ProtocolOptionsConfigImpl::createProtocolOptionsConfig( ProtocolOptionsConfigImpl::ProtocolOptionsConfigImpl( const envoy::extensions::upstreams::http::v3::HttpProtocolOptions& options, envoy::config::core::v3::Http2ProtocolOptions http2_options, + Envoy::Http::HeaderValidatorFactoryPtr&& header_validator_factory, + absl::optional cache_options, Server::Configuration::ServerFactoryContext& server_context) : http1_settings_(Envoy::Http::Http1::parseHttp1Settings( getHttpOptions(options), server_context.messageValidationVisitor())), @@ -223,8 +230,8 @@ ProtocolOptionsConfigImpl::ProtocolOptionsConfigImpl( options.upstream_http_protocol_options()) : absl::nullopt), http_filters_(options.http_filters()), - alternate_protocol_cache_options_(getAlternateProtocolsCacheOptions(options, server_context)), - header_validator_factory_(createHeaderValidatorFactory(options, server_context)), + alternate_protocol_cache_options_(std::move(cache_options)), + header_validator_factory_(std::move(header_validator_factory)), use_downstream_protocol_(options.has_use_downstream_protocol_config()), use_http2_(useHttp2(options)), use_http3_(useHttp3(options)), use_alpn_(options.has_auto_config()) { diff --git a/source/extensions/upstreams/http/config.h b/source/extensions/upstreams/http/config.h index bbd33753a38e..fcc49e722a3d 100644 --- a/source/extensions/upstreams/http/config.h +++ b/source/extensions/upstreams/http/config.h @@ -64,6 +64,8 @@ class ProtocolOptionsConfigImpl : public Upstream::ProtocolOptionsConfig { ProtocolOptionsConfigImpl( const envoy::extensions::upstreams::http::v3::HttpProtocolOptions& options, envoy::config::core::v3::Http2ProtocolOptions validated_h2_options, + Envoy::Http::HeaderValidatorFactoryPtr&& header_validator_factory, + absl::optional cache_options, Server::Configuration::ServerFactoryContext& server_context); // Constructor for legacy (deprecated) config. ProtocolOptionsConfigImpl( diff --git a/source/extensions/upstreams/http/tcp/upstream_request.h b/source/extensions/upstreams/http/tcp/upstream_request.h index 8c7431250c1e..3a0df2815529 100644 --- a/source/extensions/upstreams/http/tcp/upstream_request.h +++ b/source/extensions/upstreams/http/tcp/upstream_request.h @@ -26,19 +26,16 @@ class TcpConnPool : public Router::GenericConnPool, public Envoy::Tcp::Connectio Upstream::ResourcePriority priority, Upstream::LoadBalancerContext* ctx) { conn_pool_data_ = thread_local_cluster.tcpConnPool(priority, ctx); } + ~TcpConnPool() override { + ENVOY_BUG(upstream_handle_ == nullptr, "upstream_handle not null"); + resetUpstreamHandleIfSet(); + } // Router::GenericConnPool void newStream(Router::GenericConnectionPoolCallbacks* callbacks) override { callbacks_ = callbacks; upstream_handle_ = conn_pool_data_.value().newConnection(*this); } - bool cancelAnyPendingStream() override { - if (upstream_handle_) { - upstream_handle_->cancel(Envoy::Tcp::ConnectionPool::CancelPolicy::Default); - upstream_handle_ = nullptr; - return true; - } - return false; - } + bool cancelAnyPendingStream() override { return resetUpstreamHandleIfSet(); } Upstream::HostDescriptionConstSharedPtr host() const override { return conn_pool_data_.value().host(); } @@ -56,6 +53,15 @@ class TcpConnPool : public Router::GenericConnPool, public Envoy::Tcp::Connectio Upstream::HostDescriptionConstSharedPtr host) override; private: + bool resetUpstreamHandleIfSet() { + if (upstream_handle_) { + upstream_handle_->cancel(Envoy::Tcp::ConnectionPool::CancelPolicy::Default); + upstream_handle_ = nullptr; + return true; + } + return false; + } + absl::optional conn_pool_data_; Envoy::Tcp::ConnectionPool::Cancellable* upstream_handle_{}; Router::GenericConnectionPoolCallbacks* callbacks_{}; diff --git a/source/server/hot_restart_impl.cc b/source/server/hot_restart_impl.cc index 033aa5d12cc1..679981a39120 100644 --- a/source/server/hot_restart_impl.cc +++ b/source/server/hot_restart_impl.cc @@ -97,10 +97,10 @@ void initializeMutex(pthread_mutex_t& mutex) { // the socket names to entirely prevent collisions between consecutive base ids. HotRestartImpl::HotRestartImpl(uint32_t base_id, uint32_t restart_epoch, const std::string& socket_path, mode_t socket_mode, - bool skip_hot_restart_on_no_parent) + bool skip_hot_restart_on_no_parent, bool skip_parent_stats) : base_id_(base_id), scaled_base_id_(base_id * 10), as_child_(HotRestartingChild(scaled_base_id_, restart_epoch, socket_path, socket_mode, - skip_hot_restart_on_no_parent)), + skip_hot_restart_on_no_parent, skip_parent_stats)), as_parent_(HotRestartingParent(scaled_base_id_, restart_epoch, socket_path, socket_mode)), shmem_(attachSharedMemory(scaled_base_id_, restart_epoch)), log_lock_(shmem_->log_lock_), access_log_lock_(shmem_->access_log_lock_) { diff --git a/source/server/hot_restart_impl.h b/source/server/hot_restart_impl.h index 3e22a2e2cfff..2182a590784f 100644 --- a/source/server/hot_restart_impl.h +++ b/source/server/hot_restart_impl.h @@ -98,7 +98,7 @@ class ProcessSharedMutex : public Thread::BasicLockable { class HotRestartImpl : public HotRestart { public: HotRestartImpl(uint32_t base_id, uint32_t restart_epoch, const std::string& socket_path, - mode_t socket_mode, bool skip_hot_restart_on_no_parent); + mode_t socket_mode, bool skip_hot_restart_on_no_parent, bool skip_parent_stats); // Server::HotRestart void drainParentListeners() override; diff --git a/source/server/hot_restarting_child.cc b/source/server/hot_restarting_child.cc index d077b9fce229..7e1f21109b1b 100644 --- a/source/server/hot_restarting_child.cc +++ b/source/server/hot_restarting_child.cc @@ -50,10 +50,11 @@ HotRestartingChild::UdpForwardingContext::getListenerForDestination( // drained and terminated. HotRestartingChild::HotRestartingChild(int base_id, int restart_epoch, const std::string& socket_path, mode_t socket_mode, - bool skip_hot_restart_on_no_parent) + bool skip_hot_restart_on_no_parent, bool skip_parent_stats) : HotRestartingBase(base_id), restart_epoch_(restart_epoch), parent_terminated_(restart_epoch == 0), parent_drained_(restart_epoch == 0), - skip_hot_restart_on_no_parent_(skip_hot_restart_on_no_parent) { + skip_hot_restart_on_no_parent_(skip_hot_restart_on_no_parent), + skip_parent_stats_(skip_parent_stats) { main_rpc_stream_.initDomainSocketAddress(&parent_address_); std::string socket_path_udp = socket_path + "_udp"; udp_forwarding_rpc_stream_.initDomainSocketAddress(&parent_address_udp_forwarding_); @@ -144,7 +145,7 @@ int HotRestartingChild::duplicateParentListenSocket(const std::string& address, } std::unique_ptr HotRestartingChild::getParentStats() { - if (parent_terminated_) { + if (parent_terminated_ || skip_parent_stats_) { return nullptr; } diff --git a/source/server/hot_restarting_child.h b/source/server/hot_restarting_child.h index 48b9b7e59d1d..ac44390d6c0d 100644 --- a/source/server/hot_restarting_child.h +++ b/source/server/hot_restarting_child.h @@ -43,7 +43,8 @@ class HotRestartingChild : public HotRestartingBase, }; HotRestartingChild(int base_id, int restart_epoch, const std::string& socket_path, - mode_t socket_mode, bool skip_hot_restart_on_no_parent); + mode_t socket_mode, bool skip_hot_restart_on_no_parent, + bool skip_parent_stats); ~HotRestartingChild() override = default; void initialize(Event::Dispatcher& dispatcher); @@ -76,6 +77,7 @@ class HotRestartingChild : public HotRestartingBase, bool parent_terminated_; bool parent_drained_ ABSL_GUARDED_BY(registry_mu_); const bool skip_hot_restart_on_no_parent_; + const bool skip_parent_stats_; sockaddr_un parent_address_; sockaddr_un parent_address_udp_forwarding_; std::unique_ptr stat_merger_{}; diff --git a/source/server/options_impl.cc b/source/server/options_impl.cc index 75b9899a3380..eaa6005bb260 100644 --- a/source/server/options_impl.cc +++ b/source/server/options_impl.cc @@ -71,6 +71,12 @@ OptionsImpl::OptionsImpl(std::vector args, " connection to the parent cannot be established. Set this to true to instead continue with" " a regular startup, while retaining the new epoch value.", cmd, false); + TCLAP::SwitchArg skip_hot_restart_parent_stats( + "", "skip-hot-restart-parent-stats", + "When hot restarting, by default the child instance copies stats from the parent" + " instance periodically during the draining period. This can potentially be an" + " expensive operation; set this to true to reset all stats in child process.", + cmd, false); TCLAP::ValueArg base_id_path( "", "base-id-path", "Path to which the base ID is written", false, "", "string", cmd); TCLAP::ValueArg concurrency("", "concurrency", "# of worker threads to run", false, @@ -235,6 +241,7 @@ OptionsImpl::OptionsImpl(std::vector args, base_id_ = base_id.getValue(); use_dynamic_base_id_ = use_dynamic_base_id.getValue(); skip_hot_restart_on_no_parent_ = skip_hot_restart_on_no_parent.getValue(); + skip_hot_restart_parent_stats_ = skip_hot_restart_parent_stats.getValue(); base_id_path_ = base_id_path.getValue(); restart_epoch_ = restart_epoch.getValue(); @@ -380,6 +387,7 @@ Server::CommandLineOptionsPtr OptionsImpl::toCommandLineOptions() const { command_line_options->set_base_id(baseId()); command_line_options->set_use_dynamic_base_id(useDynamicBaseId()); command_line_options->set_skip_hot_restart_on_no_parent(skipHotRestartOnNoParent()); + command_line_options->set_skip_hot_restart_parent_stats(skipHotRestartParentStats()); command_line_options->set_base_id_path(baseIdPath()); command_line_options->set_concurrency(concurrency()); command_line_options->set_config_path(configPath()); diff --git a/source/server/options_impl_base.h b/source/server/options_impl_base.h index 6199025f97d3..edbcf9a3f9e3 100644 --- a/source/server/options_impl_base.h +++ b/source/server/options_impl_base.h @@ -33,6 +33,7 @@ class OptionsImplBase : public Server::Options, protected Logger::Loggable>(); + NiceMock stream_info; + stream_info.upstreamInfo()->setUpstreamHost(mock_host); + MockTimeSystem time_system; auto& upstream_timing = stream_info.upstream_info_->upstreamTiming(); @@ -648,30 +653,113 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { ProtoEq(ValueUtil::nullValue())); } + { + StreamInfoFormatter upstream_format("UPSTREAM_HOST_NAME"); + + // Hostname is used. + mock_host->hostname_ = "upstream_host_xxx"; + EXPECT_EQ("upstream_host_xxx", upstream_format.formatWithContext({}, stream_info)); + EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::stringValue("upstream_host_xxx"))); + + // Hostname is not used then the main address is used. + mock_host->hostname_.clear(); + EXPECT_EQ("10.0.0.1:443", upstream_format.formatWithContext({}, stream_info)); + EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::stringValue("10.0.0.1:443"))); + } + + auto test_upstream_remote_address = + Network::Address::InstanceConstSharedPtr{new Network::Address::Ipv4Instance("10.0.0.2", 80)}; + auto default_upstream_remote_address = stream_info.upstreamInfo()->upstreamRemoteAddress(); + { StreamInfoFormatter upstream_format("UPSTREAM_HOST"); EXPECT_EQ("10.0.0.1:443", upstream_format.formatWithContext({}, stream_info)); EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::stringValue("10.0.0.1:443"))); + + stream_info.upstreamInfo()->setUpstreamHost(nullptr); + EXPECT_EQ(absl::nullopt, upstream_format.formatWithContext({}, stream_info)); + EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::nullValue())); + + // Reset the state. + stream_info.upstreamInfo()->setUpstreamHost(mock_host); + } + + { + StreamInfoFormatter upstream_format("UPSTREAM_REMOTE_ADDRESS"); + + // Has valid upstream remote address and it will be used as priority. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(test_upstream_remote_address); + EXPECT_EQ("10.0.0.2:80", upstream_format.formatWithContext({}, stream_info)); + EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::stringValue("10.0.0.2:80"))); + + // Upstream remote address is not available. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(nullptr); + EXPECT_EQ(absl::nullopt, upstream_format.formatWithContext({}, stream_info)); + EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::nullValue())); + + // Reset to default one. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(default_upstream_remote_address); } { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.upstream_remote_address_use_connection", "false"}}); + StreamInfoFormatter upstream_format("UPSTREAM_REMOTE_ADDRESS"); + + // Has valid upstream remote address but it would not be used because of the runtime feature. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(test_upstream_remote_address); EXPECT_EQ("10.0.0.1:443", upstream_format.formatWithContext({}, stream_info)); EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), ProtoEq(ValueUtil::stringValue("10.0.0.1:443"))); + + // Reset to default one. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(default_upstream_remote_address); } + { StreamInfoFormatter upstream_format("UPSTREAM_REMOTE_ADDRESS_WITHOUT_PORT"); - EXPECT_EQ("10.0.0.1", upstream_format.formatWithContext({}, stream_info)); + + // Has valid upstream remote address and it will be used as priority. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(test_upstream_remote_address); + EXPECT_EQ("10.0.0.2", upstream_format.formatWithContext({}, stream_info)); EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::stringValue("10.0.0.1"))); + ProtoEq(ValueUtil::stringValue("10.0.0.2"))); + + // Upstream remote address is not available. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(nullptr); + EXPECT_EQ(absl::nullopt, upstream_format.formatWithContext({}, stream_info)); + EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::nullValue())); + + // Reset to default one. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(default_upstream_remote_address); } + { StreamInfoFormatter upstream_format("UPSTREAM_REMOTE_PORT"); - EXPECT_EQ("443", upstream_format.formatWithContext({}, stream_info)); + + // Has valid upstream remote address and it will be used as priority. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(test_upstream_remote_address); + EXPECT_EQ("80", upstream_format.formatWithContext({}, stream_info)); EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::numberValue(443))); + ProtoEq(ValueUtil::numberValue(80))); + + // Upstream remote address is not available. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(nullptr); + EXPECT_EQ(absl::nullopt, upstream_format.formatWithContext({}, stream_info)); + EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), + ProtoEq(ValueUtil::nullValue())); + + // Reset to default one. + stream_info.upstreamInfo()->setUpstreamRemoteAddress(default_upstream_remote_address); } { @@ -696,14 +784,6 @@ TEST(SubstitutionFormatterTest, streamInfoFormatter) { ProtoEq(ValueUtil::nullValue())); } - { - StreamInfoFormatter upstream_format("UPSTREAM_HOST"); - stream_info.upstreamInfo()->setUpstreamHost(nullptr); - EXPECT_EQ(absl::nullopt, upstream_format.formatWithContext({}, stream_info)); - EXPECT_THAT(upstream_format.formatValueWithContext({}, stream_info), - ProtoEq(ValueUtil::nullValue())); - } - { NiceMock os_sys_calls; TestThreadsafeSingletonInjector os_calls(&os_sys_calls); diff --git a/test/common/grpc/grpc_client_integration_test_harness.h b/test/common/grpc/grpc_client_integration_test_harness.h index 352cac598059..994da5cce62b 100644 --- a/test/common/grpc/grpc_client_integration_test_harness.h +++ b/test/common/grpc/grpc_client_integration_test_harness.h @@ -27,7 +27,8 @@ #include "source/common/stats/symbol_table.h" #include "source/common/tls/context_config_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/client_ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "test/common/grpc/grpc_client_integration.h" #include "test/common/grpc/utility.h" diff --git a/test/common/http/async_client_impl_test.cc b/test/common/http/async_client_impl_test.cc index 3aefd2475118..ae939f633e59 100644 --- a/test/common/http/async_client_impl_test.cc +++ b/test/common/http/async_client_impl_test.cc @@ -177,6 +177,59 @@ TEST_F(AsyncClientImplTest, BasicStream) { .value()); } +TEST_F(AsyncClientImplTest, BasicStreamWithInternalHeadersDisabled) { + Buffer::InstancePtr body{new Buffer::OwnedImpl("test body")}; + + EXPECT_CALL(cm_.thread_local_cluster_.conn_pool_, newStream(_, _, _)) + .WillOnce(Invoke( + [&](ResponseDecoder& decoder, ConnectionPool::Callbacks& callbacks, + const ConnectionPool::Instance::StreamOptions&) -> ConnectionPool::Cancellable* { + // The backing object of 'decoder' should also implemented the + // Router::UpstreamToDownstream. + const auto* upstream_to_downstream = + dynamic_cast(&decoder); + EXPECT_NE(nullptr, upstream_to_downstream); + // Ensure the route() is populated and valid. + EXPECT_NE(nullptr, upstream_to_downstream->route().routeEntry()); + + callbacks.onPoolReady(stream_encoder_, cm_.thread_local_cluster_.conn_pool_.host_, + stream_info_, {}); + response_decoder_ = &decoder; + return nullptr; + })); + + // Create the headers without x-envoy-internal and x-forwarded-for. + TestRequestHeaderMapImpl headers; + HttpTestUtility::addDefaultHeaders(headers); + + EXPECT_CALL(stream_encoder_, encodeHeaders(HeaderMapEqualRef(&headers), false)); + EXPECT_CALL(stream_encoder_, encodeData(BufferEqual(body.get()), true)); + + expectResponseHeaders(stream_callbacks_, 200, false); + EXPECT_CALL(stream_callbacks_, onData(BufferEqual(body.get()), true)); + EXPECT_CALL(stream_callbacks_, onComplete()); + + AsyncClient::StreamOptions option = AsyncClient::StreamOptions(); + // Set stream option to disable x-envoy-internal and x-forwarded-for headers. + option.setSendInternal(false); + option.setSendXff(false); + AsyncClient::Stream* stream = client_.start(stream_callbacks_, option); + + TestRequestHeaderMapImpl send_headers = headers; + stream->sendHeaders(send_headers, false); + stream->sendData(*body, true); + + response_decoder_->decode1xxHeaders( + ResponseHeaderMapPtr(new TestResponseHeaderMapImpl{{":status", "100"}})); + response_decoder_->decodeHeaders( + ResponseHeaderMapPtr(new TestResponseHeaderMapImpl{{":status", "200"}}), false); + response_decoder_->decodeData(*body, true); + + EXPECT_EQ( + 1UL, + cm_.thread_local_cluster_.cluster_.info_->stats_store_.counter("upstream_rq_200").value()); +} + TEST_F(AsyncClientImplTest, Basic) { message_->body().add("test body"); Buffer::Instance& data = message_->body(); diff --git a/test/common/http/http1/codec_impl_test.cc b/test/common/http/http1/codec_impl_test.cc index 89c649961247..7b96950ed324 100644 --- a/test/common/http/http1/codec_impl_test.cc +++ b/test/common/http/http1/codec_impl_test.cc @@ -198,8 +198,10 @@ class Http1ServerConnectionImplTest : public Http1CodecTestBase { void testServerAllowChunkedContentLength(uint32_t content_length, bool allow_chunked_length); - void testValueHasNullCharacter(absl::string_view value, absl::string_view expected_error_details, - absl::string_view expected_error_message); + void testRequestWithValueExpectSuccess(absl::string_view value, absl::string_view expected_value); + void testRequestWithValueExpectFailure(absl::string_view value, + absl::string_view expected_error_details, + absl::string_view expected_error_message); // Send the request, and validate the received request headers. // Then send a response just to clean up. @@ -2554,7 +2556,9 @@ class Http1ClientConnectionImplTest : public Http1CodecTestBase { Http::ClientConnectionPtr codec_; void testClientAllowChunkedContentLength(uint32_t content_length, bool allow_chunked_length); - void testValueHasNullCharacter(absl::string_view value, absl::string_view expected_error_message); + void testRequestWithValueExpectSuccess(absl::string_view value, absl::string_view expected_value); + void testRequestWithValueExpectFailure(absl::string_view value, + absl::string_view expected_error_message); protected: Stats::TestUtil::TestStore store_; @@ -4886,7 +4890,27 @@ TEST_P(Http1ClientConnectionImplTest, ObsFold) { // SPELLCHECKER(on) } -void Http1ServerConnectionImplTest::testValueHasNullCharacter( +void Http1ServerConnectionImplTest::testRequestWithValueExpectSuccess( + absl::string_view value, absl::string_view expected_value) { + initialize(); + + StrictMock decoder; + TestRequestHeaderMapImpl expected_headers{ + {":path", "/"}, + {":method", "GET"}, + {"key", std::string(expected_value)}, + }; + EXPECT_CALL(callbacks_, newStream(_, _)).WillOnce(ReturnRef(decoder)); + EXPECT_CALL(decoder, decodeHeaders_(HeaderMapEqual(&expected_headers), true)); + + Buffer::OwnedImpl buffer(absl::StrCat("GET / HTTP/1.1\r\n" + "key: ", + value, "\r\n\r\n")); + auto status = codec_->dispatch(buffer); + EXPECT_TRUE(status.ok()); +} + +void Http1ServerConnectionImplTest::testRequestWithValueExpectFailure( absl::string_view value, absl::string_view expected_error_details, absl::string_view expected_error_message) { initialize(); @@ -4908,11 +4932,11 @@ TEST_P(Http1ServerConnectionImplTest, ValueStartsWithNullCharacter) { const std::string value = absl::StrCat(kNullCharacter, "value starts with null character"); if (parser_impl_ == Http1ParserImpl::BalsaParser) { - testValueHasNullCharacter(value, "http1.invalid_characters", - "header value contains invalid chars"); + testRequestWithValueExpectFailure(value, "http1.invalid_characters", + "header value contains invalid chars"); } else { - testValueHasNullCharacter(value, "http1.invalid_characters", - "header value contains invalid chars"); + testRequestWithValueExpectFailure(value, "http1.invalid_characters", + "header value contains invalid chars"); } } @@ -4921,10 +4945,10 @@ TEST_P(Http1ServerConnectionImplTest, ValueWithNullCharacterInTheMiddle) { absl::StrCat("value has", kNullCharacter, "null character in the middle"); if (parser_impl_ == Http1ParserImpl::BalsaParser) { - testValueHasNullCharacter(value, "http1.invalid_characters", - "header value contains invalid chars"); + testRequestWithValueExpectFailure(value, "http1.invalid_characters", + "header value contains invalid chars"); } else { - testValueHasNullCharacter(value, "http1.codec_error", "HPE_INVALID_HEADER_TOKEN"); + testRequestWithValueExpectFailure(value, "http1.codec_error", "HPE_INVALID_HEADER_TOKEN"); } } @@ -4932,14 +4956,91 @@ TEST_P(Http1ServerConnectionImplTest, ValueEndsWithNullCharacter) { const std::string value = absl::StrCat("value ends in null character", kNullCharacter); if (parser_impl_ == Http1ParserImpl::BalsaParser) { - testValueHasNullCharacter(value, "http1.invalid_characters", - "header value contains invalid chars"); + testRequestWithValueExpectFailure(value, "http1.invalid_characters", + "header value contains invalid chars"); + } else { + testRequestWithValueExpectFailure(value, "http1.codec_error", "HPE_INVALID_HEADER_TOKEN"); + } +} + +TEST_P(Http1ServerConnectionImplTest, ValueStartsWithCR) { + const absl::string_view value = "\r value starts with carriage return"; + + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + const absl::string_view expected_value = "value starts with carriage return"; + testRequestWithValueExpectSuccess(value, expected_value); + } else { +#ifdef ENVOY_ENABLE_UHV + testRequestWithValueExpectFailure(value, "http1.codec_error", "HPE_INVALID_HEADER_TOKEN"); +#else + testRequestWithValueExpectFailure(value, "http1.codec_error", "HPE_STRICT"); +#endif + } +} + +TEST_P(Http1ServerConnectionImplTest, ValueWithCRInTheMiddle) { + const absl::string_view value = "value has \r carriage return in the middle"; + + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + const absl::string_view expected_value = "value has carriage return in the middle"; + testRequestWithValueExpectSuccess(value, expected_value); + } else { + testRequestWithValueExpectFailure(value, "http1.codec_error", "HPE_LF_EXPECTED"); + } +} + +TEST_P(Http1ServerConnectionImplTest, ValueEndsWithCR) { + const absl::string_view value = "value ends in carriage return \r"; + + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + const absl::string_view expected_value = "value ends in carriage return"; + testRequestWithValueExpectSuccess(value, expected_value); } else { - testValueHasNullCharacter(value, "http1.codec_error", "HPE_INVALID_HEADER_TOKEN"); + testRequestWithValueExpectFailure(value, "http1.codec_error", "HPE_LF_EXPECTED"); } } -void Http1ClientConnectionImplTest::testValueHasNullCharacter( +TEST_P(Http1ServerConnectionImplTest, ValueStartsWithLF) { + const absl::string_view value = "\n value starts with line feed"; + const absl::string_view expected_value = "value starts with line feed"; + testRequestWithValueExpectSuccess(value, expected_value); +} + +TEST_P(Http1ServerConnectionImplTest, ValueWithLFInTheMiddle) { + const absl::string_view value = "value has \n line feed in the middle"; + const absl::string_view expected_value = "value has line feed in the middle"; + testRequestWithValueExpectSuccess(value, expected_value); +} + +TEST_P(Http1ServerConnectionImplTest, ValueEndsWithLF) { + const absl::string_view value = "value ends in line feed \n"; + const absl::string_view expected_value = "value ends in line feed"; + testRequestWithValueExpectSuccess(value, expected_value); +} + +void Http1ClientConnectionImplTest::testRequestWithValueExpectSuccess( + absl::string_view value, absl::string_view expected_value) { + initialize(); + + NiceMock response_decoder; + Http::RequestEncoder& request_encoder = codec_->newStream(response_decoder); + TestRequestHeaderMapImpl headers{{":method", "GET"}, {":path", "/"}, {":authority", "host"}}; + EXPECT_TRUE(request_encoder.encodeHeaders(headers, true).ok()); + + TestResponseHeaderMapImpl expected_headers{ + {":status", "200"}, + {"key", std::string(expected_value)}, + }; + EXPECT_CALL(response_decoder, decodeHeaders_(HeaderMapEqual(&expected_headers), false)); + + Buffer::OwnedImpl response(absl::StrCat("HTTP/1.1 200 OK\r\n" + "key: ", + value, "\r\n\r\n")); + auto status = codec_->dispatch(response); + EXPECT_TRUE(status.ok()); +} + +void Http1ClientConnectionImplTest::testRequestWithValueExpectFailure( absl::string_view value, absl::string_view expected_error_message) { initialize(); @@ -4960,9 +5061,9 @@ TEST_P(Http1ClientConnectionImplTest, ValueStartsWithNullCharacter) { const std::string value = absl::StrCat(kNullCharacter, "value starts with null character"); if (parser_impl_ == Http1ParserImpl::BalsaParser) { - testValueHasNullCharacter(value, "header value contains invalid chars"); + testRequestWithValueExpectFailure(value, "header value contains invalid chars"); } else { - testValueHasNullCharacter(value, "header value contains invalid chars"); + testRequestWithValueExpectFailure(value, "header value contains invalid chars"); } } @@ -4971,9 +5072,9 @@ TEST_P(Http1ClientConnectionImplTest, ValueWithNullCharacterInTheMiddle) { absl::StrCat("value has", kNullCharacter, "null character in the middle"); if (parser_impl_ == Http1ParserImpl::BalsaParser) { - testValueHasNullCharacter(value, "header value contains invalid chars"); + testRequestWithValueExpectFailure(value, "header value contains invalid chars"); } else { - testValueHasNullCharacter(value, "HPE_INVALID_HEADER_TOKEN"); + testRequestWithValueExpectFailure(value, "HPE_INVALID_HEADER_TOKEN"); } } @@ -4981,11 +5082,66 @@ TEST_P(Http1ClientConnectionImplTest, ValueEndsWithNullCharacter) { const std::string value = absl::StrCat("value ends in null character", kNullCharacter); if (parser_impl_ == Http1ParserImpl::BalsaParser) { - testValueHasNullCharacter(value, "header value contains invalid chars"); + testRequestWithValueExpectFailure(value, "header value contains invalid chars"); + } else { + testRequestWithValueExpectFailure(value, "HPE_INVALID_HEADER_TOKEN"); + } +} + +TEST_P(Http1ClientConnectionImplTest, ValueStartsWithCR) { + const absl::string_view value = "\r value starts with carriage return"; + + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + const absl::string_view expected_value = "value starts with carriage return"; + testRequestWithValueExpectSuccess(value, expected_value); + } else { +#ifdef ENVOY_ENABLE_UHV + testRequestWithValueExpectFailure(value, "HPE_INVALID_HEADER_TOKEN"); +#else + testRequestWithValueExpectFailure(value, "HPE_STRICT"); +#endif + } +} + +TEST_P(Http1ClientConnectionImplTest, ValueWithCRInTheMiddle) { + const absl::string_view value = "value has \r carriage return in the middle"; + + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + const absl::string_view expected_value = "value has carriage return in the middle"; + testRequestWithValueExpectSuccess(value, expected_value); + } else { + testRequestWithValueExpectFailure(value, "HPE_LF_EXPECTED"); + } +} + +TEST_P(Http1ClientConnectionImplTest, ValueEndsWithCR) { + const absl::string_view value = "value ends in carriage return \r"; + + if (parser_impl_ == Http1ParserImpl::BalsaParser) { + const absl::string_view expected_value = "value ends in carriage return"; + testRequestWithValueExpectSuccess(value, expected_value); } else { - testValueHasNullCharacter(value, "HPE_INVALID_HEADER_TOKEN"); + testRequestWithValueExpectFailure(value, "HPE_LF_EXPECTED"); } } +TEST_P(Http1ClientConnectionImplTest, ValueStartsWithLF) { + const absl::string_view value = "\n value starts with line feed"; + const absl::string_view expected_value = "value starts with line feed"; + testRequestWithValueExpectSuccess(value, expected_value); +} + +TEST_P(Http1ClientConnectionImplTest, ValueWithLFInTheMiddle) { + const absl::string_view value = "value has \n line feed in the middle"; + const absl::string_view expected_value = "value has line feed in the middle"; + testRequestWithValueExpectSuccess(value, expected_value); +} + +TEST_P(Http1ClientConnectionImplTest, ValueEndsWithLF) { + const absl::string_view value = "value ends in line feed \n"; + const absl::string_view expected_value = "value ends in line feed"; + testRequestWithValueExpectSuccess(value, expected_value); +} + } // namespace Http } // namespace Envoy diff --git a/test/common/protobuf/utility_test.cc b/test/common/protobuf/utility_test.cc index e0af0dab62c9..b2f0c90eb038 100644 --- a/test/common/protobuf/utility_test.cc +++ b/test/common/protobuf/utility_test.cc @@ -1701,78 +1701,184 @@ TEST_F(ProtobufUtilityTest, GetYamlStringFromProtoInvalidAny) { } TEST(DurationUtilTest, OutOfRange) { - { - ProtobufWkt::Duration duration; - duration.set_seconds(-1); - EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); - } - { - ProtobufWkt::Duration duration; - duration.set_nanos(-1); - EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); - } - { - ProtobufWkt::Duration duration; - duration.set_nanos(1000000000); - EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); - } - { - ProtobufWkt::Duration duration; - duration.set_seconds(Protobuf::util::TimeUtil::kDurationMaxSeconds + 1); - EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); - } - { - ProtobufWkt::Duration duration; - constexpr int64_t kMaxInt64Nanoseconds = - std::numeric_limits::max() / (1000 * 1000 * 1000); - duration.set_seconds(kMaxInt64Nanoseconds + 1); - EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); + // Once the runtime feature "envoy.reloadable_features.strict_duration_validation" + // is deprecated, this test should only validate the "true" case. + for (const std::string strict_duration : {"true", "false"}) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.strict_duration_validation", strict_duration}}); + { + ProtobufWkt::Duration duration; + duration.set_seconds(-1); + EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); + } + { + ProtobufWkt::Duration duration; + duration.set_nanos(-1); + EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); + } + // Invalid number of nanoseconds. + { + ProtobufWkt::Duration duration; + duration.set_nanos(1000000000); + EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); + } + { + ProtobufWkt::Duration duration; + duration.set_seconds(Protobuf::util::TimeUtil::kDurationMaxSeconds + 1); + EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); + } + // Invalid number of seconds. + { + ProtobufWkt::Duration duration; + constexpr int64_t kMaxInt64Nanoseconds = + (std::numeric_limits::max() - 999999999) / (1000 * 1000 * 1000); + duration.set_seconds(kMaxInt64Nanoseconds + 1); + // Once the runtime feature "envoy.reloadable_features.strict_duration_validation" + // is deprecated, this test should only validate EXPECT_THROW. + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_duration_validation")) { + EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); + } else { + EXPECT_NO_THROW(DurationUtil::durationToMilliseconds(duration)); + } + } + // Max valid seconds and nanoseconds. + { + ProtobufWkt::Duration duration; + constexpr int64_t kMaxInt64Nanoseconds = + (std::numeric_limits::max() - 999999999) / (1000 * 1000 * 1000); + duration.set_seconds(kMaxInt64Nanoseconds); + duration.set_nanos(999999999); + EXPECT_NO_THROW(DurationUtil::durationToMilliseconds(duration)); + } + // Invalid combined seconds and nanoseconds. + { + // Once the runtime feature "envoy.reloadable_features.strict_duration_validation" + // is deprecated, this test should be executed unconditionally. The test is only + // with the flag because without the flag set to false it will trigger a + // runtime error (crash) with the current ASAN test suite. + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_duration_validation")) { + ProtobufWkt::Duration duration; + constexpr int64_t kMaxInt64Nanoseconds = + std::numeric_limits::max() / (1000 * 1000 * 1000); + duration.set_seconds(kMaxInt64Nanoseconds); + duration.set_nanos(999999999); + EXPECT_THROW(DurationUtil::durationToMilliseconds(duration), EnvoyException); + } + } } } TEST(DurationUtilTest, NoThrow) { - { - // In range test - ProtobufWkt::Duration duration; - duration.set_seconds(5); - duration.set_nanos(10000000); - const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); - EXPECT_TRUE(result.ok()); - EXPECT_TRUE(result.value() == 5010); + // Once the runtime feature "envoy.reloadable_features.strict_duration_validation" + // is deprecated, this test should only validate the "true" case. + for (const std::string strict_duration : {"true", "false"}) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues( + {{"envoy.reloadable_features.strict_duration_validation", strict_duration}}); + { + // In range test + ProtobufWkt::Duration duration; + duration.set_seconds(5); + duration.set_nanos(10000000); + const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); + EXPECT_TRUE(result.ok()); + EXPECT_TRUE(result.value() == 5010); + } + // Below are out-of-range tests + { + ProtobufWkt::Duration duration; + duration.set_seconds(-1); + const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); + EXPECT_FALSE(result.ok()); + } + { + ProtobufWkt::Duration duration; + duration.set_nanos(-1); + const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); + EXPECT_FALSE(result.ok()); + } + // Invalid number of nanoseconds. + { + ProtobufWkt::Duration duration; + duration.set_nanos(1000000000); + const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); + EXPECT_FALSE(result.ok()); + } + { + ProtobufWkt::Duration duration; + duration.set_seconds(Protobuf::util::TimeUtil::kDurationMaxSeconds + 1); + const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); + EXPECT_FALSE(result.ok()); + } + // Invalid number of seconds. + { + ProtobufWkt::Duration duration; + constexpr int64_t kMaxInt64Nanoseconds = + (std::numeric_limits::max() - 999999999) / (1000 * 1000 * 1000); + duration.set_seconds(kMaxInt64Nanoseconds + 1); + const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); + // Once the runtime feature "envoy.reloadable_features.strict_duration_validation" + // is deprecated, this test should only validate EXPECT_FALSE. + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_duration_validation")) { + EXPECT_FALSE(result.ok()); + } else { + EXPECT_TRUE(result.ok()); + } + } + // Max valid seconds and nanoseconds. + { + ProtobufWkt::Duration duration; + constexpr int64_t kMaxInt64Nanoseconds = + (std::numeric_limits::max() - 999999999) / (1000 * 1000 * 1000); + duration.set_seconds(kMaxInt64Nanoseconds); + duration.set_nanos(999999999); + const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); + EXPECT_TRUE(result.ok()); + } + // Invalid combined seconds and nanoseconds. + { + // Once the runtime feature "envoy.reloadable_features.strict_duration_validation" + // is deprecated, this test should be executed unconditionally. The test is only + // with the flag because without the flag set to false it will trigger a + // runtime error (crash) with the current ASAN test suite. + if (Runtime::runtimeFeatureEnabled("envoy.reloadable_features.strict_duration_validation")) { + ProtobufWkt::Duration duration; + constexpr int64_t kMaxInt64Nanoseconds = + std::numeric_limits::max() / (1000 * 1000 * 1000); + duration.set_seconds(kMaxInt64Nanoseconds); + duration.set_nanos(999999999); + const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); + EXPECT_FALSE(result.ok()); + } + } } +} + +// Validate that the duration in a message is validated correctly. +TEST_F(ProtobufUtilityTest, MessageDurationValidation) { + // Once the runtime key is deprecated, the scoped_runtime should be removed. + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.validate_duration_in_configs", "true"}}); - // Below are out-of-range tests - { - ProtobufWkt::Duration duration; - duration.set_seconds(-1); - const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); - EXPECT_FALSE(result.ok()); - } - { - ProtobufWkt::Duration duration; - duration.set_nanos(-1); - const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); - EXPECT_FALSE(result.ok()); - } { - ProtobufWkt::Duration duration; - duration.set_nanos(1000000000); - const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); - EXPECT_FALSE(result.ok()); + envoy::config::bootstrap::v3::Bootstrap bootstrap; + bootstrap.mutable_stats_flush_interval()->set_seconds(1); + EXPECT_NO_THROW(MessageUtil::validateDurationFields(bootstrap)); } + // Invalid durations. { - ProtobufWkt::Duration duration; - duration.set_seconds(Protobuf::util::TimeUtil::kDurationMaxSeconds + 1); - const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); - EXPECT_FALSE(result.ok()); + envoy::config::bootstrap::v3::Bootstrap bootstrap; + bootstrap.mutable_stats_flush_interval()->set_seconds(-1); + EXPECT_THROW_WITH_REGEX(MessageUtil::validateDurationFields(bootstrap), EnvoyException, + "Invalid duration: Expected positive duration"); } { - ProtobufWkt::Duration duration; - constexpr int64_t kMaxInt64Nanoseconds = - std::numeric_limits::max() / (1000 * 1000 * 1000); - duration.set_seconds(kMaxInt64Nanoseconds + 1); - const auto result = DurationUtil::durationToMillisecondsNoThrow(duration); - EXPECT_FALSE(result.ok()); + envoy::config::bootstrap::v3::Bootstrap bootstrap; + bootstrap.mutable_stats_flush_interval()->set_seconds(1); + bootstrap.mutable_stats_flush_interval()->set_nanos(-100); + EXPECT_THROW_WITH_REGEX(MessageUtil::validateDurationFields(bootstrap), EnvoyException, + "Invalid duration: Expected positive duration"); } } diff --git a/test/common/quic/envoy_quic_proof_source_test.cc b/test/common/quic/envoy_quic_proof_source_test.cc index a6c6bcb2d4f3..ae0f7295162e 100644 --- a/test/common/quic/envoy_quic_proof_source_test.cc +++ b/test/common/quic/envoy_quic_proof_source_test.cc @@ -5,6 +5,7 @@ #include "source/common/quic/envoy_quic_proof_source.h" #include "source/common/quic/envoy_quic_proof_verifier.h" #include "source/common/quic/envoy_quic_utils.h" +#include "source/common/tls/client_context_impl.h" #include "source/common/tls/context_config_impl.h" #include "test/common/quic/test_utils.h" diff --git a/test/common/quic/envoy_quic_proof_verifier_test.cc b/test/common/quic/envoy_quic_proof_verifier_test.cc index 97b5bd52724a..d7c466438edb 100644 --- a/test/common/quic/envoy_quic_proof_verifier_test.cc +++ b/test/common/quic/envoy_quic_proof_verifier_test.cc @@ -3,6 +3,7 @@ #include "source/common/network/transport_socket_options_impl.h" #include "source/common/quic/envoy_quic_proof_verifier.h" +#include "source/common/tls/client_context_impl.h" #include "source/common/tls/context_config_impl.h" #include "test/common/config/dummy_config.pb.h" diff --git a/test/common/tls/handshaker_factory_test.cc b/test/common/tls/handshaker_factory_test.cc index fbf1fbfa2efe..d34d4fc159b4 100644 --- a/test/common/tls/handshaker_factory_test.cc +++ b/test/common/tls/handshaker_factory_test.cc @@ -5,10 +5,10 @@ #include "envoy/ssl/handshaker.h" #include "source/common/stream_info/stream_info_impl.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_config_impl.h" #include "source/common/tls/context_manager_impl.h" #include "source/common/tls/ssl_handshaker.h" -#include "source/common/tls/ssl_socket.h" #include "source/server/process_context_impl.h" #include "test/mocks/network/connection.h" diff --git a/test/common/tls/integration/ssl_integration_test.cc b/test/common/tls/integration/ssl_integration_test.cc index 370a8b85447c..17086b049b3c 100644 --- a/test/common/tls/integration/ssl_integration_test.cc +++ b/test/common/tls/integration/ssl_integration_test.cc @@ -9,11 +9,12 @@ #include "source/common/event/dispatcher_impl.h" #include "source/common/network/connection_impl.h" #include "source/common/network/utility.h" +#include "source/common/tls/client_context_impl.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_config_impl.h" #include "source/common/tls/context_impl.h" #include "source/common/tls/context_manager_impl.h" #include "source/common/tls/ssl_handshaker.h" -#include "source/common/tls/ssl_socket.h" #include "test/common/config/dummy_config.pb.h" #include "test/common/tls/cert_validator/timed_cert_validator.h" diff --git a/test/common/tls/ocsp/asn1_utility_test.cc b/test/common/tls/ocsp/asn1_utility_test.cc index 3d3cbcaed6a7..022818b5ff1f 100644 --- a/test/common/tls/ocsp/asn1_utility_test.cc +++ b/test/common/tls/ocsp/asn1_utility_test.cc @@ -38,10 +38,10 @@ class Asn1UtilityTest : public testing::Test { } template - void expectParseResultErrorOnWrongTag(std::function(CBS&)> parse) { + void expectParseResultErrorOnWrongTag(std::function(CBS&)> parse) { CBS cbs; CBS_init(&cbs, asn1_true.data(), asn1_true.size()); - EXPECT_NO_THROW(absl::get<1>(parse(cbs))); + EXPECT_FALSE(parse(cbs).status().ok()); } const std::vector asn1_true = {0x1u, 1, 0xff}; @@ -70,8 +70,9 @@ TEST_F(Asn1UtilityTest, ParseSequenceOfEmptySequenceTest) { CBS_init(&cbs, asn1_empty_seq.data(), asn1_empty_seq.size()); std::vector> vec; - auto actual = absl::get<0>( - Asn1Utility::parseSequenceOf>(cbs, Asn1Utility::parseOctetString)); + auto actual = + Asn1Utility::parseSequenceOf>(cbs, Asn1Utility::parseOctetString) + .value(); EXPECT_EQ(vec, actual); } @@ -100,8 +101,9 @@ TEST_F(Asn1UtilityTest, ParseSequenceOfMultipleElementSequenceTest) { CBS_init(&cbs, octet_seq.data(), octet_seq.size()); std::vector> vec = {{0x1, 0x2}, {0x3, 0x4}, {0x5, 0x6}}; - auto actual = absl::get<0>( - Asn1Utility::parseSequenceOf>(cbs, Asn1Utility::parseOctetString)); + auto actual = + Asn1Utility::parseSequenceOf>(cbs, Asn1Utility::parseOctetString) + .value(); EXPECT_EQ(vec, actual); } @@ -120,8 +122,9 @@ TEST_F(Asn1UtilityTest, SequenceOfLengthMismatchErrorTest) { CBS_init(&cbs, malformed.data(), malformed.size()); EXPECT_EQ("Input is not a well-formed ASN.1 OCTETSTRING", - absl::get<1>(Asn1Utility::parseSequenceOf>( - cbs, Asn1Utility::parseOctetString))); + Asn1Utility::parseSequenceOf>(cbs, Asn1Utility::parseOctetString) + .status() + .message()); } TEST_F(Asn1UtilityTest, SequenceOfMixedTypeErrorTest) { @@ -143,8 +146,9 @@ TEST_F(Asn1UtilityTest, SequenceOfMixedTypeErrorTest) { CBS_init(&cbs, mixed_type.data(), mixed_type.size()); EXPECT_EQ("Input is not a well-formed ASN.1 OCTETSTRING", - absl::get<1>(Asn1Utility::parseSequenceOf>( - cbs, Asn1Utility::parseOctetString))); + Asn1Utility::parseSequenceOf>(cbs, Asn1Utility::parseOctetString) + .status() + .message()); } TEST_F(Asn1UtilityTest, GetOptionalTest) { @@ -152,10 +156,10 @@ TEST_F(Asn1UtilityTest, GetOptionalTest) { CBS_init(&cbs, asn1_true.data(), asn1_true.size()); const uint8_t* start = CBS_data(&cbs); - EXPECT_EQ(absl::nullopt, absl::get<0>(Asn1Utility::getOptional(cbs, CBS_ASN1_INTEGER))); + EXPECT_EQ(absl::nullopt, Asn1Utility::getOptional(cbs, CBS_ASN1_INTEGER).value()); EXPECT_EQ(start, CBS_data(&cbs)); - CBS value = absl::get<0>(Asn1Utility::getOptional(cbs, CBS_ASN1_BOOLEAN)).value(); + CBS value = Asn1Utility::getOptional(cbs, CBS_ASN1_BOOLEAN).value().value(); EXPECT_EQ(0xff, *CBS_data(&value)); } @@ -165,8 +169,7 @@ TEST_F(Asn1UtilityTest, GetOptionalMissingValueTest) { CBS_init(&cbs, missing_val_bool.data(), missing_val_bool.size()); auto res = Asn1Utility::getOptional(cbs, CBS_ASN1_BOOLEAN); - EXPECT_TRUE(absl::holds_alternative(res)); - EXPECT_EQ("Failed to parse ASN.1 element tag", absl::get<1>(res)); + EXPECT_EQ("Failed to parse ASN.1 element tag", res.status().message()); } TEST_F(Asn1UtilityTest, ParseOptionalTest) { @@ -180,9 +183,9 @@ TEST_F(Asn1UtilityTest, ParseOptionalTest) { return res; }; - auto parse_bool_fail = [](CBS&) -> ParsingResult { + auto parse_bool_fail = [](CBS&) -> absl::StatusOr { std::cout << "failing" << std::endl; - return absl::string_view{"failed"}; + return absl::InvalidArgumentError("failed"); }; { @@ -191,24 +194,26 @@ TEST_F(Asn1UtilityTest, ParseOptionalTest) { explicit_optional_true.size()); absl::optional expected(true); - EXPECT_EQ(expected, absl::get<0>(Asn1Utility::parseOptional( - cbs_explicit_optional_true, parse_bool, - CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0))); + EXPECT_EQ(expected, + Asn1Utility::parseOptional(cbs_explicit_optional_true, parse_bool, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) + .value()); } { CBS cbs_empty_seq; CBS_init(&cbs_empty_seq, asn1_empty_seq.data(), asn1_empty_seq.size()); - EXPECT_EQ(absl::nullopt, absl::get<0>(Asn1Utility::parseOptional( - cbs_empty_seq, parse_bool, CBS_ASN1_BOOLEAN))); + EXPECT_EQ( + absl::nullopt, + Asn1Utility::parseOptional(cbs_empty_seq, parse_bool, CBS_ASN1_BOOLEAN).value()); } { CBS cbs_nothing; CBS_init(&cbs_nothing, nothing.data(), nothing.size()); - EXPECT_EQ(absl::nullopt, absl::get<0>(Asn1Utility::parseOptional(cbs_nothing, parse_bool, - CBS_ASN1_BOOLEAN))); + EXPECT_EQ(absl::nullopt, + Asn1Utility::parseOptional(cbs_nothing, parse_bool, CBS_ASN1_BOOLEAN).value()); } { @@ -216,8 +221,9 @@ TEST_F(Asn1UtilityTest, ParseOptionalTest) { CBS_init(&cbs_missing_val, missing_val_bool.data(), missing_val_bool.size()); EXPECT_EQ("Failed to parse ASN.1 element tag", - absl::get<1>( - Asn1Utility::parseOptional(cbs_missing_val, parse_bool, CBS_ASN1_BOOLEAN))); + Asn1Utility::parseOptional(cbs_missing_val, parse_bool, CBS_ASN1_BOOLEAN) + .status() + .message()); } { @@ -225,9 +231,11 @@ TEST_F(Asn1UtilityTest, ParseOptionalTest) { CBS_init(&cbs_explicit_optional_true, explicit_optional_true.data(), explicit_optional_true.size()); - EXPECT_EQ("failed", absl::get<1>(Asn1Utility::parseOptional( - cbs_explicit_optional_true, parse_bool_fail, - CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0))); + EXPECT_EQ("failed", + Asn1Utility::parseOptional(cbs_explicit_optional_true, parse_bool_fail, + CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0) + .status() + .message()); } } @@ -247,7 +255,7 @@ TEST_F(Asn1UtilityTest, ParseOidTest) { CBS_init(&cbs, buf, buf_len); bssl::UniquePtr scoped(buf); - EXPECT_EQ(oid, absl::get<0>(Asn1Utility::parseOid(cbs))); + EXPECT_EQ(oid, Asn1Utility::parseOid(cbs).value()); } TEST_F(Asn1UtilityTest, ParseOidInvalidValueTest) { @@ -257,16 +265,16 @@ TEST_F(Asn1UtilityTest, ParseOidInvalidValueTest) { CBS cbs; CBS_init(&cbs, invalid_oid.data(), invalid_oid.size()); - EXPECT_EQ("Failed to parse oid", absl::get<1>(Asn1Utility::parseOid(cbs))); + EXPECT_EQ("Failed to parse oid", Asn1Utility::parseOid(cbs).status().message()); } TEST_F(Asn1UtilityTest, ParseGeneralizedTimeWrongFormatErrorTest) { std::string invalid_time = ""; CBS cbs; bssl::UniquePtr scoped(asn1Encode(cbs, invalid_time, CBS_ASN1_GENERALIZEDTIME)); - Asn1Utility::parseGeneralizedTime(cbs); + EXPECT_FALSE(Asn1Utility::parseGeneralizedTime(cbs).status().ok()); EXPECT_EQ("Input is not a well-formed ASN.1 GENERALIZEDTIME", - absl::get(Asn1Utility::parseGeneralizedTime(cbs))); + Asn1Utility::parseGeneralizedTime(cbs).status().message()); } TEST_F(Asn1UtilityTest, ParseGeneralizedTimeTest) { @@ -276,7 +284,7 @@ TEST_F(Asn1UtilityTest, ParseGeneralizedTimeTest) { CBS cbs; bssl::UniquePtr scoped(asn1Encode(cbs, time, CBS_ASN1_GENERALIZEDTIME)); absl::Time expected = TestUtility::parseTime(expected_time, "%E4Y%m%d%H%M%S"); - auto actual = absl::get(Asn1Utility::parseGeneralizedTime(cbs)); + auto actual = Asn1Utility::parseGeneralizedTime(cbs).value(); EXPECT_EQ(absl::ToChronoTime(expected), actual); } @@ -287,7 +295,7 @@ TEST_F(Asn1UtilityTest, TestParseGeneralizedTimeRejectsNonUTCTime) { bssl::UniquePtr scoped(asn1Encode(cbs, local_time, CBS_ASN1_GENERALIZEDTIME)); EXPECT_EQ("GENERALIZEDTIME must be in UTC", - absl::get(Asn1Utility::parseGeneralizedTime(cbs))); + Asn1Utility::parseGeneralizedTime(cbs).status().message()); } TEST_F(Asn1UtilityTest, TestParseGeneralizedTimeInvalidTime) { @@ -296,7 +304,7 @@ TEST_F(Asn1UtilityTest, TestParseGeneralizedTimeInvalidTime) { bssl::UniquePtr scoped(asn1Encode(cbs, ymd, CBS_ASN1_GENERALIZEDTIME)); EXPECT_EQ("Error parsing string of GENERALIZEDTIME format", - absl::get<1>(Asn1Utility::parseGeneralizedTime(cbs))); + Asn1Utility::parseGeneralizedTime(cbs).status().message()); } // Taken from @@ -346,7 +354,7 @@ TEST_F(Asn1UtilityTest, ParseIntegerTest) { CBS_init(&cbs, buf, buf_len); bssl::UniquePtr scoped_buf(buf); - EXPECT_EQ(int_and_hex.second, absl::get<0>(Asn1Utility::parseInteger(cbs))); + EXPECT_EQ(int_and_hex.second, Asn1Utility::parseInteger(cbs).value()); cbb.Reset(); } } @@ -357,7 +365,7 @@ TEST_F(Asn1UtilityTest, ParseOctetStringTest) { CBS cbs; bssl::UniquePtr scoped(asn1Encode(cbs, data_str, CBS_ASN1_OCTETSTRING)); - EXPECT_EQ(data, absl::get<0>(Asn1Utility::parseOctetString(cbs))); + EXPECT_EQ(data, Asn1Utility::parseOctetString(cbs).value()); } TEST_F(Asn1UtilityTest, SkipOptionalPresentAdvancesTest) { @@ -365,7 +373,7 @@ TEST_F(Asn1UtilityTest, SkipOptionalPresentAdvancesTest) { CBS_init(&cbs, asn1_empty_seq.data(), asn1_empty_seq.size()); const uint8_t* start = CBS_data(&cbs); - EXPECT_NO_THROW(absl::get<0>(Asn1Utility::skipOptional(cbs, CBS_ASN1_SEQUENCE))); + EXPECT_NO_THROW(Asn1Utility::skipOptional(cbs, CBS_ASN1_SEQUENCE).value()); EXPECT_EQ(start + 2, CBS_data(&cbs)); } @@ -374,7 +382,7 @@ TEST_F(Asn1UtilityTest, SkipOptionalNotPresentDoesNotAdvanceTest) { CBS_init(&cbs, asn1_empty_seq.data(), asn1_empty_seq.size()); const uint8_t* start = CBS_data(&cbs); - EXPECT_NO_THROW(absl::get<0>(Asn1Utility::skipOptional(cbs, CBS_ASN1_BOOLEAN))); + EXPECT_NO_THROW(Asn1Utility::skipOptional(cbs, CBS_ASN1_BOOLEAN).value()); EXPECT_EQ(start, CBS_data(&cbs)); } @@ -384,7 +392,7 @@ TEST_F(Asn1UtilityTest, SkipOptionalMalformedTagTest) { CBS_init(&cbs, malformed_seq.data(), malformed_seq.size()); EXPECT_EQ("Failed to parse ASN.1 element tag", - absl::get<1>(Asn1Utility::skipOptional(cbs, CBS_ASN1_SEQUENCE))); + Asn1Utility::skipOptional(cbs, CBS_ASN1_SEQUENCE).status().message()); } TEST_F(Asn1UtilityTest, SkipMalformedTagTest) { @@ -393,7 +401,7 @@ TEST_F(Asn1UtilityTest, SkipMalformedTagTest) { CBS_init(&cbs, malformed_seq.data(), malformed_seq.size()); EXPECT_EQ("Failed to parse ASN.1 element", - absl::get<1>(Asn1Utility::skip(cbs, CBS_ASN1_SEQUENCE))); + Asn1Utility::skip(cbs, CBS_ASN1_SEQUENCE).status().message()); } } // namespace diff --git a/test/common/tls/ssl_socket_test.cc b/test/common/tls/ssl_socket_test.cc index 9695995702ed..051597642337 100644 --- a/test/common/tls/ssl_socket_test.cc +++ b/test/common/tls/ssl_socket_test.cc @@ -17,10 +17,11 @@ #include "source/common/network/transport_socket_options_impl.h" #include "source/common/network/utility.h" #include "source/common/stream_info/stream_info_impl.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_config_impl.h" #include "source/common/tls/context_impl.h" #include "source/common/tls/private_key/private_key_manager_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "test/common/tls/cert_validator/timed_cert_validator.h" #include "test/common/tls/ssl_certs_test.h" diff --git a/test/common/upstream/BUILD b/test/common/upstream/BUILD index 3b333be88502..f0b9c54af097 100644 --- a/test/common/upstream/BUILD +++ b/test/common/upstream/BUILD @@ -1,6 +1,5 @@ load( "//bazel:envoy_build_system.bzl", - "envoy_benchmark_test", "envoy_cc_benchmark_binary", "envoy_cc_fuzz_test", "envoy_cc_test", @@ -266,80 +265,12 @@ envoy_cc_test( ) envoy_cc_test( - name = "load_balancer_impl_test", - srcs = ["load_balancer_impl_test.cc"], + name = "load_balancer_context_base_test", + srcs = ["load_balancer_context_base_test.cc"], deps = [ - ":utility_lib", - "//source/common/common:random_generator_lib", - "//source/common/network:utility_lib", "//source/common/upstream:load_balancer_context_base_lib", - "//source/common/upstream:upstream_includes", - "//source/common/upstream:upstream_lib", - "//source/extensions/load_balancing_policies/least_request:config", - "//source/extensions/load_balancing_policies/random:config", - "//source/extensions/load_balancing_policies/round_robin:config", - "//test/mocks:common_lib", - "//test/mocks/runtime:runtime_mocks", - "//test/mocks/upstream:cluster_info_mocks", "//test/mocks/upstream:host_mocks", - "//test/mocks/upstream:host_set_mocks", - "//test/mocks/upstream:load_balancer_context_mock", "//test/mocks/upstream:priority_set_mocks", - "//test/test_common:logging_lib", - "//test/test_common:simulated_time_system_lib", - "//test/test_common:test_runtime_lib", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - ], -) - -envoy_cc_test_library( - name = "load_balancer_fuzz_lib", - srcs = ["load_balancer_fuzz_base.cc"], - hdrs = ["load_balancer_fuzz_base.h"], - deps = [ - ":load_balancer_fuzz_proto_cc_proto", - ":utility_lib", - "//source/common/upstream:load_balancer_context_base_lib", - "//test/fuzz:random_lib", - "//test/mocks:common_lib", - "//test/mocks/runtime:runtime_mocks", - "//test/mocks/upstream:cluster_info_mocks", - "//test/mocks/upstream:host_set_mocks", - "//test/mocks/upstream:load_balancer_context_mock", - "//test/mocks/upstream:priority_set_mocks", - "//test/test_common:simulated_time_system_lib", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - ], -) - -envoy_proto_library( - name = "load_balancer_fuzz_proto", - srcs = ["load_balancer_fuzz.proto"], - deps = [ - "//test/fuzz:common_proto", - "@envoy_api//envoy/config/cluster/v3:pkg", - ], -) - -envoy_proto_library( - name = "random_load_balancer_fuzz_proto", - srcs = ["random_load_balancer_fuzz.proto"], - deps = [ - "//test/common/upstream:load_balancer_fuzz_proto", - ], -) - -envoy_cc_fuzz_test( - name = "random_load_balancer_fuzz_test", - srcs = ["random_load_balancer_fuzz_test.cc"], - corpus = "random_load_balancer_corpus", - deps = [ - ":load_balancer_fuzz_lib", - ":load_balancer_fuzz_proto_cc_proto", - ":random_load_balancer_fuzz_proto_cc_proto", - ":utility_lib", - "//source/extensions/load_balancing_policies/random:config", ], ) @@ -470,35 +401,6 @@ envoy_cc_test( ], ) -envoy_cc_benchmark_binary( - name = "load_balancer_benchmark", - srcs = ["load_balancer_benchmark.cc"], - external_deps = [ - "benchmark", - ], - deps = [ - "//source/common/common:random_generator_lib", - "//source/common/memory:stats_lib", - "//source/common/upstream:upstream_lib", - "//source/extensions/load_balancing_policies/least_request:config", - "//source/extensions/load_balancing_policies/maglev:config", - "//source/extensions/load_balancing_policies/random:config", - "//source/extensions/load_balancing_policies/ring_hash:config", - "//source/extensions/load_balancing_policies/round_robin:config", - "//test/common/upstream:utility_lib", - "//test/mocks/upstream:cluster_info_mocks", - "//test/test_common:printers_lib", - "//test/test_common:simulated_time_system_lib", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - ], -) - -envoy_benchmark_test( - name = "load_balancer_benchmark_test", - timeout = "long", - benchmark_binary = "load_balancer_benchmark", -) - envoy_cc_test( name = "transport_socket_matcher_test", srcs = ["transport_socket_matcher_test.cc"], @@ -702,71 +604,6 @@ envoy_cc_fuzz_test( ], ) -envoy_proto_library( - name = "zone_aware_load_balancer_fuzz_proto", - srcs = ["zone_aware_load_balancer_fuzz.proto"], - deps = [ - "//test/common/upstream:load_balancer_fuzz_proto", - ], -) - -envoy_cc_test_library( - name = "zone_aware_load_balancer_fuzz_lib", - srcs = ["zone_aware_load_balancer_fuzz_base.cc"], - hdrs = ["zone_aware_load_balancer_fuzz_base.h"], - deps = [ - ":load_balancer_fuzz_lib", - ":zone_aware_load_balancer_fuzz_proto_cc_proto", - "//source/extensions/load_balancing_policies/common:load_balancer_lib", - "//test/mocks/upstream:host_set_mocks", - "//test/mocks/upstream:priority_set_mocks", - "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", - ], -) - -envoy_proto_library( - name = "round_robin_load_balancer_fuzz_proto", - srcs = ["round_robin_load_balancer_fuzz.proto"], - deps = [ - "//test/common/upstream:zone_aware_load_balancer_fuzz_proto", - "@envoy_api//envoy/config/cluster/v3:pkg", - ], -) - -envoy_cc_fuzz_test( - name = "round_robin_load_balancer_fuzz_test", - srcs = ["round_robin_load_balancer_fuzz_test.cc"], - corpus = "round_robin_load_balancer_corpus", - deps = [ - ":round_robin_load_balancer_fuzz_proto_cc_proto", - ":utility_lib", - ":zone_aware_load_balancer_fuzz_lib", - "//source/extensions/load_balancing_policies/round_robin:config", - "//test/fuzz:utility_lib", - ], -) - -envoy_proto_library( - name = "least_request_load_balancer_fuzz_proto", - srcs = ["least_request_load_balancer_fuzz.proto"], - deps = [ - "//test/common/upstream:zone_aware_load_balancer_fuzz_proto", - "@envoy_api//envoy/config/cluster/v3:pkg", - ], -) - -envoy_cc_fuzz_test( - name = "least_request_load_balancer_fuzz_test", - srcs = ["least_request_load_balancer_fuzz_test.cc"], - corpus = "least_request_load_balancer_corpus", - deps = [ - ":least_request_load_balancer_fuzz_proto_cc_proto", - ":utility_lib", - ":zone_aware_load_balancer_fuzz_lib", - "//source/extensions/load_balancing_policies/least_request:config", - ], -) - envoy_cc_benchmark_binary( name = "scheduler_benchmark", srcs = ["scheduler_benchmark.cc"], diff --git a/test/common/upstream/health_checker_impl_test.cc b/test/common/upstream/health_checker_impl_test.cc index 9ba4b7865202..dedf81e4335d 100644 --- a/test/common/upstream/health_checker_impl_test.cc +++ b/test/common/upstream/health_checker_impl_test.cc @@ -4277,6 +4277,25 @@ class TcpHealthCheckerImplTest : public testing::Test, allocHealthChecker(yaml); } + void setupDataProxyProtocol() { + std::string yaml = R"EOF( + timeout: 1s + interval: 1s + unhealthy_threshold: 2 + healthy_threshold: 2 + reuse_connection: false + tcp_health_check: + proxy_protocol_config: + version: v2 + send: + text: "01" + receive: + - text: "02" + )EOF"; + + allocHealthChecker(yaml); + } + void expectSessionCreate() { interval_timer_ = new Event::MockTimer(&dispatcher_); timeout_timer_ = new Event::MockTimer(&dispatcher_); @@ -4776,6 +4795,29 @@ TEST_F(TcpHealthCheckerImplTest, ConnectionLocalFailure) { EXPECT_EQ(0UL, cluster_->info_->stats_store_.counter("health_check.passive_failure").value()); } +TEST_F(TcpHealthCheckerImplTest, SuccessProxyProtocol) { + InSequence s; + + setupDataProxyProtocol(); + cluster_->prioritySet().getMockHostSet(0)->hosts_ = { + makeTestHost(cluster_->info_, "tcp://127.0.0.1:80", simTime())}; + expectSessionCreate(); + expectClientCreate(); + EXPECT_CALL(*connection_, write(_, _)); + EXPECT_CALL(*timeout_timer_, enableTimer(_, _)); + health_checker_->start(); + + connection_->runHighWatermarkCallbacks(); + connection_->runLowWatermarkCallbacks(); + connection_->raiseEvent(Network::ConnectionEvent::Connected); + + EXPECT_CALL(*timeout_timer_, disableTimer()); + EXPECT_CALL(*interval_timer_, enableTimer(_, _)); + Buffer::OwnedImpl response; + addUint8(response, 2); + read_filter_->onData(response, false); +} + class TestGrpcHealthCheckerImpl : public GrpcHealthCheckerImpl { public: using GrpcHealthCheckerImpl::GrpcHealthCheckerImpl; diff --git a/test/common/upstream/load_balancer_benchmark.cc b/test/common/upstream/load_balancer_benchmark.cc deleted file mode 100644 index fc71d28decf9..000000000000 --- a/test/common/upstream/load_balancer_benchmark.cc +++ /dev/null @@ -1,553 +0,0 @@ -// Usage: bazel run //test/common/upstream:load_balancer_benchmark - -#include - -#include "envoy/config/cluster/v3/cluster.pb.h" - -#include "source/common/common/random_generator.h" -#include "source/common/memory/stats.h" -#include "source/common/upstream/upstream_impl.h" -#include "source/extensions/load_balancing_policies/least_request/least_request_lb.h" -#include "source/extensions/load_balancing_policies/maglev/maglev_lb.h" -#include "source/extensions/load_balancing_policies/random/random_lb.h" -#include "source/extensions/load_balancing_policies/ring_hash/ring_hash_lb.h" -#include "source/extensions/load_balancing_policies/round_robin/round_robin_lb.h" - -#include "test/benchmark/main.h" -#include "test/common/upstream/utility.h" -#include "test/mocks/upstream/cluster_info.h" -#include "test/test_common/simulated_time_system.h" - -#include "absl/types/optional.h" -#include "benchmark/benchmark.h" - -namespace Envoy { -namespace Upstream { -namespace { - -class BaseTester : public Event::TestUsingSimulatedTime { -public: - static constexpr absl::string_view metadata_key = "key"; - // We weight the first weighted_subset_percent of hosts with weight. - BaseTester(uint64_t num_hosts, uint32_t weighted_subset_percent = 0, uint32_t weight = 0, - bool attach_metadata = false) { - HostVector hosts; - ASSERT(num_hosts < 65536); - for (uint64_t i = 0; i < num_hosts; i++) { - const bool should_weight = i < num_hosts * (weighted_subset_percent / 100.0); - const std::string url = fmt::format("tcp://10.0.{}.{}:6379", i / 256, i % 256); - const auto effective_weight = should_weight ? weight : 1; - if (attach_metadata) { - envoy::config::core::v3::Metadata metadata; - ProtobufWkt::Value value; - value.set_number_value(i); - ProtobufWkt::Struct& map = - (*metadata.mutable_filter_metadata())[Config::MetadataFilters::get().ENVOY_LB]; - (*map.mutable_fields())[std::string(metadata_key)] = value; - - hosts.push_back(makeTestHost(info_, url, metadata, simTime(), effective_weight)); - } else { - hosts.push_back(makeTestHost(info_, url, simTime(), effective_weight)); - } - } - - HostVectorConstSharedPtr updated_hosts = std::make_shared(hosts); - HostsPerLocalityConstSharedPtr hosts_per_locality = makeHostsPerLocality({hosts}); - priority_set_.updateHosts(0, HostSetImpl::partitionHosts(updated_hosts, hosts_per_locality), {}, - hosts, {}, random_.random(), absl::nullopt); - local_priority_set_.updateHosts(0, - HostSetImpl::partitionHosts(updated_hosts, hosts_per_locality), - {}, hosts, {}, random_.random(), absl::nullopt); - } - - Envoy::Thread::MutexBasicLockable lock_; - // Reduce default log level to warn while running this benchmark to avoid problems due to - // excessive debug logging in upstream_impl.cc - Envoy::Logger::Context logging_context_{spdlog::level::warn, - Envoy::Logger::Logger::DEFAULT_LOG_FORMAT, lock_, false}; - - PrioritySetImpl priority_set_; - PrioritySetImpl local_priority_set_; - - // The following are needed to create a load balancer by the load balancer factory. - LoadBalancerParams lb_params_{priority_set_, &local_priority_set_}; - - Stats::IsolatedStoreImpl stats_store_; - Stats::Scope& stats_scope_{*stats_store_.rootScope()}; - ClusterLbStatNames stat_names_{stats_store_.symbolTable()}; - ClusterLbStats stats_{stat_names_, stats_scope_}; - NiceMock runtime_; - Random::RandomGeneratorImpl random_; - envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; - envoy::config::cluster::v3::Cluster::RoundRobinLbConfig round_robin_lb_config_; - std::shared_ptr info_{new NiceMock()}; -}; - -class RoundRobinTester : public BaseTester { -public: - RoundRobinTester(uint64_t num_hosts, uint32_t weighted_subset_percent = 0, uint32_t weight = 0) - : BaseTester(num_hosts, weighted_subset_percent, weight) {} - - void initialize() { - lb_ = std::make_unique(priority_set_, &local_priority_set_, stats_, - runtime_, random_, common_config_, - round_robin_lb_config_, simTime()); - } - - std::unique_ptr lb_; -}; - -class LeastRequestTester : public BaseTester { -public: - LeastRequestTester(uint64_t num_hosts, uint32_t choice_count) : BaseTester(num_hosts) { - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; - lr_lb_config.mutable_choice_count()->set_value(choice_count); - lb_ = std::make_unique(priority_set_, &local_priority_set_, stats_, - runtime_, random_, common_config_, - lr_lb_config, simTime()); - } - - std::unique_ptr lb_; -}; - -void benchmarkRoundRobinLoadBalancerBuild(::benchmark::State& state) { - const uint64_t num_hosts = state.range(0); - const uint64_t weighted_subset_percent = state.range(1); - const uint64_t weight = state.range(2); - - if (benchmark::skipExpensiveBenchmarks() && num_hosts > 10000) { - state.SkipWithError("Skipping expensive benchmark"); - return; - } - - for (auto _ : state) { // NOLINT: Silences warning about dead store - state.PauseTiming(); - const size_t start_tester_mem = Memory::Stats::totalCurrentlyAllocated(); - RoundRobinTester tester(num_hosts, weighted_subset_percent, weight); - const size_t end_tester_mem = Memory::Stats::totalCurrentlyAllocated(); - const size_t start_mem = Memory::Stats::totalCurrentlyAllocated(); - - // We are only interested in timing the initial build. - state.ResumeTiming(); - tester.initialize(); - state.PauseTiming(); - const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); - state.counters["tester_memory"] = end_tester_mem - start_tester_mem; - state.counters["memory"] = end_mem - start_mem; - state.counters["memory_per_host"] = (end_mem - start_mem) / num_hosts; - state.ResumeTiming(); - } -} -BENCHMARK(benchmarkRoundRobinLoadBalancerBuild) - ->Args({1, 0, 1}) - ->Args({500, 0, 1}) - ->Args({500, 50, 50}) - ->Args({500, 100, 50}) - ->Args({2500, 0, 1}) - ->Args({2500, 50, 50}) - ->Args({2500, 100, 50}) - ->Args({10000, 0, 1}) - ->Args({10000, 50, 50}) - ->Args({10000, 100, 50}) - ->Args({25000, 0, 1}) - ->Args({25000, 50, 50}) - ->Args({25000, 100, 50}) - ->Args({50000, 0, 1}) - ->Args({50000, 50, 50}) - ->Args({50000, 100, 50}) - ->Unit(::benchmark::kMillisecond); - -class RingHashTester : public BaseTester { -public: - RingHashTester(uint64_t num_hosts, uint64_t min_ring_size) : BaseTester(num_hosts) { - config_ = envoy::config::cluster::v3::Cluster::RingHashLbConfig(); - config_.value().mutable_minimum_ring_size()->set_value(min_ring_size); - ring_hash_lb_ = std::make_unique( - priority_set_, stats_, stats_scope_, runtime_, random_, - config_.has_value() - ? makeOptRef( - config_.value()) - : absl::nullopt, - common_config_); - } - - absl::optional config_; - std::unique_ptr ring_hash_lb_; -}; - -class MaglevTester : public BaseTester { -public: - MaglevTester(uint64_t num_hosts, uint32_t weighted_subset_percent = 0, uint32_t weight = 0) - : BaseTester(num_hosts, weighted_subset_percent, weight) { - maglev_lb_ = std::make_unique( - priority_set_, stats_, stats_scope_, runtime_, random_, - config_.has_value() - ? makeOptRef(config_.value()) - : absl::nullopt, - common_config_); - } - - absl::optional config_; - std::unique_ptr maglev_lb_; -}; - -uint64_t hashInt(uint64_t i) { - // Hack to hash an integer. - return HashUtil::xxHash64(absl::string_view(reinterpret_cast(&i), sizeof(i))); -} - -void benchmarkRingHashLoadBalancerBuildRing(::benchmark::State& state) { - for (auto _ : state) { // NOLINT: Silences warning about dead store - state.PauseTiming(); - const uint64_t num_hosts = state.range(0); - const uint64_t min_ring_size = state.range(1); - RingHashTester tester(num_hosts, min_ring_size); - - const size_t start_mem = Memory::Stats::totalCurrentlyAllocated(); - - // We are only interested in timing the initial ring build. - state.ResumeTiming(); - ASSERT_TRUE(tester.ring_hash_lb_->initialize().ok()); - state.PauseTiming(); - const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); - state.counters["memory"] = end_mem - start_mem; - state.counters["memory_per_host"] = (end_mem - start_mem) / num_hosts; - state.ResumeTiming(); - } -} -BENCHMARK(benchmarkRingHashLoadBalancerBuildRing) - ->Args({100, 65536}) - ->Args({200, 65536}) - ->Args({500, 65536}) - ->Args({100, 256000}) - ->Args({200, 256000}) - ->Args({500, 256000}) - ->Unit(::benchmark::kMillisecond); - -void benchmarkMaglevLoadBalancerBuildTable(::benchmark::State& state) { - for (auto _ : state) { // NOLINT: Silences warning about dead store - state.PauseTiming(); - const uint64_t num_hosts = state.range(0); - MaglevTester tester(num_hosts); - - const size_t start_mem = Memory::Stats::totalCurrentlyAllocated(); - - // We are only interested in timing the initial table build. - state.ResumeTiming(); - ASSERT_TRUE(tester.maglev_lb_->initialize().ok()); - state.PauseTiming(); - const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); - state.counters["memory"] = end_mem - start_mem; - state.counters["memory_per_host"] = (end_mem - start_mem) / num_hosts; - state.ResumeTiming(); - } -} -BENCHMARK(benchmarkMaglevLoadBalancerBuildTable) - ->Arg(100) - ->Arg(200) - ->Arg(500) - ->Unit(::benchmark::kMillisecond); - -class TestLoadBalancerContext : public LoadBalancerContextBase { -public: - // Upstream::LoadBalancerContext - absl::optional computeHashKey() override { return hash_key_; } - - absl::optional hash_key_; -}; - -void computeHitStats(::benchmark::State& state, - const absl::node_hash_map& hit_counter) { - double mean = 0; - for (const auto& pair : hit_counter) { - mean += pair.second; - } - mean /= hit_counter.size(); - - double variance = 0; - for (const auto& pair : hit_counter) { - variance += std::pow(pair.second - mean, 2); - } - variance /= hit_counter.size(); - const double stddev = std::sqrt(variance); - - state.counters["mean_hits"] = mean; - state.counters["stddev_hits"] = stddev; - state.counters["relative_stddev_hits"] = (stddev / mean); -} - -void benchmarkLeastRequestLoadBalancerChooseHost(::benchmark::State& state) { - const uint64_t num_hosts = state.range(0); - const uint64_t choice_count = state.range(1); - const uint64_t keys_to_simulate = state.range(2); - - if (benchmark::skipExpensiveBenchmarks() && keys_to_simulate > 1000) { - state.SkipWithError("Skipping expensive benchmark"); - return; - } - - for (auto _ : state) { // NOLINT: Silences warning about dead store - state.PauseTiming(); - LeastRequestTester tester(num_hosts, choice_count); - absl::node_hash_map hit_counter; - TestLoadBalancerContext context; - state.ResumeTiming(); - - for (uint64_t i = 0; i < keys_to_simulate; ++i) { - hit_counter[tester.lb_->chooseHost(&context)->address()->asString()] += 1; - } - - // Do not time computation of mean, standard deviation, and relative standard deviation. - state.PauseTiming(); - computeHitStats(state, hit_counter); - state.ResumeTiming(); - } -} -BENCHMARK(benchmarkLeastRequestLoadBalancerChooseHost) - ->Args({100, 1, 1000}) - ->Args({100, 2, 1000}) - ->Args({100, 3, 1000}) - ->Args({100, 10, 1000}) - ->Args({100, 50, 1000}) - ->Args({100, 100, 1000}) - ->Args({100, 1, 1000000}) - ->Args({100, 2, 1000000}) - ->Args({100, 3, 1000000}) - ->Args({100, 10, 1000000}) - ->Args({100, 50, 1000000}) - ->Args({100, 100, 1000000}) - ->Unit(::benchmark::kMillisecond); - -void benchmarkRingHashLoadBalancerChooseHost(::benchmark::State& state) { - for (auto _ : state) { // NOLINT: Silences warning about dead store - // Do not time the creation of the ring. - state.PauseTiming(); - const uint64_t num_hosts = state.range(0); - const uint64_t min_ring_size = state.range(1); - const uint64_t keys_to_simulate = state.range(2); - RingHashTester tester(num_hosts, min_ring_size); - ASSERT_TRUE(tester.ring_hash_lb_->initialize().ok()); - LoadBalancerPtr lb = tester.ring_hash_lb_->factory()->create(tester.lb_params_); - absl::node_hash_map hit_counter; - TestLoadBalancerContext context; - state.ResumeTiming(); - - // Note: To a certain extent this is benchmarking the performance of xxhash as well as - // absl::node_hash_map. However, it should be roughly equivalent to the work done when - // comparing different hashing algorithms. - // TODO(mattklein123): When Maglev is a real load balancer, further share code with the - // other test. - for (uint64_t i = 0; i < keys_to_simulate; i++) { - context.hash_key_ = hashInt(i); - hit_counter[lb->chooseHost(&context)->address()->asString()] += 1; - } - - // Do not time computation of mean, standard deviation, and relative standard deviation. - state.PauseTiming(); - computeHitStats(state, hit_counter); - state.ResumeTiming(); - } -} -BENCHMARK(benchmarkRingHashLoadBalancerChooseHost) - ->Args({100, 65536, 100000}) - ->Args({200, 65536, 100000}) - ->Args({500, 65536, 100000}) - ->Args({100, 256000, 100000}) - ->Args({200, 256000, 100000}) - ->Args({500, 256000, 100000}) - ->Unit(::benchmark::kMillisecond); - -void benchmarkMaglevLoadBalancerChooseHost(::benchmark::State& state) { - for (auto _ : state) { // NOLINT: Silences warning about dead store - // Do not time the creation of the table. - state.PauseTiming(); - const uint64_t num_hosts = state.range(0); - const uint64_t keys_to_simulate = state.range(1); - MaglevTester tester(num_hosts); - ASSERT_TRUE(tester.maglev_lb_->initialize().ok()); - LoadBalancerPtr lb = tester.maglev_lb_->factory()->create(tester.lb_params_); - absl::node_hash_map hit_counter; - TestLoadBalancerContext context; - state.ResumeTiming(); - - // Note: To a certain extent this is benchmarking the performance of xxhash as well as - // absl::node_hash_map. However, it should be roughly equivalent to the work done when - // comparing different hashing algorithms. - for (uint64_t i = 0; i < keys_to_simulate; i++) { - context.hash_key_ = hashInt(i); - hit_counter[lb->chooseHost(&context)->address()->asString()] += 1; - } - - // Do not time computation of mean, standard deviation, and relative standard deviation. - state.PauseTiming(); - computeHitStats(state, hit_counter); - state.ResumeTiming(); - } -} -BENCHMARK(benchmarkMaglevLoadBalancerChooseHost) - ->Args({100, 100000}) - ->Args({200, 100000}) - ->Args({500, 100000}) - ->Unit(::benchmark::kMillisecond); - -void benchmarkRingHashLoadBalancerHostLoss(::benchmark::State& state) { - const uint64_t num_hosts = state.range(0); - const uint64_t min_ring_size = state.range(1); - const uint64_t hosts_to_lose = state.range(2); - const uint64_t keys_to_simulate = state.range(3); - - if (benchmark::skipExpensiveBenchmarks() && min_ring_size > 65536) { - state.SkipWithError("Skipping expensive benchmark"); - return; - } - - for (auto _ : state) { // NOLINT: Silences warning about dead store - RingHashTester tester(num_hosts, min_ring_size); - ASSERT_TRUE(tester.ring_hash_lb_->initialize().ok()); - LoadBalancerPtr lb = tester.ring_hash_lb_->factory()->create(tester.lb_params_); - std::vector hosts; - TestLoadBalancerContext context; - for (uint64_t i = 0; i < keys_to_simulate; i++) { - context.hash_key_ = hashInt(i); - hosts.push_back(lb->chooseHost(&context)); - } - - RingHashTester tester2(num_hosts - hosts_to_lose, min_ring_size); - ASSERT_TRUE(tester2.ring_hash_lb_->initialize().ok()); - lb = tester2.ring_hash_lb_->factory()->create(tester2.lb_params_); - std::vector hosts2; - for (uint64_t i = 0; i < keys_to_simulate; i++) { - context.hash_key_ = hashInt(i); - hosts2.push_back(lb->chooseHost(&context)); - } - - ASSERT(hosts.size() == hosts2.size()); - uint64_t num_different_hosts = 0; - for (uint64_t i = 0; i < hosts.size(); i++) { - if (hosts[i]->address()->asString() != hosts2[i]->address()->asString()) { - num_different_hosts++; - } - } - - state.counters["percent_different"] = - (static_cast(num_different_hosts) / hosts.size()) * 100; - state.counters["host_loss_over_N_optimal"] = - (static_cast(hosts_to_lose) / num_hosts) * 100; - } -} -BENCHMARK(benchmarkRingHashLoadBalancerHostLoss) - ->Args({500, 65536, 1, 10000}) - ->Args({500, 65536, 2, 10000}) - ->Args({500, 65536, 3, 10000}) - ->Args({500, 256000, 1, 10000}) - ->Args({500, 256000, 2, 10000}) - ->Args({500, 256000, 3, 10000}) - ->Unit(::benchmark::kMillisecond); - -void benchmarkMaglevLoadBalancerHostLoss(::benchmark::State& state) { - for (auto _ : state) { // NOLINT: Silences warning about dead store - const uint64_t num_hosts = state.range(0); - const uint64_t hosts_to_lose = state.range(1); - const uint64_t keys_to_simulate = state.range(2); - - MaglevTester tester(num_hosts); - ASSERT_TRUE(tester.maglev_lb_->initialize().ok()); - LoadBalancerPtr lb = tester.maglev_lb_->factory()->create(tester.lb_params_); - std::vector hosts; - TestLoadBalancerContext context; - for (uint64_t i = 0; i < keys_to_simulate; i++) { - context.hash_key_ = hashInt(i); - hosts.push_back(lb->chooseHost(&context)); - } - - MaglevTester tester2(num_hosts - hosts_to_lose); - ASSERT_TRUE(tester2.maglev_lb_->initialize().ok()); - lb = tester2.maglev_lb_->factory()->create(tester2.lb_params_); - std::vector hosts2; - for (uint64_t i = 0; i < keys_to_simulate; i++) { - context.hash_key_ = hashInt(i); - hosts2.push_back(lb->chooseHost(&context)); - } - - ASSERT(hosts.size() == hosts2.size()); - uint64_t num_different_hosts = 0; - for (uint64_t i = 0; i < hosts.size(); i++) { - if (hosts[i]->address()->asString() != hosts2[i]->address()->asString()) { - num_different_hosts++; - } - } - - state.counters["percent_different"] = - (static_cast(num_different_hosts) / hosts.size()) * 100; - state.counters["host_loss_over_N_optimal"] = - (static_cast(hosts_to_lose) / num_hosts) * 100; - } -} -BENCHMARK(benchmarkMaglevLoadBalancerHostLoss) - ->Args({500, 1, 10000}) - ->Args({500, 2, 10000}) - ->Args({500, 3, 10000}) - ->Unit(::benchmark::kMillisecond); - -void benchmarkMaglevLoadBalancerWeighted(::benchmark::State& state) { - for (auto _ : state) { // NOLINT: Silences warning about dead store - const uint64_t num_hosts = state.range(0); - const uint64_t weighted_subset_percent = state.range(1); - const uint64_t before_weight = state.range(2); - const uint64_t after_weight = state.range(3); - const uint64_t keys_to_simulate = state.range(4); - - MaglevTester tester(num_hosts, weighted_subset_percent, before_weight); - ASSERT_TRUE(tester.maglev_lb_->initialize().ok()); - LoadBalancerPtr lb = tester.maglev_lb_->factory()->create(tester.lb_params_); - std::vector hosts; - TestLoadBalancerContext context; - for (uint64_t i = 0; i < keys_to_simulate; i++) { - context.hash_key_ = hashInt(i); - hosts.push_back(lb->chooseHost(&context)); - } - - MaglevTester tester2(num_hosts, weighted_subset_percent, after_weight); - ASSERT_TRUE(tester2.maglev_lb_->initialize().ok()); - lb = tester2.maglev_lb_->factory()->create(tester2.lb_params_); - std::vector hosts2; - for (uint64_t i = 0; i < keys_to_simulate; i++) { - context.hash_key_ = hashInt(i); - hosts2.push_back(lb->chooseHost(&context)); - } - - ASSERT(hosts.size() == hosts2.size()); - uint64_t num_different_hosts = 0; - for (uint64_t i = 0; i < hosts.size(); i++) { - if (hosts[i]->address()->asString() != hosts2[i]->address()->asString()) { - num_different_hosts++; - } - } - - state.counters["percent_different"] = - (static_cast(num_different_hosts) / hosts.size()) * 100; - const auto weighted_hosts_percent = [weighted_subset_percent](uint32_t weight) -> double { - const double weighted_hosts = weighted_subset_percent; - const double unweighted_hosts = 100.0 - weighted_hosts; - const double total_weight = weighted_hosts * weight + unweighted_hosts; - return 100.0 * (weighted_hosts * weight) / total_weight; - }; - state.counters["optimal_percent_different"] = - std::abs(weighted_hosts_percent(before_weight) - weighted_hosts_percent(after_weight)); - } -} -BENCHMARK(benchmarkMaglevLoadBalancerWeighted) - ->Args({500, 5, 1, 1, 10000}) - ->Args({500, 5, 1, 127, 1000}) - ->Args({500, 5, 127, 1, 10000}) - ->Args({500, 50, 1, 127, 1000}) - ->Args({500, 50, 127, 1, 10000}) - ->Args({500, 95, 1, 127, 1000}) - ->Args({500, 95, 127, 1, 10000}) - ->Args({500, 95, 25, 75, 1000}) - ->Args({500, 95, 75, 25, 10000}) - ->Unit(::benchmark::kMillisecond); - -} // namespace -} // namespace Upstream -} // namespace Envoy diff --git a/test/common/upstream/load_balancer_context_base_test.cc b/test/common/upstream/load_balancer_context_base_test.cc new file mode 100644 index 000000000000..4bef1a611a74 --- /dev/null +++ b/test/common/upstream/load_balancer_context_base_test.cc @@ -0,0 +1,39 @@ +#include "source/common/upstream/load_balancer_context_base.h" + +#include "test/mocks/upstream/host.h" +#include "test/mocks/upstream/priority_set.h" + +#include "gtest/gtest.h" + +namespace Envoy { +namespace Upstream { +namespace { + +TEST(LoadBalancerContextBaseTest, LoadBalancerContextBaseTest) { + { + LoadBalancerContextBase context; + MockPrioritySet mock_priority_set; + HealthyAndDegradedLoad priority_load{Upstream::HealthyLoad({100, 0, 0}), + Upstream::DegradedLoad({0, 0, 0})}; + RetryPriority::PriorityMappingFunc empty_func = + [](const Upstream::HostDescription&) -> absl::optional { return absl::nullopt; }; + MockHost mock_host; + + EXPECT_EQ(absl::nullopt, context.computeHashKey()); + EXPECT_EQ(nullptr, context.downstreamConnection()); + EXPECT_EQ(nullptr, context.metadataMatchCriteria()); + EXPECT_EQ(nullptr, context.downstreamHeaders()); + + EXPECT_EQ(&priority_load, + &(context.determinePriorityLoad(mock_priority_set, priority_load, empty_func))); + EXPECT_EQ(false, context.shouldSelectAnotherHost(mock_host)); + EXPECT_EQ(1, context.hostSelectionRetryCount()); + EXPECT_EQ(nullptr, context.upstreamSocketOptions()); + EXPECT_EQ(nullptr, context.upstreamTransportSocketOptions()); + EXPECT_EQ(absl::nullopt, context.overrideHostToSelect()); + } +} + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc index 1b7e15d0ffe0..ec20aa0abb8a 100644 --- a/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc +++ b/test/extensions/access_loggers/grpc/tcp_grpc_access_log_integration_test.cc @@ -11,8 +11,8 @@ #include "source/common/buffer/zero_copy_input_stream_impl.h" #include "source/common/grpc/codec.h" #include "source/common/grpc/common.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_manager_impl.h" -#include "source/common/tls/ssl_socket.h" #include "source/common/version/version.h" #include "source/extensions/filters/listener/tls_inspector/tls_inspector.h" diff --git a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc index 2e5b53010036..d20861e5856d 100644 --- a/test/extensions/filters/common/ext_authz/check_request_utils_test.cc +++ b/test/extensions/filters/common/ext_authz/check_request_utils_test.cc @@ -73,7 +73,7 @@ class CheckRequestUtilsTest : public testing::Test { &callbacks_, request_headers, std::move(context_extensions), std::move(metadata_context), envoy::config::core::v3::Metadata(), request, /*max_request_bytes=*/0, /*pack_as_bytes=*/false, /*encode_raw_headers=*/false, include_peer_certificate, - want_tls_session != nullptr, labels, nullptr); + want_tls_session != nullptr, labels, nullptr, nullptr); EXPECT_EQ("source", request.attributes().source().principal()); EXPECT_EQ("destination", request.attributes().destination().principal()); @@ -112,16 +112,26 @@ class CheckRequestUtilsTest : public testing::Test { return buffer; } - MatcherSharedPtr createRequestHeaderMatchers() { + MatcherSharedPtr createRequestHeaderAllowlist() { NiceMock factory_context; envoy::extensions::filters::http::ext_authz::v3::ExtAuthz ext_autz_proto_; - ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("foo"); - ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("hello"); - ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("duplicate"); + ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("allowed"); + ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("allowed-dupe"); + ext_autz_proto_.mutable_allowed_headers()->add_patterns()->set_exact("allowed-and-disallowed"); return CheckRequestUtils::toRequestMatchers(ext_autz_proto_.allowed_headers(), false, factory_context); } + MatcherSharedPtr createRequestHeaderDenylist() { + NiceMock factory_context; + envoy::extensions::filters::http::ext_authz::v3::ExtAuthz ext_autz_proto_; + ext_autz_proto_.mutable_disallowed_headers()->add_patterns()->set_exact("disallowed"); + ext_autz_proto_.mutable_disallowed_headers()->add_patterns()->set_exact( + "allowed-and-disallowed"); + return CheckRequestUtils::toRequestMatchers(ext_autz_proto_.disallowed_headers(), false, + factory_context); + } + static void expectHeadersInHeaderMap( const envoy::config::core::v3::HeaderMap& header_map, const std::vector>& expected_headers) { @@ -249,7 +259,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttp) { &callbacks_, request_headers, Protobuf::Map(), envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, /*pack_as_bytes=*/false, /*encode_raw_headers=*/false, /*include_peer_certificate=*/false, - /*include_tls_session=*/false, Protobuf::Map(), nullptr); + /*include_tls_session=*/false, Protobuf::Map(), nullptr, nullptr); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_FALSE(request_.attributes().request().http().has_header_map()); @@ -278,7 +288,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithDuplicateHeaders) { &callbacks_, request_headers, Protobuf::Map(), envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, /*pack_as_bytes=*/false, /*encode_raw_headers=*/false, /*include_peer_certificate=*/false, - /*include_tls_session=*/false, Protobuf::Map(), nullptr); + /*include_tls_session=*/false, Protobuf::Map(), nullptr, nullptr); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_FALSE(request_.attributes().request().http().has_header_map()); @@ -290,13 +300,15 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithDuplicateHeaders) { // Verify that check request only contains allowlisted headers, // and that duplicate headers are merged. -TEST_F(CheckRequestUtilsTest, BasicHttpWithRequestHeaderMatchers) { +TEST_F(CheckRequestUtilsTest, BasicHttpWithRequestHeaderAllowlist) { const uint64_t size = 0; envoy::service::auth::v3::CheckRequest request_; Http::TestRequestHeaderMapImpl request_headers{ - {"foo", "bar"}, {"hello", "there"}, {"duplicate", "one"}, - {"duplicate", "two"}, {"not-this-one", "sorry"}, + {"allowed", "allowed value"}, + {"allowed-dupe", "one"}, + {"allowed-dupe", "two"}, + {"not-allowed", "not explicitly allowed"}, }; EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); @@ -309,14 +321,99 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithRequestHeaderMatchers) { envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, /*pack_as_bytes=*/false, /*encode_raw_headers=*/false, /*include_peer_certificate=*/false, /*include_tls_session=*/false, Protobuf::Map(), - createRequestHeaderMatchers()); + createRequestHeaderAllowlist(), nullptr); + ASSERT_EQ(size, request_.attributes().request().http().body().size()); + EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); + EXPECT_FALSE(request_.attributes().request().http().has_header_map()); + + EXPECT_TRUE(request_.attributes().request().http().headers().contains("allowed")); + EXPECT_EQ("allowed value", request_.attributes().request().http().headers().at("allowed")); + + // No denylist was used. + EXPECT_TRUE(request_.attributes().request().http().headers().contains("allowed-dupe")); + EXPECT_EQ("one,two", request_.attributes().request().http().headers().at("allowed-dupe")); + + EXPECT_FALSE(request_.attributes().request().http().headers().contains("not-allowed")); +} + +// Verify that check request only contains allowlisted headers, +// and that duplicate headers are merged. +TEST_F(CheckRequestUtilsTest, BasicHttpWithRequestHeaderDenylist) { + const uint64_t size = 0; + envoy::service::auth::v3::CheckRequest request_; + + Http::TestRequestHeaderMapImpl request_headers{ + {"not-disallowed", "not disallowed value"}, + {"not-disallowed-dupe", "one"}, + {"not-disallowed-dupe", "two"}, + {"disallowed", "disallowed value"}, + }; + + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); + expectBasicHttp(); + + CheckRequestUtils::createHttpCheck( + &callbacks_, request_headers, Protobuf::Map(), + envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, + /*pack_as_bytes=*/false, /*encode_raw_headers=*/false, /*include_peer_certificate=*/false, + /*include_tls_session=*/false, Protobuf::Map(), nullptr, + createRequestHeaderDenylist()); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_FALSE(request_.attributes().request().http().has_header_map()); - EXPECT_EQ("one,two", request_.attributes().request().http().headers().at("duplicate")); - EXPECT_EQ("bar", request_.attributes().request().http().headers().at("foo")); - EXPECT_EQ("there", request_.attributes().request().http().headers().at("hello")); - EXPECT_FALSE(request_.attributes().request().http().headers().contains("not-this-one")); + + ASSERT_TRUE(request_.attributes().request().http().headers().contains("not-disallowed-dupe")); + EXPECT_EQ("one,two", request_.attributes().request().http().headers().at("not-disallowed-dupe")); + + ASSERT_TRUE(request_.attributes().request().http().headers().contains("not-disallowed")); + EXPECT_EQ("not disallowed value", + request_.attributes().request().http().headers().at("not-disallowed")); + + EXPECT_FALSE(request_.attributes().request().http().headers().contains("disallowed")); +} + +// Verify that check request only contains allowlisted headers, +// and that duplicate headers are merged. +TEST_F(CheckRequestUtilsTest, BasicHttpWithRequestHeaderAllowlistAndDenylist) { + const uint64_t size = 0; + envoy::service::auth::v3::CheckRequest request_; + + Http::TestRequestHeaderMapImpl request_headers{ + {"allowed", "allowed value"}, + {"allowed-dupe", "one"}, + {"allowed-dupe", "two"}, + {"disallowed", "disallowed value"}, + {"allowed-and-disallowed", "allowed and disallowed value"}, + {"not-allowed-or-disallowed", "not allowed or disallowed value"}, + }; + + EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(Return(std::vector{"source"})); + EXPECT_CALL(*ssl_, uriSanLocalCertificate()) + .WillOnce(Return(std::vector{"destination"})); + expectBasicHttp(); + + CheckRequestUtils::createHttpCheck( + &callbacks_, request_headers, Protobuf::Map(), + envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, + /*pack_as_bytes=*/false, /*encode_raw_headers=*/false, /*include_peer_certificate=*/false, + /*include_tls_session=*/false, Protobuf::Map(), + createRequestHeaderAllowlist(), createRequestHeaderDenylist()); + ASSERT_EQ(size, request_.attributes().request().http().body().size()); + EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); + EXPECT_FALSE(request_.attributes().request().http().has_header_map()); + + ASSERT_TRUE(request_.attributes().request().http().headers().contains("allowed")); + EXPECT_EQ("allowed value", request_.attributes().request().http().headers().at("allowed")); + + ASSERT_TRUE(request_.attributes().request().http().headers().contains("allowed-dupe")); + EXPECT_EQ("one,two", request_.attributes().request().http().headers().at("allowed-dupe")); + + EXPECT_FALSE(request_.attributes().request().http().headers().contains("disallowed")); + EXPECT_FALSE(request_.attributes().request().http().headers().contains("allowed-and-disallowed")); + EXPECT_FALSE( + request_.attributes().request().http().headers().contains("not-allowed-or-disallowed")); } // Verify that check request object has only a portion of the request data. @@ -333,7 +430,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithPartialBody) { &callbacks_, headers_, Protobuf::Map(), envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, size, /*pack_as_bytes=*/false, /*encode_raw_headers=*/false, /*include_peer_certificate=*/false, - /*include_tls_session=*/false, Protobuf::Map(), nullptr); + /*include_tls_session=*/false, Protobuf::Map(), nullptr, nullptr); ASSERT_EQ(size, request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, size), request_.attributes().request().http().body()); EXPECT_FALSE(request_.attributes().request().http().has_header_map()); @@ -355,7 +452,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBody) { envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, buffer_->length(), /*pack_as_bytes=*/false, /*encode_raw_headers=*/false, /*include_peer_certificate=*/false, /*include_tls_session=*/false, - Protobuf::Map(), nullptr); + Protobuf::Map(), nullptr, nullptr); ASSERT_EQ(buffer_->length(), request_.attributes().request().http().body().size()); EXPECT_EQ(buffer_->toString().substr(0, buffer_->length()), request_.attributes().request().http().body()); @@ -390,7 +487,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithFullBodyPackAsBytes) { envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request_, buffer_->length(), /*pack_as_bytes=*/true, /*encode_raw_headers=*/false, /*include_peer_certificate=*/false, /*include_tls_session=*/false, - Protobuf::Map(), nullptr); + Protobuf::Map(), nullptr, nullptr); // TODO(dio): Find a way to test this without using function from testing::internal namespace. testing::internal::CaptureStderr(); @@ -432,7 +529,7 @@ TEST_F(CheckRequestUtilsTest, BasicHttpWithHeadersAsBytes) { envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request, buffer_->length(), /*pack_as_bytes=*/false, /*encode_raw_headers=*/true, /*include_peer_certificate=*/false, /*include_tls_session=*/false, - Protobuf::Map(), nullptr); + Protobuf::Map(), nullptr, nullptr); // Headers field should be empty since ext_authz should populate header_map INSTEAD. EXPECT_EQ(0, request.attributes().request().http().headers().size()); @@ -466,7 +563,7 @@ TEST_F(CheckRequestUtilsTest, HeadersAsBytesNoConcatentation) { envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request, buffer_->length(), /*pack_as_bytes=*/false, /*encode_raw_headers=*/true, /*include_peer_certificate=*/false, /*include_tls_session=*/false, - Protobuf::Map(), nullptr); + Protobuf::Map(), nullptr, nullptr); EXPECT_EQ(0, request.attributes().request().http().headers().size()); ASSERT_TRUE(request.attributes().request().http().has_header_map()); @@ -497,7 +594,7 @@ TEST_F(CheckRequestUtilsTest, HeadersAsBytesExistingPartialBodyHeader) { envoy::config::core::v3::Metadata(), envoy::config::core::v3::Metadata(), request, buffer_->length(), /*pack_as_bytes=*/false, /*encode_raw_headers=*/true, /*include_peer_certificate=*/false, /*include_tls_session=*/false, - Protobuf::Map(), nullptr); + Protobuf::Map(), nullptr, nullptr); EXPECT_EQ(0, request.attributes().request().http().headers().size()); ASSERT_TRUE(request.attributes().request().http().has_header_map()); diff --git a/test/extensions/filters/http/ext_authz/BUILD b/test/extensions/filters/http/ext_authz/BUILD index e216f5f6d63f..07b68c449a1e 100644 --- a/test/extensions/filters/http/ext_authz/BUILD +++ b/test/extensions/filters/http/ext_authz/BUILD @@ -95,13 +95,14 @@ envoy_proto_library( "//test/fuzz:common_proto", "@envoy_api//envoy/config/core/v3:pkg", "@envoy_api//envoy/extensions/filters/http/ext_authz/v3:pkg", + "@envoy_api//envoy/service/auth/v3:pkg", ], ) envoy_cc_fuzz_test( name = "ext_authz_grpc_fuzz_test", srcs = ["ext_authz_grpc_fuzz_test.cc"], - corpus = "ext_authz_corpus", + corpus = "ext_authz_grpc_corpus", deps = [ ":ext_authz_fuzz_lib", ":ext_authz_fuzz_proto_cc_proto", @@ -110,6 +111,7 @@ envoy_cc_fuzz_test( "//test/extensions/filters/common/ext_authz:ext_authz_test_common", "//test/extensions/filters/http/common/fuzz:http_filter_fuzzer_lib", "//test/mocks/grpc:grpc_mocks", + "@envoy_api//envoy/extensions/filters/http/ext_authz/v3:pkg_cc_proto", "@envoy_api//envoy/service/auth/v3:pkg_cc_proto", ], ) @@ -117,7 +119,7 @@ envoy_cc_fuzz_test( envoy_cc_fuzz_test( name = "ext_authz_http_fuzz_test", srcs = ["ext_authz_http_fuzz_test.cc"], - corpus = "ext_authz_corpus", + corpus = "ext_authz_http_corpus", deps = [ ":ext_authz_fuzz_lib", ":ext_authz_fuzz_proto_cc_proto", @@ -128,6 +130,7 @@ envoy_cc_fuzz_test( "//test/mocks/network:network_mocks", "//test/mocks/server:server_factory_context_mocks", "//test/mocks/upstream:cluster_manager_mocks", + "@envoy_api//envoy/extensions/filters/http/ext_authz/v3:pkg_cc_proto", "@envoy_api//envoy/service/auth/v3:pkg_cc_proto", ], ) @@ -144,6 +147,5 @@ envoy_cc_test_library( "//test/mocks/network:network_mocks", "//test/mocks/server:server_factory_context_mocks", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", - "@envoy_api//envoy/extensions/filters/http/ext_authz/v3:pkg_cc_proto", ], ) diff --git a/test/extensions/filters/http/ext_authz/ext_authz_corpus/bad_config b/test/extensions/filters/http/ext_authz/ext_authz_corpus/bad_config deleted file mode 100644 index c4cf684b3521..000000000000 --- a/test/extensions/filters/http/ext_authz/ext_authz_corpus/bad_config +++ /dev/null @@ -1,34 +0,0 @@ -config { - status_on_error { - code: Continue - } - metadata_context_namespaces: "=" - stat_prefix: "r" - filter_enabled_metadata { - filter: "\177\177\177\177\177\177\177\177" - path { - key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - } - value { - string_match { - safe_regex { - google_re2 { - } - regex: "z" - } - ignore_case: true - } - } - } - bootstrap_metadata_labels_key: "r" -} -request_data { -} -filter_metadata { - typed_filter_metadata { - key: ":=" - value { - } - } -} - diff --git a/test/extensions/filters/http/ext_authz/ext_authz_corpus/custom_status b/test/extensions/filters/http/ext_authz/ext_authz_corpus/custom_status deleted file mode 100644 index 6a9192e46ab3..000000000000 --- a/test/extensions/filters/http/ext_authz/ext_authz_corpus/custom_status +++ /dev/null @@ -1,22 +0,0 @@ -config { - grpc_service { - envoy_grpc { - cluster_name: "ext_authz_server" - } - } - status_on_error { - code: ServiceUnavilable - } -} -request_data { - headers { - headers { - key: ":host" - value: "example.com" - } - headers { - key: ":method" - value: "GET" - } - } -} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_corpus/error_fail_close b/test/extensions/filters/http/ext_authz/ext_authz_corpus/error_fail_close deleted file mode 100644 index 82d08abfa9cb..000000000000 --- a/test/extensions/filters/http/ext_authz/ext_authz_corpus/error_fail_close +++ /dev/null @@ -1,29 +0,0 @@ -config { - grpc_service { - envoy_grpc { - cluster_name: "ext_authz_server" - } - } - stat_prefix: "with_stat_prefix" -} -request_data { - headers { - headers { - key: ":host" - value: "example.com" - } - headers { - key: ":method" - value: "GET" - } - headers { - key: ":path" - value: "/users" - } - headers { - key: ":scheme" - value: "https" - } - } -} -result: ERROR diff --git a/test/extensions/filters/http/ext_authz/ext_authz_corpus/example b/test/extensions/filters/http/ext_authz/ext_authz_corpus/example deleted file mode 100644 index 48b7bf01c193..000000000000 --- a/test/extensions/filters/http/ext_authz/ext_authz_corpus/example +++ /dev/null @@ -1,7 +0,0 @@ -config { -} -request_data { - - -} -result: ERROR diff --git a/test/extensions/filters/http/ext_authz/ext_authz_corpus/metadata_context b/test/extensions/filters/http/ext_authz/ext_authz_corpus/metadata_context deleted file mode 100644 index 403ad1070239..000000000000 --- a/test/extensions/filters/http/ext_authz/ext_authz_corpus/metadata_context +++ /dev/null @@ -1,84 +0,0 @@ -config { - grpc_service { - envoy_grpc { - cluster_name: "ext_authz_server" - } - } - metadata_context_namespaces: "jazz.sax" - metadata_context_namespaces: "rock.guitar" - metadata_context_namespaces: "hiphop.drums" -} -filter_metadata { -filter_metadata { - key: "jazz.piano" - value { - fields { - key: "hancock" - value { - string_value: "herbie" - } - } - fields { - key: "monk" - value { - string_value: "thelonious" - } - } - } -} -filter_metadata { - key: "jazz.sax" - value { - fields { - key: "coltrane" - value { - string_value: "john" - } - } - fields { - key: "parker" - value { - string_value: "charlie" - } - } - } -} -filter_metadata { - key: "rock.guitar" - value { - fields { - key: "hendrix" - value { - string_value: "jimi" - } - } - fields { - key: "richards" - value { - string_value: "keith" - } - } - } -} -} -result: OK -request_data { - headers { - headers { - key: ":host" - value: "example.com" - } - headers { - key: ":method" - value: "GET" - } - headers { - key: ":path" - value: "/users" - } - headers { - key: ":scheme" - value: "https" - } - } -} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_corpus/no_regex_engine b/test/extensions/filters/http/ext_authz/ext_authz_corpus/no_regex_engine deleted file mode 100644 index d6953254b178..000000000000 --- a/test/extensions/filters/http/ext_authz/ext_authz_corpus/no_regex_engine +++ /dev/null @@ -1,21 +0,0 @@ -config { - clear_route_cache: true - include_peer_certificate: true - stat_prefix: "C" - filter_enabled_metadata { - filter: ";" - path { - key: "V" - } - value { - string_match { - safe_regex { - regex: "/envoy.config.route.v3.Route" - } - } - } - } - bootstrap_metadata_labels_key: "\000\000\000\000\000\000\000\000\000\000\000\000\000" -} -request_data { -} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_corpus/request_data b/test/extensions/filters/http/ext_authz/ext_authz_corpus/request_data deleted file mode 100644 index e578bd966943..000000000000 --- a/test/extensions/filters/http/ext_authz/ext_authz_corpus/request_data +++ /dev/null @@ -1,33 +0,0 @@ -config { - grpc_service { - envoy_grpc { - cluster_name: "ext_authz_server" - } - } - with_request_body { - max_request_bytes: 10 - } -} -request_data { - headers { - headers { - key: ":host" - value: "example.com" - } - headers { - key: ":method" - value: "GET" - } - headers { - key: ":path" - value: "/users" - } - headers { - key: ":scheme" - value: "https" - } - } - http_body { - data: "foobarbaz" - } -} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_fuzz.proto b/test/extensions/filters/http/ext_authz/ext_authz_fuzz.proto index 3f32b9afad1e..ef05bb1c8731 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_fuzz.proto +++ b/test/extensions/filters/http/ext_authz/ext_authz_fuzz.proto @@ -1,13 +1,37 @@ syntax = "proto3"; package envoy.extensions.filters.http.ext_authz; +import "envoy/config/core/v3/base.proto"; import "envoy/extensions/filters/http/ext_authz/v3/ext_authz.proto"; +import "envoy/service/auth/v3/external_auth.proto"; import "test/fuzz/common.proto"; -import "envoy/config/core/v3/base.proto"; import "validate/validate.proto"; // We only fuzz a single request per iteration. -message ExtAuthzTestCase { +message ExtAuthzTestCaseBase { + envoy.extensions.filters.http.ext_authz.v3.ExtAuthz config = 1 + [(validate.rules).message = {required: true}]; + // HTTP request data. + test.fuzz.HttpData request_data = 2 [(validate.rules).message = {required: true}]; + // Filter metadata. + envoy.config.core.v3.Metadata filter_metadata = 4; +} + +message ExtAuthzTestCaseGrpc { + ExtAuthzTestCaseBase base = 1 [(validate.rules).message = {required: true}]; + + oneof response_or_failure_reason { + // Full auth check result. Note it is not validated to simulate an untrusted authz server (i.e. + // it can contain garbage mutations). + envoy.service.auth.v3.CheckResponse response = 2 [(validate.rules).message.skip = true]; + // If this is set onFailure will be called instead of onSuccess. + string failure_reason = 3; + } +} + +message ExtAuthzTestCaseHttp { + ExtAuthzTestCaseBase base = 1 [(validate.rules).message = {required: true}]; + enum AuthResult { // Possible results for a check call. Taken from // https://github.com/envoyproxy/envoy/blob/945b5833f094dee31d2971cee8d40553bb0fe714/source/extensions/filters/common/ext_authz/ext_authz.h#L65 @@ -15,15 +39,6 @@ message ExtAuthzTestCase { DENIED = 1; ERROR = 2; } - - envoy.extensions.filters.http.ext_authz.v3.ExtAuthz config = 1 - [(validate.rules).message = {required: true}]; - // HTTP request data. - test.fuzz.HttpData request_data = 2 [(validate.rules).message = {required: true}]; // Set default auth check result. AuthResult result = 3 [(validate.rules).enum.defined_only = true]; - // Filter metadata. - envoy.config.core.v3.Metadata filter_metadata = 4; - // TODO: Add headers and data to ExtAuthz::Response and check that the request headers and data - // were updated. } diff --git a/test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.cc b/test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.cc index 4568ac600e4c..166b98132c2e 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.cc @@ -1,13 +1,11 @@ #include "test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.h" #include "envoy/config/core/v3/base.pb.h" -#include "envoy/extensions/filters/http/ext_authz/v3/ext_authz.pb.validate.h" #include "source/common/network/address_impl.h" #include "source/extensions/filters/http/ext_authz/ext_authz.h" #include "test/extensions/filters/http/ext_authz/ext_authz_fuzz.pb.h" -#include "test/extensions/filters/http/ext_authz/ext_authz_fuzz.pb.validate.h" #include "test/mocks/network/mocks.h" #include "gmock/gmock.h" @@ -43,14 +41,8 @@ ReusableFilterFactory::newFilter(FilterConfigSharedPtr config, } absl::StatusOr> ReusableFuzzerUtil::setup( - const envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase& input, + const envoy::extensions::filters::http::ext_authz::ExtAuthzTestCaseBase& input, Filters::Common::ExtAuthz::ClientPtr client) { - try { - TestUtility::validate(input); - } catch (const EnvoyException& e) { - ENVOY_LOG_MISC(debug, "EnvoyException during validation: {}", e.what()); - return absl::InvalidArgumentError(absl::StrCat("EnvoyException during validation: ", e.what())); - } // Prepare filter. const envoy::extensions::filters::http::ext_authz::v3::ExtAuthz proto_config = input.config(); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.h b/test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.h index 1fc346df16e1..1aa876843347 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.h +++ b/test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.h @@ -43,7 +43,7 @@ class ReusableFuzzerUtil { public: // Validate input, then create a filter using the input.config() & the provided client. absl::StatusOr> - setup(const envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase& input, + setup(const envoy::extensions::filters::http::ext_authz::ExtAuthzTestCaseBase& input, Filters::Common::ExtAuthz::ClientPtr client); private: diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/bad_config b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/bad_config new file mode 100644 index 000000000000..fe2875d5c4a1 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/bad_config @@ -0,0 +1,35 @@ +base { + config { + status_on_error { + code: Continue + } + metadata_context_namespaces: "=" + stat_prefix: "r" + filter_enabled_metadata { + filter: "\177\177\177\177\177\177\177\177" + path { + key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + value { + string_match { + safe_regex { + google_re2 { + } + regex: "z" + } + ignore_case: true + } + } + } + bootstrap_metadata_labels_key: "r" + } + request_data { + } + filter_metadata { + typed_filter_metadata { + key: ":=" + value { + } + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/custom_status b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/custom_status new file mode 100644 index 000000000000..8a78a99a1395 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/custom_status @@ -0,0 +1,24 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + status_on_error { + code: ServiceUnavilable + } + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/denied_response b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/denied_response new file mode 100644 index 000000000000..8cb3cda0e2d4 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/denied_response @@ -0,0 +1,49 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + stat_prefix: "with_stat_prefix" + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } + } +} +response { + status { + code: 7 + message: "PERMISSION DENIED" + } + denied_response { + status { + code: Forbidden + } + headers { + header { + key: "added-upstream-request-header" + value: "this should be added by ext_authz to the upstream request!" + } + append_action: APPEND_IF_EXISTS_OR_ADD + } + body: "this is the response body" + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/error_fail_close b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/error_fail_close new file mode 100644 index 000000000000..f6ecf0006c86 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/error_fail_close @@ -0,0 +1,31 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + stat_prefix: "with_stat_prefix" + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } + } +} +failure_reason: "this will trigger onFailure!" diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/example b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/example new file mode 100644 index 000000000000..edfafb97436f --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/example @@ -0,0 +1,10 @@ +base { + config { + } + request_data { + + + } +} +response { +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/invalid_headers_to_remove b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/invalid_headers_to_remove new file mode 100644 index 000000000000..de00cab63c9f --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/invalid_headers_to_remove @@ -0,0 +1,11 @@ +base { + config { + } + request_data { + } +} +response { + ok_response { + headers_to_remove: "*\000\000\000\000\000\000\000" + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/invalid_query_parameters_to_set b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/invalid_query_parameters_to_set new file mode 100644 index 000000000000..811aa67cd858 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/invalid_query_parameters_to_set @@ -0,0 +1,35 @@ +base { + config { + } + request_data { + } +} +response { + ok_response { + query_parameters_to_set { + } + query_parameters_to_set { + key: " " + } + query_parameters_to_set { + value: " " + } + query_parameters_to_set { + key: "???" + value: "&&&&" + } + query_parameters_to_set { + key: "\005\000" + value: "blah blah" + } + + query_parameters_to_set { + key: "no more query params!" + value: "#" + } + query_parameters_to_set { + key: "test" + value: "testval" + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/metadata_context b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/metadata_context new file mode 100644 index 000000000000..b09d8aa248de --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/metadata_context @@ -0,0 +1,85 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + metadata_context_namespaces: "jazz.sax" + metadata_context_namespaces: "rock.guitar" + metadata_context_namespaces: "hiphop.drums" + } + filter_metadata { + filter_metadata { + key: "jazz.piano" + value { + fields { + key: "hancock" + value { + string_value: "herbie" + } + } + fields { + key: "monk" + value { + string_value: "thelonious" + } + } + } + } + filter_metadata { + key: "jazz.sax" + value { + fields { + key: "coltrane" + value { + string_value: "john" + } + } + fields { + key: "parker" + value { + string_value: "charlie" + } + } + } + } + filter_metadata { + key: "rock.guitar" + value { + fields { + key: "hendrix" + value { + string_value: "jimi" + } + } + fields { + key: "richards" + value { + string_value: "keith" + } + } + } + } + } +} +request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/no_regex_engine b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/no_regex_engine new file mode 100644 index 000000000000..f51597e40ac5 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/no_regex_engine @@ -0,0 +1,23 @@ +base { + config { + clear_route_cache: true + include_peer_certificate: true + stat_prefix: "C" + filter_enabled_metadata { + filter: ";" + path { + key: "V" + } + value { + string_match { + safe_regex { + regex: "/envoy.config.route.v3.Route" + } + } + } + } + bootstrap_metadata_labels_key: "\000\000\000\000\000\000\000\000\000\000\000\000\000" + } +} +request_data { +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/ok_response b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/ok_response new file mode 100644 index 000000000000..dacc95dbc1ce --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/ok_response @@ -0,0 +1,59 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + stat_prefix: "with_stat_prefix" + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } + } +} +response { + status { + code: 0 + message: "LGTM!" + } + ok_response { + headers { + header { + key: "added-upstream-request-header" + value: "this should be added by ext_authz to the upstream request!" + } + append_action: APPEND_IF_EXISTS_OR_ADD + } + headers_to_remove: "blah" + headers_to_remove: "bleh" + response_headers_to_add { + header { + key: "added-downstream-response-header" + value: "this should be added by ext_authz to the downstream response!" + } + append_action: APPEND_IF_EXISTS_OR_ADD + } + query_parameters_to_set { + key: "new-query-param-key" + value: "query param value" + } + query_parameters_to_remove: "query-param-key-remove-me" + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/request_data b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/request_data new file mode 100644 index 000000000000..21b1204f5adf --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_corpus/request_data @@ -0,0 +1,35 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + with_request_body { + max_request_bytes: 10 + } + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } + http_body { + data: "foobarbaz" + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_grpc_fuzz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_grpc_fuzz_test.cc index 300e093f402c..8af3534d9b2b 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_grpc_fuzz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_grpc_fuzz_test.cc @@ -1,12 +1,17 @@ +#include + +#include "envoy/extensions/filters/http/ext_authz/v3/ext_authz.pb.validate.h" #include "envoy/grpc/status.h" #include "envoy/service/auth/v3/external_auth.pb.h" #include "source/common/common/assert.h" -#include "source/extensions/filters/common/ext_authz/ext_authz_http_impl.h" +#include "source/extensions/filters/common/ext_authz/ext_authz_grpc_impl.h" +#include "source/extensions/filters/http/ext_authz/ext_authz.h" #include "test/extensions/filters/common/ext_authz/test_common.h" #include "test/extensions/filters/http/common/fuzz/http_filter_fuzzer.h" #include "test/extensions/filters/http/ext_authz/ext_authz_fuzz.pb.h" +#include "test/extensions/filters/http/ext_authz/ext_authz_fuzz.pb.validate.h" #include "test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.h" #include "test/fuzz/fuzz_runner.h" #include "test/mocks/grpc/mocks.h" @@ -14,8 +19,7 @@ #include "gmock/gmock.h" using Envoy::Extensions::Filters::Common::ExtAuthz::TestCommon; -using envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase; -using testing::Return; +using envoy::extensions::filters::http::ext_authz::ExtAuthzTestCaseGrpc; namespace Envoy { namespace Extensions { @@ -23,29 +27,6 @@ namespace HttpFilters { namespace ExtAuthz { namespace { -std::unique_ptr -makeGrpcCheckResponse(const Grpc::Status::WellKnownGrpcStatus status) { - auto response = std::make_unique(); - response->mutable_status()->set_code(status); - // TODO: We only add the response status. - // Add fuzzed inputs for headers_to_(set/append/add), body, status_code to the Response. - return response; -} - -Grpc::Status::WellKnownGrpcStatus -resultCaseToGrpcStatus(const ExtAuthzTestCase::AuthResult result) { - switch (result) { - PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; - case ExtAuthzTestCase::OK: - return Grpc::Status::WellKnownGrpcStatus::Ok; - case ExtAuthzTestCase::ERROR: - return Grpc::Status::WellKnownGrpcStatus::Internal; - case ExtAuthzTestCase::DENIED: - return Grpc::Status::WellKnownGrpcStatus::PermissionDenied; - } - PANIC_DUE_TO_CORRUPT_ENUM; -} - class ReusableGrpcClientFactory { public: ReusableGrpcClientFactory() @@ -59,25 +40,31 @@ class ReusableGrpcClientFactory { EXPECT_TRUE(check_request.ParseFromString(serialized_req->toString())) << "Could not parse serialized check request"; - // TODO: Query the request header map in HttpFilterFuzzer to test - // headers_to_(add/remove/append). - // TODO: Test check request attributes against config - // and filter metadata. - ENVOY_LOG_MISC(trace, "Check Request attributes {}", + ENVOY_LOG_MISC(trace, "Check Request attributes:\n{}", check_request.attributes().DebugString()); - if (status_ == Grpc::Status::WellKnownGrpcStatus::Ok) { - grpc_client_->onSuccess(makeGrpcCheckResponse(status_), mock_span_); + if (failure_reason_) { + grpc_client_->onFailure(Envoy::Grpc::Status::WellKnownGrpcStatus::Unknown, + *failure_reason_, mock_span_); } else { - grpc_client_->onFailure(status_, "Fuzz input status was not ok!", mock_span_); + grpc_client_->onSuccess(std::move(response_), mock_span_); } return &grpc_async_request_; })); } std::unique_ptr - newGrpcClientImpl(const ExtAuthzTestCase::AuthResult result) { - status_ = resultCaseToGrpcStatus(result); + newGrpcClientImpl(const ExtAuthzTestCaseGrpc& input) { + if (input.has_failure_reason()) { + failure_reason_ = input.failure_reason(); + response_ = nullptr; + ENVOY_LOG_MISC(trace, "Failure reason: {}", *failure_reason_); + } else { + failure_reason_ = std::nullopt; + response_ = std::make_unique(input.response()); + ENVOY_LOG_MISC(trace, "Check Response:\n{}", response_->DebugString()); + } + grpc_client_ = new Filters::Common::ExtAuthz::GrpcClientImpl(internal_grpc_mock_client_, std::chrono::milliseconds(1000)); return std::unique_ptr{grpc_client_}; @@ -88,24 +75,35 @@ class ReusableGrpcClientFactory { Envoy::Tracing::MockSpan mock_span_; NiceMock grpc_async_request_; - // Set by calling newGrpcClientImpl - Grpc::Status::WellKnownGrpcStatus status_; + // Set by calling newGrpcClientImpl. Only one of response_ or failure_reason_ will be set. + std::unique_ptr response_; + absl::optional failure_reason_; Filters::Common::ExtAuthz::GrpcClientImpl* grpc_client_; }; -DEFINE_PROTO_FUZZER(const ExtAuthzTestCase& input) { +DEFINE_PROTO_FUZZER(ExtAuthzTestCaseGrpc& input) { static ReusableFuzzerUtil fuzzer_util; static ReusableGrpcClientFactory grpc_client_factory; - auto grpc_client = grpc_client_factory.newGrpcClientImpl(input.result()); - absl::StatusOr> filter = fuzzer_util.setup(input, std::move(grpc_client)); + + try { + TestUtility::validate(input); + } catch (const EnvoyException& e) { + ENVOY_LOG_MISC(debug, "EnvoyException during validation: {}", e.what()); + return; + } + + auto grpc_client = grpc_client_factory.newGrpcClientImpl(input); + // Force this to be true so that the filter can handle invalid mutations gracefully. + input.mutable_base()->mutable_config()->set_validate_mutations(true); + absl::StatusOr> filter = + fuzzer_util.setup(input.base(), std::move(grpc_client)); if (!filter.ok()) { return; } - // TODO: Add response headers. static Envoy::Extensions::HttpFilters::HttpFilterFuzzer fuzzer; fuzzer.runData(static_cast(filter->get()), - input.request_data()); + input.base().request_data()); } } // namespace diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/bad_config b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/bad_config new file mode 100644 index 000000000000..fe2875d5c4a1 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/bad_config @@ -0,0 +1,35 @@ +base { + config { + status_on_error { + code: Continue + } + metadata_context_namespaces: "=" + stat_prefix: "r" + filter_enabled_metadata { + filter: "\177\177\177\177\177\177\177\177" + path { + key: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + } + value { + string_match { + safe_regex { + google_re2 { + } + regex: "z" + } + ignore_case: true + } + } + } + bootstrap_metadata_labels_key: "r" + } + request_data { + } + filter_metadata { + typed_filter_metadata { + key: ":=" + value { + } + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/custom_status b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/custom_status new file mode 100644 index 000000000000..8a78a99a1395 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/custom_status @@ -0,0 +1,24 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + status_on_error { + code: ServiceUnavilable + } + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/denied_response b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/denied_response new file mode 100644 index 000000000000..9a887d459c36 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/denied_response @@ -0,0 +1,31 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + stat_prefix: "with_stat_prefix" + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } + } +} +result: DENIED diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/error_fail_close b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/error_fail_close new file mode 100644 index 000000000000..07e50ec30093 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/error_fail_close @@ -0,0 +1,31 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + stat_prefix: "with_stat_prefix" + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } + } +} +result: ERROR diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/example b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/example new file mode 100644 index 000000000000..b4ae79db12b0 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/example @@ -0,0 +1,9 @@ +base { + config { + } + request_data { + + + } +} +result: ERROR diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/metadata_context b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/metadata_context new file mode 100644 index 000000000000..b09d8aa248de --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/metadata_context @@ -0,0 +1,85 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + metadata_context_namespaces: "jazz.sax" + metadata_context_namespaces: "rock.guitar" + metadata_context_namespaces: "hiphop.drums" + } + filter_metadata { + filter_metadata { + key: "jazz.piano" + value { + fields { + key: "hancock" + value { + string_value: "herbie" + } + } + fields { + key: "monk" + value { + string_value: "thelonious" + } + } + } + } + filter_metadata { + key: "jazz.sax" + value { + fields { + key: "coltrane" + value { + string_value: "john" + } + } + fields { + key: "parker" + value { + string_value: "charlie" + } + } + } + } + filter_metadata { + key: "rock.guitar" + value { + fields { + key: "hendrix" + value { + string_value: "jimi" + } + } + fields { + key: "richards" + value { + string_value: "keith" + } + } + } + } + } +} +request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/no_regex_engine b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/no_regex_engine new file mode 100644 index 000000000000..f51597e40ac5 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/no_regex_engine @@ -0,0 +1,23 @@ +base { + config { + clear_route_cache: true + include_peer_certificate: true + stat_prefix: "C" + filter_enabled_metadata { + filter: ";" + path { + key: "V" + } + value { + string_match { + safe_regex { + regex: "/envoy.config.route.v3.Route" + } + } + } + } + bootstrap_metadata_labels_key: "\000\000\000\000\000\000\000\000\000\000\000\000\000" + } +} +request_data { +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/ok_response b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/ok_response new file mode 100644 index 000000000000..de04802385a6 --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/ok_response @@ -0,0 +1,31 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + stat_prefix: "with_stat_prefix" + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } + } +} +result: OK diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/request_data b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/request_data new file mode 100644 index 000000000000..21b1204f5adf --- /dev/null +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_corpus/request_data @@ -0,0 +1,35 @@ +base { + config { + grpc_service { + envoy_grpc { + cluster_name: "ext_authz_server" + } + } + with_request_body { + max_request_bytes: 10 + } + } + request_data { + headers { + headers { + key: ":host" + value: "example.com" + } + headers { + key: ":method" + value: "GET" + } + headers { + key: ":path" + value: "/users" + } + headers { + key: ":scheme" + value: "https" + } + } + http_body { + data: "foobarbaz" + } + } +} diff --git a/test/extensions/filters/http/ext_authz/ext_authz_http_fuzz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_http_fuzz_test.cc index 3a881cfca27c..4b4309b7452d 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_http_fuzz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_http_fuzz_test.cc @@ -1,6 +1,7 @@ #include #include +#include "envoy/extensions/filters/http/ext_authz/v3/ext_authz.pb.validate.h" #include "envoy/service/auth/v3/external_auth.pb.h" #include "source/common/common/assert.h" @@ -9,6 +10,7 @@ #include "test/extensions/filters/common/ext_authz/test_common.h" #include "test/extensions/filters/http/common/fuzz/http_filter_fuzzer.h" #include "test/extensions/filters/http/ext_authz/ext_authz_fuzz.pb.h" +#include "test/extensions/filters/http/ext_authz/ext_authz_fuzz.pb.validate.h" #include "test/extensions/filters/http/ext_authz/ext_authz_fuzz_lib.h" #include "test/fuzz/fuzz_runner.h" #include "test/mocks/server/server_factory_context.h" @@ -17,7 +19,7 @@ #include "gmock/gmock.h" using Envoy::Extensions::Filters::Common::ExtAuthz::TestCommon; -using envoy::extensions::filters::http::ext_authz::ExtAuthzTestCase; +using envoy::extensions::filters::http::ext_authz::ExtAuthzTestCaseHttp; namespace Envoy { namespace Extensions { @@ -25,14 +27,14 @@ namespace HttpFilters { namespace ExtAuthz { namespace { -std::string resultCaseToHttpStatus(const ExtAuthzTestCase::AuthResult result) { +std::string resultCaseToHttpStatus(const ExtAuthzTestCaseHttp::AuthResult result) { switch (result) { PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; - case ExtAuthzTestCase::OK: + case ExtAuthzTestCaseHttp::OK: return "200"; - case ExtAuthzTestCase::ERROR: + case ExtAuthzTestCaseHttp::ERROR: return "500"; - case ExtAuthzTestCase::DENIED: + case ExtAuthzTestCaseHttp::DENIED: return "403"; } PANIC_DUE_TO_CORRUPT_ENUM; @@ -60,7 +62,7 @@ class ReusableHttpClientFactory { } std::unique_ptr - newRawHttpClientImpl(const ExtAuthzTestCase::AuthResult result) { + newRawHttpClientImpl(const ExtAuthzTestCaseHttp::AuthResult result) { http_client_ = new Filters::Common::ExtAuthz::RawHttpClientImpl(cm_, createConfig()); status_ = resultCaseToHttpStatus(result); return std::unique_ptr(http_client_); @@ -121,11 +123,20 @@ class ReusableHttpClientFactory { Filters::Common::ExtAuthz::RawHttpClientImpl* http_client_; }; -DEFINE_PROTO_FUZZER(const ExtAuthzTestCase& input) { +DEFINE_PROTO_FUZZER(const ExtAuthzTestCaseHttp& input) { static ReusableFuzzerUtil fuzzer_util; static ReusableHttpClientFactory http_client_factory; + + try { + TestUtility::validate(input); + } catch (const EnvoyException& e) { + ENVOY_LOG_MISC(debug, "EnvoyException during validation: {}", e.what()); + return; + } + auto http_client = http_client_factory.newRawHttpClientImpl(input.result()); - absl::StatusOr> filter = fuzzer_util.setup(input, std::move(http_client)); + absl::StatusOr> filter = + fuzzer_util.setup(input.base(), std::move(http_client)); if (!filter.ok()) { return; } @@ -133,7 +144,7 @@ DEFINE_PROTO_FUZZER(const ExtAuthzTestCase& input) { // TODO: Add response headers. static Envoy::Extensions::HttpFilters::HttpFilterFuzzer fuzzer; fuzzer.runData(static_cast(filter->get()), - input.request_data()); + input.base().request_data()); } } // namespace diff --git a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc index e8f869591aaa..a1f626650a05 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_integration_test.cc @@ -172,6 +172,7 @@ class ExtAuthzGrpcIntegrationTest {"x-duplicate", "three"}, {"allowed-prefix-one", "one"}, {"allowed-prefix-two", "two"}, + {"allowed-prefix-denied", "denied"}, {"not-allowed", "nope"}, {"regex-food", "food"}, {"regex-fool", "fool"}, @@ -264,6 +265,7 @@ class ExtAuthzGrpcIntegrationTest .EnvoyAuthPartialBody.get(), "shouldn't be visible in authz request"}, {"not-allowed", std::nullopt}, + {"allowed-prefix-denied", std::nullopt}, }; for (const auto& [key, value] : unexpected_headers) { bool found = false; @@ -285,6 +287,7 @@ class ExtAuthzGrpcIntegrationTest EXPECT_EQ("one,two,three", (*http_request->mutable_headers())["x-duplicate"]); EXPECT_EQ("food", (*http_request->mutable_headers())["regex-food"]); EXPECT_EQ("fool", (*http_request->mutable_headers())["regex-fool"]); + EXPECT_FALSE(http_request->headers().contains("allowed-prefix-denied")); EXPECT_FALSE(http_request->headers().contains("not-allowed")); } @@ -609,6 +612,9 @@ class ExtAuthzGrpcIntegrationTest - prefix: allowed-prefix - safe_regex: regex: regex-foo.? + disallowed_headers: + patterns: + - prefix: allowed-prefix-denied with_request_body: max_request_bytes: 1024 @@ -653,6 +659,7 @@ class ExtAuthzHttpIntegrationTest {"x-duplicate", "three"}, {"allowed-prefix-one", "one"}, {"allowed-prefix-two", "two"}, + {"allowed-prefix-denied", "blah"}, {"not-allowed", "nope"}, {"authorization", "legit"}, {"regex-food", "food"}, @@ -689,6 +696,9 @@ class ExtAuthzHttpIntegrationTest EXPECT_TRUE(ext_authz_request_->headers() .get(Http::LowerCaseString(std::string("not-allowed"))) .empty()); + EXPECT_TRUE(ext_authz_request_->headers() + .get(Http::LowerCaseString(std::string("allowed-prefix-denied"))) + .empty()); EXPECT_EQ("food", ext_authz_request_->headers() .get(Http::LowerCaseString(std::string("regex-food")))[0] ->value() @@ -839,6 +849,9 @@ class ExtAuthzHttpIntegrationTest const std::string case_sensitive_header_value_{"Case-Sensitive"}; const std::string legacy_default_config_ = R"EOF( transport_api_version: V3 + disallowed_headers: + patterns: + - prefix: allowed-prefix-denied http_service: server_uri: uri: "ext_authz:9000" @@ -874,6 +887,9 @@ class ExtAuthzHttpIntegrationTest - safe_regex: regex: regex-foo.? - exact: x-forwarded-for + disallowed_headers: + patterns: + - prefix: allowed-prefix-denied http_service: server_uri: @@ -967,8 +983,9 @@ TEST_P(ExtAuthzGrpcIntegrationTest, CheckAfterBufferingComplete) { {":scheme", "http"}, {":authority", "host"}, {"x-duplicate", "one"}, {"x-duplicate", "two"}, {"x-duplicate", "three"}, {"allowed-prefix-one", "one"}, - {"allowed-prefix-two", "two"}, {"not-allowed", "nope"}, - {"regex-food", "food"}, {"regex-fool", "fool"}}; + {"allowed-prefix-two", "two"}, {"allowed-prefix-denied", "will not be sent"}, + {"not-allowed", "nope"}, {"regex-food", "food"}, + {"regex-fool", "fool"}}; auto conn = makeClientConnection(lookupPort("http")); codec_client_ = makeHttpConnection(std::move(conn)); @@ -1144,10 +1161,10 @@ TEST_P(ExtAuthzGrpcIntegrationTest, FailureModeAllowNonUtf8) { {":method", "POST"}, {":path", "/test"}, {":scheme", "http"}, {":authority", "host"}, {"x-bypass", invalid_unicode}, {"allowed-prefix-one", "one"}, - {"allowed-prefix-two", "two"}, {"not-allowed", "nope"}, - {"x-duplicate", "one"}, {"x-duplicate", "two"}, - {"x-duplicate", "three"}, {"regex-food", "food"}, - {"regex-fool", "fool"}}; + {"allowed-prefix-two", "two"}, {"allowed-prefix-denied", "denied"}, + {"not-allowed", "nope"}, {"x-duplicate", "one"}, + {"x-duplicate", "two"}, {"x-duplicate", "three"}, + {"regex-food", "food"}, {"regex-fool", "fool"}}; response_ = codec_client_->makeRequestWithBody(headers, {}); diff --git a/test/extensions/filters/http/ext_authz/ext_authz_test.cc b/test/extensions/filters/http/ext_authz/ext_authz_test.cc index e1e3d7fad682..1398f323338c 100644 --- a/test/extensions/filters/http/ext_authz/ext_authz_test.cc +++ b/test/extensions/filters/http/ext_authz/ext_authz_test.cc @@ -2442,7 +2442,7 @@ TEST_P(HttpFilterTestParam, RequestHeaderMatchersForGrpcService) { cluster_name: "ext_authz_server" )EOF"); - EXPECT_TRUE(config_->requestHeaderMatchers() == nullptr); + EXPECT_TRUE(config_->allowedHeadersMatcher() == nullptr); } TEST_P(HttpFilterTestParam, RequestHeaderMatchersForHttpService) { @@ -2455,12 +2455,12 @@ TEST_P(HttpFilterTestParam, RequestHeaderMatchersForHttpService) { timeout: 0.25s )EOF"); - EXPECT_TRUE(config_->requestHeaderMatchers() != nullptr); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(Http::Headers::get().Method.get())); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(Http::Headers::get().Host.get())); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(Http::Headers::get().Path.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher() != nullptr); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().Method.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().Host.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().Path.get())); EXPECT_TRUE( - config_->requestHeaderMatchers()->matches(Http::CustomHeaders::get().Authorization.get())); + config_->allowedHeadersMatcher()->matches(Http::CustomHeaders::get().Authorization.get())); } TEST_P(HttpFilterTestParam, RequestHeaderMatchersForGrpcServiceWithAllowedHeaders) { @@ -2476,8 +2476,25 @@ TEST_P(HttpFilterTestParam, RequestHeaderMatchersForGrpcServiceWithAllowedHeader ignore_case: true )EOF"); - EXPECT_TRUE(config_->requestHeaderMatchers() != nullptr); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(foo.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher() != nullptr); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(foo.get())); +} + +TEST_P(HttpFilterTestParam, RequestHeaderMatchersForGrpcServiceWithDisallowedHeaders) { + const Http::LowerCaseString foo{"foo"}; + initialize(R"EOF( + transport_api_version: V3 + grpc_service: + envoy_grpc: + cluster_name: "ext_authz_server" + disallowed_headers: + patterns: + - exact: Foo + ignore_case: true + )EOF"); + + EXPECT_TRUE(config_->disallowedHeadersMatcher() != nullptr); + EXPECT_TRUE(config_->disallowedHeadersMatcher()->matches(foo.get())); } TEST_P(HttpFilterTestParam, RequestHeaderMatchersForHttpServiceWithAllowedHeaders) { @@ -2495,13 +2512,32 @@ TEST_P(HttpFilterTestParam, RequestHeaderMatchersForHttpServiceWithAllowedHeader ignore_case: true )EOF"); - EXPECT_TRUE(config_->requestHeaderMatchers() != nullptr); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(Http::Headers::get().Method.get())); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(Http::Headers::get().Host.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher() != nullptr); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().Method.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().Host.get())); EXPECT_TRUE( - config_->requestHeaderMatchers()->matches(Http::CustomHeaders::get().Authorization.get())); - EXPECT_FALSE(config_->requestHeaderMatchers()->matches(Http::Headers::get().ContentLength.get())); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(foo.get())); + config_->allowedHeadersMatcher()->matches(Http::CustomHeaders::get().Authorization.get())); + EXPECT_FALSE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().ContentLength.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(foo.get())); +} + +TEST_P(HttpFilterTestParam, RequestHeaderMatchersForHttpServiceWithDisallowedHeaders) { + const Http::LowerCaseString foo{"foo"}; + initialize(R"EOF( + transport_api_version: V3 + http_service: + server_uri: + uri: "ext_authz:9000" + cluster: "ext_authz" + timeout: 0.25s + disallowed_headers: + patterns: + - exact: Foo + ignore_case: true + )EOF"); + + EXPECT_TRUE(config_->disallowedHeadersMatcher() != nullptr); + EXPECT_TRUE(config_->disallowedHeadersMatcher()->matches(foo.get())); } TEST_P(HttpFilterTestParam, @@ -2521,13 +2557,13 @@ TEST_P(HttpFilterTestParam, ignore_case: true )EOF"); - EXPECT_TRUE(config_->requestHeaderMatchers() != nullptr); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(Http::Headers::get().Method.get())); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(Http::Headers::get().Host.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher() != nullptr); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().Method.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().Host.get())); EXPECT_TRUE( - config_->requestHeaderMatchers()->matches(Http::CustomHeaders::get().Authorization.get())); - EXPECT_FALSE(config_->requestHeaderMatchers()->matches(Http::Headers::get().ContentLength.get())); - EXPECT_TRUE(config_->requestHeaderMatchers()->matches(foo.get())); + config_->allowedHeadersMatcher()->matches(Http::CustomHeaders::get().Authorization.get())); + EXPECT_FALSE(config_->allowedHeadersMatcher()->matches(Http::Headers::get().ContentLength.get())); + EXPECT_TRUE(config_->allowedHeadersMatcher()->matches(foo.get())); } TEST_P(HttpFilterTestParam, DEPRECATED_FEATURE_TEST(DuplicateAllowedHeadersConfigIsInvalid)) { diff --git a/test/extensions/filters/http/jwt_authn/jwt_authn_corpus/invalid_timeout b/test/extensions/filters/http/jwt_authn/jwt_authn_corpus/invalid_timeout new file mode 100644 index 000000000000..0d829f63c484 --- /dev/null +++ b/test/extensions/filters/http/jwt_authn/jwt_authn_corpus/invalid_timeout @@ -0,0 +1,35 @@ +config { + providers { + key: "H" + value { + remote_jwks { + http_uri { + uri: "https://exampl\037.com" + cluster: "provider1" + timeout { + seconds: 1 + nanos: -687866108 + } + } + } + } + } + rules { + match { + prefix: "" + } + requires { + allow_missing_or_failed { + } + } + } +} +request_data { + headers { + headers { + key: "authorization" + value: "Bearer eyJhbGciOiJSUzI1NiIsInR3cCI6IkpXVCJ9.eyJpc3MiOiJoZSIsImV4cCI6MjAwMTAwMTAwMX0.nexfP590ACPyXrivtsxg" + } + } +} +num_calls: 4 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 e7ab5407db95..ffd3a96ddacc 100644 --- a/test/extensions/filters/http/rate_limit_quota/integration_test.cc +++ b/test/extensions/filters/http/rate_limit_quota/integration_test.cc @@ -722,15 +722,21 @@ TEST_P(RateLimitQuotaIntegrationTest, DISABLED_MultiRequestWithTokenBucketThrott } } -TEST_P(RateLimitQuotaIntegrationTest, TwoRequestWithTokenBucketThrottling) { +TEST_P(RateLimitQuotaIntegrationTest, MultiRequestWithTokenBucket) { initializeConfig(); HttpIntegrationTest::initialize(); absl::flat_hash_map custom_headers = {{"environment", "staging"}, {"group", "envoy"}}; - int max_token = 0; - // First request: allowed; fail-open, default no assignment policy. - // Second request: rejected; no token remaining. - for (int i = 0; i < 2; ++i) { + int max_token = 1; + int tokens_per_fill = 30; + int fill_interval_sec = 60; + int fill_one_token_in_ms = fill_interval_sec / tokens_per_fill * 1000; + for (int i = 0; i < 10; ++i) { + // We advance time by 2s so that token bucket can be refilled. + if (i == 4 || i == 6) { + simTime().advanceTimeAndRun(std::chrono::milliseconds(fill_one_token_in_ms), *dispatcher_, + Envoy::Event::Dispatcher::RunType::NonBlock); + } // Send downstream client request to upstream. sendClientRequest(&custom_headers); @@ -765,25 +771,6 @@ TEST_P(RateLimitQuotaIntegrationTest, TwoRequestWithTokenBucketThrottling) { rlqs_stream_->sendGrpcMessage(rlqs_response); } - if (i == 1) { - ASSERT_TRUE(response_->waitForEndStream()); - EXPECT_TRUE(response_->complete()); - EXPECT_EQ(response_->headers().getStatusValue(), "429"); - } else { - // Handle the request received by upstream. - ASSERT_TRUE( - fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_)); - ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_)); - ASSERT_TRUE(upstream_request_->waitForEndStream(*dispatcher_)); - upstream_request_->encodeHeaders(Http::TestResponseHeaderMapImpl{{":status", "200"}}, false); - upstream_request_->encodeData(100, true); - - // Verify the response to downstream. - ASSERT_TRUE(response_->waitForEndStream()); - EXPECT_TRUE(response_->complete()); - EXPECT_EQ(response_->headers().getStatusValue(), "200"); - } - cleanUp(); } } diff --git a/test/extensions/filters/http/router/auto_sni_integration_test.cc b/test/extensions/filters/http/router/auto_sni_integration_test.cc index fe495f34e340..a14ecd6fa129 100644 --- a/test/extensions/filters/http/router/auto_sni_integration_test.cc +++ b/test/extensions/filters/http/router/auto_sni_integration_test.cc @@ -6,7 +6,7 @@ #include "envoy/upstream/upstream.h" #include "source/common/tls/context_config_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "test/integration/http_integration.h" diff --git a/test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc b/test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc index aa2279c70839..2a1a4e396d64 100644 --- a/test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc +++ b/test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc @@ -8,8 +8,8 @@ #include "source/common/config/api_version.h" #include "source/common/network/raw_buffer_socket.h" #include "source/common/network/utility.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_manager_impl.h" -#include "source/common/tls/ssl_socket.h" #include "source/extensions/filters/listener/tls_inspector/tls_inspector.h" #include "test/integration/integration.h" diff --git a/test/extensions/filters/network/mongo_proxy/config_test.cc b/test/extensions/filters/network/mongo_proxy/config_test.cc index 525f79940b26..7f8f1499c940 100644 --- a/test/extensions/filters/network/mongo_proxy/config_test.cc +++ b/test/extensions/filters/network/mongo_proxy/config_test.cc @@ -133,7 +133,7 @@ TEST(MongoFilterConfigTest, InvalidFaultsNegativeMs) { fixed_delay: -1s )EOF"; - handleInvalidConfiguration(yaml_string, "FixedDelay: value must be greater than 0s"); + handleInvalidConfiguration(yaml_string, "Invalid duration: Expected positive duration"); } TEST(MongoFilterConfigTest, InvalidFaultsDelayPercent) { diff --git a/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_integration_test.cc b/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_integration_test.cc index f801f805ab79..ec041d14c7e7 100644 --- a/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_integration_test.cc +++ b/test/extensions/filters/network/sni_dynamic_forward_proxy/proxy_filter_integration_test.cc @@ -3,8 +3,8 @@ #include "envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.pb.h" #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_config_impl.h" -#include "source/common/tls/ssl_socket.h" #include "test/integration/http_integration.h" #include "test/integration/ssl_utility.h" diff --git a/test/extensions/filters/network/zookeeper_proxy/config_test.cc b/test/extensions/filters/network/zookeeper_proxy/config_test.cc index c1f059a64b70..07799f663caf 100644 --- a/test/extensions/filters/network/zookeeper_proxy/config_test.cc +++ b/test/extensions/filters/network/zookeeper_proxy/config_test.cc @@ -104,12 +104,8 @@ stat_prefix: test_prefix nanos: -150000000 )EOF"; - EXPECT_THROW_WITH_REGEX( - TestUtility::loadFromYamlAndValidate(yaml, proto_config_), EnvoyException, - "Proto constraint validation failed " - "\\(ZooKeeperProxyValidationError.LatencyThresholdOverrides\\[0\\]: embedded message failed " - "validation \\| caused by LatencyThresholdOverrideValidationError.Threshold: value must be " - "greater than or equal to 1ms\\)"); + EXPECT_THROW_WITH_REGEX(TestUtility::loadFromYamlAndValidate(yaml, proto_config_), EnvoyException, + "Invalid duration: Expected positive duration"); } TEST_F(ZookeeperFilterConfigTest, TooSmallLatencyThreshold) { diff --git a/test/extensions/load_balancing_policies/common/BUILD b/test/extensions/load_balancing_policies/common/BUILD index 0d18bc3af79f..48678bf4f7a8 100644 --- a/test/extensions/load_balancing_policies/common/BUILD +++ b/test/extensions/load_balancing_policies/common/BUILD @@ -3,6 +3,7 @@ load( "envoy_cc_test", "envoy_cc_test_library", "envoy_package", + "envoy_proto_library", ) licenses(["notice"]) # Apache 2 @@ -13,6 +14,9 @@ envoy_cc_test_library( name = "benchmark_base_tester_lib", srcs = ["benchmark_base_tester.cc"], hdrs = ["benchmark_base_tester.h"], + external_deps = [ + "benchmark", + ], deps = [ "//source/common/common:random_generator_lib", "//source/common/memory:stats_lib", @@ -36,3 +40,87 @@ envoy_cc_test( "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", ], ) + +envoy_cc_test_library( + name = "load_balancer_fuzz_lib", + srcs = ["load_balancer_fuzz_base.cc"], + hdrs = ["load_balancer_fuzz_base.h"], + deps = [ + ":load_balancer_fuzz_proto_cc_proto", + "//source/common/upstream:load_balancer_context_base_lib", + "//test/common/upstream:utility_lib", + "//test/fuzz:random_lib", + "//test/mocks:common_lib", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:host_set_mocks", + "//test/mocks/upstream:load_balancer_context_mock", + "//test/mocks/upstream:priority_set_mocks", + "//test/test_common:simulated_time_system_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + ], +) + +envoy_proto_library( + name = "load_balancer_fuzz_proto", + srcs = ["load_balancer_fuzz.proto"], + deps = [ + "//test/fuzz:common_proto", + "@envoy_api//envoy/config/cluster/v3:pkg", + ], +) + +envoy_proto_library( + name = "zone_aware_load_balancer_fuzz_proto", + srcs = ["zone_aware_load_balancer_fuzz.proto"], + deps = [ + "//test/extensions/load_balancing_policies/common:load_balancer_fuzz_proto", + ], +) + +envoy_cc_test_library( + name = "zone_aware_load_balancer_fuzz_lib", + srcs = ["zone_aware_load_balancer_fuzz_base.cc"], + hdrs = ["zone_aware_load_balancer_fuzz_base.h"], + deps = [ + ":load_balancer_fuzz_lib", + ":zone_aware_load_balancer_fuzz_proto_cc_proto", + "//source/extensions/load_balancing_policies/common:load_balancer_lib", + "//test/mocks/upstream:host_set_mocks", + "//test/mocks/upstream:priority_set_mocks", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + ], +) + +envoy_cc_test_library( + name = "load_balancer_base_test_lib", + hdrs = ["load_balancer_impl_base_test.h"], + deps = [ + "//source/common/common:random_generator_lib", + "//source/common/network:utility_lib", + "//source/common/upstream:upstream_includes", + "//source/common/upstream:upstream_lib", + "//source/extensions/load_balancing_policies/common:load_balancer_lib", + "//test/common/upstream:utility_lib", + "//test/mocks:common_lib", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:host_mocks", + "//test/mocks/upstream:host_set_mocks", + "//test/mocks/upstream:load_balancer_context_mock", + "//test/mocks/upstream:priority_set_mocks", + "//test/test_common:logging_lib", + "//test/test_common:simulated_time_system_lib", + "//test/test_common:test_runtime_lib", + "@envoy_api//envoy/config/cluster/v3:pkg_cc_proto", + "@envoy_api//envoy/config/core/v3:pkg_cc_proto", + ], +) + +envoy_cc_test( + name = "load_balancer_base_test", + srcs = ["load_balancer_impl_base_test.cc"], + deps = [ + ":load_balancer_base_test_lib", + ], +) diff --git a/test/extensions/load_balancing_policies/common/benchmark_base_tester.cc b/test/extensions/load_balancing_policies/common/benchmark_base_tester.cc index 32a1521c73e4..cd4cc923b705 100644 --- a/test/extensions/load_balancing_policies/common/benchmark_base_tester.cc +++ b/test/extensions/load_balancing_policies/common/benchmark_base_tester.cc @@ -1,9 +1,7 @@ #include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" namespace Envoy { -namespace Extensions { -namespace LoadBalancingPolices { -namespace Common { +namespace Upstream { BaseTester::BaseTester(uint64_t num_hosts, uint32_t weighted_subset_percent, uint32_t weight, bool attach_metadata) { @@ -38,7 +36,5 @@ BaseTester::BaseTester(uint64_t num_hosts, uint32_t weighted_subset_percent, uin random_.random(), absl::nullopt); } -} // namespace Common -} // namespace LoadBalancingPolices -} // namespace Extensions +} // namespace Upstream } // namespace Envoy diff --git a/test/extensions/load_balancing_policies/common/benchmark_base_tester.h b/test/extensions/load_balancing_policies/common/benchmark_base_tester.h index 229f4832aec2..4d5c277d020b 100644 --- a/test/extensions/load_balancing_policies/common/benchmark_base_tester.h +++ b/test/extensions/load_balancing_policies/common/benchmark_base_tester.h @@ -13,11 +13,10 @@ #include "test/test_common/simulated_time_system.h" #include "absl/types/optional.h" +#include "benchmark/benchmark.h" namespace Envoy { -namespace Extensions { -namespace LoadBalancingPolices { -namespace Common { +namespace Upstream { class BaseTester : public Event::TestUsingSimulatedTime { public: @@ -49,7 +48,38 @@ class BaseTester : public Event::TestUsingSimulatedTime { std::shared_ptr info_{new NiceMock()}; }; -} // namespace Common -} // namespace LoadBalancingPolices -} // namespace Extensions +class TestLoadBalancerContext : public Upstream::LoadBalancerContextBase { +public: + // Upstream::LoadBalancerContext + absl::optional computeHashKey() override { return hash_key_; } + + absl::optional hash_key_; +}; + +inline void computeHitStats(::benchmark::State& state, + const absl::node_hash_map& hit_counter) { + double mean = 0; + for (const auto& pair : hit_counter) { + mean += pair.second; + } + mean /= hit_counter.size(); + + double variance = 0; + for (const auto& pair : hit_counter) { + variance += std::pow(pair.second - mean, 2); + } + variance /= hit_counter.size(); + const double stddev = std::sqrt(variance); + + state.counters["mean_hits"] = mean; + state.counters["stddev_hits"] = stddev; + state.counters["relative_stddev_hits"] = (stddev / mean); +} + +inline uint64_t hashInt(uint64_t i) { + // Hack to hash an integer. + return HashUtil::xxHash64(absl::string_view(reinterpret_cast(&i), sizeof(i))); +} + +} // namespace Upstream } // namespace Envoy diff --git a/test/common/upstream/load_balancer_fuzz.proto b/test/extensions/load_balancing_policies/common/load_balancer_fuzz.proto similarity index 100% rename from test/common/upstream/load_balancer_fuzz.proto rename to test/extensions/load_balancing_policies/common/load_balancer_fuzz.proto diff --git a/test/common/upstream/load_balancer_fuzz_base.cc b/test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.cc similarity index 99% rename from test/common/upstream/load_balancer_fuzz_base.cc rename to test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.cc index 5f29cc801c32..d808581a0127 100644 --- a/test/common/upstream/load_balancer_fuzz_base.cc +++ b/test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.cc @@ -1,4 +1,4 @@ -#include "test/common/upstream/load_balancer_fuzz_base.h" +#include "test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.h" #include "test/common/upstream/utility.h" diff --git a/test/common/upstream/load_balancer_fuzz_base.h b/test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.h similarity index 97% rename from test/common/upstream/load_balancer_fuzz_base.h rename to test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.h index d0d8d2595df3..b726d8748357 100644 --- a/test/common/upstream/load_balancer_fuzz_base.h +++ b/test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.h @@ -4,7 +4,7 @@ #include "source/common/upstream/load_balancer_context_base.h" -#include "test/common/upstream/load_balancer_fuzz.pb.validate.h" +#include "test/extensions/load_balancing_policies/common/load_balancer_fuzz.pb.validate.h" #include "test/fuzz/random.h" #include "test/mocks/common.h" #include "test/mocks/runtime/mocks.h" diff --git a/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.cc b/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.cc new file mode 100644 index 000000000000..de15d50286e4 --- /dev/null +++ b/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.cc @@ -0,0 +1,609 @@ +#include "test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Upstream { +namespace { + +using testing::ElementsAre; +using testing::Return; +using testing::ReturnRef; + +class TestLb : public LoadBalancerBase { +public: + TestLb(const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) + : LoadBalancerBase(priority_set, lb_stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( + common_config, healthy_panic_threshold, 100, 50)) {} + using LoadBalancerBase::chooseHostSet; + using LoadBalancerBase::isInPanic; + using LoadBalancerBase::percentageDegradedLoad; + using LoadBalancerBase::percentageLoad; + + HostConstSharedPtr chooseHost(LoadBalancerContext*) override { PANIC("not implemented"); } + + HostConstSharedPtr peekAnotherHost(LoadBalancerContext*) override { PANIC("not implemented"); } +}; + +class LoadBalancerBaseTest : public LoadBalancerTestBase { +public: + void updateHostSet(MockHostSet& host_set, uint32_t num_hosts, uint32_t num_healthy_hosts, + uint32_t num_degraded_hosts = 0, uint32_t num_excluded_hosts = 0) { + ASSERT(num_healthy_hosts + num_degraded_hosts + num_excluded_hosts <= num_hosts); + + host_set.hosts_.clear(); + host_set.healthy_hosts_.clear(); + host_set.degraded_hosts_.clear(); + host_set.excluded_hosts_.clear(); + for (uint32_t i = 0; i < num_hosts; ++i) { + host_set.hosts_.push_back(makeTestHost(info_, "tcp://127.0.0.1:80", simTime())); + } + uint32_t i = 0; + for (; i < num_healthy_hosts; ++i) { + host_set.healthy_hosts_.push_back(host_set.hosts_[i]); + } + for (; i < (num_healthy_hosts + num_degraded_hosts); ++i) { + host_set.degraded_hosts_.push_back(host_set.hosts_[i]); + } + + for (; i < (num_healthy_hosts + num_degraded_hosts + num_excluded_hosts); ++i) { + host_set.excluded_hosts_.push_back(host_set.hosts_[i]); + } + host_set.runCallbacks({}, {}); + } + + template + std::vector aggregatePrioritySetsValues(TestLb& lb, FUNC func) { + std::vector ret; + + for (size_t i = 0; i < priority_set_.host_sets_.size(); ++i) { + ret.push_back((lb.*func)(i)); + } + + return ret; + } + + std::vector getLoadPercentage() { + return aggregatePrioritySetsValues(lb_, &TestLb::percentageLoad); + } + + std::vector getDegradedLoadPercentage() { + return aggregatePrioritySetsValues(lb_, &TestLb::percentageDegradedLoad); + } + + std::vector getPanic() { + return aggregatePrioritySetsValues(lb_, &TestLb::isInPanic); + } + + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; + TestLb lb_{priority_set_, stats_, runtime_, random_, common_config_}; +}; + +INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, LoadBalancerBaseTest, + ::testing::Values(LoadBalancerTestParam{true, false}, + LoadBalancerTestParam{true, true})); + +// Basic test of host set selection. +TEST_P(LoadBalancerBaseTest, PrioritySelection) { + NiceMock context; + updateHostSet(host_set_, 1 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 1, 0); + + HealthyAndDegradedLoad priority_load{Upstream::HealthyLoad({100, 0, 0}), + Upstream::DegradedLoad({0, 0, 0})}; + EXPECT_CALL(context, determinePriorityLoad(_, _, _)).WillRepeatedly(ReturnRef(priority_load)); + // Primary and failover are in panic mode. Load distribution is based + // on the number of hosts regardless of their health. + EXPECT_EQ(50, lb_.percentageLoad(0)); + EXPECT_EQ(50, lb_.percentageLoad(1)); + EXPECT_EQ(&host_set_, &lb_.chooseHostSet(&context, 0).first); + + // Modify number of hosts in failover, but leave them in the unhealthy state + // primary and secondary are in panic mode, so load distribution is + // based on number of host regardless of their health. + updateHostSet(failover_host_set_, 2, 0); + EXPECT_EQ(34, lb_.percentageLoad(0)); + EXPECT_EQ(66, lb_.percentageLoad(1)); + EXPECT_EQ(&host_set_, &lb_.chooseHostSet(&context, 0).first); + + // Update the priority set with a new priority level P=2 and ensure the host + // is chosen + MockHostSet& tertiary_host_set_ = *priority_set_.getMockHostSet(2); + updateHostSet(tertiary_host_set_, 1 /* num_hosts */, 1 /* num_healthy_hosts */); + EXPECT_EQ(0, lb_.percentageLoad(0)); + EXPECT_EQ(0, lb_.percentageLoad(1)); + EXPECT_EQ(100, lb_.percentageLoad(2)); + priority_load.healthy_priority_load_ = HealthyLoad({0u, 0u, 100}); + EXPECT_EQ(&tertiary_host_set_, &lb_.chooseHostSet(&context, 0).first); + + // Now add a healthy host in P=0 and make sure it is immediately selected. + updateHostSet(host_set_, 1 /* num_hosts */, 1 /* num_healthy_hosts */); + host_set_.healthy_hosts_ = host_set_.hosts_; + host_set_.runCallbacks({}, {}); + EXPECT_EQ(100, lb_.percentageLoad(0)); + EXPECT_EQ(0, lb_.percentageLoad(2)); + priority_load.healthy_priority_load_ = HealthyLoad({100u, 0u, 0u}); + EXPECT_EQ(&host_set_, &lb_.chooseHostSet(&context, 0).first); + + // Remove the healthy host and ensure we fail back over to tertiary_host_set_ + updateHostSet(host_set_, 1 /* num_hosts */, 0 /* num_healthy_hosts */); + EXPECT_EQ(0, lb_.percentageLoad(0)); + EXPECT_EQ(100, lb_.percentageLoad(2)); + priority_load.healthy_priority_load_ = HealthyLoad({0u, 0u, 100}); + EXPECT_EQ(&tertiary_host_set_, &lb_.chooseHostSet(&context, 0).first); +} + +// Tests host selection with a randomized number of healthy, degraded and unhealthy hosts. +TEST_P(LoadBalancerBaseTest, PrioritySelectionFuzz) { + TestRandomGenerator rand; + + // Determine total number of hosts. + const auto total_hosts = 1 + (rand.random() % 10); + + NiceMock context; + + const auto host_set_hosts = rand.random() % total_hosts; + + if (host_set_hosts == 0) { + updateHostSet(host_set_, 0, 0); + } else { + // We get on average 50% healthy hosts, 25% degraded hosts and 25% unhealthy hosts. + const auto healthy_hosts = rand.random() % host_set_hosts; + const auto degraded_hosts = rand.random() % (host_set_hosts - healthy_hosts); + const auto unhealthy_hosts = host_set_hosts - healthy_hosts - degraded_hosts; + + updateHostSet(host_set_, host_set_hosts, unhealthy_hosts, degraded_hosts); + } + + const auto failover_set_hosts = total_hosts - host_set_hosts; + + if (host_set_hosts == 0) { + updateHostSet(failover_host_set_, 0, 0); + } else { + // We get on average 50% healthy hosts, 25% degraded hosts and 25% unhealthy hosts. + const auto healthy_hosts = rand.random() % failover_set_hosts; + const auto degraded_hosts = rand.random() % (failover_set_hosts - healthy_hosts); + const auto unhealthy_hosts = failover_set_hosts - healthy_hosts - degraded_hosts; + + updateHostSet(failover_host_set_, failover_set_hosts, unhealthy_hosts, degraded_hosts); + } + + EXPECT_CALL(context, determinePriorityLoad(_, _, _)) + .WillRepeatedly( + Invoke([](const auto&, const auto& original_load, + const auto&) -> const HealthyAndDegradedLoad& { return original_load; })); + + for (uint64_t i = 0; i < total_hosts; ++i) { + const auto hs = lb_.chooseHostSet(&context, 0); + switch (hs.second) { + case LoadBalancerBase::HostAvailability::Healthy: + // Either we selected one of the healthy hosts or we failed to select anything and + // defaulted to healthy. + EXPECT_TRUE(!hs.first.healthyHosts().empty() || + (hs.first.healthyHosts().empty() && hs.first.degradedHosts().empty())); + break; + case LoadBalancerBase::HostAvailability::Degraded: + EXPECT_FALSE(hs.first.degradedHosts().empty()); + break; + } + } +} + +// Test of host set selection with priority filter +TEST_P(LoadBalancerBaseTest, PrioritySelectionWithFilter) { + NiceMock context; + + HealthyAndDegradedLoad priority_load{Upstream::HealthyLoad({0u, 100u}), + Upstream::DegradedLoad({0, 0})}; + // return a filter that excludes priority 0 + EXPECT_CALL(context, determinePriorityLoad(_, _, _)).WillRepeatedly(ReturnRef(priority_load)); + + updateHostSet(host_set_, 1 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 1, 1); + + // Since we've excluded P0, we should pick the failover host set + EXPECT_EQ(failover_host_set_.priority(), lb_.chooseHostSet(&context, 0).first.priority()); + + updateHostSet(host_set_, 1 /* num_hosts */, 0 /* num_healthy_hosts */, + 1 /* num_degraded_hosts */); + updateHostSet(failover_host_set_, 1, 0, 1); + + // exclude priority 0 for degraded hosts + priority_load.healthy_priority_load_ = Upstream::HealthyLoad({0, 0}); + priority_load.degraded_priority_load_ = Upstream::DegradedLoad({0, 100}); + + // Since we've excluded P0, we should pick the failover host set + EXPECT_EQ(failover_host_set_.priority(), lb_.chooseHostSet(&context, 0).first.priority()); +} + +TEST_P(LoadBalancerBaseTest, OverProvisioningFactor) { + // Default overprovisioning factor 1.4 makes P0 receives 70% load. + updateHostSet(host_set_, 4, 2); + updateHostSet(failover_host_set_, 4, 2); + ASSERT_THAT(getLoadPercentage(), ElementsAre(70, 30)); + + // Set overprovisioning factor to 1, now it should be proportioned to healthy ratio. + host_set_.setOverprovisioningFactor(100); + updateHostSet(host_set_, 4, 2); + failover_host_set_.setOverprovisioningFactor(100); + updateHostSet(failover_host_set_, 4, 2); + ASSERT_THAT(getLoadPercentage(), ElementsAre(50, 50)); +} + +TEST_P(LoadBalancerBaseTest, WeightedPriorityHealth) { + host_set_.weighted_priority_health_ = true; + failover_host_set_.weighted_priority_health_ = true; + + // Makes math easier to read. + host_set_.setOverprovisioningFactor(100); + failover_host_set_.setOverprovisioningFactor(100); + + // Basic healthy/unhealthy test. + updateHostSet(host_set_, 4, 2, 0, 0); + updateHostSet(failover_host_set_, 1, 1); + + // Total weight is 10, healthy weight is 6. + host_set_.hosts_[0]->weight(3); // Healthy + host_set_.hosts_[1]->weight(3); // Healthy + host_set_.hosts_[2]->weight(2); // Unhealthy + host_set_.hosts_[3]->weight(2); // Unhealthy + host_set_.runCallbacks({}, {}); + ASSERT_THAT(getLoadPercentage(), ElementsAre(60, 40)); +} + +TEST_P(LoadBalancerBaseTest, WeightedPriorityHealthExcluded) { + host_set_.weighted_priority_health_ = true; + failover_host_set_.weighted_priority_health_ = true; + + // Makes math easier to read. + host_set_.setOverprovisioningFactor(100); + failover_host_set_.setOverprovisioningFactor(100); + + updateHostSet(failover_host_set_, 1, 1); + updateHostSet(host_set_, 3, 1, 0, 1); + host_set_.hosts_[0]->weight(4); // Healthy + host_set_.hosts_[1]->weight(10); // Excluded + host_set_.hosts_[2]->weight(6); // Unhealthy + host_set_.runCallbacks({}, {}); + ASSERT_THAT(getLoadPercentage(), ElementsAre(40, 60)); +} + +TEST_P(LoadBalancerBaseTest, WeightedPriorityHealthDegraded) { + host_set_.weighted_priority_health_ = true; + failover_host_set_.weighted_priority_health_ = true; + + // Makes math easier to read. + host_set_.setOverprovisioningFactor(100); + failover_host_set_.setOverprovisioningFactor(100); + + updateHostSet(host_set_, 4, 1, 1, 0); + host_set_.hosts_[0]->weight(4); // Healthy + host_set_.hosts_[1]->weight(3); // Degraded + host_set_.hosts_[2]->weight(2); // Unhealthy + host_set_.hosts_[3]->weight(1); // Unhealthy + host_set_.runCallbacks({}, {}); + + updateHostSet(failover_host_set_, 2, 1, 1); // 1 healthy host, 1 degraded. + failover_host_set_.hosts_[0]->weight(1); // Healthy + failover_host_set_.hosts_[1]->weight(9); // Degraded + failover_host_set_.runCallbacks({}, {}); + + // 40% for healthy priority 0, 10% for healthy priority 1, 30% for degraded priority zero, and the + // remaining 20% to degraded priority 1. + ASSERT_THAT(getLoadPercentage(), ElementsAre(40, 10)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(30, 20)); +} + +TEST_P(LoadBalancerBaseTest, GentleFailover) { + // With 100% of P=0 hosts healthy, P=0 gets all the load. + // None of the levels is in Panic mode + updateHostSet(host_set_, 1, 1); + updateHostSet(failover_host_set_, 1, 1); + ASSERT_THAT(getLoadPercentage(), ElementsAre(100, 0)); + ASSERT_THAT(getPanic(), ElementsAre(false, false)); + + // Health P=0 == 50*1.4 == 70 + // Total health = 70 + 70 >= 100%. None of the levels should be in panic mode. + updateHostSet(host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(70, 30)); + ASSERT_THAT(getPanic(), ElementsAre(false, false)); + + // Health P=0 == 25*1.4 == 35 P=1 is healthy so takes all spillover. + // Total health = 35+100 >= 100%. P=0 is below Panic level but it is ignored, because + // Total health >= 100%. + updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 2 /* num_hosts */, 2 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(35, 65)); + ASSERT_THAT(getPanic(), ElementsAre(false, false)); + + // Health P=0 == 25*1.4 == 35 P=1 == 35 + // Health is then scaled up by (100 / (35 + 35) == 50) + // Total health = 35% + 35% is less than 100%. Panic levels per priority kick in. + updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(50, 50)); + ASSERT_THAT(getPanic(), ElementsAre(true, true)); + + // Health P=0 == 100*1.4 == 35 P=1 == 35 + // Since 3 hosts are excluded, P=0 should be considered fully healthy. + // Total health = 100% + 35% is greater than 100%. Panic should not trigger. + updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */, 0 /* num_degraded_hosts + */ + , + 3 /* num_excluded_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(100, 0)); + ASSERT_THAT(getPanic(), ElementsAre(false, false)); + + // Health P=0 == 100*1.4 == 35 P=1 == 35 + // Total health = 35% is less than 100%. + // All priorities are in panic mode (situation called TotalPanic) + // Load is distributed based on number of hosts regardless of their health status. + // P=0 and P=1 have 4 hosts each so each priority will receive 50% of the traffic. + updateHostSet(host_set_, 4 /* num_hosts */, 0 /* num_healthy_hosts */, 0 /* num_degraded_hosts + */ + , + 4 /* num_excluded_hosts */); + updateHostSet(failover_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(50, 50)); + ASSERT_THAT(getPanic(), ElementsAre(true, true)); + + // Make sure that in TotalPanic mode (all levels are in Panic), + // load distribution depends only on number of hosts. + // excluded_hosts should not be taken into account. + // P=0 has 4 hosts with 1 excluded, P=1 has 6 hosts with 2 excluded. + // P=0 should receive 4/(4+6)=40% of traffic + // P=1 should receive 6/(4+6)=60% of traffic + updateHostSet(host_set_, 4 /* num_hosts */, 0 /* num_healthy_hosts */, 0 /* num_degraded_hosts + */ + , + 1 /* num_excluded_hosts */); + updateHostSet(failover_host_set_, 6 /* num_hosts */, 1 /* num_healthy_hosts */, + 0 /* num_degraded_hosts */, 2 /* num_excluded_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(40, 60)); + ASSERT_THAT(getPanic(), ElementsAre(true, true)); +} + +TEST_P(LoadBalancerBaseTest, GentleFailoverWithExtraLevels) { + // Add a third host set. Again with P=0 healthy, all traffic goes there. + MockHostSet& tertiary_host_set_ = *priority_set_.getMockHostSet(2); + updateHostSet(host_set_, 1, 1); + updateHostSet(failover_host_set_, 1, 1); + updateHostSet(tertiary_host_set_, 1, 1); + ASSERT_THAT(getLoadPercentage(), ElementsAre(100, 0, 0)); + ASSERT_THAT(getPanic(), ElementsAre(false, false, false)); + + // Health P=0 == 50*1.4 == 70 + // Health P=0 == 50, so can take the 30% spillover. + updateHostSet(host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(70, 30, 0)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + + // Health P=0 == 25*1.4 == 35 P=1 is healthy so takes all spillover. + updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 2 /* num_hosts */, 2 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(35, 65, 0)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + + // This is the first test where health (P=0 + P=1 < 100) + // Health P=0 == 25*1.4 == 35 P=1 == 35 P=2 == 35 + updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(35, 35, 30)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + + // This is the first test where (health P=0 + P=1 < 100) + // Health P=0 == 25*1.4 == 35 P=1 == 35 P=2 == 35 + updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(35, 35, 30)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + + // Now all health is (20% * 1.5 == 28). 28 * 3 < 100 so we have to scale. + // Each Priority level gets 33% of the load, with P=0 picking up the rounding error. + updateHostSet(host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); + + // Levels P=0 and P=1 are totally down. P=2 is totally healthy. + // 100% of the traffic should go to P=2 and P=0 and P=1 should + // not be in panic mode. + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 5 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 0, 100)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + ASSERT_THAT(getPanic(), ElementsAre(false, false, false)); + + // Levels P=0 and P=1 are totally down. P=2 is 80*1.4 >= 100% healthy. + // 100% of the traffic should go to P=2 and P=0 and P=1 should + // not be in panic mode. + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 4 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 0, 100)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + ASSERT_THAT(getPanic(), ElementsAre(false, false, false)); + + // Levels P=0 and P=1 are totally down. P=2 is 40*1.4=56%% healthy. + // 100% of the traffic should go to P=2. All levels P=0, P=1 and P=2 should + // be in panic mode. + // Since all levels are in panic mode load distribution is based + // on number of hosts in each level. + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 2 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); + + // Level P=0 is totally degraded. P=1 is 40*1.4=56% healthy and 40*1.4=56% degraded. P=2 is + // 40*1.4=56%% healthy. 100% of the traffic should go to P=2. No priorities should be in panic + // mode. + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, + 5 /* num_degraded_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 2 /* num_healthy_hosts */, + 2 /* num_degraded_hosts */); + updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 2 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 56, 44)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + ASSERT_THAT(getPanic(), ElementsAre(false, false, false)); + + // All levels are completely down - situation called TotalPanic. + // Load is distributed based on the number + // of hosts in the priority in relation to the total number of hosts. + // Here the total number of hosts is 10. + // priority 0 will receive 5/10: 50% of the traffic + // priority 1 will receive 3/10: 30% of the traffic + // priority 2 will receive 2/10: 20% of the traffic + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 2 /* num_hosts */, 0 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(50, 30, 20)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); + + // Rounding errors should be picked up by the first priority. + // All priorities are in panic mode - situation called TotalPanic. + // Load is distributed based on the number + // of hosts in the priority in relation to the total number of hosts. + // Total number of hosts is 5+6+3=14. + // priority 0 should receive 5/14=37% of traffic + // priority 1 should receive 6/14=42% of traffic + // priority 2 should receive 3/14=21% of traffic + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 6 /* num_hosts */, 2 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 3 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(37, 42, 21)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); + + // Load should spill over into degraded. + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, + 1 /* num_degraded_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, + 5 /* num_degraded_hosts */); + updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 0, 28)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(28, 44, 0)); + + // Rounding errors should be picked up by the first priority with degraded hosts when + // there are no healthy priorities. + // Disable panic threshold to prevent total panic from kicking in. + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(0)); + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, + 2 /* num_degraded_hosts */); + updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, + 1 /* num_degraded_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 0, 0)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 67, 33)); + + // Simulate Total Panic mode. There is no healthy hosts, but there are + // degraded hosts. Because there is Total Panic, load is distributed + // based just on number of hosts in priorities regardless of its health. + // Rounding errors should be picked up by the first priority. + // Enable back panic threshold. + EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) + .WillRepeatedly(Return(50)); + updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, + 2 /* num_degraded_hosts */); + updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, + 1 /* num_degraded_hosts */); + ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); + ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); + + // Rounding error should be allocated to the first non-empty priority + // In this test P=0 is not empty. + updateHostSet(host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); + ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); + ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); + + // Rounding error should be allocated to the first non-empty priority + // In this test P=0 is empty and P=1 is not empty. + updateHostSet(host_set_, 0 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 6 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); + ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); + ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 67, 33)); + // In this test P=1 is not empty. + updateHostSet(host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(failover_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); + updateHostSet(tertiary_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); + ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); + ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); +} + +TEST_P(LoadBalancerBaseTest, BoundaryConditions) { + TestRandomGenerator rand; + uint32_t num_priorities = rand.random() % 10; + + for (uint32_t i = 0; i < num_priorities; ++i) { + uint32_t num_hosts = rand.random() % 100; + uint32_t healthy_hosts = std::min(num_hosts, rand.random() % 100); + // Make sure random health situations don't trigger the assert in recalculatePerPriorityState + updateHostSet(*priority_set_.getMockHostSet(i), num_hosts, healthy_hosts); + } +} + +class TestZoneAwareLb : public ZoneAwareLoadBalancerBase { +public: + TestZoneAwareLb(const PrioritySet& priority_set, ClusterLbStats& lb_stats, + Runtime::Loader& runtime, Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) + : ZoneAwareLoadBalancerBase( + priority_set, nullptr, lb_stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, + 100, 50), + LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config)) {} + + HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { + return choose_host_once_host_; + } + HostConstSharedPtr peekAnotherHost(LoadBalancerContext*) override { PANIC("not implemented"); } + + HostConstSharedPtr choose_host_once_host_{std::make_shared>()}; +}; + +// Used to test common functions of ZoneAwareLoadBalancerBase. +class ZoneAwareLoadBalancerBaseTest : public LoadBalancerTestBase { +public: + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; + TestZoneAwareLb lb_{priority_set_, stats_, runtime_, random_, common_config_}; + TestZoneAwareLoadBalancer lbx_{priority_set_, stats_, runtime_, random_, common_config_}; +}; + +// Tests the source type static methods in zone aware load balancer. +TEST_F(ZoneAwareLoadBalancerBaseTest, SourceTypeMethods) { + { EXPECT_ENVOY_BUG(lbx_.runInvalidLocalitySourceType(), "unexpected locality source type enum"); } + + { EXPECT_ENVOY_BUG(lbx_.runInvalidSourceType(), "unexpected source type enum"); } +} + +TEST_F(ZoneAwareLoadBalancerBaseTest, BaseMethods) { + EXPECT_FALSE(lb_.lifetimeCallbacks().has_value()); + std::vector hash_key; + auto mock_host = std::make_shared>(); + EXPECT_FALSE(lb_.selectExistingConnection(nullptr, *mock_host, hash_key).has_value()); +} + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h b/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h new file mode 100644 index 000000000000..3252b4140056 --- /dev/null +++ b/test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "envoy/config/cluster/v3/cluster.pb.h" +#include "envoy/config/core/v3/base.pb.h" +#include "envoy/config/core/v3/health_check.pb.h" + +#include "source/common/common/random_generator.h" +#include "source/common/network/utility.h" +#include "source/common/upstream/load_balancer_context_base.h" +#include "source/common/upstream/upstream_impl.h" +#include "source/extensions/load_balancing_policies/common/load_balancer_impl.h" + +#include "test/common/upstream/utility.h" +#include "test/mocks/common.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/host.h" +#include "test/mocks/upstream/host_set.h" +#include "test/mocks/upstream/load_balancer_context.h" +#include "test/mocks/upstream/priority_set.h" +#include "test/test_common/logging.h" +#include "test/test_common/simulated_time_system.h" +#include "test/test_common/test_runtime.h" +#include "test/test_common/utility.h" + +namespace Envoy { +namespace Upstream { + +class EdfLoadBalancerBasePeer { +public: + static const std::chrono::milliseconds& slowStartWindow(EdfLoadBalancerBase& edf_lb) { + return edf_lb.slow_start_window_; + } + static const std::chrono::milliseconds latestHostAddedTime(EdfLoadBalancerBase& edf_lb) { + return std::chrono::time_point_cast(edf_lb.latest_host_added_time_) + .time_since_epoch(); + } + static double slowStartMinWeightPercent(const EdfLoadBalancerBase& edf_lb) { + return edf_lb.slow_start_min_weight_percent_; + } +}; + +class TestZoneAwareLoadBalancer : public ZoneAwareLoadBalancerBase { +public: + TestZoneAwareLoadBalancer( + const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, + Random::RandomGenerator& random, + const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) + : ZoneAwareLoadBalancerBase( + priority_set, nullptr, lb_stats, runtime, random, + PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, + 100, 50), + LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config)) {} + void runInvalidLocalitySourceType() { + localitySourceType(static_cast(123)); + } + void runInvalidSourceType() { sourceType(static_cast(123)); } + HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { PANIC("not implemented"); } + HostConstSharedPtr peekAnotherHost(LoadBalancerContext*) override { PANIC("not implemented"); } +}; + +struct LoadBalancerTestParam { + bool use_default_host_set; + bool use_new_locality_routing; +}; + +class LoadBalancerTestBase : public Event::TestUsingSimulatedTime, + public testing::TestWithParam { +protected: + // Run all tests against both priority 0 and priority 1 host sets, to ensure + // all the load balancers have equivalent functionality for failover host sets. + MockHostSet& hostSet() { + return GetParam().use_default_host_set ? host_set_ : failover_host_set_; + } + + LoadBalancerTestBase() + : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, *stats_store_.rootScope()) { + least_request_lb_config_.mutable_choice_count()->set_value(2); + } + + Stats::IsolatedStoreImpl stats_store_; + ClusterLbStatNames stat_names_; + ClusterLbStats stats_; + NiceMock runtime_; + NiceMock random_; + NiceMock priority_set_; + MockHostSet& host_set_ = *priority_set_.getMockHostSet(0); + MockHostSet& failover_host_set_ = *priority_set_.getMockHostSet(1); + std::shared_ptr info_{new NiceMock()}; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig least_request_lb_config_; + envoy::config::cluster::v3::Cluster::RoundRobinLbConfig round_robin_lb_config_; +}; + +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz.proto b/test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz.proto similarity index 88% rename from test/common/upstream/zone_aware_load_balancer_fuzz.proto rename to test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz.proto index fa08df063c2e..37209c6f5152 100644 --- a/test/common/upstream/zone_aware_load_balancer_fuzz.proto +++ b/test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz.proto @@ -3,7 +3,7 @@ syntax = "proto3"; package test.common.upstream; import "validate/validate.proto"; -import "test/common/upstream/load_balancer_fuzz.proto"; +import "test/extensions/load_balancing_policies/common/load_balancer_fuzz.proto"; message ZoneAwareLoadBalancerTestCase { test.common.upstream.LoadBalancerTestCase load_balancer_test_case = 1 diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc b/test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.cc similarity index 98% rename from test/common/upstream/zone_aware_load_balancer_fuzz_base.cc rename to test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.cc index 66b73e2a6519..4c41be11b3db 100644 --- a/test/common/upstream/zone_aware_load_balancer_fuzz_base.cc +++ b/test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.cc @@ -1,4 +1,4 @@ -#include "zone_aware_load_balancer_fuzz_base.h" +#include "test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.h" #include "test/mocks/upstream/host_set.h" diff --git a/test/common/upstream/zone_aware_load_balancer_fuzz_base.h b/test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.h similarity index 97% rename from test/common/upstream/zone_aware_load_balancer_fuzz_base.h rename to test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.h index 690ce40762fe..140b76ea0e13 100644 --- a/test/common/upstream/zone_aware_load_balancer_fuzz_base.h +++ b/test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.h @@ -2,11 +2,10 @@ #include "envoy/config/cluster/v3/cluster.pb.h" +#include "test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.h" #include "test/mocks/upstream/priority_set.h" #include "test/test_common/simulated_time_system.h" -#include "load_balancer_fuzz_base.h" - namespace Envoy { namespace Upstream { class ZoneAwareLoadBalancerFuzzBase : public Event::TestUsingSimulatedTime, diff --git a/test/extensions/load_balancing_policies/least_request/BUILD b/test/extensions/load_balancing_policies/least_request/BUILD index 9dcb0e83fbcb..923b383b6940 100644 --- a/test/extensions/load_balancing_policies/least_request/BUILD +++ b/test/extensions/load_balancing_policies/least_request/BUILD @@ -1,6 +1,10 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_benchmark_test", + "envoy_cc_benchmark_binary", + "envoy_cc_fuzz_test", "envoy_package", + "envoy_proto_library", ) load( "//test/extensions:extensions_build_system.bzl", @@ -37,3 +41,65 @@ envoy_extension_cc_test( "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", ], ) + +envoy_proto_library( + name = "least_request_load_balancer_fuzz_proto", + srcs = ["least_request_load_balancer_fuzz.proto"], + deps = [ + "//test/extensions/load_balancing_policies/common:zone_aware_load_balancer_fuzz_proto", + "@envoy_api//envoy/config/cluster/v3:pkg", + ], +) + +envoy_cc_fuzz_test( + name = "least_request_load_balancer_fuzz_test", + srcs = ["least_request_load_balancer_fuzz_test.cc"], + corpus = "least_request_load_balancer_corpus", + deps = [ + ":least_request_load_balancer_fuzz_proto_cc_proto", + "//source/extensions/load_balancing_policies/least_request:config", + "//test/common/upstream:utility_lib", + "//test/extensions/load_balancing_policies/common:zone_aware_load_balancer_fuzz_lib", + ], +) + +envoy_cc_benchmark_binary( + name = "least_request_lb_benchmark", + srcs = ["least_request_lb_benchmark.cc"], + deps = [ + "//source/extensions/load_balancing_policies/least_request:least_request_lb_lib", + "//test/extensions/load_balancing_policies/common:benchmark_base_tester_lib", + ], +) + +envoy_benchmark_test( + name = "least_request_lb_benchmark_test", + timeout = "long", + benchmark_binary = "least_request_lb_benchmark", +) + +envoy_extension_cc_test( + name = "least_request_lb_test", + srcs = ["least_request_lb_test.cc"], + extension_names = ["envoy.load_balancing_policies.least_request"], + deps = [ + "//source/extensions/load_balancing_policies/least_request:least_request_lb_lib", + "//test/extensions/load_balancing_policies/common:load_balancer_base_test_lib", + ], +) + +envoy_extension_cc_test( + name = "least_request_lb_simulation_test", + srcs = ["least_request_lb_simulation_test.cc"], + extension_names = ["envoy.load_balancing_policies.least_request"], + deps = [ + "//source/common/common:random_generator_lib", + "//source/common/upstream:load_balancer_context_base_lib", + "//source/extensions/load_balancing_policies/least_request:least_request_lb_lib", + "//test/common/upstream:utility_lib", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:host_set_mocks", + "//test/mocks/upstream:priority_set_mocks", + ], +) diff --git a/test/extensions/load_balancing_policies/least_request/least_request_lb_benchmark.cc b/test/extensions/load_balancing_policies/least_request/least_request_lb_benchmark.cc new file mode 100644 index 000000000000..15234b0a4a03 --- /dev/null +++ b/test/extensions/load_balancing_policies/least_request/least_request_lb_benchmark.cc @@ -0,0 +1,67 @@ +#include "source/extensions/load_balancing_policies/least_request/least_request_lb.h" + +#include "test/benchmark/main.h" +#include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" + +namespace Envoy { +namespace Upstream { +namespace { + +class LeastRequestTester : public BaseTester { +public: + LeastRequestTester(uint64_t num_hosts, uint32_t choice_count) : BaseTester(num_hosts) { + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; + lr_lb_config.mutable_choice_count()->set_value(choice_count); + lb_ = std::make_unique(priority_set_, &local_priority_set_, stats_, + runtime_, random_, common_config_, + lr_lb_config, simTime()); + } + + std::unique_ptr lb_; +}; + +void benchmarkLeastRequestLoadBalancerChooseHost(::benchmark::State& state) { + const uint64_t num_hosts = state.range(0); + const uint64_t choice_count = state.range(1); + const uint64_t keys_to_simulate = state.range(2); + + if (benchmark::skipExpensiveBenchmarks() && keys_to_simulate > 1000) { + state.SkipWithError("Skipping expensive benchmark"); + return; + } + + for (auto _ : state) { // NOLINT: Silences warning about dead store + state.PauseTiming(); + LeastRequestTester tester(num_hosts, choice_count); + absl::node_hash_map hit_counter; + TestLoadBalancerContext context; + state.ResumeTiming(); + + for (uint64_t i = 0; i < keys_to_simulate; ++i) { + hit_counter[tester.lb_->chooseHost(&context)->address()->asString()] += 1; + } + + // Do not time computation of mean, standard deviation, and relative standard deviation. + state.PauseTiming(); + computeHitStats(state, hit_counter); + state.ResumeTiming(); + } +} +BENCHMARK(benchmarkLeastRequestLoadBalancerChooseHost) + ->Args({100, 1, 1000}) + ->Args({100, 2, 1000}) + ->Args({100, 3, 1000}) + ->Args({100, 10, 1000}) + ->Args({100, 50, 1000}) + ->Args({100, 100, 1000}) + ->Args({100, 1, 1000000}) + ->Args({100, 2, 1000000}) + ->Args({100, 3, 1000000}) + ->Args({100, 10, 1000000}) + ->Args({100, 50, 1000000}) + ->Args({100, 100, 1000000}) + ->Unit(::benchmark::kMillisecond); + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/least_request/least_request_lb_simulation_test.cc b/test/extensions/load_balancing_policies/least_request/least_request_lb_simulation_test.cc new file mode 100644 index 000000000000..06d20b31f2ed --- /dev/null +++ b/test/extensions/load_balancing_policies/least_request/least_request_lb_simulation_test.cc @@ -0,0 +1,169 @@ +#include "source/common/common/random_generator.h" +#include "source/extensions/load_balancing_policies/least_request/least_request_lb.h" + +#include "test/common/upstream/utility.h" +#include "test/mocks/common.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/host_set.h" +#include "test/mocks/upstream/priority_set.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; + +namespace Envoy { +namespace Upstream { +namespace { + +// Defines parameters for LeastRequestLoadBalancerWeightTest cases. +struct LRLBTestParams { + // The total number of hosts. + uint64_t num_hosts; + + // Number of hosts that are part of the subset. + uint64_t num_subset_hosts; + + // The weight assigned to each subset host. + uint64_t weight; + + // The number of active requests each subset host will be loaded with. + uint64_t active_request_count; + + // An unordered collection of expected selection probabilities for the hosts. The test will simply + // sort the expected and observed selection probabilities and verify each element is within some + // expected tolerance. Therefore, the vector does not need to be sorted. + std::vector expected_selection_probs; +}; + +void leastRequestLBWeightTest(LRLBTestParams params) { + constexpr uint64_t num_requests = 100000; + + // Observed selection probabilities must be within tolerance_pct of the expected to pass the test. + // The expected range is [0,100). + constexpr double tolerance_pct = 1.0; + + // Validate params. + ASSERT_GT(params.num_hosts, 0); + ASSERT_LE(params.num_subset_hosts, params.num_hosts); + ASSERT_GT(params.weight, 0); + ASSERT_EQ(params.expected_selection_probs.size(), params.num_hosts); + ASSERT_LT(tolerance_pct, 100); + ASSERT_GE(tolerance_pct, 0); + + NiceMock time_source_; + HostVector hosts; + absl::node_hash_map host_hits; + std::shared_ptr info{new NiceMock()}; + for (uint64_t i = 0; i < params.num_hosts; i++) { + const bool should_weight = i < params.num_subset_hosts; + auto hostPtr = makeTestHost(info, fmt::format("tcp://10.0.{}.{}:6379", i / 256, i % 256), + time_source_, should_weight ? params.weight : 1); + host_hits[hostPtr] = 0; + hosts.push_back(hostPtr); + if (should_weight) { + hosts.back()->stats().rq_active_.set(params.active_request_count); + } + } + HostVectorConstSharedPtr updated_hosts{new HostVector(hosts)}; + HostsPerLocalitySharedPtr updated_locality_hosts{new HostsPerLocalityImpl(hosts)}; + Random::RandomGeneratorImpl random; + PrioritySetImpl priority_set; + priority_set.updateHosts( + 0, + updateHostsParams(updated_hosts, updated_locality_hosts, + std::make_shared(*updated_hosts), + updated_locality_hosts), + {}, hosts, {}, random.random(), absl::nullopt); + + Stats::IsolatedStoreImpl stats_store; + ClusterLbStatNames stat_names(stats_store.symbolTable()); + ClusterLbStats lb_stats{stat_names, *stats_store.rootScope()}; + NiceMock runtime; + auto time_source = std::make_unique>(); + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig least_request_lb_config; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config; + LeastRequestLoadBalancer lb_{ + priority_set, nullptr, lb_stats, runtime, random, common_config, least_request_lb_config, + *time_source}; + + for (uint64_t i = 0; i < num_requests; i++) { + host_hits[lb_.chooseHost(nullptr)]++; + } + + std::vector observed_pcts; + for (const auto& host : host_hits) { + observed_pcts.push_back((static_cast(host.second) / num_requests) * 100); + } + + std::sort(observed_pcts.begin(), observed_pcts.end()); + std::sort(params.expected_selection_probs.begin(), params.expected_selection_probs.end()); + ASSERT_EQ(observed_pcts.size(), params.expected_selection_probs.size()); + for (uint64_t i = 0; i < observed_pcts.size(); i++) { + EXPECT_NEAR(params.expected_selection_probs[i], observed_pcts[i], tolerance_pct); + } +} + +// Simulate weighted LR load balancer and verify expected selection probabilities. +TEST(LeastRequestLoadBalancerWeightTest, Weight) { + LRLBTestParams params; + + // No active requests or weight differences. This should look like uniform random LB. + params.num_hosts = 3; + params.num_subset_hosts = 1; + params.active_request_count = 0; + params.expected_selection_probs = {33.333, 33.333, 33.333}; + params.weight = 1; + leastRequestLBWeightTest(params); + + // Single host (out of 3) with lots of in-flight requests. Given that P2C will choose 2 hosts and + // take the one with higher weight, the only circumstance that the host with many in-flight + // requests will be picked is if P2C selects it twice. + params.num_hosts = 3; + params.num_subset_hosts = 1; + params.active_request_count = 10; + params.expected_selection_probs = {44.45, 44.45, 11.1}; + params.weight = 1; + leastRequestLBWeightTest(params); + + // Same as above, but with 2 hosts. The busy host will only be chosen if P2C picks it for both + // selections. + params.num_hosts = 2; + params.num_subset_hosts = 1; + params.active_request_count = 10; + params.expected_selection_probs = {25, 75}; + params.weight = 1; + leastRequestLBWeightTest(params); + + // Heterogeneous weights with no active requests. This should behave identically to weighted + // round-robin. + params.num_hosts = 2; + params.num_subset_hosts = 1; + params.active_request_count = 0; + params.expected_selection_probs = {66.66, 33.33}; + params.weight = 2; + leastRequestLBWeightTest(params); + + // Same as above, but we'll scale the subset's weight with active requests. With a default + // active_request_bias of 1.0, the subset host with a single active request will be cut in half, + // making both hosts have an identical weight. + params.num_hosts = 2; + params.num_subset_hosts = 1; + params.active_request_count = 1; + params.expected_selection_probs = {50, 50}; + params.weight = 2; + leastRequestLBWeightTest(params); + + // Same as above, but with 3 hosts. + params.num_hosts = 3; + params.num_subset_hosts = 1; + params.active_request_count = 1; + params.expected_selection_probs = {33.3, 33.3, 33.3}; + params.weight = 2; + leastRequestLBWeightTest(params); +} + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/least_request/least_request_lb_test.cc b/test/extensions/load_balancing_policies/least_request/least_request_lb_test.cc new file mode 100644 index 000000000000..d5d3e11f2fc2 --- /dev/null +++ b/test/extensions/load_balancing_policies/least_request/least_request_lb_test.cc @@ -0,0 +1,570 @@ +#include "source/extensions/load_balancing_policies/least_request/least_request_lb.h" + +#include "test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Upstream { +namespace { + +using testing::NiceMock; +using testing::Return; + +class LeastRequestLoadBalancerTest : public LoadBalancerTestBase { +public: + LeastRequestLoadBalancer lb_{ + priority_set_, nullptr, stats_, runtime_, random_, common_config_, least_request_lb_config_, + simTime()}; +}; + +TEST_P(LeastRequestLoadBalancerTest, NoHosts) { EXPECT_EQ(nullptr, lb_.chooseHost(nullptr)); } + +TEST_P(LeastRequestLoadBalancerTest, SingleHostAndPeek) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + // Host weight is 1 and peek. + { + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(nullptr, lb_.peekAnotherHost(nullptr)); + } +} + +TEST_P(LeastRequestLoadBalancerTest, SingleHost) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + // Host weight is 1. + { + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + } + + // Host weight is 100. + { + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + } + + HostVector empty; + { + hostSet().runCallbacks(empty, empty); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + } + + { + HostVector remove_hosts; + remove_hosts.push_back(hostSet().hosts_[0]); + hostSet().healthy_hosts_.clear(); + hostSet().hosts_.clear(); + hostSet().runCallbacks(empty, remove_hosts); + EXPECT_CALL(random_, random()).WillOnce(Return(0)); + EXPECT_EQ(nullptr, lb_.chooseHost(nullptr)); + } +} + +TEST_P(LeastRequestLoadBalancerTest, Normal) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(2); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + + hostSet().healthy_hosts_[0]->stats().rq_active_.set(2); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); + EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); +} + +TEST_P(LeastRequestLoadBalancerTest, PNC) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime())}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + hostSet().healthy_hosts_[0]->stats().rq_active_.set(4); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(3); + hostSet().healthy_hosts_[2]->stats().rq_active_.set(2); + hostSet().healthy_hosts_[3]->stats().rq_active_.set(1); + + // Creating various load balancer objects with different choice configs. + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; + lr_lb_config.mutable_choice_count()->set_value(2); + LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, + random_, common_config_, lr_lb_config, simTime()}; + lr_lb_config.mutable_choice_count()->set_value(5); + LeastRequestLoadBalancer lb_5{priority_set_, nullptr, stats_, runtime_, + random_, common_config_, lr_lb_config, simTime()}; + + // Verify correct number of choices. + + // 0 choices configured should default to P2C. + EXPECT_CALL(random_, random()).Times(3).WillRepeatedly(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + + // 2 choices configured results in P2C. + EXPECT_CALL(random_, random()).Times(3).WillRepeatedly(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + + // 5 choices configured results in P5C. + EXPECT_CALL(random_, random()).Times(6).WillRepeatedly(Return(0)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_5.chooseHost(nullptr)); + + // Verify correct host chosen in P5C scenario. + EXPECT_CALL(random_, random()) + .Times(6) + .WillOnce(Return(0)) + .WillOnce(Return(3)) + .WillOnce(Return(0)) + .WillOnce(Return(3)) + .WillOnce(Return(2)) + .WillOnce(Return(1)); + EXPECT_EQ(hostSet().healthy_hosts_[3], lb_5.chooseHost(nullptr)); +} + +TEST_P(LeastRequestLoadBalancerTest, DefaultSelectionMethod) { + envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest lr_lb_config; + EXPECT_EQ(lr_lb_config.selection_method(), + envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest::N_CHOICES); +} + +TEST_P(LeastRequestLoadBalancerTest, FullScanOneHostWithLeastRequests) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime())}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + hostSet().healthy_hosts_[0]->stats().rq_active_.set(4); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(3); + hostSet().healthy_hosts_[2]->stats().rq_active_.set(2); + hostSet().healthy_hosts_[3]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[4]->stats().rq_active_.set(5); + + envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest lr_lb_config; + + // Enable FULL_SCAN on hosts. + lr_lb_config.set_selection_method( + envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest::FULL_SCAN); + + LeastRequestLoadBalancer lb{priority_set_, nullptr, stats_, runtime_, + random_, 1, lr_lb_config, simTime()}; + + // With FULL_SCAN we will always choose the host with least number of active requests. + EXPECT_EQ(hostSet().healthy_hosts_[3], lb.chooseHost(nullptr)); +} + +TEST_P(LeastRequestLoadBalancerTest, FullScanMultipleHostsWithLeastRequests) { + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:83", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:84", simTime())}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + hostSet().healthy_hosts_[0]->stats().rq_active_.set(3); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(3); + hostSet().healthy_hosts_[2]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[3]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[4]->stats().rq_active_.set(1); + + envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest lr_lb_config; + + // Enable FULL_SCAN on hosts. + lr_lb_config.set_selection_method( + envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest::FULL_SCAN); + + auto random = Random::RandomGeneratorImpl(); + + LeastRequestLoadBalancer lb{priority_set_, nullptr, stats_, runtime_, + random, 1, lr_lb_config, simTime()}; + + // Make 1 million selections. Then, check that the selection probability is + // approximately equal among the 3 hosts tied for least requests. + // Accept a +/-0.5% deviation from the expected selection probability (33.3..%). + size_t num_selections = 1000000; + size_t expected_approx_selections_per_tied_host = num_selections / 3; + size_t abs_error = 5000; + + size_t host_2_counts = 0; + size_t host_3_counts = 0; + size_t host_4_counts = 0; + + for (size_t i = 0; i < num_selections; ++i) { + auto selected_host = lb.chooseHost(nullptr); + + if (selected_host == hostSet().healthy_hosts_[2]) { + ++host_2_counts; + } else if (selected_host == hostSet().healthy_hosts_[3]) { + ++host_3_counts; + } else if (selected_host == hostSet().healthy_hosts_[4]) { + ++host_4_counts; + } else { + FAIL() << "Must only select hosts with least requests"; + } + } + + EXPECT_NEAR(expected_approx_selections_per_tied_host, host_2_counts, abs_error); + EXPECT_NEAR(expected_approx_selections_per_tied_host, host_3_counts, abs_error); + EXPECT_NEAR(expected_approx_selections_per_tied_host, host_4_counts, abs_error); +} + +TEST_P(LeastRequestLoadBalancerTest, WeightImbalance) { + 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_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); + + // We should see 2:1 ratio for hosts[1] to hosts[0]. + 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)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); + + // Bringing hosts[1] to an active request should yield a 1:1 ratio. + hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); + 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_[0], lb_.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); + + // Settings hosts[0] to an active request and hosts[1] to no active requests should yield a 4:1 + // ratio. + hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(0); + 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_[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)); + 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)); +} + +// Validate that the load balancer defaults to an active request bias value of 1.0 if the runtime +// value is invalid (less than 0.0). +TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceWithInvalidActiveRequestBias) { + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; + lr_lb_config.mutable_active_request_bias()->set_runtime_key("ar_bias"); + lr_lb_config.mutable_active_request_bias()->set_default_value(1.0); + LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, + random_, common_config_, lr_lb_config, simTime()}; + + EXPECT_CALL(runtime_.snapshot_, getDouble("ar_bias", 1.0)).WillRepeatedly(Return(-1.0)); + + 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_; + + // Trigger callbacks. The added/removed lists are not relevant. + EXPECT_LOG_CONTAINS( + "warn", "upstream: invalid active request bias supplied (runtime key ar_bias), using 1.0", + hostSet().runCallbacks({}, {})); + + EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); + + // We should see 2:1 ratio for hosts[1] to hosts[0]. + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + + // Bringing hosts[1] to an active request should yield a 1:1 ratio. + hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + + // Settings hosts[0] to an active request and hosts[1] to no active requests should yield a 4:1 + // ratio. + hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(0); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); +} + +TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceWithCustomActiveRequestBias) { + // Create a load balancer with a custom active request bias. + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; + lr_lb_config.mutable_active_request_bias()->set_runtime_key("ar_bias"); + lr_lb_config.mutable_active_request_bias()->set_default_value(1.0); + LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, + random_, common_config_, lr_lb_config, simTime()}; + + EXPECT_CALL(runtime_.snapshot_, getDouble("ar_bias", 1.0)).WillRepeatedly(Return(0.0)); + + 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_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); + + // We should see 2:1 ratio for hosts[1] to hosts[0], regardless of the active request count. + hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); +} + +TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceCallbacks) { + 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_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); + + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); + + // Remove and verify we get other host. + HostVector empty; + HostVector hosts_removed; + hosts_removed.push_back(hostSet().hosts_[1]); + hostSet().hosts_.erase(hostSet().hosts_.begin() + 1); + hostSet().healthy_hosts_.erase(hostSet().healthy_hosts_.begin() + 1); + hostSet().runCallbacks(empty, hosts_removed); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); +} + +TEST_P(LeastRequestLoadBalancerTest, SlowStartWithDefaultParams) { + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; + LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, + random_, common_config_, lr_lb_config, simTime()}; + const auto slow_start_window = + EdfLoadBalancerBasePeer::slowStartWindow(static_cast(lb_2)); + EXPECT_EQ(std::chrono::milliseconds(0), slow_start_window); + const auto latest_host_added_time = + EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); + EXPECT_EQ(std::chrono::milliseconds(0), latest_host_added_time); + const auto slow_start_min_weight_percent = + EdfLoadBalancerBasePeer::slowStartMinWeightPercent(static_cast(lb_2)); + EXPECT_DOUBLE_EQ(slow_start_min_weight_percent, 0.1); +} + +TEST_P(LeastRequestLoadBalancerTest, SlowStartNoWait) { + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; + lr_lb_config.mutable_slow_start_config()->mutable_slow_start_window()->set_seconds(60); + lr_lb_config.mutable_active_request_bias()->set_runtime_key("ar_bias"); + lr_lb_config.mutable_active_request_bias()->set_default_value(1.0); + LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, + random_, common_config_, lr_lb_config, simTime()}; + simTime().advanceTimeWait(std::chrono::seconds(1)); + + // As no healthcheck is configured, hosts would enter slow start immediately. + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); + // Host1 is 5 secs in slow start, its weight is scaled with max((5/60)^1, 0.1)=0.1 factor. + simTime().advanceTimeWait(std::chrono::seconds(5)); + + auto latest_host_added_time = + EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); + EXPECT_EQ(std::chrono::milliseconds(1000), latest_host_added_time); + + // Advance time, so that host is no longer in slow start. + simTime().advanceTimeWait(std::chrono::seconds(56)); + + auto host2 = makeTestHost(info_, "tcp://127.0.0.1:90", simTime()); + hostSet().healthy_hosts_.push_back(host2); + hostSet().hosts_ = hostSet().healthy_hosts_; + HostVector hosts_added; + hosts_added.push_back(host2); + + hostSet().runCallbacks(hosts_added, {}); + + latest_host_added_time = + EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); + EXPECT_EQ(std::chrono::milliseconds(62000), latest_host_added_time); + + // host2 is 10 secs in slow start, the weight is scaled with time factor max(10/60, 0.1) = 0.16. + simTime().advanceTimeWait(std::chrono::seconds(10)); + + // Recalculate weights. + hostSet().runCallbacks({}, {}); + + hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(0); + // We expect 3:1 ratio, as host2 is in slow start mode and it's weight is scaled with + // 0.16 factor and host1 weight with 0.5 factor (due to active request bias). + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + + // host2 is 40 secs in slow start, the weight is scaled with time factor max(40/60, 0.1) = 0.66. + simTime().advanceTimeWait(std::chrono::seconds(30)); + // Recalculate weights. + hostSet().runCallbacks({}, {}); + + // We expect 4:3 ratio, as host2 is in slow start mode and it's weight is scaled with + // 0.66 factor and host1 weight with 0.5 factor. + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); +} + +TEST_P(LeastRequestLoadBalancerTest, SlowStartWithActiveHC) { + envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; + lr_lb_config.mutable_slow_start_config()->mutable_slow_start_window()->set_seconds(10); + lr_lb_config.mutable_slow_start_config()->mutable_aggression()->set_runtime_key("aggression"); + lr_lb_config.mutable_slow_start_config()->mutable_aggression()->set_default_value(0.9); + lr_lb_config.mutable_active_request_bias()->set_runtime_key("ar_bias"); + lr_lb_config.mutable_active_request_bias()->set_default_value(0.9); + + LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, + random_, common_config_, lr_lb_config, simTime()}; + + simTime().advanceTimeWait(std::chrono::seconds(1)); + auto host1 = makeTestHost(info_, "tcp://127.0.0.1:80", simTime()); + host1->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); + host_set_.hosts_ = {host1}; + HostVector hosts_added; + hosts_added.push_back(host1); + simTime().advanceTimeWait(std::chrono::seconds(1)); + hostSet().runCallbacks(hosts_added, {}); + auto latest_host_added_time = + EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); + EXPECT_EQ(std::chrono::milliseconds(0), latest_host_added_time); + simTime().advanceTimeWait(std::chrono::seconds(5)); + + hosts_added.clear(); + auto host2 = makeTestHost(info_, "tcp://127.0.0.1:90", simTime()); + hosts_added.push_back(host2); + + hostSet().healthy_hosts_ = {host1, host2}; + hostSet().hosts_ = hostSet().healthyHosts(); + hostSet().runCallbacks(hosts_added, {}); + latest_host_added_time = + EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); + EXPECT_EQ(std::chrono::milliseconds(7000), latest_host_added_time); + + simTime().advanceTimeWait(std::chrono::seconds(1)); + host1->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); + host1->setLastHcPassTime(simTime().monotonicTime()); + hostSet().healthy_hosts_ = {host1, host2}; + + hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(0); + hostSet().hosts_ = hostSet().healthyHosts(); + simTime().advanceTimeWait(std::chrono::seconds(7)); + // Trigger callbacks to add host1 to slow start mode. + hostSet().runCallbacks({}, {}); + // We expect 9:4 ratio, as host1 is 7 sec in slow start mode, its weight is scaled with active + // request bias and time factor 0.53 * max(pow(0.7, 1.11), 0.1)=0.35. Host2 is 8 seconds in slow + // start and its weight is scaled with time bias max(pow(0.7, 1.11), 0.1) = 0.78. + + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + + simTime().advanceTimeWait(std::chrono::seconds(1)); + host2->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); + // Trigger callbacks to remove host2 from slow start mode. + hostSet().runCallbacks({}, {}); + hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); + hostSet().healthy_hosts_[1]->stats().rq_active_.set(2); + + // We expect 6:5 ratio, as host1 is 8 seconds in slow start, its weight is scaled with active + // request bias and time factor 0.53 * max(pow(0.8, 1.11), 0.1)=0.41. Host2 is not in slow start + // and its weight is scaled with active request bias = 0.37. + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + + EXPECT_CALL(runtime_.snapshot_, getDouble("aggression", 0.9)).WillRepeatedly(Return(1.0)); + EXPECT_CALL(runtime_.snapshot_, getDouble("ar_bias", 0.9)).WillRepeatedly(Return(1.0)); + simTime().advanceTimeWait(std::chrono::seconds(20)); + host2->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); + host2->setLastHcPassTime(simTime().monotonicTime()); + hostSet().healthy_hosts_ = {host1, host2}; + hostSet().hosts_ = hostSet().healthyHosts(); + simTime().advanceTimeWait(std::chrono::seconds(5)); + hostSet().healthy_hosts_[0]->stats().rq_active_.dec(); + hostSet().healthy_hosts_[1]->stats().rq_active_.dec(); + hostSet().healthy_hosts_[1]->stats().rq_active_.dec(); + // Trigger callbacks to remove host1 from slow start. Host2 re-enters slow start due to HC pass. + hostSet().runCallbacks({}, {}); + // We expect 2:1 ratio, as host2 is in slow start mode, its weight is scaled with time factor + // max(pow(0.5, 1), 0.1)= 0.5. Host1 weight is 1. + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); +} + +INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, LeastRequestLoadBalancerTest, + ::testing::Values(LoadBalancerTestParam{true, false}, + LoadBalancerTestParam{true, true}, + LoadBalancerTestParam{false, false}, + LoadBalancerTestParam{false, true})); + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/least_request_load_balancer_corpus/large_active_request_bias b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/large_active_request_bias similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/large_active_request_bias rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/large_active_request_bias diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-high-number-of-hosts similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/least_request-high-number-of-hosts rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-high-number-of-hosts diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-max-request-value b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-max-request-value similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/least_request-max-request-value rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-max-request-value diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-no-config b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-no-config similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/least_request-no-config rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-no-config diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-no-hosts similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/least_request-no-hosts rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-no-hosts diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-normal b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-normal similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/least_request-normal rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-normal diff --git a/test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/least_request-with-locality-high-number-of-hosts diff --git a/test/common/upstream/least_request_load_balancer_corpus/small_agression b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/small_agression similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/small_agression rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/small_agression diff --git a/test/common/upstream/least_request_load_balancer_corpus/zero_min_weight_percent b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/zero_min_weight_percent similarity index 100% rename from test/common/upstream/least_request_load_balancer_corpus/zero_min_weight_percent rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_corpus/zero_min_weight_percent diff --git a/test/common/upstream/least_request_load_balancer_fuzz.proto b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_fuzz.proto similarity index 86% rename from test/common/upstream/least_request_load_balancer_fuzz.proto rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_fuzz.proto index 9e699b964b74..0918d5d02ef0 100644 --- a/test/common/upstream/least_request_load_balancer_fuzz.proto +++ b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_fuzz.proto @@ -5,7 +5,7 @@ package test.common.upstream; import "validate/validate.proto"; import "envoy/config/cluster/v3/cluster.proto"; -import "test/common/upstream/zone_aware_load_balancer_fuzz.proto"; +import "test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz.proto"; message LeastRequestLoadBalancerTestCase { test.common.upstream.ZoneAwareLoadBalancerTestCase zone_aware_load_balancer_test_case = 1 diff --git a/test/common/upstream/least_request_load_balancer_fuzz_test.cc b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_fuzz_test.cc similarity index 96% rename from test/common/upstream/least_request_load_balancer_fuzz_test.cc rename to test/extensions/load_balancing_policies/least_request/least_request_load_balancer_fuzz_test.cc index a1e022e89dbd..6b29d5d39c40 100644 --- a/test/common/upstream/least_request_load_balancer_fuzz_test.cc +++ b/test/extensions/load_balancing_policies/least_request/least_request_load_balancer_fuzz_test.cc @@ -2,8 +2,8 @@ #include "source/extensions/load_balancing_policies/least_request/least_request_lb.h" -#include "test/common/upstream/least_request_load_balancer_fuzz.pb.validate.h" -#include "test/common/upstream/zone_aware_load_balancer_fuzz_base.h" +#include "test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.h" +#include "test/extensions/load_balancing_policies/least_request/least_request_load_balancer_fuzz.pb.validate.h" #include "test/fuzz/fuzz_runner.h" #include "test/test_common/utility.h" diff --git a/test/extensions/load_balancing_policies/maglev/BUILD b/test/extensions/load_balancing_policies/maglev/BUILD index c8618de817f8..bb8b5809cc56 100644 --- a/test/extensions/load_balancing_policies/maglev/BUILD +++ b/test/extensions/load_balancing_policies/maglev/BUILD @@ -1,5 +1,7 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_benchmark_test", + "envoy_cc_benchmark_binary", "envoy_package", ) load( @@ -78,3 +80,18 @@ envoy_extension_cc_test( "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", ], ) + +envoy_cc_benchmark_binary( + name = "maglev_lb_benchmark", + srcs = ["maglev_lb_benchmark.cc"], + deps = [ + "//source/extensions/load_balancing_policies/maglev:maglev_lb_lib", + "//test/extensions/load_balancing_policies/common:benchmark_base_tester_lib", + ], +) + +envoy_benchmark_test( + name = "maglev_lb_benchmark_test", + timeout = "long", + benchmark_binary = "maglev_lb_benchmark", +) diff --git a/test/extensions/load_balancing_policies/maglev/maglev_lb_benchmark.cc b/test/extensions/load_balancing_policies/maglev/maglev_lb_benchmark.cc new file mode 100644 index 000000000000..75669eafc52c --- /dev/null +++ b/test/extensions/load_balancing_policies/maglev/maglev_lb_benchmark.cc @@ -0,0 +1,189 @@ +#include "source/extensions/load_balancing_policies/maglev/maglev_lb.h" + +#include "test/benchmark/main.h" +#include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" + +namespace Envoy { +namespace Upstream { +namespace { + +class MaglevTester : public BaseTester { +public: + MaglevTester(uint64_t num_hosts, uint32_t weighted_subset_percent = 0, uint32_t weight = 0) + : BaseTester(num_hosts, weighted_subset_percent, weight) { + maglev_lb_ = std::make_unique( + priority_set_, stats_, stats_scope_, runtime_, random_, + config_.has_value() + ? makeOptRef(config_.value()) + : absl::nullopt, + common_config_); + } + + absl::optional config_; + std::unique_ptr maglev_lb_; +}; + +void benchmarkMaglevLoadBalancerChooseHost(::benchmark::State& state) { + for (auto _ : state) { // NOLINT: Silences warning about dead store + // Do not time the creation of the table. + state.PauseTiming(); + const uint64_t num_hosts = state.range(0); + const uint64_t keys_to_simulate = state.range(1); + MaglevTester tester(num_hosts); + ASSERT_TRUE(tester.maglev_lb_->initialize().ok()); + LoadBalancerPtr lb = tester.maglev_lb_->factory()->create(tester.lb_params_); + absl::node_hash_map hit_counter; + TestLoadBalancerContext context; + state.ResumeTiming(); + + // Note: To a certain extent this is benchmarking the performance of xxhash as well as + // absl::node_hash_map. However, it should be roughly equivalent to the work done when + // comparing different hashing algorithms. + for (uint64_t i = 0; i < keys_to_simulate; i++) { + context.hash_key_ = hashInt(i); + hit_counter[lb->chooseHost(&context)->address()->asString()] += 1; + } + + // Do not time computation of mean, standard deviation, and relative standard deviation. + state.PauseTiming(); + computeHitStats(state, hit_counter); + state.ResumeTiming(); + } +} +BENCHMARK(benchmarkMaglevLoadBalancerChooseHost) + ->Args({100, 100000}) + ->Args({200, 100000}) + ->Args({500, 100000}) + ->Unit(::benchmark::kMillisecond); + +void benchmarkMaglevLoadBalancerBuildTable(::benchmark::State& state) { + for (auto _ : state) { // NOLINT: Silences warning about dead store + state.PauseTiming(); + const uint64_t num_hosts = state.range(0); + MaglevTester tester(num_hosts); + + const size_t start_mem = Memory::Stats::totalCurrentlyAllocated(); + + // We are only interested in timing the initial table build. + state.ResumeTiming(); + ASSERT_TRUE(tester.maglev_lb_->initialize().ok()); + state.PauseTiming(); + const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); + state.counters["memory"] = end_mem - start_mem; + state.counters["memory_per_host"] = (end_mem - start_mem) / num_hosts; + state.ResumeTiming(); + } +} +BENCHMARK(benchmarkMaglevLoadBalancerBuildTable) + ->Arg(100) + ->Arg(200) + ->Arg(500) + ->Unit(::benchmark::kMillisecond); + +void benchmarkMaglevLoadBalancerHostLoss(::benchmark::State& state) { + for (auto _ : state) { // NOLINT: Silences warning about dead store + const uint64_t num_hosts = state.range(0); + const uint64_t hosts_to_lose = state.range(1); + const uint64_t keys_to_simulate = state.range(2); + + MaglevTester tester(num_hosts); + ASSERT_TRUE(tester.maglev_lb_->initialize().ok()); + LoadBalancerPtr lb = tester.maglev_lb_->factory()->create(tester.lb_params_); + std::vector hosts; + TestLoadBalancerContext context; + for (uint64_t i = 0; i < keys_to_simulate; i++) { + context.hash_key_ = hashInt(i); + hosts.push_back(lb->chooseHost(&context)); + } + + MaglevTester tester2(num_hosts - hosts_to_lose); + ASSERT_TRUE(tester2.maglev_lb_->initialize().ok()); + lb = tester2.maglev_lb_->factory()->create(tester2.lb_params_); + std::vector hosts2; + for (uint64_t i = 0; i < keys_to_simulate; i++) { + context.hash_key_ = hashInt(i); + hosts2.push_back(lb->chooseHost(&context)); + } + + ASSERT(hosts.size() == hosts2.size()); + uint64_t num_different_hosts = 0; + for (uint64_t i = 0; i < hosts.size(); i++) { + if (hosts[i]->address()->asString() != hosts2[i]->address()->asString()) { + num_different_hosts++; + } + } + + state.counters["percent_different"] = + (static_cast(num_different_hosts) / hosts.size()) * 100; + state.counters["host_loss_over_N_optimal"] = + (static_cast(hosts_to_lose) / num_hosts) * 100; + } +} +BENCHMARK(benchmarkMaglevLoadBalancerHostLoss) + ->Args({500, 1, 10000}) + ->Args({500, 2, 10000}) + ->Args({500, 3, 10000}) + ->Unit(::benchmark::kMillisecond); + +void benchmarkMaglevLoadBalancerWeighted(::benchmark::State& state) { + for (auto _ : state) { // NOLINT: Silences warning about dead store + const uint64_t num_hosts = state.range(0); + const uint64_t weighted_subset_percent = state.range(1); + const uint64_t before_weight = state.range(2); + const uint64_t after_weight = state.range(3); + const uint64_t keys_to_simulate = state.range(4); + + MaglevTester tester(num_hosts, weighted_subset_percent, before_weight); + ASSERT_TRUE(tester.maglev_lb_->initialize().ok()); + LoadBalancerPtr lb = tester.maglev_lb_->factory()->create(tester.lb_params_); + std::vector hosts; + TestLoadBalancerContext context; + for (uint64_t i = 0; i < keys_to_simulate; i++) { + context.hash_key_ = hashInt(i); + hosts.push_back(lb->chooseHost(&context)); + } + + MaglevTester tester2(num_hosts, weighted_subset_percent, after_weight); + ASSERT_TRUE(tester2.maglev_lb_->initialize().ok()); + lb = tester2.maglev_lb_->factory()->create(tester2.lb_params_); + std::vector hosts2; + for (uint64_t i = 0; i < keys_to_simulate; i++) { + context.hash_key_ = hashInt(i); + hosts2.push_back(lb->chooseHost(&context)); + } + + ASSERT(hosts.size() == hosts2.size()); + uint64_t num_different_hosts = 0; + for (uint64_t i = 0; i < hosts.size(); i++) { + if (hosts[i]->address()->asString() != hosts2[i]->address()->asString()) { + num_different_hosts++; + } + } + + state.counters["percent_different"] = + (static_cast(num_different_hosts) / hosts.size()) * 100; + const auto weighted_hosts_percent = [weighted_subset_percent](uint32_t weight) -> double { + const double weighted_hosts = weighted_subset_percent; + const double unweighted_hosts = 100.0 - weighted_hosts; + const double total_weight = weighted_hosts * weight + unweighted_hosts; + return 100.0 * (weighted_hosts * weight) / total_weight; + }; + state.counters["optimal_percent_different"] = + std::abs(weighted_hosts_percent(before_weight) - weighted_hosts_percent(after_weight)); + } +} +BENCHMARK(benchmarkMaglevLoadBalancerWeighted) + ->Args({500, 5, 1, 1, 10000}) + ->Args({500, 5, 1, 127, 1000}) + ->Args({500, 5, 127, 1, 10000}) + ->Args({500, 50, 1, 127, 1000}) + ->Args({500, 50, 127, 1, 10000}) + ->Args({500, 95, 1, 127, 1000}) + ->Args({500, 95, 127, 1, 10000}) + ->Args({500, 95, 25, 75, 1000}) + ->Args({500, 95, 75, 25, 10000}) + ->Unit(::benchmark::kMillisecond); + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/random/BUILD b/test/extensions/load_balancing_policies/random/BUILD index 6dff34dcd0af..8f295c307ea1 100644 --- a/test/extensions/load_balancing_policies/random/BUILD +++ b/test/extensions/load_balancing_policies/random/BUILD @@ -1,6 +1,10 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_benchmark_test", + "envoy_cc_benchmark_binary", + "envoy_cc_fuzz_test", "envoy_package", + "envoy_proto_library", ) load( "//test/extensions:extensions_build_system.bzl", @@ -37,3 +41,64 @@ envoy_extension_cc_test( "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", ], ) + +envoy_proto_library( + name = "random_load_balancer_fuzz_proto", + srcs = ["random_load_balancer_fuzz.proto"], + deps = [ + "//test/extensions/load_balancing_policies/common:load_balancer_fuzz_proto", + ], +) + +envoy_cc_fuzz_test( + name = "random_load_balancer_fuzz_test", + srcs = ["random_load_balancer_fuzz_test.cc"], + corpus = "random_load_balancer_corpus", + deps = [ + ":random_load_balancer_fuzz_proto_cc_proto", + "//source/extensions/load_balancing_policies/random:config", + "//test/common/upstream:utility_lib", + "//test/extensions/load_balancing_policies/common:load_balancer_fuzz_lib", + ], +) + +envoy_cc_benchmark_binary( + name = "random_lb_benchmark", + srcs = ["random_lb_benchmark.cc"], + deps = [ + "//source/extensions/load_balancing_policies/random:random_lb_lib", + "//test/extensions/load_balancing_policies/common:benchmark_base_tester_lib", + ], +) + +envoy_benchmark_test( + name = "random_lb_benchmark_test", + timeout = "long", + benchmark_binary = "random_lb_benchmark", +) + +envoy_extension_cc_test( + name = "random_lb_test", + srcs = ["random_lb_test.cc"], + extension_names = ["envoy.load_balancing_policies.random"], + deps = [ + "//source/extensions/load_balancing_policies/random:random_lb_lib", + "//test/extensions/load_balancing_policies/common:load_balancer_base_test_lib", + ], +) + +envoy_extension_cc_test( + name = "random_lb_simulation_test", + srcs = ["random_lb_simulation_test.cc"], + extension_names = ["envoy.load_balancing_policies.random"], + deps = [ + "//source/common/common:random_generator_lib", + "//source/common/upstream:load_balancer_context_base_lib", + "//source/extensions/load_balancing_policies/random:random_lb_lib", + "//test/common/upstream:utility_lib", + "//test/mocks/runtime:runtime_mocks", + "//test/mocks/upstream:cluster_info_mocks", + "//test/mocks/upstream:host_set_mocks", + "//test/mocks/upstream:priority_set_mocks", + ], +) diff --git a/test/extensions/load_balancing_policies/random/random_lb_benchmark.cc b/test/extensions/load_balancing_policies/random/random_lb_benchmark.cc new file mode 100644 index 000000000000..8af387beada2 --- /dev/null +++ b/test/extensions/load_balancing_policies/random/random_lb_benchmark.cc @@ -0,0 +1,9 @@ +#include "source/extensions/load_balancing_policies/random/random_lb.h" + +#include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" + +namespace Envoy { +namespace Upstream { +namespace {} +} // namespace Upstream +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/random/random_lb_simulation_test.cc b/test/extensions/load_balancing_policies/random/random_lb_simulation_test.cc new file mode 100644 index 000000000000..af1b6c2e3c1e --- /dev/null +++ b/test/extensions/load_balancing_policies/random/random_lb_simulation_test.cc @@ -0,0 +1,211 @@ +#include "source/common/common/random_generator.h" +#include "source/extensions/load_balancing_policies/random/random_lb.h" + +#include "test/common/upstream/utility.h" +#include "test/mocks/common.h" +#include "test/mocks/runtime/mocks.h" +#include "test/mocks/upstream/cluster_info.h" +#include "test/mocks/upstream/host_set.h" +#include "test/mocks/upstream/priority_set.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using testing::NiceMock; +using testing::Return; + +namespace Envoy { +namespace Upstream { +namespace { + +static HostSharedPtr newTestHost(Upstream::ClusterInfoConstSharedPtr cluster, + const std::string& url, TimeSource& time_source, + uint32_t weight = 1, const std::string& zone = "") { + envoy::config::core::v3::Locality locality; + locality.set_zone(zone); + return HostSharedPtr{ + new HostImpl(cluster, "", Network::Utility::resolveUrl(url), nullptr, weight, locality, + envoy::config::endpoint::v3::Endpoint::HealthCheckConfig::default_instance(), 0, + envoy::config::core::v3::UNKNOWN, time_source)}; +} + +/** + * This test is for simulation only and should not be run as part of unit tests. + */ +class DISABLED_SimulationTest : public testing::Test { // NOLINT(readability-identifier-naming) +public: + DISABLED_SimulationTest() + : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, *stats_store_.rootScope()) { + ON_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50U)) + .WillByDefault(Return(50U)); + ON_CALL(runtime_.snapshot_, featureEnabled("upstream.zone_routing.enabled", 100)) + .WillByDefault(Return(true)); + ON_CALL(runtime_.snapshot_, getInteger("upstream.zone_routing.min_cluster_size", 6)) + .WillByDefault(Return(6)); + } + + /** + * Run simulation with given parameters. Generate statistics on per host requests. + * + * @param originating_cluster total number of hosts in each zone in originating cluster. + * @param all_destination_cluster total number of hosts in each zone in upstream cluster. + * @param healthy_destination_cluster total number of healthy hosts in each zone in upstream + * cluster. + */ + void run(std::vector originating_cluster, std::vector all_destination_cluster, + std::vector healthy_destination_cluster) { + local_priority_set_ = new PrioritySetImpl; + // TODO(mattklein123): make load balancer per originating cluster host. + RandomLoadBalancer lb(priority_set_, local_priority_set_, stats_, runtime_, random_, + common_config_); + + HostsPerLocalitySharedPtr upstream_per_zone_hosts = + generateHostsPerZone(healthy_destination_cluster); + HostsPerLocalitySharedPtr local_per_zone_hosts = generateHostsPerZone(originating_cluster); + + HostVectorSharedPtr originating_hosts = generateHostList(originating_cluster); + HostVectorSharedPtr healthy_destination = generateHostList(healthy_destination_cluster); + host_set_.healthy_hosts_ = *healthy_destination; + HostVectorSharedPtr all_destination = generateHostList(all_destination_cluster); + host_set_.hosts_ = *all_destination; + + std::map hits; + for (uint32_t i = 0; i < total_number_of_requests; ++i) { + HostSharedPtr from_host = selectOriginatingHost(*originating_hosts); + uint32_t from_zone = atoi(from_host->locality().zone().c_str()); + + // Populate host set for upstream cluster. + std::vector per_zone_upstream; + per_zone_upstream.push_back(upstream_per_zone_hosts->get()[from_zone]); + for (size_t zone = 0; zone < upstream_per_zone_hosts->get().size(); ++zone) { + if (zone == from_zone) { + continue; + } + + per_zone_upstream.push_back(upstream_per_zone_hosts->get()[zone]); + } + auto per_zone_upstream_shared = makeHostsPerLocality(std::move(per_zone_upstream)); + host_set_.hosts_per_locality_ = per_zone_upstream_shared; + host_set_.healthy_hosts_per_locality_ = per_zone_upstream_shared; + + // Populate host set for originating cluster. + std::vector per_zone_local; + per_zone_local.push_back(local_per_zone_hosts->get()[from_zone]); + for (size_t zone = 0; zone < local_per_zone_hosts->get().size(); ++zone) { + if (zone == from_zone) { + continue; + } + + per_zone_local.push_back(local_per_zone_hosts->get()[zone]); + } + auto per_zone_local_shared = makeHostsPerLocality(std::move(per_zone_local)); + local_priority_set_->updateHosts( + 0, + updateHostsParams(originating_hosts, per_zone_local_shared, + std::make_shared(*originating_hosts), + per_zone_local_shared), + {}, empty_vector_, empty_vector_, random_.random(), absl::nullopt); + + HostConstSharedPtr selected = lb.chooseHost(nullptr); + hits[selected->address()->asString()]++; + } + + double mean = total_number_of_requests * 1.0 / hits.size(); + for (const auto& host_hit_num_pair : hits) { + double percent_diff = std::abs((mean - host_hit_num_pair.second) / mean) * 100; + std::cout << fmt::format("url:{}, hits:{}, {} % from mean", host_hit_num_pair.first, + host_hit_num_pair.second, percent_diff) + << std::endl; + } + } + + HostSharedPtr selectOriginatingHost(const HostVector& hosts) { + // Originating cluster should have roughly the same per host request distribution. + return hosts[random_.random() % hosts.size()]; + } + + /** + * Generate list of hosts based on number of hosts in the given zone. + * @param hosts number of hosts per zone. + */ + HostVectorSharedPtr generateHostList(const std::vector& hosts) { + HostVectorSharedPtr ret(new HostVector()); + for (size_t i = 0; i < hosts.size(); ++i) { + const std::string zone = std::to_string(i); + for (uint32_t j = 0; j < hosts[i]; ++j) { + const std::string url = fmt::format("tcp://host.{}.{}:80", i, j); + ret->push_back(newTestHost(info_, url, time_source_, 1, zone)); + } + } + + return ret; + } + + /** + * Generate hosts by zone. + * @param hosts number of hosts per zone. + */ + HostsPerLocalitySharedPtr generateHostsPerZone(const std::vector& hosts) { + std::vector ret; + for (size_t i = 0; i < hosts.size(); ++i) { + const std::string zone = std::to_string(i); + HostVector zone_hosts; + + for (uint32_t j = 0; j < hosts[i]; ++j) { + const std::string url = fmt::format("tcp://host.{}.{}:80", i, j); + zone_hosts.push_back(newTestHost(info_, url, time_source_, 1, zone)); + } + + ret.push_back(std::move(zone_hosts)); + } + + return makeHostsPerLocality(std::move(ret)); + }; + + const uint32_t total_number_of_requests = 1000000; + HostVector empty_vector_; + + PrioritySetImpl* local_priority_set_; + NiceMock priority_set_; + MockHostSet& host_set_ = *priority_set_.getMockHostSet(0); + std::shared_ptr info_{new NiceMock()}; + NiceMock runtime_; + NiceMock time_source_; + Random::RandomGeneratorImpl random_; + Stats::IsolatedStoreImpl stats_store_; + ClusterLbStatNames stat_names_; + ClusterLbStats stats_; + envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; +}; + +TEST_F(DISABLED_SimulationTest, StrictlyEqualDistribution) { + run({1U, 1U, 1U}, {3U, 3U, 3U}, {3U, 3U, 3U}); +} + +TEST_F(DISABLED_SimulationTest, UnequalZoneDistribution) { + run({1U, 1U, 1U}, {2U, 5U, 5U}, {2U, 5U, 5U}); +} + +TEST_F(DISABLED_SimulationTest, UnequalZoneDistribution2) { + run({1U, 1U, 1U}, {5U, 5U, 6U}, {5U, 5U, 6U}); +} + +TEST_F(DISABLED_SimulationTest, UnequalZoneDistribution3) { + run({1U, 1U, 1U}, {10U, 10U, 10U}, {10U, 8U, 8U}); +} + +TEST_F(DISABLED_SimulationTest, UnequalZoneDistribution4) { + run({20U, 20U, 21U}, {4U, 5U, 5U}, {4U, 5U, 5U}); +} + +TEST_F(DISABLED_SimulationTest, UnequalZoneDistribution5) { + run({3U, 2U, 5U}, {4U, 5U, 5U}, {4U, 5U, 5U}); +} + +TEST_F(DISABLED_SimulationTest, UnequalZoneDistribution6) { + run({3U, 2U, 5U}, {3U, 4U, 5U}, {3U, 4U, 5U}); +} + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/random/random_lb_test.cc b/test/extensions/load_balancing_policies/random/random_lb_test.cc new file mode 100644 index 000000000000..afd7b4ed3b94 --- /dev/null +++ b/test/extensions/load_balancing_policies/random/random_lb_test.cc @@ -0,0 +1,67 @@ +#include "source/extensions/load_balancing_policies/random/random_lb.h" + +#include "test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace Envoy { +namespace Upstream { +namespace { + +using testing::Return; + +class RandomLoadBalancerTest : public LoadBalancerTestBase { +public: + void init() { + lb_ = std::make_shared(priority_set_, nullptr, stats_, runtime_, random_, + common_config_); + } + std::shared_ptr lb_; +}; + +TEST_P(RandomLoadBalancerTest, NoHosts) { + init(); + + EXPECT_EQ(nullptr, lb_->peekAnotherHost(nullptr)); + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); +} + +TEST_P(RandomLoadBalancerTest, Normal) { + init(); + hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}; + hostSet().hosts_ = hostSet().healthy_hosts_; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + + EXPECT_CALL(random_, random()).WillOnce(Return(2)); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->peekAnotherHost(nullptr)); + + EXPECT_CALL(random_, random()).WillOnce(Return(3)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->peekAnotherHost(nullptr)); + + EXPECT_CALL(random_, random()).Times(0); + EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); + EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); +} + +TEST_P(RandomLoadBalancerTest, FailClusterOnPanic) { + common_config_.mutable_zone_aware_lb_config()->set_fail_traffic_on_panic(true); + init(); + + hostSet().healthy_hosts_ = {}; + hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), + makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}; + hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. + EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); +} + +INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, RandomLoadBalancerTest, + ::testing::Values(LoadBalancerTestParam{true, false}, + LoadBalancerTestParam{true, true}, + LoadBalancerTestParam{false, false}, + LoadBalancerTestParam{false, true})); + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/random_load_balancer_corpus/random_256_ports b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_256_ports similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_256_ports rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_256_ports diff --git a/test/common/upstream/random_load_balancer_corpus/random_NoHosts b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_NoHosts similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_NoHosts rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_NoHosts diff --git a/test/common/upstream/random_load_balancer_corpus/random_Normal b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_Normal similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_Normal rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_Normal diff --git a/test/common/upstream/random_load_balancer_corpus/random_crash-55abbf82c64b5a62e299b93d7b254045471199c9 b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_crash-55abbf82c64b5a62e299b93d7b254045471199c9 similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_crash-55abbf82c64b5a62e299b93d7b254045471199c9 rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_crash-55abbf82c64b5a62e299b93d7b254045471199c9 diff --git a/test/common/upstream/random_load_balancer_corpus/random_crash-5a64c214e5c2038299c8f2e85f143f1163077c1b b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_crash-5a64c214e5c2038299c8f2e85f143f1163077c1b similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_crash-5a64c214e5c2038299c8f2e85f143f1163077c1b rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_crash-5a64c214e5c2038299c8f2e85f143f1163077c1b diff --git a/test/common/upstream/random_load_balancer_corpus/random_crash-ba5efdfd9c412a8507087120783fe6529b1ac0cb b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_crash-ba5efdfd9c412a8507087120783fe6529b1ac0cb similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_crash-ba5efdfd9c412a8507087120783fe6529b1ac0cb rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_crash-ba5efdfd9c412a8507087120783fe6529b1ac0cb diff --git a/test/common/upstream/random_load_balancer_corpus/random_largest-port-value b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_largest-port-value similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_largest-port-value rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_largest-port-value diff --git a/test/common/upstream/random_load_balancer_corpus/random_many_choose_hosts b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_many_choose_hosts similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_many_choose_hosts rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_many_choose_hosts diff --git a/test/common/upstream/random_load_balancer_corpus/random_max_ports b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_max_ports similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_max_ports rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_max_ports diff --git a/test/common/upstream/random_load_balancer_corpus/random_overflowing_ports b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_overflowing_ports similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_overflowing_ports rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_overflowing_ports diff --git a/test/common/upstream/random_load_balancer_corpus/random_slow-unit-eed4596101efb3e737f736c8d5bcd4f0815a8728 b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_slow-unit-eed4596101efb3e737f736c8d5bcd4f0815a8728 similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_slow-unit-eed4596101efb3e737f736c8d5bcd4f0815a8728 rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_slow-unit-eed4596101efb3e737f736c8d5bcd4f0815a8728 diff --git a/test/common/upstream/random_load_balancer_corpus/random_slow-unit-test b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_slow-unit-test similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_slow-unit-test rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_slow-unit-test diff --git a/test/common/upstream/random_load_balancer_corpus/random_test_something b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_test_something similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_test_something rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_test_something diff --git a/test/common/upstream/random_load_balancer_corpus/random_timeout-6b0d6b83136a4cf0b9ccd468f11207a792859d43 b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_timeout-6b0d6b83136a4cf0b9ccd468f11207a792859d43 similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_timeout-6b0d6b83136a4cf0b9ccd468f11207a792859d43 rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_timeout-6b0d6b83136a4cf0b9ccd468f11207a792859d43 diff --git a/test/common/upstream/random_load_balancer_corpus/random_timeout-9144cfbb40b5101ecc28b205b10e6c36a72aae83 b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_timeout-9144cfbb40b5101ecc28b205b10e6c36a72aae83 similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_timeout-9144cfbb40b5101ecc28b205b10e6c36a72aae83 rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_timeout-9144cfbb40b5101ecc28b205b10e6c36a72aae83 diff --git a/test/common/upstream/random_load_balancer_corpus/random_with-locality b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_with-locality similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_with-locality rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_with-locality diff --git a/test/common/upstream/random_load_balancer_corpus/random_with-locality-high-number-of-hosts b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_with-locality-high-number-of-hosts similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_with-locality-high-number-of-hosts rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_with-locality-high-number-of-hosts diff --git a/test/common/upstream/random_load_balancer_corpus/random_with_locality-50000-hosts b/test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_with_locality-50000-hosts similarity index 100% rename from test/common/upstream/random_load_balancer_corpus/random_with_locality-50000-hosts rename to test/extensions/load_balancing_policies/random/random_load_balancer_corpus/random_with_locality-50000-hosts diff --git a/test/common/upstream/random_load_balancer_fuzz.proto b/test/extensions/load_balancing_policies/random/random_load_balancer_fuzz.proto similarity index 77% rename from test/common/upstream/random_load_balancer_fuzz.proto rename to test/extensions/load_balancing_policies/random/random_load_balancer_fuzz.proto index ba277976d0fe..386f9376734d 100644 --- a/test/common/upstream/random_load_balancer_fuzz.proto +++ b/test/extensions/load_balancing_policies/random/random_load_balancer_fuzz.proto @@ -3,7 +3,7 @@ syntax = "proto3"; package test.common.upstream; import "validate/validate.proto"; -import "test/common/upstream/load_balancer_fuzz.proto"; +import "test/extensions/load_balancing_policies/common/load_balancer_fuzz.proto"; //This has no specific logic needed for initialization message RandomLoadBalancerTestCase { diff --git a/test/common/upstream/random_load_balancer_fuzz_test.cc b/test/extensions/load_balancing_policies/random/random_load_balancer_fuzz_test.cc similarity index 86% rename from test/common/upstream/random_load_balancer_fuzz_test.cc rename to test/extensions/load_balancing_policies/random/random_load_balancer_fuzz_test.cc index a2b49e234741..23a9c048c817 100644 --- a/test/common/upstream/random_load_balancer_fuzz_test.cc +++ b/test/extensions/load_balancing_policies/random/random_load_balancer_fuzz_test.cc @@ -2,8 +2,8 @@ #include "source/extensions/load_balancing_policies/random/random_lb.h" -#include "test/common/upstream/load_balancer_fuzz_base.h" -#include "test/common/upstream/random_load_balancer_fuzz.pb.validate.h" +#include "test/extensions/load_balancing_policies/common/load_balancer_fuzz_base.h" +#include "test/extensions/load_balancing_policies/random/random_load_balancer_fuzz.pb.validate.h" #include "test/fuzz/fuzz_runner.h" #include "test/test_common/utility.h" diff --git a/test/extensions/load_balancing_policies/ring_hash/BUILD b/test/extensions/load_balancing_policies/ring_hash/BUILD index e44db5929a93..a55f329031c7 100644 --- a/test/extensions/load_balancing_policies/ring_hash/BUILD +++ b/test/extensions/load_balancing_policies/ring_hash/BUILD @@ -1,5 +1,7 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_benchmark_test", + "envoy_cc_benchmark_binary", "envoy_package", ) load( @@ -61,3 +63,18 @@ envoy_extension_cc_test( "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", ], ) + +envoy_cc_benchmark_binary( + name = "ring_hash_lb_benchmark", + srcs = ["ring_hash_lb_benchmark.cc"], + deps = [ + "//source/extensions/load_balancing_policies/ring_hash:ring_hash_lb_lib", + "//test/extensions/load_balancing_policies/common:benchmark_base_tester_lib", + ], +) + +envoy_benchmark_test( + name = "ring_hash_lb_benchmark_test", + timeout = "long", + benchmark_binary = "ring_hash_lb_benchmark", +) diff --git a/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_benchmark.cc b/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_benchmark.cc new file mode 100644 index 000000000000..3b1a27fe4909 --- /dev/null +++ b/test/extensions/load_balancing_policies/ring_hash/ring_hash_lb_benchmark.cc @@ -0,0 +1,151 @@ +#include "source/extensions/load_balancing_policies/ring_hash/ring_hash_lb.h" + +#include "test/benchmark/main.h" +#include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" + +namespace Envoy { +namespace Upstream { +namespace { + +class RingHashTester : public BaseTester { +public: + RingHashTester(uint64_t num_hosts, uint64_t min_ring_size) : BaseTester(num_hosts) { + config_ = envoy::config::cluster::v3::Cluster::RingHashLbConfig(); + config_.value().mutable_minimum_ring_size()->set_value(min_ring_size); + ring_hash_lb_ = std::make_unique( + priority_set_, stats_, stats_scope_, runtime_, random_, + config_.has_value() + ? makeOptRef( + config_.value()) + : absl::nullopt, + common_config_); + } + + absl::optional config_; + std::unique_ptr ring_hash_lb_; +}; + +void benchmarkRingHashLoadBalancerBuildRing(::benchmark::State& state) { + for (auto _ : state) { // NOLINT: Silences warning about dead store + state.PauseTiming(); + const uint64_t num_hosts = state.range(0); + const uint64_t min_ring_size = state.range(1); + RingHashTester tester(num_hosts, min_ring_size); + + const size_t start_mem = Memory::Stats::totalCurrentlyAllocated(); + + // We are only interested in timing the initial ring build. + state.ResumeTiming(); + ASSERT_TRUE(tester.ring_hash_lb_->initialize().ok()); + state.PauseTiming(); + const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); + state.counters["memory"] = end_mem - start_mem; + state.counters["memory_per_host"] = (end_mem - start_mem) / num_hosts; + state.ResumeTiming(); + } +} +BENCHMARK(benchmarkRingHashLoadBalancerBuildRing) + ->Args({100, 65536}) + ->Args({200, 65536}) + ->Args({500, 65536}) + ->Args({100, 256000}) + ->Args({200, 256000}) + ->Args({500, 256000}) + ->Unit(::benchmark::kMillisecond); + +void benchmarkRingHashLoadBalancerChooseHost(::benchmark::State& state) { + for (auto _ : state) { // NOLINT: Silences warning about dead store + // Do not time the creation of the ring. + state.PauseTiming(); + const uint64_t num_hosts = state.range(0); + const uint64_t min_ring_size = state.range(1); + const uint64_t keys_to_simulate = state.range(2); + RingHashTester tester(num_hosts, min_ring_size); + ASSERT_TRUE(tester.ring_hash_lb_->initialize().ok()); + LoadBalancerPtr lb = tester.ring_hash_lb_->factory()->create(tester.lb_params_); + absl::node_hash_map hit_counter; + TestLoadBalancerContext context; + state.ResumeTiming(); + + // Note: To a certain extent this is benchmarking the performance of xxhash as well as + // absl::node_hash_map. However, it should be roughly equivalent to the work done when + // comparing different hashing algorithms. + // TODO(mattklein123): When Maglev is a real load balancer, further share code with the + // other test. + for (uint64_t i = 0; i < keys_to_simulate; i++) { + context.hash_key_ = hashInt(i); + hit_counter[lb->chooseHost(&context)->address()->asString()] += 1; + } + + // Do not time computation of mean, standard deviation, and relative standard deviation. + state.PauseTiming(); + computeHitStats(state, hit_counter); + state.ResumeTiming(); + } +} +BENCHMARK(benchmarkRingHashLoadBalancerChooseHost) + ->Args({100, 65536, 100000}) + ->Args({200, 65536, 100000}) + ->Args({500, 65536, 100000}) + ->Args({100, 256000, 100000}) + ->Args({200, 256000, 100000}) + ->Args({500, 256000, 100000}) + ->Unit(::benchmark::kMillisecond); + +void benchmarkRingHashLoadBalancerHostLoss(::benchmark::State& state) { + const uint64_t num_hosts = state.range(0); + const uint64_t min_ring_size = state.range(1); + const uint64_t hosts_to_lose = state.range(2); + const uint64_t keys_to_simulate = state.range(3); + + if (benchmark::skipExpensiveBenchmarks() && min_ring_size > 65536) { + state.SkipWithError("Skipping expensive benchmark"); + return; + } + + for (auto _ : state) { // NOLINT: Silences warning about dead store + RingHashTester tester(num_hosts, min_ring_size); + ASSERT_TRUE(tester.ring_hash_lb_->initialize().ok()); + LoadBalancerPtr lb = tester.ring_hash_lb_->factory()->create(tester.lb_params_); + std::vector hosts; + TestLoadBalancerContext context; + for (uint64_t i = 0; i < keys_to_simulate; i++) { + context.hash_key_ = hashInt(i); + hosts.push_back(lb->chooseHost(&context)); + } + + RingHashTester tester2(num_hosts - hosts_to_lose, min_ring_size); + ASSERT_TRUE(tester2.ring_hash_lb_->initialize().ok()); + lb = tester2.ring_hash_lb_->factory()->create(tester2.lb_params_); + std::vector hosts2; + for (uint64_t i = 0; i < keys_to_simulate; i++) { + context.hash_key_ = hashInt(i); + hosts2.push_back(lb->chooseHost(&context)); + } + + ASSERT(hosts.size() == hosts2.size()); + uint64_t num_different_hosts = 0; + for (uint64_t i = 0; i < hosts.size(); i++) { + if (hosts[i]->address()->asString() != hosts2[i]->address()->asString()) { + num_different_hosts++; + } + } + + state.counters["percent_different"] = + (static_cast(num_different_hosts) / hosts.size()) * 100; + state.counters["host_loss_over_N_optimal"] = + (static_cast(hosts_to_lose) / num_hosts) * 100; + } +} +BENCHMARK(benchmarkRingHashLoadBalancerHostLoss) + ->Args({500, 65536, 1, 10000}) + ->Args({500, 65536, 2, 10000}) + ->Args({500, 65536, 3, 10000}) + ->Args({500, 256000, 1, 10000}) + ->Args({500, 256000, 2, 10000}) + ->Args({500, 256000, 3, 10000}) + ->Unit(::benchmark::kMillisecond); + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/extensions/load_balancing_policies/round_robin/BUILD b/test/extensions/load_balancing_policies/round_robin/BUILD index 3efd84d4e5be..e627d16684b3 100644 --- a/test/extensions/load_balancing_policies/round_robin/BUILD +++ b/test/extensions/load_balancing_policies/round_robin/BUILD @@ -1,6 +1,10 @@ load( "//bazel:envoy_build_system.bzl", + "envoy_benchmark_test", + "envoy_cc_benchmark_binary", + "envoy_cc_fuzz_test", "envoy_package", + "envoy_proto_library", ) load( "//test/extensions:extensions_build_system.bzl", @@ -37,3 +41,50 @@ envoy_extension_cc_test( "@envoy_api//envoy/config/endpoint/v3:pkg_cc_proto", ], ) + +envoy_proto_library( + name = "round_robin_load_balancer_fuzz_proto", + srcs = ["round_robin_load_balancer_fuzz.proto"], + deps = [ + "//test/extensions/load_balancing_policies/common:zone_aware_load_balancer_fuzz_proto", + "@envoy_api//envoy/config/cluster/v3:pkg", + ], +) + +envoy_cc_fuzz_test( + name = "round_robin_load_balancer_fuzz_test", + srcs = ["round_robin_load_balancer_fuzz_test.cc"], + corpus = "round_robin_load_balancer_corpus", + deps = [ + ":round_robin_load_balancer_fuzz_proto_cc_proto", + "//source/extensions/load_balancing_policies/round_robin:config", + "//test/common/upstream:utility_lib", + "//test/extensions/load_balancing_policies/common:zone_aware_load_balancer_fuzz_lib", + "//test/fuzz:utility_lib", + ], +) + +envoy_cc_benchmark_binary( + name = "round_robin_lb_benchmark", + srcs = ["round_robin_lb_benchmark.cc"], + deps = [ + "//source/extensions/load_balancing_policies/round_robin:config", + "//test/extensions/load_balancing_policies/common:benchmark_base_tester_lib", + ], +) + +envoy_benchmark_test( + name = "round_robin_lb_benchmark_test", + timeout = "long", + benchmark_binary = "round_robin_lb_benchmark", +) + +envoy_extension_cc_test( + name = "round_robin_lb_test", + srcs = ["round_robin_lb_test.cc"], + extension_names = ["envoy.load_balancing_policies.round_robin"], + deps = [ + "//source/extensions/load_balancing_policies/round_robin:round_robin_lb_lib", + "//test/extensions/load_balancing_policies/common:load_balancer_base_test_lib", + ], +) diff --git a/test/extensions/load_balancing_policies/round_robin/round_robin_lb_benchmark.cc b/test/extensions/load_balancing_policies/round_robin/round_robin_lb_benchmark.cc new file mode 100644 index 000000000000..520e819d2e53 --- /dev/null +++ b/test/extensions/load_balancing_policies/round_robin/round_robin_lb_benchmark.cc @@ -0,0 +1,73 @@ +#include "source/extensions/load_balancing_policies/round_robin/round_robin_lb.h" + +#include "test/benchmark/main.h" +#include "test/extensions/load_balancing_policies/common/benchmark_base_tester.h" + +namespace Envoy { +namespace Upstream { +namespace { + +class RoundRobinTester : public BaseTester { +public: + RoundRobinTester(uint64_t num_hosts, uint32_t weighted_subset_percent = 0, uint32_t weight = 0) + : BaseTester(num_hosts, weighted_subset_percent, weight) {} + + void initialize() { + lb_ = std::make_unique(priority_set_, &local_priority_set_, stats_, + runtime_, random_, common_config_, + round_robin_lb_config_, simTime()); + } + + std::unique_ptr lb_; +}; + +void benchmarkRoundRobinLoadBalancerBuild(::benchmark::State& state) { + const uint64_t num_hosts = state.range(0); + const uint64_t weighted_subset_percent = state.range(1); + const uint64_t weight = state.range(2); + + if (benchmark::skipExpensiveBenchmarks() && num_hosts > 10000) { + state.SkipWithError("Skipping expensive benchmark"); + return; + } + + for (auto _ : state) { // NOLINT: Silences warning about dead store + state.PauseTiming(); + const size_t start_tester_mem = Memory::Stats::totalCurrentlyAllocated(); + RoundRobinTester tester(num_hosts, weighted_subset_percent, weight); + const size_t end_tester_mem = Memory::Stats::totalCurrentlyAllocated(); + const size_t start_mem = Memory::Stats::totalCurrentlyAllocated(); + + // We are only interested in timing the initial build. + state.ResumeTiming(); + tester.initialize(); + state.PauseTiming(); + const size_t end_mem = Memory::Stats::totalCurrentlyAllocated(); + state.counters["tester_memory"] = end_tester_mem - start_tester_mem; + state.counters["memory"] = end_mem - start_mem; + state.counters["memory_per_host"] = (end_mem - start_mem) / num_hosts; + state.ResumeTiming(); + } +} +BENCHMARK(benchmarkRoundRobinLoadBalancerBuild) + ->Args({1, 0, 1}) + ->Args({500, 0, 1}) + ->Args({500, 50, 50}) + ->Args({500, 100, 50}) + ->Args({2500, 0, 1}) + ->Args({2500, 50, 50}) + ->Args({2500, 100, 50}) + ->Args({10000, 0, 1}) + ->Args({10000, 50, 50}) + ->Args({10000, 100, 50}) + ->Args({25000, 0, 1}) + ->Args({25000, 50, 50}) + ->Args({25000, 100, 50}) + ->Args({50000, 0, 1}) + ->Args({50000, 50, 50}) + ->Args({50000, 100, 50}) + ->Unit(::benchmark::kMillisecond); + +} // namespace +} // namespace Upstream +} // namespace Envoy diff --git a/test/common/upstream/load_balancer_impl_test.cc b/test/extensions/load_balancing_policies/round_robin/round_robin_lb_test.cc similarity index 62% rename from test/common/upstream/load_balancer_impl_test.cc rename to test/extensions/load_balancing_policies/round_robin/round_robin_lb_test.cc index 316e53811411..25ff046acb72 100644 --- a/test/common/upstream/load_balancer_impl_test.cc +++ b/test/extensions/load_balancing_policies/round_robin/round_robin_lb_test.cc @@ -1,708 +1,13 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include "envoy/config/cluster/v3/cluster.pb.h" -#include "envoy/config/core/v3/base.pb.h" -#include "envoy/config/core/v3/health_check.pb.h" - -#include "source/common/common/random_generator.h" -#include "source/common/network/utility.h" -#include "source/common/upstream/load_balancer_context_base.h" -#include "source/common/upstream/upstream_impl.h" -#include "source/extensions/load_balancing_policies/least_request/least_request_lb.h" -#include "source/extensions/load_balancing_policies/random/random_lb.h" #include "source/extensions/load_balancing_policies/round_robin/round_robin_lb.h" -#include "test/common/upstream/utility.h" -#include "test/mocks/common.h" -#include "test/mocks/runtime/mocks.h" -#include "test/mocks/upstream/cluster_info.h" -#include "test/mocks/upstream/host.h" -#include "test/mocks/upstream/host_set.h" -#include "test/mocks/upstream/load_balancer_context.h" -#include "test/mocks/upstream/priority_set.h" -#include "test/test_common/logging.h" -#include "test/test_common/simulated_time_system.h" -#include "test/test_common/test_runtime.h" -#include "test/test_common/utility.h" - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -using testing::ElementsAre; -using testing::NiceMock; -using testing::Return; -using testing::ReturnRef; +#include "test/extensions/load_balancing_policies/common/load_balancer_impl_base_test.h" namespace Envoy { namespace Upstream { - -class EdfLoadBalancerBasePeer { -public: - static const std::chrono::milliseconds& slowStartWindow(EdfLoadBalancerBase& edf_lb) { - return edf_lb.slow_start_window_; - } - static const std::chrono::milliseconds latestHostAddedTime(EdfLoadBalancerBase& edf_lb) { - return std::chrono::time_point_cast(edf_lb.latest_host_added_time_) - .time_since_epoch(); - } - static double slowStartMinWeightPercent(const EdfLoadBalancerBase& edf_lb) { - return edf_lb.slow_start_min_weight_percent_; - } -}; - -class TestZoneAwareLoadBalancer : public ZoneAwareLoadBalancerBase { -public: - TestZoneAwareLoadBalancer( - const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, - Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : ZoneAwareLoadBalancerBase( - priority_set, nullptr, lb_stats, runtime, random, - PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, - 100, 50), - LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config)) {} - void runInvalidLocalitySourceType() { - localitySourceType(static_cast(123)); - } - void runInvalidSourceType() { sourceType(static_cast(123)); } - HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { PANIC("not implemented"); } - HostConstSharedPtr peekAnotherHost(LoadBalancerContext*) override { PANIC("not implemented"); } -}; - namespace { -struct LoadBalancerTestParam { - bool use_default_host_set; - bool use_new_locality_routing; -}; - -class LoadBalancerTestBase : public Event::TestUsingSimulatedTime, - public testing::TestWithParam { -protected: - // Run all tests against both priority 0 and priority 1 host sets, to ensure - // all the load balancers have equivalent functionality for failover host sets. - MockHostSet& hostSet() { - return GetParam().use_default_host_set ? host_set_ : failover_host_set_; - } - - LoadBalancerTestBase() - : stat_names_(stats_store_.symbolTable()), stats_(stat_names_, *stats_store_.rootScope()) { - least_request_lb_config_.mutable_choice_count()->set_value(2); - } - - Stats::IsolatedStoreImpl stats_store_; - ClusterLbStatNames stat_names_; - ClusterLbStats stats_; - NiceMock runtime_; - NiceMock random_; - NiceMock priority_set_; - MockHostSet& host_set_ = *priority_set_.getMockHostSet(0); - MockHostSet& failover_host_set_ = *priority_set_.getMockHostSet(1); - std::shared_ptr info_{new NiceMock()}; - envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig least_request_lb_config_; - envoy::config::cluster::v3::Cluster::RoundRobinLbConfig round_robin_lb_config_; -}; - -class TestLb : public LoadBalancerBase { -public: - TestLb(const PrioritySet& priority_set, ClusterLbStats& lb_stats, Runtime::Loader& runtime, - Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : LoadBalancerBase(priority_set, lb_stats, runtime, random, - PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT( - common_config, healthy_panic_threshold, 100, 50)) {} - using LoadBalancerBase::chooseHostSet; - using LoadBalancerBase::isInPanic; - using LoadBalancerBase::percentageDegradedLoad; - using LoadBalancerBase::percentageLoad; - - HostConstSharedPtr chooseHost(LoadBalancerContext*) override { PANIC("not implemented"); } - - HostConstSharedPtr peekAnotherHost(LoadBalancerContext*) override { PANIC("not implemented"); } -}; - -class LoadBalancerBaseTest : public LoadBalancerTestBase { -public: - void updateHostSet(MockHostSet& host_set, uint32_t num_hosts, uint32_t num_healthy_hosts, - uint32_t num_degraded_hosts = 0, uint32_t num_excluded_hosts = 0) { - ASSERT(num_healthy_hosts + num_degraded_hosts + num_excluded_hosts <= num_hosts); - - host_set.hosts_.clear(); - host_set.healthy_hosts_.clear(); - host_set.degraded_hosts_.clear(); - host_set.excluded_hosts_.clear(); - for (uint32_t i = 0; i < num_hosts; ++i) { - host_set.hosts_.push_back(makeTestHost(info_, "tcp://127.0.0.1:80", simTime())); - } - uint32_t i = 0; - for (; i < num_healthy_hosts; ++i) { - host_set.healthy_hosts_.push_back(host_set.hosts_[i]); - } - for (; i < (num_healthy_hosts + num_degraded_hosts); ++i) { - host_set.degraded_hosts_.push_back(host_set.hosts_[i]); - } - - for (; i < (num_healthy_hosts + num_degraded_hosts + num_excluded_hosts); ++i) { - host_set.excluded_hosts_.push_back(host_set.hosts_[i]); - } - host_set.runCallbacks({}, {}); - } - - template - std::vector aggregatePrioritySetsValues(TestLb& lb, FUNC func) { - std::vector ret; - - for (size_t i = 0; i < priority_set_.host_sets_.size(); ++i) { - ret.push_back((lb.*func)(i)); - } - - return ret; - } - - std::vector getLoadPercentage() { - return aggregatePrioritySetsValues(lb_, &TestLb::percentageLoad); - } - - std::vector getDegradedLoadPercentage() { - return aggregatePrioritySetsValues(lb_, &TestLb::percentageDegradedLoad); - } - - std::vector getPanic() { - return aggregatePrioritySetsValues(lb_, &TestLb::isInPanic); - } - - envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; - TestLb lb_{priority_set_, stats_, runtime_, random_, common_config_}; -}; - -INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, LoadBalancerBaseTest, - ::testing::Values(LoadBalancerTestParam{true, false}, - LoadBalancerTestParam{true, true})); - -// Basic test of host set selection. -TEST_P(LoadBalancerBaseTest, PrioritySelection) { - NiceMock context; - updateHostSet(host_set_, 1 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 1, 0); - - HealthyAndDegradedLoad priority_load{Upstream::HealthyLoad({100, 0, 0}), - Upstream::DegradedLoad({0, 0, 0})}; - EXPECT_CALL(context, determinePriorityLoad(_, _, _)).WillRepeatedly(ReturnRef(priority_load)); - // Primary and failover are in panic mode. Load distribution is based - // on the number of hosts regardless of their health. - EXPECT_EQ(50, lb_.percentageLoad(0)); - EXPECT_EQ(50, lb_.percentageLoad(1)); - EXPECT_EQ(&host_set_, &lb_.chooseHostSet(&context, 0).first); - - // Modify number of hosts in failover, but leave them in the unhealthy state - // primary and secondary are in panic mode, so load distribution is - // based on number of host regardless of their health. - updateHostSet(failover_host_set_, 2, 0); - EXPECT_EQ(34, lb_.percentageLoad(0)); - EXPECT_EQ(66, lb_.percentageLoad(1)); - EXPECT_EQ(&host_set_, &lb_.chooseHostSet(&context, 0).first); - - // Update the priority set with a new priority level P=2 and ensure the host - // is chosen - MockHostSet& tertiary_host_set_ = *priority_set_.getMockHostSet(2); - updateHostSet(tertiary_host_set_, 1 /* num_hosts */, 1 /* num_healthy_hosts */); - EXPECT_EQ(0, lb_.percentageLoad(0)); - EXPECT_EQ(0, lb_.percentageLoad(1)); - EXPECT_EQ(100, lb_.percentageLoad(2)); - priority_load.healthy_priority_load_ = HealthyLoad({0u, 0u, 100}); - EXPECT_EQ(&tertiary_host_set_, &lb_.chooseHostSet(&context, 0).first); - - // Now add a healthy host in P=0 and make sure it is immediately selected. - updateHostSet(host_set_, 1 /* num_hosts */, 1 /* num_healthy_hosts */); - host_set_.healthy_hosts_ = host_set_.hosts_; - host_set_.runCallbacks({}, {}); - EXPECT_EQ(100, lb_.percentageLoad(0)); - EXPECT_EQ(0, lb_.percentageLoad(2)); - priority_load.healthy_priority_load_ = HealthyLoad({100u, 0u, 0u}); - EXPECT_EQ(&host_set_, &lb_.chooseHostSet(&context, 0).first); - - // Remove the healthy host and ensure we fail back over to tertiary_host_set_ - updateHostSet(host_set_, 1 /* num_hosts */, 0 /* num_healthy_hosts */); - EXPECT_EQ(0, lb_.percentageLoad(0)); - EXPECT_EQ(100, lb_.percentageLoad(2)); - priority_load.healthy_priority_load_ = HealthyLoad({0u, 0u, 100}); - EXPECT_EQ(&tertiary_host_set_, &lb_.chooseHostSet(&context, 0).first); -} - -// Tests host selection with a randomized number of healthy, degraded and unhealthy hosts. -TEST_P(LoadBalancerBaseTest, PrioritySelectionFuzz) { - TestRandomGenerator rand; - - // Determine total number of hosts. - const auto total_hosts = 1 + (rand.random() % 10); - - NiceMock context; - - const auto host_set_hosts = rand.random() % total_hosts; - - if (host_set_hosts == 0) { - updateHostSet(host_set_, 0, 0); - } else { - // We get on average 50% healthy hosts, 25% degraded hosts and 25% unhealthy hosts. - const auto healthy_hosts = rand.random() % host_set_hosts; - const auto degraded_hosts = rand.random() % (host_set_hosts - healthy_hosts); - const auto unhealthy_hosts = host_set_hosts - healthy_hosts - degraded_hosts; - - updateHostSet(host_set_, host_set_hosts, unhealthy_hosts, degraded_hosts); - } - - const auto failover_set_hosts = total_hosts - host_set_hosts; - - if (host_set_hosts == 0) { - updateHostSet(failover_host_set_, 0, 0); - } else { - // We get on average 50% healthy hosts, 25% degraded hosts and 25% unhealthy hosts. - const auto healthy_hosts = rand.random() % failover_set_hosts; - const auto degraded_hosts = rand.random() % (failover_set_hosts - healthy_hosts); - const auto unhealthy_hosts = failover_set_hosts - healthy_hosts - degraded_hosts; - - updateHostSet(failover_host_set_, failover_set_hosts, unhealthy_hosts, degraded_hosts); - } - - EXPECT_CALL(context, determinePriorityLoad(_, _, _)) - .WillRepeatedly( - Invoke([](const auto&, const auto& original_load, - const auto&) -> const HealthyAndDegradedLoad& { return original_load; })); - - for (uint64_t i = 0; i < total_hosts; ++i) { - const auto hs = lb_.chooseHostSet(&context, 0); - switch (hs.second) { - case LoadBalancerBase::HostAvailability::Healthy: - // Either we selected one of the healthy hosts or we failed to select anything and - // defaulted to healthy. - EXPECT_TRUE(!hs.first.healthyHosts().empty() || - (hs.first.healthyHosts().empty() && hs.first.degradedHosts().empty())); - break; - case LoadBalancerBase::HostAvailability::Degraded: - EXPECT_FALSE(hs.first.degradedHosts().empty()); - break; - } - } -} - -// Test of host set selection with priority filter -TEST_P(LoadBalancerBaseTest, PrioritySelectionWithFilter) { - NiceMock context; - - HealthyAndDegradedLoad priority_load{Upstream::HealthyLoad({0u, 100u}), - Upstream::DegradedLoad({0, 0})}; - // return a filter that excludes priority 0 - EXPECT_CALL(context, determinePriorityLoad(_, _, _)).WillRepeatedly(ReturnRef(priority_load)); - - updateHostSet(host_set_, 1 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 1, 1); - - // Since we've excluded P0, we should pick the failover host set - EXPECT_EQ(failover_host_set_.priority(), lb_.chooseHostSet(&context, 0).first.priority()); - - updateHostSet(host_set_, 1 /* num_hosts */, 0 /* num_healthy_hosts */, - 1 /* num_degraded_hosts */); - updateHostSet(failover_host_set_, 1, 0, 1); - - // exclude priority 0 for degraded hosts - priority_load.healthy_priority_load_ = Upstream::HealthyLoad({0, 0}); - priority_load.degraded_priority_load_ = Upstream::DegradedLoad({0, 100}); - - // Since we've excluded P0, we should pick the failover host set - EXPECT_EQ(failover_host_set_.priority(), lb_.chooseHostSet(&context, 0).first.priority()); -} - -TEST_P(LoadBalancerBaseTest, OverProvisioningFactor) { - // Default overprovisioning factor 1.4 makes P0 receives 70% load. - updateHostSet(host_set_, 4, 2); - updateHostSet(failover_host_set_, 4, 2); - ASSERT_THAT(getLoadPercentage(), ElementsAre(70, 30)); - - // Set overprovisioning factor to 1, now it should be proportioned to healthy ratio. - host_set_.setOverprovisioningFactor(100); - updateHostSet(host_set_, 4, 2); - failover_host_set_.setOverprovisioningFactor(100); - updateHostSet(failover_host_set_, 4, 2); - ASSERT_THAT(getLoadPercentage(), ElementsAre(50, 50)); -} - -TEST_P(LoadBalancerBaseTest, WeightedPriorityHealth) { - host_set_.weighted_priority_health_ = true; - failover_host_set_.weighted_priority_health_ = true; - - // Makes math easier to read. - host_set_.setOverprovisioningFactor(100); - failover_host_set_.setOverprovisioningFactor(100); - - // Basic healthy/unhealthy test. - updateHostSet(host_set_, 4, 2, 0, 0); - updateHostSet(failover_host_set_, 1, 1); - - // Total weight is 10, healthy weight is 6. - host_set_.hosts_[0]->weight(3); // Healthy - host_set_.hosts_[1]->weight(3); // Healthy - host_set_.hosts_[2]->weight(2); // Unhealthy - host_set_.hosts_[3]->weight(2); // Unhealthy - host_set_.runCallbacks({}, {}); - ASSERT_THAT(getLoadPercentage(), ElementsAre(60, 40)); -} - -TEST_P(LoadBalancerBaseTest, WeightedPriorityHealthExcluded) { - host_set_.weighted_priority_health_ = true; - failover_host_set_.weighted_priority_health_ = true; - - // Makes math easier to read. - host_set_.setOverprovisioningFactor(100); - failover_host_set_.setOverprovisioningFactor(100); - - updateHostSet(failover_host_set_, 1, 1); - updateHostSet(host_set_, 3, 1, 0, 1); - host_set_.hosts_[0]->weight(4); // Healthy - host_set_.hosts_[1]->weight(10); // Excluded - host_set_.hosts_[2]->weight(6); // Unhealthy - host_set_.runCallbacks({}, {}); - ASSERT_THAT(getLoadPercentage(), ElementsAre(40, 60)); -} - -TEST_P(LoadBalancerBaseTest, WeightedPriorityHealthDegraded) { - host_set_.weighted_priority_health_ = true; - failover_host_set_.weighted_priority_health_ = true; - - // Makes math easier to read. - host_set_.setOverprovisioningFactor(100); - failover_host_set_.setOverprovisioningFactor(100); - - updateHostSet(host_set_, 4, 1, 1, 0); - host_set_.hosts_[0]->weight(4); // Healthy - host_set_.hosts_[1]->weight(3); // Degraded - host_set_.hosts_[2]->weight(2); // Unhealthy - host_set_.hosts_[3]->weight(1); // Unhealthy - host_set_.runCallbacks({}, {}); - - updateHostSet(failover_host_set_, 2, 1, 1); // 1 healthy host, 1 degraded. - failover_host_set_.hosts_[0]->weight(1); // Healthy - failover_host_set_.hosts_[1]->weight(9); // Degraded - failover_host_set_.runCallbacks({}, {}); - - // 40% for healthy priority 0, 10% for healthy priority 1, 30% for degraded priority zero, and the - // remaining 20% to degraded priority 1. - ASSERT_THAT(getLoadPercentage(), ElementsAre(40, 10)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(30, 20)); -} - -TEST_P(LoadBalancerBaseTest, GentleFailover) { - // With 100% of P=0 hosts healthy, P=0 gets all the load. - // None of the levels is in Panic mode - updateHostSet(host_set_, 1, 1); - updateHostSet(failover_host_set_, 1, 1); - ASSERT_THAT(getLoadPercentage(), ElementsAre(100, 0)); - ASSERT_THAT(getPanic(), ElementsAre(false, false)); - - // Health P=0 == 50*1.4 == 70 - // Total health = 70 + 70 >= 100%. None of the levels should be in panic mode. - updateHostSet(host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(70, 30)); - ASSERT_THAT(getPanic(), ElementsAre(false, false)); - - // Health P=0 == 25*1.4 == 35 P=1 is healthy so takes all spillover. - // Total health = 35+100 >= 100%. P=0 is below Panic level but it is ignored, because - // Total health >= 100%. - updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 2 /* num_hosts */, 2 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(35, 65)); - ASSERT_THAT(getPanic(), ElementsAre(false, false)); - - // Health P=0 == 25*1.4 == 35 P=1 == 35 - // Health is then scaled up by (100 / (35 + 35) == 50) - // Total health = 35% + 35% is less than 100%. Panic levels per priority kick in. - updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(50, 50)); - ASSERT_THAT(getPanic(), ElementsAre(true, true)); - - // Health P=0 == 100*1.4 == 35 P=1 == 35 - // Since 3 hosts are excluded, P=0 should be considered fully healthy. - // Total health = 100% + 35% is greater than 100%. Panic should not trigger. - updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */, 0 /* num_degraded_hosts - */ - , - 3 /* num_excluded_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(100, 0)); - ASSERT_THAT(getPanic(), ElementsAre(false, false)); - - // Health P=0 == 100*1.4 == 35 P=1 == 35 - // Total health = 35% is less than 100%. - // All priorities are in panic mode (situation called TotalPanic) - // Load is distributed based on number of hosts regardless of their health status. - // P=0 and P=1 have 4 hosts each so each priority will receive 50% of the traffic. - updateHostSet(host_set_, 4 /* num_hosts */, 0 /* num_healthy_hosts */, 0 /* num_degraded_hosts - */ - , - 4 /* num_excluded_hosts */); - updateHostSet(failover_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(50, 50)); - ASSERT_THAT(getPanic(), ElementsAre(true, true)); - - // Make sure that in TotalPanic mode (all levels are in Panic), - // load distribution depends only on number of hosts. - // excluded_hosts should not be taken into account. - // P=0 has 4 hosts with 1 excluded, P=1 has 6 hosts with 2 excluded. - // P=0 should receive 4/(4+6)=40% of traffic - // P=1 should receive 6/(4+6)=60% of traffic - updateHostSet(host_set_, 4 /* num_hosts */, 0 /* num_healthy_hosts */, 0 /* num_degraded_hosts - */ - , - 1 /* num_excluded_hosts */); - updateHostSet(failover_host_set_, 6 /* num_hosts */, 1 /* num_healthy_hosts */, - 0 /* num_degraded_hosts */, 2 /* num_excluded_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(40, 60)); - ASSERT_THAT(getPanic(), ElementsAre(true, true)); -} - -TEST_P(LoadBalancerBaseTest, GentleFailoverWithExtraLevels) { - // Add a third host set. Again with P=0 healthy, all traffic goes there. - MockHostSet& tertiary_host_set_ = *priority_set_.getMockHostSet(2); - updateHostSet(host_set_, 1, 1); - updateHostSet(failover_host_set_, 1, 1); - updateHostSet(tertiary_host_set_, 1, 1); - ASSERT_THAT(getLoadPercentage(), ElementsAre(100, 0, 0)); - ASSERT_THAT(getPanic(), ElementsAre(false, false, false)); - - // Health P=0 == 50*1.4 == 70 - // Health P=0 == 50, so can take the 30% spillover. - updateHostSet(host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(70, 30, 0)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - - // Health P=0 == 25*1.4 == 35 P=1 is healthy so takes all spillover. - updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 2 /* num_hosts */, 2 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 2 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(35, 65, 0)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - - // This is the first test where health (P=0 + P=1 < 100) - // Health P=0 == 25*1.4 == 35 P=1 == 35 P=2 == 35 - updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(35, 35, 30)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - - // This is the first test where (health P=0 + P=1 < 100) - // Health P=0 == 25*1.4 == 35 P=1 == 35 P=2 == 35 - updateHostSet(host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 4 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(35, 35, 30)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - - // Now all health is (20% * 1.5 == 28). 28 * 3 < 100 so we have to scale. - // Each Priority level gets 33% of the load, with P=0 picking up the rounding error. - updateHostSet(host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); - - // Levels P=0 and P=1 are totally down. P=2 is totally healthy. - // 100% of the traffic should go to P=2 and P=0 and P=1 should - // not be in panic mode. - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 5 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 0, 100)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - ASSERT_THAT(getPanic(), ElementsAre(false, false, false)); - - // Levels P=0 and P=1 are totally down. P=2 is 80*1.4 >= 100% healthy. - // 100% of the traffic should go to P=2 and P=0 and P=1 should - // not be in panic mode. - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 4 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 0, 100)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - ASSERT_THAT(getPanic(), ElementsAre(false, false, false)); - - // Levels P=0 and P=1 are totally down. P=2 is 40*1.4=56%% healthy. - // 100% of the traffic should go to P=2. All levels P=0, P=1 and P=2 should - // be in panic mode. - // Since all levels are in panic mode load distribution is based - // on number of hosts in each level. - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 2 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); - - // Level P=0 is totally degraded. P=1 is 40*1.4=56% healthy and 40*1.4=56% degraded. P=2 is - // 40*1.4=56%% healthy. 100% of the traffic should go to P=2. No priorities should be in panic - // mode. - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, - 5 /* num_degraded_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 2 /* num_healthy_hosts */, - 2 /* num_degraded_hosts */); - updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 2 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 56, 44)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - ASSERT_THAT(getPanic(), ElementsAre(false, false, false)); - - // All levels are completely down - situation called TotalPanic. - // Load is distributed based on the number - // of hosts in the priority in relation to the total number of hosts. - // Here the total number of hosts is 10. - // priority 0 will receive 5/10: 50% of the traffic - // priority 1 will receive 3/10: 30% of the traffic - // priority 2 will receive 2/10: 20% of the traffic - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 2 /* num_hosts */, 0 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(50, 30, 20)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); - - // Rounding errors should be picked up by the first priority. - // All priorities are in panic mode - situation called TotalPanic. - // Load is distributed based on the number - // of hosts in the priority in relation to the total number of hosts. - // Total number of hosts is 5+6+3=14. - // priority 0 should receive 5/14=37% of traffic - // priority 1 should receive 6/14=42% of traffic - // priority 2 should receive 3/14=21% of traffic - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 6 /* num_hosts */, 2 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 3 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(37, 42, 21)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); - - // Load should spill over into degraded. - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, - 1 /* num_degraded_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, - 5 /* num_degraded_hosts */); - updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 1 /* num_healthy_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 0, 28)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(28, 44, 0)); - - // Rounding errors should be picked up by the first priority with degraded hosts when - // there are no healthy priorities. - // Disable panic threshold to prevent total panic from kicking in. - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(0)); - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, - 2 /* num_degraded_hosts */); - updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, - 1 /* num_degraded_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 0, 0)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 67, 33)); - - // Simulate Total Panic mode. There is no healthy hosts, but there are - // degraded hosts. Because there is Total Panic, load is distributed - // based just on number of hosts in priorities regardless of its health. - // Rounding errors should be picked up by the first priority. - // Enable back panic threshold. - EXPECT_CALL(runtime_.snapshot_, getInteger("upstream.healthy_panic_threshold", 50)) - .WillRepeatedly(Return(50)); - updateHostSet(host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, - 2 /* num_degraded_hosts */); - updateHostSet(tertiary_host_set_, 5 /* num_hosts */, 0 /* num_healthy_hosts */, - 1 /* num_degraded_hosts */); - ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); - ASSERT_THAT(getDegradedLoadPercentage(), ElementsAre(0, 0, 0)); - - // Rounding error should be allocated to the first non-empty priority - // In this test P=0 is not empty. - updateHostSet(host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); - ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); - ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); - - // Rounding error should be allocated to the first non-empty priority - // In this test P=0 is empty and P=1 is not empty. - updateHostSet(host_set_, 0 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 6 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); - ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); - ASSERT_THAT(getLoadPercentage(), ElementsAre(0, 67, 33)); - // In this test P=1 is not empty. - updateHostSet(host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(failover_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); - updateHostSet(tertiary_host_set_, 3 /* num_hosts */, 0 /* num_healthy_hosts */); - ASSERT_THAT(getPanic(), ElementsAre(true, true, true)); - ASSERT_THAT(getLoadPercentage(), ElementsAre(34, 33, 33)); -} - -TEST_P(LoadBalancerBaseTest, BoundaryConditions) { - TestRandomGenerator rand; - uint32_t num_priorities = rand.random() % 10; - - for (uint32_t i = 0; i < num_priorities; ++i) { - uint32_t num_hosts = rand.random() % 100; - uint32_t healthy_hosts = std::min(num_hosts, rand.random() % 100); - // Make sure random health situations don't trigger the assert in recalculatePerPriorityState - updateHostSet(*priority_set_.getMockHostSet(i), num_hosts, healthy_hosts); - } -} - -class TestZoneAwareLb : public ZoneAwareLoadBalancerBase { -public: - TestZoneAwareLb(const PrioritySet& priority_set, ClusterLbStats& lb_stats, - Runtime::Loader& runtime, Random::RandomGenerator& random, - const envoy::config::cluster::v3::Cluster::CommonLbConfig& common_config) - : ZoneAwareLoadBalancerBase( - priority_set, nullptr, lb_stats, runtime, random, - PROTOBUF_PERCENT_TO_ROUNDED_INTEGER_OR_DEFAULT(common_config, healthy_panic_threshold, - 100, 50), - LoadBalancerConfigHelper::localityLbConfigFromCommonLbConfig(common_config)) {} - - HostConstSharedPtr chooseHostOnce(LoadBalancerContext*) override { - return choose_host_once_host_; - } - HostConstSharedPtr peekAnotherHost(LoadBalancerContext*) override { PANIC("not implemented"); } - - HostConstSharedPtr choose_host_once_host_{std::make_shared>()}; -}; - -// Used to test common functions of ZoneAwareLoadBalancerBase. -class ZoneAwareLoadBalancerBaseTest : public LoadBalancerTestBase { -public: - envoy::config::cluster::v3::Cluster::CommonLbConfig common_config_; - TestZoneAwareLb lb_{priority_set_, stats_, runtime_, random_, common_config_}; - TestZoneAwareLoadBalancer lbx_{priority_set_, stats_, runtime_, random_, common_config_}; -}; - -// Tests the source type static methods in zone aware load balancer. -TEST_F(ZoneAwareLoadBalancerBaseTest, SourceTypeMethods) { - { EXPECT_ENVOY_BUG(lbx_.runInvalidLocalitySourceType(), "unexpected locality source type enum"); } - - { EXPECT_ENVOY_BUG(lbx_.runInvalidSourceType(), "unexpected source type enum"); } -} - -TEST_F(ZoneAwareLoadBalancerBaseTest, BaseMethods) { - EXPECT_FALSE(lb_.lifetimeCallbacks().has_value()); - std::vector hash_key; - auto mock_host = std::make_shared>(); - EXPECT_FALSE(lb_.selectExistingConnection(nullptr, *mock_host, hash_key).has_value()); -} +using testing::Return; +using testing::ReturnRef; class RoundRobinLoadBalancerTest : public LoadBalancerTestBase { public: @@ -2837,635 +2142,6 @@ TEST_P(RoundRobinLoadBalancerTest, SlowStartNoWaitMinWeightPercent35) { EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); } -class LeastRequestLoadBalancerTest : public LoadBalancerTestBase { -public: - LeastRequestLoadBalancer lb_{ - priority_set_, nullptr, stats_, runtime_, random_, common_config_, least_request_lb_config_, - simTime()}; -}; - -TEST_P(LeastRequestLoadBalancerTest, NoHosts) { EXPECT_EQ(nullptr, lb_.chooseHost(nullptr)); } - -TEST_P(LeastRequestLoadBalancerTest, SingleHostAndPeek) { - hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}; - hostSet().hosts_ = hostSet().healthy_hosts_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - // Host weight is 1 and peek. - { - EXPECT_CALL(random_, random()).WillOnce(Return(0)); - EXPECT_EQ(nullptr, lb_.peekAnotherHost(nullptr)); - } -} - -TEST_P(LeastRequestLoadBalancerTest, SingleHost) { - hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}; - hostSet().hosts_ = hostSet().healthy_hosts_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - // Host weight is 1. - { - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); - } - - // Host weight is 100. - { - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); - } - - HostVector empty; - { - hostSet().runCallbacks(empty, empty); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); - } - - { - HostVector remove_hosts; - remove_hosts.push_back(hostSet().hosts_[0]); - hostSet().healthy_hosts_.clear(); - hostSet().hosts_.clear(); - hostSet().runCallbacks(empty, remove_hosts); - EXPECT_CALL(random_, random()).WillOnce(Return(0)); - EXPECT_EQ(nullptr, lb_.chooseHost(nullptr)); - } -} - -TEST_P(LeastRequestLoadBalancerTest, Normal) { - hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}; - hostSet().hosts_ = hostSet().healthy_hosts_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(2); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); - - hostSet().healthy_hosts_[0]->stats().rq_active_.set(2); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); - EXPECT_CALL(random_, random()).WillOnce(Return(0)).WillOnce(Return(2)).WillOnce(Return(3)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); -} - -TEST_P(LeastRequestLoadBalancerTest, PNC) { - hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:83", simTime())}; - hostSet().hosts_ = hostSet().healthy_hosts_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - hostSet().healthy_hosts_[0]->stats().rq_active_.set(4); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(3); - hostSet().healthy_hosts_[2]->stats().rq_active_.set(2); - hostSet().healthy_hosts_[3]->stats().rq_active_.set(1); - - // Creating various load balancer objects with different choice configs. - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; - lr_lb_config.mutable_choice_count()->set_value(2); - LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, - random_, common_config_, lr_lb_config, simTime()}; - lr_lb_config.mutable_choice_count()->set_value(5); - LeastRequestLoadBalancer lb_5{priority_set_, nullptr, stats_, runtime_, - random_, common_config_, lr_lb_config, simTime()}; - - // Verify correct number of choices. - - // 0 choices configured should default to P2C. - EXPECT_CALL(random_, random()).Times(3).WillRepeatedly(Return(0)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); - - // 2 choices configured results in P2C. - EXPECT_CALL(random_, random()).Times(3).WillRepeatedly(Return(0)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - - // 5 choices configured results in P5C. - EXPECT_CALL(random_, random()).Times(6).WillRepeatedly(Return(0)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_5.chooseHost(nullptr)); - - // Verify correct host chosen in P5C scenario. - EXPECT_CALL(random_, random()) - .Times(6) - .WillOnce(Return(0)) - .WillOnce(Return(3)) - .WillOnce(Return(0)) - .WillOnce(Return(3)) - .WillOnce(Return(2)) - .WillOnce(Return(1)); - EXPECT_EQ(hostSet().healthy_hosts_[3], lb_5.chooseHost(nullptr)); -} - -TEST_P(LeastRequestLoadBalancerTest, DefaultSelectionMethod) { - envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest lr_lb_config; - EXPECT_EQ(lr_lb_config.selection_method(), - envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest::N_CHOICES); -} - -TEST_P(LeastRequestLoadBalancerTest, FullScanOneHostWithLeastRequests) { - hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:83", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:84", simTime())}; - hostSet().hosts_ = hostSet().healthy_hosts_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - hostSet().healthy_hosts_[0]->stats().rq_active_.set(4); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(3); - hostSet().healthy_hosts_[2]->stats().rq_active_.set(2); - hostSet().healthy_hosts_[3]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[4]->stats().rq_active_.set(5); - - envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest lr_lb_config; - - // Enable FULL_SCAN on hosts. - lr_lb_config.set_selection_method( - envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest::FULL_SCAN); - - LeastRequestLoadBalancer lb{priority_set_, nullptr, stats_, runtime_, - random_, 1, lr_lb_config, simTime()}; - - // With FULL_SCAN we will always choose the host with least number of active requests. - EXPECT_EQ(hostSet().healthy_hosts_[3], lb.chooseHost(nullptr)); -} - -TEST_P(LeastRequestLoadBalancerTest, FullScanMultipleHostsWithLeastRequests) { - hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:82", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:83", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:84", simTime())}; - hostSet().hosts_ = hostSet().healthy_hosts_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - hostSet().healthy_hosts_[0]->stats().rq_active_.set(3); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(3); - hostSet().healthy_hosts_[2]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[3]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[4]->stats().rq_active_.set(1); - - envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest lr_lb_config; - - // Enable FULL_SCAN on hosts. - lr_lb_config.set_selection_method( - envoy::extensions::load_balancing_policies::least_request::v3::LeastRequest::FULL_SCAN); - - auto random = Random::RandomGeneratorImpl(); - - LeastRequestLoadBalancer lb{priority_set_, nullptr, stats_, runtime_, - random, 1, lr_lb_config, simTime()}; - - // Make 1 million selections. Then, check that the selection probability is - // approximately equal among the 3 hosts tied for least requests. - // Accept a +/-0.5% deviation from the expected selection probability (33.3..%). - size_t num_selections = 1000000; - size_t expected_approx_selections_per_tied_host = num_selections / 3; - size_t abs_error = 5000; - - size_t host_2_counts = 0; - size_t host_3_counts = 0; - size_t host_4_counts = 0; - - for (size_t i = 0; i < num_selections; ++i) { - auto selected_host = lb.chooseHost(nullptr); - - if (selected_host == hostSet().healthy_hosts_[2]) { - ++host_2_counts; - } else if (selected_host == hostSet().healthy_hosts_[3]) { - ++host_3_counts; - } else if (selected_host == hostSet().healthy_hosts_[4]) { - ++host_4_counts; - } else { - FAIL() << "Must only select hosts with least requests"; - } - } - - EXPECT_NEAR(expected_approx_selections_per_tied_host, host_2_counts, abs_error); - EXPECT_NEAR(expected_approx_selections_per_tied_host, host_3_counts, abs_error); - EXPECT_NEAR(expected_approx_selections_per_tied_host, host_4_counts, abs_error); -} - -TEST_P(LeastRequestLoadBalancerTest, WeightImbalance) { - 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_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); - - // We should see 2:1 ratio for hosts[1] to hosts[0]. - 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)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); - - // Bringing hosts[1] to an active request should yield a 1:1 ratio. - hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); - 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_[0], lb_.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); - - // Settings hosts[0] to an active request and hosts[1] to no active requests should yield a 4:1 - // ratio. - hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(0); - 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_[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)); - 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)); -} - -// Validate that the load balancer defaults to an active request bias value of 1.0 if the runtime -// value is invalid (less than 0.0). -TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceWithInvalidActiveRequestBias) { - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; - lr_lb_config.mutable_active_request_bias()->set_runtime_key("ar_bias"); - lr_lb_config.mutable_active_request_bias()->set_default_value(1.0); - LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, - random_, common_config_, lr_lb_config, simTime()}; - - EXPECT_CALL(runtime_.snapshot_, getDouble("ar_bias", 1.0)).WillRepeatedly(Return(-1.0)); - - 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_; - - // Trigger callbacks. The added/removed lists are not relevant. - EXPECT_LOG_CONTAINS( - "warn", "upstream: invalid active request bias supplied (runtime key ar_bias), using 1.0", - hostSet().runCallbacks({}, {})); - - EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); - - // We should see 2:1 ratio for hosts[1] to hosts[0]. - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - - // Bringing hosts[1] to an active request should yield a 1:1 ratio. - hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - - // Settings hosts[0] to an active request and hosts[1] to no active requests should yield a 4:1 - // ratio. - hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(0); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); -} - -TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceWithCustomActiveRequestBias) { - // Create a load balancer with a custom active request bias. - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; - lr_lb_config.mutable_active_request_bias()->set_runtime_key("ar_bias"); - lr_lb_config.mutable_active_request_bias()->set_default_value(1.0); - LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, - random_, common_config_, lr_lb_config, simTime()}; - - EXPECT_CALL(runtime_.snapshot_, getDouble("ar_bias", 1.0)).WillRepeatedly(Return(0.0)); - - 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_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); - - // We should see 2:1 ratio for hosts[1] to hosts[0], regardless of the active request count. - hostSet().healthy_hosts_[1]->stats().rq_active_.set(1); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); -} - -TEST_P(LeastRequestLoadBalancerTest, WeightImbalanceCallbacks) { - 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_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - EXPECT_CALL(random_, random()).WillRepeatedly(Return(0)); - - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); - - // Remove and verify we get other host. - HostVector empty; - HostVector hosts_removed; - hosts_removed.push_back(hostSet().hosts_[1]); - hostSet().hosts_.erase(hostSet().hosts_.begin() + 1); - hostSet().healthy_hosts_.erase(hostSet().healthy_hosts_.begin() + 1); - hostSet().runCallbacks(empty, hosts_removed); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_.chooseHost(nullptr)); -} - -TEST_P(LeastRequestLoadBalancerTest, SlowStartWithDefaultParams) { - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; - LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, - random_, common_config_, lr_lb_config, simTime()}; - const auto slow_start_window = - EdfLoadBalancerBasePeer::slowStartWindow(static_cast(lb_2)); - EXPECT_EQ(std::chrono::milliseconds(0), slow_start_window); - const auto latest_host_added_time = - EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); - EXPECT_EQ(std::chrono::milliseconds(0), latest_host_added_time); - const auto slow_start_min_weight_percent = - EdfLoadBalancerBasePeer::slowStartMinWeightPercent(static_cast(lb_2)); - EXPECT_DOUBLE_EQ(slow_start_min_weight_percent, 0.1); -} - -TEST_P(LeastRequestLoadBalancerTest, SlowStartNoWait) { - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; - lr_lb_config.mutable_slow_start_config()->mutable_slow_start_window()->set_seconds(60); - lr_lb_config.mutable_active_request_bias()->set_runtime_key("ar_bias"); - lr_lb_config.mutable_active_request_bias()->set_default_value(1.0); - LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, - random_, common_config_, lr_lb_config, simTime()}; - simTime().advanceTimeWait(std::chrono::seconds(1)); - - // As no healthcheck is configured, hosts would enter slow start immediately. - hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime())}; - hostSet().hosts_ = hostSet().healthy_hosts_; - hostSet().runCallbacks({}, {}); - // Host1 is 5 secs in slow start, its weight is scaled with max((5/60)^1, 0.1)=0.1 factor. - simTime().advanceTimeWait(std::chrono::seconds(5)); - - auto latest_host_added_time = - EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); - EXPECT_EQ(std::chrono::milliseconds(1000), latest_host_added_time); - - // Advance time, so that host is no longer in slow start. - simTime().advanceTimeWait(std::chrono::seconds(56)); - - auto host2 = makeTestHost(info_, "tcp://127.0.0.1:90", simTime()); - hostSet().healthy_hosts_.push_back(host2); - hostSet().hosts_ = hostSet().healthy_hosts_; - HostVector hosts_added; - hosts_added.push_back(host2); - - hostSet().runCallbacks(hosts_added, {}); - - latest_host_added_time = - EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); - EXPECT_EQ(std::chrono::milliseconds(62000), latest_host_added_time); - - // host2 is 10 secs in slow start, the weight is scaled with time factor max(10/60, 0.1) = 0.16. - simTime().advanceTimeWait(std::chrono::seconds(10)); - - // Recalculate weights. - hostSet().runCallbacks({}, {}); - - hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(0); - // We expect 3:1 ratio, as host2 is in slow start mode and it's weight is scaled with - // 0.16 factor and host1 weight with 0.5 factor (due to active request bias). - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - - // host2 is 40 secs in slow start, the weight is scaled with time factor max(40/60, 0.1) = 0.66. - simTime().advanceTimeWait(std::chrono::seconds(30)); - // Recalculate weights. - hostSet().runCallbacks({}, {}); - - // We expect 4:3 ratio, as host2 is in slow start mode and it's weight is scaled with - // 0.66 factor and host1 weight with 0.5 factor. - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); -} - -TEST_P(LeastRequestLoadBalancerTest, SlowStartWithActiveHC) { - envoy::config::cluster::v3::Cluster::LeastRequestLbConfig lr_lb_config; - lr_lb_config.mutable_slow_start_config()->mutable_slow_start_window()->set_seconds(10); - lr_lb_config.mutable_slow_start_config()->mutable_aggression()->set_runtime_key("aggression"); - lr_lb_config.mutable_slow_start_config()->mutable_aggression()->set_default_value(0.9); - lr_lb_config.mutable_active_request_bias()->set_runtime_key("ar_bias"); - lr_lb_config.mutable_active_request_bias()->set_default_value(0.9); - - LeastRequestLoadBalancer lb_2{priority_set_, nullptr, stats_, runtime_, - random_, common_config_, lr_lb_config, simTime()}; - - simTime().advanceTimeWait(std::chrono::seconds(1)); - auto host1 = makeTestHost(info_, "tcp://127.0.0.1:80", simTime()); - host1->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); - host_set_.hosts_ = {host1}; - HostVector hosts_added; - hosts_added.push_back(host1); - simTime().advanceTimeWait(std::chrono::seconds(1)); - hostSet().runCallbacks(hosts_added, {}); - auto latest_host_added_time = - EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); - EXPECT_EQ(std::chrono::milliseconds(0), latest_host_added_time); - simTime().advanceTimeWait(std::chrono::seconds(5)); - - hosts_added.clear(); - auto host2 = makeTestHost(info_, "tcp://127.0.0.1:90", simTime()); - hosts_added.push_back(host2); - - hostSet().healthy_hosts_ = {host1, host2}; - hostSet().hosts_ = hostSet().healthyHosts(); - hostSet().runCallbacks(hosts_added, {}); - latest_host_added_time = - EdfLoadBalancerBasePeer::latestHostAddedTime(static_cast(lb_2)); - EXPECT_EQ(std::chrono::milliseconds(7000), latest_host_added_time); - - simTime().advanceTimeWait(std::chrono::seconds(1)); - host1->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); - host1->setLastHcPassTime(simTime().monotonicTime()); - hostSet().healthy_hosts_ = {host1, host2}; - - hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(0); - hostSet().hosts_ = hostSet().healthyHosts(); - simTime().advanceTimeWait(std::chrono::seconds(7)); - // Trigger callbacks to add host1 to slow start mode. - hostSet().runCallbacks({}, {}); - // We expect 9:4 ratio, as host1 is 7 sec in slow start mode, its weight is scaled with active - // request bias and time factor 0.53 * max(pow(0.7, 1.11), 0.1)=0.35. Host2 is 8 seconds in slow - // start and its weight is scaled with time bias max(pow(0.7, 1.11), 0.1) = 0.78. - - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - - simTime().advanceTimeWait(std::chrono::seconds(1)); - host2->healthFlagSet(Host::HealthFlag::FAILED_ACTIVE_HC); - // Trigger callbacks to remove host2 from slow start mode. - hostSet().runCallbacks({}, {}); - hostSet().healthy_hosts_[0]->stats().rq_active_.set(1); - hostSet().healthy_hosts_[1]->stats().rq_active_.set(2); - - // We expect 6:5 ratio, as host1 is 8 seconds in slow start, its weight is scaled with active - // request bias and time factor 0.53 * max(pow(0.8, 1.11), 0.1)=0.41. Host2 is not in slow start - // and its weight is scaled with active request bias = 0.37. - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - - EXPECT_CALL(runtime_.snapshot_, getDouble("aggression", 0.9)).WillRepeatedly(Return(1.0)); - EXPECT_CALL(runtime_.snapshot_, getDouble("ar_bias", 0.9)).WillRepeatedly(Return(1.0)); - simTime().advanceTimeWait(std::chrono::seconds(20)); - host2->healthFlagClear(Host::HealthFlag::FAILED_ACTIVE_HC); - host2->setLastHcPassTime(simTime().monotonicTime()); - hostSet().healthy_hosts_ = {host1, host2}; - hostSet().hosts_ = hostSet().healthyHosts(); - simTime().advanceTimeWait(std::chrono::seconds(5)); - hostSet().healthy_hosts_[0]->stats().rq_active_.dec(); - hostSet().healthy_hosts_[1]->stats().rq_active_.dec(); - hostSet().healthy_hosts_[1]->stats().rq_active_.dec(); - // Trigger callbacks to remove host1 from slow start. Host2 re-enters slow start due to HC pass. - hostSet().runCallbacks({}, {}); - // We expect 2:1 ratio, as host2 is in slow start mode, its weight is scaled with time factor - // max(pow(0.5, 1), 0.1)= 0.5. Host1 weight is 1. - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_2.chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_2.chooseHost(nullptr)); -} - -INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, LeastRequestLoadBalancerTest, - ::testing::Values(LoadBalancerTestParam{true, false}, - LoadBalancerTestParam{true, true}, - LoadBalancerTestParam{false, false}, - LoadBalancerTestParam{false, true})); - -class RandomLoadBalancerTest : public LoadBalancerTestBase { -public: - void init() { - lb_ = std::make_shared(priority_set_, nullptr, stats_, runtime_, random_, - common_config_); - } - std::shared_ptr lb_; -}; - -TEST_P(RandomLoadBalancerTest, NoHosts) { - init(); - - EXPECT_EQ(nullptr, lb_->peekAnotherHost(nullptr)); - EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); -} - -TEST_P(RandomLoadBalancerTest, Normal) { - init(); - hostSet().healthy_hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}; - hostSet().hosts_ = hostSet().healthy_hosts_; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - - EXPECT_CALL(random_, random()).WillOnce(Return(2)); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->peekAnotherHost(nullptr)); - - EXPECT_CALL(random_, random()).WillOnce(Return(3)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->peekAnotherHost(nullptr)); - - EXPECT_CALL(random_, random()).Times(0); - EXPECT_EQ(hostSet().healthy_hosts_[0], lb_->chooseHost(nullptr)); - EXPECT_EQ(hostSet().healthy_hosts_[1], lb_->chooseHost(nullptr)); -} - -TEST_P(RandomLoadBalancerTest, FailClusterOnPanic) { - common_config_.mutable_zone_aware_lb_config()->set_fail_traffic_on_panic(true); - init(); - - hostSet().healthy_hosts_ = {}; - hostSet().hosts_ = {makeTestHost(info_, "tcp://127.0.0.1:80", simTime()), - makeTestHost(info_, "tcp://127.0.0.1:81", simTime())}; - hostSet().runCallbacks({}, {}); // Trigger callbacks. The added/removed lists are not relevant. - EXPECT_EQ(nullptr, lb_->chooseHost(nullptr)); -} - -INSTANTIATE_TEST_SUITE_P(PrimaryOrFailoverAndLegacyOrNew, RandomLoadBalancerTest, - ::testing::Values(LoadBalancerTestParam{true, false}, - LoadBalancerTestParam{true, true}, - LoadBalancerTestParam{false, false}, - LoadBalancerTestParam{false, true})); - -TEST(LoadBalancerContextBaseTest, LoadBalancerContextBaseTest) { - { - LoadBalancerContextBase context; - MockPrioritySet mock_priority_set; - HealthyAndDegradedLoad priority_load{Upstream::HealthyLoad({100, 0, 0}), - Upstream::DegradedLoad({0, 0, 0})}; - RetryPriority::PriorityMappingFunc empty_func = - [](const Upstream::HostDescription&) -> absl::optional { return absl::nullopt; }; - MockHost mock_host; - - EXPECT_EQ(absl::nullopt, context.computeHashKey()); - EXPECT_EQ(nullptr, context.downstreamConnection()); - EXPECT_EQ(nullptr, context.metadataMatchCriteria()); - EXPECT_EQ(nullptr, context.downstreamHeaders()); - - EXPECT_EQ(&priority_load, - &(context.determinePriorityLoad(mock_priority_set, priority_load, empty_func))); - EXPECT_EQ(false, context.shouldSelectAnotherHost(mock_host)); - EXPECT_EQ(1, context.hostSelectionRetryCount()); - EXPECT_EQ(nullptr, context.upstreamSocketOptions()); - EXPECT_EQ(nullptr, context.upstreamTransportSocketOptions()); - EXPECT_EQ(absl::nullopt, context.overrideHostToSelect()); - } -} - } // namespace } // namespace Upstream } // namespace Envoy diff --git a/test/common/upstream/round_robin_load_balancer_corpus/aggression_with_many_hosts b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/aggression_with_many_hosts similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/aggression_with_many_hosts rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/aggression_with_many_hosts diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin-clusterfuzz-testcase-round_robin_load_balancer_fuzz_test-5193127549468672 diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin-high-number-of-hosts diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin-with-locality-high-number-of-hosts diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin_local_priority similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin_local_priority diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin_local_priority_update_hosts diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin_no_hosts similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/round_robin_no_hosts rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin_no_hosts diff --git a/test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin_normal similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/round_robin_normal rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/round_robin_normal diff --git a/test/common/upstream/round_robin_load_balancer_corpus/small_aggression b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/small_aggression similarity index 100% rename from test/common/upstream/round_robin_load_balancer_corpus/small_aggression rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_corpus/small_aggression diff --git a/test/common/upstream/round_robin_load_balancer_fuzz.proto b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_fuzz.proto similarity index 81% rename from test/common/upstream/round_robin_load_balancer_fuzz.proto rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_fuzz.proto index 60da8d643768..83f0cc540e01 100644 --- a/test/common/upstream/round_robin_load_balancer_fuzz.proto +++ b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_fuzz.proto @@ -5,7 +5,7 @@ package test.common.upstream; import "validate/validate.proto"; import "envoy/config/cluster/v3/cluster.proto"; -import "test/common/upstream/zone_aware_load_balancer_fuzz.proto"; +import "test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz.proto"; message RoundRobinLoadBalancerTestCase { test.common.upstream.ZoneAwareLoadBalancerTestCase zone_aware_load_balancer_test_case = 1 diff --git a/test/common/upstream/round_robin_load_balancer_fuzz_test.cc b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_fuzz_test.cc similarity index 92% rename from test/common/upstream/round_robin_load_balancer_fuzz_test.cc rename to test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_fuzz_test.cc index 19540c30c279..b1ff4e6919ff 100644 --- a/test/common/upstream/round_robin_load_balancer_fuzz_test.cc +++ b/test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_fuzz_test.cc @@ -4,8 +4,8 @@ #include "source/extensions/load_balancing_policies/round_robin/round_robin_lb.h" -#include "test/common/upstream/round_robin_load_balancer_fuzz.pb.validate.h" -#include "test/common/upstream/zone_aware_load_balancer_fuzz_base.h" +#include "test/extensions/load_balancing_policies/common/zone_aware_load_balancer_fuzz_base.h" +#include "test/extensions/load_balancing_policies/round_robin/round_robin_load_balancer_fuzz.pb.validate.h" #include "test/fuzz/fuzz_runner.h" #include "test/test_common/utility.h" diff --git a/test/extensions/load_balancing_policies/subset/subset_benchmark.cc b/test/extensions/load_balancing_policies/subset/subset_benchmark.cc index 6f3b454ce9d9..eaeaec500268 100644 --- a/test/extensions/load_balancing_policies/subset/subset_benchmark.cc +++ b/test/extensions/load_balancing_policies/subset/subset_benchmark.cc @@ -27,7 +27,7 @@ namespace LoadBalancingPolices { namespace Subset { namespace { -class SubsetLbTester : public LoadBalancingPolices::Common::BaseTester { +class SubsetLbTester : public Upstream::BaseTester { public: SubsetLbTester(uint64_t num_hosts, bool single_host_per_subset) : BaseTester(num_hosts, 0, 0, true /* attach metadata */) { 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 d6275409c502..f2e070c6783e 100644 --- a/test/extensions/network/dns_resolver/cares/dns_impl_test.cc +++ b/test/extensions/network/dns_resolver/cares/dns_impl_test.cc @@ -1095,18 +1095,11 @@ TEST_P(DnsImplTest, CallbackException) { checkStats(1 /*resolve_total*/, 0 /*pending_resolutions*/, 0 /*not_found*/, 0 /*get_addr_failure*/, 0 /*timeouts*/); - EXPECT_EQ(nullptr, resolveWithException("1.2.3.4", DnsLookupFamily::V4Only, - std::runtime_error("runtime error"))); - EXPECT_THROW_WITH_MESSAGE(dispatcher_->run(Event::Dispatcher::RunType::Block), EnvoyException, - "runtime error"); - checkStats(2 /*resolve_total*/, 0 /*pending_resolutions*/, 0 /*not_found*/, - 0 /*get_addr_failure*/, 0 /*timeouts*/); - EXPECT_EQ(nullptr, resolveWithException("1.2.3.4", DnsLookupFamily::V4Only, std::string())); EXPECT_THROW_WITH_MESSAGE(dispatcher_->run(Event::Dispatcher::RunType::Block), EnvoyException, "unknown"); - checkStats(3 /*resolve_total*/, 0 /*pending_resolutions*/, 0 /*not_found*/, + checkStats(2 /*resolve_total*/, 0 /*pending_resolutions*/, 0 /*not_found*/, 0 /*get_addr_failure*/, 0 /*timeouts*/); } diff --git a/test/extensions/transport_sockets/http_11_proxy/connect_test.cc b/test/extensions/transport_sockets/http_11_proxy/connect_test.cc index 1b032484daf0..291a6e10f60b 100644 --- a/test/extensions/transport_sockets/http_11_proxy/connect_test.cc +++ b/test/extensions/transport_sockets/http_11_proxy/connect_test.cc @@ -11,6 +11,7 @@ #include "test/mocks/ssl/mocks.h" #include "test/test_common/environment.h" #include "test/test_common/network_utility.h" +#include "test/test_common/test_runtime.h" #include "test/test_common/utility.h" #include "gmock/gmock.h" @@ -433,6 +434,9 @@ TEST(ParseTest, TestValidResponse) { // The SelfContainedParser is only intended for header parsing but for coverage, // test a request with a body. TEST(ParseTest, CoverResponseBodyHttp10) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.http1_balsa_delay_reset", "true"}}); + std::string headers = "HTTP/1.0 200 OK\r\ncontent-length: 2\r\n\r\n"; std::string body = "ab"; @@ -450,6 +454,9 @@ TEST(ParseTest, CoverResponseBodyHttp10) { } TEST(ParseTest, CoverResponseBodyHttp11) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.http1_balsa_delay_reset", "true"}}); + std::string headers = "HTTP/1.1 200 OK\r\ncontent-length: 2\r\n\r\n"; std::string body = "ab"; @@ -468,6 +475,9 @@ TEST(ParseTest, CoverResponseBodyHttp11) { // Regression tests for #34096. TEST(ParseTest, ContentLengthZeroHttp10) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.http1_balsa_delay_reset", "true"}}); + constexpr absl::string_view headers = "HTTP/1.0 200 OK\r\ncontent-length: 0\r\n\r\n"; SelfContainedParser parser; @@ -483,6 +493,9 @@ TEST(ParseTest, ContentLengthZeroHttp10) { } TEST(ParseTest, ContentLengthZeroHttp11) { + TestScopedRuntime scoped_runtime; + scoped_runtime.mergeValues({{"envoy.reloadable_features.http1_balsa_delay_reset", "true"}}); + constexpr absl::string_view headers = "HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n"; SelfContainedParser parser; diff --git a/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc b/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc index e22289f995e3..288b8953814a 100644 --- a/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc +++ b/test/extensions/transport_sockets/starttls/upstream_starttls_integration_test.cc @@ -3,7 +3,7 @@ #include "source/common/network/connection_impl.h" #include "source/common/tls/context_config_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "source/extensions/filters/network/common/factory_base.h" #include "source/extensions/transport_sockets/starttls/starttls_socket.h" diff --git a/test/extensions/upstreams/http/config_test.cc b/test/extensions/upstreams/http/config_test.cc index c851fa63db32..4e443f8fcafe 100644 --- a/test/extensions/upstreams/http/config_test.cc +++ b/test/extensions/upstreams/http/config_test.cc @@ -85,11 +85,10 @@ TEST_F(ConfigTest, AutoHttp3) { TEST_F(ConfigTest, AutoHttp3NoCache) { options_.mutable_auto_config(); options_.mutable_auto_config()->mutable_http3_protocol_options(); - EXPECT_THROW_WITH_MESSAGE( - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_).value(), - EnvoyException, - "alternate protocols cache must be configured when HTTP/3 is enabled with auto_config"); + EXPECT_EQ(ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .message(), + "alternate protocols cache must be configured when HTTP/3 is enabled with auto_config"); } TEST_F(ConfigTest, KvStoreConcurrencyFail) { @@ -99,10 +98,10 @@ TEST_F(ConfigTest, KvStoreConcurrencyFail) { ->mutable_alternate_protocols_cache_options() ->mutable_key_value_store_config(); server_context_.options_.concurrency_ = 2; - EXPECT_THROW_WITH_MESSAGE( - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_).value(), - EnvoyException, + EXPECT_EQ( + ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .message(), "options has key value store but Envoy has concurrency = 2 : key_value_store_config {\n}\n"); } @@ -196,13 +195,9 @@ TEST_F(ConfigTest, HeaderValidatorConfig) { ::Envoy::Http::Protocol::Http2, stats)); #else // If UHV is disabled, providing config should result in rejection - EXPECT_THROW( - { - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) - .value(); - }, - EnvoyException); + EXPECT_FALSE(ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .ok()); #endif } @@ -227,13 +222,9 @@ TEST_F(ConfigTest, HeaderValidatorConfigWithRuntimeDisabled) { EXPECT_EQ(nullptr, config->header_validator_factory_); #else // If UHV is disabled, providing config should result in rejection - EXPECT_THROW( - { - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) - .value(); - }, - EnvoyException); + EXPECT_FALSE(ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .ok()); #endif } @@ -256,13 +247,9 @@ TEST_F(ConfigTest, DefaultHeaderValidatorConfigWithRuntimeEnabled) { #else // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the // config is rejected - EXPECT_THROW( - { - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) - .value(); - }, - EnvoyException); + EXPECT_FALSE(ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .ok()); #endif } @@ -305,13 +292,9 @@ TEST_F(ConfigTest, TranslateDownstreamLegacyConfigToDefaultHeaderValidatorConfig #else // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the // config is rejected - EXPECT_THROW( - { - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) - .value(); - }, - EnvoyException); + EXPECT_FALSE(ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .ok()); #endif } @@ -341,13 +324,9 @@ TEST_F(ConfigTest, TranslateAutoLegacyConfigToDefaultHeaderValidatorConfig) { #else // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the // config is rejected - EXPECT_THROW( - { - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) - .value(); - }, - EnvoyException); + EXPECT_FALSE(ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .ok()); #endif } @@ -377,13 +356,9 @@ TEST_F(ConfigTest, TranslateExplicitLegacyConfigToDefaultHeaderValidatorConfig) #else // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the // config is rejected - EXPECT_THROW( - { - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) - .value(); - }, - EnvoyException); + EXPECT_FALSE(ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .ok()); #endif } @@ -413,13 +388,9 @@ TEST_F(ConfigTest, TranslateExplicitH2LegacyConfigToDefaultHeaderValidatorConfig #else // If UHV is disabled but envoy.reloadable_features.enable_universal_header_validator is set, the // config is rejected - EXPECT_THROW( - { - std::shared_ptr config = - ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) - .value(); - }, - EnvoyException); + EXPECT_FALSE(ProtocolOptionsConfigImpl::createProtocolOptionsConfig(options_, server_context_) + .status() + .ok()); #endif } diff --git a/test/integration/BUILD b/test/integration/BUILD index 555d93c0121d..76fd743bcc1f 100644 --- a/test/integration/BUILD +++ b/test/integration/BUILD @@ -1827,6 +1827,8 @@ envoy_cc_test( ":tcp_tunneling_integration_lib", "//source/extensions/filters/network/tcp_proxy:config", "//source/extensions/upstreams/http/tcp:config", + "//test/integration/filters:add_header_filter_config_lib", + "//test/integration/filters:add_header_filter_proto_cc_proto", "@envoy_api//envoy/config/bootstrap/v3:pkg_cc_proto", "@envoy_api//envoy/config/core/v3:pkg_cc_proto", "@envoy_api//envoy/extensions/access_loggers/file/v3:pkg_cc_proto", diff --git a/test/integration/alpn_selection_integration_test.cc b/test/integration/alpn_selection_integration_test.cc index ed4bdef8ee0d..07fcb5cc62b0 100644 --- a/test/integration/alpn_selection_integration_test.cc +++ b/test/integration/alpn_selection_integration_test.cc @@ -5,7 +5,7 @@ #include "source/common/http/utility.h" #include "source/common/tls/context_config_impl.h" #include "source/common/tls/context_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "test/integration/http_integration.h" diff --git a/test/integration/base_integration_test.cc b/test/integration/base_integration_test.cc index baab4dd3be91..4fd831a6d23a 100644 --- a/test/integration/base_integration_test.cc +++ b/test/integration/base_integration_test.cc @@ -19,7 +19,7 @@ #include "source/common/event/libevent.h" #include "source/common/network/utility.h" #include "source/common/tls/context_config_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "source/server/proto_descriptors.h" #include "test/integration/utility.h" diff --git a/test/integration/health_check_integration_test.cc b/test/integration/health_check_integration_test.cc index caf8d559760c..2ca266e82611 100644 --- a/test/integration/health_check_integration_test.cc +++ b/test/integration/health_check_integration_test.cc @@ -568,6 +568,44 @@ class TcpHealthCheckIntegrationTest : public Event::TestUsingSimulatedTime, ASSERT_TRUE(cluster_data.host_fake_raw_connection_->waitForData( FakeRawConnection::waitForInexactMatch("Ping"))); } + + void + initProxyProtoHealthCheck(uint32_t cluster_idx, + envoy::config::core::v3::ProxyProtocolConfig proxy_protocol_config) { + auto& cluster_data = clusters_[cluster_idx]; + auto health_check = addHealthCheck(cluster_data.cluster_); + health_check->mutable_tcp_health_check()->mutable_send()->set_text("50696E67"); // "Ping" + health_check->mutable_tcp_health_check()->add_receive()->set_text("506F6E67"); // "Pong" + health_check->mutable_tcp_health_check()->mutable_proxy_protocol_config()->CopyFrom( + proxy_protocol_config); + + // Introduce the cluster using compareDiscoveryRequest / sendDiscoveryResponse. + EXPECT_TRUE(compareDiscoveryRequest(Config::TypeUrl::get().Cluster, "", {}, {}, {}, true)); + sendDiscoveryResponse( + Config::TypeUrl::get().Cluster, {cluster_data.cluster_}, {cluster_data.cluster_}, {}, "55"); + + // Wait for upstream to receive TCP HC request. + ASSERT_TRUE( + cluster_data.host_upstream_->waitForRawConnection(cluster_data.host_fake_raw_connection_)); + if (proxy_protocol_config.version() == + envoy::config::core::v3::ProxyProtocolConfig_Version_V1) { + ASSERT_TRUE( + cluster_data.host_fake_raw_connection_->waitForData([](const std::string& data) -> bool { + if (GetParam() == Network::Address::IpVersion::v4) { + return data.find("Ping") != std::string::npos && + data.find("PROXY TCP4 127.0.0.1 127.0.0.1") != std::string::npos; + } + return data.find("Ping") != std::string::npos && + data.find("PROXY TCP6 ::1 ::1") != std::string::npos; + })); + } else { + // ProxyProtocol Signature + Local Command + "Ping" + const char header[] = {0x0d, 0x0a, 0x0d, 0x0a, 0x00, 0x0d, 0x0a, 0x51, 0x55, 0x49, + 0x54, 0x0a, 0x20, 0x00, 0x00, 0x00, 0x50, 0x69, 0x6e, 0x67}; + ASSERT_TRUE(cluster_data.host_fake_raw_connection_->waitForData( + FakeRawConnection::waitForInexactMatch(std::string(header).c_str()))); + } + } }; INSTANTIATE_TEST_SUITE_P(IpVersions, TcpHealthCheckIntegrationTest, @@ -607,6 +645,39 @@ TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointWrongResponseTcp) { EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); } +// Tests that a healthy endpoint returns a valid TCP health check response with ProxyProtocol. +TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointHealthyTcpWithProxyProtocolV1) { + envoy::config::core::v3::ProxyProtocolConfig proxy_protocol_config; + proxy_protocol_config.set_version(envoy::config::core::v3::ProxyProtocolConfig_Version_V1); + + const uint32_t cluster_idx = 0; + initialize(); + initProxyProtoHealthCheck(cluster_idx, proxy_protocol_config); + + AssertionResult result = clusters_[cluster_idx].host_fake_raw_connection_->write("Pong"); + RELEASE_ASSERT(result, result.message()); + + test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); + EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); +} + +TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointHealthyTcpWithProxyProtocolV2) { + envoy::config::core::v3::ProxyProtocolConfig proxy_protocol_config; + proxy_protocol_config.set_version(envoy::config::core::v3::ProxyProtocolConfig_Version_V2); + + const uint32_t cluster_idx = 0; + initialize(); + initProxyProtoHealthCheck(cluster_idx, proxy_protocol_config); + + AssertionResult result = clusters_[cluster_idx].host_fake_raw_connection_->write("Pong"); + RELEASE_ASSERT(result, result.message()); + + test_server_->waitForCounterGe("cluster.cluster_1.health_check.success", 1); + EXPECT_EQ(1, test_server_->counter("cluster.cluster_1.health_check.success")->value()); + EXPECT_EQ(0, test_server_->counter("cluster.cluster_1.health_check.failure")->value()); +} + // Tests that no TCP health check response results in timeout and unhealthy endpoint. TEST_P(TcpHealthCheckIntegrationTest, SingleEndpointTimeoutTcp) { const uint32_t cluster_idx = 0; diff --git a/test/integration/http_integration.cc b/test/integration/http_integration.cc index 5a00fa621b12..5a83d26a03a6 100644 --- a/test/integration/http_integration.cc +++ b/test/integration/http_integration.cc @@ -34,7 +34,8 @@ #include "source/common/tls/context_config_impl.h" #include "source/common/tls/context_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/client_ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "test/common/upstream/utility.h" #include "test/integration/autonomous_upstream.h" diff --git a/test/integration/quic_http_integration_test.cc b/test/integration/quic_http_integration_test.cc index b2c4e7a4cd05..9d90bb044204 100644 --- a/test/integration/quic_http_integration_test.cc +++ b/test/integration/quic_http_integration_test.cc @@ -20,6 +20,7 @@ #include "source/common/quic/envoy_quic_proof_verifier.h" #include "source/common/quic/envoy_quic_utils.h" #include "source/common/quic/quic_client_transport_socket_factory.h" +#include "source/common/tls/client_context_impl.h" #include "source/common/tls/context_config_impl.h" #include "test/common/config/dummy_config.pb.h" diff --git a/test/integration/sds_dynamic_integration_test.cc b/test/integration/sds_dynamic_integration_test.cc index c3b8cb52fb89..261146c6bcf7 100644 --- a/test/integration/sds_dynamic_integration_test.cc +++ b/test/integration/sds_dynamic_integration_test.cc @@ -21,7 +21,7 @@ #include "source/common/tls/context_config_impl.h" #include "source/common/tls/context_manager_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "test/common/grpc/grpc_client_integration.h" #include "test/config/integration/certs/clientcert_hash.h" diff --git a/test/integration/ssl_utility.cc b/test/integration/ssl_utility.cc index de35d4e3da51..56c92fa8b770 100644 --- a/test/integration/ssl_utility.cc +++ b/test/integration/ssl_utility.cc @@ -5,9 +5,10 @@ #include "source/common/http/utility.h" #include "source/common/json/json_loader.h" #include "source/common/network/utility.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_config_impl.h" #include "source/common/tls/context_manager_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "test/config/utility.h" #include "test/integration/server.h" diff --git a/test/integration/tcp_tunneling_integration_test.cc b/test/integration/tcp_tunneling_integration_test.cc index 07c5740489df..63760daba054 100644 --- a/test/integration/tcp_tunneling_integration_test.cc +++ b/test/integration/tcp_tunneling_integration_test.cc @@ -6,6 +6,7 @@ #include "envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.pb.h" #include "envoy/extensions/upstreams/http/tcp/v3/tcp_connection_pool.pb.h" +#include "test/integration/filters/add_header_filter.pb.h" #include "test/integration/http_integration.h" #include "test/integration/http_protocol_integration.h" #include "test/integration/tcp_tunneling_integration.h" @@ -716,6 +717,8 @@ TEST_P(ProxyingConnectIntegrationTest, 2xxStatusCode) { cleanupUpstreamAndDownstream(); } +using HttpFilterProto = + envoy::extensions::filters::network::http_connection_manager::v3::HttpFilter; // Tunneling downstream TCP over an upstream HTTP CONNECT tunnel. class TcpTunnelingIntegrationTest : public BaseTcpTunnelingIntegrationTest { public: @@ -743,6 +746,39 @@ class TcpTunnelingIntegrationTest : public BaseTcpTunnelingIntegrationTest { BaseTcpTunnelingIntegrationTest::SetUp(); } + void addHttpUpstreamFilterToCluster(const HttpFilterProto& config) { + config_helper_.addConfigModifier([config](envoy::config::bootstrap::v3::Bootstrap& bootstrap) { + auto* cluster = bootstrap.mutable_static_resources()->mutable_clusters(0); + ConfigHelper::HttpProtocolOptions protocol_options = + MessageUtil::anyConvert( + (*cluster->mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"]); + *protocol_options.add_http_filters() = config; + (*cluster->mutable_typed_extension_protocol_options()) + ["envoy.extensions.upstreams.http.v3.HttpProtocolOptions"] + .PackFrom(protocol_options); + }); + } + + const HttpFilterProto getAddHeaderFilterConfig(const std::string& name, const std::string& key, + const std::string& value) { + HttpFilterProto filter_config; + filter_config.set_name(name); + auto configuration = test::integration::filters::AddHeaderFilterConfig(); + configuration.set_header_key(key); + configuration.set_header_value(value); + filter_config.mutable_typed_config()->PackFrom(configuration); + return filter_config; + } + + const HttpFilterProto getCodecFilterConfig() { + HttpFilterProto filter_config; + filter_config.set_name("envoy.filters.http.upstream_codec"); + auto configuration = envoy::extensions::filters::http::upstream_codec::v3::UpstreamCodec(); + filter_config.mutable_typed_config()->PackFrom(configuration); + return filter_config; + } + void setUpConnection(FakeHttpConnectionPtr& fake_upstream_connection) { // Start a connection, and verify the upgrade headers are received upstream. tcp_client_ = makeTcpConnection(lookupPort("tcp_proxy")); @@ -796,6 +832,22 @@ TEST_P(TcpTunnelingIntegrationTest, Basic) { closeConnection(fake_upstream_connection_); } +TEST_P(TcpTunnelingIntegrationTest, UpstreamHttpFilters) { + if (!(GetParam().tunneling_with_upstream_filters)) { + return; + } + addHttpUpstreamFilterToCluster(getAddHeaderFilterConfig("add_header", "foo", "bar")); + addHttpUpstreamFilterToCluster(getCodecFilterConfig()); + initialize(); + + setUpConnection(fake_upstream_connection_); + sendBidiData(fake_upstream_connection_); + EXPECT_EQ( + "bar", + upstream_request_->headers().get(Http::LowerCaseString("foo"))[0]->value().getStringView()); + closeConnection(fake_upstream_connection_); +} + TEST_P(TcpTunnelingIntegrationTest, SendDataUpstreamAfterUpstreamClose) { if (upstreamProtocol() == Http::CodecType::HTTP1) { // HTTP/1.1 can't frame with FIN bits. diff --git a/test/integration/xfcc_integration_test.cc b/test/integration/xfcc_integration_test.cc index 689611d5345a..2db728c8a78f 100644 --- a/test/integration/xfcc_integration_test.cc +++ b/test/integration/xfcc_integration_test.cc @@ -12,9 +12,10 @@ #include "source/common/event/dispatcher_impl.h" #include "source/common/http/header_map_impl.h" #include "source/common/network/utility.h" +#include "source/common/tls/client_ssl_socket.h" #include "source/common/tls/context_config_impl.h" #include "source/common/tls/context_manager_impl.h" -#include "source/common/tls/ssl_socket.h" +#include "source/common/tls/server_ssl_socket.h" #include "test/test_common/network_utility.h" #include "test/test_common/printers.h" diff --git a/test/mocks/server/options.h b/test/mocks/server/options.h index 64f3d72b8b26..124ba5c044b4 100644 --- a/test/mocks/server/options.h +++ b/test/mocks/server/options.h @@ -18,6 +18,7 @@ class MockOptions : public Options { MOCK_METHOD(uint64_t, baseId, (), (const)); MOCK_METHOD(bool, useDynamicBaseId, (), (const)); MOCK_METHOD(bool, skipHotRestartOnNoParent, (), (const)); + MOCK_METHOD(bool, skipHotRestartParentStats, (), (const)); MOCK_METHOD(const std::string&, baseIdPath, (), (const)); MOCK_METHOD(uint32_t, concurrency, (), (const)); MOCK_METHOD(const std::string&, configPath, (), (const)); diff --git a/test/mocks/stream_info/mocks.cc b/test/mocks/stream_info/mocks.cc index 2db8ffc73631..783fba886a9e 100644 --- a/test/mocks/stream_info/mocks.cc +++ b/test/mocks/stream_info/mocks.cc @@ -43,6 +43,11 @@ MockUpstreamInfo::MockUpstreamInfo() Invoke([this](const Network::Address::InstanceConstSharedPtr& upstream_local_address) { upstream_local_address_ = upstream_local_address; })); + ON_CALL(*this, setUpstreamRemoteAddress(_)) + .WillByDefault( + Invoke([this](const Network::Address::InstanceConstSharedPtr& upstream_remote_address) { + upstream_remote_address_ = upstream_remote_address; + })); ON_CALL(*this, upstreamLocalAddress()).WillByDefault(ReturnRef(upstream_local_address_)); ON_CALL(*this, setUpstreamTransportFailureReason(_)) .WillByDefault(Invoke([this](absl::string_view failure_reason) { diff --git a/test/server/hot_restart_impl_test.cc b/test/server/hot_restart_impl_test.cc index 47c86aa10983..2b18c90da746 100644 --- a/test/server/hot_restart_impl_test.cc +++ b/test/server/hot_restart_impl_test.cc @@ -67,7 +67,7 @@ class HotRestartImplTest : public testing::Test { EXPECT_CALL(os_sys_calls_, bind(_, _, _)).Times(4); // Test we match the correct stat with empty-slots before, after, or both. - hot_restart_ = std::make_unique(0, 0, socket_addr_, 0, false); + hot_restart_ = std::make_unique(0, 0, socket_addr_, 0, false, false); hot_restart_->drainParentListeners(); // We close both sockets, both ends, totaling 4. @@ -133,7 +133,7 @@ TEST_P(DomainSocketErrorTest, DomainSocketAlreadyInUse) { }); EXPECT_CALL(os_sys_calls_, close(_)).Times(GetParam()); - EXPECT_THROW(std::make_unique(0, 0, socket_addr_, 0, false), + EXPECT_THROW(std::make_unique(0, 0, socket_addr_, 0, false, false), Server::HotRestartDomainSocketInUseException); } @@ -149,7 +149,8 @@ TEST_P(DomainSocketErrorTest, DomainSocketError) { }); EXPECT_CALL(os_sys_calls_, close(_)).Times(GetParam()); - EXPECT_THROW(std::make_unique(0, 0, socket_addr_, 0, false), EnvoyException); + EXPECT_THROW(std::make_unique(0, 0, socket_addr_, 0, false, false), + EnvoyException); } class HotRestartUdpForwardingContextTest : public HotRestartImplTest { diff --git a/test/server/hot_restarting_child_test.cc b/test/server/hot_restarting_child_test.cc index 7e2d332119dc..e8d983367070 100644 --- a/test/server/hot_restarting_child_test.cc +++ b/test/server/hot_restarting_child_test.cc @@ -91,8 +91,8 @@ class HotRestartingChildTest : public testing::Test { EXPECT_CALL(os_sys_calls_, bind(_, _, _)).Times(4); EXPECT_CALL(os_sys_calls_, close(_)).Times(4); fake_parent_ = std::make_unique(os_sys_calls_, 0, 0, socket_path_); - hot_restarting_child_ = - std::make_unique(0, 1, socket_path_, 0, skipHotRestartOnNoParent()); + hot_restarting_child_ = std::make_unique( + 0, 1, socket_path_, 0, skipHotRestartOnNoParent(), skipParentStats()); if (skipHotRestartOnNoParent()) { if (hotRestartIsSkipped()) { // A message is attempted to be sent to the parent and returns ECONNREFUSED. @@ -121,6 +121,7 @@ class HotRestartingChildTest : public testing::Test { std::unique_ptr hot_restarting_child_; virtual bool skipHotRestartOnNoParent() const { return false; } virtual bool hotRestartIsSkipped() const { return false; } + virtual bool skipParentStats() const { return false; } }; class HotRestartingChildWithSkipTest : public HotRestartingChildTest { diff --git a/test/server/hot_restarting_parent_test.cc b/test/server/hot_restarting_parent_test.cc index ab20778f856e..a37d124e3a51 100644 --- a/test/server/hot_restarting_parent_test.cc +++ b/test/server/hot_restarting_parent_test.cc @@ -299,7 +299,7 @@ TEST_F(HotRestartingParentTest, RetainDynamicStats) { Stats::Gauge& g2 = child_store.rootScope()->gaugeFromStatName( dynamic.add("g2"), Stats::Gauge::ImportMode::Accumulate); - HotRestartingChild hot_restarting_child(0, 0, testDomainSocketName(), 0, false); + HotRestartingChild hot_restarting_child(0, 0, testDomainSocketName(), 0, false, false); hot_restarting_child.mergeParentStats(child_store, stats_proto); EXPECT_EQ(1, c1.value()); EXPECT_EQ(1, c2.value()); diff --git a/test/server/options_impl_test.cc b/test/server/options_impl_test.cc index d406476c0389..732ab9f03ed9 100644 --- a/test/server/options_impl_test.cc +++ b/test/server/options_impl_test.cc @@ -96,6 +96,8 @@ TEST_F(OptionsImplTest, All) { "--local-address-ip-version v6 -l info --component-log-level upstream:debug,connection:trace " "--service-cluster cluster --service-node node --service-zone zone " "--file-flush-interval-msec 9000 " + "--skip-hot-restart-on-no-parent " + "--skip-hot-restart-parent-stats " "--drain-time-s 60 --log-format [%v] --enable-fine-grain-logging --parent-shutdown-time-s 90 " "--log-path " "/foo/bar " @@ -114,6 +116,8 @@ TEST_F(OptionsImplTest, All) { EXPECT_EQ(2, options->componentLogLevels().size()); EXPECT_EQ("[%v]", options->logFormat()); EXPECT_TRUE(options->logFormatSet()); + EXPECT_TRUE(options->skipHotRestartParentStats()); + EXPECT_TRUE(options->skipHotRestartOnNoParent()); EXPECT_EQ("/foo/bar", options->logPath()); EXPECT_EQ(true, options->enableFineGrainLogging()); EXPECT_EQ("cluster", options->serviceClusterName()); @@ -519,6 +523,12 @@ TEST_F(OptionsImplTest, LogFormatDefault) { EXPECT_FALSE(options->logFormatSet()); } +TEST_F(OptionsImplTest, SkipHotRestartDefaults) { + std::unique_ptr options = createOptionsImpl({"envoy", "-c", "hello"}); + EXPECT_FALSE(options->skipHotRestartOnNoParent()); + EXPECT_FALSE(options->skipHotRestartParentStats()); +} + TEST_F(OptionsImplTest, LogFormatOverride) { std::unique_ptr options = createOptionsImpl({"envoy", "-c", "hello", "--log-format", "%%v %v %t %v"}); diff --git a/tools/base/requirements.txt b/tools/base/requirements.txt index 6096eec4ad1a..a8ed738a563a 100644 --- a/tools/base/requirements.txt +++ b/tools/base/requirements.txt @@ -488,9 +488,9 @@ envoy-base-utils==0.5.1 \ # envoy-docs-sphinx-runner # envoy-github-release # envoy-gpg-sign -envoy-code-check==0.5.12 \ - --hash=sha256:6cd41acfd28874c6312db8aebb3dd8cc50a626a45639d4353c8980a8ec29cdb9 \ - --hash=sha256:b71178b1ca1b2bfc236978ff8c10f78b17329f089147cc6bca01c71ec25721d2 +envoy-code-check==0.5.13 \ + --hash=sha256:58c31be3ba1a3273eec8a76d1dcfe1a3ae5eae4730ca9d70a85fec0d641846c4 \ + --hash=sha256:6c568d477642abdf7b41a0b6a5bb21fd480d92e500c53120837a01d4436d8591 # via -r requirements.in envoy-dependency-check==0.1.12 \ --hash=sha256:4673cb4cf9c0e2c55b2a0e0b39df3b8df9993d6524c6edb9527d3c8fb1ec24e2 \ diff --git a/tools/code_format/config.yaml b/tools/code_format/config.yaml index e84caaee0ddf..5319422d1e23 100644 --- a/tools/code_format/config.yaml +++ b/tools/code_format/config.yaml @@ -163,8 +163,8 @@ paths: - source/common/grpc/google_grpc_creds_impl.cc - source/common/local_reply/local_reply.cc - source/common/tls/context_impl.cc + - source/common/tls/server_context_impl.cc - source/common/tls/context_config_impl.cc - - source/common/tls/ocsp/ocsp.cc - source/common/config/watched_directory.cc - source/server/drain_manager_impl.cc - source/common/router/rds_impl.cc @@ -413,3 +413,4 @@ visibility_excludes: - source/extensions/load_balancing_policies/random/ - source/extensions/load_balancing_policies/cluster_provided/ - source/extensions/filters/http/match_delegate/ +- source/extensions/transport_sockets/tls/ diff --git a/tools/protoprint/protoprint.py b/tools/protoprint/protoprint.py index 21ea74b8cf70..ca081ef1bb43 100644 --- a/tools/protoprint/protoprint.py +++ b/tools/protoprint/protoprint.py @@ -14,7 +14,6 @@ import os import pathlib import re -import shutil import subprocess from collections import deque from functools import cached_property diff --git a/tools/repo/BUILD b/tools/repo/BUILD index aee406f02364..bc8560d225b7 100644 --- a/tools/repo/BUILD +++ b/tools/repo/BUILD @@ -12,10 +12,13 @@ envoy_py_namespace() envoy_pytool_binary( name = "notify", srcs = ["notify.py"], + args = ["$(location //:reviewers.yaml)"], + data = ["//:reviewers.yaml"], deps = [ requirement("aio.api.github"), requirement("aio.run.runner"), requirement("icalendar"), + requirement("pyyaml"), requirement("slack_sdk"), ], ) diff --git a/tools/repo/notify.py b/tools/repo/notify.py index 8ca0e97fd3d4..d64f2bbbc418 100644 --- a/tools/repo/notify.py +++ b/tools/repo/notify.py @@ -12,11 +12,13 @@ import icalendar import json import os +import pathlib import sys from datetime import datetime as dt from functools import cached_property import aiohttp +import yaml from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.errors import SlackApiError @@ -33,71 +35,26 @@ ISSUE_LINK = "https://github.com/envoyproxy/envoy/issues?q=is%3Aissue+is%3Aopen+label%3Atriage" SLACK_EXPORT_URL = "https://api.slack.com/apps/A023NPQQ33K/oauth?" -OPSGENIE_TO_SLACK = { - 'Adi': 'UT17EMMTP', - 'Alyssa': 'U78RP48V9', - 'Greg': 'U78MBV869', - 'Harvey': 'U78E7055Z', - 'Joshua': 'U80HPLBPG', - 'Kevin': 'U016ZPU8KBK', - 'Keith': 'UGS5P90CF', - 'kuat': 'U7KTRAA8M', - 'Lizan': 'U79E51EQ6', - 'Matt': 'U5CALEVSL', - 'Kateryna': 'UDYUWRL13', - 'phlax': 'U017PLM0GNQ', - 'Raven': 'U02MJHFEX35', - 'Ryan': 'U01SW3JC8GP', - 'Hejie': 'U01GNQ3B8AY', - 'Baiping': 'U017KF5C0Q6', - 'Yan': 'UJHLR5KFS', - 'Stephan': 'U78J72Q82', -} - -MAINTAINERS = { - 'adisuissa': 'UT17EMMTP', - 'alyssawilk': 'U78RP48V9', - 'ggreenway': 'U78MBV869', - 'htuch': 'U78E7055Z', - 'jmarantz': 'U80HPLBPG', - 'KBaichoo': 'U016ZPU8KBK', - 'keith': 'UGS5P90CF', - 'kyessenov': 'U7KTRAA8M', - 'lizan': 'U79E51EQ6', - 'mattklein123': 'U5CALEVSL', - 'nezdolik': 'UDYUWRL13', - 'phlax': 'U017PLM0GNQ', - 'ravenblackx': 'U02MJHFEX35', - 'RyanTheOptimist': 'U01SW3JC8GP', - 'soulxu': 'U01GNQ3B8AY', - 'wbpcode': 'U017KF5C0Q6', - 'yanavlasov': 'UJHLR5KFS', - 'zuercher': 'U78J72Q82', -} - -# First pass reviewers who are not maintainers should get -# notifications but not result in a PR not getting assigned a -# maintainer owner. -FIRST_PASS = { - 'botengyao': 'U037YUAK147', - 'daixiang0': 'U020CJG6UU8', - 'silverstar194': 'U03LNPC8JN9', - 'tyxia': 'U023U1ZN9SP', -} - -# Only notify API reviewers who aren't maintainers. -# Maintainers are already notified of pending PRs. -API_REVIEWERS = { - 'markdroth': 'UMN8K55A6', -} - class RepoNotifier(runner.Runner): + @cached_property + def api_reviewers(self): + """Only notify API reviewers who aren't maintainers. + Maintainers are already notified of pending PRs.""" + return {k: v["slack"] for k, v in self.reviewers.items() if v.get("api")} + @property def dry_run(self): return self.args.dry_run + @cached_property + def first_pass_reviewers(self): + """First pass reviewers who are not maintainers should get + notifications but not result in a PR not getting assigned a + maintainer owner.""" + return {k: v["slack"] for k, v in self.reviewers.items() if v.get("first-pass")} + @cached_property def github(self): return github.GithubAPI(self.session, "", oauth_token=self.github_token) @@ -110,6 +67,10 @@ def github_token(self): async def maintainer_notifications(self): return (await self.tracked_prs)["maintainer_notifications"] + @cached_property + def maintainers(self): + return {k: v["slack"] for k, v in self.reviewers.items() if v.get("maintainer")} + @async_property async def pulls(self): async for pull in self.repo.getiter("pulls"): @@ -125,6 +86,10 @@ async def pulls(self): def repo(self): return self.github[ENVOY_REPO] + @cached_property + def reviewers(self): + return yaml.safe_load(pathlib.Path(self.args.reviewers).read_text()) + @cached_property def session(self): return aiohttp.ClientSession() @@ -183,11 +148,15 @@ async def oncall_slack_handle(self): # Snag the first name from the "oncall transitioning to" entry. opsgenie_name = opsgenie_string.split(' ', 1)[0] # Check that the name is in the OPSGENIE_TO_SLACK list, else cc alyssa. - if not (uid := OPSGENIE_TO_SLACK.get(opsgenie_name)): + if not (uid := self.opsgenie_to_slack.get(opsgenie_name)): print("could not find", opsgenie_name) - return OPSGENIE_TO_SLACK.get('Alyssa') + return self.opsgenie_to_slack.get('Alyssa') return uid + @cached_property + def opsgenie_to_slack(self): + return {v["opsgenie"]: v["slack"] for v in self.reviewers.values() if "opsgenie" in v} + @async_property(cache=True) async def tracked_prs(self): # A dict of maintainer : outstanding_pr_string to be sent to slack @@ -206,7 +175,7 @@ async def tracked_prs(self): message = self.pr_message(age, pull) if await self.needs_api_review(pull): - for assignee in self.get_assignees(pull, API_REVIEWERS): + for assignee in self.get_assignees(pull, self.api_reviewers): api_review[assignee["login"]] = api_review.get( assignee["login"], [f"Hello, {assignee['login']}, here are your PR reminders for the day"]) @@ -217,8 +186,9 @@ async def tracked_prs(self): stalled_prs.append(message) has_maintainer = False - for assignee in self.get_assignees(pull, {**MAINTAINERS, **FIRST_PASS}): - if MAINTAINERS.get(assignee["login"]): + assignees = self.get_assignees(pull, {**self.maintainers, **self.first_pass_reviewers}) + for assignee in assignees: + if self.maintainers.get(assignee["login"]): has_maintainer = True maintainers_and_prs[assignee["login"]] = maintainers_and_prs.get( assignee["login"], []) @@ -239,6 +209,7 @@ async def unassigned_prs(self): def add_arguments(self, parser) -> None: super().add_arguments(parser) + parser.add_argument('reviewers', help="YAML reviewer config") parser.add_argument( '--dry_run', action="store_true", @@ -277,9 +248,9 @@ async def notify(self): await self.post_to_assignees() async def post_to_assignees(self): - review_notifications = ((API_REVIEWERS, await self.shepherd_notifications), - (MAINTAINERS, await self.maintainer_notifications), - (FIRST_PASS, await self.maintainer_notifications)) + review_notifications = ((self.api_reviewers, await self.shepherd_notifications), + (self.maintainers, await self.maintainer_notifications), + (self.first_pass_reviewers, await self.maintainer_notifications)) for assignees, messages in review_notifications: await self._post_to_assignees(assignees, messages)